Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions examples/oft-solana/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
- Rust `1.84.1`
- Anchor `0.31.1`
- Solana CLI `2.2.20`
- Surfpool CLI (required for `pnpm test:anchor`)
- Docker `28.3.0`
- Node.js `>=20.19.5`
- `pnpm` (recommended) - or another package manager of your choice (npm, yarn)
Expand Down Expand Up @@ -360,6 +361,24 @@ Before deploying, ensure the following:
pnpm test
```

To run the Surfpool-backed Solana tests:

```bash
pnpm test:anchor
```

`pnpm test:anchor` starts a Surfnet forked from mainnet-beta by default. If mainnet-beta state blocks initialization (pre-existing PDAs), set a devnet upstream instead:

```bash
SURFPOOL_RPC_URL=https://api.devnet.solana.com pnpm test:anchor
```

To avoid upstream state entirely, deploy local LayerZero program binaries into Surfnet:

```bash
SURFPOOL_USE_LOCAL_PROGRAMS=1 pnpm test:anchor
```

### Adding other chains

To add additional chains to your OFT deployment:
Expand Down
13 changes: 6 additions & 7 deletions examples/oft-solana/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,8 @@
"lint:js": "eslint '**/*.{js,ts,json}' && prettier --check .",
"lint:sol": "solhint 'contracts/**/*.sol'",
"test": "$npm_execpath run test:forge && $npm_execpath run test:hardhat",
"test:anchor": "$npm_execpath run test:generate-features && OFT_ID=9UovNrJD8pQyBLheeHNayuG1wJSEAoxkmM14vw5gcsTT anchor build --no-idl && $npm_execpath exec ts-mocha -b -p ./tsconfig.json -t 10000000 test/anchor/index.test.ts",
"test:anchor": "OFT_ID=$(node scripts/resolve-oft-id.js) anchor build --no-idl && $npm_execpath exec ts-mocha -b -p ./tsconfig.json -t 10000000 test/anchor/index.test.ts",
"test:forge": "forge test",
"test:generate-features": "ts-node scripts/generate-features.ts",
"test:hardhat": "hardhat test",
"test:scripts": "jest --config jest.config.ts --runInBand --testMatch \"**/*.script.test.ts\""
},
Expand All @@ -24,11 +23,6 @@
"ethers": "^5.7.2",
"hardhat-deploy": "^0.12.1"
},
"overrides": {
"@solana/web3.js": "^1.98.0",
"ethers": "^5.7.2",
"hardhat-deploy": "^0.12.1"
},
"devDependencies": {
"@coral-xyz/anchor": "^0.31.1",
"@ethersproject/abi": "^5.7.0",
Expand Down Expand Up @@ -124,5 +118,10 @@
"ethers": "^5.7.2",
"hardhat-deploy": "^0.12.1"
}
},
"overrides": {
"@solana/web3.js": "^1.98.0",
"ethers": "^5.7.2",
"hardhat-deploy": "^0.12.1"
}
}
92 changes: 0 additions & 92 deletions examples/oft-solana/scripts/generate-features.ts

This file was deleted.

28 changes: 28 additions & 0 deletions examples/oft-solana/scripts/resolve-oft-id.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
#!/usr/bin/env node
'use strict';

// This script is used specifically in the `test:anchor` npm script to resolve
// the OFT program ID and output it to stdout. The test/anchor/constants.ts file
// has its own implementation since it needs the value at TypeScript import time.

const fs = require('fs');

const { Keypair } = require('@solana/web3.js');

const keypairPath = 'target/deploy/oft-keypair.json';
const fallbackId = '9UovNrJD8pQyBLheeHNayuG1wJSEAoxkmM14vw5gcsTT';

function resolveOftId() {
try {
if (!fs.existsSync(keypairPath)) {
return fallbackId;
}

const secret = JSON.parse(fs.readFileSync(keypairPath, 'utf8'));
return Keypair.fromSecretKey(Uint8Array.from(secret)).publicKey.toBase58();
} catch (error) {
return fallbackId;
}
}

process.stdout.write(resolveOftId());
54 changes: 38 additions & 16 deletions examples/oft-solana/test/anchor/constants.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
import fs from 'fs'
import path from 'path'

import { publicKey } from '@metaplex-foundation/umi'
import { utils } from '@noble/secp256k1'
import { Keypair } from '@solana/web3.js'

import { UMI } from '@layerzerolabs/lz-solana-sdk-v2'

Expand All @@ -8,26 +12,44 @@ export const DST_EID = 50125
export const INVALID_EID = 999999 // Non-existent EID for testing
export const TON_EID = 50343

export const OFT_PROGRAM_ID = publicKey('9UovNrJD8pQyBLheeHNayuG1wJSEAoxkmM14vw5gcsTT')
const DEFAULT_OFT_KEYPAIR = path.resolve(process.cwd(), 'target/deploy/oft-keypair.json')
const OFT_PROGRAM_ID_VALUE =
process.env.OFT_ID ?? readKeypairPublicKey(DEFAULT_OFT_KEYPAIR) ?? '9UovNrJD8pQyBLheeHNayuG1wJSEAoxkmM14vw5gcsTT'
const ENDPOINT_PROGRAM_ID_VALUE = process.env.LZ_ENDPOINT_PROGRAM_ID ?? '76y77prsiCMvXMjuoZ5VRrhG5qYBrUMYTE5WgHqgjEn6'
const ULN_PROGRAM_ID_VALUE = process.env.LZ_ULN_PROGRAM_ID ?? '7a4WjyR8VZ7yZz5XJAKm39BUGn5iT9CKcv2pmG9tdXVH'
const EXECUTOR_PROGRAM_ID_VALUE = process.env.LZ_EXECUTOR_PROGRAM_ID ?? '6doghB248px58JSSwG4qejQ46kFMW4AMj7vzJnWZHNZn'
const PRICEFEED_PROGRAM_ID_VALUE = process.env.LZ_PRICEFEED_PROGRAM_ID ?? '8ahPGPjEbpgGaZx2NV1iG5Shj7TDwvsjkEDcGWjt94TP'
const DVN_PROGRAM_IDS_VALUE = (process.env.LZ_DVN_PROGRAM_IDS ?? 'HtEYV4xB4wvsj5fgTkcfuChYpvGYzgzwvNhgDZQNh7wW')
.split(',')
.map((value) => value.trim())
.filter(Boolean)

export const OFT_PROGRAM_ID = publicKey(OFT_PROGRAM_ID_VALUE)

export const DVN_SIGNERS = new Array(4).fill(0).map(() => utils.randomPrivateKey())

export const OFT_DECIMALS = 6

export const defaultMultiplierBps = 12500 // 125%

export const simpleMessageLib: UMI.SimpleMessageLibProgram.SimpleMessageLib =
new UMI.SimpleMessageLibProgram.SimpleMessageLib(UMI.SimpleMessageLibProgram.SIMPLE_MESSAGELIB_PROGRAM_ID)

export const endpoint: UMI.EndpointProgram.Endpoint = new UMI.EndpointProgram.Endpoint(
UMI.EndpointProgram.ENDPOINT_PROGRAM_ID
)
export const uln: UMI.UlnProgram.Uln = new UMI.UlnProgram.Uln(UMI.UlnProgram.ULN_PROGRAM_ID)
export const executor: UMI.ExecutorProgram.Executor = new UMI.ExecutorProgram.Executor(
UMI.ExecutorProgram.EXECUTOR_PROGRAM_ID
)
export const priceFeed: UMI.PriceFeedProgram.PriceFeed = new UMI.PriceFeedProgram.PriceFeed(
UMI.PriceFeedProgram.PRICEFEED_PROGRAM_ID
)

export const dvns = [publicKey('HtEYV4xB4wvsj5fgTkcfuChYpvGYzgzwvNhgDZQNh7wW')]
export const endpoint: UMI.EndpointProgram.Endpoint = new UMI.EndpointProgram.Endpoint(ENDPOINT_PROGRAM_ID_VALUE)
export const uln: UMI.UlnProgram.Uln = new UMI.UlnProgram.Uln(ULN_PROGRAM_ID_VALUE)
export const executor: UMI.ExecutorProgram.Executor = new UMI.ExecutorProgram.Executor(EXECUTOR_PROGRAM_ID_VALUE)
export const priceFeed: UMI.PriceFeedProgram.PriceFeed = new UMI.PriceFeedProgram.PriceFeed(PRICEFEED_PROGRAM_ID_VALUE)

export const dvns = DVN_PROGRAM_IDS_VALUE.map((value) => publicKey(value))

function readKeypairPublicKey(keypairPath: string): string | undefined {
if (!fs.existsSync(keypairPath)) {
return undefined
}

try {
const secret = JSON.parse(fs.readFileSync(keypairPath, 'utf-8')) as number[]
const keypair = Keypair.fromSecretKey(Uint8Array.from(secret))
return keypair.publicKey.toBase58()
} catch (error) {
console.warn(`Failed to read keypair at ${keypairPath}: ${String(error)}`)
return undefined
}
}
52 changes: 52 additions & 0 deletions examples/oft-solana/test/anchor/got-shim.cjs
Original file line number Diff line number Diff line change
@@ -1,7 +1,59 @@
const fs = require('fs');
const path = require('path');
const Module = require('module');
const { Keypair } = require('@solana/web3.js');

const originalLoad = Module._load;

const ROOT_DIR = path.join(__dirname, '..', '..');
const KEYPAIR_DIR = path.join(ROOT_DIR, 'target', 'surfnet-programs');

const USE_LOCAL_PROGRAMS = process.env.SURFPOOL_USE_LOCAL_PROGRAMS === '1';
process.env.SURFPOOL_USE_LOCAL_PROGRAMS = USE_LOCAL_PROGRAMS ? '1' : '0';

if (USE_LOCAL_PROGRAMS) {
if (!process.env.SURFPOOL_OFFLINE) {
process.env.SURFPOOL_OFFLINE = '1';
}
if (!fs.existsSync(KEYPAIR_DIR)) {
fs.mkdirSync(KEYPAIR_DIR, { recursive: true });
}

const programKeypairs = [
{ name: 'endpoint', envId: 'LZ_ENDPOINT_PROGRAM_ID', envKeypair: 'LZ_ENDPOINT_PROGRAM_KEYPAIR' },
{ name: 'uln', envId: 'LZ_ULN_PROGRAM_ID', envKeypair: 'LZ_ULN_PROGRAM_KEYPAIR' },
{ name: 'executor', envId: 'LZ_EXECUTOR_PROGRAM_ID', envKeypair: 'LZ_EXECUTOR_PROGRAM_KEYPAIR' },
{ name: 'pricefeed', envId: 'LZ_PRICEFEED_PROGRAM_ID', envKeypair: 'LZ_PRICEFEED_PROGRAM_KEYPAIR' },
{ name: 'dvn', envId: 'LZ_DVN_PROGRAM_ID', envKeypair: 'LZ_DVN_PROGRAM_KEYPAIR' },
];

const dvnIds = [];
programKeypairs.forEach((program) => {
const keypairPath = path.join(KEYPAIR_DIR, `${program.name}-keypair.json`);

if (!fs.existsSync(keypairPath)) {
const keypair = Keypair.generate();
fs.writeFileSync(keypairPath, JSON.stringify(Array.from(keypair.secretKey)));
if (!process.env[program.envId]) {
process.env[program.envId] = keypair.publicKey.toBase58();
}
} else if (!process.env[program.envId]) {
const secret = JSON.parse(fs.readFileSync(keypairPath, 'utf-8'));
const keypair = Keypair.fromSecretKey(Uint8Array.from(secret));
process.env[program.envId] = keypair.publicKey.toBase58();
}

process.env[program.envKeypair] = keypairPath;
if (program.name === 'dvn') {
dvnIds.push(process.env[program.envId]);
}
});

if (!process.env.LZ_DVN_PROGRAM_IDS && dvnIds.length) {
process.env.LZ_DVN_PROGRAM_IDS = dvnIds.join(',');
}
}

const gotStub = {
default: Object.assign(
async () => {
Expand Down
Loading
Loading