Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat: save failed result backport #ntrn-80 #220

Merged
merged 2 commits into from
Oct 13, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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