diff --git a/package.json b/package.json index b1a54de0..db11d025 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,7 @@ "scripts": { "test": "yarn test:parallel && yarn test:run_in_band", "test:parallel": "jest -b src/testcases/parallel", - "test:run_in_band": "yarn test:tge:auction && yarn test:tge:credits && yarn test:interchaintx && yarn test:interchain_kv_query && yarn test:interchain_tx_query_plain && yarn test:tokenomics && yarn test:reserve && yarn test:ibc_hooks && yarn test:float && yarn test:parameters && yarn test:dex_stargate && yarn test:globalfee && yarn test:dex_bindings && yarn test:pob && yarn test:slinky && yarn test:chain_manager", + "test:run_in_band": "yarn test:tge:auction && yarn test:tge:credits && yarn test:interchaintx && yarn test:interchain_kv_query && yarn test:interchain_tx_query_plain && yarn test:tokenomics && yarn test:reserve && yarn test:ibc_hooks && yarn test:float && yarn test:parameters && yarn test:dex_stargate && yarn test:dex_bindings && yarn test:slinky && yarn test:chain_manager && yarn test:feemarket", "test:simple": "jest -b src/testcases/parallel/simple", "test:slinky": "jest -b src/testcases/run_in_band/slinky", "test:stargate_queries": "jest -b src/testcases/parallel/stargate_queries", @@ -21,7 +21,6 @@ "test:tge:credits": "NO_WAIT_CHANNEL1=1 NO_WAIT_HTTP2=1 NO_WAIT_CHANNEL2=1 NO_WAIT_DELAY=1 jest -b src/testcases/run_in_band/tge.credits", "test:tokenomics": "jest -b src/testcases/run_in_band/tokenomics", "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:chain_manager": "jest -b src/testcases/run_in_band/chain_manager", @@ -33,8 +32,8 @@ "test:voting_registry": "jest -b src/testcases/parallel/voting_registry", "test:float": "NO_WAIT_CHANNEL1=1 NO_WAIT_HTTP2=1 NO_WAIT_CHANNEL2=1 NO_WAIT_DELAY=1 jest -b src/testcases/run_in_band/float", "test:dex_stargate": "NO_WAIT_CHANNEL1=1 NO_WAIT_HTTP2=1 NO_WAIT_CHANNEL2=1 NO_WAIT_DELAY=1 jest -b src/testcases/run_in_band/dex_stargate", - "test:pob": "NO_WAIT_CHANNEL1=1 NO_WAIT_HTTP2=1 NO_WAIT_CHANNEL2=1 NO_WAIT_DELAY=1 jest -b src/testcases/run_in_band/pob", "test:dex_bindings": "NO_WAIT_CHANNEL1=1 NO_WAIT_HTTP2=1 NO_WAIT_CHANNEL2=1 NO_WAIT_DELAY=1 jest -b src/testcases/run_in_band/dex_bindings", + "test:feemarket": "NO_WAIT_CHANNEL1=1 NO_WAIT_HTTP2=1 NO_WAIT_CHANNEL2=1 NO_WAIT_DELAY=1 jest -b src/testcases/run_in_band/feemarket", "gen:proto": "bash ./gen-proto.sh", "lint": "eslint ./src", "fmt": "eslint ./src --fix", @@ -47,7 +46,7 @@ "@cosmos-client/core": "^0.47.4", "@cosmos-client/cosmwasm": "^0.40.3", "@cosmos-client/ibc": "^1.2.1", - "@neutron-org/neutronjsplus": "https://github.com/neutron-org/neutronjsplus.git#b0acb8a3bb7ad3af7cde5f67ba259a94c2fa3e4f", + "@neutron-org/neutronjsplus": "https://github.com/neutron-org/neutronjsplus.git#bd92e7b5ef1d5eb7042b78a957e57561dd379764", "@types/lodash": "^4.14.182", "@types/long": "^5.0.0", "axios": "^0.27.2", @@ -86,9 +85,11 @@ "eslint --max-warnings=0", "jest --bail --findRelatedTests" ], - "./**/*.{ts,tsx,js,jsx,md,json}": ["prettier --write"] + "./**/*.{ts,tsx,js,jsx,md,json}": [ + "prettier --write" + ] }, "engines": { "node": ">=16.0 <17" } -} +} \ No newline at end of file diff --git a/setup/docker-compose.yml b/setup/docker-compose.yml index 74c38d99..1fcf5b9c 100644 --- a/setup/docker-compose.yml +++ b/setup/docker-compose.yml @@ -12,6 +12,7 @@ services: environment: - RUN_BACKGROUND=0 - ORACLE_ADDRESS=oracle:8080 + - FEEMARKET_ENABLED=false networks: - neutron-testing diff --git a/src/testcases/run_in_band/feemarket.test.ts b/src/testcases/run_in_band/feemarket.test.ts new file mode 100644 index 00000000..3624b07b --- /dev/null +++ b/src/testcases/run_in_band/feemarket.test.ts @@ -0,0 +1,270 @@ +import Long from 'long'; +import '@neutron-org/neutronjsplus'; +import { + WalletWrapper, + CosmosWrapper, + NEUTRON_DENOM, + IBC_ATOM_DENOM, + packAnyMsg, +} from '@neutron-org/neutronjsplus/dist/cosmos'; +import { TestStateLocalCosmosTestNet } from '@neutron-org/neutronjsplus'; +import { getWithAttempts } from '@neutron-org/neutronjsplus/dist/wait'; +import { + Dao, + DaoMember, + getDaoContracts, +} from '@neutron-org/neutronjsplus/dist/dao'; +import { DynamicFeesParams } from '@neutron-org/neutronjsplus/dist/feemarket'; +import { DecCoin } from '@neutron-org/neutronjsplus/dist/proto/neutron/cosmos/base/v1beta1/coin_pb'; +import { MsgSend } from '@neutron-org/neutronjsplus/dist/proto/cosmos_sdk/cosmos/bank/v1beta1/tx_pb'; + +const config = require('../../config.json'); + +describe('Neutron / Fee Market', () => { + let testState: TestStateLocalCosmosTestNet; + let neutronChain: CosmosWrapper; + let neutronAccount: WalletWrapper; + let daoMember: DaoMember; + let daoMain: Dao; + + beforeAll(async () => { + testState = new TestStateLocalCosmosTestNet(config); + await testState.init(); + neutronChain = new CosmosWrapper( + testState.sdk1, + testState.blockWaiter1, + NEUTRON_DENOM, + ); + neutronAccount = new WalletWrapper( + neutronChain, + testState.wallets.qaNeutron.genQaWal1, + ); + + const daoCoreAddress = await neutronChain.getNeutronDAOCore(); + const daoContracts = await getDaoContracts(neutronChain, daoCoreAddress); + daoMain = new Dao(neutronChain, daoContracts); + daoMember = new DaoMember(neutronAccount, daoMain); + await daoMember.bondFunds('10000'); + await getWithAttempts( + neutronChain.blockWaiter, + async () => + await daoMain.queryVotingPower( + daoMember.user.wallet.address.toString(), + ), + async (response) => response.power == 10000, + 20, + ); + + await daoMember.user.msgSend(daoMain.contracts.core.address, '1000', { + gas_limit: Long.fromString('200000'), + amount: [{ denom: daoMember.user.chain.denom, amount: '500' }], + }); + await executeSwitchFeemarket(daoMember, 'enable feemarket', true); + }); + + let counter = 1; + + const executeSwitchFeemarket = async ( + daoMember: DaoMember, + kind: string, + enabled: boolean, + ) => { + const params = (await neutronChain.getFeemarketParams()).params; + params.enabled = enabled; + + const chainManagerAddress = (await neutronChain.getChainAdmins())[0]; + const proposalId = await daoMember.submitFeeMarketChangeParamsProposal( + chainManagerAddress, + 'Change Proposal - ' + kind + ' #' + counter, + 'Param change proposal. It will change enabled params of feemarket module.', + '1000', + params, + ); + + await daoMember.voteYes(proposalId, 'single', { + gas_limit: Long.fromString('4000000'), + amount: [{ denom: daoMember.user.chain.denom, amount: '100000' }], + }); + await daoMain.checkPassedProposal(proposalId); + await daoMember.executeProposalWithAttempts(proposalId, { + gas_limit: Long.fromString('4000000'), + amount: [{ denom: daoMember.user.chain.denom, amount: '100000' }], + }); + + counter++; + }; + + const executeChangeGasPrices = async ( + daoMember: DaoMember, + kind: string, + params: DynamicFeesParams, + ) => { + const chainManagerAddress = (await neutronChain.getChainAdmins())[0]; + const proposalId = await daoMember.submitDynamicfeesChangeParamsProposal( + chainManagerAddress, + 'Change Proposal - ' + kind + ' #' + counter, + 'Param change proposal. It will change gas price list of dynamicfees/feemarket module.', + '1000', + params, + ); + + await daoMember.voteYes(proposalId, 'single', { + gas_limit: Long.fromString('4000000'), + amount: [{ denom: daoMember.user.chain.denom, amount: '100000' }], + }); + await daoMain.checkPassedProposal(proposalId); + await daoMember.executeProposalWithAttempts(proposalId, { + gas_limit: Long.fromString('4000000'), + amount: [{ denom: daoMember.user.chain.denom, amount: '100000' }], + }); + + counter++; + }; + + test('success tx', async () => { + const res = await neutronAccount.msgSend( + daoMain.contracts.core.address, + '1000', + { + gas_limit: Long.fromString('200000'), + amount: [{ denom: daoMember.user.chain.denom, amount: '500' }], // 0.0025 + }, + ); + + await neutronChain.blockWaiter.waitBlocks(2); + + expect(res.code).toEqual(0); + }); + + test('failed: insufficient fee', async () => { + await expect( + neutronAccount.msgSend(daoMain.contracts.core.address, '1000', { + gas_limit: Long.fromString('200000'), + amount: [{ denom: daoMember.user.chain.denom, amount: '200' }], // 0.001 + }), + ).rejects.toThrowError( + /error checking fee: got: 200untrn required: 500untrn, minGasPrice: 0.002500000000000000untrn/, + ); + }); + + test('additional ibc denom', async () => { + await expect( + neutronAccount.msgSend(daoMain.contracts.core.address, '1000', { + gas_limit: Long.fromString('200000'), + amount: [{ denom: IBC_ATOM_DENOM, amount: '200' }], + }), + ).rejects.toThrowError( + /unable to get min gas price for denom uibcatom: unknown denom/, + ); + + // 5 ntrn per ATOM, gives atom gas price 5 times lower, 0.0005 IBC_ATOM_DENOM and 0.0025 NTRN + + await executeChangeGasPrices(daoMember, 'dynamicfees gasprices', { + ntrn_prices: [DecCoin.fromJson({ denom: IBC_ATOM_DENOM, amount: '5' })], + }); + + await expect( + neutronAccount.msgSend(daoMain.contracts.core.address, '1000', { + gas_limit: Long.fromString('200000'), + amount: [{ denom: IBC_ATOM_DENOM, amount: '50' }], // 0.00025 + }), + ).rejects.toThrowError( + /error checking fee: got: 50uibcatom required: 100uibcatom, minGasPrice: 0.000500000000000000uibcatom/, + ); + + const res = await neutronAccount.msgSend( + daoMain.contracts.core.address, + '1000', + { + gas_limit: Long.fromString('200000'), + amount: [{ denom: IBC_ATOM_DENOM, amount: '100' }], // 0.0005 + }, + ); + + await neutronChain.blockWaiter.waitBlocks(2); + + expect(res.code).toEqual(0); + }); + + test('disable/enable feemarket module', async () => { + await executeSwitchFeemarket(daoMember, 'disable feemarket', false); + + // feemarket disabled + // with a zero fee we fail due to default cosmos ante handler check + await expect( + neutronAccount.msgSend(daoMain.contracts.core.address, '1000', { + gas_limit: Long.fromString('200000'), + amount: [{ denom: IBC_ATOM_DENOM, amount: '0' }], + }), + ).rejects.toThrowError( + /insufficient fees; got: 0uibcatom required: 500ibc\/27394FB092D2ECCD56123C74F36E4C1F926001CEADA9CA97EA622B25F41E5EB2,500untrn: insufficient fee/, + ); + + await neutronChain.blockWaiter.waitBlocks(2); + + await executeSwitchFeemarket(daoMember, 'enable feemarket', true); + + // feemarket enabled + // with a zero fee we fail due to feemarket ante handler check + await expect( + neutronAccount.msgSend(daoMain.contracts.core.address, '1000', { + gas_limit: Long.fromString('200000'), + amount: [{ denom: daoMember.user.chain.denom, amount: '0' }], + }), + ).rejects.toThrowError( + /error checking fee: got: 0untrn required: 500untrn, minGasPrice: 0.002500000000000000untrn/, + ); + }); + test('gas price gets up and down', async () => { + const msgSend = new MsgSend({ + fromAddress: neutronAccount.wallet.address.toString(), + toAddress: daoMain.contracts.core.address, + amount: [{ denom: neutronAccount.chain.denom, amount: '1000' }], + }); + let ntrnGasPrice = Number( + (await neutronChain.getGasPrice('untrn')).price.amount, + ); + const requiredGas = '30000000'; + // due to rounding poor accuracy, it's recommended pay a little bit more fees + const priceAdjustment = 1.05; + for (let i = 0; i < 15; i++) { + const fees = Math.floor( + Number(requiredGas) * ntrnGasPrice * priceAdjustment, + ).toString(); + // 1200msgs consume ~27m gas + const res = await neutronAccount.execTx( + { + gas_limit: Long.fromString(requiredGas), + amount: [{ denom: daoMember.user.chain.denom, amount: fees }], + }, + new Array(1200).fill( + packAnyMsg('/cosmos.bank.v1beta1.MsgSend', msgSend), + ), + ); + expect(res?.tx_response.code).toEqual(0); + const currNtrnGasPrice = Number( + (await neutronChain.getGasPrice('untrn')).price.amount, + ); + // gas price constantly grows on 95% full blocks + expect(currNtrnGasPrice).toBeGreaterThan(ntrnGasPrice); + ntrnGasPrice = currNtrnGasPrice; + const prices = await neutronChain.getGasPrices(); + console.log(prices); + } + console.log('------'); + for (;;) { + await neutronChain.blockWaiter.waitBlocks(1); + const currNtrnGasPrice = Number( + (await neutronChain.getGasPrice('untrn')).price.amount, + ); + // gas price constantly get down when blocks are empty + expect(currNtrnGasPrice).toBeLessThan(ntrnGasPrice); + ntrnGasPrice = currNtrnGasPrice; + const prices = await neutronChain.getGasPrices(); + console.log(prices); + if (currNtrnGasPrice == 0.0025) { + break; + } + } + }); +}); diff --git a/src/testcases/run_in_band/globalfee.test.ts b/src/testcases/run_in_band/globalfee.test.ts deleted file mode 100644 index 556a74a6..00000000 --- a/src/testcases/run_in_band/globalfee.test.ts +++ /dev/null @@ -1,266 +0,0 @@ -import Long from 'long'; -import '@neutron-org/neutronjsplus'; -import { - WalletWrapper, - CosmosWrapper, - NEUTRON_DENOM, -} from '@neutron-org/neutronjsplus/dist/cosmos'; -import { TestStateLocalCosmosTestNet } from '@neutron-org/neutronjsplus'; -import { getWithAttempts } from '@neutron-org/neutronjsplus/dist/wait'; -import { - Dao, - DaoMember, - getDaoContracts, -} from '@neutron-org/neutronjsplus/dist/dao'; -import { updateGlobalFeeParamsProposal } from '@neutron-org/neutronjsplus/dist/proposal'; -import cosmosclient from '@cosmos-client/core'; - -const config = require('../../config.json'); - -describe('Neutron / Global Fee', () => { - let testState: TestStateLocalCosmosTestNet; - let neutronChain: CosmosWrapper; - let neutronAccount: WalletWrapper; - let daoMember: DaoMember; - let daoMain: Dao; - - beforeAll(async () => { - testState = new TestStateLocalCosmosTestNet(config); - await testState.init(); - neutronChain = new CosmosWrapper( - testState.sdk1, - testState.blockWaiter1, - NEUTRON_DENOM, - ); - neutronAccount = new WalletWrapper( - neutronChain, - testState.wallets.qaNeutron.genQaWal1, - ); - - const daoCoreAddress = await neutronChain.getNeutronDAOCore(); - const daoContracts = await getDaoContracts(neutronChain, daoCoreAddress); - daoMain = new Dao(neutronChain, daoContracts); - daoMember = new DaoMember(neutronAccount, daoMain); - await daoMember.bondFunds('10000'); - await getWithAttempts( - neutronChain.blockWaiter, - async () => - await daoMain.queryVotingPower( - daoMember.user.wallet.address.toString(), - ), - async (response) => response.power == 10000, - 20, - ); - - await daoMember.user.msgSend(daoMain.contracts.core.address, '1000', { - gas_limit: Long.fromString('200000'), - amount: [{ denom: daoMember.user.chain.denom, amount: '500' }], - }); - }); - - afterAll(async () => { - await daoMember.unbondFunds('10000'); - }); - - let counter = 1; - - const executeParamChange = async ( - daoMember: DaoMember, - kind: string, - bypassMinFeeMsgTypes: string[], - minimumGasPrices: cosmosclient.proto.cosmos.base.v1beta1.ICoin[], - maxTotalBypassMinFeesgGasUsage: string, - ) => { - const params = await neutronChain.queryGlobalfeeParams(); - if (bypassMinFeeMsgTypes == null) { - bypassMinFeeMsgTypes = params.bypass_min_fee_msg_types; - } - if (minimumGasPrices == null) { - minimumGasPrices = params.minimum_gas_prices; - } - if (maxTotalBypassMinFeesgGasUsage == null) { - maxTotalBypassMinFeesgGasUsage = - params.max_total_bypass_min_fee_msg_gas_usage; - } - - const chainManagerAddress = (await neutronChain.getChainAdmins())[0]; - const proposalId = await daoMember.submitUpdateParamsGlobalfeeProposal( - chainManagerAddress, - 'Change Proposal - ' + kind + ' #' + counter, - 'Param change proposal. It will change the bypass min fee msg types of the global fee module to use MsgSend.', - updateGlobalFeeParamsProposal({ - bypass_min_fee_msg_types: bypassMinFeeMsgTypes, - max_total_bypass_min_fee_msg_gas_usage: maxTotalBypassMinFeesgGasUsage, - minimum_gas_prices: minimumGasPrices, - }), - '1000', - { - gas_limit: Long.fromString('4000000'), - amount: [{ denom: neutronChain.denom, amount: '100000' }], - }, - ); - - await daoMember.voteYes(proposalId, 'single', { - gas_limit: Long.fromString('4000000'), - amount: [{ denom: daoMember.user.chain.denom, amount: '100000' }], - }); - await daoMain.checkPassedProposal(proposalId); - await daoMember.executeProposalWithAttempts(proposalId, { - gas_limit: Long.fromString('4000000'), - amount: [{ denom: daoMember.user.chain.denom, amount: '100000' }], - }); - - counter++; - }; - - test('check globalfee params before proposal execution', async () => { - const params = await neutronChain.queryGlobalfeeParams(); - expect(params.minimum_gas_prices).toEqual([ - { - denom: - 'ibc/27394FB092D2ECCD56123C74F36E4C1F926001CEADA9CA97EA622B25F41E5EB2', - amount: '0.000000000000000000', - }, - { denom: 'untrn', amount: '0.000000000000000000' }, - ]); - expect(params.bypass_min_fee_msg_types).toEqual([ - '/ibc.core.channel.v1.Msg/RecvPacket', - '/ibc.core.channel.v1.Msg/Acknowledgement', - '/ibc.core.client.v1.Msg/UpdateClient', - ]); - expect(params.max_total_bypass_min_fee_msg_gas_usage).toEqual('1000000'); - }); - - test('change minimum gas price parameter', async () => { - await executeParamChange( - daoMember, - 'MinimumGasPricesParam', - null, - [{ denom: 'untrn', amount: '0.01' }], - null, - ); - }); - - test('check globalfee minimum param changed', async () => { - const params = await neutronChain.queryGlobalfeeParams(); - expect(params.minimum_gas_prices).toEqual([ - { denom: 'untrn', amount: '0.010000000000000000' }, - ]); - }); - - test('check minumum global fees with bank send command', async () => { - await expect( - neutronAccount.msgSend(daoMain.contracts.core.address, '1000', { - gas_limit: Long.fromString('200000'), - amount: [{ denom: daoMember.user.chain.denom, amount: '500' }], - }), - ).rejects.toThrowError( - /Insufficient fees; got: 500untrn required: 2000untrn: insufficient fee/, - ); - }); - - test('set bypass_min_fee_msg_types to allow bypass for MsgSend', async () => { - await executeParamChange( - daoMember, - 'BypassMinFeeMsgTypes', - ['/cosmos.bank.v1beta1.MsgSend'], - null, - null, - ); - }); - - test('check globalfee params after setting bypass_min_fee_msg_types', async () => { - const params = await neutronChain.queryGlobalfeeParams(); - expect(params.bypass_min_fee_msg_types).toEqual([ - '/cosmos.bank.v1beta1.MsgSend', - ]); - }); - - test('check that MsgSend passes check for allowed messages - now works with only validator fees', async () => { - const res = await neutronAccount.msgSend( - daoMain.contracts.core.address, - '1000', - { - gas_limit: Long.fromString('200000'), - amount: [{ denom: daoMember.user.chain.denom, amount: '500' }], - }, - ); - - await neutronChain.blockWaiter.waitBlocks(2); - - expect(res.code).toEqual(0); - }); - - test('set max_total_bypass_min_fee_msg_gas_usage to very low value', async () => { - await executeParamChange( - daoMember, - 'MaxTotalBypassMinFeeMsgGasUsage', - null, - null, - '50', - ); - }); - - test('check globalfee params after setting max_total_bypass_min_fee_msg_gas_usage', async () => { - const params = await neutronChain.queryGlobalfeeParams(); - expect(params.max_total_bypass_min_fee_msg_gas_usage).toEqual('50'); - }); - - test('check that MsgSend does not work without minimal fees now', async () => { - await neutronChain.blockWaiter.waitBlocks(2); - await expect( - neutronAccount.msgSend(daoMain.contracts.core.address, '1000', { - gas_limit: Long.fromString('200000'), - amount: [{ denom: daoMember.user.chain.denom, amount: '500' }], - }), - ).rejects.toThrowError( - /Insufficient fees; bypass-min-fee-msg-types with gas consumption 200000 exceeds the maximum allowed gas value of 50.: insufficient fee/, - ); - }); - - test('revert minimum gas price parameter to zero values', async () => { - await executeParamChange( - daoMember, - 'MinimumGasPricesParam', - null, - [ - { - denom: - 'ibc/27394FB092D2ECCD56123C74F36E4C1F926001CEADA9CA97EA622B25F41E5EB2', - amount: '0', - }, - { denom: 'untrn', amount: '0' }, - ], - null, - ); - }); - - test('revert bypass_min_fee_msg_types to defaults', async () => { - await executeParamChange( - daoMember, - 'BypassMinFeeMsgTypes', - [ - '/ibc.core.channel.v1.Msg/RecvPacket', - '/ibc.core.channel.v1.Msg/Acknowledgement', - '/ibc.core.client.v1.Msg/UpdateClient', - ], - null, - null, - ); - }); - - test('check minumum global fees with bank send command after revert with zero value (only validator min fee settings applied)', async () => { - const res = await neutronAccount.msgSend( - daoMain.contracts.core.address, - '1000', - { - gas_limit: Long.fromString('200000'), - amount: [{ denom: daoMember.user.chain.denom, amount: '500' }], - }, - ); - - await neutronChain.blockWaiter.waitBlocks(2); - - expect(res.code).toEqual(0); - }); -}); diff --git a/src/testcases/run_in_band/pob.test.ts b/src/testcases/run_in_band/pob.test.ts deleted file mode 100644 index ae6b6ee0..00000000 --- a/src/testcases/run_in_band/pob.test.ts +++ /dev/null @@ -1,228 +0,0 @@ -import '@neutron-org/neutronjsplus'; -import Long from 'long'; -import { MsgSend } from '@neutron-org/neutronjsplus/dist/proto/cosmos_sdk/cosmos/bank/v1beta1/tx_pb'; -import { - CosmosWrapper, - NEUTRON_DENOM, - packAnyMsg, - WalletWrapper, -} from '@neutron-org/neutronjsplus/dist/cosmos'; -import { TestStateLocalCosmosTestNet } from '@neutron-org/neutronjsplus'; -import { getHeight } from '@neutron-org/neutronjsplus/dist/env'; -const fee = { - gas_limit: Long.fromString('200000'), - amount: [{ denom: NEUTRON_DENOM, amount: '1000' }], -}; - -const config = require('../../config.json'); - -describe('Neutron / IBC hooks', () => { - let testState: TestStateLocalCosmosTestNet; - let neutronChain: CosmosWrapper; - let neutronAccount: WalletWrapper; - let n1: WalletWrapper; - let TreasuryAddress: string; - - beforeAll(async () => { - testState = new TestStateLocalCosmosTestNet(config); - await testState.init(); - neutronChain = new CosmosWrapper( - testState.sdk1, - testState.blockWaiter1, - NEUTRON_DENOM, - ); - neutronAccount = new WalletWrapper( - neutronChain, - testState.wallets.neutron.demo1, - ); - n1 = new WalletWrapper(neutronChain, testState.wallets.qaNeutron.genQaWal1); - - TreasuryAddress = await neutronChain.getNeutronDAOCore(); - }); - - describe('POB', () => { - test('single pob tx', async () => { - await neutronChain.blockWaiter.waitBlocks(1); - const amount = '1000000'; - const to = n1.wallet.address.toString(); - const msgSend = new MsgSend({ - fromAddress: neutronAccount.wallet.address.toString(), - toAddress: to, - amount: [{ denom: NEUTRON_DENOM, amount }], - }); - const txBuilder = neutronAccount.buildTx( - fee, - [packAnyMsg('/cosmos.bank.v1beta1.MsgSend', msgSend)], - Number(neutronAccount.wallet.account.sequence) + 1, - (await getHeight(neutronChain.sdk)) + 1, - ); - const d = Buffer.from(txBuilder.txBytes(), 'base64'); - await neutronAccount.msgSendAuction( - neutronAccount.wallet.address.toString(), - { - denom: NEUTRON_DENOM, - amount: '1000', - }, - [d], - ); - neutronAccount.wallet.account.sequence++; - }); - - test('single pob tx(zero tx fee)', async () => { - // zero tx only works works if no globalfee module configured - const amount = '1000000'; - const to = n1.wallet.address.toString(); - const msgSend = new MsgSend({ - fromAddress: neutronAccount.wallet.address.toString(), - toAddress: to, - amount: [{ denom: NEUTRON_DENOM, amount }], - }); - const txBuilder = neutronAccount.buildTx( - { - gas_limit: Long.fromString('1000000'), - amount: [{ denom: NEUTRON_DENOM, amount: '0' }], - }, - [packAnyMsg('/cosmos.bank.v1beta1.MsgSend', msgSend)], - Number(neutronAccount.wallet.account.sequence) + 1, - (await getHeight(neutronChain.sdk)) + 1, - ); - const d = Buffer.from(txBuilder.txBytes(), 'base64'); - await neutronAccount.msgSendAuction( - neutronAccount.wallet.address.toString(), - { - denom: NEUTRON_DENOM, - amount: '1000', - }, - [d], - ); - neutronAccount.wallet.account.sequence++; - }); - - test('backrun tx + decreased fee', async () => { - const amount = '1000000'; - const to = n1.wallet.address.toString(); - const backrunnedMsgSend = new MsgSend({ - fromAddress: neutronAccount.wallet.address.toString(), - toAddress: to, - amount: [{ denom: NEUTRON_DENOM, amount }], - }); - const backrunnerTxBuilder = neutronAccount.buildTx( - fee, - [packAnyMsg('/cosmos.bank.v1beta1.MsgSend', backrunnedMsgSend)], - +neutronAccount.wallet.account.sequence, - (await getHeight(neutronChain.sdk)) + 2, - ); - // wait for new block, to be sured the next txs are sent within a single block - await neutronChain.blockWaiter.waitBlocks(1); - await neutronAccount.broadcastTx(backrunnerTxBuilder); - // tx broadcasted with origHash in Sync mode, we want to "rebroadcast it" by another user with POB - const msgSendN1 = new MsgSend({ - fromAddress: n1.wallet.address.toString(), - toAddress: to, - amount: [{ denom: NEUTRON_DENOM, amount }], - }); - const txBuilderN1 = n1.buildTx( - fee, - [packAnyMsg('/cosmos.bank.v1beta1.MsgSend', msgSendN1)], - Number(n1.wallet.account.sequence) + 1, - (await getHeight(neutronChain.sdk)) + 1, - ); - const overriderMsgSend = new MsgSend({ - fromAddress: neutronAccount.wallet.address.toString(), - toAddress: to, - amount: [{ denom: NEUTRON_DENOM, amount }], - }); - const overriderTxBuilder = neutronAccount.buildTx( - { - gas_limit: Long.fromString('200000'), - amount: [{ denom: NEUTRON_DENOM, amount: '0' }], - }, - [packAnyMsg('/cosmos.bank.v1beta1.MsgSend', overriderMsgSend)], - // a previous broadcast event has increased seq_number, but we want to override it - Number(neutronAccount.wallet.account.sequence) - 1, - (await getHeight(neutronChain.sdk)) + 1, - ); - const overriderTxData = Buffer.from( - overriderTxBuilder.txBytes(), - 'base64', - ); - const txData = Buffer.from(txBuilderN1.txBytes(), 'base64'); - const balBeforeAuction = await n1.chain.queryDenomBalance( - TreasuryAddress, - 'untrn', - ); - const res = await n1.msgSendAuction( - n1.wallet.address.toString(), - { - denom: NEUTRON_DENOM, - amount: '1000', - }, - [overriderTxData, txData], - ); - expect(res.code).toEqual(0); - const events = res.events; - const attrs = events.find((e) => e.type === 'auction_bid')?.attributes; - expect(attrs).toEqual( - expect.arrayContaining([ - { - index: true, - key: 'bid', - value: `1000${NEUTRON_DENOM}`, - }, - ]), - ); - const balAfterAuction = await n1.chain.queryDenomBalance( - TreasuryAddress, - 'untrn', - ); - expect(balAfterAuction - balBeforeAuction).toBe(750); - n1.wallet.account.sequence++; - }); - - test('frontrun should fail', async () => { - const amount = '1000000'; - const to = n1.wallet.address.toString(); - const frontrunnedMsgSend = new MsgSend({ - fromAddress: neutronAccount.wallet.address.toString(), - toAddress: to, - amount: [{ denom: NEUTRON_DENOM, amount }], - }); - const frontrunnedTxBuilder = neutronAccount.buildTx(fee, [ - packAnyMsg('/cosmos.bank.v1beta1.MsgSend', frontrunnedMsgSend), - ]); - // wait for new block, to be sured the next txs are sent within one block - await neutronChain.blockWaiter.waitBlocks(1); - await neutronAccount.broadcastTx(frontrunnedTxBuilder); - // tx broadcasted with origHash in Sync mode, we want to "rebroadcast it" by another user with POB - const maliciousMsgSend = new MsgSend({ - fromAddress: n1.wallet.address.toString(), - toAddress: to, - amount: [{ denom: NEUTRON_DENOM, amount }], - }); - const maliciousTxBuilder = n1.buildTx( - fee, - [packAnyMsg('/cosmos.bank.v1beta1.MsgSend', maliciousMsgSend)], - Number(n1.wallet.account.sequence) + 1, - ); - const maliciousTxData = Buffer.from( - maliciousTxBuilder.txBytes(), - 'base64', - ); - const frontrunnedTxData = Buffer.from( - frontrunnedTxBuilder.txBytes(), - 'base64', - ); - await expect( - n1.msgSendAuction( - n1.wallet.address.toString(), - { - denom: NEUTRON_DENOM, - amount: '1000', - }, - [maliciousTxData, frontrunnedTxData], - ), - ).rejects.toThrow(/possible front-running or sandwich attack/); - n1.wallet.account.sequence++; - }); - }); -}); diff --git a/yarn.lock b/yarn.lock index 588fa94d..0780b633 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1568,9 +1568,9 @@ resolved "https://registry.yarnpkg.com/@ledgerhq/logs/-/logs-5.50.0.tgz#29c6419e8379d496ab6d0426eadf3c4d100cd186" integrity sha512-swKHYCOZUGyVt4ge0u8a7AwNcA//h4nx5wIi0sruGye1IJ5Cva0GyK9L2/WdX+kWVTKp92ZiEo1df31lrWGPgA== -"@neutron-org/neutronjsplus@https://github.com/neutron-org/neutronjsplus.git#b0acb8a3bb7ad3af7cde5f67ba259a94c2fa3e4f": +"@neutron-org/neutronjsplus@https://github.com/neutron-org/neutronjsplus.git#bd92e7b5ef1d5eb7042b78a957e57561dd379764": version "0.4.0-rc19" - resolved "https://github.com/neutron-org/neutronjsplus.git#b0acb8a3bb7ad3af7cde5f67ba259a94c2fa3e4f" + resolved "https://github.com/neutron-org/neutronjsplus.git#bd92e7b5ef1d5eb7042b78a957e57561dd379764" dependencies: "@bufbuild/protobuf" "^1.4.2" "@cosmos-client/core" "^0.47.4"