From e7fe7ed00460ebba52459aac6c04b6ba666eab39 Mon Sep 17 00:00:00 2001 From: sotnikov-s Date: Fri, 15 Sep 2023 19:02:45 +0300 Subject: [PATCH 1/5] add voting registry test --- .../parallel/voting_registry.test.ts | 850 ++++++++++++++++++ 1 file changed, 850 insertions(+) create mode 100644 src/testcases/parallel/voting_registry.test.ts diff --git a/src/testcases/parallel/voting_registry.test.ts b/src/testcases/parallel/voting_registry.test.ts new file mode 100644 index 00000000..8fddf992 --- /dev/null +++ b/src/testcases/parallel/voting_registry.test.ts @@ -0,0 +1,850 @@ +import { + NEUTRON_DENOM, + WalletWrapper, + CosmosWrapper, +} from '../../helpers/cosmos'; +import { NeutronContract } from '../../helpers/types'; +import { getHeight } from '../../helpers/wait'; +import { TestStateLocalCosmosTestNet } from '../common_localcosmosnet'; + +// 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; + let cmInstantiator: WalletWrapper; + let cmDaoMember: 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(); + await testState.init(); + neutronChain = new CosmosWrapper( + testState.sdk1, + testState.blockWaiter1, + NEUTRON_DENOM, + ); + cmInstantiator = new WalletWrapper( + neutronChain, + testState.wallets.qaNeutronThree.genQaWal1, + ); + cmDaoMember = new 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, + instantiator: WalletWrapper, +): Promise> => { + const codeIds: Record = {}; + for (const contract of [ + VOTING_REGISTRY_CONTRACT_KEY, + NEUTRON_VAULT_CONTRACT_KEY, + ]) { + const codeId = await instantiator.storeWasm(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: 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: 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: WalletWrapper, vault: string, amount: string) => + cm.executeContract( + vault, + JSON.stringify({ + bond: {}, + }), + [{ denom: NEUTRON_DENOM, amount: amount }], + ); + +const unbondFunds = async (cm: WalletWrapper, vault: string, amount: string) => + cm.executeContract( + vault, + JSON.stringify({ + unbond: { amount: amount }, + }), + [], + ); + +const activateVotingVault = async ( + cm: WalletWrapper, + registry: string, + vault: string, +) => + cm.executeContract( + registry, + JSON.stringify({ + activate_voting_vault: { + voting_vault_contract: vault, + }, + }), + [], + ); + +const deactivateVotingVault = async ( + cm: WalletWrapper, + registry: string, + vault: string, +) => + cm.executeContract( + registry, + JSON.stringify({ + deactivate_voting_vault: { + voting_vault_contract: vault, + }, + }), + [], + ); + +const addVotingVault = async ( + cm: 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, + address: string, + contractAddresses: Record, + height?: number, +): Promise => { + if (typeof height === 'undefined') { + height = await 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, + contract: string, + height?: number, +): Promise => + chain.queryContract(contract, { + total_power_at_height: + typeof height === 'undefined' ? {} : { height: height }, + }); + +const getVotingPowerAtHeight = async ( + chain: 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, + 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; +}; From 522289faee5b191a5b7bc4e271b24be735cc7d7f Mon Sep 17 00:00:00 2001 From: sotnikov-s Date: Fri, 15 Sep 2023 19:06:56 +0300 Subject: [PATCH 2/5] add voting registry test to package.json --- package.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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 From 6a717404ad187ee94a04aeac0111c8ce3ce4adf9 Mon Sep 17 00:00:00 2001 From: sotnikov-s Date: Mon, 18 Sep 2023 14:59:57 +0300 Subject: [PATCH 3/5] replace local imports with neutronjsplus for voting registry test --- .../parallel/voting_registry.test.ts | 67 +++++++++++-------- 1 file changed, 39 insertions(+), 28 deletions(-) diff --git a/src/testcases/parallel/voting_registry.test.ts b/src/testcases/parallel/voting_registry.test.ts index 8fddf992..58807ffe 100644 --- a/src/testcases/parallel/voting_registry.test.ts +++ b/src/testcases/parallel/voting_registry.test.ts @@ -1,11 +1,12 @@ import { NEUTRON_DENOM, - WalletWrapper, - CosmosWrapper, -} from '../../helpers/cosmos'; -import { NeutronContract } from '../../helpers/types'; -import { getHeight } from '../../helpers/wait'; -import { TestStateLocalCosmosTestNet } from '../common_localcosmosnet'; + 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'; @@ -17,9 +18,9 @@ const NEUTRON_VAULT_3_CONTRACT_KEY = 'NEUTRON_VAULT_3'; describe('Neutron / Voting Registry', () => { let testState: TestStateLocalCosmosTestNet; - let neutronChain: CosmosWrapper; - let cmInstantiator: WalletWrapper; - let cmDaoMember: WalletWrapper; + let neutronChain: cosmosWrapper.CosmosWrapper; + let cmInstantiator: cosmosWrapper.WalletWrapper; + let cmDaoMember: cosmosWrapper.WalletWrapper; let contractAddresses: Record = {}; let votingRegistryAddr: string; let vault1Addr: string; @@ -38,18 +39,18 @@ describe('Neutron / Voting Registry', () => { const vault3Bonding = 5_000_000; beforeAll(async () => { - testState = new TestStateLocalCosmosTestNet(); + testState = new TestStateLocalCosmosTestNet(config); await testState.init(); - neutronChain = new CosmosWrapper( + neutronChain = new cosmosWrapper.CosmosWrapper( testState.sdk1, testState.blockWaiter1, NEUTRON_DENOM, ); - cmInstantiator = new WalletWrapper( + cmInstantiator = new cosmosWrapper.WalletWrapper( neutronChain, testState.wallets.qaNeutronThree.genQaWal1, ); - cmDaoMember = new WalletWrapper( + cmDaoMember = new cosmosWrapper.WalletWrapper( neutronChain, testState.wallets.qaNeutron.genQaWal1, ); @@ -529,15 +530,17 @@ describe('Neutron / Voting Registry', () => { }); const deployContracts = async ( - chain: CosmosWrapper, - instantiator: WalletWrapper, + 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(NeutronContract[contract]); + const codeId = await instantiator.storeWasm( + types.NeutronContract[contract], + ); expect(codeId).toBeGreaterThan(0); codeIds[contract] = codeId; } @@ -576,7 +579,7 @@ const deployContracts = async ( }; const deployVotingRegistry = async ( - instantiator: WalletWrapper, + instantiator: cosmosWrapper.WalletWrapper, vaults: string[], codeIds: Record, contractAddresses: Record, @@ -594,7 +597,7 @@ const deployVotingRegistry = async ( }; const deployNeutronVault = async ( - instantiator: WalletWrapper, + instantiator: cosmosWrapper.WalletWrapper, vaultKey: string, codeIds: Record, contractAddresses: Record, @@ -613,7 +616,11 @@ const deployNeutronVault = async ( contractAddresses[vaultKey] = res[0]._contract_address; }; -const bondFunds = async (cm: WalletWrapper, vault: string, amount: string) => +const bondFunds = async ( + cm: cosmosWrapper.WalletWrapper, + vault: string, + amount: string, +) => cm.executeContract( vault, JSON.stringify({ @@ -622,7 +629,11 @@ const bondFunds = async (cm: WalletWrapper, vault: string, amount: string) => [{ denom: NEUTRON_DENOM, amount: amount }], ); -const unbondFunds = async (cm: WalletWrapper, vault: string, amount: string) => +const unbondFunds = async ( + cm: cosmosWrapper.WalletWrapper, + vault: string, + amount: string, +) => cm.executeContract( vault, JSON.stringify({ @@ -632,7 +643,7 @@ const unbondFunds = async (cm: WalletWrapper, vault: string, amount: string) => ); const activateVotingVault = async ( - cm: WalletWrapper, + cm: cosmosWrapper.WalletWrapper, registry: string, vault: string, ) => @@ -647,7 +658,7 @@ const activateVotingVault = async ( ); const deactivateVotingVault = async ( - cm: WalletWrapper, + cm: cosmosWrapper.WalletWrapper, registry: string, vault: string, ) => @@ -662,7 +673,7 @@ const deactivateVotingVault = async ( ); const addVotingVault = async ( - cm: WalletWrapper, + cm: cosmosWrapper.WalletWrapper, registry: string, vault: string, ) => @@ -681,13 +692,13 @@ const addVotingVault = async ( * retrieves total voting powerdata from the same contracts. */ const getVotingPowerInfo = async ( - chain: CosmosWrapper, + chain: cosmosWrapper.CosmosWrapper, address: string, contractAddresses: Record, height?: number, ): Promise => { if (typeof height === 'undefined') { - height = await getHeight(chain.sdk); + height = await env.getHeight(chain.sdk); } const vault1Power = getVotingPowerAtHeight( chain, @@ -748,7 +759,7 @@ const getVotingPowerInfo = async ( }; const getTotalPowerAtHeight = async ( - chain: CosmosWrapper, + chain: cosmosWrapper.CosmosWrapper, contract: string, height?: number, ): Promise => @@ -758,7 +769,7 @@ const getTotalPowerAtHeight = async ( }); const getVotingPowerAtHeight = async ( - chain: CosmosWrapper, + chain: cosmosWrapper.CosmosWrapper, contract: string, address: string, height?: number, @@ -776,7 +787,7 @@ const getVotingPowerAtHeight = async ( }); const getVotingVaults = async ( - chain: CosmosWrapper, + chain: cosmosWrapper.CosmosWrapper, registry: string, height?: number, ): Promise => From 889fca4d41a80f759be233dfafdf64eb1ab96f9e Mon Sep 17 00:00:00 2001 From: sotnikov-s Date: Tue, 19 Sep 2023 18:12:06 +0300 Subject: [PATCH 4/5] describe expected total voting power --- src/testcases/parallel/governance.test.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/testcases/parallel/governance.test.ts b/src/testcases/parallel/governance.test.ts index 057da3de..417a3a94 100644 --- a/src/testcases/parallel/governance.test.ts +++ b/src/testcases/parallel/governance.test.ts @@ -131,6 +131,7 @@ describe('Neutron / Governance', () => { await wait.getWithAttempts( neutronChain.blockWaiter, async () => await mainDao.queryTotalVotingPower(), + // 3x1000000000 + 100000000 from investors vault (see neutron/network/init-neutrond.sh) async (response) => response.power == 3100000000, 20, ); From 884b55e415c4c079d89752bea7c61b85809c9ef0 Mon Sep 17 00:00:00 2001 From: sotnikov-s Date: Wed, 27 Sep 2023 14:34:27 +0300 Subject: [PATCH 5/5] add historical vp checks on schedules add/remove --- .../tge.investors_vesting_vault.test.ts | 663 +++++++++++++----- 1 file changed, 489 insertions(+), 174 deletions(-) diff --git a/src/testcases/parallel/tge.investors_vesting_vault.test.ts b/src/testcases/parallel/tge.investors_vesting_vault.test.ts index a69029a6..e255f01a 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); + }); }); }); }); @@ -515,6 +783,26 @@ describe('Neutron / TGE / Investors vesting vault', () => { ).rejects.toThrow(/Unauthorized/); }); + 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 @@ -644,7 +932,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: { @@ -656,7 +944,6 @@ const setInvestorsVestingAsset = async ( }, }), ); - expect(execRes.code).toBe(0); }; const deployInvestorsVestingVaultContract = async ( @@ -695,3 +982,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, + }, + });