import * as Action from './Action.js' const sourceToken = '0x833589fcd6edb6e08f4c7c32d4f71b54bda02913' const spender = `0x${'44'.repeat(20)}` function approveCall(amount: bigint, options: approveCall.Options = {}): Action.Call { const target = options.spender ?? spender return { data: `0x095ea7b3${target.slice(2).padStart(64, '0')}${amount.toString(16).padStart(64, '0')}`, to: options.token ?? sourceToken, value: '0x0', } } declare namespace approveCall { type Options = { spender?: string | undefined token?: string | undefined } } const depositCall: Action.Call = { data: '0xe8017952', to: spender, value: '0x0' } describe('validateEvmCalls', () => { test('accepts approvals bounded to the route amount', () => { Action.validateEvmCalls({ action: { calls: [approveCall(1_000_000n), depositCall], type: 'evm:calls' }, sourceAmount: '1000000', sourceTokenAddress: sourceToken, }) }) test('rejects unlimited approvals', () => { expect(() => Action.validateEvmCalls({ action: { calls: [approveCall((1n << 256n) - 1n), depositCall], type: 'evm:calls' }, sourceAmount: '1000000', sourceTokenAddress: sourceToken, }), ).toThrowErrorMatchingInlineSnapshot( `[Routes.Action.InvalidActionError: Approval must stay bounded to the route amount.]`, ) }) test('rejects approvals above the route amount', () => { expect(() => Action.validateEvmCalls({ action: { calls: [approveCall(1_000_001n)], type: 'evm:calls' }, sourceAmount: '1000000', sourceTokenAddress: sourceToken, }), ).toThrow(Action.InvalidActionError) }) test('rejects approvals targeting another token', () => { expect(() => Action.validateEvmCalls({ action: { calls: [{ ...approveCall(1n), to: `0x${'99'.repeat(20)}` }, depositCall], type: 'evm:calls', }, sourceAmount: '1000000', sourceTokenAddress: sourceToken, }), ).toThrowErrorMatchingInlineSnapshot( `[Routes.Action.InvalidActionError: Approval must stay bounded to the route amount.]`, ) }) test('accepts an exact provider-validated approval for another token', () => { const feeToken = `0x${'99'.repeat(20)}` Action.validateEvmCalls({ action: { calls: [approveCall(2_000_000n, { token: feeToken }), depositCall], type: 'evm:calls', }, sourceAmount: '1000000', sourceTokenAddress: sourceToken, tokenApprovals: [{ amount: '2000000', spender, token: feeToken }], }) }) test('rejects approvals outside the exact provider token allowance', () => { const feeToken = `0x${'99'.repeat(20)}` for (const call of [ approveCall(2_000_001n, { token: feeToken }), approveCall(2_000_000n, { spender: `0x${'88'.repeat(20)}`, token: feeToken }), approveCall(2_000_000n, { token: `0x${'77'.repeat(20)}` }), ]) expect(() => Action.validateEvmCalls({ action: { calls: [call, depositCall], type: 'evm:calls' }, sourceAmount: '1000000', sourceTokenAddress: sourceToken, tokenApprovals: [{ amount: '2000000', spender, token: feeToken }], }), ).toThrow(Action.InvalidActionError) }) test('rejects malformed provider token allowances', () => { expect(() => Action.validateEvmCalls({ action: { calls: [approveCall(1_000_000n), depositCall], type: 'evm:calls' }, sourceAmount: '1000000', sourceTokenAddress: sourceToken, tokenApprovals: [{ amount: '0x10', spender, token: sourceToken }], }), ).toThrowErrorMatchingInlineSnapshot( `[Routes.Action.InvalidActionError: Malformed token approval allowance.]`, ) }) test('rejects approval spenders that do not execute a prepared call', () => { const attacker = `0x${'99'.repeat(20)}` expect(() => Action.validateEvmCalls({ action: { calls: [ { data: `0x095ea7b3${attacker.slice(2).padStart(64, '0')}${(1_000_000).toString(16).padStart(64, '0')}`, to: sourceToken, value: '0x0', }, depositCall, ], type: 'evm:calls', }, sourceAmount: '1000000', sourceTokenAddress: sourceToken, }), ).toThrowErrorMatchingInlineSnapshot( `[Routes.Action.InvalidActionError: Approval spender must match a later prepared executor.]`, ) }) test('rejects truncated approval calldata', () => { expect(() => Action.validateEvmCalls({ action: { calls: [{ data: '0x095ea7b3', to: sourceToken, value: '0x0' }], type: 'evm:calls', }, sourceAmount: '1000000', sourceTokenAddress: sourceToken, }), ).toThrowErrorMatchingInlineSnapshot( `[Routes.Action.InvalidActionError: Malformed approval calldata.]`, ) }) test('rejects odd-length calldata', () => { expect(() => Action.validateEvmCalls({ action: { calls: [{ data: '0x1', to: spender, value: '0x0' }], type: 'evm:calls' }, sourceAmount: '1000000', sourceTokenAddress: sourceToken, }), ).toThrowErrorMatchingInlineSnapshot(`[Routes.Action.InvalidActionError: Malformed EVM calls.]`) }) test('rejects unsupported source token calls', () => { expect(() => Action.validateEvmCalls({ action: { calls: [{ data: '0xa9059cbb', to: sourceToken, value: '0x0' }], type: 'evm:calls', }, sourceAmount: '1000000', sourceTokenAddress: sourceToken, }), ).toThrowErrorMatchingInlineSnapshot( `[Routes.Action.InvalidActionError: Unsupported source token call.]`, ) }) test('rejects calls carrying native value', () => { expect(() => Action.validateEvmCalls({ action: { calls: [{ ...depositCall, value: '0x1' }], type: 'evm:calls' }, sourceAmount: '1000000', sourceTokenAddress: sourceToken, }), ).toThrowErrorMatchingInlineSnapshot( `[Routes.Action.InvalidActionError: Native value must match a validated provider fee.]`, ) }) test('accepts an exact provider-validated native value', () => { Action.validateEvmCalls({ action: { calls: [approveCall(1_000_000n), { ...depositCall, value: '0x10' }], type: 'evm:calls', }, nativeValues: [{ to: spender, value: '0x10' }], sourceAmount: '1000000', sourceTokenAddress: sourceToken, }) }) test('rejects malformed native value allowances', () => { expect(() => Action.validateEvmCalls({ action: { calls: [approveCall(1_000_000n), depositCall], type: 'evm:calls' }, nativeValues: [{ to: spender, value: '10' }], sourceAmount: '1000000', sourceTokenAddress: sourceToken, }), ).toThrowErrorMatchingInlineSnapshot( `[Routes.Action.InvalidActionError: Malformed native value allowance.]`, ) }) test('rejects native values outside the exact provider allowance', () => { for (const call of [ { ...depositCall, to: `0x${'99'.repeat(20)}`, value: '0x10' }, { ...depositCall, value: '0x11' }, ]) expect(() => Action.validateEvmCalls({ action: { calls: [approveCall(1_000_000n), call], type: 'evm:calls' }, nativeValues: [{ to: spender, value: '0x10' }], sourceAmount: '1000000', sourceTokenAddress: sourceToken, }), ).toThrow(Action.InvalidActionError) }) })