Skip to content

Commit

Permalink
Merge pull request #220 from neutron-org/feat/save-failed-result-back…
Browse files Browse the repository at this point in the history
…port

Feat: save failed result backport #ntrn-80
  • Loading branch information
pr0n00gler authored Oct 13, 2023
2 parents 8c1dbf5 + 57bf970 commit 257f16f
Show file tree
Hide file tree
Showing 5 changed files with 186 additions and 10 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
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@
"@cosmos-client/core": "0.45.13",
"@cosmos-client/cosmwasm": "^0.20.1",
"@cosmos-client/ibc": "^1.2.1",
"@neutron-org/neutronjsplus": "^0.0.9",
"@neutron-org/neutronjsplus": "^0.0.15",
"@types/lodash": "^4.14.182",
"@types/long": "^4.0.2",
"axios": "^0.27.2",
Expand Down Expand Up @@ -86,4 +86,4 @@
"engines": {
"node": ">=11.0 <17"
}
}
}
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
9 changes: 5 additions & 4 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1591,15 +1591,16 @@
"@jridgewell/resolve-uri" "^3.0.3"
"@jridgewell/sourcemap-codec" "^1.4.10"

"@neutron-org/neutronjsplus@^0.0.9":
version "0.0.9"
resolved "https://registry.yarnpkg.com/@neutron-org/neutronjsplus/-/neutronjsplus-0.0.9.tgz#d4aea3ddc828438b22cd60e0f5746063f957614d"
integrity sha512-yAzfPiwgE9500lbD02zhQXGbgzwl9OkMWC58y1oMKav0LIHLN9In8a9t4Wg6QGFg0eGxW0teQZrXudGNufW4qQ==
"@neutron-org/neutronjsplus@^0.0.15":
version "0.0.15"
resolved "https://registry.yarnpkg.com/@neutron-org/neutronjsplus/-/neutronjsplus-0.0.15.tgz#0bcebb8138fc380bed1210474ec9189340ec874d"
integrity sha512-kF82bsu74QrXPZya7rS8/+Ziu+sht9nvjKTfKBIsExVr2U+VePA8D/CJkqx7ts4yi1D7ECATgGFCA8GJW8I4VQ==
dependencies:
"@cosmos-client/core" "0.45.13"
"@cosmos-client/cosmwasm" "^0.20.1"
"@cosmos-client/ibc" "^1.2.1"
axios "^0.27.2"
long "^5.2.1"
merkletreejs "^0.3.9"

"@nodelib/fs.scandir@2.1.5":
Expand Down

0 comments on commit 257f16f

Please sign in to comment.