@layerzerolabs/protocol-stellar-v2 0.2.11 → 0.2.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +202 -194
- package/.turbo/turbo-lint.log +38 -38
- package/.turbo/turbo-test.log +891 -891
- package/Cargo.lock +1 -1
- package/contracts/common-macros/src/lib.rs +3 -36
- package/contracts/endpoint-v2/src/endpoint_v2.rs +4 -4
- package/contracts/endpoint-v2/src/events.rs +40 -22
- package/contracts/endpoint-v2/src/interfaces/message_lib.rs +2 -2
- package/contracts/endpoint-v2/src/interfaces/message_lib_manager.rs +2 -2
- package/contracts/endpoint-v2/src/interfaces/messaging_channel.rs +2 -2
- package/contracts/endpoint-v2/src/interfaces/messaging_composer.rs +2 -2
- package/contracts/endpoint-v2/src/interfaces/send_lib.rs +2 -2
- package/contracts/endpoint-v2/src/message_lib_manager.rs +3 -3
- package/contracts/endpoint-v2/src/messaging_channel.rs +1 -1
- package/contracts/endpoint-v2/src/messaging_composer.rs +1 -1
- package/contracts/endpoint-v2/src/tests/message_lib_manager/set_default_receive_lib_timeout.rs +4 -8
- package/contracts/endpoint-v2/src/tests/message_lib_manager/set_default_receive_library.rs +3 -7
- package/contracts/message-libs/{block-message-lib → blocked-message-lib}/Cargo.toml +1 -1
- package/contracts/message-libs/treasury/src/events.rs +9 -6
- package/contracts/message-libs/uln-302/src/events.rs +19 -11
- package/contracts/message-libs/uln-302/src/interfaces/receive_uln.rs +2 -2
- package/contracts/message-libs/uln-302/src/interfaces/send_uln.rs +2 -2
- package/contracts/message-libs/uln-302/src/receive_uln.rs +2 -2
- package/contracts/message-libs/uln-302/src/send_uln.rs +3 -3
- package/contracts/message-libs/uln-302/src/tests/receive_uln302/set_default_receive_uln_configs.rs +5 -5
- package/contracts/message-libs/uln-302/src/tests/send_uln302/set_default_send_uln_configs.rs +5 -5
- package/contracts/message-libs/uln-302/src/tests/setup.rs +3 -3
- package/contracts/message-libs/uln-302/src/types.rs +24 -24
- package/contracts/message-libs/uln-302/src/uln302.rs +1 -1
- package/contracts/oapps/counter/integration_tests/utils.rs +1 -1
- package/contracts/oapps/oapp/src/oapp_core.rs +4 -3
- package/contracts/oapps/oapp/src/oapp_options_type3.rs +4 -3
- package/contracts/oapps/oft/integration-tests/utils.rs +1 -1
- package/contracts/oapps/oft/src/events.rs +5 -4
- package/contracts/oapps/oft/src/extensions/oft_fee.rs +10 -6
- package/contracts/oapps/oft/src/extensions/pausable.rs +4 -4
- package/contracts/oapps/oft/src/extensions/rate_limiter.rs +8 -6
- package/contracts/utils/src/ownable.rs +6 -4
- package/contracts/utils/src/tests/testing_utils.rs +7 -5
- package/contracts/utils/src/ttl.rs +5 -4
- package/contracts/workers/dvn/src/auth.rs +59 -45
- package/contracts/workers/dvn/src/dvn.rs +84 -16
- package/contracts/workers/dvn/src/errors.rs +10 -13
- package/contracts/workers/dvn/src/events.rs +7 -5
- package/contracts/workers/dvn/src/interfaces/dvn.rs +29 -1
- package/contracts/workers/dvn/src/multisig.rs +94 -71
- package/contracts/workers/dvn/src/storage.rs +9 -12
- package/contracts/workers/dvn/src/tests/auth.rs +56 -26
- package/contracts/workers/dvn/src/tests/dvn.rs +37 -37
- package/contracts/workers/dvn/src/tests/multisig/set_signer.rs +8 -8
- package/contracts/workers/dvn/src/tests/multisig/set_threshold.rs +9 -9
- package/contracts/workers/dvn/src/tests/multisig/verify_signatures.rs +6 -6
- package/contracts/workers/dvn/src/tests/setup.rs +5 -5
- package/contracts/workers/executor/src/auth.rs +93 -0
- package/contracts/workers/executor/src/events.rs +5 -4
- package/contracts/workers/executor/src/{lz_executor.rs → executor.rs} +25 -98
- package/contracts/workers/executor/src/interfaces/mod.rs +1 -1
- package/contracts/workers/executor/src/lib.rs +6 -5
- package/contracts/workers/worker/src/events.rs +23 -13
- package/contracts/workers/worker/src/worker.rs +32 -21
- package/package.json +3 -3
- package/sdk/dist/generated/bml.js +23 -23
- package/sdk/dist/generated/counter.js +25 -25
- package/sdk/dist/generated/endpoint.js +23 -23
- package/sdk/dist/generated/sml.js +23 -23
- package/sdk/dist/generated/uln302.d.ts +1 -1
- package/sdk/dist/generated/uln302.js +33 -33
- package/sdk/package.json +1 -1
- package/sdk/test/index.test.ts +1 -1
- package/sdk/test/oft.test.ts +847 -0
- package/sdk/test/suites/scan.ts +20 -4
- package/tools/ts-bindings-gen/src/main.rs +2 -1
- package/contracts/common-macros/src/event.rs +0 -16
- /package/contracts/message-libs/{block-message-lib → blocked-message-lib}/src/lib.rs +0 -0
|
@@ -0,0 +1,847 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Address,
|
|
3
|
+
Asset,
|
|
4
|
+
BASE_FEE,
|
|
5
|
+
Contract,
|
|
6
|
+
Keypair,
|
|
7
|
+
nativeToScVal,
|
|
8
|
+
Operation,
|
|
9
|
+
rpc,
|
|
10
|
+
scValToNative,
|
|
11
|
+
StrKey,
|
|
12
|
+
TransactionBuilder,
|
|
13
|
+
} from '@stellar/stellar-sdk';
|
|
14
|
+
import path from 'path';
|
|
15
|
+
import { afterAll, beforeAll, describe, expect, it } from 'vitest';
|
|
16
|
+
import { $ } from 'zx';
|
|
17
|
+
|
|
18
|
+
import { getFullyQualifiedRepoRootPath } from '@layerzerolabs/common-node-utils';
|
|
19
|
+
import { PacketSerializer, PacketV1Codec } from '@layerzerolabs/lz-v2-utilities';
|
|
20
|
+
|
|
21
|
+
import { Client as EndpointClient } from '../src/generated/endpoint';
|
|
22
|
+
import { Client as ExecutorClient } from '../src/generated/executor';
|
|
23
|
+
import { Client as ExecutorHelperClient } from '../src/generated/executor_helper';
|
|
24
|
+
import { Client as OFTStdClient, SendParam } from '../src/generated/oft_std';
|
|
25
|
+
import { Client as SMLClient } from '../src/generated/sml';
|
|
26
|
+
import {
|
|
27
|
+
DEFAULT_DEPLOYER,
|
|
28
|
+
EID,
|
|
29
|
+
NATIVE_TOKEN_ADDRESS,
|
|
30
|
+
NETWORK_PASSPHRASE,
|
|
31
|
+
RPC_URL,
|
|
32
|
+
ZRO_TOKEN_ADDRESS,
|
|
33
|
+
} from './suites/constants';
|
|
34
|
+
import { deployAssetSac, deployContract } from './suites/deploy';
|
|
35
|
+
import { fundAccount, startStellarLocalnet, stopStellarLocalnet } from './suites/localnet';
|
|
36
|
+
import { PacketSentEvent, scanPacketSentEvents } from './suites/scan';
|
|
37
|
+
import { assertTransactionsSucceeded, signExecutorAuthEntries } from './utils';
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Helper to get SAC token balance for an address
|
|
41
|
+
*/
|
|
42
|
+
async function getSacBalance(tokenAddress: string, accountAddress: string): Promise<bigint> {
|
|
43
|
+
const server = new rpc.Server(RPC_URL, { allowHttp: true });
|
|
44
|
+
const tokenContract = new Contract(tokenAddress);
|
|
45
|
+
|
|
46
|
+
// Build the balance call
|
|
47
|
+
const balanceOp = tokenContract.call(
|
|
48
|
+
'balance',
|
|
49
|
+
nativeToScVal(Address.fromString(accountAddress), { type: 'address' }),
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
const account = await server.getAccount(DEFAULT_DEPLOYER.publicKey());
|
|
53
|
+
const tx = new TransactionBuilder(account, {
|
|
54
|
+
fee: BASE_FEE,
|
|
55
|
+
networkPassphrase: NETWORK_PASSPHRASE,
|
|
56
|
+
})
|
|
57
|
+
.addOperation(balanceOp)
|
|
58
|
+
.setTimeout(30)
|
|
59
|
+
.build();
|
|
60
|
+
|
|
61
|
+
const simulated = await server.simulateTransaction(tx);
|
|
62
|
+
if (rpc.Api.isSimulationError(simulated)) {
|
|
63
|
+
throw new Error(`Balance query failed: ${JSON.stringify(simulated)}`);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Extract result from simulation
|
|
67
|
+
const result = (simulated as rpc.Api.SimulateTransactionSuccessResponse).result;
|
|
68
|
+
if (result?.retval) {
|
|
69
|
+
return scValToNative(result.retval) as bigint;
|
|
70
|
+
}
|
|
71
|
+
return 0n;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
$.verbose = true;
|
|
75
|
+
$.stdio = ['inherit', 'pipe', process.stderr];
|
|
76
|
+
|
|
77
|
+
// Contract addresses
|
|
78
|
+
const CONTRACT_ADDRESSES = {
|
|
79
|
+
endpointV2: '',
|
|
80
|
+
sml: '',
|
|
81
|
+
executor: '',
|
|
82
|
+
executorHelper: '',
|
|
83
|
+
oftToken: '', // The SAC for OFT token
|
|
84
|
+
lockUnlockOft: '',
|
|
85
|
+
mintBurnOft: '',
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
// Clients
|
|
89
|
+
let endpointClient: EndpointClient;
|
|
90
|
+
let smlClient: SMLClient;
|
|
91
|
+
let executorClient: ExecutorClient;
|
|
92
|
+
let executorHelperClient: ExecutorHelperClient;
|
|
93
|
+
let lockUnlockOftClient: OFTStdClient;
|
|
94
|
+
let mintBurnOftClient: OFTStdClient;
|
|
95
|
+
|
|
96
|
+
// Test accounts
|
|
97
|
+
const EXECUTOR_ADMIN = Keypair.random();
|
|
98
|
+
const TOKEN_ISSUER = Keypair.random();
|
|
99
|
+
// Use DEFAULT_DEPLOYER as SENDER (same pattern as Counter test)
|
|
100
|
+
// The deployer has proper signing setup with the contract clients
|
|
101
|
+
|
|
102
|
+
// Recipients for each direction
|
|
103
|
+
const RECIPIENT_A = Keypair.random(); // Receives tokens in Lock/Unlock -> Mint/Burn direction
|
|
104
|
+
const RECIPIENT_B = Keypair.random(); // Receives tokens in Mint/Burn -> Lock/Unlock direction
|
|
105
|
+
|
|
106
|
+
// OFT Token asset (custom token for testing)
|
|
107
|
+
const OFT_TOKEN_CODE = 'OFT';
|
|
108
|
+
let OFT_ASSET: Asset;
|
|
109
|
+
|
|
110
|
+
// Constants
|
|
111
|
+
const SHARED_DECIMALS = 6;
|
|
112
|
+
const INITIAL_TOKEN_AMOUNT = '1000'; // 1000 tokens
|
|
113
|
+
const SEND_AMOUNT = 100_0000000n; // 100 tokens in local decimals (7 decimals)
|
|
114
|
+
|
|
115
|
+
// NOTE: run `stellar contract build` before running the test
|
|
116
|
+
|
|
117
|
+
describe('OFT E2E Testing with SAC', async () => {
|
|
118
|
+
const repoRoot = await getFullyQualifiedRepoRootPath();
|
|
119
|
+
const wasmDir = path.join(
|
|
120
|
+
repoRoot,
|
|
121
|
+
'contracts',
|
|
122
|
+
'protocol',
|
|
123
|
+
'stellar',
|
|
124
|
+
'target',
|
|
125
|
+
'wasm32v1-none',
|
|
126
|
+
'release',
|
|
127
|
+
);
|
|
128
|
+
|
|
129
|
+
const ENDPOINT_WASM_PATH = path.join(wasmDir, 'endpoint_v2.wasm');
|
|
130
|
+
const SML_WASM_PATH = path.join(wasmDir, 'simple_message_lib.wasm');
|
|
131
|
+
const EXECUTOR_WASM_PATH = path.join(wasmDir, 'executor.wasm');
|
|
132
|
+
const EXECUTOR_HELPER_WASM_PATH = path.join(wasmDir, 'executor_helper.wasm');
|
|
133
|
+
const OFT_STD_WASM_PATH = path.join(wasmDir, 'oft_std.wasm');
|
|
134
|
+
|
|
135
|
+
beforeAll(async () => {
|
|
136
|
+
await startStellarLocalnet();
|
|
137
|
+
// Fund test accounts
|
|
138
|
+
await fundAccount(EXECUTOR_ADMIN.publicKey());
|
|
139
|
+
await fundAccount(TOKEN_ISSUER.publicKey());
|
|
140
|
+
await fundAccount(RECIPIENT_A.publicKey());
|
|
141
|
+
await fundAccount(RECIPIENT_B.publicKey());
|
|
142
|
+
|
|
143
|
+
// Create the OFT asset (TOKEN_ISSUER is the issuer)
|
|
144
|
+
OFT_ASSET = new Asset(OFT_TOKEN_CODE, TOKEN_ISSUER.publicKey());
|
|
145
|
+
}, 120000); // 2 minute timeout for setup
|
|
146
|
+
|
|
147
|
+
afterAll(async () => {
|
|
148
|
+
await stopStellarLocalnet();
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
describe('Contract Deployments', () => {
|
|
152
|
+
it('Deploy Endpoint', async () => {
|
|
153
|
+
endpointClient = await deployContract<EndpointClient>(
|
|
154
|
+
EndpointClient,
|
|
155
|
+
ENDPOINT_WASM_PATH,
|
|
156
|
+
{
|
|
157
|
+
eid: EID,
|
|
158
|
+
owner: DEFAULT_DEPLOYER.publicKey(),
|
|
159
|
+
native_token: NATIVE_TOKEN_ADDRESS,
|
|
160
|
+
},
|
|
161
|
+
DEFAULT_DEPLOYER,
|
|
162
|
+
);
|
|
163
|
+
|
|
164
|
+
CONTRACT_ADDRESSES.endpointV2 = endpointClient.options.contractId;
|
|
165
|
+
console.log('✅ Endpoint deployed:', CONTRACT_ADDRESSES.endpointV2);
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
it('Deploy SimpleMessageLib', async () => {
|
|
169
|
+
smlClient = await deployContract<SMLClient>(
|
|
170
|
+
SMLClient,
|
|
171
|
+
SML_WASM_PATH,
|
|
172
|
+
{
|
|
173
|
+
owner: DEFAULT_DEPLOYER.publicKey(),
|
|
174
|
+
endpoint: CONTRACT_ADDRESSES.endpointV2,
|
|
175
|
+
fee_recipient: DEFAULT_DEPLOYER.publicKey(),
|
|
176
|
+
},
|
|
177
|
+
DEFAULT_DEPLOYER,
|
|
178
|
+
);
|
|
179
|
+
|
|
180
|
+
CONTRACT_ADDRESSES.sml = smlClient.options.contractId;
|
|
181
|
+
console.log('✅ SimpleMessageLib deployed:', CONTRACT_ADDRESSES.sml);
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
it('Deploy Executor Helper', async () => {
|
|
185
|
+
executorHelperClient = await deployContract<ExecutorHelperClient>(
|
|
186
|
+
ExecutorHelperClient,
|
|
187
|
+
EXECUTOR_HELPER_WASM_PATH,
|
|
188
|
+
undefined,
|
|
189
|
+
DEFAULT_DEPLOYER,
|
|
190
|
+
);
|
|
191
|
+
CONTRACT_ADDRESSES.executorHelper = executorHelperClient.options.contractId;
|
|
192
|
+
console.log('✅ Executor Helper deployed:', CONTRACT_ADDRESSES.executorHelper);
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
it('Deploy Executor', async () => {
|
|
196
|
+
const whitelist = [
|
|
197
|
+
{ contract: CONTRACT_ADDRESSES.executorHelper, fn_name: 'native_drop_and_execute' },
|
|
198
|
+
{ contract: CONTRACT_ADDRESSES.executorHelper, fn_name: 'execute' },
|
|
199
|
+
{ contract: CONTRACT_ADDRESSES.executorHelper, fn_name: 'compose' },
|
|
200
|
+
{ contract: CONTRACT_ADDRESSES.executorHelper, fn_name: 'native_drop' },
|
|
201
|
+
];
|
|
202
|
+
|
|
203
|
+
executorClient = await deployContract<ExecutorClient>(
|
|
204
|
+
ExecutorClient,
|
|
205
|
+
EXECUTOR_WASM_PATH,
|
|
206
|
+
{
|
|
207
|
+
owner: DEFAULT_DEPLOYER.publicKey(),
|
|
208
|
+
endpoint: CONTRACT_ADDRESSES.endpointV2,
|
|
209
|
+
whitelist,
|
|
210
|
+
admins: [EXECUTOR_ADMIN.publicKey()],
|
|
211
|
+
message_libs: [CONTRACT_ADDRESSES.sml],
|
|
212
|
+
// FIXME: Add price feed
|
|
213
|
+
price_feed: CONTRACT_ADDRESSES.endpointV2,
|
|
214
|
+
default_multiplier_bps: 10000,
|
|
215
|
+
},
|
|
216
|
+
DEFAULT_DEPLOYER,
|
|
217
|
+
);
|
|
218
|
+
CONTRACT_ADDRESSES.executor = executorClient.options.contractId;
|
|
219
|
+
console.log('✅ Executor deployed:', CONTRACT_ADDRESSES.executor);
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
it('Deploy OFT Token SAC', async () => {
|
|
223
|
+
const server = new rpc.Server(RPC_URL, { allowHttp: true });
|
|
224
|
+
|
|
225
|
+
// Step 1: Issue the OFT token to DEFAULT_DEPLOYER and set up trustlines for recipients
|
|
226
|
+
const issuerAccount = await server.getAccount(TOKEN_ISSUER.publicKey());
|
|
227
|
+
const issueTx = new TransactionBuilder(issuerAccount, {
|
|
228
|
+
fee: BASE_FEE,
|
|
229
|
+
networkPassphrase: NETWORK_PASSPHRASE,
|
|
230
|
+
})
|
|
231
|
+
// Trustline for DEFAULT_DEPLOYER (sender)
|
|
232
|
+
.addOperation(
|
|
233
|
+
Operation.changeTrust({
|
|
234
|
+
asset: OFT_ASSET,
|
|
235
|
+
source: DEFAULT_DEPLOYER.publicKey(),
|
|
236
|
+
}),
|
|
237
|
+
)
|
|
238
|
+
// Trustline for RECIPIENT_A (receives minted tokens)
|
|
239
|
+
.addOperation(
|
|
240
|
+
Operation.changeTrust({
|
|
241
|
+
asset: OFT_ASSET,
|
|
242
|
+
source: RECIPIENT_A.publicKey(),
|
|
243
|
+
}),
|
|
244
|
+
)
|
|
245
|
+
// Trustline for RECIPIENT_B (receives unlocked tokens)
|
|
246
|
+
.addOperation(
|
|
247
|
+
Operation.changeTrust({
|
|
248
|
+
asset: OFT_ASSET,
|
|
249
|
+
source: RECIPIENT_B.publicKey(),
|
|
250
|
+
}),
|
|
251
|
+
)
|
|
252
|
+
// Issue tokens to DEFAULT_DEPLOYER
|
|
253
|
+
.addOperation(
|
|
254
|
+
Operation.payment({
|
|
255
|
+
asset: OFT_ASSET,
|
|
256
|
+
amount: INITIAL_TOKEN_AMOUNT,
|
|
257
|
+
destination: DEFAULT_DEPLOYER.publicKey(),
|
|
258
|
+
}),
|
|
259
|
+
)
|
|
260
|
+
.setTimeout(30)
|
|
261
|
+
.build();
|
|
262
|
+
|
|
263
|
+
issueTx.sign(TOKEN_ISSUER, DEFAULT_DEPLOYER, RECIPIENT_A, RECIPIENT_B);
|
|
264
|
+
const sendResult = await server.sendTransaction(issueTx);
|
|
265
|
+
if (sendResult.status !== 'PENDING') {
|
|
266
|
+
throw new Error(`Failed to issue OFT token: ${JSON.stringify(sendResult)}`);
|
|
267
|
+
}
|
|
268
|
+
const txResult = await server.pollTransaction(sendResult.hash);
|
|
269
|
+
if (txResult.status !== 'SUCCESS') {
|
|
270
|
+
throw new Error(`Failed to issue OFT token: ${JSON.stringify(txResult)}`);
|
|
271
|
+
}
|
|
272
|
+
console.log('✅ OFT token issued to DEFAULT_DEPLOYER');
|
|
273
|
+
|
|
274
|
+
// Step 2: Deploy the SAC for the OFT token
|
|
275
|
+
CONTRACT_ADDRESSES.oftToken = await deployAssetSac(OFT_ASSET);
|
|
276
|
+
console.log('✅ OFT Token SAC deployed:', CONTRACT_ADDRESSES.oftToken);
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
it('Deploy Lock/Unlock OFT', async () => {
|
|
280
|
+
lockUnlockOftClient = await deployContract<OFTStdClient>(
|
|
281
|
+
OFTStdClient,
|
|
282
|
+
OFT_STD_WASM_PATH,
|
|
283
|
+
{
|
|
284
|
+
token: CONTRACT_ADDRESSES.oftToken,
|
|
285
|
+
owner: DEFAULT_DEPLOYER.publicKey(),
|
|
286
|
+
endpoint: CONTRACT_ADDRESSES.endpointV2,
|
|
287
|
+
delegate: DEFAULT_DEPLOYER.publicKey(),
|
|
288
|
+
shared_decimals: SHARED_DECIMALS,
|
|
289
|
+
is_lock_unlock: true, // Lock/Unlock mode
|
|
290
|
+
},
|
|
291
|
+
DEFAULT_DEPLOYER,
|
|
292
|
+
);
|
|
293
|
+
|
|
294
|
+
CONTRACT_ADDRESSES.lockUnlockOft = lockUnlockOftClient.options.contractId;
|
|
295
|
+
console.log('✅ Lock/Unlock OFT deployed:', CONTRACT_ADDRESSES.lockUnlockOft);
|
|
296
|
+
|
|
297
|
+
// Verify it's in lock/unlock mode
|
|
298
|
+
const { result: isLockUnlock } = await lockUnlockOftClient.is_lock_unlock();
|
|
299
|
+
expect(isLockUnlock).toBe(true);
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
it('Deploy Mint/Burn OFT', async () => {
|
|
303
|
+
mintBurnOftClient = await deployContract<OFTStdClient>(
|
|
304
|
+
OFTStdClient,
|
|
305
|
+
OFT_STD_WASM_PATH,
|
|
306
|
+
{
|
|
307
|
+
token: CONTRACT_ADDRESSES.oftToken,
|
|
308
|
+
owner: DEFAULT_DEPLOYER.publicKey(),
|
|
309
|
+
endpoint: CONTRACT_ADDRESSES.endpointV2,
|
|
310
|
+
delegate: DEFAULT_DEPLOYER.publicKey(),
|
|
311
|
+
shared_decimals: SHARED_DECIMALS,
|
|
312
|
+
is_lock_unlock: false, // Mint/Burn mode
|
|
313
|
+
},
|
|
314
|
+
DEFAULT_DEPLOYER,
|
|
315
|
+
);
|
|
316
|
+
|
|
317
|
+
CONTRACT_ADDRESSES.mintBurnOft = mintBurnOftClient.options.contractId;
|
|
318
|
+
console.log('✅ Mint/Burn OFT deployed:', CONTRACT_ADDRESSES.mintBurnOft);
|
|
319
|
+
|
|
320
|
+
// Verify it's in mint/burn mode
|
|
321
|
+
const { result: isLockUnlock } = await mintBurnOftClient.is_lock_unlock();
|
|
322
|
+
expect(isLockUnlock).toBe(false);
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
it('Verify all contracts deployed', () => {
|
|
326
|
+
console.log('\n📋 All deployed contracts:');
|
|
327
|
+
console.log(' Endpoint:', CONTRACT_ADDRESSES.endpointV2);
|
|
328
|
+
console.log(' SimpleMessageLib:', CONTRACT_ADDRESSES.sml);
|
|
329
|
+
console.log(' Executor:', CONTRACT_ADDRESSES.executor);
|
|
330
|
+
console.log(' Executor Helper:', CONTRACT_ADDRESSES.executorHelper);
|
|
331
|
+
console.log(' OFT Token SAC:', CONTRACT_ADDRESSES.oftToken);
|
|
332
|
+
console.log(' Lock/Unlock OFT:', CONTRACT_ADDRESSES.lockUnlockOft);
|
|
333
|
+
console.log(' Mint/Burn OFT:', CONTRACT_ADDRESSES.mintBurnOft);
|
|
334
|
+
});
|
|
335
|
+
});
|
|
336
|
+
|
|
337
|
+
describe('Wire Contracts', () => {
|
|
338
|
+
it('Register Library', async () => {
|
|
339
|
+
const assembledTx = await endpointClient.register_library({
|
|
340
|
+
new_lib: CONTRACT_ADDRESSES.sml,
|
|
341
|
+
});
|
|
342
|
+
await assembledTx.signAndSend();
|
|
343
|
+
const { result: libs } = await endpointClient.get_registered_libraries({
|
|
344
|
+
start: 0,
|
|
345
|
+
max_count: 100,
|
|
346
|
+
});
|
|
347
|
+
expect(libs.length).toBe(1);
|
|
348
|
+
expect(libs[0]).toBe(CONTRACT_ADDRESSES.sml);
|
|
349
|
+
console.log('✅ Library registered');
|
|
350
|
+
});
|
|
351
|
+
|
|
352
|
+
it('Set Default Send Library', async () => {
|
|
353
|
+
const assembledTx = await endpointClient.set_default_send_library({
|
|
354
|
+
dst_eid: EID,
|
|
355
|
+
new_lib: CONTRACT_ADDRESSES.sml,
|
|
356
|
+
});
|
|
357
|
+
await assembledTx.signAndSend();
|
|
358
|
+
const { result: defaultSendLib } = await endpointClient.default_send_library({
|
|
359
|
+
dst_eid: EID,
|
|
360
|
+
});
|
|
361
|
+
expect(defaultSendLib).toBe(CONTRACT_ADDRESSES.sml);
|
|
362
|
+
console.log('✅ Default send library set');
|
|
363
|
+
});
|
|
364
|
+
|
|
365
|
+
it('Set Default Receive Library', async () => {
|
|
366
|
+
const assembledTx = await endpointClient.set_default_receive_library({
|
|
367
|
+
src_eid: EID,
|
|
368
|
+
new_lib: CONTRACT_ADDRESSES.sml,
|
|
369
|
+
grace_period: 0n,
|
|
370
|
+
});
|
|
371
|
+
await assembledTx.signAndSend();
|
|
372
|
+
const { result: defaultReceiveLib } = await endpointClient.default_receive_library({
|
|
373
|
+
src_eid: EID,
|
|
374
|
+
});
|
|
375
|
+
expect(defaultReceiveLib).toBe(CONTRACT_ADDRESSES.sml);
|
|
376
|
+
console.log('✅ Default receive library set');
|
|
377
|
+
});
|
|
378
|
+
|
|
379
|
+
it('Set ZRO Token', async () => {
|
|
380
|
+
const setZroTx = await endpointClient.set_zro({
|
|
381
|
+
zro: ZRO_TOKEN_ADDRESS,
|
|
382
|
+
});
|
|
383
|
+
await setZroTx.signAndSend();
|
|
384
|
+
|
|
385
|
+
const { result: newZroToken } = await endpointClient.zro();
|
|
386
|
+
expect(newZroToken).toBe(ZRO_TOKEN_ADDRESS);
|
|
387
|
+
console.log('✅ ZRO token set:', ZRO_TOKEN_ADDRESS);
|
|
388
|
+
});
|
|
389
|
+
|
|
390
|
+
it('Set Lock/Unlock OFT Peer (to Mint/Burn OFT)', async () => {
|
|
391
|
+
const mintBurnPeerBytes = StrKey.decodeContract(CONTRACT_ADDRESSES.mintBurnOft);
|
|
392
|
+
|
|
393
|
+
const assembledTx = await lockUnlockOftClient.set_peer({
|
|
394
|
+
eid: EID,
|
|
395
|
+
peer: Buffer.from(mintBurnPeerBytes),
|
|
396
|
+
});
|
|
397
|
+
await assembledTx.signAndSend();
|
|
398
|
+
|
|
399
|
+
const { result: peer } = await lockUnlockOftClient.peer({
|
|
400
|
+
eid: EID,
|
|
401
|
+
});
|
|
402
|
+
expect(peer?.toString()).toBe(Buffer.from(mintBurnPeerBytes).toString());
|
|
403
|
+
console.log('✅ Lock/Unlock OFT peer set to Mint/Burn OFT');
|
|
404
|
+
});
|
|
405
|
+
|
|
406
|
+
it('Set Mint/Burn OFT Peer (to Lock/Unlock OFT)', async () => {
|
|
407
|
+
const lockUnlockPeerBytes = StrKey.decodeContract(CONTRACT_ADDRESSES.lockUnlockOft);
|
|
408
|
+
|
|
409
|
+
const assembledTx = await mintBurnOftClient.set_peer({
|
|
410
|
+
eid: EID,
|
|
411
|
+
peer: Buffer.from(lockUnlockPeerBytes),
|
|
412
|
+
});
|
|
413
|
+
await assembledTx.signAndSend();
|
|
414
|
+
|
|
415
|
+
const { result: peer } = await mintBurnOftClient.peer({
|
|
416
|
+
eid: EID,
|
|
417
|
+
});
|
|
418
|
+
expect(peer?.toString()).toBe(Buffer.from(lockUnlockPeerBytes).toString());
|
|
419
|
+
console.log('✅ Mint/Burn OFT peer set to Lock/Unlock OFT');
|
|
420
|
+
});
|
|
421
|
+
|
|
422
|
+
it('Set SAC Admin to Mint/Burn OFT (for minting)', async () => {
|
|
423
|
+
// The Mint/Burn OFT needs to be the admin of the SAC to mint tokens
|
|
424
|
+
// Use the SAC's set_admin function
|
|
425
|
+
const server = new rpc.Server(RPC_URL, { allowHttp: true });
|
|
426
|
+
|
|
427
|
+
// Build set_admin transaction
|
|
428
|
+
// SAC's set_admin function requires the current admin (TOKEN_ISSUER) to sign
|
|
429
|
+
const account = await server.getAccount(TOKEN_ISSUER.publicKey());
|
|
430
|
+
|
|
431
|
+
// Build the invokeContract operation for SAC's set_admin
|
|
432
|
+
const setAdminTx = new TransactionBuilder(account, {
|
|
433
|
+
fee: BASE_FEE,
|
|
434
|
+
networkPassphrase: NETWORK_PASSPHRASE,
|
|
435
|
+
})
|
|
436
|
+
.addOperation(
|
|
437
|
+
Operation.invokeContractFunction({
|
|
438
|
+
contract: CONTRACT_ADDRESSES.oftToken,
|
|
439
|
+
function: 'set_admin',
|
|
440
|
+
args: [
|
|
441
|
+
// new_admin: Address
|
|
442
|
+
Address.fromString(CONTRACT_ADDRESSES.mintBurnOft).toScVal(),
|
|
443
|
+
],
|
|
444
|
+
}),
|
|
445
|
+
)
|
|
446
|
+
.setTimeout(30)
|
|
447
|
+
.build();
|
|
448
|
+
|
|
449
|
+
const simulated = await server.simulateTransaction(setAdminTx);
|
|
450
|
+
if (rpc.Api.isSimulationError(simulated)) {
|
|
451
|
+
throw new Error(`Simulation failed: ${JSON.stringify(simulated)}`);
|
|
452
|
+
}
|
|
453
|
+
const preparedTx = rpc.assembleTransaction(setAdminTx, simulated).build();
|
|
454
|
+
preparedTx.sign(TOKEN_ISSUER);
|
|
455
|
+
|
|
456
|
+
const sendResult = await server.sendTransaction(preparedTx);
|
|
457
|
+
if (sendResult.status !== 'PENDING') {
|
|
458
|
+
throw new Error(`Failed to set admin: ${JSON.stringify(sendResult)}`);
|
|
459
|
+
}
|
|
460
|
+
const txResult = await server.pollTransaction(sendResult.hash);
|
|
461
|
+
if (txResult.status !== 'SUCCESS') {
|
|
462
|
+
throw new Error(`Failed to set admin: ${JSON.stringify(txResult)}`);
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
console.log('✅ SAC admin set to Mint/Burn OFT');
|
|
466
|
+
});
|
|
467
|
+
});
|
|
468
|
+
|
|
469
|
+
describe('Send: Lock/Unlock -> Mint/Burn', () => {
|
|
470
|
+
let sendLedger = 0;
|
|
471
|
+
let packetSentEvent: PacketSentEvent;
|
|
472
|
+
let guid: Buffer;
|
|
473
|
+
let message: Buffer;
|
|
474
|
+
|
|
475
|
+
it('Verify initial balances', async () => {
|
|
476
|
+
// Check sender balance (DEFAULT_DEPLOYER has 1000 OFT tokens)
|
|
477
|
+
const senderBalance = await getSacBalance(
|
|
478
|
+
CONTRACT_ADDRESSES.oftToken,
|
|
479
|
+
DEFAULT_DEPLOYER.publicKey(),
|
|
480
|
+
);
|
|
481
|
+
console.log('📊 Initial Balances:');
|
|
482
|
+
console.log(` - Sender (DEFAULT_DEPLOYER): ${senderBalance} (expected: 10000000000)`);
|
|
483
|
+
|
|
484
|
+
// RECIPIENT_A should have 0 tokens
|
|
485
|
+
const recipientABalance = await getSacBalance(
|
|
486
|
+
CONTRACT_ADDRESSES.oftToken,
|
|
487
|
+
RECIPIENT_A.publicKey(),
|
|
488
|
+
);
|
|
489
|
+
console.log(` - RECIPIENT_A: ${recipientABalance} (expected: 0)`);
|
|
490
|
+
|
|
491
|
+
expect(senderBalance).toBe(10000000000n); // 1000 tokens with 7 decimals
|
|
492
|
+
expect(recipientABalance).toBe(0n);
|
|
493
|
+
});
|
|
494
|
+
|
|
495
|
+
it('Quote OFT send', async () => {
|
|
496
|
+
// Build SendParam - send to RECIPIENT_A
|
|
497
|
+
const receiverBytes = StrKey.decodeEd25519PublicKey(RECIPIENT_A.publicKey());
|
|
498
|
+
const sendParam: SendParam = {
|
|
499
|
+
dst_eid: EID,
|
|
500
|
+
to: Buffer.from(receiverBytes),
|
|
501
|
+
amount_ld: SEND_AMOUNT,
|
|
502
|
+
min_amount_ld: SEND_AMOUNT, // No slippage for test
|
|
503
|
+
extra_options: Buffer.from([]),
|
|
504
|
+
compose_msg: Buffer.from([]),
|
|
505
|
+
oft_cmd: Buffer.from([]),
|
|
506
|
+
};
|
|
507
|
+
|
|
508
|
+
// Quote OFT
|
|
509
|
+
const { result: quoteResult } = await lockUnlockOftClient.quote_oft({
|
|
510
|
+
send_param: sendParam,
|
|
511
|
+
});
|
|
512
|
+
const [limit, feeDetails, receipt] = quoteResult;
|
|
513
|
+
console.log('📊 OFT Quote:');
|
|
514
|
+
console.log(' Limit:', limit);
|
|
515
|
+
console.log(' Fee Details:', feeDetails);
|
|
516
|
+
console.log(' Receipt:', receipt);
|
|
517
|
+
|
|
518
|
+
expect(receipt.amount_sent_ld).toBe(SEND_AMOUNT);
|
|
519
|
+
});
|
|
520
|
+
|
|
521
|
+
it('Send tokens (Lock/Unlock -> Mint/Burn)', async () => {
|
|
522
|
+
// Build SendParam - send to RECIPIENT_A
|
|
523
|
+
const receiverBytes = StrKey.decodeEd25519PublicKey(RECIPIENT_A.publicKey());
|
|
524
|
+
const sendParam: SendParam = {
|
|
525
|
+
dst_eid: EID,
|
|
526
|
+
to: Buffer.from(receiverBytes),
|
|
527
|
+
amount_ld: SEND_AMOUNT,
|
|
528
|
+
min_amount_ld: SEND_AMOUNT,
|
|
529
|
+
extra_options: Buffer.from([]),
|
|
530
|
+
compose_msg: Buffer.from([]),
|
|
531
|
+
oft_cmd: Buffer.from([]),
|
|
532
|
+
};
|
|
533
|
+
|
|
534
|
+
// Quote send fee (pay in ZRO like Counter test does)
|
|
535
|
+
const { result: fee } = await lockUnlockOftClient.quote_send({
|
|
536
|
+
sender: DEFAULT_DEPLOYER.publicKey(),
|
|
537
|
+
send_param: sendParam,
|
|
538
|
+
pay_in_zro: true,
|
|
539
|
+
});
|
|
540
|
+
console.log('📊 Messaging Fee:', fee);
|
|
541
|
+
|
|
542
|
+
// Send tokens
|
|
543
|
+
const assembledTx = await lockUnlockOftClient.send({
|
|
544
|
+
sender: DEFAULT_DEPLOYER.publicKey(),
|
|
545
|
+
send_param: sendParam,
|
|
546
|
+
fee: fee,
|
|
547
|
+
refund_address: DEFAULT_DEPLOYER.publicKey(),
|
|
548
|
+
});
|
|
549
|
+
|
|
550
|
+
// Sign and send
|
|
551
|
+
const sentTx = await assembledTx.signAndSend();
|
|
552
|
+
|
|
553
|
+
// Extract ledger number
|
|
554
|
+
const txResponse = sentTx.getTransactionResponse;
|
|
555
|
+
if (txResponse && 'ledger' in txResponse) {
|
|
556
|
+
sendLedger = txResponse.ledger;
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
assertTransactionsSucceeded(sentTx, 'OFT Send');
|
|
560
|
+
console.log('✅ Tokens sent, ledger:', sendLedger);
|
|
561
|
+
});
|
|
562
|
+
|
|
563
|
+
it('Scan PacketSent events', async () => {
|
|
564
|
+
const packetSentEvents = await scanPacketSentEvents(
|
|
565
|
+
CONTRACT_ADDRESSES.endpointV2,
|
|
566
|
+
sendLedger,
|
|
567
|
+
);
|
|
568
|
+
expect(packetSentEvents.length).toBeGreaterThan(0);
|
|
569
|
+
packetSentEvent = packetSentEvents[0];
|
|
570
|
+
console.log(`✅ PacketSent events scanned. Found ${packetSentEvents.length} events`);
|
|
571
|
+
});
|
|
572
|
+
|
|
573
|
+
it('Validate packet via SML', async () => {
|
|
574
|
+
const packet = PacketSerializer.deserialize(packetSentEvent.encoded_packet);
|
|
575
|
+
guid = Buffer.from(packet.guid.replace('0x', ''), 'hex');
|
|
576
|
+
message = Buffer.from(packet.message.replace('0x', ''), 'hex');
|
|
577
|
+
const codec = PacketV1Codec.from(packet);
|
|
578
|
+
const packetHeader = codec.header();
|
|
579
|
+
const payloadHash = codec.payloadHash();
|
|
580
|
+
|
|
581
|
+
const assembledTx = await smlClient.validate_packet({
|
|
582
|
+
header_bytes: Buffer.from(packetHeader.replace('0x', ''), 'hex'),
|
|
583
|
+
payload_hash: Buffer.from(payloadHash.replace('0x', ''), 'hex'),
|
|
584
|
+
});
|
|
585
|
+
await assembledTx.signAndSend();
|
|
586
|
+
console.log('✅ Packet validated via SML');
|
|
587
|
+
});
|
|
588
|
+
|
|
589
|
+
it('Receive tokens (mint on Mint/Burn OFT)', async () => {
|
|
590
|
+
const lockUnlockPeerBytes = StrKey.decodeContract(CONTRACT_ADDRESSES.lockUnlockOft);
|
|
591
|
+
const origin = {
|
|
592
|
+
nonce: 1n,
|
|
593
|
+
sender: Buffer.from(lockUnlockPeerBytes),
|
|
594
|
+
src_eid: EID,
|
|
595
|
+
};
|
|
596
|
+
|
|
597
|
+
// Use native_drop_and_execute with empty native_drop_params
|
|
598
|
+
const assembledTx = await executorHelperClient.native_drop_and_execute({
|
|
599
|
+
executor: CONTRACT_ADDRESSES.executor,
|
|
600
|
+
admin: EXECUTOR_ADMIN.publicKey(),
|
|
601
|
+
origin,
|
|
602
|
+
dst_eid: EID,
|
|
603
|
+
oapp: CONTRACT_ADDRESSES.mintBurnOft,
|
|
604
|
+
native_drop_params: [],
|
|
605
|
+
execute_params: {
|
|
606
|
+
extra_data: Buffer.from([]),
|
|
607
|
+
gas_limit: 0n,
|
|
608
|
+
guid,
|
|
609
|
+
message,
|
|
610
|
+
origin,
|
|
611
|
+
receiver: CONTRACT_ADDRESSES.mintBurnOft,
|
|
612
|
+
value: 0n,
|
|
613
|
+
},
|
|
614
|
+
});
|
|
615
|
+
|
|
616
|
+
// Sign the Executor's auth entries
|
|
617
|
+
await signExecutorAuthEntries(
|
|
618
|
+
CONTRACT_ADDRESSES.executor,
|
|
619
|
+
EXECUTOR_ADMIN,
|
|
620
|
+
assembledTx,
|
|
621
|
+
NETWORK_PASSPHRASE,
|
|
622
|
+
);
|
|
623
|
+
|
|
624
|
+
// Sign and send
|
|
625
|
+
const sentTx = await assembledTx.signAndSend();
|
|
626
|
+
assertTransactionsSucceeded(sentTx, 'LzReceive (Mint)');
|
|
627
|
+
|
|
628
|
+
console.log('✅ Tokens received and minted on Mint/Burn OFT');
|
|
629
|
+
});
|
|
630
|
+
|
|
631
|
+
it('Verify balances after forward send', async () => {
|
|
632
|
+
// Balance changes after forward send:
|
|
633
|
+
// - Sender (DEFAULT_DEPLOYER): 1000 - 100 = 900 tokens
|
|
634
|
+
// - Lock/Unlock OFT contract: holds 100 locked tokens
|
|
635
|
+
// - RECIPIENT_A: received 100 minted tokens
|
|
636
|
+
|
|
637
|
+
const senderBalance = await getSacBalance(
|
|
638
|
+
CONTRACT_ADDRESSES.oftToken,
|
|
639
|
+
DEFAULT_DEPLOYER.publicKey(),
|
|
640
|
+
);
|
|
641
|
+
const lockUnlockOftBalance = await getSacBalance(
|
|
642
|
+
CONTRACT_ADDRESSES.oftToken,
|
|
643
|
+
CONTRACT_ADDRESSES.lockUnlockOft,
|
|
644
|
+
);
|
|
645
|
+
const recipientABalance = await getSacBalance(
|
|
646
|
+
CONTRACT_ADDRESSES.oftToken,
|
|
647
|
+
RECIPIENT_A.publicKey(),
|
|
648
|
+
);
|
|
649
|
+
|
|
650
|
+
console.log('📊 Balances after forward send:');
|
|
651
|
+
console.log(` - Sender (DEFAULT_DEPLOYER): ${senderBalance} (expected: 9000000000)`);
|
|
652
|
+
console.log(
|
|
653
|
+
` - Lock/Unlock OFT (locked): ${lockUnlockOftBalance} (expected: 1000000000)`,
|
|
654
|
+
);
|
|
655
|
+
console.log(` - RECIPIENT_A (minted): ${recipientABalance} (expected: 1000000000)`);
|
|
656
|
+
|
|
657
|
+
expect(senderBalance).toBe(9000000000n); // 900 tokens
|
|
658
|
+
expect(lockUnlockOftBalance).toBe(1000000000n); // 100 tokens locked
|
|
659
|
+
expect(recipientABalance).toBe(1000000000n); // 100 tokens minted
|
|
660
|
+
});
|
|
661
|
+
});
|
|
662
|
+
|
|
663
|
+
describe('Send: Mint/Burn -> Lock/Unlock (Reverse)', () => {
|
|
664
|
+
let sendLedger = 0;
|
|
665
|
+
let packetSentEvent: PacketSentEvent;
|
|
666
|
+
let guid: Buffer;
|
|
667
|
+
let message: Buffer;
|
|
668
|
+
const REVERSE_SEND_AMOUNT = 50_0000000n; // 50 tokens
|
|
669
|
+
|
|
670
|
+
it('Quote OFT send (reverse)', async () => {
|
|
671
|
+
// Send to RECIPIENT_B
|
|
672
|
+
const receiverBytes = StrKey.decodeEd25519PublicKey(RECIPIENT_B.publicKey());
|
|
673
|
+
const sendParam: SendParam = {
|
|
674
|
+
dst_eid: EID,
|
|
675
|
+
to: Buffer.from(receiverBytes),
|
|
676
|
+
amount_ld: REVERSE_SEND_AMOUNT,
|
|
677
|
+
min_amount_ld: REVERSE_SEND_AMOUNT,
|
|
678
|
+
extra_options: Buffer.from([]),
|
|
679
|
+
compose_msg: Buffer.from([]),
|
|
680
|
+
oft_cmd: Buffer.from([]),
|
|
681
|
+
};
|
|
682
|
+
|
|
683
|
+
const { result: quoteResult } = await mintBurnOftClient.quote_oft({
|
|
684
|
+
send_param: sendParam,
|
|
685
|
+
});
|
|
686
|
+
const [limit, feeDetails, receipt] = quoteResult;
|
|
687
|
+
console.log('📊 Reverse OFT Quote:');
|
|
688
|
+
console.log(' Limit:', limit);
|
|
689
|
+
console.log(' Fee Details:', feeDetails);
|
|
690
|
+
console.log(' Receipt:', receipt);
|
|
691
|
+
|
|
692
|
+
expect(receipt.amount_sent_ld).toBe(REVERSE_SEND_AMOUNT);
|
|
693
|
+
});
|
|
694
|
+
|
|
695
|
+
it('Send tokens (Mint/Burn -> Lock/Unlock)', async () => {
|
|
696
|
+
// DEFAULT_DEPLOYER sends via Mint/Burn OFT to RECIPIENT_B
|
|
697
|
+
const receiverBytes = StrKey.decodeEd25519PublicKey(RECIPIENT_B.publicKey());
|
|
698
|
+
const sendParam: SendParam = {
|
|
699
|
+
dst_eid: EID,
|
|
700
|
+
to: Buffer.from(receiverBytes),
|
|
701
|
+
amount_ld: REVERSE_SEND_AMOUNT,
|
|
702
|
+
min_amount_ld: REVERSE_SEND_AMOUNT,
|
|
703
|
+
extra_options: Buffer.from([]),
|
|
704
|
+
compose_msg: Buffer.from([]),
|
|
705
|
+
oft_cmd: Buffer.from([]),
|
|
706
|
+
};
|
|
707
|
+
|
|
708
|
+
// Quote send fee (pay in ZRO like Counter test does)
|
|
709
|
+
const { result: fee } = await mintBurnOftClient.quote_send({
|
|
710
|
+
sender: DEFAULT_DEPLOYER.publicKey(),
|
|
711
|
+
send_param: sendParam,
|
|
712
|
+
pay_in_zro: true,
|
|
713
|
+
});
|
|
714
|
+
console.log('📊 Reverse Messaging Fee:', fee);
|
|
715
|
+
|
|
716
|
+
// Send tokens
|
|
717
|
+
const assembledTx = await mintBurnOftClient.send({
|
|
718
|
+
sender: DEFAULT_DEPLOYER.publicKey(),
|
|
719
|
+
send_param: sendParam,
|
|
720
|
+
fee: fee,
|
|
721
|
+
refund_address: DEFAULT_DEPLOYER.publicKey(),
|
|
722
|
+
});
|
|
723
|
+
|
|
724
|
+
// Sign and send
|
|
725
|
+
const sentTx = await assembledTx.signAndSend();
|
|
726
|
+
|
|
727
|
+
// Extract ledger number
|
|
728
|
+
const txResponse = sentTx.getTransactionResponse;
|
|
729
|
+
if (txResponse && 'ledger' in txResponse) {
|
|
730
|
+
sendLedger = txResponse.ledger;
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
assertTransactionsSucceeded(sentTx, 'OFT Reverse Send');
|
|
734
|
+
console.log('✅ Tokens sent (reverse), ledger:', sendLedger);
|
|
735
|
+
});
|
|
736
|
+
|
|
737
|
+
it('Scan PacketSent events (reverse)', async () => {
|
|
738
|
+
const packetSentEvents = await scanPacketSentEvents(
|
|
739
|
+
CONTRACT_ADDRESSES.endpointV2,
|
|
740
|
+
sendLedger,
|
|
741
|
+
);
|
|
742
|
+
expect(packetSentEvents.length).toBeGreaterThan(0);
|
|
743
|
+
packetSentEvent = packetSentEvents[0];
|
|
744
|
+
console.log(
|
|
745
|
+
`✅ PacketSent events scanned (reverse). Found ${packetSentEvents.length} events`,
|
|
746
|
+
);
|
|
747
|
+
});
|
|
748
|
+
|
|
749
|
+
it('Validate packet via SML (reverse)', async () => {
|
|
750
|
+
const packet = PacketSerializer.deserialize(packetSentEvent.encoded_packet);
|
|
751
|
+
guid = Buffer.from(packet.guid.replace('0x', ''), 'hex');
|
|
752
|
+
message = Buffer.from(packet.message.replace('0x', ''), 'hex');
|
|
753
|
+
const codec = PacketV1Codec.from(packet);
|
|
754
|
+
const packetHeader = codec.header();
|
|
755
|
+
const payloadHash = codec.payloadHash();
|
|
756
|
+
|
|
757
|
+
const assembledTx = await smlClient.validate_packet({
|
|
758
|
+
header_bytes: Buffer.from(packetHeader.replace('0x', ''), 'hex'),
|
|
759
|
+
payload_hash: Buffer.from(payloadHash.replace('0x', ''), 'hex'),
|
|
760
|
+
});
|
|
761
|
+
await assembledTx.signAndSend();
|
|
762
|
+
console.log('✅ Packet validated via SML (reverse)');
|
|
763
|
+
});
|
|
764
|
+
|
|
765
|
+
it('Receive tokens (unlock on Lock/Unlock OFT)', async () => {
|
|
766
|
+
const mintBurnPeerBytes = StrKey.decodeContract(CONTRACT_ADDRESSES.mintBurnOft);
|
|
767
|
+
const origin = {
|
|
768
|
+
nonce: 1n,
|
|
769
|
+
sender: Buffer.from(mintBurnPeerBytes),
|
|
770
|
+
src_eid: EID,
|
|
771
|
+
};
|
|
772
|
+
|
|
773
|
+
// Use native_drop_and_execute with empty native_drop_params
|
|
774
|
+
const assembledTx = await executorHelperClient.native_drop_and_execute({
|
|
775
|
+
executor: CONTRACT_ADDRESSES.executor,
|
|
776
|
+
admin: EXECUTOR_ADMIN.publicKey(),
|
|
777
|
+
origin,
|
|
778
|
+
dst_eid: EID,
|
|
779
|
+
oapp: CONTRACT_ADDRESSES.lockUnlockOft,
|
|
780
|
+
native_drop_params: [],
|
|
781
|
+
execute_params: {
|
|
782
|
+
extra_data: Buffer.from([]),
|
|
783
|
+
gas_limit: 0n,
|
|
784
|
+
guid,
|
|
785
|
+
message,
|
|
786
|
+
origin,
|
|
787
|
+
receiver: CONTRACT_ADDRESSES.lockUnlockOft,
|
|
788
|
+
value: 0n,
|
|
789
|
+
},
|
|
790
|
+
});
|
|
791
|
+
|
|
792
|
+
// Sign the Executor's auth entries
|
|
793
|
+
await signExecutorAuthEntries(
|
|
794
|
+
CONTRACT_ADDRESSES.executor,
|
|
795
|
+
EXECUTOR_ADMIN,
|
|
796
|
+
assembledTx,
|
|
797
|
+
NETWORK_PASSPHRASE,
|
|
798
|
+
);
|
|
799
|
+
|
|
800
|
+
// Sign and send
|
|
801
|
+
const sentTx = await assembledTx.signAndSend();
|
|
802
|
+
assertTransactionsSucceeded(sentTx, 'LzReceive (Unlock)');
|
|
803
|
+
|
|
804
|
+
console.log('✅ Tokens received and unlocked on Lock/Unlock OFT');
|
|
805
|
+
});
|
|
806
|
+
|
|
807
|
+
it('Verify final balances', async () => {
|
|
808
|
+
// Final balance summary:
|
|
809
|
+
// - DEFAULT_DEPLOYER: started with 1000, sent 100 (locked), sent 50 (burned) = 850
|
|
810
|
+
// - Lock/Unlock OFT: locked 100, unlocked 50 = 50 held
|
|
811
|
+
// - RECIPIENT_A: received 100 (minted)
|
|
812
|
+
// - RECIPIENT_B: received 50 (unlocked)
|
|
813
|
+
|
|
814
|
+
const senderBalance = await getSacBalance(
|
|
815
|
+
CONTRACT_ADDRESSES.oftToken,
|
|
816
|
+
DEFAULT_DEPLOYER.publicKey(),
|
|
817
|
+
);
|
|
818
|
+
const lockUnlockOftBalance = await getSacBalance(
|
|
819
|
+
CONTRACT_ADDRESSES.oftToken,
|
|
820
|
+
CONTRACT_ADDRESSES.lockUnlockOft,
|
|
821
|
+
);
|
|
822
|
+
const recipientABalance = await getSacBalance(
|
|
823
|
+
CONTRACT_ADDRESSES.oftToken,
|
|
824
|
+
RECIPIENT_A.publicKey(),
|
|
825
|
+
);
|
|
826
|
+
const recipientBBalance = await getSacBalance(
|
|
827
|
+
CONTRACT_ADDRESSES.oftToken,
|
|
828
|
+
RECIPIENT_B.publicKey(),
|
|
829
|
+
);
|
|
830
|
+
|
|
831
|
+
console.log('\n📊 Final Balance Summary:');
|
|
832
|
+
console.log(` - Sender (DEFAULT_DEPLOYER): ${senderBalance} (expected: 8500000000)`);
|
|
833
|
+
console.log(
|
|
834
|
+
` - Lock/Unlock OFT (locked): ${lockUnlockOftBalance} (expected: 500000000)`,
|
|
835
|
+
);
|
|
836
|
+
console.log(` - RECIPIENT_A (minted): ${recipientABalance} (expected: 1000000000)`);
|
|
837
|
+
console.log(` - RECIPIENT_B (unlocked): ${recipientBBalance} (expected: 500000000)`);
|
|
838
|
+
|
|
839
|
+
expect(senderBalance).toBe(8500000000n); // 850 tokens (1000 - 100 - 50)
|
|
840
|
+
expect(lockUnlockOftBalance).toBe(500000000n); // 50 tokens (100 - 50)
|
|
841
|
+
expect(recipientABalance).toBe(1000000000n); // 100 tokens minted
|
|
842
|
+
expect(recipientBBalance).toBe(500000000n); // 50 tokens unlocked
|
|
843
|
+
|
|
844
|
+
console.log('✅ OFT E2E test completed successfully!');
|
|
845
|
+
});
|
|
846
|
+
});
|
|
847
|
+
});
|