UNPKG

@hyperlane-xyz/sdk

Version:

The official SDK for the Hyperlane Network

193 lines 7.12 kB
import { expect } from 'chai'; import { decodeFunctionData, zeroAddress } from 'viem'; import { buildExecuteCalldata, buildQuoteCalldata } from './builder.js'; import { QuotedCallsCommand, TokenPullMode } from './types.js'; const QUOTED_CALLS = '0x1111111111111111111111111111111111111111'; const WARP_ROUTE = '0x2222222222222222222222222222222222222222'; const TOKEN = '0x3333333333333333333333333333333333333333'; const RECIPIENT = '0x0000000000000000000000004444444444444444444444444444444444444444'; const CLIENT_SALT = '0x5555555555555555555555555555555555555555555555555555555555555555'; const MOCK_QUOTE = { quoter: '0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', quote: { context: '0xdeadbeef', data: '0xcafebabe', issuedAt: 1000, expiry: 1000, salt: CLIENT_SALT, submitter: QUOTED_CALLS, }, signature: '0xaabb', }; const BASE_PARAMS = { quotedCallsAddress: QUOTED_CALLS, warpRoute: WARP_ROUTE, destination: 42161, recipient: RECIPIENT, amount: 1000n, token: TOKEN, quotes: [MOCK_QUOTE], clientSalt: CLIENT_SALT, }; const executeAbi = [ { name: 'execute', type: 'function', inputs: [ { name: 'commands', type: 'bytes' }, { name: 'inputs', type: 'bytes[]' }, ], outputs: [], stateMutability: 'payable', }, { name: 'quoteExecute', type: 'function', inputs: [ { name: 'commands', type: 'bytes' }, { name: 'inputs', type: 'bytes[]' }, ], outputs: [], stateMutability: 'nonpayable', }, ]; function decodeCommands(data) { const decoded = decodeFunctionData({ abi: executeAbi, data }); const commandsHex = decoded.args[0].slice(2); const commands = []; for (let i = 0; i < commandsHex.length; i += 2) { commands.push(parseInt(commandsHex.slice(i, i + 2), 16)); } return { fn: decoded.functionName, commands }; } // Mock fee quotes from quoteExecute const MOCK_FEE_QUOTES = [ [], // SUBMIT_QUOTE → empty [ // TRANSFER_REMOTE → 3 quotes { token: zeroAddress, amount: 100n }, // IGP native fee { token: TOKEN, amount: 1050n }, // amount + internal fee { token: TOKEN, amount: 10n }, // external fee ], ]; describe('buildQuoteCalldata', () => { it('builds quoteExecute with SUBMIT_QUOTE + TRANSFER_REMOTE', () => { const result = buildQuoteCalldata(BASE_PARAMS); expect(result.to).to.equal(QUOTED_CALLS); expect(result.value).to.equal(0n); const { fn, commands } = decodeCommands(result.data); expect(fn).to.equal('quoteExecute'); expect(commands).to.deep.equal([ QuotedCallsCommand.SUBMIT_QUOTE, QuotedCallsCommand.TRANSFER_REMOTE, ]); }); it('builds quoteExecute with TRANSFER_REMOTE_TO for cross-collateral', () => { const targetRouter = '0x0000000000000000000000007777777777777777777777777777777777777777'; const result = buildQuoteCalldata({ ...BASE_PARAMS, targetRouter }); const { commands } = decodeCommands(result.data); expect(commands).to.deep.equal([ QuotedCallsCommand.SUBMIT_QUOTE, QuotedCallsCommand.TRANSFER_REMOTE_TO, ]); }); }); describe('buildExecuteCalldata', () => { it('builds ERC20 TransferFrom execute from fee quotes', () => { const result = buildExecuteCalldata({ ...BASE_PARAMS, feeQuotes: MOCK_FEE_QUOTES, tokenPullMode: TokenPullMode.TransferFrom, }); expect(result.to).to.equal(QUOTED_CALLS); // native value = IGP fee (100) expect(result.value).to.equal(100n); const { fn, commands } = decodeCommands(result.data); expect(fn).to.equal('execute'); expect(commands).to.deep.equal([ QuotedCallsCommand.SUBMIT_QUOTE, QuotedCallsCommand.TRANSFER_FROM, QuotedCallsCommand.TRANSFER_REMOTE, QuotedCallsCommand.SWEEP, ]); }); it('builds Permit2 execute from fee quotes', () => { const result = buildExecuteCalldata({ ...BASE_PARAMS, feeQuotes: MOCK_FEE_QUOTES, tokenPullMode: TokenPullMode.Permit2, permit2Data: { permitSingle: { details: { token: TOKEN, amount: 2000n, expiration: 9999, nonce: 0, }, spender: QUOTED_CALLS, sigDeadline: 9999, }, signature: '0xaabb', }, }); const { commands } = decodeCommands(result.data); expect(commands).to.deep.equal([ QuotedCallsCommand.SUBMIT_QUOTE, QuotedCallsCommand.PERMIT2_PERMIT, QuotedCallsCommand.PERMIT2_TRANSFER_FROM, QuotedCallsCommand.TRANSFER_REMOTE, QuotedCallsCommand.SWEEP, ]); }); it('builds native route execute (no TRANSFER_FROM)', () => { const nativeFeeQuotes = [ [], [{ token: zeroAddress, amount: 5100n }], ]; const result = buildExecuteCalldata({ ...BASE_PARAMS, token: zeroAddress, amount: 5000n, feeQuotes: nativeFeeQuotes, tokenPullMode: TokenPullMode.TransferFrom, }); // msg.value = native total from quotes (5100 already includes transfer amount) expect(result.value).to.equal(5100n); const { commands } = decodeCommands(result.data); // No TRANSFER_FROM for native route expect(commands).to.deep.equal([ QuotedCallsCommand.SUBMIT_QUOTE, QuotedCallsCommand.TRANSFER_REMOTE, QuotedCallsCommand.SWEEP, ]); }); it('skips SWEEP for TransferFrom with zero token fees', () => { const zeroTokenFeeQuotes = [ [], // SUBMIT_QUOTE → empty [ // TRANSFER_REMOTE → native fee only, no token fee { token: zeroAddress, amount: 100n }, ], ]; const result = buildExecuteCalldata({ ...BASE_PARAMS, feeQuotes: zeroTokenFeeQuotes, tokenPullMode: TokenPullMode.TransferFrom, }); const { commands } = decodeCommands(result.data); // No TRANSFER_FROM (totalTokenNeeded=0) and no SWEEP (TransferFrom + zero token fees) expect(commands).to.deep.equal([ QuotedCallsCommand.SUBMIT_QUOTE, QuotedCallsCommand.TRANSFER_REMOTE, ]); expect(result.value).to.equal(100n); }); it('throws when Permit2 mode without permit2Data', () => { expect(() => buildExecuteCalldata({ ...BASE_PARAMS, feeQuotes: MOCK_FEE_QUOTES, tokenPullMode: TokenPullMode.Permit2, })).to.throw('permit2Data required'); }); }); //# sourceMappingURL=builder.test.js.map