import * as Action from './Action.js' const sourceToken = '0x833589fcd6edb6e08f4c7c32d4f71b54bda02913' const spender = `0x${'44'.repeat(20)}` function approveCall(amount: bigint): Action.Call { return { data: `0x095ea7b3${spender.slice(2).padStart(64, '0')}${amount.toString(16).padStart(64, '0')}`, to: sourceToken, value: '0x0', } } 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( `[Funding.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)}` }], type: 'evm:calls', }, sourceAmount: '1000000', sourceTokenAddress: sourceToken, }), ).toThrowErrorMatchingInlineSnapshot( `[Funding.Action.InvalidActionError: Approval must target the source token.]`, ) }) 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( `[Funding.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( `[Funding.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( `[Funding.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( `[Funding.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( `[Funding.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( `[Funding.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) }) })