diff --git a/src/helpers/types.ts b/src/helpers/types.ts index 5b183587..9780bf12 100644 --- a/src/helpers/types.ts +++ b/src/helpers/types.ts @@ -79,9 +79,27 @@ export type AckFailuresResponse = { // Failure represents a single contractmanager failure type Failure = { address: string; - id: number; - ack_id: number; + id: string; + ack: { + response: { + result: string | null; // base64 encoded bytes + error: string | null; // error text + }; + }; ack_type: string; + packet: { + data: string; + destination_channel: string; + destination_port: string; + sequence: string; + source_channel: string; + source_port: string; + timeout_height: { + revision_height: string; + revision_number: string; + }; + timeout_timestamp: string; + }; }; export type ScheduleResponse = { diff --git a/src/testcases/parallel/simple.test.ts b/src/testcases/parallel/simple.test.ts index 2851b9e4..b9568fb3 100644 --- a/src/testcases/parallel/simple.test.ts +++ b/src/testcases/parallel/simple.test.ts @@ -446,7 +446,7 @@ describe('Neutron / Simple', () => { }); }); - describe('Not enough amount of tokens on contract to pay fee', () => { + describe('Failing sudo handlers', () => { beforeAll(async () => { await neutronAccount.executeContract( contractAddress, @@ -470,7 +470,9 @@ describe('Neutron / Simple', () => { await neutronAccount.executeContract( contractAddress, JSON.stringify({ - integration_tests_set_sudo_failure_mock: {}, + integration_tests_set_sudo_failure_mock: { + state: 'enabled', + }, }), ); @@ -516,7 +518,7 @@ describe('Neutron / Simple', () => { const failuresAfterCall = await getWithAttempts( neutronChain.blockWaiter, async () => neutronChain.queryAckFailures(contractAddress), - // Wait until there 4 failure in the list + // Wait until there 4 failures in the list async (data) => data.failures.length == 4, ); @@ -551,7 +553,118 @@ describe('Neutron / Simple', () => { }), ); }); + + // TODO: uncomment when LIMIT param is https://www.notion.so/hadron/Gas-Errors-Interchain-Txs-2b2f1caacdcd4981950641e0996cac27 implemented + // then for this test need to add limit low enough to trigger out of gas + // then change later tests length of failures (should be +2 more) + // test('execute contract with sudo out of gas', async () => { + // // Mock sudo handler to fail + // await neutronAccount.executeContract( + // contractAddress, + // JSON.stringify({ + // integration_tests_set_sudo_failure_mock: { + // state: 'enabled_infinite_loop', + // }, + // }), + // ); + + // await neutronAccount.executeContract( + // contractAddress, + // JSON.stringify({ + // send: { + // channel: 'channel-0', + // to: gaiaAccount.wallet.address.toString(), + // denom: NEUTRON_DENOM, + // amount: '1000', + // }, + // }), + // ); + + // await neutronChain.blockWaiter.waitBlocks(5); + + // const res = await getWithAttempts( + // neutronChain.blockWaiter, + // async () => neutronChain.queryAckFailures(contractAddress), + // // Wait until there 6 failures in the list + // async (data) => data.failures.length == 6, + // ); + // expect(res.failures.length).toEqual(6); + // }); + + test('failed attempt to resubmit failure', async () => { + // Mock sudo handler to fail + await neutronAccount.executeContract( + contractAddress, + JSON.stringify({ + integration_tests_set_sudo_failure_mock: { + state: 'enabled', + }, + }), + ); + + await neutronChain.blockWaiter.waitBlocks(2); + + // Try to resubmit failure + const failuresResBefore = await neutronChain.queryAckFailures( + contractAddress, + ); + + await expect( + neutronAccount.executeContract( + contractAddress, + JSON.stringify({ + resubmit_failure: { + failure_id: +failuresResBefore.failures[0].id, + }, + }), + ), + ).rejects.toThrowError(); + + await neutronChain.blockWaiter.waitBlocks(5); + + // check that failures count is the same + const failuresResAfter = await neutronChain.queryAckFailures( + contractAddress, + ); + expect(failuresResAfter.failures.length).toEqual(4); + + // Restore sudo handler's normal state + await neutronAccount.executeContract( + contractAddress, + JSON.stringify({ + integration_tests_unset_sudo_failure_mock: {}, + }), + ); + await neutronChain.blockWaiter.waitBlocks(5); + }); + + test('successful resubmit failure', async () => { + // Resubmit failure + const failuresResBefore = await neutronChain.queryAckFailures( + contractAddress, + ); + const failure = failuresResBefore.failures[0]; + const failureId = +failure.id; + const res = await neutronAccount.executeContract( + contractAddress, + JSON.stringify({ + resubmit_failure: { + failure_id: +failureId, + }, + }), + ); + expect(res.code).toBe(0); + + await neutronChain.blockWaiter.waitBlocks(5); + + // check that failures count is changed + const failuresResAfter = await neutronChain.queryAckFailures( + contractAddress, + ); + expect(failuresResAfter.failures.length).toEqual(3); + }); }); + describe('Failures limit test', () => { test("failures with small limit doesn't return an error", async () => { const pagination: PageRequest = { diff --git a/src/testcases/run_in_band/interchaintx.test.ts b/src/testcases/run_in_band/interchaintx.test.ts index 6bf19dc9..43adebe9 100644 --- a/src/testcases/run_in_band/interchaintx.test.ts +++ b/src/testcases/run_in_band/interchaintx.test.ts @@ -620,7 +620,9 @@ describe('Neutron / Interchain TXs', () => { await neutronAccount.executeContract( contractAddress, JSON.stringify({ - integration_tests_set_sudo_failure_mock: {}, + integration_tests_set_sudo_failure_mock: { + state: 'enabled', + }, }), ); @@ -749,7 +751,9 @@ describe('Neutron / Interchain TXs', () => { await neutronAccount.executeContract( contractAddress, JSON.stringify({ - integration_tests_set_sudo_failure_mock: {}, + integration_tests_set_sudo_failure_mock: { + state: 'enabled', + }, }), ); @@ -788,48 +792,184 @@ describe('Neutron / Interchain TXs', () => { ); }); + // TODO: uncomment when LIMIT param is https://www.notion.so/hadron/Gas-Errors-Interchain-Txs-2b2f1caacdcd4981950641e0996cac27 implemented + // then for this test need to add limit low enough to trigger out of gas + // test('ack failure during sudo out of gas', async () => { + // // Mock sudo handler to fail + // await neutronAccount.executeContract( + // contractAddress, + // JSON.stringify({ + // integration_tests_set_sudo_failure_mock: { + // state: 'enabled_infinite_loop', + // }, + // }), + // ); + + // // Testing ACK failure + // await neutronAccount.executeContract( + // contractAddress, + // JSON.stringify({ + // delegate: { + // interchain_account_id: icaId1, + // validator: testState.wallets.cosmos.val1.address.toString(), + // amount: '10', + // denom: gaiaChain.denom, + // }, + // }), + // ); + + // // wait until sudo is called and processed and failure is recorder + // await getWithAttempts( + // neutronChain.blockWaiter, + // async () => neutronChain.queryAckFailures(contractAddress), + // async (data) => data.failures.length == 5, + // 100, + // ); + + // // make sure contract's state hasn't been changed + // const acks = await getAcks(neutronChain, contractAddress); + // expect(acks.length).toEqual(0); + + // // Restore sudo handler's normal state + // await neutronAccount.executeContract( + // contractAddress, + // JSON.stringify({ + // integration_tests_unset_sudo_failure_mock: {}, + // }), + // ); + // }); + test('check stored failures and acks', async () => { const failures = await neutronChain.queryAckFailures(contractAddress); // 3 ack failures, 1 timeout failure, just as described in the tests above expect(failures.failures).toEqual([ - { - channel_id: 'channel-3', + expect.objectContaining({ address: 'neutron1m0z0kk0qqug74n9u9ul23e28x5fszr628h20xwt6jywjpp64xn4qatgvm0', id: '0', - ack_id: '2', + packet: expect.objectContaining({ + source_channel: 'channel-3', + sequence: '2', + }), ack_type: 'ack', - }, - { - channel_id: 'channel-3', + }), + expect.objectContaining({ address: 'neutron1m0z0kk0qqug74n9u9ul23e28x5fszr628h20xwt6jywjpp64xn4qatgvm0', id: '1', - ack_id: '3', + packet: expect.objectContaining({ + source_channel: 'channel-3', + sequence: '3', + }), ack_type: 'ack', - }, - { - channel_id: 'channel-3', + }), + expect.objectContaining({ address: 'neutron1m0z0kk0qqug74n9u9ul23e28x5fszr628h20xwt6jywjpp64xn4qatgvm0', id: '2', - ack_id: '4', + packet: expect.objectContaining({ + source_channel: 'channel-3', + sequence: '4', + }), ack_type: 'ack', - }, - { - channel_id: 'channel-3', + }), + expect.objectContaining({ address: 'neutron1m0z0kk0qqug74n9u9ul23e28x5fszr628h20xwt6jywjpp64xn4qatgvm0', id: '3', - ack_id: '5', + packet: expect.objectContaining({ + source_channel: 'channel-3', + sequence: '5', + }), ack_type: 'timeout', - }, + }), ]); const acks = await getAcks(neutronChain, contractAddress); // no acks at all because all sudo handling cases resulted in an error expect(acks).toEqual([]); }); + + test('failed attempt to resubmit failure', async () => { + // Mock sudo handler to fail + await neutronAccount.executeContract( + contractAddress, + JSON.stringify({ + integration_tests_set_sudo_failure_mock: { + state: 'enabled', + }, + }), + ); + + await neutronChain.blockWaiter.waitBlocks(5); + + // Try to resubmit failure + const failuresResBefore = await neutronChain.queryAckFailures( + contractAddress, + ); + await expect( + neutronAccount.executeContract( + contractAddress, + JSON.stringify({ + resubmit_failure: { + failure_id: +failuresResBefore.failures[0].id, + }, + }), + ), + ).rejects.toThrowError(); + + await neutronChain.blockWaiter.waitBlocks(5); + + // check that failures count is the same + const failuresResAfter = await neutronChain.queryAckFailures( + contractAddress, + ); + expect(failuresResAfter.failures.length).toEqual(4); + + // make sure contract's state hasn't been changed + const acks = await getAcks(neutronChain, contractAddress); + expect(acks.length).toEqual(0); + + // Restore sudo handler's normal state + await neutronAccount.executeContract( + contractAddress, + JSON.stringify({ + integration_tests_unset_sudo_failure_mock: {}, + }), + ); + await neutronChain.blockWaiter.waitBlocks(5); + }); + + test('successful resubmit failure', async () => { + // Resubmit failure + const failuresResBefore = await neutronChain.queryAckFailures( + contractAddress, + ); + const failure = failuresResBefore.failures[0]; + const failureId = failure.id; + const res = await neutronAccount.executeContract( + contractAddress, + JSON.stringify({ + resubmit_failure: { + failure_id: +failureId, + }, + }), + ); + expect(res.code).toBe(0); + + await neutronChain.blockWaiter.waitBlocks(5); + + // check that failures count is changed + const failuresResAfter = await neutronChain.queryAckFailures( + contractAddress, + ); + expect(failuresResAfter.failures.length).toEqual(3); + + // make sure contract's state has been changed + const acks = await getAcks(neutronChain, contractAddress); + expect(acks.length).toEqual(1); + expect(acks[0].sequence_id).toEqual(+failure.packet.sequence); + }); }); }); });