Skip to content

Commit

Permalink
backport: save failed result for proposal execution
Browse files Browse the repository at this point in the history
  • Loading branch information
NeverHappened committed Oct 12, 2023
1 parent a65b977 commit 39ca1ae
Show file tree
Hide file tree
Showing 3 changed files with 179 additions and 4 deletions.
2 changes: 1 addition & 1 deletion .tool-versions
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
nodejs 14.20.0
nodejs 16.20.0
yarn 1.22.10
golang 1.18
1 change: 1 addition & 0 deletions src/testcases/parallel/overrule.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ describe('Neutron / Subdao', () => {
daoContracts.core.address,
daoContracts.proposals.overrule?.pre_propose?.address || '',
neutronAccount1.wallet.address.toString(),
false, // do not close proposal on failure since otherwise we wont get an error exception from submsgs
);

subdaoMember1 = new dao.DaoMember(neutronAccount1, subDao);
Expand Down
180 changes: 177 additions & 3 deletions src/testcases/parallel/subdao.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@ import {
TestStateLocalCosmosTestNet,
types,
wait,
proposal,
} from '@neutron-org/neutronjsplus';
import Long from 'long';

const config = require('../../config.json');

Expand Down Expand Up @@ -113,7 +115,7 @@ describe('Neutron / Subdao', () => {
expect(timelockedProp.msgs).toHaveLength(1);
});

test('execute timelocked: nonexistant ', async () => {
test('execute timelocked: nonexistant', async () => {
await expect(
subdaoMember1.executeTimelockedProposal(1_000_000),
).rejects.toThrow(/SingleChoiceProposal not found/);
Expand All @@ -135,6 +137,9 @@ describe('Neutron / Subdao', () => {
expect(timelockedProp.id).toEqual(proposalId);
expect(timelockedProp.status).toEqual('execution_failed');
expect(timelockedProp.msgs).toHaveLength(1);

const error = await subDao.getTimelockedProposalError(proposalId);
expect(error).toEqual('codespace: sdk, code: 5'); // 'insufficient funds' error
});

test('execute timelocked(ExecutionFailed): WrongStatus error', async () => {
Expand All @@ -148,6 +153,173 @@ describe('Neutron / Subdao', () => {
overruleTimelockedProposalMock(subdaoMember1, proposalId),
).rejects.toThrow(/Wrong proposal status \(execution_failed\)/);
});

let proposalId2: number;
test('proposal timelock 2 with two messages, one of them fails', async () => {
// pack two messages in one proposal
const failMessage = proposal.paramChangeProposal({
title: 'paramchange',
description: 'paramchange',
subspace: 'icahost',
key: 'HostEnabled',
value: '123123123', // expected boolean, provided number
});
const goodMessage = proposal.sendProposal({
to: neutronAccount2.wallet.address.toString(),
denom: NEUTRON_DENOM,
amount: '100',
});
const fee = {
gas_limit: Long.fromString('4000000'),
amount: [{ denom: NEUTRON_DENOM, amount: '10000' }],
};
proposalId2 = await subdaoMember1.submitSingleChoiceProposal(
'proposal2',
'proposal2',
[goodMessage, failMessage],
'1000',
'single',
fee,
);

const timelockedProp = await subdaoMember1.supportAndExecuteProposal(
proposalId2,
);

expect(timelockedProp.id).toEqual(proposalId2);
expect(timelockedProp.status).toEqual('timelocked');
expect(timelockedProp.msgs).toHaveLength(1);
});

test('execute timelocked 2: execution failed', async () => {
await neutronAccount1.msgSend(subDao.contracts.core.address, '100000'); // fund the subdao treasury
const balance2 = await neutronAccount2.queryDenomBalance(NEUTRON_DENOM);

//wait for timelock durations
await wait.waitSeconds(20);
// timelocked proposal execution failed due to invalid param value
await subdaoMember1.executeTimelockedProposal(proposalId2);
const timelockedProp = await subDao.getTimelockedProposal(proposalId2);
expect(timelockedProp.id).toEqual(proposalId2);
expect(timelockedProp.status).toEqual('execution_failed');
expect(timelockedProp.msgs).toHaveLength(1);

const error = await subDao.getTimelockedProposalError(proposalId2);
expect(error).toEqual('codespace: undefined, code: 1');

// check that goodMessage failed as well
const balance2After = await neutronAccount2.queryDenomBalance(
NEUTRON_DENOM,
);
expect(balance2After).toEqual(balance2);

// cannot execute failed proposal with closeOnProposalExecutionFailed=true
await expect(
subdaoMember1.executeTimelockedProposal(proposalId2),
).rejects.toThrow(/Wrong proposal status \(execution_failed\)/);
await neutronChain.blockWaiter.waitBlocks(2);
});

test('change subdao proposal config with closeOnProposalExecutionFailed = false', async () => {
const subdaoConfig =
await neutronChain.queryContract<dao.SubdaoProposalConfig>(
subDao.contracts.proposals.single.address,
{
config: {},
},
);
expect(subdaoConfig.close_proposal_on_execution_failure).toEqual(true);
subdaoConfig.close_proposal_on_execution_failure = false;

const proposalId = await subdaoMember1.submitUpdateConfigProposal(
'updateconfig',
'updateconfig',
subdaoConfig,
'1000',
);
const timelockedProp = await subdaoMember1.supportAndExecuteProposal(
proposalId,
);
expect(timelockedProp.status).toEqual('timelocked');
//wait for timelock durations
await wait.waitSeconds(20);
await subdaoMember1.executeTimelockedProposal(proposalId); // should execute no problem

await neutronChain.blockWaiter.waitBlocks(2);

const subdaoConfigAfter =
await neutronChain.queryContract<dao.SubdaoProposalConfig>(
subDao.contracts.proposals.single.address,
{
config: {},
},
);
expect(subdaoConfigAfter.close_proposal_on_execution_failure).toEqual(
false,
);
});

let proposalId3: number;
test('proposal timelock 3 with not enough funds initially to resubmit later', async () => {
proposalId3 = await subdaoMember1.submitSendProposal('send', 'send', [
{
recipient: demo2Addr.toString(),
amount: 200000,
denom: neutronChain.denom,
},
]);

const timelockedProp = await subdaoMember1.supportAndExecuteProposal(
proposalId3,
);

expect(timelockedProp.id).toEqual(proposalId3);
expect(timelockedProp.status).toEqual('timelocked');
expect(timelockedProp.msgs).toHaveLength(1);
});

test('execute timelocked 3: execution failed at first and then successful after funds sent', async () => {
const subdaoConfig =
await neutronChain.queryContract<dao.SubdaoProposalConfig>(
subDao.contracts.proposals.single.address,
{
config: {},
},
);
expect(subdaoConfig.close_proposal_on_execution_failure).toEqual(false);

//wait for timelock durations
await wait.waitSeconds(20);
// timelocked proposal execution failed due to insufficient funds
await expect(
subdaoMember1.executeTimelockedProposal(proposalId3),
).rejects.toThrow(/insufficient funds/);
const timelockedProp = await subDao.getTimelockedProposal(proposalId3);
expect(timelockedProp.id).toEqual(proposalId3);
expect(timelockedProp.status).toEqual('timelocked');
expect(timelockedProp.msgs).toHaveLength(1);

const error = await subDao.getTimelockedProposalError(proposalId3);
// do not have an error because we did not have reply
expect(error).toEqual(null);

await neutronAccount1.msgSend(subDao.contracts.core.address, '300000');

// now that we have funds should execute without problems

const balanceBefore = await neutronChain.queryDenomBalance(
demo2Addr.toString(),
NEUTRON_DENOM,
);
await subdaoMember1.executeTimelockedProposal(proposalId3);
await neutronChain.blockWaiter.waitBlocks(2);
const balanceAfter = await neutronChain.queryDenomBalance(
demo2Addr.toString(),
NEUTRON_DENOM,
);

expect(balanceAfter - balanceBefore).toEqual(200000);
});
});

describe('Timelock: Succeed execution', () => {
Expand Down Expand Up @@ -832,10 +1004,12 @@ describe('Neutron / Subdao', () => {
await subdaoMember1.supportAndExecuteProposal(proposalId);

await wait.waitSeconds(20);
await subdaoMember1.executeTimelockedProposal(proposalId);
await expect(
subdaoMember1.executeTimelockedProposal(proposalId),
).rejects.toThrow(/config name cannot be empty/);
const timelockedProp = await subDao.getTimelockedProposal(proposalId);
expect(timelockedProp.id).toEqual(proposalId);
expect(timelockedProp.status).toEqual('execution_failed');
expect(timelockedProp.status).toEqual('timelocked');
expect(timelockedProp.msgs).toHaveLength(1);
const configAfter = await neutronChain.queryContract<dao.SubDaoConfig>(
subDao.contracts.core.address,
Expand Down

0 comments on commit 39ca1ae

Please sign in to comment.