diff --git a/contracts_thirdparty/cw20_merkle_airdrop_orig.wasm b/contracts_thirdparty/cw20_merkle_airdrop_orig.wasm new file mode 100644 index 00000000..0476848e Binary files /dev/null and b/contracts_thirdparty/cw20_merkle_airdrop_orig.wasm differ diff --git a/src/helpers/cosmos.ts b/src/helpers/cosmos.ts index c30eaf82..72f001ad 100644 --- a/src/helpers/cosmos.ts +++ b/src/helpers/cosmos.ts @@ -558,6 +558,33 @@ export class WalletWrapper { ]); } + async migrateContract( + contract: string, + codeId: number, + msg: string | Record, + ): Promise { + const sender = this.wallet.address.toString(); + const msgMigrate = new cosmwasmproto.cosmwasm.wasm.v1.MsgMigrateContract({ + sender, + contract, + code_id: codeId + '', + msg: Buffer.from(typeof msg === 'string' ? msg : JSON.stringify(msg)), + }); + const res = await this.execTx( + { + gas_limit: Long.fromString('5000000'), + amount: [{ denom: this.chain.denom, amount: '20000' }], + }, + [msgMigrate], + ); + if (res.tx_response.code !== 0) { + throw new Error( + `${res.tx_response.raw_log}\nFailed tx hash: ${res.tx_response.txhash}`, + ); + } + return res?.tx_response; + } + async executeContract( contract: string, msg: string, diff --git a/src/testcases/run_in_band/tge.airdrop.test.ts b/src/testcases/run_in_band/tge.airdrop.test.ts index e01a8207..7d15a9d8 100644 --- a/src/testcases/run_in_band/tge.airdrop.test.ts +++ b/src/testcases/run_in_band/tge.airdrop.test.ts @@ -21,6 +21,7 @@ describe('Neutron / TGE / Airdrop', () => { let neutronChain: CosmosWrapper; let neutronAccount1: WalletWrapper; let neutronAccount2: WalletWrapper; + let neutronAccount3: WalletWrapper; const codeIds: Record = {}; const contractAddresses: Record = {}; let airdrop: InstanceType; @@ -45,6 +46,10 @@ describe('Neutron / TGE / Airdrop', () => { neutronChain, testState.wallets.qaNeutronThree.genQaWal1, ); + neutronAccount3 = new WalletWrapper( + neutronChain, + testState.wallets.qaNeutronFour.genQaWal1, + ); const accounts = [ { address: testState.wallets.neutron.demo1.address.toString(), @@ -75,6 +80,15 @@ describe('Neutron / TGE / Airdrop', () => { expect(codeId).toBeGreaterThan(0); codeIds[contract] = codeId; } + // wasmcode to test migration airdrop contract from the mainnet codeId 22 to a new wasm code + // $ sha256sum contracts_thirdparty/cw20_merkle_airdrop_orig.wasm + // b6595694b8cf752a085b34584ae37bf59e2236d916a1fd01dd014af8967204aa contracts_thirdparty/cw20_merkle_airdrop_orig.wasm + // https://neutron.celat.one/neutron-1/codes/22 + const codeId = await neutronAccount1.storeWasm( + '../contracts_thirdparty/cw20_merkle_airdrop_orig.wasm', + ); + expect(codeId).toBeGreaterThan(0); + codeIds['TGE_AIRDROP_ORIG'] = codeId; }); it('should instantiate credits contract', async () => { const res = await neutronAccount1.instantiateContract( @@ -102,7 +116,7 @@ describe('Neutron / TGE / Airdrop', () => { hrp: 'neutron', }; const res = await neutronAccount1.instantiateContract( - codeIds['TGE_AIRDROP'], + codeIds['TGE_AIRDROP_ORIG'], JSON.stringify(initParams), 'airdrop', ); @@ -135,6 +149,30 @@ describe('Neutron / TGE / Airdrop', () => { ); expect(res.code).toEqual(0); }); + + it('should not update reserve address by owner', async () => { + await expect( + neutronAccount1.executeContract( + contractAddresses['TGE_AIRDROP'], + JSON.stringify({ + update_reserve: { + address: neutronAccount3.wallet.address.toString(), + }, + }), + ), + ).rejects.toThrow( + /unknown variant `update_reserve`, expected one of `claim`, `withdraw_all`, `pause`, `resume`/, + ); + expect( + await neutronChain.queryContract(contractAddresses.TGE_AIRDROP, { + config: {}, + }), + ).toMatchObject({ + owner: neutronAccount1.wallet.address.toString(), + credits_address: contractAddresses.TGE_CREDITS, + reserve_address: reserveAddress, + }); + }); }); describe('Airdrop', () => { @@ -269,6 +307,102 @@ describe('Neutron / TGE / Airdrop', () => { ); expect(res.code).toEqual(0); }); + + it('should migrate in the middle of TGE', async () => { + const res = await neutronAccount1.migrateContract( + contractAddresses['TGE_AIRDROP'], + codeIds['TGE_AIRDROP'], + {}, + ); + expect(res.code).toEqual(0); + }); + + it('should not update reserve address by random account', async () => { + await expect( + neutronAccount3.executeContract( + contractAddresses['TGE_AIRDROP'], + JSON.stringify({ + update_reserve: { + address: neutronAccount3.wallet.address.toString(), + }, + }), + ), + ).rejects.toThrow(/Unauthorized/); + expect( + await neutronChain.queryContract(contractAddresses.TGE_AIRDROP, { + config: {}, + }), + ).toMatchObject({ + owner: neutronAccount1.wallet.address.toString(), + credits_address: contractAddresses.TGE_CREDITS, + reserve_address: reserveAddress, + }); + }); + + it('should update reserve address by owner account', async () => { + const res = await neutronAccount1.executeContract( + contractAddresses['TGE_AIRDROP'], + JSON.stringify({ + update_reserve: { + address: neutronAccount3.wallet.address.toString(), + }, + }), + ); + expect(res.code).toEqual(0); + expect( + await neutronChain.queryContract(contractAddresses.TGE_AIRDROP, { + config: {}, + }), + ).toMatchObject({ + owner: neutronAccount1.wallet.address.toString(), + credits_address: contractAddresses.TGE_CREDITS, + reserve_address: neutronAccount3.wallet.address.toString(), + }); + }); + + it('should not update reserve address by old reserve', async () => { + await expect( + neutronAccount2.executeContract( + contractAddresses['TGE_AIRDROP'], + JSON.stringify({ + update_reserve: { + address: neutronAccount2.wallet.address.toString(), + }, + }), + ), + ).rejects.toThrow(/Unauthorized/); + expect( + await neutronChain.queryContract(contractAddresses.TGE_AIRDROP, { + config: {}, + }), + ).toMatchObject({ + owner: neutronAccount1.wallet.address.toString(), + credits_address: contractAddresses.TGE_CREDITS, + reserve_address: neutronAccount3.wallet.address.toString(), + }); + }); + + it('should update reserve address by new reserve', async () => { + const res = await neutronAccount3.executeContract( + contractAddresses['TGE_AIRDROP'], + JSON.stringify({ + update_reserve: { + address: neutronAccount2.wallet.address.toString(), + }, + }), + ); + expect(res.code).toEqual(0); + expect( + await neutronChain.queryContract(contractAddresses.TGE_AIRDROP, { + config: {}, + }), + ).toMatchObject({ + owner: neutronAccount1.wallet.address.toString(), + credits_address: contractAddresses.TGE_CREDITS, + reserve_address: reserveAddress, + }); + }); + it('should return is claimed true', async () => { const res = await neutronChain.queryContract<{ is_claimed: boolean }>( contractAddresses['TGE_AIRDROP'], @@ -392,7 +526,7 @@ describe('Neutron / TGE / Airdrop', () => { }); it('should not be able to withdraw all before end', async () => { await expect( - neutronAccount1.executeContract( + neutronAccount2.executeContract( contractAddresses['TGE_AIRDROP'], JSON.stringify({ withdraw_all: {}, @@ -403,8 +537,20 @@ describe('Neutron / TGE / Airdrop', () => { /withdraw_all is unavailable, it will become available at/, ); }); - it('should be able to withdraw all', async () => { + it('should not be able to withdraw all by non reserve address', async () => { await waitTill(times.airdropVestingStart + times.vestingDuration + 5); + await expect( + neutronAccount1.executeContract( + contractAddresses['TGE_AIRDROP'], + JSON.stringify({ + withdraw_all: {}, + }), + [], + ), + ).rejects.toThrow(/Unauthorized/); + }); + + it('should be able to withdraw all by reserve address', async () => { const availableBalanceCNTRN = await neutronChain.queryContract<{ balance: string; }>(contractAddresses['TGE_CREDITS'], { @@ -415,7 +561,7 @@ describe('Neutron / TGE / Airdrop', () => { const reserveBalanceNTRN = ( await neutronChain.queryBalances(reserveAddress) ).balances.find((b) => b.denom === NEUTRON_DENOM)?.amount; - const res = await neutronAccount1.executeContract( + const res = await neutronAccount2.executeContract( contractAddresses['TGE_AIRDROP'], JSON.stringify({ withdraw_all: {}, @@ -437,7 +583,8 @@ describe('Neutron / TGE / Airdrop', () => { expect(availableBalanceCNTRNAfter.balance).toEqual('0'); expect( parseInt(reserveBalanceNTRNAfter || '0') - - parseInt(reserveBalanceNTRN || '0'), + parseInt(reserveBalanceNTRN || '0') + + 10000, // fee compensation for execution withdraw all ).toEqual(parseInt(availableBalanceCNTRN.balance)); }); });