diff --git a/package.json b/package.json index 8a9010d1..a0278eb7 100644 --- a/package.json +++ b/package.json @@ -23,6 +23,7 @@ "test:dao": "NO_WAIT_CHANNEL1=1 NO_WAIT_HTTP2=1 NO_WAIT_CHANNEL2=1 NO_WAIT_DELAY=1 jest -b src/testcases/parallel/dao_assert", "test:globalfee": "jest -b src/testcases/run_in_band/globalfee", "test:ibc_hooks": "jest -b src/testcases/run_in_band/ibc_hooks", + "test:parameters": "jest -b src/testcases/run_in_band/parameters", "test:tokenfactory": "jest -b src/testcases/parallel/tokenfactory", "test:overrule": "jest -b src/testcases/parallel/overrule", "test:tge:vesting_lp_vault": "jest -b src/testcases/parallel/tge.vesting_lp_vault", @@ -85,4 +86,4 @@ "engines": { "node": ">=11.0 <17" } -} \ No newline at end of file +} diff --git a/src/helpers/cosmos.ts b/src/helpers/cosmos.ts index 0c05e757..1ca4737a 100644 --- a/src/helpers/cosmos.ts +++ b/src/helpers/cosmos.ts @@ -31,6 +31,14 @@ import { InterchaintxsParamsResponse, } from './types'; import { DEBUG_SUBMIT_TX, getContractBinary } from './env'; +import { + ParamsContractmanagerInfo, + ParamsCronInfo, + ParamsFeeburnerInfo, + ParamsFeerefunderInfo, + ParamsInterchainqueriesInfo, + ParamsTokenfactoryInfo, +} from './proposal'; const adminmodule = AdminProto.adminmodule.adminmodule; export const NEUTRON_DENOM = process.env.NEUTRON_DENOM || 'untrn'; @@ -68,6 +76,28 @@ export type TotalBurnedNeutronsAmountResponse = { }; }; +export type ParamsContractmanagerResponse = { + params: ParamsContractmanagerInfo; +}; + +export type ParamsCronResponse = { + params: ParamsCronInfo; +}; + +export type ParamsFeerefunderResponse = { + params: ParamsFeerefunderInfo; +}; + +export type ParamsFeeburnerResponse = { + params: ParamsFeeburnerInfo; +}; + +export type ParamsTokenfactoryResponse = { + params: ParamsTokenfactoryInfo; +}; +export type ParamsInterchainqueriesResponse = { + params: ParamsInterchainqueriesInfo; +}; cosmosclient.codec.register( '/neutron.interchainqueries.MsgRemoveInterchainQueryRequest', neutron.interchainqueries.MsgRemoveInterchainQueryRequest, @@ -201,7 +231,7 @@ export class CosmosWrapper { return account.sequence; } - async queryInterchainqueriesParams(): Promise { + async queryInterchainqueriesParams(): Promise { const req = await axios.get( `${this.sdk.url}/neutron/interchainqueries/params`, ); @@ -209,6 +239,42 @@ export class CosmosWrapper { return req.data; } + async queryFeeburnerParams(): Promise { + const req = await axios.get(`${this.sdk.url}/neutron/feeburner/params`); + + return req.data; + } + + async queryFeerefunderParams(): Promise { + const req = await axios.get( + `${this.sdk.url}/neutron-org/neutron/feerefunder/params`, + ); + + return req.data; + } + + async queryContractmanagerParams(): Promise { + const req = await axios.get( + `${this.sdk.url}/neutron/contractmanager/params`, + ); + + return req.data; + } + + async queryCronParams(): Promise { + const req = await axios.get(`${this.sdk.url}/neutron/cron/params`); + + return req.data; + } + + async queryTokenfactoryParams(): Promise { + const req = await axios.get( + `${this.sdk.url}/osmosis/tokenfactory/v1beta1/params`, + ); + + return req.data; + } + async queryDelegations(delegatorAddr: cosmosclient.AccAddress): Promise { const balances = await cosmosclient.rest.staking.delegatorDelegations( this.sdk, diff --git a/src/helpers/dao.ts b/src/helpers/dao.ts index 34112c43..fd412004 100644 --- a/src/helpers/dao.ts +++ b/src/helpers/dao.ts @@ -29,8 +29,14 @@ import { SendProposalInfo, unpinCodesProposal, updateAdminProposal, - updateInterchaintxsParamsProposal, + updateContractmanagerParamsProposal, + ParamsCronInfo, + ParamsFeerefunderInfo, + ParamsInterchainqueriesInfo, + ParamsInterchaintxsInfo, + ParamsTokenfactoryInfo, upgradeProposal, + ParamsFeeburnerInfo, } from './proposal'; import { ibc } from '../generated/ibc/proto'; import cosmosclient from '@cosmos-client/core'; @@ -1216,11 +1222,121 @@ export class DaoMember { async submitUpdateParamsInterchaintxsProposal( title: string, description: string, - msgSubmitTxMaxMessages: number, + message: ParamsInterchaintxsInfo, amount: string, ): Promise { - const message = updateInterchaintxsParamsProposal({ - msg_submit_tx_max_messages: msgSubmitTxMaxMessages, + return await this.submitSingleChoiceProposal( + title, + description, + [message], + amount, + ); + } + + /** + * submitUpdateParamsInterchainqueriesProposal creates proposal which changes params of interchaintxs module. + */ + + async submitUpdateParamsInterchainqueriesProposal( + title: string, + description: string, + message: ParamsInterchainqueriesInfo, + amount: string, + ): Promise { + return await this.submitSingleChoiceProposal( + title, + description, + [message], + amount, + ); + } + + /** + * submitUpdateParamsTokenfactoryProposal creates proposal which changes params of tokenfactory module. + */ + + async submitUpdateParamsTokenfactoryProposal( + title: string, + description: string, + message: ParamsTokenfactoryInfo, + amount: string, + ): Promise { + return await this.submitSingleChoiceProposal( + title, + description, + [message], + amount, + ); + } + + /** + * submitUpdateParamsFeeburnerProposal creates proposal which changes some params of feeburner module. + */ + + async submitUpdateParamsFeeburnerProposal( + title: string, + description: string, + message: ParamsFeeburnerInfo, + amount: string, + ): Promise { + return await this.submitSingleChoiceProposal( + title, + description, + [message], + amount, + ); + } + + /** + * submitUpdateParamsFeerefunderProposal creates proposal which changes some params of feerefunder module. + */ + + async submitUpdateParamsFeerefunderProposal( + title: string, + description: string, + amount: string, + message: ParamsFeerefunderInfo = { + min_fee: { recv_fee: null, ack_fee: null, timeout_fee: null }, + }, + ): Promise { + return await this.submitSingleChoiceProposal( + title, + description, + [message], + amount, + ); + } + + /** + * submitUpdateParamsCronProposal creates proposal which changes soe params of cron module. + */ + + async submitUpdateParamsCronProposal( + title: string, + description: string, + message: ParamsCronInfo, + amount: string, + ): Promise { + return await this.submitSingleChoiceProposal( + title, + description, + [message], + amount, + ); + } + + /** + * submitUpdateParamsContractmanageProposal creates proposal which changes some params of contractmanager module. + */ + + async submitUpdateParamsContractmanageProposal( + title: string, + description: string, + sudoCallGasLimit: string, + amount: string, + ): Promise { + const message = updateContractmanagerParamsProposal({ + sudo_call_gas_limit: sudoCallGasLimit, }); return await this.submitSingleChoiceProposal( title, diff --git a/src/helpers/proposal.ts b/src/helpers/proposal.ts index e5284364..360e2671 100644 --- a/src/helpers/proposal.ts +++ b/src/helpers/proposal.ts @@ -16,9 +16,42 @@ export type PinCodesInfo = { codes_ids: number[]; }; -export type UpdateParamsInterchaintxsInfo = { +export type ParamsInterchaintxsInfo = { msg_submit_tx_max_messages: number; }; + +export type ParamsInterchainqueriesInfo = { + query_submit_timeout: number; + query_deposit: null; + tx_query_removal_limit: number; +}; + +export type ParamsTokenfactoryInfo = { + denom_creation_fee: any; + denom_creation_gas_consume: number; +}; + +export type ParamsFeeburnerInfo = { + treasury_address: string; +}; + +export type ParamsFeerefunderInfo = { + min_fee: { + recv_fee: any; + ack_fee: any; + timeout_fee: any; + }; +}; + +export type ParamsCronInfo = { + security_address: string; + limit: number; +}; + +export type ParamsContractmanagerInfo = { + sudo_call_gas_limit: string; +}; + export type UpdateAdmin = { sender: string; contract: string; @@ -196,7 +229,7 @@ export const unpinCodesProposal = (info: PinCodesInfo): any => ({ }); export const updateInterchaintxsParamsProposal = ( - info: UpdateParamsInterchaintxsInfo, + info: ParamsInterchaintxsInfo, ): any => ({ custom: { submit_admin_proposal: { @@ -215,6 +248,129 @@ export const updateInterchaintxsParamsProposal = ( }, }); +export const updateInterchainqueriesParamsProposal = ( + info: ParamsInterchainqueriesInfo, +): any => ({ + custom: { + submit_admin_proposal: { + admin_proposal: { + proposal_execute_message: { + message: JSON.stringify({ + '@type': '/neutron.interchainqueries.MsgUpdateParams', + authority: ADMIN_MODULE_ADDRESS, + params: { + query_submit_timeout: info.query_submit_timeout, + query_deposit: null, + tx_query_removal_limit: info.tx_query_removal_limit, + }, + }), + }, + }, + }, + }, +}); + +export const updateTokenfacoryParamsProposal = ( + info: ParamsTokenfactoryInfo, +): any => ({ + custom: { + submit_admin_proposal: { + admin_proposal: { + proposal_execute_message: { + message: JSON.stringify({ + '@type': '/osmosis.tokenfactory.v1beta1.MsgUpdateParams', + authority: ADMIN_MODULE_ADDRESS, + params: { + denom_creation_fee: info.denom_creation_fee, + denom_creation_gas_consume: info.denom_creation_gas_consume, + fee_collector_address: null, + }, + }), + }, + }, + }, + }, +}); + +export const updateFeeburnerParamsProposal = ( + info: ParamsFeeburnerInfo, +): any => ({ + custom: { + submit_admin_proposal: { + admin_proposal: { + proposal_execute_message: { + message: JSON.stringify({ + '@type': '/neutron.feeburner.MsgUpdateParams', + authority: ADMIN_MODULE_ADDRESS, + params: { + treasury_address: info.treasury_address, + }, + }), + }, + }, + }, + }, +}); + +export const updateFeerefunderParamsProposal = ( + info: ParamsFeerefunderInfo, +): any => ({ + custom: { + submit_admin_proposal: { + admin_proposal: { + proposal_execute_message: { + message: JSON.stringify({ + '@type': '/neutron.feerefunder.MsgUpdateParams', + authority: ADMIN_MODULE_ADDRESS, + params: { + min_fee: info.min_fee, + }, + }), + }, + }, + }, + }, +}); + +export const updateCronParamsProposal = (info: ParamsCronInfo): any => ({ + custom: { + submit_admin_proposal: { + admin_proposal: { + proposal_execute_message: { + message: JSON.stringify({ + '@type': '/neutron.cron.MsgUpdateParams', + authority: ADMIN_MODULE_ADDRESS, + params: { + security_address: info.security_address, + limit: info.limit, + }, + }), + }, + }, + }, + }, +}); + +export const updateContractmanagerParamsProposal = ( + info: ParamsContractmanagerInfo, +): any => ({ + custom: { + submit_admin_proposal: { + admin_proposal: { + proposal_execute_message: { + message: JSON.stringify({ + '@type': '/neutron.contractmanager.MsgUpdateParams', + authority: ADMIN_MODULE_ADDRESS, + params: { + sudo_call_gas_limit: info.sudo_call_gas_limit, + }, + }), + }, + }, + }, + }, +}); + export const updateAdminProposal = (info: UpdateAdmin): any => ({ custom: { submit_admin_proposal: { diff --git a/src/helpers/types.ts b/src/helpers/types.ts index 38f4f9d0..ae303e68 100644 --- a/src/helpers/types.ts +++ b/src/helpers/types.ts @@ -266,6 +266,13 @@ export type IcaHostParamsResponse = { }; }; +export type InterchainqueriesParamsResponse = { + params: { + query_submit_timeout: number; + tx_query_removal_limit: number; + }; +}; + export type InterchaintxsParamsResponse = { params: { msg_submit_tx_max_messages: string; diff --git a/src/testcases/parallel/governance.test.ts b/src/testcases/parallel/governance.test.ts index 9e21b5d8..c9d16817 100644 --- a/src/testcases/parallel/governance.test.ts +++ b/src/testcases/parallel/governance.test.ts @@ -8,6 +8,7 @@ import { TestStateLocalCosmosTestNet } from '../common_localcosmosnet'; import { getWithAttempts } from '../../helpers/wait'; import { NeutronContract } from '../../helpers/types'; import { Dao, DaoMember, getDaoContracts } from '../../helpers/dao'; +import { updateInterchaintxsParamsProposal } from '../../helpers/proposal'; describe('Neutron / Governance', () => { let testState: TestStateLocalCosmosTestNet; @@ -393,7 +394,9 @@ describe('Neutron / Governance', () => { await daoMember1.submitUpdateParamsInterchaintxsProposal( 'Proposal #20', 'Update interchaintxs params', - 11, + updateInterchaintxsParamsProposal({ + msg_submit_tx_max_messages: 11, + }), '1000', ); }); diff --git a/src/testcases/run_in_band/parameters.test.ts b/src/testcases/run_in_band/parameters.test.ts new file mode 100644 index 00000000..72e1ced6 --- /dev/null +++ b/src/testcases/run_in_band/parameters.test.ts @@ -0,0 +1,359 @@ +import { + CosmosWrapper, + NEUTRON_DENOM, + WalletWrapper, +} from '../../helpers/cosmos'; +import { TestStateLocalCosmosTestNet } from '../common_localcosmosnet'; +import { getWithAttempts } from '../../helpers/wait'; +import { Dao, DaoMember, getDaoContracts } from '../../helpers/dao'; +import { + updateContractmanagerParamsProposal, + updateCronParamsProposal, + updateFeeburnerParamsProposal, + updateInterchainqueriesParamsProposal, + updateInterchaintxsParamsProposal, + updateTokenfacoryParamsProposal, +} from '../../helpers/proposal'; + +describe('Neutron / Parameters', () => { + let testState: TestStateLocalCosmosTestNet; + let neutronChain: CosmosWrapper; + let neutronAccount: WalletWrapper; + let daoMember1: DaoMember; + let dao: Dao; + + beforeAll(async () => { + testState = new TestStateLocalCosmosTestNet(); + await testState.init(); + neutronChain = new CosmosWrapper( + testState.sdk1, + testState.blockWaiter1, + NEUTRON_DENOM, + ); + neutronAccount = new WalletWrapper( + neutronChain, + testState.wallets.qaNeutron.genQaWal1, + ); + const daoCoreAddress = (await neutronChain.getChainAdmins())[0]; + const daoContracts = await getDaoContracts(neutronChain, daoCoreAddress); + dao = new Dao(neutronChain, daoContracts); + daoMember1 = new DaoMember(neutronAccount, dao); + }); + + describe('prepare: bond funds', () => { + test('bond form wallet 1', async () => { + await daoMember1.bondFunds('1000'); + await getWithAttempts( + neutronChain.blockWaiter, + async () => + await dao.queryVotingPower(daoMember1.user.wallet.address.toString()), + async (response) => response.power == 1000, + 20, + ); + }); + test('check voting power', async () => { + await getWithAttempts( + neutronChain.blockWaiter, + async () => await dao.queryTotalVotingPower(), + async (response) => response.power == 1000, + 20, + ); + }); + }); + + describe('Interchain queries params proposal', () => { + test('create proposal', async () => { + await daoMember1.submitUpdateParamsInterchainqueriesProposal( + 'Proposal #1', + 'Param change proposal. This one will pass', + updateInterchainqueriesParamsProposal({ + query_submit_timeout: 30, + query_deposit: null, + tx_query_removal_limit: 20, + }), + '1000', + ); + }); + + describe('vote for proposal', () => { + const proposalId = 1; + test('vote YES', async () => { + await daoMember1.voteYes(proposalId); + }); + }); + + describe('execute proposal', () => { + const proposalId = 1; + let paramsBefore; + test('check if proposal is passed', async () => { + await dao.checkPassedProposal(proposalId); + }); + test('execute passed proposal', async () => { + paramsBefore = await neutronChain.queryInterchainqueriesParams(); + const host = await neutronChain.queryHostEnabled(); + expect(host).toEqual(true); + await daoMember1.executeProposalWithAttempts(proposalId); + }); + test('check if params changed after proposal execution', async () => { + const paramsAfter = await neutronChain.queryInterchainqueriesParams(); + expect(paramsAfter.params.query_submit_timeout).not.toEqual( + paramsBefore.params.query_submit_timeout, + ); + expect(paramsAfter.params.tx_query_removal_limit).not.toEqual( + paramsBefore.params.tx_query_removal_limit, + ); + expect(paramsAfter.params.query_submit_timeout).toEqual('30'); + expect(paramsAfter.params.tx_query_removal_limit).toEqual('20'); + }); + }); + }); + + describe('Tokenfactory params proposal', () => { + test('create proposal', async () => { + await daoMember1.submitUpdateParamsTokenfactoryProposal( + 'Proposal #2', + 'Tokenfactory params proposal', + updateTokenfacoryParamsProposal({ + denom_creation_fee: null, + denom_creation_gas_consume: 100000, + }), + '1000', + ); + }); + + describe('vote for proposal', () => { + const proposalId = 2; + test('vote YES', async () => { + await daoMember1.voteYes(proposalId); + }); + }); + + describe('execute proposal', () => { + const proposalId = 2; + let paramsBefore; + test('check if proposal is passed', async () => { + await dao.checkPassedProposal(proposalId); + }); + test('execute passed proposal', async () => { + paramsBefore = await neutronChain.queryTokenfactoryParams(); + await daoMember1.executeProposalWithAttempts(proposalId); + }); + test('check if params changed after proposal execution', async () => { + const paramsAfter = await neutronChain.queryTokenfactoryParams(); + + expect(paramsAfter.params.denom_creation_fee).toEqual( + paramsBefore.params.denom_creation_fee, + ); + expect(paramsAfter.params.denom_creation_gas_consume).not.toEqual( + paramsBefore.params.denom_creation_gas_consume, + ); + expect(paramsAfter.params.denom_creation_fee).toHaveLength(0); + expect(paramsAfter.params.denom_creation_gas_consume).toEqual('100000'); + }); + }); + }); + + describe('Feeburner params proposal', () => { + test('create proposal', async () => { + await daoMember1.submitUpdateParamsFeeburnerProposal( + 'Proposal #3', + 'Feeburner params proposal', + updateFeeburnerParamsProposal({ + treasury_address: dao.contracts.voting.address, + }), + '1000', + ); + }); + + describe('vote for proposal', () => { + const proposalId = 3; + test('vote YES from wallet 1', async () => { + await daoMember1.voteYes(proposalId); + }); + }); + + describe('execute proposal', () => { + const proposalId = 3; + let paramsBefore; + test('check if proposal is passed', async () => { + await dao.checkPassedProposal(proposalId); + }); + test('execute passed proposal', async () => { + paramsBefore = await neutronChain.queryFeeburnerParams(); + await daoMember1.executeProposalWithAttempts(proposalId); + }); + test('check if params changed after proposal execution', async () => { + const paramsAfter = await neutronChain.queryFeeburnerParams(); + expect(paramsAfter.params.treasury_address).not.toEqual( + paramsBefore.params.treasury_address, + ); + expect(paramsAfter.params.treasury_address).toEqual( + dao.contracts.voting.address, + ); + }); + }); + }); + + describe('Feerefunder params proposal', () => { + test('create proposal', async () => { + await daoMember1.submitUpdateParamsFeerefunderProposal( + 'Proposal #4', + 'Feerefunder update params proposal', + '1000', + ); + }); + + describe('vote for proposal', () => { + const proposalId = 4; + test('vote YES', async () => { + await daoMember1.voteYes(proposalId); + }); + }); + + describe('execute proposal', () => { + const proposalId = 4; + let paramsBefore; + test('check if proposal is passed', async () => { + await dao.checkPassedProposal(proposalId); + }); + test('execute passed proposal', async () => { + paramsBefore = await neutronChain.queryFeerefunderParams(); + await daoMember1.executeProposalWithAttempts(proposalId); + }); + test('check if params changed after proposal execution', async () => { + const paramsAfter = await neutronChain.queryFeerefunderParams(); + expect(paramsAfter.params.min_fee.recv_fee).toEqual( + paramsBefore.params.min_fee.recv_fee, + ); + expect(paramsAfter.params.min_fee.ack_fee).not.toEqual( + paramsBefore.params.min_fee.ack_fee, + ); + expect(paramsAfter.params.min_fee.timeout_fee).not.toEqual( + paramsBefore.params.min_fee.timeout_fee, + ); + // toHaveLength(0) equals fee struct is '[]' + expect(paramsAfter.params.min_fee.recv_fee).toHaveLength(0); + expect(paramsAfter.params.min_fee.ack_fee).toHaveLength(0); + expect(paramsAfter.params.min_fee.timeout_fee).toHaveLength(0); + }); + }); + }); + + describe('Cron params proposal', () => { + test('create proposal', async () => { + await daoMember1.submitUpdateParamsCronProposal( + 'Proposal #5', + 'Cron update params proposal. Will pass', + updateCronParamsProposal({ + security_address: dao.contracts.voting.address, + limit: 10, + }), + '1000', + ); + }); + + describe('vote for proposal', () => { + const proposalId = 5; + test('vote YES from wallet 1', async () => { + await daoMember1.voteYes(proposalId); + }); + }); + + describe('execute proposal', () => { + const proposalId = 5; + let paramsBefore; + test('check if proposal is passed', async () => { + await dao.checkPassedProposal(proposalId); + }); + test('execute passed proposal', async () => { + paramsBefore = await neutronChain.queryCronParams(); + await daoMember1.executeProposalWithAttempts(proposalId); + }); + test('check if params changed after proposal execution', async () => { + const paramsAfter = await neutronChain.queryCronParams(); + expect(paramsAfter.params.security_address).not.toEqual( + paramsBefore.params.security_address, + ); + expect(paramsAfter.params.security_address).toEqual( + dao.contracts.voting.address, + ); + }); + }); + }); + + describe('Contractanager params proposal', () => { + test('create proposal', async () => { + await daoMember1.submitUpdateParamsContractmanageProposal( + 'Proposal #6', + 'Contractanager params proposal', + updateContractmanagerParamsProposal({ + sudo_call_gas_limit: '1000', + }), + '1000', + ); + }); + + describe('vote for proposal', () => { + const proposalId = 6; + test('vote YES', async () => { + await daoMember1.voteYes(proposalId); + }); + }); + + describe('execute proposal', () => { + const proposalId = 6; + let paramsBefore; + test('check if proposal is passed', async () => { + await dao.checkPassedProposal(proposalId); + }); + test('execute passed proposal', async () => { + paramsBefore = await neutronChain.queryContractmanagerParams(); + await daoMember1.executeProposalWithAttempts(proposalId); + }); + test('check if params changed after proposal execution', async () => { + const paramsAfter = await neutronChain.queryContractmanagerParams(); + expect(paramsAfter.params.sudo_call_gas_limit).not.toEqual( + paramsBefore.params.sudo_call_gas_limit, + ); + expect(paramsAfter.params.sudo_call_gas_limit).toEqual('1000'); + }); + }); + }); + + describe('Interchaintxs params proposal', () => { + test('create proposal', async () => { + await daoMember1.submitUpdateParamsInterchaintxsProposal( + 'Proposal #7', + 'Update interchaintxs params', + updateInterchaintxsParamsProposal({ + msg_submit_tx_max_messages: 11, + }), + '1000', + ); + }); + + describe('vote for proposal', () => { + const proposalId = 7; + test('vote YES', async () => { + await daoMember1.voteYes(proposalId); + }); + }); + + describe('execute proposal', () => { + const proposalId = 7; + let paramBefore; + test('check if proposal is passed', async () => { + await dao.checkPassedProposal(proposalId); + }); + test('execute passed proposal', async () => { + paramBefore = await neutronChain.queryMaxTxsAllowed(); + await daoMember1.executeProposalWithAttempts(proposalId); + }); + test('check if params changed after proposal execution', async () => { + const paramAfter = await neutronChain.queryMaxTxsAllowed(); + expect(paramAfter).not.toEqual(paramBefore); + expect(paramAfter).toEqual('11'); + }); + }); + }); +});