diff --git a/idl/voter_stake_registry.ts b/idl/voter_stake_registry.ts index 95aab438..c1fee068 100644 --- a/idl/voter_stake_registry.ts +++ b/idl/voter_stake_registry.ts @@ -13,10 +13,9 @@ export type VoterStakeRegistry = { "", "- Create a SPL governance realm.", "- Create a governance registry account.", - "- Add exchange rates for any tokens one wants to deposit. For example,", - "if one wants to vote with tokens A and B, where token B has twice the", - "voting power of token A, then the exchange rate of B would be 2 and the", - "exchange rate of A would be 1.", + "- Add exchange rates for any tokens one wants to deposit. For example, if one wants to vote with", + "tokens A and B, where token B has twice the voting power of token A, then the exchange rate of", + "B would be 2 and the exchange rate of A would be 1.", "- Create a voter account.", "- Deposit tokens into this program, with an optional lockup period.", "- Vote.", @@ -181,22 +180,6 @@ export type VoterStakeRegistry = { "name": "idx", "type": "u16" }, - { - "name": "digitShift", - "type": "i8" - }, - { - "name": "baselineVoteWeightScaledFactor", - "type": "u64" - }, - { - "name": "maxExtraLockupVoteWeightScaledFactor", - "type": "u64" - }, - { - "name": "lockupSaturationSecs", - "type": "u64" - }, { "name": "grantAuthority", "type": { @@ -269,7 +252,8 @@ export type VoterStakeRegistry = { "isMut": true, "isSigner": false, "docs": [ - "PDA([\"reward_pool\", deposit_authority , fill_authority], reward_program)" + "PDA([\"reward_pool\", deposit_authority , fill_authority],", + "reward_program)" ] }, { @@ -277,7 +261,8 @@ export type VoterStakeRegistry = { "isMut": true, "isSigner": false, "docs": [ - "PDA([\"mining\", mining owner , reward_pool], reward_program)" + "PDA([\"mining\", mining owner , reward_pool],", + "reward_program)" ] }, { @@ -344,11 +329,6 @@ export type VoterStakeRegistry = { "name": "associatedTokenProgram", "isMut": false, "isSigner": false - }, - { - "name": "rent", - "isMut": false, - "isSigner": false } ], "args": [ @@ -475,21 +455,6 @@ export type VoterStakeRegistry = { "name": "tokenProgram", "isMut": false, "isSigner": false - }, - { - "name": "rewardPool", - "isMut": true, - "isSigner": false - }, - { - "name": "depositMining", - "isMut": true, - "isSigner": false - }, - { - "name": "rewardsProgram", - "isMut": false, - "isSigner": false } ], "args": [ @@ -500,18 +465,6 @@ export type VoterStakeRegistry = { { "name": "amount", "type": "u64" - }, - { - "name": "registrarBump", - "type": "u8" - }, - { - "name": "realmGoverningMintPubkey", - "type": "publicKey" - }, - { - "name": "realmPubkey", - "type": "publicKey" } ] }, @@ -579,6 +532,21 @@ export type VoterStakeRegistry = { "name": "voterAuthority", "isMut": false, "isSigner": true + }, + { + "name": "rewardPool", + "isMut": true, + "isSigner": false + }, + { + "name": "depositMining", + "isMut": true, + "isSigner": false + }, + { + "name": "rewardsProgram", + "isMut": false, + "isSigner": false } ], "args": [ @@ -606,6 +574,23 @@ export type VoterStakeRegistry = { "isMut": false, "isSigner": true }, + { + "name": "depositMining", + "isMut": true, + "isSigner": false, + "docs": [ + "PDA([\"mining\", mining owner , reward_pool],", + "reward_program)" + ] + }, + { + "name": "rewardPool", + "isMut": false, + "isSigner": false, + "docs": [ + "PDA([\"reward_pool\", deposit_authority[aka registrar in our case]], rewards_program)" + ] + }, { "name": "solDestination", "isMut": true, @@ -615,6 +600,11 @@ export type VoterStakeRegistry = { "name": "tokenProgram", "isMut": false, "isSigner": false + }, + { + "name": "rewardsProgram", + "isMut": false, + "isSigner": false } ], "args": [] @@ -645,7 +635,7 @@ export type VoterStakeRegistry = { ] }, { - "name": "lockTokens", + "name": "stake", "accounts": [ { "name": "registrar", @@ -667,7 +657,8 @@ export type VoterStakeRegistry = { "isMut": true, "isSigner": false, "docs": [ - "PDA([\"reward_pool\", deposit_authority , fill_authority], reward_program)" + "PDA([\"reward_pool\", deposit_authority , fill_authority],", + "reward_program)" ] }, { @@ -675,7 +666,8 @@ export type VoterStakeRegistry = { "isMut": true, "isSigner": false, "docs": [ - "PDA([\"mining\", mining owner , reward_pool], reward_program)" + "PDA([\"mining\", mining owner , reward_pool],", + "reward_program)" ] }, { @@ -696,19 +688,11 @@ export type VoterStakeRegistry = { { "name": "amount", "type": "u64" - }, - { - "name": "realmGoverningMintPubkey", - "type": "publicKey" - }, - { - "name": "realmPubkey", - "type": "publicKey" } ] }, { - "name": "restakeDeposit", + "name": "extendStake", "accounts": [ { "name": "registrar", @@ -721,39 +705,26 @@ export type VoterStakeRegistry = { "isSigner": false }, { - "name": "depositToken", - "isMut": true, - "isSigner": false - }, - { - "name": "depositAuthority", - "isMut": false, - "isSigner": true, - "docs": [ - "The owner of the deposit and its reward's mining account" - ] - }, - { - "name": "tokenProgram", + "name": "voterAuthority", "isMut": false, - "isSigner": false - }, - { - "name": "vault", - "isMut": true, - "isSigner": false + "isSigner": true }, { "name": "rewardPool", "isMut": true, - "isSigner": false + "isSigner": false, + "docs": [ + "PDA([\"reward_pool\", deposit_authority , fill_authority],", + "reward_program)" + ] }, { "name": "depositMining", "isMut": true, "isSigner": false, "docs": [ - "PDA([\"mining\", mining owner , reward_pool], reward_program)" + "PDA([\"mining\", mining owner , reward_pool],", + "reward_program)" ] }, { @@ -764,7 +735,11 @@ export type VoterStakeRegistry = { ], "args": [ { - "name": "depositEntryIndex", + "name": "sourceDepositEntryIndex", + "type": "u8" + }, + { + "name": "targetDepositEntryIndex", "type": "u8" }, { @@ -773,18 +748,6 @@ export type VoterStakeRegistry = { "defined": "LockupPeriod" } }, - { - "name": "registrarBump", - "type": "u8" - }, - { - "name": "realmGoverningMintPubkey", - "type": "publicKey" - }, - { - "name": "realmPubkey", - "type": "publicKey" - }, { "name": "additionalAmount", "type": "u64" @@ -821,12 +784,13 @@ export type VoterStakeRegistry = { "isMut": true, "isSigner": false, "docs": [ - "PDA([\"mining\", mining owner , reward_pool], reward_program)" + "PDA([\"mining\", mining owner , reward_pool],", + "reward_program)" ] }, { "name": "miningOwner", - "isMut": true, + "isMut": false, "isSigner": true }, { @@ -872,7 +836,7 @@ export type VoterStakeRegistry = { ], "accounts": [ { - "name": "Registrar", + "name": "registrar", "docs": [ "Instance of a voting rights distributor." ], @@ -910,22 +874,24 @@ export type VoterStakeRegistry = { ] } }, - { - "name": "timeOffset", - "docs": [ - "Debug only: time offset, to allow tests to move forward in time." - ], - "type": "i64" - }, { "name": "bump", "type": "u8" + }, + { + "name": "padding", + "type": { + "array": [ + "u8", + 7 + ] + } } ] } }, { - "name": "Voter", + "name": "voter", "docs": [ "User account for minting voting rights." ], @@ -973,6 +939,63 @@ export type VoterStakeRegistry = { } ], "types": [ + { + "name": "VestingInfo", + "type": { + "kind": "struct", + "fields": [ + { + "name": "rate", + "docs": [ + "Amount of tokens vested each period" + ], + "type": "u64" + }, + { + "name": "nextTimestamp", + "docs": [ + "Time of the next upcoming vesting" + ], + "type": "u64" + } + ] + } + }, + { + "name": "LockingInfo", + "type": { + "kind": "struct", + "fields": [ + { + "name": "amount", + "docs": [ + "Amount of locked tokens" + ], + "type": "u64" + }, + { + "name": "endTimestamp", + "docs": [ + "Time at which the lockup fully ends (None for Constant lockup)" + ], + "type": { + "option": "u64" + } + }, + { + "name": "vesting", + "docs": [ + "Information about vesting, if any" + ], + "type": { + "option": { + "defined": "VestingInfo" + } + } + } + ] + } + }, { "name": "DepositEntry", "docs": [ @@ -1033,8 +1056,6 @@ export type VoterStakeRegistry = { { "name": "startTs", "docs": [ - "Note, that if start_ts is in the future, the funds are nevertheless", - "locked up!", "Start of the lockup." ], "type": "u64" @@ -1091,15 +1112,198 @@ export type VoterStakeRegistry = { } }, { - "name": "LockupKind", + "name": "VotingMintConfig", + "docs": [ + "Exchange rate for an asset that can be used to mint voting rights.", + "", + "See documentation of configure_voting_mint for details on how", + "native token amounts convert to vote weight." + ], + "type": { + "kind": "struct", + "fields": [ + { + "name": "mint", + "docs": [ + "Mint for this entry." + ], + "type": "publicKey" + }, + { + "name": "grantAuthority", + "docs": [ + "The authority that is allowed to push grants into voters" + ], + "type": "publicKey" + } + ] + } + }, + { + "name": "RewardsInstruction", "type": { "kind": "enum", "variants": [ { - "name": "None" + "name": "InitializePool", + "fields": [ + { + "name": "fill_authority", + "docs": [ + "Account can fill the reward vault" + ], + "type": "publicKey" + }, + { + "name": "distribution_authority", + "docs": [ + "Account can distribute rewards for stakers" + ], + "type": "publicKey" + } + ] + }, + { + "name": "FillVault", + "fields": [ + { + "name": "amount", + "docs": [ + "Amount to fill" + ], + "type": "u64" + }, + { + "name": "distribution_ends_at", + "docs": [ + "Rewards distribution ends at given date" + ], + "type": "u64" + } + ] }, { - "name": "Constant" + "name": "InitializeMining", + "fields": [ + { + "name": "mining_owner", + "docs": [ + "Represent the end-user, owner of the mining" + ], + "type": "publicKey" + } + ] + }, + { + "name": "DepositMining", + "fields": [ + { + "name": "amount", + "docs": [ + "Amount to deposit" + ], + "type": "u64" + }, + { + "name": "lockup_period", + "docs": [ + "Lockup Period" + ], + "type": { + "defined": "LockupPeriod" + } + }, + { + "name": "owner", + "docs": [ + "Specifies the owner of the Mining Account" + ], + "type": "publicKey" + } + ] + }, + { + "name": "WithdrawMining", + "fields": [ + { + "name": "amount", + "docs": [ + "Amount to withdraw" + ], + "type": "u64" + }, + { + "name": "owner", + "docs": [ + "Specifies the owner of the Mining Account" + ], + "type": "publicKey" + } + ] + }, + { + "name": "Claim" + }, + { + "name": "ExtendStake", + "fields": [ + { + "name": "old_lockup_period", + "docs": [ + "Lockup period before restaking. Actually it's only needed", + "for Flex to AnyPeriod edge case" + ], + "type": { + "defined": "LockupPeriod" + } + }, + { + "name": "new_lockup_period", + "docs": [ + "Requested lockup period for restaking" + ], + "type": { + "defined": "LockupPeriod" + } + }, + { + "name": "deposit_start_ts", + "docs": [ + "Deposit start_ts" + ], + "type": "u64" + }, + { + "name": "base_amount", + "docs": [ + "Amount of tokens to be restaked, this", + "number cannot be decreased. It reflects the number of staked tokens", + "before the extend_stake function call" + ], + "type": "u64" + }, + { + "name": "additional_amount", + "docs": [ + "In case user wants to increase it's staked number of tokens,", + "the addition amount might be provided" + ], + "type": "u64" + }, + { + "name": "mining_owner", + "docs": [ + "The wallet who owns the mining account" + ], + "type": "publicKey" + } + ] + }, + { + "name": "DistributeRewards" + }, + { + "name": "CloseMining" } ] } @@ -1112,6 +1316,9 @@ export type VoterStakeRegistry = { { "name": "None" }, + { + "name": "Flex" + }, { "name": "ThreeMonths" }, @@ -1121,9 +1328,6 @@ export type VoterStakeRegistry = { { "name": "OneYear" }, - { - "name": "Flex" - }, { "name": "Test" } @@ -1131,137 +1335,36 @@ export type VoterStakeRegistry = { } }, { - "name": "VotingMintConfig", - "docs": [ - "Exchange rate for an asset that can be used to mint voting rights.", - "", - "See documentation of configure_voting_mint for details on how", - "native token amounts convert to vote weight." - ], + "name": "LockupKind", "type": { - "kind": "struct", - "fields": [ - { - "name": "mint", - "docs": [ - "Mint for this entry." - ], - "type": "publicKey" - }, - { - "name": "grantAuthority", - "docs": [ - "The authority that is allowed to push grants into voters" - ], - "type": "publicKey" - }, - { - "name": "baselineVoteWeightScaledFactor", - "docs": [ - "Vote weight factor for all funds in the account, no matter if locked or not.", - "", - "In 1/SCALED_FACTOR_BASE units." - ], - "type": "u64" - }, - { - "name": "maxExtraLockupVoteWeightScaledFactor", - "docs": [ - "Maximum extra vote weight factor for lockups.", - "", - "This is the extra votes gained for lockups lasting lockup_saturation_secs or", - "longer. Shorter lockups receive only a fraction of the maximum extra vote weight,", - "based on lockup_time divided by lockup_saturation_secs.", - "", - "In 1/SCALED_FACTOR_BASE units." - ], - "type": "u64" - }, - { - "name": "lockupSaturationSecs", - "docs": [ - "Number of seconds of lockup needed to reach the maximum lockup bonus." - ], - "type": "u64" - }, - { - "name": "digitShift", - "docs": [ - "Number of digits to shift native amounts, applying a 10^digit_shift factor." - ], - "type": "i8" - }, - { - "name": "padding", - "type": { - "array": [ - "u8", - 7 - ] - } - } - ] - } - }, - { - "name": "LockingInfo", - "type": { - "kind": "struct", - "fields": [ - { - "name": "amount", - "docs": [ - "Amount of locked tokens" - ], - "type": "u64" - }, - { - "name": "endTimestamp", - "docs": [ - "Time at which the lockup fully ends (None for Constant lockup)" - ], - "type": { - "option": "u64" - } - }, - { - "name": "vesting", - "docs": [ - "Information about vesting, if any" - ], - "type": { - "option": { - "defined": "VestingInfo" - } - } - } - ] - } - }, - { - "name": "VestingInfo", - "type": { - "kind": "struct", - "fields": [ + "kind": "enum", + "variants": [ { - "name": "rate", - "docs": [ - "Amount of tokens vested each period" - ], - "type": "u64" + "name": "None" }, { - "name": "nextTimestamp", - "docs": [ - "Time of the next upcoming vesting" - ], - "type": "u64" + "name": "Constant" } ] } } ], "events": [ + { + "name": "VoterInfo", + "fields": [ + { + "name": "votingPower", + "type": "u64", + "index": false + }, + { + "name": "votingPowerBaseline", + "type": "u64", + "index": false + } + ] + }, { "name": "DepositEntryInfo", "fields": [ @@ -1300,21 +1403,6 @@ export type VoterStakeRegistry = { "index": false } ] - }, - { - "name": "VoterInfo", - "fields": [ - { - "name": "votingPower", - "type": "u64", - "index": false - }, - { - "name": "votingPowerBaseline", - "type": "u64", - "index": false - } - ] } ], "errors": [ @@ -1435,7 +1523,7 @@ export type VoterStakeRegistry = { }, { "code": 6023, - "name": "RestakeDepositIsNotAllowed", + "name": "ExtendDepositIsNotAllowed", "msg": "" }, { @@ -1457,6 +1545,11 @@ export type VoterStakeRegistry = { "code": 6027, "name": "DepositEntryIsOld", "msg": "Locking up tokens is only allowed for freshly-deposited deposit entry" + }, + { + "code": 6028, + "name": "ArithmeticOverflow", + "msg": "Arithmetic operation has beed overflowed" } ] }; @@ -1476,10 +1569,9 @@ export const IDL: VoterStakeRegistry = { "", "- Create a SPL governance realm.", "- Create a governance registry account.", - "- Add exchange rates for any tokens one wants to deposit. For example,", - "if one wants to vote with tokens A and B, where token B has twice the", - "voting power of token A, then the exchange rate of B would be 2 and the", - "exchange rate of A would be 1.", + "- Add exchange rates for any tokens one wants to deposit. For example, if one wants to vote with", + "tokens A and B, where token B has twice the voting power of token A, then the exchange rate of", + "B would be 2 and the exchange rate of A would be 1.", "- Create a voter account.", "- Deposit tokens into this program, with an optional lockup period.", "- Vote.", @@ -1644,22 +1736,6 @@ export const IDL: VoterStakeRegistry = { "name": "idx", "type": "u16" }, - { - "name": "digitShift", - "type": "i8" - }, - { - "name": "baselineVoteWeightScaledFactor", - "type": "u64" - }, - { - "name": "maxExtraLockupVoteWeightScaledFactor", - "type": "u64" - }, - { - "name": "lockupSaturationSecs", - "type": "u64" - }, { "name": "grantAuthority", "type": { @@ -1732,7 +1808,8 @@ export const IDL: VoterStakeRegistry = { "isMut": true, "isSigner": false, "docs": [ - "PDA([\"reward_pool\", deposit_authority , fill_authority], reward_program)" + "PDA([\"reward_pool\", deposit_authority , fill_authority],", + "reward_program)" ] }, { @@ -1740,7 +1817,8 @@ export const IDL: VoterStakeRegistry = { "isMut": true, "isSigner": false, "docs": [ - "PDA([\"mining\", mining owner , reward_pool], reward_program)" + "PDA([\"mining\", mining owner , reward_pool],", + "reward_program)" ] }, { @@ -1807,11 +1885,6 @@ export const IDL: VoterStakeRegistry = { "name": "associatedTokenProgram", "isMut": false, "isSigner": false - }, - { - "name": "rent", - "isMut": false, - "isSigner": false } ], "args": [ @@ -1938,21 +2011,6 @@ export const IDL: VoterStakeRegistry = { "name": "tokenProgram", "isMut": false, "isSigner": false - }, - { - "name": "rewardPool", - "isMut": true, - "isSigner": false - }, - { - "name": "depositMining", - "isMut": true, - "isSigner": false - }, - { - "name": "rewardsProgram", - "isMut": false, - "isSigner": false } ], "args": [ @@ -1963,18 +2021,6 @@ export const IDL: VoterStakeRegistry = { { "name": "amount", "type": "u64" - }, - { - "name": "registrarBump", - "type": "u8" - }, - { - "name": "realmGoverningMintPubkey", - "type": "publicKey" - }, - { - "name": "realmPubkey", - "type": "publicKey" } ] }, @@ -2042,6 +2088,21 @@ export const IDL: VoterStakeRegistry = { "name": "voterAuthority", "isMut": false, "isSigner": true + }, + { + "name": "rewardPool", + "isMut": true, + "isSigner": false + }, + { + "name": "depositMining", + "isMut": true, + "isSigner": false + }, + { + "name": "rewardsProgram", + "isMut": false, + "isSigner": false } ], "args": [ @@ -2069,6 +2130,23 @@ export const IDL: VoterStakeRegistry = { "isMut": false, "isSigner": true }, + { + "name": "depositMining", + "isMut": true, + "isSigner": false, + "docs": [ + "PDA([\"mining\", mining owner , reward_pool],", + "reward_program)" + ] + }, + { + "name": "rewardPool", + "isMut": false, + "isSigner": false, + "docs": [ + "PDA([\"reward_pool\", deposit_authority[aka registrar in our case]], rewards_program)" + ] + }, { "name": "solDestination", "isMut": true, @@ -2078,6 +2156,11 @@ export const IDL: VoterStakeRegistry = { "name": "tokenProgram", "isMut": false, "isSigner": false + }, + { + "name": "rewardsProgram", + "isMut": false, + "isSigner": false } ], "args": [] @@ -2108,7 +2191,7 @@ export const IDL: VoterStakeRegistry = { ] }, { - "name": "lockTokens", + "name": "stake", "accounts": [ { "name": "registrar", @@ -2130,7 +2213,8 @@ export const IDL: VoterStakeRegistry = { "isMut": true, "isSigner": false, "docs": [ - "PDA([\"reward_pool\", deposit_authority , fill_authority], reward_program)" + "PDA([\"reward_pool\", deposit_authority , fill_authority],", + "reward_program)" ] }, { @@ -2138,7 +2222,8 @@ export const IDL: VoterStakeRegistry = { "isMut": true, "isSigner": false, "docs": [ - "PDA([\"mining\", mining owner , reward_pool], reward_program)" + "PDA([\"mining\", mining owner , reward_pool],", + "reward_program)" ] }, { @@ -2159,19 +2244,11 @@ export const IDL: VoterStakeRegistry = { { "name": "amount", "type": "u64" - }, - { - "name": "realmGoverningMintPubkey", - "type": "publicKey" - }, - { - "name": "realmPubkey", - "type": "publicKey" } ] }, { - "name": "restakeDeposit", + "name": "extendStake", "accounts": [ { "name": "registrar", @@ -2184,39 +2261,26 @@ export const IDL: VoterStakeRegistry = { "isSigner": false }, { - "name": "depositToken", - "isMut": true, - "isSigner": false - }, - { - "name": "depositAuthority", - "isMut": false, - "isSigner": true, - "docs": [ - "The owner of the deposit and its reward's mining account" - ] - }, - { - "name": "tokenProgram", + "name": "voterAuthority", "isMut": false, - "isSigner": false - }, - { - "name": "vault", - "isMut": true, - "isSigner": false + "isSigner": true }, { "name": "rewardPool", "isMut": true, - "isSigner": false + "isSigner": false, + "docs": [ + "PDA([\"reward_pool\", deposit_authority , fill_authority],", + "reward_program)" + ] }, { "name": "depositMining", "isMut": true, "isSigner": false, "docs": [ - "PDA([\"mining\", mining owner , reward_pool], reward_program)" + "PDA([\"mining\", mining owner , reward_pool],", + "reward_program)" ] }, { @@ -2227,7 +2291,11 @@ export const IDL: VoterStakeRegistry = { ], "args": [ { - "name": "depositEntryIndex", + "name": "sourceDepositEntryIndex", + "type": "u8" + }, + { + "name": "targetDepositEntryIndex", "type": "u8" }, { @@ -2236,18 +2304,6 @@ export const IDL: VoterStakeRegistry = { "defined": "LockupPeriod" } }, - { - "name": "registrarBump", - "type": "u8" - }, - { - "name": "realmGoverningMintPubkey", - "type": "publicKey" - }, - { - "name": "realmPubkey", - "type": "publicKey" - }, { "name": "additionalAmount", "type": "u64" @@ -2284,12 +2340,13 @@ export const IDL: VoterStakeRegistry = { "isMut": true, "isSigner": false, "docs": [ - "PDA([\"mining\", mining owner , reward_pool], reward_program)" + "PDA([\"mining\", mining owner , reward_pool],", + "reward_program)" ] }, { "name": "miningOwner", - "isMut": true, + "isMut": false, "isSigner": true }, { @@ -2335,7 +2392,7 @@ export const IDL: VoterStakeRegistry = { ], "accounts": [ { - "name": "Registrar", + "name": "registrar", "docs": [ "Instance of a voting rights distributor." ], @@ -2373,22 +2430,24 @@ export const IDL: VoterStakeRegistry = { ] } }, - { - "name": "timeOffset", - "docs": [ - "Debug only: time offset, to allow tests to move forward in time." - ], - "type": "i64" - }, { "name": "bump", "type": "u8" + }, + { + "name": "padding", + "type": { + "array": [ + "u8", + 7 + ] + } } ] } }, { - "name": "Voter", + "name": "voter", "docs": [ "User account for minting voting rights." ], @@ -2436,6 +2495,63 @@ export const IDL: VoterStakeRegistry = { } ], "types": [ + { + "name": "VestingInfo", + "type": { + "kind": "struct", + "fields": [ + { + "name": "rate", + "docs": [ + "Amount of tokens vested each period" + ], + "type": "u64" + }, + { + "name": "nextTimestamp", + "docs": [ + "Time of the next upcoming vesting" + ], + "type": "u64" + } + ] + } + }, + { + "name": "LockingInfo", + "type": { + "kind": "struct", + "fields": [ + { + "name": "amount", + "docs": [ + "Amount of locked tokens" + ], + "type": "u64" + }, + { + "name": "endTimestamp", + "docs": [ + "Time at which the lockup fully ends (None for Constant lockup)" + ], + "type": { + "option": "u64" + } + }, + { + "name": "vesting", + "docs": [ + "Information about vesting, if any" + ], + "type": { + "option": { + "defined": "VestingInfo" + } + } + } + ] + } + }, { "name": "DepositEntry", "docs": [ @@ -2496,8 +2612,6 @@ export const IDL: VoterStakeRegistry = { { "name": "startTs", "docs": [ - "Note, that if start_ts is in the future, the funds are nevertheless", - "locked up!", "Start of the lockup." ], "type": "u64" @@ -2553,46 +2667,6 @@ export const IDL: VoterStakeRegistry = { ] } }, - { - "name": "LockupKind", - "type": { - "kind": "enum", - "variants": [ - { - "name": "None" - }, - { - "name": "Constant" - } - ] - } - }, - { - "name": "LockupPeriod", - "type": { - "kind": "enum", - "variants": [ - { - "name": "None" - }, - { - "name": "ThreeMonths" - }, - { - "name": "SixMonths" - }, - { - "name": "OneYear" - }, - { - "name": "Flex" - }, - { - "name": "Test" - } - ] - } - }, { "name": "VotingMintConfig", "docs": [ @@ -2617,114 +2691,236 @@ export const IDL: VoterStakeRegistry = { "The authority that is allowed to push grants into voters" ], "type": "publicKey" - }, - { - "name": "baselineVoteWeightScaledFactor", - "docs": [ - "Vote weight factor for all funds in the account, no matter if locked or not.", - "", - "In 1/SCALED_FACTOR_BASE units." - ], - "type": "u64" - }, + } + ] + } + }, + { + "name": "RewardsInstruction", + "type": { + "kind": "enum", + "variants": [ { - "name": "maxExtraLockupVoteWeightScaledFactor", - "docs": [ - "Maximum extra vote weight factor for lockups.", - "", - "This is the extra votes gained for lockups lasting lockup_saturation_secs or", - "longer. Shorter lockups receive only a fraction of the maximum extra vote weight,", - "based on lockup_time divided by lockup_saturation_secs.", - "", - "In 1/SCALED_FACTOR_BASE units." - ], - "type": "u64" + "name": "InitializePool", + "fields": [ + { + "name": "fill_authority", + "docs": [ + "Account can fill the reward vault" + ], + "type": "publicKey" + }, + { + "name": "distribution_authority", + "docs": [ + "Account can distribute rewards for stakers" + ], + "type": "publicKey" + } + ] + }, + { + "name": "FillVault", + "fields": [ + { + "name": "amount", + "docs": [ + "Amount to fill" + ], + "type": "u64" + }, + { + "name": "distribution_ends_at", + "docs": [ + "Rewards distribution ends at given date" + ], + "type": "u64" + } + ] }, { - "name": "lockupSaturationSecs", - "docs": [ - "Number of seconds of lockup needed to reach the maximum lockup bonus." - ], - "type": "u64" + "name": "InitializeMining", + "fields": [ + { + "name": "mining_owner", + "docs": [ + "Represent the end-user, owner of the mining" + ], + "type": "publicKey" + } + ] + }, + { + "name": "DepositMining", + "fields": [ + { + "name": "amount", + "docs": [ + "Amount to deposit" + ], + "type": "u64" + }, + { + "name": "lockup_period", + "docs": [ + "Lockup Period" + ], + "type": { + "defined": "LockupPeriod" + } + }, + { + "name": "owner", + "docs": [ + "Specifies the owner of the Mining Account" + ], + "type": "publicKey" + } + ] + }, + { + "name": "WithdrawMining", + "fields": [ + { + "name": "amount", + "docs": [ + "Amount to withdraw" + ], + "type": "u64" + }, + { + "name": "owner", + "docs": [ + "Specifies the owner of the Mining Account" + ], + "type": "publicKey" + } + ] + }, + { + "name": "Claim" + }, + { + "name": "ExtendStake", + "fields": [ + { + "name": "old_lockup_period", + "docs": [ + "Lockup period before restaking. Actually it's only needed", + "for Flex to AnyPeriod edge case" + ], + "type": { + "defined": "LockupPeriod" + } + }, + { + "name": "new_lockup_period", + "docs": [ + "Requested lockup period for restaking" + ], + "type": { + "defined": "LockupPeriod" + } + }, + { + "name": "deposit_start_ts", + "docs": [ + "Deposit start_ts" + ], + "type": "u64" + }, + { + "name": "base_amount", + "docs": [ + "Amount of tokens to be restaked, this", + "number cannot be decreased. It reflects the number of staked tokens", + "before the extend_stake function call" + ], + "type": "u64" + }, + { + "name": "additional_amount", + "docs": [ + "In case user wants to increase it's staked number of tokens,", + "the addition amount might be provided" + ], + "type": "u64" + }, + { + "name": "mining_owner", + "docs": [ + "The wallet who owns the mining account" + ], + "type": "publicKey" + } + ] }, { - "name": "digitShift", - "docs": [ - "Number of digits to shift native amounts, applying a 10^digit_shift factor." - ], - "type": "i8" + "name": "DistributeRewards" }, { - "name": "padding", - "type": { - "array": [ - "u8", - 7 - ] - } + "name": "CloseMining" } ] } }, { - "name": "LockingInfo", + "name": "LockupPeriod", "type": { - "kind": "struct", - "fields": [ + "kind": "enum", + "variants": [ { - "name": "amount", - "docs": [ - "Amount of locked tokens" - ], - "type": "u64" + "name": "None" }, { - "name": "endTimestamp", - "docs": [ - "Time at which the lockup fully ends (None for Constant lockup)" - ], - "type": { - "option": "u64" - } + "name": "Flex" }, { - "name": "vesting", - "docs": [ - "Information about vesting, if any" - ], - "type": { - "option": { - "defined": "VestingInfo" - } - } + "name": "ThreeMonths" + }, + { + "name": "SixMonths" + }, + { + "name": "OneYear" + }, + { + "name": "Test" } ] } }, { - "name": "VestingInfo", + "name": "LockupKind", "type": { - "kind": "struct", - "fields": [ + "kind": "enum", + "variants": [ { - "name": "rate", - "docs": [ - "Amount of tokens vested each period" - ], - "type": "u64" + "name": "None" }, { - "name": "nextTimestamp", - "docs": [ - "Time of the next upcoming vesting" - ], - "type": "u64" + "name": "Constant" } ] } } ], "events": [ + { + "name": "VoterInfo", + "fields": [ + { + "name": "votingPower", + "type": "u64", + "index": false + }, + { + "name": "votingPowerBaseline", + "type": "u64", + "index": false + } + ] + }, { "name": "DepositEntryInfo", "fields": [ @@ -2763,21 +2959,6 @@ export const IDL: VoterStakeRegistry = { "index": false } ] - }, - { - "name": "VoterInfo", - "fields": [ - { - "name": "votingPower", - "type": "u64", - "index": false - }, - { - "name": "votingPowerBaseline", - "type": "u64", - "index": false - } - ] } ], "errors": [ @@ -2898,7 +3079,7 @@ export const IDL: VoterStakeRegistry = { }, { "code": 6023, - "name": "RestakeDepositIsNotAllowed", + "name": "ExtendDepositIsNotAllowed", "msg": "" }, { @@ -2920,6 +3101,11 @@ export const IDL: VoterStakeRegistry = { "code": 6027, "name": "DepositEntryIsOld", "msg": "Locking up tokens is only allowed for freshly-deposited deposit entry" + }, + { + "code": 6028, + "name": "ArithmeticOverflow", + "msg": "Arithmetic operation has beed overflowed" } ] }; diff --git a/program-states/src/error.rs b/program-states/src/error.rs index 4af5e0a7..e5646941 100644 --- a/program-states/src/error.rs +++ b/program-states/src/error.rs @@ -89,4 +89,19 @@ pub enum VsrError { // 6028 / 0x178c #[msg("Arithmetic operation has beed overflowed")] ArithmeticOverflow, + // 6029 / 0x178d + #[msg("Rewards: Delegate must have at least 15_000_000 of own weighted stake")] + InsufficientWeightedStake, + // 6030 / 0x178e + #[msg("Rewards: Invalid delegate account")] + InvalidDelegate, + // 6031 / 0x178f + #[msg("Rewards: Invalid mining account")] + InvalidMining, + // 6032 / 0x1790 + #[msg("Rewards: Updating delegate is sooner than 5 days")] + DelegateUpdateIsTooSoon, + // 6033 / 0x1791 + #[msg("Rewards: Cannot change delegate to the same delegate")] + SameDelegate, } diff --git a/program-states/src/state/deposit_entry.rs b/program-states/src/state/deposit_entry.rs index 6dc650e7..49cb8b39 100644 --- a/program-states/src/state/deposit_entry.rs +++ b/program-states/src/state/deposit_entry.rs @@ -1,29 +1,31 @@ -use crate::state::lockup::{Lockup, LockupKind}; - -use crate::state::LockupPeriod; +use crate::state::{ + lockup::{Lockup, LockupKind}, + LockupPeriod, +}; use anchor_lang::prelude::*; /// Bookkeeping for a single deposit for a given mint and lockup schedule. #[zero_copy] -#[derive(Default)] +#[derive(Default, Debug)] pub struct DepositEntry { // Locked state. pub lockup: Lockup, - /// Delegated staker + /// Delegated staker. It's an address of a Delegate. pub delegate: Pubkey, /// Amount in deposited, in native currency. Withdraws of vested tokens /// directly reduce this amount. - /// /// This directly tracks the total amount added by the user. They may /// never withdraw more than this amount. pub amount_deposited_native: u64, + /// The last time when the delegate was updated + pub delegate_last_update_ts: u64, // Points to the VotingMintConfig this deposit uses. pub voting_mint_config_idx: u8, // True if the deposit entry is being used. pub is_used: bool, pub _reserved1: [u8; 6], } -const_assert!(std::mem::size_of::() == 32 + 32 + 8 + 1 + 1 + 6); +const_assert!(std::mem::size_of::() == 32 + 32 + 8 + 8 + 1 + 1 + 6); const_assert!(std::mem::size_of::() % 8 == 0); impl DepositEntry { @@ -84,7 +86,10 @@ impl DepositEntry { #[cfg(test)] mod tests { use super::*; - use crate::state::{LockupKind::Constant, LockupKind::None, LockupPeriod}; + use crate::state::{ + LockupKind::{Constant, None}, + LockupPeriod, + }; #[test] pub fn far_future_lockup_start_test() -> Result<()> { @@ -101,13 +106,10 @@ mod tests { end_ts: lockup_start + LockupPeriod::Flex.to_secs(), // start + cooldown + period kind: Constant, period, - cooldown_requested: false, - cooldown_ends_at: 0, - _reserved1: [0; 5], + ..Default::default() }, is_used: true, - voting_mint_config_idx: 0, - _reserved1: [0; 6], + ..Default::default() }; let baseline_vote_weight = deposit.amount_deposited_native; @@ -127,11 +129,7 @@ mod tests { fn test_weighted_stake_unused() { let deposit = DepositEntry { amount_deposited_native: 20_000, - lockup: Lockup::default(), - is_used: false, - voting_mint_config_idx: 0, - delegate: Pubkey::default(), - _reserved1: [0; 6], + ..Default::default() }; assert_eq!(deposit.weighted_stake(0), 0); } @@ -141,19 +139,9 @@ mod tests { let amount = 20_000; let deposit = DepositEntry { amount_deposited_native: amount, - lockup: Lockup { - start_ts: 0, - end_ts: 0, - kind: Constant, - period: LockupPeriod::Flex, - cooldown_requested: false, - cooldown_ends_at: 0, - _reserved1: [0; 5], - }, + lockup: Lockup::default(), is_used: true, - voting_mint_config_idx: 0, - delegate: Pubkey::default(), - _reserved1: [0; 6], + ..Default::default() }; assert_eq!(deposit.weighted_stake(10), amount); } @@ -164,18 +152,12 @@ mod tests { let deposit = DepositEntry { amount_deposited_native: amount, lockup: Lockup { - start_ts: 0, end_ts: 100, - kind: Constant, - period: LockupPeriod::Flex, cooldown_requested: true, - cooldown_ends_at: 200, - _reserved1: [0; 5], + ..Default::default() }, is_used: true, - voting_mint_config_idx: 0, - delegate: Pubkey::default(), - _reserved1: [0; 6], + ..Default::default() }; assert_eq!(deposit.weighted_stake(150), 0); } @@ -186,18 +168,13 @@ mod tests { let deposit = DepositEntry { amount_deposited_native: amount, lockup: Lockup { - start_ts: 0, end_ts: 100, kind: Constant, period: LockupPeriod::OneYear, - cooldown_requested: false, - cooldown_ends_at: 0, - _reserved1: [0; 5], + ..Default::default() }, is_used: true, - voting_mint_config_idx: 0, - delegate: Pubkey::default(), - _reserved1: [0; 6], + ..Default::default() }; assert_eq!( deposit.weighted_stake(50), @@ -212,18 +189,13 @@ mod tests { let deposit = DepositEntry { amount_deposited_native: amount, lockup: Lockup { - start_ts: 0, end_ts: 200, kind: None, period: LockupPeriod::None, - cooldown_requested: false, - cooldown_ends_at: 0, - _reserved1: [0; 5], + ..Default::default() }, is_used: true, - voting_mint_config_idx: 0, - delegate: Pubkey::default(), - _reserved1: [0; 6], + ..Default::default() }; assert_eq!(deposit.weighted_stake(50), 0); diff --git a/program-states/src/state/lockup.rs b/program-states/src/state/lockup.rs index f4a6f2bf..1a4f2ed7 100644 --- a/program-states/src/state/lockup.rs +++ b/program-states/src/state/lockup.rs @@ -1,5 +1,6 @@ use crate::error::*; use anchor_lang::prelude::*; +use borsh::{BorshDeserialize, BorshSerialize}; /// Seconds in one day. pub const SECS_PER_DAY: u64 = 86_400; @@ -11,7 +12,7 @@ pub const SECS_PER_MONTH: u64 = 365 * SECS_PER_DAY / 12; pub const COOLDOWN_SECS: u64 = 86_400 * 5; #[zero_copy] -#[derive(Default)] +#[derive(Default, Debug)] pub struct Lockup { /// Start of the lockup. pub start_ts: u64, @@ -161,10 +162,7 @@ impl Lockup { } } -#[repr(u8)] -#[derive( - AnchorSerialize, AnchorDeserialize, Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, -)] +#[derive(BorshDeserialize, BorshSerialize, Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] pub enum LockupPeriod { None, Flex, @@ -201,8 +199,7 @@ impl LockupPeriod { } } -#[repr(u8)] -#[derive(AnchorSerialize, AnchorDeserialize, Debug, Clone, Copy, PartialEq)] +#[derive(BorshSerialize, BorshDeserialize, Debug, Clone, Copy, PartialEq)] pub enum LockupKind { /// No lockup, tokens can be withdrawn as long as not engaged in a proposal. None, diff --git a/program-states/src/state/voter.rs b/program-states/src/state/voter.rs index 810cc4af..90aa9164 100644 --- a/program-states/src/state/voter.rs +++ b/program-states/src/state/voter.rs @@ -11,10 +11,12 @@ pub struct Voter { pub voter_weight_record_bump: u8, pub _reserved1: [u8; 14], } -const_assert!(std::mem::size_of::() == 80 * 32 + 32 + 32 + 1 + 1 + 14); +const_assert!(std::mem::size_of::() == 88 * 32 + 32 + 32 + 1 + 1 + 14); const_assert!(std::mem::size_of::() % 8 == 0); impl Voter { + pub const MIN_OWN_WEIGHTED_STAKE: u64 = 15_000_000; + /// The full vote weight available to the voter pub fn weight(&self) -> Result { self.deposits diff --git a/programs/voter-stake-registry/src/cpi_instructions.rs b/programs/voter-stake-registry/src/cpi_instructions.rs index f84ce4d2..e820c09d 100644 --- a/programs/voter-stake-registry/src/cpi_instructions.rs +++ b/programs/voter-stake-registry/src/cpi_instructions.rs @@ -1,7 +1,5 @@ -use anchor_lang::{ - prelude::{borsh, Pubkey}, - AnchorDeserialize, AnchorSerialize, Key, -}; +use anchor_lang::{prelude::*, Key}; +use borsh::{BorshDeserialize, BorshSerialize}; use mplx_staking_states::state::LockupPeriod; use solana_program::{ account_info::AccountInfo, @@ -14,7 +12,7 @@ use solana_program::{ pub const REWARD_CONTRACT_ID: Pubkey = solana_program::pubkey!("BF5PatmRTQDgEKoXR7iHRbkibEEi83nVM38cUKWzQcTR"); -#[derive(Debug, AnchorSerialize, AnchorDeserialize, PartialEq, Eq)] +#[derive(Debug, BorshSerialize, BorshDeserialize, PartialEq, Eq)] pub enum RewardsInstruction { /// Creates and initializes a reward pool account @@ -142,7 +140,22 @@ pub enum RewardsInstruction { /// [RS] Distribute rewards authority DistributeRewards, + /// Closes Mining Account CloseMining, + + /// Changes delegate for the existing stake + /// + /// Accounts: + /// [W] Reward pool account + /// [W] Mining + /// [RS] Deposit authority + /// [RS] Mining owner + /// [W] Old delegate mining + /// [W] New delegate mining + ChangeDelegate { + /// Amount of staked tokens + staked_amount: u64, + }, } /// This function initializes pool. Some sort of a "root" @@ -237,6 +250,7 @@ pub fn deposit_mining<'a>( reward_pool: AccountInfo<'a>, mining: AccountInfo<'a>, deposit_authority: AccountInfo<'a>, + delegate_mining: AccountInfo<'a>, amount: u64, lockup_period: LockupPeriod, owner: &Pubkey, @@ -246,6 +260,7 @@ pub fn deposit_mining<'a>( AccountMeta::new(reward_pool.key(), false), AccountMeta::new(mining.key(), false), AccountMeta::new_readonly(deposit_authority.key(), true), + AccountMeta::new(delegate_mining.key(), false), ]; let ix = Instruction::new_with_borsh( @@ -260,18 +275,25 @@ pub fn deposit_mining<'a>( invoke_signed( &ix, - &[reward_pool, mining, deposit_authority, program_id], + &[ + reward_pool, + mining, + deposit_authority, + delegate_mining, + program_id, + ], &[signers_seeds], ) } -/// Extend deposit +/// Extend stake #[allow(clippy::too_many_arguments)] -pub fn extend_deposit<'a>( +pub fn extend_stake<'a>( program_id: AccountInfo<'a>, reward_pool: AccountInfo<'a>, mining: AccountInfo<'a>, deposit_authority: AccountInfo<'a>, + delegate_mining: AccountInfo<'a>, old_lockup_period: LockupPeriod, new_lockup_period: LockupPeriod, deposit_start_ts: u64, @@ -284,6 +306,7 @@ pub fn extend_deposit<'a>( AccountMeta::new(reward_pool.key(), false), AccountMeta::new(mining.key(), false), AccountMeta::new_readonly(deposit_authority.key(), true), + AccountMeta::new(delegate_mining.key(), false), ]; let ix = Instruction::new_with_borsh( @@ -301,7 +324,13 @@ pub fn extend_deposit<'a>( invoke_signed( &ix, - &[reward_pool, mining, deposit_authority, program_id], + &[ + reward_pool, + mining, + deposit_authority, + delegate_mining, + program_id, + ], &[signers_seeds], )?; @@ -315,6 +344,7 @@ pub fn withdraw_mining<'a>( reward_pool: AccountInfo<'a>, mining: AccountInfo<'a>, deposit_authority: AccountInfo<'a>, + delegate_mining: AccountInfo<'a>, amount: u64, owner: &Pubkey, signers_seeds: &[&[u8]], @@ -323,6 +353,7 @@ pub fn withdraw_mining<'a>( AccountMeta::new(reward_pool.key(), false), AccountMeta::new(mining.key(), false), AccountMeta::new_readonly(deposit_authority.key(), true), + AccountMeta::new(delegate_mining.key(), false), ]; let ix = Instruction::new_with_borsh( @@ -336,7 +367,13 @@ pub fn withdraw_mining<'a>( invoke_signed( &ix, - &[reward_pool, mining, deposit_authority, program_id], + &[ + reward_pool, + mining, + deposit_authority, + delegate_mining, + program_id, + ], &[signers_seeds], ) } @@ -420,3 +457,44 @@ pub fn close_mining<'a>( &[signers_seeds], ) } + +pub fn change_delegate<'a>( + program_id: AccountInfo<'a>, + reward_pool: AccountInfo<'a>, + mining: AccountInfo<'a>, + deposit_authority: AccountInfo<'a>, + mining_owner: AccountInfo<'a>, + old_delegate_mining: AccountInfo<'a>, + new_delegate_mining: AccountInfo<'a>, + staked_amount: u64, + signers_seeds: &[&[u8]], +) -> ProgramResult { + let accounts = vec![ + AccountMeta::new(reward_pool.key(), false), + AccountMeta::new(mining.key(), false), + AccountMeta::new_readonly(deposit_authority.key(), true), + AccountMeta::new_readonly(mining_owner.key(), true), + AccountMeta::new(old_delegate_mining.key(), false), + AccountMeta::new(new_delegate_mining.key(), false), + ]; + + let ix = Instruction::new_with_borsh( + program_id.key(), + &RewardsInstruction::ChangeDelegate { staked_amount }, + accounts, + ); + + invoke_signed( + &ix, + &[ + reward_pool, + mining, + deposit_authority, + mining_owner, + old_delegate_mining, + new_delegate_mining, + program_id, + ], + &[signers_seeds], + ) +} diff --git a/programs/voter-stake-registry/src/instructions/change_delegate.rs b/programs/voter-stake-registry/src/instructions/change_delegate.rs new file mode 100644 index 00000000..01c88b4a --- /dev/null +++ b/programs/voter-stake-registry/src/instructions/change_delegate.rs @@ -0,0 +1,137 @@ +use crate::{ + clock_unix_timestamp, cpi_instructions, find_mining_address, find_reward_pool_address, +}; +use anchor_lang::prelude::*; +use mplx_staking_states::{ + error::VsrError, + state::{Registrar, Voter}, +}; +use solana_program::clock::SECONDS_PER_DAY; +pub const DELEGATE_UPDATE_DIFF_THRESHOLD: u64 = 5 * SECONDS_PER_DAY; + +#[derive(Accounts)] +pub struct ChangeDelegate<'info> { + pub registrar: AccountLoader<'info, Registrar>, + + // checking the PDA address it just an extra precaution, + // the other constraints must be exhaustive + #[account( + mut, + seeds = [registrar.key().as_ref(), b"voter".as_ref(), voter_authority.key().as_ref()], + bump = voter.load()?.voter_bump, + has_one = voter_authority, + has_one = registrar)] + pub voter: AccountLoader<'info, Voter>, + pub voter_authority: Signer<'info>, + + pub delegate_voter: AccountLoader<'info, Voter>, + + /// CHECK: Mining Account that belongs to Rewards Program and some delegate + /// The address of the mining account on the rewards program + /// derived from PDA(["mining", delegate wallet addr, reward_pool], rewards_program) + /// Seeds derivation will be checked on the rewards contract + #[account(mut)] + pub old_delegate_mining: UncheckedAccount<'info>, + + /// CHECK: Mining Account that belongs to Rewards Program and some delegate + /// The address of the mining account on the rewards program + /// derived from PDA(["mining", delegate wallet addr, reward_pool], rewards_program) + /// Seeds derivation will be checked on the rewards contract + #[account(mut)] + pub new_delegate_mining: UncheckedAccount<'info>, + + /// CHECK: Reward Pool PDA will be checked in the rewards contract + /// PDA(["reward_pool", deposit_authority , fill_authority], + /// reward_program) + #[account(mut)] + pub reward_pool: UncheckedAccount<'info>, + + /// CHECK: mining PDA will be checked in the rewards contract + /// PDA(["mining", mining owner , reward_pool], + /// reward_program) + #[account(mut)] + pub deposit_mining: UncheckedAccount<'info>, + + /// CHECK: Rewards Program account + #[account(executable)] + pub rewards_program: UncheckedAccount<'info>, +} + +/// Changes delegate for the existing stake. +/// +/// Rewards will be recalculated, and the new delegate will start receiving rewards. +/// The old delegate will stop receiving rewards. +/// It might be done once per five days. +pub fn change_delegate(ctx: Context, deposit_entry_index: u8) -> Result<()> { + let registrar = &ctx.accounts.registrar.load()?; + let voter = &ctx.accounts.voter.load()?; + let delegate_voter = &ctx.accounts.delegate_voter.load()?; + let target = voter.active_deposit(deposit_entry_index)?; + + require!( + delegate_voter.voter_authority != target.delegate, + VsrError::SameDelegate + ); + + let curr_ts = clock_unix_timestamp(); + let delegate_voter_weighted_stake = delegate_voter + .deposits + .iter() + .fold(0, |acc, d| acc + d.weighted_stake(curr_ts)); + require!( + delegate_voter_weighted_stake >= Voter::MIN_OWN_WEIGHTED_STAKE, + VsrError::InsufficientWeightedStake + ); + + let delegate_last_update_diff = curr_ts + .checked_sub(target.delegate_last_update_ts) + .ok_or(VsrError::ArithmeticOverflow)?; + + require!( + delegate_last_update_diff > DELEGATE_UPDATE_DIFF_THRESHOLD, + VsrError::DelegateUpdateIsTooSoon + ); + + let (reward_pool, _) = find_reward_pool_address( + &ctx.accounts.rewards_program.key(), + &ctx.accounts.registrar.key(), + ); + let (delegate_mining, _) = find_mining_address( + &ctx.accounts.rewards_program.key(), + &delegate_voter.voter_authority, + &reward_pool, + ); + + require!( + delegate_mining == ctx.accounts.new_delegate_mining.key(), + VsrError::InvalidMining + ); + + let reward_pool = ctx.accounts.reward_pool.to_account_info(); + let mining = ctx.accounts.deposit_mining.to_account_info(); + let deposit_authority = ctx.accounts.registrar.to_account_info(); + let old_delegate_mining = ctx.accounts.old_delegate_mining.to_account_info(); + let new_delegate_mining = ctx.accounts.new_delegate_mining.to_account_info(); + let signers_seeds = &[ + ®istrar.realm.key().to_bytes(), + b"registrar".as_ref(), + ®istrar.realm_governing_token_mint.key().to_bytes(), + &[registrar.bump][..], + ]; + let staked_amount = target.amount_deposited_native; + let mining_owner = ctx.accounts.voter_authority.to_account_info(); + + cpi_instructions::change_delegate( + ctx.accounts.rewards_program.to_account_info(), + reward_pool, + mining, + deposit_authority, + mining_owner, + old_delegate_mining, + new_delegate_mining, + staked_amount, + signers_seeds, + )?; + + Ok(()) +} diff --git a/programs/voter-stake-registry/src/instructions/create_deposit_entry.rs b/programs/voter-stake-registry/src/instructions/create_deposit_entry.rs index fff0ba31..a83697a6 100644 --- a/programs/voter-stake-registry/src/instructions/create_deposit_entry.rs +++ b/programs/voter-stake-registry/src/instructions/create_deposit_entry.rs @@ -30,8 +30,8 @@ pub struct CreateDepositEntry<'info> { payer = payer )] pub vault: Box>, - pub voter_authority: Signer<'info>, + pub delegate_voter: AccountLoader<'info, Voter>, #[account(mut)] pub payer: Signer<'info>, @@ -49,23 +49,44 @@ pub struct CreateDepositEntry<'info> { /// /// - `deposit_entry_index`: deposit entry to use /// - `kind`: Type of lockup to use. -/// - `start_ts`: Start timestamp in seconds, defaults to current clock. The lockup will end after -/// `start + LockupPeriod::to_ts + COOLDOWNS_SECS. -/// -/// Note that tokens will already be locked before start_ts, it only defines -/// the vesting start time and the anchor for the periods computation. -/// -/// - `period`: An enum that represents possible options for locking up +/// - `period`: An enum that represents possible options for locking up. pub fn create_deposit_entry( ctx: Context, deposit_entry_index: u8, kind: LockupKind, period: LockupPeriod, - delegate: Pubkey, ) -> Result<()> { // Load accounts. let registrar = &ctx.accounts.registrar.load()?; - let voter = &mut ctx.accounts.voter.load_mut()?; + let mut voter = ctx.accounts.voter.load_mut()?; + + let delegate = if ctx.accounts.delegate_voter.key() != ctx.accounts.voter.key() { + let curr_ts = clock_unix_timestamp(); + let delegate_voter = ctx.accounts.delegate_voter.load()?; + + let delegate_voter_weighted_stake = delegate_voter + .deposits + .iter() + .fold(0, |acc, d| acc + d.weighted_stake(curr_ts)); + require!( + delegate_voter_weighted_stake >= Voter::MIN_OWN_WEIGHTED_STAKE, + VsrError::InsufficientUnlockedTokens + ); + + delegate_voter.voter_authority.key() + } else { + voter.voter_authority.key() + }; + + // if both period and lockup are None, that means the deposit entry is not lockable + // in that case delegate field doesn't make sense and should be the same as mining account + // derived from voter + if period == LockupPeriod::None && kind == LockupKind::None { + require!( + delegate == voter.voter_authority.key(), + VsrError::InvalidDelegate + ); + } // Get the exchange rate entry associated with this deposit. let mint_idx = registrar.voting_mint_config_index(ctx.accounts.deposit_mint.key())?; @@ -81,6 +102,7 @@ pub fn create_deposit_entry( let start_ts = clock_unix_timestamp(); *d_entry = DepositEntry::default(); + d_entry.delegate = delegate; d_entry.is_used = true; d_entry.voting_mint_config_idx = mint_idx as u8; diff --git a/programs/voter-stake-registry/src/instructions/extend_stake.rs b/programs/voter-stake-registry/src/instructions/extend_stake.rs index 10b8d7a5..b7c7f4aa 100644 --- a/programs/voter-stake-registry/src/instructions/extend_stake.rs +++ b/programs/voter-stake-registry/src/instructions/extend_stake.rs @@ -68,6 +68,8 @@ pub fn extend_stake( source_mint_idx, VsrError::InvalidMint ); + ctx.accounts.verify_delegate_and_its_mining(target)?; + target.amount_deposited_native = target .amount_deposited_native .checked_add(additional_amount) @@ -78,9 +80,10 @@ pub fn extend_stake( .ok_or(VsrError::InvalidTimestampArguments)?; target.lockup.period = new_lockup_period; - let reward_pool = &ctx.accounts.reward_pool; - let mining = &ctx.accounts.deposit_mining; - let pool_deposit_authority = &ctx.accounts.registrar.to_account_info(); + let reward_pool = ctx.accounts.reward_pool.to_account_info(); + let mining = ctx.accounts.deposit_mining.to_account_info(); + let deposit_authority = ctx.accounts.registrar.to_account_info(); + let delegate_mining = ctx.accounts.delegate_mining.to_account_info(); let signers_seeds = &[ ®istrar.realm.key().to_bytes(), b"registrar".as_ref(), @@ -89,11 +92,12 @@ pub fn extend_stake( ]; let mining_owner = &ctx.accounts.voter_authority.key(); - cpi_instructions::extend_deposit( + cpi_instructions::extend_stake( ctx.accounts.rewards_program.to_account_info(), - reward_pool.to_account_info(), + reward_pool, mining.to_account_info(), - pool_deposit_authority.to_account_info(), + deposit_authority, + delegate_mining, current_lockup_period, new_lockup_period, start_ts, diff --git a/programs/voter-stake-registry/src/instructions/mod.rs b/programs/voter-stake-registry/src/instructions/mod.rs index 70e9f066..c789400b 100644 --- a/programs/voter-stake-registry/src/instructions/mod.rs +++ b/programs/voter-stake-registry/src/instructions/mod.rs @@ -1,3 +1,4 @@ +pub use change_delegate::*; pub use claim::*; pub use close_deposit_entry::*; pub use close_voter::*; @@ -8,12 +9,13 @@ pub use create_voter::*; pub use deposit::*; pub use extend_stake::*; pub use log_voter_info::*; -use solana_program::{clock::Clock, sysvar::Sysvar}; +use solana_program::{clock::Clock, pubkey::Pubkey, sysvar::Sysvar}; pub use stake::*; pub use unlock_tokens::*; pub use update_voter_weight_record::*; pub use withdraw::*; +mod change_delegate; mod claim; mod close_deposit_entry; mod close_voter; @@ -32,3 +34,27 @@ mod withdraw; pub fn clock_unix_timestamp() -> u64 { Clock::get().unwrap().unix_timestamp as u64 } + +/// Generates mining address +pub fn find_mining_address( + program_id: &Pubkey, + mining_owner: &Pubkey, + reward_pool: &Pubkey, +) -> (Pubkey, u8) { + Pubkey::find_program_address( + &[ + "mining".as_bytes(), + &mining_owner.to_bytes(), + &reward_pool.to_bytes(), + ], + program_id, + ) +} + +/// Generates reward pool address +pub fn find_reward_pool_address(program_id: &Pubkey, registrar: &Pubkey) -> (Pubkey, u8) { + Pubkey::find_program_address( + &["reward_pool".as_bytes(), ®istrar.to_bytes()], + program_id, + ) +} diff --git a/programs/voter-stake-registry/src/instructions/stake.rs b/programs/voter-stake-registry/src/instructions/stake.rs index 0cd646c5..d3700039 100644 --- a/programs/voter-stake-registry/src/instructions/stake.rs +++ b/programs/voter-stake-registry/src/instructions/stake.rs @@ -1,4 +1,6 @@ -use crate::{clock_unix_timestamp, cpi_instructions, Stake}; +use crate::{ + clock_unix_timestamp, cpi_instructions, find_mining_address, find_reward_pool_address, Stake, +}; use anchor_lang::prelude::*; use mplx_staking_states::{error::VsrError, state::LockupKind}; @@ -48,15 +50,18 @@ pub fn stake( target.amount_deposited_native == 0, VsrError::DepositEntryIsOld ); + ctx.accounts.verify_delegate_and_its_mining(target)?; + // Add target amounts target.amount_deposited_native = target .amount_deposited_native .checked_add(amount) .ok_or(VsrError::ArithmeticOverflow)?; - let reward_pool = &ctx.accounts.reward_pool; - let mining = &ctx.accounts.deposit_mining; - let pool_deposit_authority = &ctx.accounts.registrar.to_account_info(); + let reward_pool = ctx.accounts.reward_pool.to_account_info(); + let mining = ctx.accounts.deposit_mining.to_account_info(); + let deposit_authority = ctx.accounts.registrar.to_account_info(); + let delegate_mining = ctx.accounts.delegate_mining.to_account_info(); let signers_seeds = &[ ®istrar.realm.key().to_bytes(), b"registrar".as_ref(), @@ -67,9 +72,10 @@ pub fn stake( cpi_instructions::deposit_mining( ctx.accounts.rewards_program.to_account_info(), - reward_pool.to_account_info(), - mining.to_account_info(), - pool_deposit_authority.to_account_info(), + reward_pool, + mining, + deposit_authority, + delegate_mining, amount, target.lockup.period, owner, diff --git a/programs/voter-stake-registry/src/instructions/unlock_tokens.rs b/programs/voter-stake-registry/src/instructions/unlock_tokens.rs index 530d63d9..4e9875d0 100644 --- a/programs/voter-stake-registry/src/instructions/unlock_tokens.rs +++ b/programs/voter-stake-registry/src/instructions/unlock_tokens.rs @@ -1,39 +1,8 @@ -use crate::{clock_unix_timestamp, cpi_instructions::withdraw_mining}; +use crate::{clock_unix_timestamp, cpi_instructions::withdraw_mining, Stake}; use anchor_lang::prelude::*; -use mplx_staking_states::{ - error::VsrError, - state::{Registrar, Voter, COOLDOWN_SECS}, -}; +use mplx_staking_states::{error::VsrError, state::COOLDOWN_SECS}; -#[derive(Accounts)] -pub struct UnlockTokens<'info> { - pub registrar: AccountLoader<'info, Registrar>, - - // checking the PDA address it just an extra precaution, - // the other constraints must be exhaustive - #[account( - mut, - seeds = [registrar.key().as_ref(), b"voter".as_ref(), voter_authority.key().as_ref()], - bump = voter.load()?.voter_bump, - has_one = voter_authority, - has_one = registrar)] - pub voter: AccountLoader<'info, Voter>, - pub voter_authority: Signer<'info>, - - /// CHECK: Reward Pool PDA will be checked in the rewards contract - #[account(mut)] - pub reward_pool: UncheckedAccount<'info>, - - /// CHECK: mining PDA will be checked in the rewards contract - #[account(mut)] - pub deposit_mining: UncheckedAccount<'info>, - - /// CHECK: Rewards Program account - #[account(executable)] - pub rewards_program: UncheckedAccount<'info>, -} - -pub fn unlock_tokens(ctx: Context, deposit_entry_index: u8) -> Result<()> { +pub fn unlock_tokens(ctx: Context, deposit_entry_index: u8) -> Result<()> { let voter = &mut ctx.accounts.voter.load_mut()?; let curr_ts = clock_unix_timestamp(); @@ -49,30 +18,34 @@ pub fn unlock_tokens(ctx: Context, deposit_entry_index: u8) -> Res VsrError::DepositStillLocked ); + ctx.accounts + .verify_delegate_and_its_mining(&deposit_entry)?; + deposit_entry.lockup.cooldown_requested = true; deposit_entry.lockup.cooldown_ends_at = curr_ts .checked_add(COOLDOWN_SECS) .ok_or(VsrError::InvalidTimestampArguments)?; - let rewards_program = &ctx.accounts.rewards_program; - let reward_pool = &ctx.accounts.reward_pool; - let mining = &ctx.accounts.deposit_mining; - - let owner = &ctx.accounts.voter_authority; - let registrar = &ctx.accounts.registrar.load()?; + let rewards_program = ctx.accounts.rewards_program.to_account_info(); + let reward_pool = ctx.accounts.reward_pool.to_account_info(); + let mining = ctx.accounts.deposit_mining.to_account_info(); + let delegate_mining = ctx.accounts.delegate_mining.to_account_info(); + let owner = ctx.accounts.voter_authority.to_account_info(); + let registrar = ctx.accounts.registrar.load()?; + let deposit_authority = ctx.accounts.registrar.to_account_info(); let signers_seeds = &[ registrar.realm.as_ref(), b"registrar".as_ref(), - ®istrar.realm_governing_token_mint.as_ref(), + (registrar.realm_governing_token_mint.as_ref()), &[registrar.bump][..], ]; - let pool_deposit_authority = &ctx.accounts.registrar; withdraw_mining( - rewards_program.to_account_info(), - reward_pool.to_account_info(), - mining.to_account_info(), - pool_deposit_authority.to_account_info(), + rewards_program, + reward_pool, + mining, + deposit_authority, + delegate_mining, deposit_entry.amount_deposited_native, owner.key, signers_seeds, diff --git a/programs/voter-stake-registry/src/lib.rs b/programs/voter-stake-registry/src/lib.rs index 47cd1ab2..35791f99 100644 --- a/programs/voter-stake-registry/src/lib.rs +++ b/programs/voter-stake-registry/src/lib.rs @@ -1,8 +1,11 @@ use anchor_lang::prelude::*; use instructions::*; -use mplx_staking_states::state::{ - lockup::{LockupKind, LockupPeriod}, - Registrar, Voter, +use mplx_staking_states::{ + error::VsrError, + state::{ + lockup::{LockupKind, LockupPeriod}, + DepositEntry, Registrar, Voter, + }, }; pub mod cpi_instructions; @@ -91,9 +94,8 @@ pub mod voter_stake_registry { deposit_entry_index: u8, kind: LockupKind, period: LockupPeriod, - delegate: Pubkey, ) -> Result<()> { - instructions::create_deposit_entry(ctx, deposit_entry_index, kind, period, delegate) + instructions::create_deposit_entry(ctx, deposit_entry_index, kind, period) } pub fn deposit(ctx: Context, deposit_entry_index: u8, amount: u64) -> Result<()> { @@ -115,7 +117,7 @@ pub mod voter_stake_registry { instructions::update_voter_weight_record(ctx) } - pub fn unlock_tokens(ctx: Context, deposit_entry_index: u8) -> Result<()> { + pub fn unlock_tokens(ctx: Context, deposit_entry_index: u8) -> Result<()> { instructions::unlock_tokens(ctx, deposit_entry_index) } @@ -174,6 +176,10 @@ pub mod voter_stake_registry { realm_pubkey, ) } + + pub fn change_delegate(ctx: Context, deposit_entry_index: u8) -> Result<()> { + instructions::change_delegate(ctx, deposit_entry_index) + } } #[derive(Accounts)] @@ -183,14 +189,24 @@ pub struct Stake<'info> { // checking the PDA address it just an extra precaution, // the other constraints must be exhaustive #[account( - mut, - seeds = [registrar.key().as_ref(), b"voter".as_ref(), voter_authority.key().as_ref()], - bump = voter.load()?.voter_bump, - has_one = voter_authority, - has_one = registrar)] + mut, + seeds = [registrar.key().as_ref(), b"voter".as_ref(), voter_authority.key().as_ref()], + bump = voter.load()?.voter_bump, + has_one = voter_authority, + has_one = registrar) + ] pub voter: AccountLoader<'info, Voter>, pub voter_authority: Signer<'info>, + /// CHECK: delegate might be any arbitrary address + pub delegate: UncheckedAccount<'info>, + + /// CHECK: Mining Account that belongs to Rewards Program and some delegate + /// The address of the mining account on the rewards progra, + /// derived from PDA(["mining", delegate wallet addr, reward_pool], rewards_program) + #[account(mut)] + pub delegate_mining: UncheckedAccount<'info>, + /// CHECK: Reward Pool PDA will be checked in the rewards contract /// PDA(["reward_pool", deposit_authority , fill_authority], /// reward_program) @@ -207,3 +223,31 @@ pub struct Stake<'info> { #[account(executable)] pub rewards_program: UncheckedAccount<'info>, } + +impl Stake<'_> { + pub fn verify_delegate_and_its_mining(&self, deposit_entry: &DepositEntry) -> Result<()> { + // check whether target delegate mining is the same as delegate mining from passed context + require_eq!( + deposit_entry.delegate, + *self.delegate.to_account_info().key, + VsrError::InvalidDelegate + ); + + let (reward_pool, _) = find_reward_pool_address( + &self.rewards_program.to_account_info().key(), + &self.registrar.to_account_info().key(), + ); + let (calculated_delegate_mining, _) = find_mining_address( + &self.rewards_program.to_account_info().key(), + &self.delegate.to_account_info().key(), + &reward_pool, + ); + require_eq!( + calculated_delegate_mining, + self.delegate_mining.to_account_info().key(), + VsrError::InvalidMining + ); + + Ok(()) + } +} diff --git a/programs/voter-stake-registry/tests/fixtures/mplx_rewards.so b/programs/voter-stake-registry/tests/fixtures/mplx_rewards.so index 273ee63e..0fbd3e85 100755 Binary files a/programs/voter-stake-registry/tests/fixtures/mplx_rewards.so and b/programs/voter-stake-registry/tests/fixtures/mplx_rewards.so differ diff --git a/programs/voter-stake-registry/tests/fixtures/spl-governance-3.1.0 b/programs/voter-stake-registry/tests/fixtures/spl-governance-3.1.0 new file mode 100644 index 00000000..bb8a89ec Binary files /dev/null and b/programs/voter-stake-registry/tests/fixtures/spl-governance-3.1.0 differ diff --git a/programs/voter-stake-registry/tests/program_test/addin.rs b/programs/voter-stake-registry/tests/program_test/addin.rs index 3c1e3d6e..d3106574 100644 --- a/programs/voter-stake-registry/tests/program_test/addin.rs +++ b/programs/voter-stake-registry/tests/program_test/addin.rs @@ -31,12 +31,70 @@ pub struct VotingMintConfigCookie { pub struct VoterCookie { pub address: Pubkey, - pub authority: Pubkey, + pub authority: Keypair, pub voter_weight_record: Pubkey, pub token_owner_record: Pubkey, } impl AddinCookie { + #[allow(clippy::too_many_arguments)] + pub async fn change_delegate( + &self, + // accounts + registrar: &RegistrarCookie, + voter: &VoterCookie, + delegate_voter: &VoterCookie, + old_delegate_mining: &Pubkey, + rewards_program: &Pubkey, + // params + deposit_entry_index: u8, + ) -> std::result::Result<(), BanksClientError> { + let data = anchor_lang::InstructionData::data( + &voter_stake_registry::instruction::ChangeDelegate { + deposit_entry_index, + }, + ); + + let (reward_pool, _reward_pool_bump) = Pubkey::find_program_address( + &["reward_pool".as_bytes(), ®istrar.address.to_bytes()], + rewards_program, + ); + + let (deposit_mining, _) = + find_deposit_mining_addr(rewards_program, &voter.authority.pubkey(), &reward_pool); + + let (new_delegate_mining, _) = find_deposit_mining_addr( + rewards_program, + &delegate_voter.authority.pubkey(), + &reward_pool, + ); + + let accounts = anchor_lang::ToAccountMetas::to_account_metas( + &voter_stake_registry::accounts::ChangeDelegate { + registrar: registrar.address, + voter: voter.address, + voter_authority: voter.authority.pubkey(), + delegate_voter: delegate_voter.address, + old_delegate_mining: *old_delegate_mining, + new_delegate_mining, + reward_pool, + deposit_mining, + rewards_program: *rewards_program, + }, + None, + ); + + let instructions = vec![Instruction { + program_id: self.program_id, + accounts, + data, + }]; + + self.solana + .process_transaction(&instructions, Some(&[&voter.authority])) + .await + } + pub async fn create_registrar( &self, realm: &GovernanceRealmCookie, @@ -120,12 +178,13 @@ impl AddinCookie { (registrar_cookie, reward_pool) } + #[allow(clippy::too_many_arguments)] pub async fn stake( &self, // accounts registrar: &RegistrarCookie, voter: &VoterCookie, - voter_authority: &Keypair, + delegate: Pubkey, rewards_program: &Pubkey, // params source_deposit_entry_index: u8, @@ -143,14 +202,19 @@ impl AddinCookie { rewards_program, ); - let deposit_mining = - find_deposit_mining_addr(&voter_authority.pubkey(), &reward_pool, rewards_program); + let (deposit_mining, _) = + find_deposit_mining_addr(rewards_program, &voter.authority.pubkey(), &reward_pool); + + let (delegate_mining, _) = + find_deposit_mining_addr(rewards_program, &delegate, &reward_pool); let accounts = anchor_lang::ToAccountMetas::to_account_metas( &voter_stake_registry::accounts::Stake { registrar: registrar.address, voter: voter.address, - voter_authority: voter_authority.pubkey(), + voter_authority: voter.authority.pubkey(), + delegate, + delegate_mining, reward_pool, deposit_mining, rewards_program: *rewards_program, @@ -165,7 +229,7 @@ impl AddinCookie { }]; self.solana - .process_transaction(&instructions, Some(&[voter_authority])) + .process_transaction(&instructions, Some(&[&voter.authority])) .await } @@ -279,13 +343,15 @@ impl AddinCookie { }]; self.solana - .process_transaction(&instructions, Some(&[payer, authority])) + .process_transaction(&instructions, Some(&[&payer, &authority])) .await .unwrap(); + let authority = Keypair::from_bytes(&authority.to_bytes()).unwrap(); + VoterCookie { address: voter, - authority: authority.pubkey(), + authority, voter_weight_record, token_owner_record: token_owner_record.address, } @@ -296,12 +362,11 @@ impl AddinCookie { &self, registrar: &RegistrarCookie, voter: &VoterCookie, - voter_authority: &Keypair, + delegate_voter: &VoterCookie, voting_mint: &VotingMintConfigCookie, deposit_entry_index: u8, lockup_kind: LockupKind, period: LockupPeriod, - delegate: Pubkey, ) -> std::result::Result<(), BanksClientError> { let vault = voter.vault_address(voting_mint); @@ -310,7 +375,6 @@ impl AddinCookie { deposit_entry_index, kind: lockup_kind, period, - delegate, }, ); @@ -319,12 +383,13 @@ impl AddinCookie { vault, registrar: registrar.address, voter: voter.address, - voter_authority: voter_authority.pubkey(), - payer: voter_authority.pubkey(), + voter_authority: voter.authority.pubkey(), + payer: voter.authority.pubkey(), deposit_mint: voting_mint.mint.pubkey.unwrap(), system_program: solana_sdk::system_program::id(), token_program: spl_token::id(), associated_token_program: spl_associated_token_account::id(), + delegate_voter: delegate_voter.address, }, None, ); @@ -336,7 +401,7 @@ impl AddinCookie { }]; self.solana - .process_transaction(&instructions, Some(&[voter_authority])) + .process_transaction(&instructions, Some(&[&voter.authority])) .await } @@ -389,6 +454,7 @@ impl AddinCookie { registrar: &RegistrarCookie, voter: &VoterCookie, voter_authority: &Keypair, + delegate: &Pubkey, rewards_program: &Pubkey, // params source_deposit_entry_index: u8, @@ -409,14 +475,19 @@ impl AddinCookie { rewards_program, ); - let deposit_mining = - find_deposit_mining_addr(&voter_authority.pubkey(), &reward_pool, rewards_program); + let (deposit_mining, _) = + find_deposit_mining_addr(rewards_program, &voter_authority.pubkey(), &reward_pool); + + let (delegate_mining, _) = + find_deposit_mining_addr(rewards_program, delegate, &reward_pool); let accounts = anchor_lang::ToAccountMetas::to_account_metas( &voter_stake_registry::accounts::Stake { registrar: registrar.address, voter: voter.address, voter_authority: voter_authority.pubkey(), + delegate: *delegate, + delegate_mining, reward_pool, deposit_mining, rewards_program: *rewards_program, @@ -435,14 +506,14 @@ impl AddinCookie { .await } + #[allow(clippy::too_many_arguments)] pub async fn unlock_tokens( &self, registrar: &RegistrarCookie, voter: &VoterCookie, - authority: &Keypair, + delegate_voter: &VoterCookie, deposit_entry_index: u8, reward_pool: &Pubkey, - deposit_mining: &Pubkey, rewards_program: &Pubkey, ) -> std::result::Result<(), BanksClientError> { let data = @@ -450,13 +521,24 @@ impl AddinCookie { deposit_entry_index, }); + let (deposit_mining, _) = + find_deposit_mining_addr(rewards_program, &voter.authority.pubkey(), &reward_pool); + + let (delegate_mining, _) = find_deposit_mining_addr( + rewards_program, + &delegate_voter.authority.pubkey(), + &reward_pool, + ); + let accounts = anchor_lang::ToAccountMetas::to_account_metas( - &voter_stake_registry::accounts::UnlockTokens { + &voter_stake_registry::accounts::Stake { registrar: registrar.address, voter: voter.address, - voter_authority: authority.pubkey(), + voter_authority: voter.authority.pubkey(), + delegate: delegate_voter.authority.pubkey(), reward_pool: *reward_pool, - deposit_mining: *deposit_mining, + deposit_mining, + delegate_mining, rewards_program: *rewards_program, }, None, @@ -469,7 +551,7 @@ impl AddinCookie { }]; self.solana - .process_transaction(&instructions, Some(&[authority])) + .process_transaction(&instructions, Some(&[&voter.authority])) .await } @@ -532,8 +614,8 @@ impl AddinCookie { rewards_program, ); - let deposit_mining = - find_deposit_mining_addr(&voter_authority.pubkey(), &reward_pool, rewards_program); + let (deposit_mining, _) = + find_deposit_mining_addr(rewards_program, &voter_authority.pubkey(), &reward_pool); let data = anchor_lang::InstructionData::data(&voter_stake_registry::instruction::CloseVoter {}); diff --git a/programs/voter-stake-registry/tests/program_test/governance.rs b/programs/voter-stake-registry/tests/program_test/governance.rs index c4321c75..f1bf64e9 100644 --- a/programs/voter-stake-registry/tests/program_test/governance.rs +++ b/programs/voter-stake-registry/tests/program_test/governance.rs @@ -4,9 +4,9 @@ use solana_sdk::{ pubkey::Pubkey, signature::{Keypair, Signer}, }; -use spl_governance::state::realm::GoverningTokenConfigAccountArgs; -use spl_governance::state::realm_config::GoverningTokenType; -use spl_governance::state::{proposal, vote_record}; +use spl_governance::state::{ + proposal, realm::GoverningTokenConfigAccountArgs, realm_config::GoverningTokenType, vote_record, +}; use std::rc::Rc; #[derive(Clone)] diff --git a/programs/voter-stake-registry/tests/program_test/mod.rs b/programs/voter-stake-registry/tests/program_test/mod.rs index 2d2c8c9c..7284937a 100644 --- a/programs/voter-stake-registry/tests/program_test/mod.rs +++ b/programs/voter-stake-registry/tests/program_test/mod.rs @@ -17,7 +17,7 @@ use std::{ str::FromStr, sync::{Arc, RwLock}, }; -pub use utils::*; +pub use utils::{assert_custom_on_chain_error::AssertCustomOnChainErr, *}; pub mod addin; pub mod cookies; diff --git a/programs/voter-stake-registry/tests/program_test/utils.rs b/programs/voter-stake-registry/tests/program_test/utils.rs index f08ecd9f..6b1e868a 100644 --- a/programs/voter-stake-registry/tests/program_test/utils.rs +++ b/programs/voter-stake-registry/tests/program_test/utils.rs @@ -1,9 +1,13 @@ use bytemuck::{bytes_of, Contiguous}; -use solana_program::program_error::ProgramError; +use solana_program::{instruction::InstructionError, program_error::ProgramError}; use solana_program_test::{BanksClientError, ProgramTestContext}; use solana_sdk::{ - program_pack::Pack, pubkey::Pubkey, signature::Keypair, signer::Signer, system_instruction, - transaction::Transaction, + program_pack::Pack, + pubkey::Pubkey, + signature::Keypair, + signer::Signer, + system_instruction, + transaction::{Transaction, TransactionError}, }; use std::borrow::BorrowMut; @@ -71,19 +75,18 @@ pub async fn create_mint( } pub fn find_deposit_mining_addr( - user: &Pubkey, - rewards_pool: &Pubkey, - rewards_program_addr: &Pubkey, -) -> Pubkey { - let (deposit_mining, _bump) = Pubkey::find_program_address( + program_id: &Pubkey, + mining_owner: &Pubkey, + reward_pool: &Pubkey, +) -> (Pubkey, u8) { + Pubkey::find_program_address( &[ "mining".as_bytes(), - &user.to_bytes(), - &rewards_pool.to_bytes(), + &mining_owner.to_bytes(), + &reward_pool.to_bytes(), ], - rewards_program_addr, - ); - deposit_mining + program_id, + ) } pub async fn advance_clock_by_ts(context: &mut ProgramTestContext, ts: i64) { @@ -102,3 +105,28 @@ pub async fn advance_clock_by_ts(context: &mut ProgramTestContext, ts: i64) { new_clock.unix_timestamp += ts; context.borrow_mut().set_sysvar(&new_clock); } + +pub mod assert_custom_on_chain_error { + use super::*; + use mplx_staking_states::error::VsrError; + use std::fmt::Debug; + + pub trait AssertCustomOnChainErr { + fn assert_on_chain_err(self, expected_err: VsrError); + } + + impl AssertCustomOnChainErr for Result { + fn assert_on_chain_err(self, expected_err: VsrError) { + assert!(self.is_err()); + match self.unwrap_err() { + BanksClientError::TransactionError(TransactionError::InstructionError( + _, + InstructionError::Custom(code), + )) => { + debug_assert_eq!((expected_err as u32) + 6000, code); + } + _ => unreachable!("BanksClientError has no 'Custom' variant."), + } + } + } +} diff --git a/programs/voter-stake-registry/tests/test_all_deposits.rs b/programs/voter-stake-registry/tests/test_all_deposits.rs index 603b3bd7..c6c4bc18 100644 --- a/programs/voter-stake-registry/tests/test_all_deposits.rs +++ b/programs/voter-stake-registry/tests/test_all_deposits.rs @@ -54,10 +54,10 @@ async fn test_all_deposits() -> Result<(), TransportError> { ) .await; - let deposit_mining = find_deposit_mining_addr( + let (deposit_mining, _) = find_deposit_mining_addr( + &context.rewards.program_id, &voter_authority.pubkey(), &rewards_pool, - &context.rewards.program_id, ); let voter = addin @@ -72,17 +72,15 @@ async fn test_all_deposits() -> Result<(), TransportError> { ) .await; - let delegate = Keypair::new(); addin .create_deposit_entry( ®istrar, &voter, - voter_authority, + &voter, &mngo_voting_mint, 0, LockupKind::None, LockupPeriod::None, - delegate.pubkey(), ) .await .unwrap(); @@ -100,17 +98,15 @@ async fn test_all_deposits() -> Result<(), TransportError> { .unwrap(); for i in 1..32 { - let delegate = Keypair::new(); addin .create_deposit_entry( ®istrar, &voter, - voter_authority, + &voter, &mngo_voting_mint, i, LockupKind::Constant, LockupPeriod::ThreeMonths, - delegate.pubkey(), ) .await .unwrap(); @@ -118,7 +114,7 @@ async fn test_all_deposits() -> Result<(), TransportError> { .stake( ®istrar, &voter, - voter_authority, + voter.authority.pubkey(), &context.rewards.program_id, 0, i, @@ -146,10 +142,9 @@ async fn test_all_deposits() -> Result<(), TransportError> { .unlock_tokens( ®istrar, &voter, - voter_authority, + &voter, 0, &rewards_pool, - &deposit_mining, &context.rewards.program_id, ) .await diff --git a/programs/voter-stake-registry/tests/test_basic.rs b/programs/voter-stake-registry/tests/test_basic.rs index 4bd4442f..0751405a 100644 --- a/programs/voter-stake-registry/tests/test_basic.rs +++ b/programs/voter-stake-registry/tests/test_basic.rs @@ -68,10 +68,10 @@ async fn test_basic() -> Result<(), TransportError> { // TODO: ??? voter_authority == deposit_authority ??? let voter_authority = deposit_authority; - let deposit_mining = find_deposit_mining_addr( + let (deposit_mining, _) = find_deposit_mining_addr( + &context.rewards.program_id, &voter_authority.pubkey(), &rewards_pool, - &context.rewards.program_id, ); let voter = context @@ -96,18 +96,16 @@ async fn test_basic() -> Result<(), TransportError> { let balance_initial = voter.deposit_amount(&context.solana, 0).await; assert_eq!(balance_initial, 0); - let delegate = Keypair::new(); context .addin .create_deposit_entry( ®istrar, &voter, - voter_authority, + &voter, &mngo_voting_mint, 0, LockupKind::None, LockupPeriod::None, - delegate.pubkey(), ) .await?; @@ -116,12 +114,11 @@ async fn test_basic() -> Result<(), TransportError> { .create_deposit_entry( ®istrar, &voter, - voter_authority, + &voter, &mngo_voting_mint, 1, LockupKind::Constant, LockupPeriod::ThreeMonths, - delegate.pubkey(), ) .await?; @@ -143,7 +140,7 @@ async fn test_basic() -> Result<(), TransportError> { .stake( ®istrar, &voter, - deposit_authority, + voter.authority.pubkey(), &context.rewards.program_id, 0, 1, @@ -173,10 +170,9 @@ async fn test_basic() -> Result<(), TransportError> { .unlock_tokens( ®istrar, &voter, - voter_authority, + &voter, 1, &rewards_pool, - &deposit_mining, &context.rewards.program_id, ) .await diff --git a/programs/voter-stake-registry/tests/test_change_delegate.rs b/programs/voter-stake-registry/tests/test_change_delegate.rs new file mode 100644 index 00000000..36fa04ad --- /dev/null +++ b/programs/voter-stake-registry/tests/test_change_delegate.rs @@ -0,0 +1,610 @@ +use anchor_spl::token::TokenAccount; +use mplx_staking_states::{ + error::VsrError, + state::{LockupKind, LockupPeriod}, +}; +use program_test::*; +use solana_program_test::*; +use solana_sdk::{signature::Keypair, signer::Signer, transport::TransportError}; + +mod program_test; + +#[tokio::test] +async fn change_from_own_delegate_to_new_delegate() -> Result<(), TransportError> { + let context = TestContext::new().await; + + let payer = &context.users[0].key; + let realm_authority = Keypair::new(); + let realm = context + .governance + .create_realm( + "testrealm", + realm_authority.pubkey(), + &context.mints[0], + payer, + &context.addin.program_id, + ) + .await; + + let voter_authority = &context.users[1].key; + let token_owner_record = realm + .create_token_owner_record(voter_authority.pubkey(), payer) + .await; + + let fill_authority = Keypair::from_bytes(&context.users[3].key.to_bytes()).unwrap(); + let distribution_authority = Keypair::new(); + let (registrar, rewards_pool) = context + .addin + .create_registrar( + &realm, + &realm_authority, + payer, + &fill_authority.pubkey(), + &distribution_authority.pubkey(), + &context.rewards.program_id, + ) + .await; + context + .addin + .configure_voting_mint( + ®istrar, + &realm_authority, + payer, + 0, + &context.mints[0], + None, + None, + ) + .await; + let mngo_voting_mint = context + .addin + .configure_voting_mint( + ®istrar, + &realm_authority, + payer, + 0, + &context.mints[0], + None, + None, + ) + .await; + + let (deposit_mining, _) = find_deposit_mining_addr( + &context.rewards.program_id, + &voter_authority.pubkey(), + &rewards_pool, + ); + + // CREATE DELEGATE + let delegate_authority = &context.users[2].key; + let delegate_token_account = context.users[2].token_accounts[0]; + + let (delegate_mining, _) = find_deposit_mining_addr( + &context.rewards.program_id, + &delegate_authority.pubkey(), + &rewards_pool, + ); + + let delegate_voter = context + .addin + .create_voter( + ®istrar, + &token_owner_record, + delegate_authority, + payer, + &rewards_pool, + &delegate_mining, + &context.rewards.program_id, + ) + .await; + context + .addin + .create_deposit_entry( + ®istrar, + &delegate_voter, + &delegate_voter, + &mngo_voting_mint, + 0, + LockupKind::None, + LockupPeriod::None, + ) + .await?; + context + .addin + .create_deposit_entry( + ®istrar, + &delegate_voter, + &delegate_voter, + &mngo_voting_mint, + 1, + LockupKind::Constant, + LockupPeriod::OneYear, + ) + .await?; + context + .addin + .deposit( + ®istrar, + &delegate_voter, + &mngo_voting_mint, + delegate_authority, + delegate_token_account, + 0, + 6_000_000, + ) + .await?; + context + .addin + .stake( + ®istrar, + &delegate_voter, + delegate_authority.pubkey(), + &context.rewards.program_id, + 0, + 1, + 6_000_000, + ) + .await?; + + // CREATE VOTER + let voter = context + .addin + .create_voter( + ®istrar, + &token_owner_record, + voter_authority, + payer, + &rewards_pool, + &deposit_mining, + &context.rewards.program_id, + ) + .await; + + // test deposit and stake + let voter_token_account = context.users[1].token_accounts[0]; + context + .addin + .create_deposit_entry( + ®istrar, + &voter, + &voter, + &mngo_voting_mint, + 0, + LockupKind::None, + LockupPeriod::None, + ) + .await?; + context + .addin + .create_deposit_entry( + ®istrar, + &voter, + &voter, + &mngo_voting_mint, + 1, + LockupKind::Constant, + LockupPeriod::OneYear, + ) + .await?; + context + .addin + .deposit( + ®istrar, + &voter, + &mngo_voting_mint, + voter_authority, + voter_token_account, + 0, + 10000, + ) + .await?; + context + .addin + .stake( + ®istrar, + &voter, + voter.authority.pubkey(), + &context.rewards.program_id, + 0, + 1, + 10000, + ) + .await?; + + advance_clock_by_ts(&mut context.solana.context.borrow_mut(), 5 * 86400).await; + + let (old_delegate_mining, _) = find_deposit_mining_addr( + &context.rewards.program_id, + &voter.authority.pubkey(), + &rewards_pool, + ); + + context + .addin + .change_delegate( + ®istrar, + &voter, + &delegate_voter, + &old_delegate_mining, + &context.rewards.program_id, + 1, + ) + .await?; + + Ok(()) +} + +#[tokio::test] +async fn stake_is_too_little() -> Result<(), TransportError> { + let context = TestContext::new().await; + + let payer = &context.users[0].key; + let realm_authority = Keypair::new(); + let realm = context + .governance + .create_realm( + "testrealm", + realm_authority.pubkey(), + &context.mints[0], + payer, + &context.addin.program_id, + ) + .await; + + let voter_authority = &context.users[1].key; + let token_owner_record = realm + .create_token_owner_record(voter_authority.pubkey(), payer) + .await; + + let fill_authority = Keypair::from_bytes(&context.users[3].key.to_bytes()).unwrap(); + let distribution_authority = Keypair::new(); + let (registrar, rewards_pool) = context + .addin + .create_registrar( + &realm, + &realm_authority, + payer, + &fill_authority.pubkey(), + &distribution_authority.pubkey(), + &context.rewards.program_id, + ) + .await; + context + .addin + .configure_voting_mint( + ®istrar, + &realm_authority, + payer, + 0, + &context.mints[0], + None, + None, + ) + .await; + let mngo_voting_mint = context + .addin + .configure_voting_mint( + ®istrar, + &realm_authority, + payer, + 0, + &context.mints[0], + None, + None, + ) + .await; + + let (deposit_mining, _) = find_deposit_mining_addr( + &context.rewards.program_id, + &voter_authority.pubkey(), + &rewards_pool, + ); + + // CREATE DELEGATE + let delegate_authority = &context.users[2].key; + let delegate_token_account = context.users[2].token_accounts[0]; + + let (delegate_mining, _) = find_deposit_mining_addr( + &context.rewards.program_id, + &delegate_authority.pubkey(), + &rewards_pool, + ); + + let delegate_voter = context + .addin + .create_voter( + ®istrar, + &token_owner_record, + delegate_authority, + payer, + &rewards_pool, + &delegate_mining, + &context.rewards.program_id, + ) + .await; + context + .addin + .create_deposit_entry( + ®istrar, + &delegate_voter, + &delegate_voter, + &mngo_voting_mint, + 0, + LockupKind::None, + LockupPeriod::None, + ) + .await?; + context + .addin + .create_deposit_entry( + ®istrar, + &delegate_voter, + &delegate_voter, + &mngo_voting_mint, + 1, + LockupKind::Constant, + LockupPeriod::OneYear, + ) + .await?; + context + .addin + .deposit( + ®istrar, + &delegate_voter, + &mngo_voting_mint, + delegate_authority, + delegate_token_account, + 0, + 100, + ) + .await?; + context + .addin + .stake( + ®istrar, + &delegate_voter, + delegate_authority.pubkey(), + &context.rewards.program_id, + 0, + 1, + 100, + ) + .await?; + + // CREATE VOTER + let voter = context + .addin + .create_voter( + ®istrar, + &token_owner_record, + voter_authority, + payer, + &rewards_pool, + &deposit_mining, + &context.rewards.program_id, + ) + .await; + + // test deposit and stake + let voter_token_account = context.users[1].token_accounts[0]; + context + .addin + .create_deposit_entry( + ®istrar, + &voter, + &voter, + &mngo_voting_mint, + 0, + LockupKind::None, + LockupPeriod::None, + ) + .await?; + context + .addin + .create_deposit_entry( + ®istrar, + &voter, + &voter, + &mngo_voting_mint, + 1, + LockupKind::Constant, + LockupPeriod::OneYear, + ) + .await?; + context + .addin + .deposit( + ®istrar, + &voter, + &mngo_voting_mint, + voter_authority, + voter_token_account, + 0, + 10000, + ) + .await?; + context + .addin + .stake( + ®istrar, + &voter, + voter.authority.pubkey(), + &context.rewards.program_id, + 0, + 1, + 10000, + ) + .await?; + + let (old_delegate_mining, _) = find_deposit_mining_addr( + &context.rewards.program_id, + &voter.authority.pubkey(), + &rewards_pool, + ); + context + .addin + .change_delegate( + ®istrar, + &voter, + &delegate_voter, + &old_delegate_mining, + &context.rewards.program_id, + 1, + ) + .await + .assert_on_chain_err(VsrError::InsufficientWeightedStake); + + Ok(()) +} + +#[tokio::test] +async fn delegate_is_the_same() -> Result<(), TransportError> { + let context = TestContext::new().await; + + let payer = &context.users[0].key; + let realm_authority = Keypair::new(); + let realm = context + .governance + .create_realm( + "testrealm", + realm_authority.pubkey(), + &context.mints[0], + payer, + &context.addin.program_id, + ) + .await; + + let voter_authority = &context.users[1].key; + let token_owner_record = realm + .create_token_owner_record(voter_authority.pubkey(), payer) + .await; + + let fill_authority = Keypair::from_bytes(&context.users[3].key.to_bytes()).unwrap(); + let distribution_authority = Keypair::new(); + let (registrar, rewards_pool) = context + .addin + .create_registrar( + &realm, + &realm_authority, + payer, + &fill_authority.pubkey(), + &distribution_authority.pubkey(), + &context.rewards.program_id, + ) + .await; + context + .addin + .configure_voting_mint( + ®istrar, + &realm_authority, + payer, + 0, + &context.mints[0], + None, + None, + ) + .await; + let mngo_voting_mint = context + .addin + .configure_voting_mint( + ®istrar, + &realm_authority, + payer, + 0, + &context.mints[0], + None, + None, + ) + .await; + + let (deposit_mining, _) = find_deposit_mining_addr( + &context.rewards.program_id, + &voter_authority.pubkey(), + &rewards_pool, + ); + + // CREATE VOTER + let voter = context + .addin + .create_voter( + ®istrar, + &token_owner_record, + voter_authority, + payer, + &rewards_pool, + &deposit_mining, + &context.rewards.program_id, + ) + .await; + + // test deposit and stake + let voter_token_account = context.users[1].token_accounts[0]; + context + .addin + .create_deposit_entry( + ®istrar, + &voter, + &voter, + &mngo_voting_mint, + 0, + LockupKind::None, + LockupPeriod::None, + ) + .await?; + context + .addin + .create_deposit_entry( + ®istrar, + &voter, + &voter, + &mngo_voting_mint, + 1, + LockupKind::Constant, + LockupPeriod::OneYear, + ) + .await?; + context + .addin + .deposit( + ®istrar, + &voter, + &mngo_voting_mint, + voter_authority, + voter_token_account, + 0, + 6_000_000, + ) + .await?; + context + .addin + .stake( + ®istrar, + &voter, + voter.authority.pubkey(), + &context.rewards.program_id, + 0, + 1, + 6_000_000, + ) + .await?; + + let (old_delegate_mining, _) = find_deposit_mining_addr( + &context.rewards.program_id, + &voter.authority.pubkey(), + &rewards_pool, + ); + context + .addin + .change_delegate( + ®istrar, + &voter, + &voter, + &old_delegate_mining, + &context.rewards.program_id, + 1, + ) + .await + .assert_on_chain_err(VsrError::SameDelegate); + + Ok(()) +} diff --git a/programs/voter-stake-registry/tests/test_claim.rs b/programs/voter-stake-registry/tests/test_claim.rs index fd338cc3..042f166b 100644 --- a/programs/voter-stake-registry/tests/test_claim.rs +++ b/programs/voter-stake-registry/tests/test_claim.rs @@ -68,10 +68,10 @@ async fn successeful_claim() -> Result<(), TransportError> { // TODO: ??? voter_authority == deposit_authority ??? let voter_authority = deposit_authority; - let deposit_mining = find_deposit_mining_addr( + let (deposit_mining, _) = find_deposit_mining_addr( + &context.rewards.program_id, &voter_authority.pubkey(), &rewards_pool, - &context.rewards.program_id, ); let voter_authority_ata = context .rewards @@ -97,18 +97,16 @@ async fn successeful_claim() -> Result<(), TransportError> { let depositer_token_account = context.users[1].token_accounts[0]; - let delegate = Keypair::new(); context .addin .create_deposit_entry( ®istrar, &voter, - voter_authority, + &voter, &mngo_voting_mint, 0, LockupKind::None, LockupPeriod::None, - delegate.pubkey(), ) .await?; context @@ -116,12 +114,11 @@ async fn successeful_claim() -> Result<(), TransportError> { .create_deposit_entry( ®istrar, &voter, - voter_authority, + &voter, &mngo_voting_mint, 1, LockupKind::Constant, LockupPeriod::ThreeMonths, - delegate.pubkey(), ) .await?; @@ -143,7 +140,7 @@ async fn successeful_claim() -> Result<(), TransportError> { .stake( ®istrar, &voter, - deposit_authority, + voter.authority.pubkey(), &context.rewards.program_id, 0, 1, diff --git a/programs/voter-stake-registry/tests/test_deposit_constant.rs b/programs/voter-stake-registry/tests/test_deposit_constant.rs index 8865140d..b9ff022f 100644 --- a/programs/voter-stake-registry/tests/test_deposit_constant.rs +++ b/programs/voter-stake-registry/tests/test_deposit_constant.rs @@ -1,5 +1,8 @@ use anchor_spl::token::TokenAccount; -use mplx_staking_states::state::{LockupKind, LockupPeriod}; +use mplx_staking_states::{ + error::VsrError, + state::{LockupKind, LockupPeriod}, +}; use program_test::*; use solana_program_test::*; use solana_sdk::{pubkey::Pubkey, signature::Keypair, signer::Signer, transport::TransportError}; @@ -91,10 +94,10 @@ async fn test_deposit_constant() -> Result<(), TransportError> { // TODO: ??? voter_authority == deposit_authority ??? let voter_authority = deposit_authority; - let deposit_mining = find_deposit_mining_addr( + let (deposit_mining, _) = find_deposit_mining_addr( + &context.rewards.program_id, &voter_authority.pubkey(), &rewards_pool, - &context.rewards.program_id, ); let voter = addin @@ -149,17 +152,15 @@ async fn test_deposit_constant() -> Result<(), TransportError> { .token_account_balance(reference_account) .await; - let delegate = Keypair::new(); addin .create_deposit_entry( ®istrar, &voter, - voter_authority, + &voter, &mngo_voting_mint, 0, LockupKind::None, LockupPeriod::None, - delegate.pubkey(), ) .await .unwrap(); @@ -167,12 +168,11 @@ async fn test_deposit_constant() -> Result<(), TransportError> { .create_deposit_entry( ®istrar, &voter, - voter_authority, + &voter, &mngo_voting_mint, 1, LockupKind::Constant, LockupPeriod::ThreeMonths, - delegate.pubkey(), ) .await .unwrap(); @@ -181,7 +181,7 @@ async fn test_deposit_constant() -> Result<(), TransportError> { .stake( ®istrar, &voter, - voter_authority, + voter.authority.pubkey(), &context.rewards.program_id, 0, 1, @@ -194,7 +194,9 @@ async fn test_deposit_constant() -> Result<(), TransportError> { assert_eq!(after_deposit.voter_weight, after_deposit.vault); // unchanged assert_eq!(after_deposit.vault, 10_000); assert_eq!(after_deposit.deposit, 10_000); - withdraw(1, 1).await.expect_err("all locked up"); + withdraw(1, 1) + .await + .assert_on_chain_err(VsrError::UnlockMustBeCalledFirst); // advance to day 95. Just to be sure withdraw isn't possible without unlocking first // even at lockup period + cooldown period (90 + 5 respectively in that case) @@ -208,17 +210,16 @@ async fn test_deposit_constant() -> Result<(), TransportError> { .unlock_tokens( ®istrar, &voter, - voter_authority, + &voter, 1, &rewards_pool, - &deposit_mining, &context.rewards.program_id, ) .await .unwrap(); withdraw(10_000, 1) .await - .expect_err("Cooldown still not passed"); + .assert_on_chain_err(VsrError::InvalidTimestampArguments); context.solana.advance_clock_by_slots(2).await; // avoid caching of transactions // warp to day 100. (90 days of lockup + fake cooldown (5 days)) + 5 days of true cooldown @@ -280,10 +281,10 @@ async fn test_withdrawing_without_unlocking() -> Result<(), TransportError> { ) .await; - let deposit_mining = find_deposit_mining_addr( + let (deposit_mining, _) = find_deposit_mining_addr( + &context.rewards.program_id, &voter_authority.pubkey(), &rewards_pool, - &context.rewards.program_id, ); let voter = addin @@ -322,17 +323,15 @@ async fn test_withdrawing_without_unlocking() -> Result<(), TransportError> { ) }; - let delegate = Keypair::new(); addin .create_deposit_entry( ®istrar, &voter, - voter_authority, + &voter, &mngo_voting_mint, 0, LockupKind::None, LockupPeriod::None, - delegate.pubkey(), ) .await .unwrap(); @@ -340,12 +339,11 @@ async fn test_withdrawing_without_unlocking() -> Result<(), TransportError> { .create_deposit_entry( ®istrar, &voter, - voter_authority, + &voter, &mngo_voting_mint, 1, LockupKind::Constant, LockupPeriod::ThreeMonths, - delegate.pubkey(), ) .await .unwrap(); @@ -354,7 +352,7 @@ async fn test_withdrawing_without_unlocking() -> Result<(), TransportError> { .stake( ®istrar, &voter, - voter_authority, + voter.authority.pubkey(), &context.rewards.program_id, 0, 1, @@ -370,7 +368,7 @@ async fn test_withdrawing_without_unlocking() -> Result<(), TransportError> { // withdraw withdraw(10_000) .await - .expect_err("impossible to withdraw without unlocking"); + .assert_on_chain_err(VsrError::InsufficientUnlockedTokens); Ok(()) } diff --git a/programs/voter-stake-registry/tests/test_deposit_no_locking.rs b/programs/voter-stake-registry/tests/test_deposit_no_locking.rs index 79791e8b..363da3b2 100644 --- a/programs/voter-stake-registry/tests/test_deposit_no_locking.rs +++ b/programs/voter-stake-registry/tests/test_deposit_no_locking.rs @@ -1,5 +1,8 @@ use anchor_spl::token::TokenAccount; -use mplx_staking_states::state::{LockupKind, LockupPeriod}; +use mplx_staking_states::{ + error::VsrError, + state::{LockupKind, LockupPeriod}, +}; use program_test::*; use solana_program_test::*; use solana_sdk::{pubkey::Pubkey, signature::Keypair, signer::Signer, transport::TransportError}; @@ -95,10 +98,10 @@ async fn test_deposit_no_locking() -> Result<(), TransportError> { // TODO: ??? voter_authority == deposit_authority ??? let voter_authority = deposit_authority; - let deposit_mining_voter = find_deposit_mining_addr( + let (deposit_mining_voter, _) = find_deposit_mining_addr( + &context.rewards.program_id, &voter_authority.pubkey(), &rewards_pool, - &context.rewards.program_id, ); let voter = addin .create_voter( @@ -113,10 +116,10 @@ async fn test_deposit_no_locking() -> Result<(), TransportError> { .await; let voter2_authority = deposit2_authority; - let deposit_mining_voter2 = find_deposit_mining_addr( + let (deposit_mining_voter2, _) = find_deposit_mining_addr( + &context.rewards.program_id, &voter2_authority.pubkey(), &rewards_pool, - &context.rewards.program_id, ); let voter2 = addin .create_voter( @@ -169,17 +172,15 @@ async fn test_deposit_no_locking() -> Result<(), TransportError> { .token_account_balance(reference_account) .await; - let delegate = Keypair::new(); addin .create_deposit_entry( ®istrar, &voter, - voter_authority, + &voter, &mngo_voting_mint, 0, LockupKind::None, LockupPeriod::None, - delegate.pubkey(), ) .await .unwrap(); @@ -191,18 +192,16 @@ async fn test_deposit_no_locking() -> Result<(), TransportError> { assert_eq!(after_deposit.vault, 15000); assert_eq!(after_deposit.deposit, 15000); - let delegate = Keypair::new(); // create a separate deposit (index 1) addin .create_deposit_entry( ®istrar, &voter, - voter_authority, + &voter, &mngo_voting_mint, 1, LockupKind::None, LockupPeriod::None, - delegate.pubkey(), ) .await .unwrap(); @@ -222,7 +221,9 @@ async fn test_deposit_no_locking() -> Result<(), TransportError> { assert_eq!(after_withdraw1.vault, 12000); assert_eq!(after_withdraw1.deposit, 5000); - withdraw(5001).await.expect_err("withdrew too much"); + withdraw(5001) + .await + .assert_on_chain_err(VsrError::InsufficientUnlockedTokens); withdraw(5000).await.unwrap(); @@ -236,11 +237,11 @@ async fn test_deposit_no_locking() -> Result<(), TransportError> { addin .close_deposit_entry(&voter, voter_authority, 2) .await - .expect_err("deposit not in use"); + .assert_on_chain_err(VsrError::UnusedDepositEntryIndex); addin .close_deposit_entry(&voter, voter_authority, 1) .await - .expect_err("deposit not empty"); + .assert_on_chain_err(VsrError::VotingTokenNonZero); addin .close_deposit_entry(&voter, voter_authority, 0) .await @@ -265,17 +266,16 @@ async fn test_deposit_no_locking() -> Result<(), TransportError> { assert_eq!(voter2_voter_weight, 0); // now voter2 deposits - let delegate = Keypair::new(); + addin .create_deposit_entry( ®istrar, &voter2, - voter2_authority, + &voter2, &mngo_voting_mint, 5, LockupKind::None, LockupPeriod::None, - delegate.pubkey(), ) .await .unwrap(); @@ -306,17 +306,16 @@ async fn test_deposit_no_locking() -> Result<(), TransportError> { assert_eq!(voter2_balances.vault, 1000); // when voter1 deposits again, they can reuse deposit index 0 - let delegate = Keypair::new(); + addin .create_deposit_entry( ®istrar, &voter, - voter_authority, + &voter, &mngo_voting_mint, 0, LockupKind::None, LockupPeriod::None, - delegate.pubkey(), ) .await .unwrap(); diff --git a/programs/voter-stake-registry/tests/test_extend_deposit.rs b/programs/voter-stake-registry/tests/test_extend_deposit.rs index 50637ca1..4283570f 100644 --- a/programs/voter-stake-registry/tests/test_extend_deposit.rs +++ b/programs/voter-stake-registry/tests/test_extend_deposit.rs @@ -1,5 +1,8 @@ use anchor_spl::token::TokenAccount; -use mplx_staking_states::state::{LockupKind, LockupPeriod}; +use mplx_staking_states::{ + error::VsrError, + state::{LockupKind, LockupPeriod}, +}; use program_test::*; use solana_program_test::*; use solana_sdk::{ @@ -68,10 +71,10 @@ async fn extend_from_flex() -> Result<(), TransportError> { ) .await; - let deposit_mining = find_deposit_mining_addr( + let (deposit_mining, _) = find_deposit_mining_addr( + &context.rewards.program_id, &voter_authority.pubkey(), &rewards_pool, - &context.rewards.program_id, ); let voter = context @@ -89,18 +92,16 @@ async fn extend_from_flex() -> Result<(), TransportError> { // test deposit and withdraw let reference_account = context.users[1].token_accounts[0]; - let delegate = Keypair::new(); context .addin .create_deposit_entry( ®istrar, &voter, - voter_authority, + &voter, &mngo_voting_mint, 0, LockupKind::None, LockupPeriod::None, - delegate.pubkey(), ) .await?; context @@ -108,12 +109,11 @@ async fn extend_from_flex() -> Result<(), TransportError> { .create_deposit_entry( ®istrar, &voter, - voter_authority, + &voter, &mngo_voting_mint, 1, LockupKind::Constant, LockupPeriod::OneYear, - delegate.pubkey(), ) .await?; context @@ -133,7 +133,7 @@ async fn extend_from_flex() -> Result<(), TransportError> { .stake( ®istrar, &voter, - voter_authority, + voter.authority.pubkey(), &context.rewards.program_id, 0, 1, @@ -152,6 +152,7 @@ async fn extend_from_flex() -> Result<(), TransportError> { ®istrar, &voter, voter_authority, + &voter_authority.pubkey(), &context.rewards.program_id, 0, 1, @@ -231,10 +232,10 @@ async fn extend_from_three_months_deposit() -> Result<(), TransportError> { ) .await; - let deposit_mining = find_deposit_mining_addr( + let (deposit_mining, _) = find_deposit_mining_addr( + &context.rewards.program_id, &voter_authority.pubkey(), &rewards_pool, - &context.rewards.program_id, ); let voter = context @@ -252,18 +253,16 @@ async fn extend_from_three_months_deposit() -> Result<(), TransportError> { // test deposit and withdraw let reference_account = context.users[1].token_accounts[0]; - let delegate = Keypair::new(); context .addin .create_deposit_entry( ®istrar, &voter, - voter_authority, + &voter, &mngo_voting_mint, 0, LockupKind::None, LockupPeriod::None, - delegate.pubkey(), ) .await?; context @@ -271,12 +270,11 @@ async fn extend_from_three_months_deposit() -> Result<(), TransportError> { .create_deposit_entry( ®istrar, &voter, - voter_authority, + &voter, &mngo_voting_mint, 1, LockupKind::Constant, LockupPeriod::OneYear, - delegate.pubkey(), ) .await?; context @@ -296,7 +294,7 @@ async fn extend_from_three_months_deposit() -> Result<(), TransportError> { .stake( ®istrar, &voter, - voter_authority, + voter.authority.pubkey(), &context.rewards.program_id, 0, 1, @@ -315,6 +313,7 @@ async fn extend_from_three_months_deposit() -> Result<(), TransportError> { ®istrar, &voter, voter_authority, + &voter_authority.pubkey(), &context.rewards.program_id, 0, 1, @@ -395,10 +394,10 @@ async fn extend_deposit_after_one_year_for_three_months_with_top_up() -> Result< ) .await; - let deposit_mining = find_deposit_mining_addr( + let (deposit_mining, _) = find_deposit_mining_addr( + &context.rewards.program_id, &voter_authority.pubkey(), &rewards_pool, - &context.rewards.program_id, ); let voter = context @@ -416,18 +415,16 @@ async fn extend_deposit_after_one_year_for_three_months_with_top_up() -> Result< // test deposit and withdraw let reference_account = context.users[1].token_accounts[0]; - let delegate = Keypair::new(); context .addin .create_deposit_entry( ®istrar, &voter, - voter_authority, + &voter, &mngo_voting_mint, 0, LockupKind::None, LockupPeriod::None, - delegate.pubkey(), ) .await?; context @@ -435,12 +432,11 @@ async fn extend_deposit_after_one_year_for_three_months_with_top_up() -> Result< .create_deposit_entry( ®istrar, &voter, - voter_authority, + &voter, &mngo_voting_mint, 1, LockupKind::Constant, LockupPeriod::OneYear, - delegate.pubkey(), ) .await?; context @@ -460,7 +456,7 @@ async fn extend_deposit_after_one_year_for_three_months_with_top_up() -> Result< .stake( ®istrar, &voter, - voter_authority, + voter.authority.pubkey(), &context.rewards.program_id, 0, 1, @@ -479,6 +475,7 @@ async fn extend_deposit_after_one_year_for_three_months_with_top_up() -> Result< ®istrar, &voter, voter_authority, + &voter_authority.pubkey(), &context.rewards.program_id, 0, 1, @@ -558,10 +555,10 @@ async fn extend_from_flex_deposit_with_top_up() -> Result<(), TransportError> { ) .await; - let deposit_mining = find_deposit_mining_addr( + let (deposit_mining, _) = find_deposit_mining_addr( + &context.rewards.program_id, &voter_authority.pubkey(), &rewards_pool, - &context.rewards.program_id, ); let voter = context @@ -579,18 +576,16 @@ async fn extend_from_flex_deposit_with_top_up() -> Result<(), TransportError> { // test deposit and withdraw let reference_account = context.users[1].token_accounts[0]; - let delegate = Keypair::new(); context .addin .create_deposit_entry( ®istrar, &voter, - voter_authority, + &voter, &mngo_voting_mint, 0, LockupKind::None, LockupPeriod::None, - delegate.pubkey(), ) .await?; context @@ -598,12 +593,11 @@ async fn extend_from_flex_deposit_with_top_up() -> Result<(), TransportError> { .create_deposit_entry( ®istrar, &voter, - voter_authority, + &voter, &mngo_voting_mint, 1, LockupKind::Constant, LockupPeriod::OneYear, - delegate.pubkey(), ) .await?; context @@ -623,7 +617,7 @@ async fn extend_from_flex_deposit_with_top_up() -> Result<(), TransportError> { .stake( ®istrar, &voter, - voter_authority, + voter.authority.pubkey(), &context.rewards.program_id, 0, 1, @@ -642,6 +636,7 @@ async fn extend_from_flex_deposit_with_top_up() -> Result<(), TransportError> { ®istrar, &voter, voter_authority, + &voter_authority.pubkey(), &context.rewards.program_id, 0, 1, @@ -717,10 +712,10 @@ async fn extend_from_three_month_to_one_year() -> Result<(), TransportError> { ) .await; - let deposit_mining = find_deposit_mining_addr( + let (deposit_mining, _) = find_deposit_mining_addr( + &context.rewards.program_id, &voter_authority.pubkey(), &rewards_pool, - &context.rewards.program_id, ); let voter = context @@ -738,18 +733,16 @@ async fn extend_from_three_month_to_one_year() -> Result<(), TransportError> { // test deposit and withdraw let reference_account = context.users[1].token_accounts[0]; - let delegate = Keypair::new(); context .addin .create_deposit_entry( ®istrar, &voter, - voter_authority, + &voter, &mngo_voting_mint, 0, LockupKind::None, LockupPeriod::None, - delegate.pubkey(), ) .await?; context @@ -757,12 +750,11 @@ async fn extend_from_three_month_to_one_year() -> Result<(), TransportError> { .create_deposit_entry( ®istrar, &voter, - voter_authority, + &voter, &mngo_voting_mint, 1, LockupKind::Constant, LockupPeriod::ThreeMonths, - delegate.pubkey(), ) .await?; context @@ -782,7 +774,7 @@ async fn extend_from_three_month_to_one_year() -> Result<(), TransportError> { .stake( ®istrar, &voter, - voter_authority, + voter.authority.pubkey(), &context.rewards.program_id, 0, 1, @@ -801,6 +793,7 @@ async fn extend_from_three_month_to_one_year() -> Result<(), TransportError> { ®istrar, &voter, voter_authority, + &voter_authority.pubkey(), &context.rewards.program_id, 0, 1, @@ -808,7 +801,235 @@ async fn extend_from_three_month_to_one_year() -> Result<(), TransportError> { 50, ) .await - .expect_err("Impossible to extend stake from existing stake (not flex) to another period"); + .assert_on_chain_err(VsrError::ArithmeticOverflow); + + Ok(()) +} + +#[tokio::test] +async fn prolongs_with_delegate() -> Result<(), TransportError> { + let context = TestContext::new().await; + + let payer = &context.users[0].key; + let realm_authority = Keypair::new(); + let realm = context + .governance + .create_realm( + "testrealm", + realm_authority.pubkey(), + &context.mints[0], + payer, + &context.addin.program_id, + ) + .await; + + let voter_authority = &context.users[1].key; + let token_owner_record = realm + .create_token_owner_record(voter_authority.pubkey(), payer) + .await; + + let fill_authority = Keypair::from_bytes(&context.users[3].key.to_bytes()).unwrap(); + let distribution_authority = Keypair::new(); + let (registrar, rewards_pool) = context + .addin + .create_registrar( + &realm, + &realm_authority, + payer, + &fill_authority.pubkey(), + &distribution_authority.pubkey(), + &context.rewards.program_id, + ) + .await; + context + .addin + .configure_voting_mint( + ®istrar, + &realm_authority, + payer, + 0, + &context.mints[0], + None, + None, + ) + .await; + let mngo_voting_mint = context + .addin + .configure_voting_mint( + ®istrar, + &realm_authority, + payer, + 0, + &context.mints[0], + None, + None, + ) + .await; + + let (deposit_mining, _) = find_deposit_mining_addr( + &context.rewards.program_id, + &voter_authority.pubkey(), + &rewards_pool, + ); + + // CREATE DELEGATE + let delegate_authority = &context.users[2].key; + let delegate_token_account = context.users[2].token_accounts[0]; + + let (delegate_mining, _) = find_deposit_mining_addr( + &context.rewards.program_id, + &delegate_authority.pubkey(), + &rewards_pool, + ); + + let delegate_voter = context + .addin + .create_voter( + ®istrar, + &token_owner_record, + delegate_authority, + payer, + &rewards_pool, + &delegate_mining, + &context.rewards.program_id, + ) + .await; + context + .addin + .create_deposit_entry( + ®istrar, + &delegate_voter, + &delegate_voter, + &mngo_voting_mint, + 0, + LockupKind::None, + LockupPeriod::None, + ) + .await?; + context + .addin + .create_deposit_entry( + ®istrar, + &delegate_voter, + &delegate_voter, + &mngo_voting_mint, + 1, + LockupKind::Constant, + LockupPeriod::OneYear, + ) + .await?; + context + .addin + .deposit( + ®istrar, + &delegate_voter, + &mngo_voting_mint, + delegate_authority, + delegate_token_account, + 0, + 6_000_000, + ) + .await?; + context + .addin + .stake( + ®istrar, + &delegate_voter, + delegate_authority.pubkey(), + &context.rewards.program_id, + 0, + 1, + 6_000_000, + ) + .await?; + + // CREATE VOTER + let voter = context + .addin + .create_voter( + ®istrar, + &token_owner_record, + voter_authority, + payer, + &rewards_pool, + &deposit_mining, + &context.rewards.program_id, + ) + .await; + + // test deposit and stake + let voter_token_account = context.users[1].token_accounts[0]; + context + .addin + .create_deposit_entry( + ®istrar, + &voter, + &voter, + &mngo_voting_mint, + 0, + LockupKind::None, + LockupPeriod::None, + ) + .await?; + context + .addin + .create_deposit_entry( + ®istrar, + &voter, + &delegate_voter, + &mngo_voting_mint, + 1, + LockupKind::Constant, + LockupPeriod::OneYear, + ) + .await?; + context + .addin + .deposit( + ®istrar, + &voter, + &mngo_voting_mint, + voter_authority, + voter_token_account, + 0, + 10000, + ) + .await?; + context + .addin + .stake( + ®istrar, + &voter, + delegate_authority.pubkey(), + &context.rewards.program_id, + 0, + 1, + 10000, + ) + .await?; + + context + .addin + .extend_stake( + ®istrar, + &voter, + voter_authority, + &delegate_authority.pubkey(), + &context.rewards.program_id, + 0, + 1, + LockupPeriod::OneYear, + 0, + ) + .await?; + + let vault_balance = mngo_voting_mint + .vault_balance(&context.solana, &voter) + .await; + let deposit_amount = voter.deposit_amount(&context.solana, 1).await; + + assert_eq!(vault_balance, 10000); + assert_eq!(deposit_amount, 10000); Ok(()) } diff --git a/programs/voter-stake-registry/tests/test_lockup.rs b/programs/voter-stake-registry/tests/test_lockup.rs index 9d10d7a2..c227f41a 100644 --- a/programs/voter-stake-registry/tests/test_lockup.rs +++ b/programs/voter-stake-registry/tests/test_lockup.rs @@ -1,8 +1,12 @@ use anchor_spl::token::TokenAccount; -use mplx_staking_states::state::{LockupKind, LockupPeriod}; +use mplx_staking_states::{ + error::VsrError, + state::{LockupKind, LockupPeriod}, +}; use program_test::*; use solana_program_test::*; use solana_sdk::{signature::Keypair, signer::Signer, transport::TransportError}; +use AssertCustomOnChainErr; mod program_test; @@ -68,10 +72,10 @@ async fn test_unlock_and_withdraw_before_end_ts() -> Result<(), TransportError> // TODO: ??? voter_authority == deposit_authority ??? let voter_authority = deposit_authority; - let deposit_mining = find_deposit_mining_addr( + let (deposit_mining, _) = find_deposit_mining_addr( + &context.rewards.program_id, &voter_authority.pubkey(), &rewards_pool, - &context.rewards.program_id, ); let voter = context @@ -89,18 +93,17 @@ async fn test_unlock_and_withdraw_before_end_ts() -> Result<(), TransportError> // test deposit and withdraw let reference_account = context.users[1].token_accounts[0]; - let delegate = Keypair::new(); + context .addin .create_deposit_entry( ®istrar, &voter, - voter_authority, + &voter, &mngo_voting_mint, 0, LockupKind::None, LockupPeriod::None, - delegate.pubkey(), ) .await?; context @@ -108,12 +111,11 @@ async fn test_unlock_and_withdraw_before_end_ts() -> Result<(), TransportError> .create_deposit_entry( ®istrar, &voter, - voter_authority, + &voter, &mngo_voting_mint, 1, LockupKind::Constant, LockupPeriod::OneYear, - delegate.pubkey(), ) .await?; context @@ -133,7 +135,7 @@ async fn test_unlock_and_withdraw_before_end_ts() -> Result<(), TransportError> .stake( ®istrar, &voter, - voter_authority, + voter.authority.pubkey(), &context.rewards.program_id, 0, 1, @@ -146,14 +148,14 @@ async fn test_unlock_and_withdraw_before_end_ts() -> Result<(), TransportError> .unlock_tokens( ®istrar, &voter, - voter_authority, + &voter, 1, &rewards_pool, - &deposit_mining, &context.rewards.program_id, ) .await - .expect_err("fails because it's too early to unlock is invalid"); + .assert_on_chain_err(VsrError::DepositStillLocked); + context .addin .withdraw( @@ -166,7 +168,7 @@ async fn test_unlock_and_withdraw_before_end_ts() -> Result<(), TransportError> 10000, ) .await - .expect_err("fails because it's impossible to withdraw without unlock"); + .assert_on_chain_err(VsrError::UnlockMustBeCalledFirst); Ok(()) } @@ -231,10 +233,10 @@ async fn test_unlock_after_end_ts() -> Result<(), TransportError> { ) .await; - let deposit_mining = find_deposit_mining_addr( + let (deposit_mining, _) = find_deposit_mining_addr( + &context.rewards.program_id, &voter_authority.pubkey(), &rewards_pool, - &context.rewards.program_id, ); let voter = context @@ -252,18 +254,17 @@ async fn test_unlock_after_end_ts() -> Result<(), TransportError> { // test deposit and withdraw let reference_account = context.users[1].token_accounts[0]; - let delegate = Keypair::new(); + context .addin .create_deposit_entry( ®istrar, &voter, - voter_authority, + &voter, &mngo_voting_mint, 0, LockupKind::None, LockupPeriod::None, - delegate.pubkey(), ) .await?; context @@ -271,12 +272,11 @@ async fn test_unlock_after_end_ts() -> Result<(), TransportError> { .create_deposit_entry( ®istrar, &voter, - voter_authority, + &voter, &mngo_voting_mint, 1, LockupKind::Constant, LockupPeriod::OneYear, - delegate.pubkey(), ) .await?; context @@ -296,7 +296,7 @@ async fn test_unlock_after_end_ts() -> Result<(), TransportError> { .stake( ®istrar, &voter, - voter_authority, + voter.authority.pubkey(), &context.rewards.program_id, 0, 1, @@ -317,10 +317,9 @@ async fn test_unlock_after_end_ts() -> Result<(), TransportError> { .unlock_tokens( ®istrar, &voter, - voter_authority, + &voter, 1, &rewards_pool, - &deposit_mining, &context.rewards.program_id, ) .await @@ -339,7 +338,7 @@ async fn test_unlock_after_end_ts() -> Result<(), TransportError> { 10000, ) .await - .expect_err("fails because cooldown is ongoing"); + .assert_on_chain_err(VsrError::InvalidTimestampArguments); Ok(()) } @@ -404,10 +403,10 @@ async fn test_unlock_and_withdraw_after_end_ts_and_cooldown() -> Result<(), Tran ) .await; - let deposit_mining = find_deposit_mining_addr( + let (deposit_mining, _) = find_deposit_mining_addr( + &context.rewards.program_id, &voter_authority.pubkey(), &rewards_pool, - &context.rewards.program_id, ); let voter = context @@ -425,18 +424,17 @@ async fn test_unlock_and_withdraw_after_end_ts_and_cooldown() -> Result<(), Tran // test deposit and withdraw let reference_account = context.users[1].token_accounts[0]; - let delegate = Keypair::new(); + context .addin .create_deposit_entry( ®istrar, &voter, - voter_authority, + &voter, &mngo_voting_mint, 0, LockupKind::None, LockupPeriod::None, - delegate.pubkey(), ) .await?; context @@ -444,12 +442,11 @@ async fn test_unlock_and_withdraw_after_end_ts_and_cooldown() -> Result<(), Tran .create_deposit_entry( ®istrar, &voter, - voter_authority, + &voter, &mngo_voting_mint, 1, LockupKind::Constant, LockupPeriod::OneYear, - delegate.pubkey(), ) .await?; context @@ -469,7 +466,7 @@ async fn test_unlock_and_withdraw_after_end_ts_and_cooldown() -> Result<(), Tran .stake( ®istrar, &voter, - voter_authority, + voter.authority.pubkey(), &context.rewards.program_id, 0, 1, @@ -488,10 +485,9 @@ async fn test_unlock_and_withdraw_after_end_ts_and_cooldown() -> Result<(), Tran .unlock_tokens( ®istrar, &voter, - voter_authority, + &voter, 1, &rewards_pool, - &deposit_mining, &context.rewards.program_id, ) .await diff --git a/programs/voter-stake-registry/tests/test_log_voter_info.rs b/programs/voter-stake-registry/tests/test_log_voter_info.rs index 6ce58e56..f7ef3e73 100644 --- a/programs/voter-stake-registry/tests/test_log_voter_info.rs +++ b/programs/voter-stake-registry/tests/test_log_voter_info.rs @@ -77,10 +77,10 @@ async fn test_log_voter_info() -> Result<(), TransportError> { // TODO: ??? voter_authority == deposit_authority ??? let voter_authority = deposit_authority; - let deposit_mining = find_deposit_mining_addr( + let (deposit_mining, _) = find_deposit_mining_addr( + &context.rewards.program_id, &voter_authority.pubkey(), &rewards_pool, - &context.rewards.program_id, ); let voter = addin @@ -95,17 +95,15 @@ async fn test_log_voter_info() -> Result<(), TransportError> { ) .await; - let delegate = Keypair::new(); addin .create_deposit_entry( ®istrar, &voter, - voter_authority, + &voter, &mngo_voting_mint, 0, LockupKind::None, LockupPeriod::None, - delegate.pubkey(), ) .await .unwrap(); @@ -113,12 +111,11 @@ async fn test_log_voter_info() -> Result<(), TransportError> { .create_deposit_entry( ®istrar, &voter, - voter_authority, + &voter, &mngo_voting_mint, 1, LockupKind::Constant, LockupPeriod::OneYear, - delegate.pubkey(), ) .await .unwrap(); @@ -138,7 +135,7 @@ async fn test_log_voter_info() -> Result<(), TransportError> { .stake( ®istrar, &voter, - voter_authority, + voter.authority.pubkey(), &context.rewards.program_id, 0, 1, diff --git a/programs/voter-stake-registry/tests/test_stake.rs b/programs/voter-stake-registry/tests/test_stake.rs new file mode 100644 index 00000000..2709aa8d --- /dev/null +++ b/programs/voter-stake-registry/tests/test_stake.rs @@ -0,0 +1,220 @@ +use anchor_spl::token::TokenAccount; +use mplx_staking_states::state::{LockupKind, LockupPeriod}; +use program_test::*; +use solana_program_test::*; +use solana_sdk::{signature::Keypair, signer::Signer, transport::TransportError}; + +mod program_test; + +#[tokio::test] +async fn stake_with_delegate() -> Result<(), TransportError> { + let context = TestContext::new().await; + + let payer = &context.users[0].key; + let realm_authority = Keypair::new(); + let realm = context + .governance + .create_realm( + "testrealm", + realm_authority.pubkey(), + &context.mints[0], + payer, + &context.addin.program_id, + ) + .await; + + let voter_authority = &context.users[1].key; + let token_owner_record = realm + .create_token_owner_record(voter_authority.pubkey(), payer) + .await; + + let fill_authority = Keypair::from_bytes(&context.users[3].key.to_bytes()).unwrap(); + let distribution_authority = Keypair::new(); + let (registrar, rewards_pool) = context + .addin + .create_registrar( + &realm, + &realm_authority, + payer, + &fill_authority.pubkey(), + &distribution_authority.pubkey(), + &context.rewards.program_id, + ) + .await; + context + .addin + .configure_voting_mint( + ®istrar, + &realm_authority, + payer, + 0, + &context.mints[0], + None, + None, + ) + .await; + let mngo_voting_mint = context + .addin + .configure_voting_mint( + ®istrar, + &realm_authority, + payer, + 0, + &context.mints[0], + None, + None, + ) + .await; + + let (deposit_mining, _) = find_deposit_mining_addr( + &context.rewards.program_id, + &voter_authority.pubkey(), + &rewards_pool, + ); + + let voter = context + .addin + .create_voter( + ®istrar, + &token_owner_record, + voter_authority, + payer, + &rewards_pool, + &deposit_mining, + &context.rewards.program_id, + ) + .await; + + // CREATE DELEGATE + let delegate_authority = &context.users[2].key; + let delegate_token_account = context.users[2].token_accounts[0]; + + let (delegate_mining, _) = find_deposit_mining_addr( + &context.rewards.program_id, + &delegate_authority.pubkey(), + &rewards_pool, + ); + + let delegate_voter = context + .addin + .create_voter( + ®istrar, + &token_owner_record, + delegate_authority, + payer, + &rewards_pool, + &delegate_mining, + &context.rewards.program_id, + ) + .await; + context + .addin + .create_deposit_entry( + ®istrar, + &delegate_voter, + &delegate_voter, + &mngo_voting_mint, + 0, + LockupKind::None, + LockupPeriod::None, + ) + .await?; + context + .addin + .create_deposit_entry( + ®istrar, + &delegate_voter, + &delegate_voter, + &mngo_voting_mint, + 1, + LockupKind::Constant, + LockupPeriod::OneYear, + ) + .await?; + context + .addin + .deposit( + ®istrar, + &delegate_voter, + &mngo_voting_mint, + delegate_authority, + delegate_token_account, + 0, + 6_000_000, + ) + .await?; + context + .addin + .stake( + ®istrar, + &delegate_voter, + delegate_authority.pubkey(), + &context.rewards.program_id, + 0, + 1, + 6_000_000, + ) + .await?; + + // Create voter and stake with delegate + // test deposit and stake + let voter_token_account = context.users[1].token_accounts[0]; + context + .addin + .create_deposit_entry( + ®istrar, + &voter, + &voter, + &mngo_voting_mint, + 0, + LockupKind::None, + LockupPeriod::None, + ) + .await?; + context + .addin + .create_deposit_entry( + ®istrar, + &voter, + &delegate_voter, + &mngo_voting_mint, + 1, + LockupKind::Constant, + LockupPeriod::OneYear, + ) + .await?; + context + .addin + .deposit( + ®istrar, + &voter, + &mngo_voting_mint, + voter_authority, + voter_token_account, + 0, + 10000, + ) + .await?; + context + .addin + .stake( + ®istrar, + &voter, + delegate_voter.authority.pubkey(), + &context.rewards.program_id, + 0, + 1, + 10000, + ) + .await?; + + let vault_balance = mngo_voting_mint + .vault_balance(&context.solana, &voter) + .await; + let deposit_amount = voter.deposit_amount(&context.solana, 1).await; + + assert_eq!(vault_balance, 10000); + assert_eq!(deposit_amount, 10000); + + Ok(()) +} diff --git a/programs/voter-stake-registry/tests/test_voting.rs b/programs/voter-stake-registry/tests/test_voting.rs index 287ef6b3..48d0d5fd 100644 --- a/programs/voter-stake-registry/tests/test_voting.rs +++ b/programs/voter-stake-registry/tests/test_voting.rs @@ -69,10 +69,10 @@ async fn test_voting() -> Result<(), TransportError> { ) .await; - let deposit_mining_voter = find_deposit_mining_addr( + let (deposit_mining_voter, _) = find_deposit_mining_addr( + &context.rewards.program_id, &voter_authority.pubkey(), &rewards_pool, - &context.rewards.program_id, ); let voter = addin .create_voter( @@ -86,10 +86,10 @@ async fn test_voting() -> Result<(), TransportError> { ) .await; - let deposit_mining_voter2 = find_deposit_mining_addr( + let (deposit_mining_voter2, _) = find_deposit_mining_addr( + &context.rewards.program_id, &voter2_authority.pubkey(), &rewards_pool, - &context.rewards.program_id, ); let voter2 = addin .create_voter( @@ -114,17 +114,15 @@ async fn test_voting() -> Result<(), TransportError> { ) .await; - let delegate = Keypair::new(); addin .create_deposit_entry( ®istrar, &voter, - voter_authority, + &voter, &mngo_voting_mint, 0, LockupKind::None, LockupPeriod::None, - delegate.pubkey(), ) .await .unwrap(); @@ -167,17 +165,15 @@ async fn test_voting() -> Result<(), TransportError> { .await .expect_err("could not withdraw"); - let delegate = Keypair::new(); addin .create_deposit_entry( ®istrar, &voter2, - voter2_authority, + &voter2, &mngo_voting_mint, 0, LockupKind::None, LockupPeriod::None, - delegate.pubkey(), ) .await .unwrap(); @@ -194,17 +190,15 @@ async fn test_voting() -> Result<(), TransportError> { .await .unwrap(); - let delegate = Keypair::new(); addin .create_deposit_entry( ®istrar, &voter2, - voter2_authority, + &voter2, &usdc_voting_mint, 1, LockupKind::None, LockupPeriod::None, - delegate.pubkey(), ) .await .unwrap();