diff --git a/package.json b/package.json index 9f6af9c6..4ab87129 100644 --- a/package.json +++ b/package.json @@ -28,6 +28,7 @@ "test:tge:vesting_lp_vault": "jest -b src/testcases/parallel/tge.vesting_lp_vault", "test:tge:credits_vault": "jest -b src/testcases/parallel/tge.credits_vault", "test:tge:investors_vesting_vault": "jest -b src/testcases/parallel/tge.investors_vesting_vault", + "test:voting_registry": "jest -b src/testcases/parallel/voting_registry", "gen:proto": "bash ./gen-proto.sh", "lint": "eslint ./src", "fmt": "eslint ./src --fix" @@ -85,4 +86,4 @@ "engines": { "node": ">=11.0 <17" } -} +} \ No newline at end of file diff --git a/src/testcases/parallel/tge.investors_vesting_vault.test.ts b/src/testcases/parallel/tge.investors_vesting_vault.test.ts index 7453f970..89da05c0 100644 --- a/src/testcases/parallel/tge.investors_vesting_vault.test.ts +++ b/src/testcases/parallel/tge.investors_vesting_vault.test.ts @@ -107,7 +107,7 @@ describe('Neutron / TGE / Investors vesting vault', () => { describe('prepare investors vesting vault', () => { test('create vesting accounts', async () => { - const execRes = await cmInstantiator.executeContract( + await cmInstantiator.executeContract( contractAddresses[INVESTORS_VESTING_CONTRACT_KEY], JSON.stringify({ register_vesting_accounts: { @@ -133,7 +133,6 @@ describe('Neutron / TGE / Investors vesting vault', () => { }), [{ denom: NEUTRON_DENOM, amount: totalVestingAmount.toString() }], ); - expect(execRes.code).toBe(0); }); test('check unclaimed amounts', async () => { await neutronChain.blockWaiter.waitBlocks(1); @@ -171,36 +170,39 @@ describe('Neutron / TGE / Investors vesting vault', () => { }); }); + // vars for init state + let user1VpInit: VotingPowerResponse; + let user2VpInit: VotingPowerResponse; + let totalVpInit: VotingPowerResponse; + let heightInit: number; describe('voting power', () => { describe('check initial voting power', () => { test('total power at height', async () => { - const res = await neutronChain.queryContract( + heightInit = await env.getHeight(neutronChain.sdk); + totalVpInit = await totalPowerAtHeight( + neutronChain, contractAddresses[INVESTORS_VESTING_VAULT_CONTRACT_KEY], - { total_power_at_height: {} }, + heightInit, ); - expect(+res.power).toBe(totalVestingAmount); + expect(+totalVpInit.power).toBe(totalVestingAmount); }); test('user1 power at height', async () => { - const res = await neutronChain.queryContract( + user1VpInit = await votingPowerAtHeight( + neutronChain, contractAddresses[INVESTORS_VESTING_VAULT_CONTRACT_KEY], - { - voting_power_at_height: { - address: cmUser1.wallet.address.toString(), - }, - }, + cmUser1.wallet.address.toString(), + heightInit, ); - expect(+res.power).toBe(user1VestingAmount); + expect(+user1VpInit.power).toBe(user1VestingAmount); }); test('user2 power at height', async () => { - const res = await neutronChain.queryContract( + user2VpInit = await votingPowerAtHeight( + neutronChain, contractAddresses[INVESTORS_VESTING_VAULT_CONTRACT_KEY], - { - voting_power_at_height: { - address: cmUser2.wallet.address.toString(), - }, - }, + cmUser2.wallet.address.toString(), + heightInit, ); - expect(+res.power).toBe(user2VestingAmount); + expect(+user2VpInit.power).toBe(user2VestingAmount); }); }); @@ -212,7 +214,7 @@ describe('Neutron / TGE / Investors vesting vault', () => { await neutronChain.blockWaiter.waitBlocks(1); // so it's before claim for sure }); test('user1 partial claim', async () => { - const execRes = await cmUser1.executeContract( + await cmUser1.executeContract( contractAddresses[INVESTORS_VESTING_CONTRACT_KEY], JSON.stringify({ claim: { @@ -220,51 +222,43 @@ describe('Neutron / TGE / Investors vesting vault', () => { }, }), ); - expect(execRes.code).toBe(0); await neutronChain.blockWaiter.waitBlocks(1); - const res = await neutronChain.queryContract( + const res = await votingPowerAtHeight( + neutronChain, contractAddresses[INVESTORS_VESTING_VAULT_CONTRACT_KEY], - { - voting_power_at_height: { - address: cmUser1.wallet.address.toString(), - }, - }, + cmUser1.wallet.address.toString(), ); expect(+res.power).toBe(user1VestingAmount - user1PartialClaim); }); test('total voting power check after user1 partial claim', async () => { - const res = await neutronChain.queryContract( + const res = await totalPowerAtHeight( + neutronChain, contractAddresses[INVESTORS_VESTING_VAULT_CONTRACT_KEY], - { total_power_at_height: {} }, ); expect(+res.power).toBe(totalVestingAmount - user1PartialClaim); }); test('user2 full claim', async () => { - const execRes = await cmUser2.executeContract( + await cmUser2.executeContract( contractAddresses[INVESTORS_VESTING_CONTRACT_KEY], JSON.stringify({ claim: {}, }), ); - expect(execRes.code).toBe(0); await neutronChain.blockWaiter.waitBlocks(1); - const res = await neutronChain.queryContract( + const res = await votingPowerAtHeight( + neutronChain, contractAddresses[INVESTORS_VESTING_VAULT_CONTRACT_KEY], - { - voting_power_at_height: { - address: cmUser2.wallet.address.toString(), - }, - }, + cmUser2.wallet.address.toString(), ); expect(+res.power).toBe(0); }); test('total voting power check after user2 full claim', async () => { - const res = await neutronChain.queryContract( + const res = await totalPowerAtHeight( + neutronChain, contractAddresses[INVESTORS_VESTING_VAULT_CONTRACT_KEY], - { total_power_at_height: {} }, ); expect(+res.power).toBe( totalVestingAmount - user1PartialClaim - user2VestingAmount, @@ -272,29 +266,25 @@ describe('Neutron / TGE / Investors vesting vault', () => { }); test('user1 full claim', async () => { - const execRes = await cmUser1.executeContract( + await cmUser1.executeContract( contractAddresses[INVESTORS_VESTING_CONTRACT_KEY], JSON.stringify({ claim: {}, }), ); - expect(execRes.code).toBe(0); await neutronChain.blockWaiter.waitBlocks(1); - const res = await neutronChain.queryContract( + const res = await votingPowerAtHeight( + neutronChain, contractAddresses[INVESTORS_VESTING_VAULT_CONTRACT_KEY], - { - voting_power_at_height: { - address: cmUser1.wallet.address.toString(), - }, - }, + cmUser1.wallet.address.toString(), ); expect(+res.power).toBe(0); }); test('total voting power check after full claim', async () => { - const res = await neutronChain.queryContract( + const res = await totalPowerAtHeight( + neutronChain, contractAddresses[INVESTORS_VESTING_VAULT_CONTRACT_KEY], - { total_power_at_height: {} }, ); expect(+res.power).toBe(0); }); @@ -305,33 +295,28 @@ describe('Neutron / TGE / Investors vesting vault', () => { // at that point regardless of the following claim calls and TWAP changes. describe('check voting power before claim', () => { test('total power', async () => { - const res = await neutronChain.queryContract( + const res = await totalPowerAtHeight( + neutronChain, contractAddresses[INVESTORS_VESTING_VAULT_CONTRACT_KEY], - { total_power_at_height: { height: heightBeforeClaim } }, + heightBeforeClaim, ); expect(+res.power).toBe(totalVestingAmount); }); test('user1 power', async () => { - const res = await neutronChain.queryContract( + const res = await votingPowerAtHeight( + neutronChain, contractAddresses[INVESTORS_VESTING_VAULT_CONTRACT_KEY], - { - voting_power_at_height: { - address: cmUser1.wallet.address.toString(), - height: heightBeforeClaim, - }, - }, + cmUser1.wallet.address.toString(), + heightBeforeClaim, ); expect(+res.power).toBe(user1VestingAmount); }); test('user2 power', async () => { - const res = await neutronChain.queryContract( + const res = await votingPowerAtHeight( + neutronChain, contractAddresses[INVESTORS_VESTING_VAULT_CONTRACT_KEY], - { - voting_power_at_height: { - address: cmUser2.wallet.address.toString(), - height: heightBeforeClaim, - }, - }, + cmUser2.wallet.address.toString(), + heightBeforeClaim, ); expect(+res.power).toBe(user2VestingAmount); }); @@ -339,69 +324,259 @@ describe('Neutron / TGE / Investors vesting vault', () => { }); }); + /* + Here we test how vesting account addition/removal affect current and historical voting power + The flow is as follows: + 1. record voting power before vesting account additions/removals; + 2. add a vesting account for both user1 and user2 (endPoint=vestingAmount); + 3. record voting power and make sure it's changed properly; + 4. make sure historical voting power (cmp to init and p.1) hasn't changed; + 5. remove a vesting account for user2 (vestingAmount); + 6. make sure voting power has changed properly; + 7. make sure historical voting power (cmp to init, p.1 and p.3) hasn't changed; + */ describe('manage vesting accounts', () => { const vestingAmount = 500_000_000; - test('create a new vesting account', async () => { - const execRes = await cmInstantiator.executeContract( - contractAddresses[INVESTORS_VESTING_CONTRACT_KEY], - JSON.stringify({ - register_vesting_accounts: { - vesting_accounts: [ - types.vestingAccount(cmUser1.wallet.address.toString(), [ - types.vestingSchedule( - types.vestingSchedulePoint(0, vestingAmount.toString()), - ), - ]), + // vars for state before voting accounts added + let user1VpBeforeAdd: VotingPowerResponse; + let user2VpBeforeAdd: VotingPowerResponse; + let totalVpBeforeAdd: VotingPowerResponse; + let heightBeforeAdd: number; + // vars for state after voting accounts added + let user1VpAfterAdd: VotingPowerResponse; + let user2VpAfterAdd: VotingPowerResponse; + let totalVpAfterAdd: VotingPowerResponse; + let heightAfterAdd: number; + describe('add vesting accounts', () => { + test('record current voting power', async () => { + heightBeforeAdd = await env.getHeight(neutronChain.sdk); + user1VpBeforeAdd = await votingPowerAtHeight( + neutronChain, + contractAddresses[INVESTORS_VESTING_VAULT_CONTRACT_KEY], + cmUser1.wallet.address.toString(), + heightBeforeAdd, + ); + user2VpBeforeAdd = await votingPowerAtHeight( + neutronChain, + contractAddresses[INVESTORS_VESTING_VAULT_CONTRACT_KEY], + cmUser2.wallet.address.toString(), + heightBeforeAdd, + ); + totalVpBeforeAdd = await totalPowerAtHeight( + neutronChain, + contractAddresses[INVESTORS_VESTING_VAULT_CONTRACT_KEY], + heightBeforeAdd, + ); + }); + + describe('register vesting accounts', () => { + test('execute register_vesting_accounts', async () => { + await cmInstantiator.executeContract( + contractAddresses[INVESTORS_VESTING_CONTRACT_KEY], + JSON.stringify({ + register_vesting_accounts: { + vesting_accounts: [ + types.vestingAccount(cmUser1.wallet.address.toString(), [ + types.vestingSchedule( + types.vestingSchedulePoint(0, vestingAmount.toString()), + ), + ]), + types.vestingAccount(cmUser2.wallet.address.toString(), [ + types.vestingSchedule( + types.vestingSchedulePoint(0, vestingAmount.toString()), + ), + ]), + ], + }, + }), + [ + { + denom: NEUTRON_DENOM, + amount: (2 * vestingAmount).toString(), + }, ], - }, - }), - [{ denom: NEUTRON_DENOM, amount: vestingAmount.toString() }], - ); - expect(execRes.code).toBe(0); - await neutronChain.blockWaiter.waitBlocks(1); - const currentHeight = await env.getHeight(neutronChain.sdk); - expect( - await neutronChain.queryContract( - contractAddresses[INVESTORS_VESTING_CONTRACT_KEY], - { - historical_extension: { - msg: { - unclaimed_amount_at_height: { - address: cmUser1.wallet.address.toString(), - height: currentHeight, + ); + await neutronChain.blockWaiter.waitBlocks(1); + }); + + test('check available amounts', async () => { + const currentHeight = await env.getHeight(neutronChain.sdk); + expect( + await neutronChain.queryContract( + contractAddresses[INVESTORS_VESTING_CONTRACT_KEY], + { + historical_extension: { + msg: { + unclaimed_amount_at_height: { + address: cmUser1.wallet.address.toString(), + height: currentHeight, + }, + }, }, }, - }, - }, - ), - ).toBe(vestingAmount.toString()); - expect( - await neutronChain.queryContract( - contractAddresses[INVESTORS_VESTING_CONTRACT_KEY], - { - historical_extension: { - msg: { - unclaimed_total_amount_at_height: { - height: currentHeight, + ), + ).toBe(vestingAmount.toString()); + expect( + await neutronChain.queryContract( + contractAddresses[INVESTORS_VESTING_CONTRACT_KEY], + { + historical_extension: { + msg: { + unclaimed_amount_at_height: { + address: cmUser2.wallet.address.toString(), + height: currentHeight, + }, + }, }, }, - }, - }, - ), - ).toBe(vestingAmount.toString()); + ), + ).toBe(vestingAmount.toString()); + expect( + await neutronChain.queryContract( + contractAddresses[INVESTORS_VESTING_CONTRACT_KEY], + { + historical_extension: { + msg: { + unclaimed_total_amount_at_height: { + height: currentHeight, + }, + }, + }, + }, + ), + ).toBe((2 * vestingAmount).toString()); + }); + + test('record voting power after vesting account addition', async () => { + heightAfterAdd = await env.getHeight(neutronChain.sdk); + user1VpAfterAdd = await votingPowerAtHeight( + neutronChain, + contractAddresses[INVESTORS_VESTING_VAULT_CONTRACT_KEY], + cmUser1.wallet.address.toString(), + heightAfterAdd, + ); + user2VpAfterAdd = await votingPowerAtHeight( + neutronChain, + contractAddresses[INVESTORS_VESTING_VAULT_CONTRACT_KEY], + cmUser2.wallet.address.toString(), + heightAfterAdd, + ); + totalVpAfterAdd = await totalPowerAtHeight( + neutronChain, + contractAddresses[INVESTORS_VESTING_VAULT_CONTRACT_KEY], + heightAfterAdd, + ); + }); + test('check voting power change', async () => { + expect(+user1VpAfterAdd.power).toEqual( + +user1VpBeforeAdd.power + vestingAmount, + ); + expect(+user2VpAfterAdd.power).toEqual( + +user2VpBeforeAdd.power + vestingAmount, + ); + expect(+totalVpAfterAdd.power).toEqual( + +totalVpBeforeAdd.power + 2 * vestingAmount, + ); + }); + }); + + describe('check historical voting power', () => { + test('compare to initial voting power', async () => { + expect( + await votingPowerAtHeight( + neutronChain, + contractAddresses[INVESTORS_VESTING_VAULT_CONTRACT_KEY], + cmUser1.wallet.address.toString(), + heightInit, + ), + ).toEqual(user1VpInit); + expect( + await votingPowerAtHeight( + neutronChain, + contractAddresses[INVESTORS_VESTING_VAULT_CONTRACT_KEY], + cmUser2.wallet.address.toString(), + heightInit, + ), + ).toEqual(user2VpInit); + expect( + await totalPowerAtHeight( + neutronChain, + contractAddresses[INVESTORS_VESTING_VAULT_CONTRACT_KEY], + heightInit, + ), + ).toEqual(totalVpInit); + }); + + test('compare to voting power before vesting account addition', async () => { + expect( + await votingPowerAtHeight( + neutronChain, + contractAddresses[INVESTORS_VESTING_VAULT_CONTRACT_KEY], + cmUser1.wallet.address.toString(), + heightBeforeAdd, + ), + ).toEqual(user1VpBeforeAdd); + expect( + await votingPowerAtHeight( + neutronChain, + contractAddresses[INVESTORS_VESTING_VAULT_CONTRACT_KEY], + cmUser2.wallet.address.toString(), + heightBeforeAdd, + ), + ).toEqual(user2VpBeforeAdd); + expect( + await totalPowerAtHeight( + neutronChain, + contractAddresses[INVESTORS_VESTING_VAULT_CONTRACT_KEY], + heightBeforeAdd, + ), + ).toEqual(totalVpBeforeAdd); + }); + }); }); - describe('remove vesting account', () => { + describe('remove vesting accounts', () => { let clawbackAccount: string; let clawbackAccountBalance: number; - test('not allowed to a stranger', async () => { - clawbackAccount = cmManager.wallet.address.toString(); - clawbackAccountBalance = await neutronChain.queryDenomBalance( - clawbackAccount, - NEUTRON_DENOM, + // vars for state before voting accounts removed + let user1VpBeforeRm: VotingPowerResponse; + let user2VpBeforeRm: VotingPowerResponse; + let totalVpBeforeRm: VotingPowerResponse; + let heightBeforeRm: number; + // vars for state after voting accounts removed + let user1VpAfterRm: VotingPowerResponse; + let user2VpAfterRm: VotingPowerResponse; + let totalVpAfterRm: VotingPowerResponse; + let heightAfterRm: number; + test('record current voting power', async () => { + heightBeforeRm = await env.getHeight(neutronChain.sdk); + user1VpBeforeRm = await votingPowerAtHeight( + neutronChain, + contractAddresses[INVESTORS_VESTING_VAULT_CONTRACT_KEY], + cmUser1.wallet.address.toString(), + heightBeforeRm, ); - await expect( - cmUser2.executeContract( + user2VpBeforeRm = await votingPowerAtHeight( + neutronChain, + contractAddresses[INVESTORS_VESTING_VAULT_CONTRACT_KEY], + cmUser2.wallet.address.toString(), + heightBeforeRm, + ); + totalVpBeforeRm = await totalPowerAtHeight( + neutronChain, + contractAddresses[INVESTORS_VESTING_VAULT_CONTRACT_KEY], + heightBeforeRm, + ); + }); + + describe('remove vesting accounts', () => { + test('execute remove_vesting_accounts', async () => { + clawbackAccount = cmManager.wallet.address.toString(); + clawbackAccountBalance = await neutronChain.queryDenomBalance( + clawbackAccount, + NEUTRON_DENOM, + ); + await cmInstantiator.executeContract( contractAddresses[INVESTORS_VESTING_CONTRACT_KEY], JSON.stringify({ managed_extension: { @@ -413,69 +588,162 @@ describe('Neutron / TGE / Investors vesting vault', () => { }, }, }), - [{ denom: NEUTRON_DENOM, amount: vestingAmount.toString() }], - ), - ).rejects.toThrow(/Unauthorized/); - }); - test('successful as the owner', async () => { - const execRes = await cmInstantiator.executeContract( - contractAddresses[INVESTORS_VESTING_CONTRACT_KEY], - JSON.stringify({ - managed_extension: { - msg: { - remove_vesting_accounts: { - vesting_accounts: [cmUser1.wallet.address.toString()], - clawback_account: clawbackAccount, - }, - }, - }, - }), - [{ denom: NEUTRON_DENOM, amount: vestingAmount.toString() }], - ); - expect(execRes.code).toBe(0); - }); - test('unclaimed amount after removal', async () => { - await neutronChain.blockWaiter.waitBlocks(1); - const currentHeight = await env.getHeight(neutronChain.sdk); - expect( - await neutronChain.queryContract( - contractAddresses[INVESTORS_VESTING_CONTRACT_KEY], - { - historical_extension: { - msg: { - unclaimed_amount_at_height: { - address: cmUser1.wallet.address.toString(), - height: currentHeight, + ); + }); + + test('unclaimed amount after removal', async () => { + await neutronChain.blockWaiter.waitBlocks(1); + const currentHeight = await env.getHeight(neutronChain.sdk); + expect( + await neutronChain.queryContract( + contractAddresses[INVESTORS_VESTING_CONTRACT_KEY], + { + historical_extension: { + msg: { + unclaimed_amount_at_height: { + address: cmUser1.wallet.address.toString(), + height: currentHeight, + }, }, }, }, - }, - ), - ).toBe('0'); - expect( - await neutronChain.queryContract( - contractAddresses[INVESTORS_VESTING_CONTRACT_KEY], - { - historical_extension: { - msg: { - unclaimed_total_amount_at_height: { - height: currentHeight, + ), + ).toBe('0'); + expect( + await neutronChain.queryContract( + contractAddresses[INVESTORS_VESTING_CONTRACT_KEY], + { + historical_extension: { + msg: { + unclaimed_total_amount_at_height: { + height: currentHeight, + }, }, }, }, - }, - ), - ).toBe('0'); - }); - test('clawback account topped up', async () => { - const clawbackAccountBalanceAfterRemoval = - await neutronChain.queryDenomBalance( - clawbackAccount, - NEUTRON_DENOM, + ), + ).toBe(vestingAmount.toString()); // only user2's part left + }); + + test('record voting power after vesting account removal', async () => { + heightAfterRm = await env.getHeight(neutronChain.sdk); + user1VpAfterRm = await votingPowerAtHeight( + neutronChain, + contractAddresses[INVESTORS_VESTING_VAULT_CONTRACT_KEY], + cmUser1.wallet.address.toString(), + heightAfterRm, ); - expect(clawbackAccountBalanceAfterRemoval).toBe( - clawbackAccountBalance + vestingAmount, - ); + user2VpAfterRm = await votingPowerAtHeight( + neutronChain, + contractAddresses[INVESTORS_VESTING_VAULT_CONTRACT_KEY], + cmUser2.wallet.address.toString(), + heightAfterRm, + ); + totalVpAfterRm = await totalPowerAtHeight( + neutronChain, + contractAddresses[INVESTORS_VESTING_VAULT_CONTRACT_KEY], + heightAfterRm, + ); + }); + + test('check voting power change', async () => { + expect(+user1VpAfterRm.power).toEqual(0); + expect(+user2VpAfterRm.power).toEqual( + +user2VpBeforeRm.power, // wasn't changed + ); + expect(+totalVpAfterRm.power).toEqual( + +user2VpBeforeRm.power, // only user2's part left + ); + }); + + test('clawback account topped up', async () => { + const clawbackAccountBalanceAfterRemoval = + await neutronChain.queryDenomBalance( + clawbackAccount, + NEUTRON_DENOM, + ); + expect(clawbackAccountBalanceAfterRemoval).toBe( + clawbackAccountBalance + vestingAmount, + ); + }); + }); + + describe('check historical voting power', () => { + test('compare to initial voting power', async () => { + expect( + await votingPowerAtHeight( + neutronChain, + contractAddresses[INVESTORS_VESTING_VAULT_CONTRACT_KEY], + cmUser1.wallet.address.toString(), + heightInit, + ), + ).toEqual(user1VpInit); + expect( + await votingPowerAtHeight( + neutronChain, + contractAddresses[INVESTORS_VESTING_VAULT_CONTRACT_KEY], + cmUser2.wallet.address.toString(), + heightInit, + ), + ).toEqual(user2VpInit); + expect( + await totalPowerAtHeight( + neutronChain, + contractAddresses[INVESTORS_VESTING_VAULT_CONTRACT_KEY], + heightInit, + ), + ).toEqual(totalVpInit); + }); + test('compare to voting power before vesting account addition', async () => { + expect( + await votingPowerAtHeight( + neutronChain, + contractAddresses[INVESTORS_VESTING_VAULT_CONTRACT_KEY], + cmUser1.wallet.address.toString(), + heightBeforeAdd, + ), + ).toEqual(user1VpBeforeAdd); + expect( + await votingPowerAtHeight( + neutronChain, + contractAddresses[INVESTORS_VESTING_VAULT_CONTRACT_KEY], + cmUser2.wallet.address.toString(), + heightBeforeAdd, + ), + ).toEqual(user2VpBeforeAdd); + expect( + await totalPowerAtHeight( + neutronChain, + contractAddresses[INVESTORS_VESTING_VAULT_CONTRACT_KEY], + heightBeforeAdd, + ), + ).toEqual(totalVpBeforeAdd); + }); + test('compare to voting power before vesting account removal', async () => { + expect( + await votingPowerAtHeight( + neutronChain, + contractAddresses[INVESTORS_VESTING_VAULT_CONTRACT_KEY], + cmUser1.wallet.address.toString(), + heightBeforeRm, + ), + ).toEqual(user1VpBeforeRm); + expect( + await votingPowerAtHeight( + neutronChain, + contractAddresses[INVESTORS_VESTING_VAULT_CONTRACT_KEY], + cmUser2.wallet.address.toString(), + heightBeforeRm, + ), + ).toEqual(user2VpBeforeRm); + expect( + await totalPowerAtHeight( + neutronChain, + contractAddresses[INVESTORS_VESTING_VAULT_CONTRACT_KEY], + heightBeforeRm, + ), + ).toEqual(totalVpBeforeRm); + }); }); }); }); @@ -532,6 +800,26 @@ describe('Neutron / TGE / Investors vesting vault', () => { ).rejects.toThrow(/Vesting token is already set!/); }); + describe('remove vesting accounts is permissioned', () => { + test('removal not allowed to a stranger', async () => { + await expect( + cmUser2.executeContract( + contractAddresses[INVESTORS_VESTING_CONTRACT_KEY], + JSON.stringify({ + managed_extension: { + msg: { + remove_vesting_accounts: { + vesting_accounts: [cmUser1.wallet.address.toString()], + clawback_account: cmUser2.wallet.address.toString(), + }, + }, + }, + }), + ), + ).rejects.toThrow(/Unauthorized/); + }); + }); + describe('register vesting accounts is permissioned', () => { test('via send cw20 by a stranger', async () => { // create a random cw20 token with allocation to user1 @@ -661,7 +949,7 @@ const setInvestorsVestingAsset = async ( instantiator: cosmosWrapper.WalletWrapper, contractAddresses: Record, ) => { - const execRes = await instantiator.executeContract( + await instantiator.executeContract( contractAddresses[INVESTORS_VESTING_CONTRACT_KEY], JSON.stringify({ set_vesting_token: { @@ -673,7 +961,6 @@ const setInvestorsVestingAsset = async ( }, }), ); - expect(execRes.code).toBe(0); }; const deployInvestorsVestingVaultContract = async ( @@ -712,3 +999,31 @@ type VotingPowerResponse = { power: string; height: number; }; + +const totalPowerAtHeight = async ( + chain: cosmosWrapper.CosmosWrapper, + contract: string, + height?: number, +): Promise => + chain.queryContract(contract, { + total_power_at_height: + typeof height === 'undefined' ? {} : { height: height }, + }); + +const votingPowerAtHeight = async ( + chain: cosmosWrapper.CosmosWrapper, + contract: string, + address: string, + height?: number, +): Promise => + chain.queryContract(contract, { + voting_power_at_height: + typeof height === 'undefined' + ? { + address: address, + } + : { + address: address, + height: height, + }, + }); diff --git a/src/testcases/parallel/voting_registry.test.ts b/src/testcases/parallel/voting_registry.test.ts new file mode 100644 index 00000000..58807ffe --- /dev/null +++ b/src/testcases/parallel/voting_registry.test.ts @@ -0,0 +1,861 @@ +import { + NEUTRON_DENOM, + cosmosWrapper, + types, + env, + TestStateLocalCosmosTestNet, +} from '@neutron-org/neutronjsplus'; + +const config = require('../../config.json'); + +// general contract keys used across the tests +const VOTING_REGISTRY_CONTRACT_KEY = 'VOTING_REGISTRY'; +const NEUTRON_VAULT_CONTRACT_KEY = 'NEUTRON_VAULT'; +// specific contract keys used across the tests +const NEUTRON_VAULT_1_CONTRACT_KEY = 'NEUTRON_VAULT_1'; +const NEUTRON_VAULT_2_CONTRACT_KEY = 'NEUTRON_VAULT_2'; +const NEUTRON_VAULT_3_CONTRACT_KEY = 'NEUTRON_VAULT_3'; + +describe('Neutron / Voting Registry', () => { + let testState: TestStateLocalCosmosTestNet; + let neutronChain: cosmosWrapper.CosmosWrapper; + let cmInstantiator: cosmosWrapper.WalletWrapper; + let cmDaoMember: cosmosWrapper.WalletWrapper; + let contractAddresses: Record = {}; + let votingRegistryAddr: string; + let vault1Addr: string; + let vault2Addr: string; + let vault3Addr: string; + + let vpHistory: VotingPowerInfoHistory; + // initial bondings + const vault1Bonding = 1_000_000; + const vault2Bonding = 500_000; + // additional bonding amount + const vault1AddBonding = 10_000; + // partial unbonding amount + const vault1Unbonding = 100_000; + // bonding to an additional vault + const vault3Bonding = 5_000_000; + + beforeAll(async () => { + testState = new TestStateLocalCosmosTestNet(config); + await testState.init(); + neutronChain = new cosmosWrapper.CosmosWrapper( + testState.sdk1, + testState.blockWaiter1, + NEUTRON_DENOM, + ); + cmInstantiator = new cosmosWrapper.WalletWrapper( + neutronChain, + testState.wallets.qaNeutronThree.genQaWal1, + ); + cmDaoMember = new cosmosWrapper.WalletWrapper( + neutronChain, + testState.wallets.qaNeutron.genQaWal1, + ); + contractAddresses = await deployContracts(neutronChain, cmInstantiator); + votingRegistryAddr = contractAddresses[VOTING_REGISTRY_CONTRACT_KEY]; + vault1Addr = contractAddresses[NEUTRON_VAULT_1_CONTRACT_KEY]; + vault2Addr = contractAddresses[NEUTRON_VAULT_2_CONTRACT_KEY]; + vault3Addr = contractAddresses[NEUTRON_VAULT_3_CONTRACT_KEY]; + + vpHistory = initVotingPowerInfoHistory(); + }); + + describe('assert init state', () => { + test('check voting vaults', async () => { + const votingVaults = await getVotingVaults( + neutronChain, + votingRegistryAddr, + ); + // initially there are only vault1 and vault2 in the registry, vault3 is to be added later + expect(votingVaults.length).toBe(2); + expect(votingVaults).toContainEqual({ + address: vault1Addr, + description: NEUTRON_VAULT_1_CONTRACT_KEY, + name: NEUTRON_VAULT_1_CONTRACT_KEY, + state: 'Active', + }); + expect(votingVaults).toContainEqual({ + address: vault2Addr, + description: NEUTRON_VAULT_2_CONTRACT_KEY, + name: NEUTRON_VAULT_2_CONTRACT_KEY, + state: 'Active', + }); + }); + test('check voting power', async () => { + const vpInfo = await getVotingPowerInfo( + neutronChain, + cmDaoMember.wallet.address.toString(), + contractAddresses, + ); + expect(vpInfo.vault1Power).toBe(0); + expect(vpInfo.vault1TotalPower).toBe(0); + expect(vpInfo.vault2Power).toBe(0); + expect(vpInfo.vault2TotalPower).toBe(0); + expect(vpInfo.vault3Power).toBe(0); + expect(vpInfo.vault3TotalPower).toBe(0); + expect(vpInfo.votingRegistryPower).toBe(0); + expect(vpInfo.votingRegistryTotalPower).toBe(0); + }); + }); + + describe('accrue init voting power', () => { + test('bond funds', async () => { + await bondFunds(cmDaoMember, vault1Addr, vault1Bonding.toString()); + await bondFunds(cmDaoMember, vault2Addr, vault2Bonding.toString()); + // we bond to vault3 in advance regardless of this is not in the registry yet + await bondFunds(cmDaoMember, vault3Addr, vault3Bonding.toString()); + await neutronChain.blockWaiter.waitBlocks(1); + }); + + test('check accrued voting power', async () => { + const vpInfo = await getVotingPowerInfo( + neutronChain, + cmDaoMember.wallet.address.toString(), + contractAddresses, + ); + expect(vpInfo.vault1Power).toEqual(vault1Bonding); + expect(vpInfo.vault1TotalPower).toEqual(vault1Bonding); + expect(vpInfo.vault2Power).toEqual(vault2Bonding); + expect(vpInfo.vault2TotalPower).toEqual(vault2Bonding); + expect(vpInfo.vault3Power).toEqual(vault3Bonding); + expect(vpInfo.vault3TotalPower).toEqual(vault3Bonding); + // no vault3 in the registry yet + expect(vpInfo.votingRegistryPower).toEqual(vault1Bonding + vault2Bonding); + expect(vpInfo.votingRegistryTotalPower).toEqual( + vault1Bonding + vault2Bonding, + ); + vpHistory.init = vpInfo; + }); + }); + + describe('VP on bond and unbond', () => { + test('bond funds', async () => { + await bondFunds(cmDaoMember, vault1Addr, vault1AddBonding.toString()); + await neutronChain.blockWaiter.waitBlocks(1); + }); + test('check voting power after bonding', async () => { + const vpInfo = await getVotingPowerInfo( + neutronChain, + cmDaoMember.wallet.address.toString(), + contractAddresses, + ); + + // compare the new values to the prev state ones + // expect the vault1 VP to increase by bonding amount + expect(vpInfo.vault1Power).toEqual( + vpHistory.init.vault1Power + vault1AddBonding, + ); + expect(vpInfo.vault1TotalPower).toEqual( + vpHistory.init.vault1TotalPower + vault1AddBonding, + ); + + // expect the vault2 VP to remain the same + expect(vpInfo.vault2Power).toEqual(vpHistory.init.vault2Power); + expect(vpInfo.vault2TotalPower).toEqual(vpHistory.init.vault2TotalPower); + + // expect the vault3 VP to remain the same + expect(vpInfo.vault3Power).toEqual(vpHistory.init.vault3Power); + expect(vpInfo.vault3TotalPower).toEqual(vpHistory.init.vault3TotalPower); + + // expect the registry VP to increase by bonding amount + expect(vpInfo.votingRegistryPower).toEqual( + vpHistory.init.vault1TotalPower + + vault1AddBonding + + vpHistory.init.vault2TotalPower, + ); + expect(vpInfo.votingRegistryTotalPower).toEqual( + vpHistory.init.vault1TotalPower + + vault1AddBonding + + vpHistory.init.vault2TotalPower, + ); + vpHistory.additionalBonding = vpInfo; + }); + + test('unbond funds', async () => { + await unbondFunds(cmDaoMember, vault1Addr, vault1Unbonding.toString()); + await neutronChain.blockWaiter.waitBlocks(1); + }); + test('check voting power after unbonding', async () => { + const vpInfo = await getVotingPowerInfo( + neutronChain, + cmDaoMember.wallet.address.toString(), + contractAddresses, + ); + + // compare the new values to the prev state ones + // expect the vault1 VP to decrease by bonding amount + expect(vpInfo.vault1Power).toEqual( + vpHistory.additionalBonding.vault1Power - vault1Unbonding, + ); + expect(vpInfo.vault1TotalPower).toEqual( + vpHistory.additionalBonding.vault1TotalPower - vault1Unbonding, + ); + + // expect the vault2 VP to remain the same + expect(vpInfo.vault2Power).toEqual( + vpHistory.additionalBonding.vault2Power, + ); + expect(vpInfo.vault2TotalPower).toEqual( + vpHistory.additionalBonding.vault2TotalPower, + ); + + // expect the vault3 VP to remain the same + expect(vpInfo.vault3Power).toEqual( + vpHistory.additionalBonding.vault3Power, + ); + expect(vpInfo.vault3TotalPower).toEqual( + vpHistory.additionalBonding.vault3TotalPower, + ); + + // expect the registry VP to decrease by unbonding amount + expect(vpInfo.votingRegistryPower).toEqual( + vpHistory.additionalBonding.vault1TotalPower - + vault1Unbonding + + vpHistory.additionalBonding.vault2TotalPower, + ); + expect(vpInfo.votingRegistryTotalPower).toEqual( + vpHistory.additionalBonding.vault1TotalPower - + vault1Unbonding + + vpHistory.additionalBonding.vault2TotalPower, + ); + vpHistory.unbonding = vpInfo; + }); + + // expect VP infos taken from heights in the past to be the same as they were at that points + test('check historical voting power', async () => { + const initVpInfo = await getVotingPowerInfo( + neutronChain, + cmDaoMember.wallet.address.toString(), + contractAddresses, + vpHistory.init.height, + ); + expect(initVpInfo).toMatchObject(vpHistory.init); + + const atAdditionalBondingVpInfo = await getVotingPowerInfo( + neutronChain, + cmDaoMember.wallet.address.toString(), + contractAddresses, + vpHistory.additionalBonding.height, + ); + expect(atAdditionalBondingVpInfo).toMatchObject( + vpHistory.additionalBonding, + ); + + const atUnbondingVpInfo = await getVotingPowerInfo( + neutronChain, + cmDaoMember.wallet.address.toString(), + contractAddresses, + vpHistory.unbonding.height, + ); + expect(atUnbondingVpInfo).toMatchObject(vpHistory.unbonding); + }); + }); + + describe('VP on vaults list mutation', () => { + test('deactivate vault', async () => { + await deactivateVotingVault( + cmInstantiator, + votingRegistryAddr, + vault2Addr, + ); + await neutronChain.blockWaiter.waitBlocks(1); + + const votingVaults = await getVotingVaults( + neutronChain, + votingRegistryAddr, + ); + expect(votingVaults.length).toBe(2); + expect(votingVaults).toContainEqual({ + address: vault1Addr, + description: NEUTRON_VAULT_1_CONTRACT_KEY, + name: NEUTRON_VAULT_1_CONTRACT_KEY, + state: 'Active', + }); + expect(votingVaults).toContainEqual({ + address: vault2Addr, + description: NEUTRON_VAULT_2_CONTRACT_KEY, + name: NEUTRON_VAULT_2_CONTRACT_KEY, + state: 'Inactive', + }); + }); + test('check voting power after deactivation', async () => { + const vpInfo = await getVotingPowerInfo( + neutronChain, + cmDaoMember.wallet.address.toString(), + contractAddresses, + ); + + // compare the new values to the prev state ones + // expect the vault1 VP to remain the same + expect(vpInfo.vault1Power).toEqual(vpHistory.unbonding.vault1Power); + expect(vpInfo.vault1TotalPower).toEqual( + vpHistory.unbonding.vault1TotalPower, + ); + + // expect the vault2 VP to remain the same + expect(vpInfo.vault2Power).toEqual(vpHistory.unbonding.vault2Power); + expect(vpInfo.vault2TotalPower).toEqual( + vpHistory.unbonding.vault2TotalPower, + ); + + // expect the vault3 VP to remain the same + expect(vpInfo.vault3Power).toEqual(vpHistory.unbonding.vault3Power); + expect(vpInfo.vault3TotalPower).toEqual( + vpHistory.unbonding.vault3TotalPower, + ); + + // expect the registry VP to decrease by deactivated vault's power + expect(vpInfo.votingRegistryPower).toEqual( + vpHistory.unbonding.votingRegistryPower - + vpHistory.unbonding.vault2Power, + ); + expect(vpInfo.votingRegistryTotalPower).toEqual( + vpHistory.unbonding.votingRegistryTotalPower - + vpHistory.unbonding.vault2TotalPower, + ); + vpHistory.vaultDeactivation = vpInfo; + }); + + test('add another vault', async () => { + await addVotingVault(cmInstantiator, votingRegistryAddr, vault3Addr); + await neutronChain.blockWaiter.waitBlocks(1); + + const votingVaults = await getVotingVaults( + neutronChain, + votingRegistryAddr, + ); + expect(votingVaults.length).toBe(3); + expect(votingVaults).toContainEqual({ + address: vault1Addr, + description: NEUTRON_VAULT_1_CONTRACT_KEY, + name: NEUTRON_VAULT_1_CONTRACT_KEY, + state: 'Active', + }); + expect(votingVaults).toContainEqual({ + address: vault2Addr, + description: NEUTRON_VAULT_2_CONTRACT_KEY, + name: NEUTRON_VAULT_2_CONTRACT_KEY, + state: 'Inactive', + }); + expect(votingVaults).toContainEqual({ + address: vault3Addr, + description: NEUTRON_VAULT_3_CONTRACT_KEY, + name: NEUTRON_VAULT_3_CONTRACT_KEY, + state: 'Active', + }); + }); + test('check voting power after vault addition', async () => { + const vpInfo = await getVotingPowerInfo( + neutronChain, + cmDaoMember.wallet.address.toString(), + contractAddresses, + ); + + // compare the new values to the prev state ones + // expect the vault1 VP to remain the same + expect(vpInfo.vault1Power).toEqual( + vpHistory.vaultDeactivation.vault1Power, + ); + expect(vpInfo.vault1TotalPower).toEqual( + vpHistory.vaultDeactivation.vault1TotalPower, + ); + + // expect the vault2 VP to remain the same + expect(vpInfo.vault2Power).toEqual( + vpHistory.vaultDeactivation.vault2Power, + ); + expect(vpInfo.vault2TotalPower).toEqual( + vpHistory.vaultDeactivation.vault2TotalPower, + ); + + // expect the vault3 VP to remain the same + expect(vpInfo.vault3Power).toEqual( + vpHistory.vaultDeactivation.vault3Power, + ); + expect(vpInfo.vault3TotalPower).toEqual( + vpHistory.vaultDeactivation.vault3TotalPower, + ); + + // expect the registry VP to increase by added vault's power + expect(vpInfo.votingRegistryPower).toEqual( + vpHistory.vaultDeactivation.votingRegistryPower + + vpHistory.vaultDeactivation.vault3Power, + ); + expect(vpInfo.votingRegistryTotalPower).toEqual( + vpHistory.vaultDeactivation.votingRegistryTotalPower + + vpHistory.vaultDeactivation.vault3TotalPower, + ); + vpHistory.vaultAdded = vpInfo; + }); + + test('activate vault', async () => { + await activateVotingVault(cmInstantiator, votingRegistryAddr, vault2Addr); + await neutronChain.blockWaiter.waitBlocks(1); + + const votingVaults = await getVotingVaults( + neutronChain, + votingRegistryAddr, + ); + expect(votingVaults.length).toBe(3); + expect(votingVaults).toContainEqual({ + address: vault1Addr, + description: NEUTRON_VAULT_1_CONTRACT_KEY, + name: NEUTRON_VAULT_1_CONTRACT_KEY, + state: 'Active', + }); + expect(votingVaults).toContainEqual({ + address: vault2Addr, + description: NEUTRON_VAULT_2_CONTRACT_KEY, + name: NEUTRON_VAULT_2_CONTRACT_KEY, + state: 'Active', + }); + expect(votingVaults).toContainEqual({ + address: vault3Addr, + description: NEUTRON_VAULT_3_CONTRACT_KEY, + name: NEUTRON_VAULT_3_CONTRACT_KEY, + state: 'Active', + }); + }); + test('check voting power after activation', async () => { + const vpInfo = await getVotingPowerInfo( + neutronChain, + cmDaoMember.wallet.address.toString(), + contractAddresses, + ); + + // compare the new values to the prev state ones + // expect the vault1 VP to remain the same + expect(vpInfo.vault1Power).toEqual(vpHistory.vaultAdded.vault1Power); + expect(vpInfo.vault1TotalPower).toEqual( + vpHistory.vaultAdded.vault1TotalPower, + ); + + // expect the vault2 VP to remain the same + expect(vpInfo.vault2Power).toEqual(vpHistory.vaultAdded.vault2Power); + expect(vpInfo.vault2TotalPower).toEqual( + vpHistory.vaultAdded.vault2TotalPower, + ); + + // expect the vault3 VP to remain the same + expect(vpInfo.vault3Power).toEqual(vpHistory.vaultAdded.vault3Power); + expect(vpInfo.vault3TotalPower).toEqual( + vpHistory.vaultAdded.vault3TotalPower, + ); + + // expect the registry VP to increase by activated vault's power + expect(vpInfo.votingRegistryPower).toEqual( + vpHistory.vaultAdded.votingRegistryPower + + vpHistory.vaultAdded.vault2Power, + ); + expect(vpInfo.votingRegistryTotalPower).toEqual( + vpHistory.vaultAdded.votingRegistryTotalPower + + vpHistory.vaultAdded.vault2TotalPower, + ); + vpHistory.vaultActivation = vpInfo; + }); + + // expect VP infos taken from heights in the past to be the same as they were at that points + test('check historical voting power', async () => { + const initVpInfo = await getVotingPowerInfo( + neutronChain, + cmDaoMember.wallet.address.toString(), + contractAddresses, + vpHistory.init.height, + ); + expect(initVpInfo).toMatchObject(vpHistory.init); + + const atAdditionalBondingVpInfo = await getVotingPowerInfo( + neutronChain, + cmDaoMember.wallet.address.toString(), + contractAddresses, + vpHistory.additionalBonding.height, + ); + expect(atAdditionalBondingVpInfo).toMatchObject( + vpHistory.additionalBonding, + ); + expect(atAdditionalBondingVpInfo.height).toBeGreaterThan( + initVpInfo.height, + ); + + const atUnbondingVpInfo = await getVotingPowerInfo( + neutronChain, + cmDaoMember.wallet.address.toString(), + contractAddresses, + vpHistory.unbonding.height, + ); + expect(atUnbondingVpInfo).toMatchObject(vpHistory.unbonding); + expect(atUnbondingVpInfo.height).toBeGreaterThan( + atAdditionalBondingVpInfo.height, + ); + + const atVaultDeactivationVpInfo = await getVotingPowerInfo( + neutronChain, + cmDaoMember.wallet.address.toString(), + contractAddresses, + vpHistory.vaultDeactivation.height, + ); + expect(atVaultDeactivationVpInfo).toMatchObject( + vpHistory.vaultDeactivation, + ); + expect(atVaultDeactivationVpInfo.height).toBeGreaterThan( + atUnbondingVpInfo.height, + ); + + const atVaultAddedVpInfo = await getVotingPowerInfo( + neutronChain, + cmDaoMember.wallet.address.toString(), + contractAddresses, + vpHistory.vaultAdded.height, + ); + expect(atVaultAddedVpInfo).toMatchObject(vpHistory.vaultAdded); + expect(atVaultAddedVpInfo.height).toBeGreaterThan( + atVaultDeactivationVpInfo.height, + ); + + const atVaultActivationVpInfo = await getVotingPowerInfo( + neutronChain, + cmDaoMember.wallet.address.toString(), + contractAddresses, + vpHistory.vaultActivation.height, + ); + expect(atVaultActivationVpInfo).toMatchObject(vpHistory.vaultActivation); + expect(atVaultActivationVpInfo.height).toBeGreaterThan( + atVaultAddedVpInfo.height, + ); + }); + }); +}); + +const deployContracts = async ( + chain: cosmosWrapper.CosmosWrapper, + instantiator: cosmosWrapper.WalletWrapper, +): Promise> => { + const codeIds: Record = {}; + for (const contract of [ + VOTING_REGISTRY_CONTRACT_KEY, + NEUTRON_VAULT_CONTRACT_KEY, + ]) { + const codeId = await instantiator.storeWasm( + types.NeutronContract[contract], + ); + expect(codeId).toBeGreaterThan(0); + codeIds[contract] = codeId; + } + + const contractAddresses: Record = {}; + await deployNeutronVault( + instantiator, + NEUTRON_VAULT_1_CONTRACT_KEY, + codeIds, + contractAddresses, + ); + await deployNeutronVault( + instantiator, + NEUTRON_VAULT_2_CONTRACT_KEY, + codeIds, + contractAddresses, + ); + await deployVotingRegistry( + instantiator, + [ + contractAddresses[NEUTRON_VAULT_1_CONTRACT_KEY], + contractAddresses[NEUTRON_VAULT_2_CONTRACT_KEY], + ], + codeIds, + contractAddresses, + ); + + // just deploy for later use + await deployNeutronVault( + instantiator, + NEUTRON_VAULT_3_CONTRACT_KEY, + codeIds, + contractAddresses, + ); + return contractAddresses; +}; + +const deployVotingRegistry = async ( + instantiator: cosmosWrapper.WalletWrapper, + vaults: string[], + codeIds: Record, + contractAddresses: Record, +) => { + const res = await instantiator.instantiateContract( + codeIds[VOTING_REGISTRY_CONTRACT_KEY], + JSON.stringify({ + owner: instantiator.wallet.address.toString(), + voting_vaults: vaults, + }), + 'voting_registry', + ); + expect(res).toBeTruthy(); + contractAddresses[VOTING_REGISTRY_CONTRACT_KEY] = res[0]._contract_address; +}; + +const deployNeutronVault = async ( + instantiator: cosmosWrapper.WalletWrapper, + vaultKey: string, + codeIds: Record, + contractAddresses: Record, +) => { + const res = await instantiator.instantiateContract( + codeIds[NEUTRON_VAULT_CONTRACT_KEY], + JSON.stringify({ + owner: instantiator.wallet.address.toString(), + name: vaultKey, + description: vaultKey, + denom: NEUTRON_DENOM, + }), + 'neutron_vault', + ); + expect(res).toBeTruthy(); + contractAddresses[vaultKey] = res[0]._contract_address; +}; + +const bondFunds = async ( + cm: cosmosWrapper.WalletWrapper, + vault: string, + amount: string, +) => + cm.executeContract( + vault, + JSON.stringify({ + bond: {}, + }), + [{ denom: NEUTRON_DENOM, amount: amount }], + ); + +const unbondFunds = async ( + cm: cosmosWrapper.WalletWrapper, + vault: string, + amount: string, +) => + cm.executeContract( + vault, + JSON.stringify({ + unbond: { amount: amount }, + }), + [], + ); + +const activateVotingVault = async ( + cm: cosmosWrapper.WalletWrapper, + registry: string, + vault: string, +) => + cm.executeContract( + registry, + JSON.stringify({ + activate_voting_vault: { + voting_vault_contract: vault, + }, + }), + [], + ); + +const deactivateVotingVault = async ( + cm: cosmosWrapper.WalletWrapper, + registry: string, + vault: string, +) => + cm.executeContract( + registry, + JSON.stringify({ + deactivate_voting_vault: { + voting_vault_contract: vault, + }, + }), + [], + ); + +const addVotingVault = async ( + cm: cosmosWrapper.WalletWrapper, + registry: string, + vault: string, +) => + cm.executeContract( + registry, + JSON.stringify({ + add_voting_vault: { + new_voting_vault_contract: vault, + }, + }), + [], + ); + +/** + * Retrieves voting power data for a given address from both vaults and voting registry. Also + * retrieves total voting powerdata from the same contracts. + */ +const getVotingPowerInfo = async ( + chain: cosmosWrapper.CosmosWrapper, + address: string, + contractAddresses: Record, + height?: number, +): Promise => { + if (typeof height === 'undefined') { + height = await env.getHeight(chain.sdk); + } + const vault1Power = getVotingPowerAtHeight( + chain, + contractAddresses[NEUTRON_VAULT_1_CONTRACT_KEY], + address, + height, + ); + const vault1TotalPower = getTotalPowerAtHeight( + chain, + contractAddresses[NEUTRON_VAULT_1_CONTRACT_KEY], + height, + ); + const vault2Power = getVotingPowerAtHeight( + chain, + contractAddresses[NEUTRON_VAULT_2_CONTRACT_KEY], + address, + height, + ); + const vault2TotalPower = getTotalPowerAtHeight( + chain, + contractAddresses[NEUTRON_VAULT_2_CONTRACT_KEY], + height, + ); + const vault3Power = getVotingPowerAtHeight( + chain, + contractAddresses[NEUTRON_VAULT_3_CONTRACT_KEY], + address, + height, + ); + const vault3TotalPower = getTotalPowerAtHeight( + chain, + contractAddresses[NEUTRON_VAULT_3_CONTRACT_KEY], + height, + ); + const registryPower = getVotingPowerAtHeight( + chain, + contractAddresses[VOTING_REGISTRY_CONTRACT_KEY], + address, + height, + ); + const registryTotalPower = getTotalPowerAtHeight( + chain, + contractAddresses[VOTING_REGISTRY_CONTRACT_KEY], + height, + ); + + return { + height: height, + vault1Power: +(await vault1Power).power, + vault1TotalPower: +(await vault1TotalPower).power, + vault2Power: +(await vault2Power).power, + vault2TotalPower: +(await vault2TotalPower).power, + vault3Power: +(await vault3Power).power, + vault3TotalPower: +(await vault3TotalPower).power, + votingRegistryPower: +(await registryPower).power, + votingRegistryTotalPower: +(await registryTotalPower).power, + }; +}; + +const getTotalPowerAtHeight = async ( + chain: cosmosWrapper.CosmosWrapper, + contract: string, + height?: number, +): Promise => + chain.queryContract(contract, { + total_power_at_height: + typeof height === 'undefined' ? {} : { height: height }, + }); + +const getVotingPowerAtHeight = async ( + chain: cosmosWrapper.CosmosWrapper, + contract: string, + address: string, + height?: number, +): Promise => + chain.queryContract(contract, { + voting_power_at_height: + typeof height === 'undefined' + ? { + address: address, + } + : { + address: address, + height: height, + }, + }); + +const getVotingVaults = async ( + chain: cosmosWrapper.CosmosWrapper, + registry: string, + height?: number, +): Promise => + chain.queryContract(registry, { + voting_vaults: typeof height === 'undefined' ? {} : { height: height }, + }); + +type VotingPowerResponse = { + power: string; + height: number; +}; + +type VotingPowerInfo = { + height: number; + vault1Power: number; + vault1TotalPower: number; + vault2Power: number; + vault2TotalPower: number; + vault3Power: number; + vault3TotalPower: number; + votingRegistryPower: number; + votingRegistryTotalPower: number; +}; + +/** + * Contains voting power info for each important point in the test history. Used to make sure + * historical voting power queries are correct. Fields are placed in order of their occurrence + * in the test. + */ +type VotingPowerInfoHistory = { + /** initial voting power info */ + init: VotingPowerInfo; + /** voting power info after making an additional bonding */ + additionalBonding: VotingPowerInfo; + /** voting power info after making a partial unbonding */ + unbonding: VotingPowerInfo; + /** voting power info after a vault deactivation */ + vaultDeactivation: VotingPowerInfo; + /** voting power info after vault addition */ + vaultAdded: VotingPowerInfo; + /** voting power info after the deactivated vault activation */ + vaultActivation: VotingPowerInfo; +}; + +const initVotingPowerInfoHistory = (): VotingPowerInfoHistory => ({ + init: initVotingPowerInfo(), + additionalBonding: initVotingPowerInfo(), + unbonding: initVotingPowerInfo(), + vaultDeactivation: initVotingPowerInfo(), + vaultAdded: initVotingPowerInfo(), + vaultActivation: initVotingPowerInfo(), +}); + +const initVotingPowerInfo = (): VotingPowerInfo => ({ + height: 0, + vault1Power: 0, + vault1TotalPower: 0, + vault2Power: 0, + vault2TotalPower: 0, + vault3Power: 0, + vault3TotalPower: 0, + votingRegistryPower: 0, + votingRegistryTotalPower: 0, +}); + +type VotingVault = { + address: string; + name: string; + description: string; + state: string; +};