diff --git a/packages/validator-bonds-sdk/generated/validator_bonds.ts b/packages/validator-bonds-sdk/generated/validator_bonds.ts index 112d6a66..19d62d4f 100644 --- a/packages/validator-bonds-sdk/generated/validator_bonds.ts +++ b/packages/validator-bonds-sdk/generated/validator_bonds.ts @@ -5,7 +5,7 @@ export type ValidatorBonds = { { "name": "PROGRAM_ID", "type": "string", - "value": "\"vbondsKbsC4QSLQQnn6ngZvkqfywn6KgEeQbkGSpk1V\"" + "value": "\"vBoNdEvzMrSai7is21XgVYik65mqtaKXuSdMBJ1xkW4\"" }, { "name": "BOND_SEED", @@ -122,11 +122,11 @@ export type ValidatorBonds = { "isSigner": false }, { - "name": "authority", + "name": "authorizedWithdrawer", "isMut": false, "isSigner": true, "docs": [ - "only the owner authority of the validator vote account can create the bond" + "only validator vote account withdrawer authority may can create the bond" ] }, { @@ -232,7 +232,7 @@ export type ValidatorBonds = { ] }, { - "name": "depositBond", + "name": "fundBond", "accounts": [ { "name": "config", @@ -272,7 +272,7 @@ export type ValidatorBonds = { ] }, { - "name": "bondsStakeAuthority", + "name": "bondsWithdrawerAuthority", "isMut": false, "isSigner": false, "docs": [ @@ -329,7 +329,7 @@ export type ValidatorBonds = { "args": [] }, { - "name": "createWithdrawRequest", + "name": "initWithdrawRequest", "accounts": [ { "name": "config", @@ -424,7 +424,7 @@ export type ValidatorBonds = { { "name": "createWithdrawRequestArgs", "type": { - "defined": "CreateWithdrawRequestArgs" + "defined": "InitWithdrawRequestArgs" } } ] @@ -505,7 +505,7 @@ export type ValidatorBonds = { "args": [] }, { - "name": "withdrawDeposit", + "name": "claimWithdrawRequest", "accounts": [ { "name": "config", @@ -696,29 +696,7 @@ export type ValidatorBonds = { { "name": "settlement", "isMut": true, - "isSigner": false, - "pda": { - "seeds": [ - { - "kind": "const", - "type": "string", - "value": "settlement_account" - }, - { - "kind": "account", - "type": "publicKey", - "account": "Bond", - "path": "bond" - }, - { - "kind": "arg", - "type": { - "defined": "InitSettlementArgs" - }, - "path": "params.merkle_root" - } - ] - } + "isSigner": false }, { "name": "operatorAuthority", @@ -814,11 +792,21 @@ export type ValidatorBonds = { "path": "bond" }, { - "kind": "arg", + "kind": "account", "type": { - "defined": "CloseSettlementArgs" + "array": [ + "u8", + 32 + ] }, - "path": "params.merkle_root" + "account": "Settlement", + "path": "settlement.merkle_root" + }, + { + "kind": "account", + "type": "u64", + "account": "Settlement", + "path": "settlement.epoch_created_at" } ] }, @@ -890,8 +878,8 @@ export type ValidatorBonds = { "name": "config", "isMut": false, "isSigner": false, - "docs": [ - "the config root account under which the bond was created" + "relations": [ + "operator_authority" ] }, { @@ -925,21 +913,70 @@ export type ValidatorBonds = { }, { "name": "settlement", + "isMut": true, + "isSigner": false, + "pda": { + "seeds": [ + { + "kind": "const", + "type": "string", + "value": "settlement_account" + }, + { + "kind": "account", + "type": "publicKey", + "account": "Bond", + "path": "bond" + }, + { + "kind": "account", + "type": { + "array": [ + "u8", + 32 + ] + }, + "account": "Settlement", + "path": "settlement.merkle_root" + }, + { + "kind": "account", + "type": "u64", + "account": "Settlement", + "path": "settlement.epoch_created_at" + } + ] + }, + "relations": [ + "bond", + "settlement_authority" + ] + }, + { + "name": "operatorAuthority", "isMut": false, - "isSigner": false + "isSigner": true, + "docs": [ + "operator signer authority is allowed to fund the settlement account", + "(making this operation permission-ed, at least for the first version of the contract)" + ] }, { "name": "stakeAccount", "isMut": true, "isSigner": false, "docs": [ - "stake account belonging to authority of the settlement" + "stake account to be funded into the settlement" ] }, { "name": "settlementAuthority", "isMut": false, "isSigner": false, + "docs": [ + "settlement stake authority to differentiate deposited and funded stake accounts", + "deposited has got bonds_withdrawer_authority, whilst funded has got the settlement authority" + ], "pda": { "seeds": [ { @@ -950,6 +987,7 @@ export type ValidatorBonds = { { "kind": "account", "type": "publicKey", + "account": "Settlement", "path": "settlement" } ] @@ -960,7 +998,7 @@ export type ValidatorBonds = { "isMut": false, "isSigner": false, "docs": [ - "authority that manages (owns being withdrawer authority) all stakes account under the bonds program" + "authority that manages (owns) all stakes account under the bonds program" ], "pda": { "seeds": [ @@ -978,6 +1016,30 @@ export type ValidatorBonds = { ] } }, + { + "name": "splitStakeAccount", + "isMut": true, + "isSigner": true, + "docs": [ + "a split stake account is needed when the provided stake_account is bigger than the settlement" + ] + }, + { + "name": "splitStakeRentPayer", + "isMut": true, + "isSigner": true, + "docs": [ + "This is an account used to prefund the split stake account.", + "If a split stake account is not needed then rent payer is fully refunded at the end of the transaction.", + "If a split stake account is created for the settlement, the payer needs to manually close the claim_settlement", + "instruction to get the rent back (success only when the stake account is already deactivated)." + ] + }, + { + "name": "systemProgram", + "isMut": false, + "isSigner": false + }, { "name": "stakeHistory", "isMut": false, @@ -988,6 +1050,11 @@ export type ValidatorBonds = { "isMut": false, "isSigner": false }, + { + "name": "rent", + "isMut": false, + "isSigner": false + }, { "name": "stakeProgram", "isMut": false, @@ -1089,6 +1156,12 @@ export type ValidatorBonds = { }, "account": "Settlement", "path": "settlement.merkle_root" + }, + { + "kind": "account", + "type": "u64", + "account": "Settlement", + "path": "settlement.epoch_created_at" } ] }, @@ -1121,14 +1194,14 @@ export type ValidatorBonds = { "type": { "defined": "ClaimSettlementArgs" }, - "path": "params.stake_authority" + "path": "params.staker" }, { "kind": "arg", "type": { "defined": "ClaimSettlementArgs" }, - "path": "params.withdraw_authority" + "path": "params.withdrawer" }, { "kind": "arg", @@ -1156,7 +1229,7 @@ export type ValidatorBonds = { ] }, { - "name": "withdrawAuthority", + "name": "withdrawerAuthority", "isMut": true, "isSigner": false, "docs": [ @@ -1332,28 +1405,14 @@ export type ValidatorBonds = { { "name": "settlementAuthority", "isMut": false, - "isSigner": false, - "pda": { - "seeds": [ - { - "kind": "const", - "type": "string", - "value": "settlement_authority" - }, - { - "kind": "account", - "type": "publicKey", - "path": "settlement" - } - ] - } + "isSigner": false }, { "name": "bondsWithdrawerAuthority", "isMut": false, "isSigner": false, "docs": [ - "authority that manages (owns being withdrawer authority) all stakes account under the bonds program" + "authority that owns (withdrawer authority) all stakes account under the bonds program" ], "pda": { "seeds": [ @@ -1371,11 +1430,21 @@ export type ValidatorBonds = { ] } }, + { + "name": "validatorVoteAccount", + "isMut": false, + "isSigner": false + }, { "name": "stakeHistory", "isMut": false, "isSigner": false }, + { + "name": "stakeConfig", + "isMut": false, + "isSigner": false + }, { "name": "clock", "isMut": false, @@ -1426,9 +1495,9 @@ export type ValidatorBonds = { "type": "publicKey" }, { - "name": "revenueShareConfig", + "name": "revenueShare", "docs": [ - "Revenue that is distributed from the bond to the protocol" + "Revenue that is distributed from the bond (from validator) to the protocol" ], "type": { "defined": "HundredthBasisPoint" @@ -1489,6 +1558,13 @@ export type ValidatorBonds = { ], "type": "u64" }, + { + "name": "minimumStakeLamports", + "docs": [ + "Minimum amount of lamports to be considered for a stake account operations (e.g., split)" + ], + "type": "u64" + }, { "name": "bondsWithdrawerAuthorityBump", "docs": [ @@ -1528,16 +1604,16 @@ export type ValidatorBonds = { "type": "publicKey" }, { - "name": "stakeAuthority", + "name": "stakerAuthority", "docs": [ - "stake authority as part of the merkle proof for this claim" + "staker authority as part of the merkle proof for this claim" ], "type": "publicKey" }, { - "name": "withdrawAuthority", + "name": "withdrawerAuthority", "docs": [ - "withdraw authority that has got permission to withdraw the claim" + "withdrawer authority that has got permission to withdraw the claim" ], "type": "publicKey" }, @@ -1585,7 +1661,7 @@ export type ValidatorBonds = { "name": "settlement", "docs": [ "Settlement account for a particular config and merkle root", - "Settlement defines an insurance event happens and it's needed to be settled" + "Settlement defines that a protected event happened and it will be settled" ], "type": { "kind": "struct", @@ -1721,13 +1797,6 @@ export type ValidatorBonds = { ], "type": "publicKey" }, - { - "name": "bump", - "docs": [ - "PDA account bump" - ], - "type": "u8" - }, { "name": "epoch", "docs": [ @@ -1749,6 +1818,13 @@ export type ValidatorBonds = { ], "type": "u64" }, + { + "name": "bump", + "docs": [ + "PDA account bump" + ], + "type": "u8" + }, { "name": "reserved", "docs": [ @@ -1879,7 +1955,7 @@ export type ValidatorBonds = { } }, { - "name": "revenueShareConfig", + "name": "revenueShare", "type": { "option": { "defined": "HundredthBasisPoint" @@ -1899,7 +1975,7 @@ export type ValidatorBonds = { "type": "publicKey" }, { - "name": "revenueShareConfig", + "name": "revenueShare", "type": { "defined": "HundredthBasisPoint" } @@ -1913,13 +1989,13 @@ export type ValidatorBonds = { "kind": "struct", "fields": [ { - "name": "adminAuthority", + "name": "admin", "type": { "option": "publicKey" } }, { - "name": "operatorAuthority", + "name": "operator", "type": { "option": "publicKey" } @@ -1935,6 +2011,12 @@ export type ValidatorBonds = { "type": { "option": "u64" } + }, + { + "name": "minimumStakeLamports", + "type": { + "option": "u64" + } } ] } @@ -1984,11 +2066,14 @@ export type ValidatorBonds = { } }, { - "name": "stakeAuthority", + "name": "staker", "type": "publicKey" }, { - "name": "withdrawAuthority", + "name": "withdrawer", + "docs": [ + "claim holder, withdrawer_authority" + ], "type": "publicKey" }, { @@ -2002,23 +2087,6 @@ export type ValidatorBonds = { ] } }, - { - "name": "CloseSettlementArgs", - "type": { - "kind": "struct", - "fields": [ - { - "name": "merkleRoot", - "type": { - "array": [ - "u8", - 32 - ] - } - } - ] - } - }, { "name": "InitSettlementArgs", "type": { @@ -2065,19 +2133,7 @@ export type ValidatorBonds = { } }, { - "name": "ResetStateArgs", - "type": { - "kind": "struct", - "fields": [ - { - "name": "settlementStakeAuthorityBump", - "type": "u8" - } - ] - } - }, - { - "name": "CreateWithdrawRequestArgs", + "name": "InitWithdrawRequestArgs", "type": { "kind": "struct", "fields": [ @@ -2164,7 +2220,7 @@ export type ValidatorBonds = { "index": false }, { - "name": "revenueShareConfig", + "name": "revenueShare", "type": { "defined": "HundredthBasisPoint" }, @@ -2190,7 +2246,7 @@ export type ValidatorBonds = { "index": false }, { - "name": "revenueShareConfig", + "name": "revenueShare", "type": { "option": { "defined": "HundrethBasisPointChange" @@ -2219,7 +2275,7 @@ export type ValidatorBonds = { "index": false }, { - "name": "revenueShareConfig", + "name": "revenueShare", "type": { "defined": "HundredthBasisPoint" }, @@ -2285,6 +2341,11 @@ export type ValidatorBonds = { "type": "u64", "index": false }, + { + "name": "minimumStakeLamports", + "type": "u64", + "index": false + }, { "name": "bondsWithdrawerAuthority", "type": "publicKey", @@ -2327,6 +2388,15 @@ export type ValidatorBonds = { }, "index": false }, + { + "name": "minimumStakeLamports", + "type": { + "option": { + "defined": "U64ValueChange" + } + }, + "index": false + }, { "name": "withdrawLockupEpochs", "type": { @@ -2352,12 +2422,12 @@ export type ValidatorBonds = { "index": false }, { - "name": "stakeAuthority", + "name": "stakerAuthority", "type": "publicKey", "index": false }, { - "name": "withdrawAuthority", + "name": "withdrawerAuthority", "type": "publicKey", "index": false }, @@ -2659,6 +2729,11 @@ export type ValidatorBonds = { "type": "publicKey", "index": false }, + { + "name": "validatorVoteAcount", + "type": "publicKey", + "index": false + }, { "name": "settlementAuthority", "type": "publicKey", @@ -2946,7 +3021,7 @@ export type ValidatorBonds = { }, { "code": 6031, - "name": "ClaimAmountExceedsMaxNumNodes", + "name": "ClaimCountExceedsMaxNumNodes", "msg": "Claim exceeded number of claimable nodes in the merkle tree" }, { @@ -3024,7 +3099,7 @@ export const IDL: ValidatorBonds = { { "name": "PROGRAM_ID", "type": "string", - "value": "\"vbondsKbsC4QSLQQnn6ngZvkqfywn6KgEeQbkGSpk1V\"" + "value": "\"vBoNdEvzMrSai7is21XgVYik65mqtaKXuSdMBJ1xkW4\"" }, { "name": "BOND_SEED", @@ -3141,11 +3216,11 @@ export const IDL: ValidatorBonds = { "isSigner": false }, { - "name": "authority", + "name": "authorizedWithdrawer", "isMut": false, "isSigner": true, "docs": [ - "only the owner authority of the validator vote account can create the bond" + "only validator vote account withdrawer authority may can create the bond" ] }, { @@ -3251,7 +3326,7 @@ export const IDL: ValidatorBonds = { ] }, { - "name": "depositBond", + "name": "fundBond", "accounts": [ { "name": "config", @@ -3291,7 +3366,7 @@ export const IDL: ValidatorBonds = { ] }, { - "name": "bondsStakeAuthority", + "name": "bondsWithdrawerAuthority", "isMut": false, "isSigner": false, "docs": [ @@ -3348,7 +3423,7 @@ export const IDL: ValidatorBonds = { "args": [] }, { - "name": "createWithdrawRequest", + "name": "initWithdrawRequest", "accounts": [ { "name": "config", @@ -3443,7 +3518,7 @@ export const IDL: ValidatorBonds = { { "name": "createWithdrawRequestArgs", "type": { - "defined": "CreateWithdrawRequestArgs" + "defined": "InitWithdrawRequestArgs" } } ] @@ -3524,7 +3599,7 @@ export const IDL: ValidatorBonds = { "args": [] }, { - "name": "withdrawDeposit", + "name": "claimWithdrawRequest", "accounts": [ { "name": "config", @@ -3715,29 +3790,7 @@ export const IDL: ValidatorBonds = { { "name": "settlement", "isMut": true, - "isSigner": false, - "pda": { - "seeds": [ - { - "kind": "const", - "type": "string", - "value": "settlement_account" - }, - { - "kind": "account", - "type": "publicKey", - "account": "Bond", - "path": "bond" - }, - { - "kind": "arg", - "type": { - "defined": "InitSettlementArgs" - }, - "path": "params.merkle_root" - } - ] - } + "isSigner": false }, { "name": "operatorAuthority", @@ -3833,11 +3886,21 @@ export const IDL: ValidatorBonds = { "path": "bond" }, { - "kind": "arg", + "kind": "account", "type": { - "defined": "CloseSettlementArgs" + "array": [ + "u8", + 32 + ] }, - "path": "params.merkle_root" + "account": "Settlement", + "path": "settlement.merkle_root" + }, + { + "kind": "account", + "type": "u64", + "account": "Settlement", + "path": "settlement.epoch_created_at" } ] }, @@ -3909,8 +3972,8 @@ export const IDL: ValidatorBonds = { "name": "config", "isMut": false, "isSigner": false, - "docs": [ - "the config root account under which the bond was created" + "relations": [ + "operator_authority" ] }, { @@ -3944,21 +4007,70 @@ export const IDL: ValidatorBonds = { }, { "name": "settlement", + "isMut": true, + "isSigner": false, + "pda": { + "seeds": [ + { + "kind": "const", + "type": "string", + "value": "settlement_account" + }, + { + "kind": "account", + "type": "publicKey", + "account": "Bond", + "path": "bond" + }, + { + "kind": "account", + "type": { + "array": [ + "u8", + 32 + ] + }, + "account": "Settlement", + "path": "settlement.merkle_root" + }, + { + "kind": "account", + "type": "u64", + "account": "Settlement", + "path": "settlement.epoch_created_at" + } + ] + }, + "relations": [ + "bond", + "settlement_authority" + ] + }, + { + "name": "operatorAuthority", "isMut": false, - "isSigner": false + "isSigner": true, + "docs": [ + "operator signer authority is allowed to fund the settlement account", + "(making this operation permission-ed, at least for the first version of the contract)" + ] }, { "name": "stakeAccount", "isMut": true, "isSigner": false, "docs": [ - "stake account belonging to authority of the settlement" + "stake account to be funded into the settlement" ] }, { "name": "settlementAuthority", "isMut": false, "isSigner": false, + "docs": [ + "settlement stake authority to differentiate deposited and funded stake accounts", + "deposited has got bonds_withdrawer_authority, whilst funded has got the settlement authority" + ], "pda": { "seeds": [ { @@ -3969,6 +4081,7 @@ export const IDL: ValidatorBonds = { { "kind": "account", "type": "publicKey", + "account": "Settlement", "path": "settlement" } ] @@ -3979,7 +4092,7 @@ export const IDL: ValidatorBonds = { "isMut": false, "isSigner": false, "docs": [ - "authority that manages (owns being withdrawer authority) all stakes account under the bonds program" + "authority that manages (owns) all stakes account under the bonds program" ], "pda": { "seeds": [ @@ -3997,6 +4110,30 @@ export const IDL: ValidatorBonds = { ] } }, + { + "name": "splitStakeAccount", + "isMut": true, + "isSigner": true, + "docs": [ + "a split stake account is needed when the provided stake_account is bigger than the settlement" + ] + }, + { + "name": "splitStakeRentPayer", + "isMut": true, + "isSigner": true, + "docs": [ + "This is an account used to prefund the split stake account.", + "If a split stake account is not needed then rent payer is fully refunded at the end of the transaction.", + "If a split stake account is created for the settlement, the payer needs to manually close the claim_settlement", + "instruction to get the rent back (success only when the stake account is already deactivated)." + ] + }, + { + "name": "systemProgram", + "isMut": false, + "isSigner": false + }, { "name": "stakeHistory", "isMut": false, @@ -4007,6 +4144,11 @@ export const IDL: ValidatorBonds = { "isMut": false, "isSigner": false }, + { + "name": "rent", + "isMut": false, + "isSigner": false + }, { "name": "stakeProgram", "isMut": false, @@ -4108,6 +4250,12 @@ export const IDL: ValidatorBonds = { }, "account": "Settlement", "path": "settlement.merkle_root" + }, + { + "kind": "account", + "type": "u64", + "account": "Settlement", + "path": "settlement.epoch_created_at" } ] }, @@ -4140,14 +4288,14 @@ export const IDL: ValidatorBonds = { "type": { "defined": "ClaimSettlementArgs" }, - "path": "params.stake_authority" + "path": "params.staker" }, { "kind": "arg", "type": { "defined": "ClaimSettlementArgs" }, - "path": "params.withdraw_authority" + "path": "params.withdrawer" }, { "kind": "arg", @@ -4175,7 +4323,7 @@ export const IDL: ValidatorBonds = { ] }, { - "name": "withdrawAuthority", + "name": "withdrawerAuthority", "isMut": true, "isSigner": false, "docs": [ @@ -4351,28 +4499,14 @@ export const IDL: ValidatorBonds = { { "name": "settlementAuthority", "isMut": false, - "isSigner": false, - "pda": { - "seeds": [ - { - "kind": "const", - "type": "string", - "value": "settlement_authority" - }, - { - "kind": "account", - "type": "publicKey", - "path": "settlement" - } - ] - } + "isSigner": false }, { "name": "bondsWithdrawerAuthority", "isMut": false, "isSigner": false, "docs": [ - "authority that manages (owns being withdrawer authority) all stakes account under the bonds program" + "authority that owns (withdrawer authority) all stakes account under the bonds program" ], "pda": { "seeds": [ @@ -4390,11 +4524,21 @@ export const IDL: ValidatorBonds = { ] } }, + { + "name": "validatorVoteAccount", + "isMut": false, + "isSigner": false + }, { "name": "stakeHistory", "isMut": false, "isSigner": false }, + { + "name": "stakeConfig", + "isMut": false, + "isSigner": false + }, { "name": "clock", "isMut": false, @@ -4445,9 +4589,9 @@ export const IDL: ValidatorBonds = { "type": "publicKey" }, { - "name": "revenueShareConfig", + "name": "revenueShare", "docs": [ - "Revenue that is distributed from the bond to the protocol" + "Revenue that is distributed from the bond (from validator) to the protocol" ], "type": { "defined": "HundredthBasisPoint" @@ -4508,6 +4652,13 @@ export const IDL: ValidatorBonds = { ], "type": "u64" }, + { + "name": "minimumStakeLamports", + "docs": [ + "Minimum amount of lamports to be considered for a stake account operations (e.g., split)" + ], + "type": "u64" + }, { "name": "bondsWithdrawerAuthorityBump", "docs": [ @@ -4547,16 +4698,16 @@ export const IDL: ValidatorBonds = { "type": "publicKey" }, { - "name": "stakeAuthority", + "name": "stakerAuthority", "docs": [ - "stake authority as part of the merkle proof for this claim" + "staker authority as part of the merkle proof for this claim" ], "type": "publicKey" }, { - "name": "withdrawAuthority", + "name": "withdrawerAuthority", "docs": [ - "withdraw authority that has got permission to withdraw the claim" + "withdrawer authority that has got permission to withdraw the claim" ], "type": "publicKey" }, @@ -4604,7 +4755,7 @@ export const IDL: ValidatorBonds = { "name": "settlement", "docs": [ "Settlement account for a particular config and merkle root", - "Settlement defines an insurance event happens and it's needed to be settled" + "Settlement defines that a protected event happened and it will be settled" ], "type": { "kind": "struct", @@ -4740,13 +4891,6 @@ export const IDL: ValidatorBonds = { ], "type": "publicKey" }, - { - "name": "bump", - "docs": [ - "PDA account bump" - ], - "type": "u8" - }, { "name": "epoch", "docs": [ @@ -4768,6 +4912,13 @@ export const IDL: ValidatorBonds = { ], "type": "u64" }, + { + "name": "bump", + "docs": [ + "PDA account bump" + ], + "type": "u8" + }, { "name": "reserved", "docs": [ @@ -4898,7 +5049,7 @@ export const IDL: ValidatorBonds = { } }, { - "name": "revenueShareConfig", + "name": "revenueShare", "type": { "option": { "defined": "HundredthBasisPoint" @@ -4918,7 +5069,7 @@ export const IDL: ValidatorBonds = { "type": "publicKey" }, { - "name": "revenueShareConfig", + "name": "revenueShare", "type": { "defined": "HundredthBasisPoint" } @@ -4932,13 +5083,13 @@ export const IDL: ValidatorBonds = { "kind": "struct", "fields": [ { - "name": "adminAuthority", + "name": "admin", "type": { "option": "publicKey" } }, { - "name": "operatorAuthority", + "name": "operator", "type": { "option": "publicKey" } @@ -4954,6 +5105,12 @@ export const IDL: ValidatorBonds = { "type": { "option": "u64" } + }, + { + "name": "minimumStakeLamports", + "type": { + "option": "u64" + } } ] } @@ -5003,11 +5160,14 @@ export const IDL: ValidatorBonds = { } }, { - "name": "stakeAuthority", + "name": "staker", "type": "publicKey" }, { - "name": "withdrawAuthority", + "name": "withdrawer", + "docs": [ + "claim holder, withdrawer_authority" + ], "type": "publicKey" }, { @@ -5021,23 +5181,6 @@ export const IDL: ValidatorBonds = { ] } }, - { - "name": "CloseSettlementArgs", - "type": { - "kind": "struct", - "fields": [ - { - "name": "merkleRoot", - "type": { - "array": [ - "u8", - 32 - ] - } - } - ] - } - }, { "name": "InitSettlementArgs", "type": { @@ -5084,19 +5227,7 @@ export const IDL: ValidatorBonds = { } }, { - "name": "ResetStateArgs", - "type": { - "kind": "struct", - "fields": [ - { - "name": "settlementStakeAuthorityBump", - "type": "u8" - } - ] - } - }, - { - "name": "CreateWithdrawRequestArgs", + "name": "InitWithdrawRequestArgs", "type": { "kind": "struct", "fields": [ @@ -5183,7 +5314,7 @@ export const IDL: ValidatorBonds = { "index": false }, { - "name": "revenueShareConfig", + "name": "revenueShare", "type": { "defined": "HundredthBasisPoint" }, @@ -5209,7 +5340,7 @@ export const IDL: ValidatorBonds = { "index": false }, { - "name": "revenueShareConfig", + "name": "revenueShare", "type": { "option": { "defined": "HundrethBasisPointChange" @@ -5238,7 +5369,7 @@ export const IDL: ValidatorBonds = { "index": false }, { - "name": "revenueShareConfig", + "name": "revenueShare", "type": { "defined": "HundredthBasisPoint" }, @@ -5304,6 +5435,11 @@ export const IDL: ValidatorBonds = { "type": "u64", "index": false }, + { + "name": "minimumStakeLamports", + "type": "u64", + "index": false + }, { "name": "bondsWithdrawerAuthority", "type": "publicKey", @@ -5346,6 +5482,15 @@ export const IDL: ValidatorBonds = { }, "index": false }, + { + "name": "minimumStakeLamports", + "type": { + "option": { + "defined": "U64ValueChange" + } + }, + "index": false + }, { "name": "withdrawLockupEpochs", "type": { @@ -5371,12 +5516,12 @@ export const IDL: ValidatorBonds = { "index": false }, { - "name": "stakeAuthority", + "name": "stakerAuthority", "type": "publicKey", "index": false }, { - "name": "withdrawAuthority", + "name": "withdrawerAuthority", "type": "publicKey", "index": false }, @@ -5678,6 +5823,11 @@ export const IDL: ValidatorBonds = { "type": "publicKey", "index": false }, + { + "name": "validatorVoteAcount", + "type": "publicKey", + "index": false + }, { "name": "settlementAuthority", "type": "publicKey", @@ -5965,7 +6115,7 @@ export const IDL: ValidatorBonds = { }, { "code": 6031, - "name": "ClaimAmountExceedsMaxNumNodes", + "name": "ClaimCountExceedsMaxNumNodes", "msg": "Claim exceeded number of claimable nodes in the merkle tree" }, { diff --git a/programs/validator-bonds/Cargo.toml b/programs/validator-bonds/Cargo.toml index 7dcf922d..70cd8fea 100644 --- a/programs/validator-bonds/Cargo.toml +++ b/programs/validator-bonds/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "validator-bonds" version = "0.1.0" -description = "Marinade validator bonds program to insure validator behavior" +description = "Marinade validator bonds program protecting validator behavior" edition = "2021" license = "Apache-2.0" authors = ["Marinade.Finance"] diff --git a/programs/validator-bonds/src/checks.rs b/programs/validator-bonds/src/checks.rs index f00951df..436574e9 100644 --- a/programs/validator-bonds/src/checks.rs +++ b/programs/validator-bonds/src/checks.rs @@ -6,31 +6,49 @@ use anchor_lang::require_keys_eq; use anchor_lang::solana_program::stake::state::{Delegation, Meta, Stake}; use anchor_lang::solana_program::stake_history::{Epoch, StakeHistoryEntry}; use anchor_lang::solana_program::vote::program::id as vote_program_id; -use anchor_lang::solana_program::vote::state::VoteState; use anchor_spl::stake::StakeAccount; use std::ops::Deref; -/// Verification the account is a vote account + matching owner (withdrawer authority) -pub fn check_validator_vote_account_owner( +/// Verification the account is owned by vote program + matching withdrawer authority (owner) +pub fn check_validator_vote_account_withdrawer_authority( validator_vote_account: &UncheckedAccount, expected_owner: &Pubkey, -) -> Result { +) -> Result<()> { require!( validator_vote_account.owner == &vote_program_id(), ErrorCode::InvalidVoteAccountProgramId ); let validator_vote_data = &validator_vote_account.data.borrow()[..]; - let vote_account = VoteState::deserialize(validator_vote_data).map_err(|err| { - msg!("Cannot deserialize vote account: {:?}", err); - error!(ErrorCode::FailedToDeserializeVoteAccount) - .with_values(("validator_vote_account", validator_vote_account.key())) - })?; + // let's find position of the authorized withdrawer within the vote state account data + // https://github.com/solana-labs/solana/pull/30515 + // https://github.com/solana-labs/solana/blob/v1.17.10/sdk/program/src/vote/state/mod.rs#L290 + let pos = 36; + if validator_vote_data.len() < pos + 32 { + msg!( + "Cannot get withdrawer authority from vote account {} data", + validator_vote_account.key + ); + return Err(ErrorCode::FailedToDeserializeVoteAccount.into()); + } + let withdrawer_slice: [u8; 32] = + validator_vote_data[pos..pos + 32] + .try_into() + .map_err(|err| { + msg!( + "Cannot get withdrawer authority from vote account {} data: {:?}", + validator_vote_account.key, + err + ); + error!(ErrorCode::FailedToDeserializeVoteAccount) + .with_values(("validator_vote_account", validator_vote_account.key())) + })?; + let authorized_withdrawer = Pubkey::from(withdrawer_slice); require_keys_eq!( *expected_owner, - vote_account.authorized_withdrawer, + authorized_withdrawer, ErrorCode::ValidatorVoteAccountOwnerMismatch ); - Ok(vote_account) + Ok(()) } /// Bond account change is permitted to bond authority or validator vote account owner @@ -42,7 +60,7 @@ pub fn check_bond_change_permitted( if authority == &bond_account.authority.key() { true } else { - check_validator_vote_account_owner(validator_vote_account, authority) + check_validator_vote_account_withdrawer_authority(validator_vote_account, authority) .map_or(false, |_| true) } } @@ -69,7 +87,7 @@ pub fn check_stake_valid_delegation( } } -pub fn check_stake_is_initialized_with_authority( +pub fn check_stake_is_initialized_with_withdrawer_authority( stake_account: &StakeAccount, authority: &Pubkey, stake_account_attribute_name: &str, @@ -98,11 +116,12 @@ pub fn check_stake_is_initialized_with_authority( pub fn check_stake_is_not_locked( stake_account: &StakeAccount, clock: &Clock, - custodian: Option<&Pubkey>, stake_account_attribute_name: &str, ) -> Result<()> { if let Some(stake_lockup) = stake_account.lockup() { - if stake_lockup.is_in_force(clock, custodian) { + // TODO: consider working with custodian, would need to be passed from the caller + // and on authorize withdrawer we would need to change the lockup to the new custodian + if stake_lockup.is_in_force(clock, None) { return Err(error!(ErrorCode::StakeLockedUp) .with_account_name(stake_account_attribute_name) .with_values(( @@ -180,7 +199,10 @@ mod tests { ); let wrong_owner_account = UncheckedAccount::try_from(&account); assert_eq!( - check_validator_vote_account_owner(&wrong_owner_account, &vote_init.authorized_voter,), + check_validator_vote_account_withdrawer_authority( + &wrong_owner_account, + &vote_init.authorized_voter, + ), Err(ErrorCode::InvalidVoteAccountProgramId.into()) ); @@ -197,14 +219,23 @@ mod tests { ); let unchecked_account = UncheckedAccount::try_from(&account); - check_validator_vote_account_owner(&unchecked_account, &vote_init.authorized_withdrawer) - .unwrap(); + check_validator_vote_account_withdrawer_authority( + &unchecked_account, + &vote_init.authorized_withdrawer, + ) + .unwrap(); assert_eq!( - check_validator_vote_account_owner(&unchecked_account, &vote_init.authorized_voter,), + check_validator_vote_account_withdrawer_authority( + &unchecked_account, + &vote_init.authorized_voter, + ), Err(ErrorCode::ValidatorVoteAccountOwnerMismatch.into()) ); assert_eq!( - check_validator_vote_account_owner(&unchecked_account, &Pubkey::default(),), + check_validator_vote_account_withdrawer_authority( + &unchecked_account, + &Pubkey::default(), + ), Err(ErrorCode::ValidatorVoteAccountOwnerMismatch.into()) ); } @@ -304,7 +335,7 @@ mod tests { pub fn stake_initialized_with_authority_check() { let uninitialized_stake_account = get_stake_account(StakeState::Uninitialized); assert_eq!( - check_stake_is_initialized_with_authority( + check_stake_is_initialized_with_withdrawer_authority( &uninitialized_stake_account, &Pubkey::default(), "" @@ -313,7 +344,7 @@ mod tests { ); let rewards_pool_stake_account = get_stake_account(StakeState::RewardsPool); assert_eq!( - check_stake_is_initialized_with_authority( + check_stake_is_initialized_with_withdrawer_authority( &rewards_pool_stake_account, &Pubkey::default(), "" @@ -323,7 +354,7 @@ mod tests { let initialized_stake_account = get_stake_account(StakeState::Initialized(Meta::default())); assert_eq!( - check_stake_is_initialized_with_authority( + check_stake_is_initialized_with_withdrawer_authority( &initialized_stake_account, &Pubkey::default(), "" @@ -333,7 +364,7 @@ mod tests { let default_delegated_stake_account = get_stake_account(StakeState::Stake(Meta::default(), Stake::default())); assert_eq!( - check_stake_is_initialized_with_authority( + check_stake_is_initialized_with_withdrawer_authority( &default_delegated_stake_account, &Pubkey::default(), "" @@ -347,7 +378,11 @@ mod tests { let delegated_stake_account = get_delegated_stake_account(None, Some(withdrawer), Some(staker)); assert_eq!( - check_stake_is_initialized_with_authority(&delegated_stake_account, &withdrawer, ""), + check_stake_is_initialized_with_withdrawer_authority( + &delegated_stake_account, + &withdrawer, + "" + ), Ok(Meta { authorized: Authorized { withdrawer, staker }, ..Meta::default() @@ -359,7 +394,7 @@ mod tests { let delegated_stake_account = get_delegated_stake_account(None, Some(withdrawer), Some(staker)); assert_eq!( - check_stake_is_initialized_with_authority( + check_stake_is_initialized_with_withdrawer_authority( &delegated_stake_account, &wrong_withdrawer, "" @@ -375,24 +410,24 @@ mod tests { // no lock on default stake account let unlocked_stake_account = get_stake_account(StakeState::Uninitialized); assert_eq!( - check_stake_is_not_locked(&unlocked_stake_account, &clock, None, ""), + check_stake_is_not_locked(&unlocked_stake_account, &clock, ""), Ok(()) ); let rewards_pool_stake_account = get_stake_account(StakeState::RewardsPool); assert_eq!( - check_stake_is_not_locked(&rewards_pool_stake_account, &clock, None, ""), + check_stake_is_not_locked(&rewards_pool_stake_account, &clock, ""), Ok(()) ); let initialized_stake_account = get_stake_account(StakeState::Initialized(Meta::default())); assert_eq!( - check_stake_is_not_locked(&initialized_stake_account, &clock, None, ""), + check_stake_is_not_locked(&initialized_stake_account, &clock, ""), Ok(()) ); let default_delegated_stake_account = get_stake_account(StakeState::Stake(Meta::default(), Stake::default())); assert_eq!( - check_stake_is_not_locked(&default_delegated_stake_account, &clock, None, ""), + check_stake_is_not_locked(&default_delegated_stake_account, &clock, ""), Ok(()) ); @@ -412,26 +447,15 @@ mod tests { assert!(clock.epoch > 0 && clock.unix_timestamp > 0); - // locked, wrong custodian - let wrong_custodian = Pubkey::new_unique(); + // locked assert_eq!( - check_stake_is_not_locked(&epoch_locked_stake_account, &clock, None, ""), + check_stake_is_not_locked(&epoch_locked_stake_account, &clock, ""), Err(ErrorCode::StakeLockedUp.into()) ); assert_eq!( - check_stake_is_not_locked( - &epoch_locked_stake_account, - &clock, - Some(&wrong_custodian), - "" - ), + check_stake_is_not_locked(&epoch_locked_stake_account, &clock, ""), Err(ErrorCode::StakeLockedUp.into()) ); - // locked, correct custodian - assert_eq!( - check_stake_is_not_locked(&epoch_locked_stake_account, &clock, Some(&custodian), ""), - Ok(()) - ); let unix_timestamp_lockup = Lockup { epoch: 0, @@ -446,7 +470,7 @@ mod tests { Stake::default(), )); assert_eq!( - check_stake_is_not_locked(&unix_locked_stake_account, &clock, None, ""), + check_stake_is_not_locked(&unix_locked_stake_account, &clock, ""), Err(ErrorCode::StakeLockedUp.into()) ); } diff --git a/programs/validator-bonds/src/constants.rs b/programs/validator-bonds/src/constants.rs index bd849b9d..fbeb2f97 100644 --- a/programs/validator-bonds/src/constants.rs +++ b/programs/validator-bonds/src/constants.rs @@ -1,7 +1,7 @@ use anchor_lang::prelude::*; #[constant] -pub const PROGRAM_ID: &str = "vbondsKbsC4QSLQQnn6ngZvkqfywn6KgEeQbkGSpk1V"; +pub const PROGRAM_ID: &str = "vBoNdEvzMrSai7is21XgVYik65mqtaKXuSdMBJ1xkW4"; // TODO: anchor-0.29: constants cannot be used in anchor #[Account] when seeds = true // https://github.com/coral-xyz/anchor/issues/2697 diff --git a/programs/validator-bonds/src/error.rs b/programs/validator-bonds/src/error.rs index ab2202fa..8497dc6f 100644 --- a/programs/validator-bonds/src/error.rs +++ b/programs/validator-bonds/src/error.rs @@ -96,7 +96,7 @@ pub enum ErrorCode { ClaimAmountExceedsMaxTotalClaim, // 6030 0x178e #[msg("Claim exceeded number of claimable nodes in the merkle tree")] - ClaimAmountExceedsMaxNumNodes, // 6031 0x178f + ClaimCountExceedsMaxNumNodes, // 6031 0x178f #[msg("Empty merkle tree, nothing to be claimed")] EmptySettlementMerkleTree, // 6032 0x1790 diff --git a/programs/validator-bonds/src/events/bond.rs b/programs/validator-bonds/src/events/bond.rs index 9f30babf..032c98c0 100644 --- a/programs/validator-bonds/src/events/bond.rs +++ b/programs/validator-bonds/src/events/bond.rs @@ -8,14 +8,14 @@ pub struct InitBondEvent { pub validator_vote_account: Pubkey, pub validator_vote_withdrawer: Pubkey, pub authority: Pubkey, - pub revenue_share_config: HundredthBasisPoint, + pub revenue_share: HundredthBasisPoint, pub bond_bump: u8, } #[event] pub struct ConfigureBondEvent { pub bond_authority: Option, - pub revenue_share_config: Option, + pub revenue_share: Option, } #[event] @@ -23,7 +23,7 @@ pub struct CloseBondEvent { pub config_address: Pubkey, pub validator_vote_account: Pubkey, pub authority: Pubkey, - pub revenue_share_config: HundredthBasisPoint, + pub revenue_share: HundredthBasisPoint, pub bump: u8, } diff --git a/programs/validator-bonds/src/events/config.rs b/programs/validator-bonds/src/events/config.rs index 3af42c7a..50ce81cf 100644 --- a/programs/validator-bonds/src/events/config.rs +++ b/programs/validator-bonds/src/events/config.rs @@ -7,6 +7,7 @@ pub struct InitConfigEvent { pub operator_authority: Pubkey, pub withdraw_lockup_epochs: u64, pub epochs_to_claim_settlement: u64, + pub minimum_stake_lamports: u64, pub bonds_withdrawer_authority: Pubkey, pub bonds_withdrawer_authority_bump: u8, } @@ -16,5 +17,6 @@ pub struct ConfigureConfigEvent { pub admin_authority: Option, pub operator_authority: Option, pub epochs_to_claim_settlement: Option, + pub minimum_stake_lamports: Option, pub withdraw_lockup_epochs: Option, } diff --git a/programs/validator-bonds/src/events/settlement_claim.rs b/programs/validator-bonds/src/events/settlement_claim.rs index 137e91f4..402e41fe 100644 --- a/programs/validator-bonds/src/events/settlement_claim.rs +++ b/programs/validator-bonds/src/events/settlement_claim.rs @@ -4,8 +4,8 @@ use anchor_lang::prelude::*; pub struct ClaimSettlementEvent { pub settlement: Pubkey, pub settlement_claim: Pubkey, - pub stake_authority: Pubkey, - pub withdraw_authority: Pubkey, + pub staker_authority: Pubkey, + pub withdrawer_authority: Pubkey, pub vote_account: Pubkey, pub claim: u64, pub rent_collector: Pubkey, diff --git a/programs/validator-bonds/src/events/stake.rs b/programs/validator-bonds/src/events/stake.rs index 5d523fb1..e4247014 100644 --- a/programs/validator-bonds/src/events/stake.rs +++ b/programs/validator-bonds/src/events/stake.rs @@ -17,6 +17,7 @@ pub struct ResetEvent { pub bond: Pubkey, pub settlement: Pubkey, pub stake_account: Pubkey, + pub validator_vote_acount: Pubkey, pub settlement_authority: Pubkey, pub bonds_withdrawer_authority: Pubkey, } diff --git a/programs/validator-bonds/src/instructions/bond/configure_bond.rs b/programs/validator-bonds/src/instructions/bond/configure_bond.rs index bbdfbcad..9aab580a 100644 --- a/programs/validator-bonds/src/instructions/bond/configure_bond.rs +++ b/programs/validator-bonds/src/instructions/bond/configure_bond.rs @@ -8,7 +8,7 @@ use anchor_lang::prelude::*; #[derive(AnchorDeserialize, AnchorSerialize)] pub struct ConfigureBondArgs { pub bond_authority: Option, - pub revenue_share_config: Option, + pub revenue_share: Option, } /// Change parameters of validator bond account @@ -40,7 +40,7 @@ impl<'info> ConfigureBond<'info> { &mut self, ConfigureBondArgs { bond_authority, - revenue_share_config, + revenue_share, }: ConfigureBondArgs, ) -> Result<()> { require!( @@ -60,10 +60,10 @@ impl<'info> ConfigureBond<'info> { new: authority, } }); - let revenue_share_config_change = match revenue_share_config { + let revenue_share_change = match revenue_share { Some(revenue) => { - let old = self.bond.revenue_share_config; - self.bond.revenue_share_config = revenue.check()?; + let old = self.bond.revenue_share; + self.bond.revenue_share = revenue.check()?; Some(HundrethBasisPointChange { old, new: revenue }) } None => None, @@ -71,7 +71,7 @@ impl<'info> ConfigureBond<'info> { emit!(ConfigureBondEvent { bond_authority: bond_authority_change, - revenue_share_config: revenue_share_config_change, + revenue_share: revenue_share_change, }); Ok(()) diff --git a/programs/validator-bonds/src/instructions/bond/deposit_bond.rs b/programs/validator-bonds/src/instructions/bond/fund_bond.rs similarity index 84% rename from programs/validator-bonds/src/instructions/bond/deposit_bond.rs rename to programs/validator-bonds/src/instructions/bond/fund_bond.rs index 83e9c67a..17cda313 100644 --- a/programs/validator-bonds/src/instructions/bond/deposit_bond.rs +++ b/programs/validator-bonds/src/instructions/bond/fund_bond.rs @@ -1,5 +1,5 @@ use crate::checks::{ - check_stake_exist_and_fully_activated, check_stake_is_initialized_with_authority, + check_stake_exist_and_fully_activated, check_stake_is_initialized_with_withdrawer_authority, check_stake_is_not_locked, check_stake_valid_delegation, }; use crate::error::ErrorCode; @@ -12,7 +12,7 @@ use anchor_spl::stake::{authorize, Authorize, Stake, StakeAccount}; /// Deposit stake account as validator bond record #[derive(Accounts)] -pub struct DepositBond<'info> { +pub struct FundBond<'info> { #[account()] config: Account<'info, Config>, @@ -38,7 +38,7 @@ pub struct DepositBond<'info> { ], bump = config.bonds_withdrawer_authority_bump, )] - bonds_stake_authority: UncheckedAccount<'info>, + bonds_withdrawer_authority: UncheckedAccount<'info>, /// stake account to be deposited #[account()] @@ -55,19 +55,14 @@ pub struct DepositBond<'info> { stake_program: Program<'info, Stake>, } -impl<'info> DepositBond<'info> { +impl<'info> FundBond<'info> { pub fn process(&mut self) -> Result<()> { - check_stake_is_initialized_with_authority( + check_stake_is_initialized_with_withdrawer_authority( &self.stake_account, &self.stake_authority.key(), "stake_account", )?; - check_stake_is_not_locked( - &self.stake_account, - &self.clock, - Some(self.stake_authority.key), - "stake_account", - )?; + check_stake_is_not_locked(&self.stake_account, &self.clock, "stake_account")?; check_stake_exist_and_fully_activated( &self.stake_account, self.clock.epoch, @@ -81,7 +76,7 @@ impl<'info> DepositBond<'info> { Authorize { stake: self.stake_account.to_account_info(), authorized: self.stake_authority.to_account_info(), - new_authorized: self.bonds_stake_authority.to_account_info(), + new_authorized: self.bonds_withdrawer_authority.to_account_info(), clock: self.clock.to_account_info(), }, ), @@ -95,11 +90,11 @@ impl<'info> DepositBond<'info> { Authorize { stake: self.stake_account.to_account_info(), authorized: self.stake_authority.to_account_info(), - new_authorized: self.bonds_stake_authority.to_account_info(), + new_authorized: self.bonds_withdrawer_authority.to_account_info(), clock: self.clock.to_account_info(), }, ), - // withdraw authority (owner) is the validator bonds program + // withdrawer authority (owner) is the validator bonds program StakeAuthorize::Withdrawer, None, )?; diff --git a/programs/validator-bonds/src/instructions/bond/init_bond.rs b/programs/validator-bonds/src/instructions/bond/init_bond.rs index ce0e2125..14e198ca 100644 --- a/programs/validator-bonds/src/instructions/bond/init_bond.rs +++ b/programs/validator-bonds/src/instructions/bond/init_bond.rs @@ -1,4 +1,4 @@ -use crate::checks::check_validator_vote_account_owner; +use crate::checks::check_validator_vote_account_withdrawer_authority; use crate::error::ErrorCode; use crate::events::bond::InitBondEvent; use crate::state::bond::Bond; @@ -12,7 +12,7 @@ use anchor_lang::solana_program::vote::program::ID as vote_program_id; #[derive(AnchorDeserialize, AnchorSerialize)] pub struct InitBondArgs { pub bond_authority: Pubkey, - pub revenue_share_config: HundredthBasisPoint, + pub revenue_share: HundredthBasisPoint, } /// Creates new validator bond account based on the validator vote address @@ -28,9 +28,9 @@ pub struct InitBond<'info> { )] validator_vote_account: UncheckedAccount<'info>, - /// only the owner authority of the validator vote account can create the bond + /// only validator vote account withdrawer authority may can create the bond #[account()] - authority: Signer<'info>, + authorized_withdrawer: Signer<'info>, #[account( init, @@ -60,27 +60,30 @@ impl<'info> InitBond<'info> { &mut self, InitBondArgs { bond_authority, - revenue_share_config, + revenue_share, }: InitBondArgs, bond_bump: u8, ) -> Result<()> { // verification of the validator vote account - check_validator_vote_account_owner(&self.validator_vote_account, &self.authority.key())?; + check_validator_vote_account_withdrawer_authority( + &self.validator_vote_account, + &self.authorized_withdrawer.key(), + )?; self.bond.set_inner(Bond { config: self.config.key(), validator_vote_account: self.validator_vote_account.key(), authority: bond_authority, - revenue_share_config: revenue_share_config.check()?, + revenue_share: revenue_share.check()?, bump: bond_bump, reserved: Reserved150::default(), }); emit!(InitBondEvent { config_address: self.bond.config, validator_vote_account: self.bond.validator_vote_account, - validator_vote_withdrawer: self.authority.key(), + validator_vote_withdrawer: self.authorized_withdrawer.key(), authority: self.bond.authority, - revenue_share_config: self.bond.revenue_share_config, + revenue_share: self.bond.revenue_share, bond_bump: self.bond.bump, }); diff --git a/programs/validator-bonds/src/instructions/bond/mod.rs b/programs/validator-bonds/src/instructions/bond/mod.rs index cdc3ecdc..fccbaeba 100644 --- a/programs/validator-bonds/src/instructions/bond/mod.rs +++ b/programs/validator-bonds/src/instructions/bond/mod.rs @@ -1,8 +1,8 @@ pub mod init_bond; pub mod configure_bond; -pub mod deposit_bond; +pub mod fund_bond; pub use configure_bond::*; -pub use deposit_bond::*; +pub use fund_bond::*; pub use init_bond::*; diff --git a/programs/validator-bonds/src/instructions/config/configure_config.rs b/programs/validator-bonds/src/instructions/config/configure_config.rs index 76f57990..88bb67f5 100644 --- a/programs/validator-bonds/src/instructions/config/configure_config.rs +++ b/programs/validator-bonds/src/instructions/config/configure_config.rs @@ -5,10 +5,11 @@ use anchor_lang::prelude::*; #[derive(AnchorDeserialize, AnchorSerialize)] pub struct ConfigureConfigArgs { - pub admin_authority: Option, - pub operator_authority: Option, + pub admin: Option, + pub operator: Option, pub epochs_to_claim_settlement: Option, pub withdraw_lockup_epochs: Option, + pub minimum_stake_lamports: Option, } /// Configures bond program with the config root account params @@ -30,19 +31,20 @@ impl<'info> ConfigureConfig<'info> { pub fn process( &mut self, ConfigureConfigArgs { - admin_authority, - operator_authority, + admin, + operator, epochs_to_claim_settlement, withdraw_lockup_epochs, + minimum_stake_lamports, }: ConfigureConfigArgs, ) -> Result<()> { - let admin_authority_change = admin_authority.map(|admin| { + let admin_authority_change = admin.map(|admin| { let old = self.config.admin_authority; self.config.admin_authority = admin; PubkeyValueChange { old, new: admin } }); - let operator_authority_change = operator_authority.map(|operator| { + let operator_authority_change = operator.map(|operator| { let old = self.config.operator_authority; self.config.operator_authority = operator; PubkeyValueChange { old, new: operator } @@ -67,11 +69,21 @@ impl<'info> ConfigureConfig<'info> { } }); + let minimum_stake_lamports_change = minimum_stake_lamports.map(|minimum_stake| { + let old = self.config.minimum_stake_lamports; + self.config.minimum_stake_lamports = minimum_stake; + U64ValueChange { + old, + new: minimum_stake, + } + }); + emit!(ConfigureConfigEvent { admin_authority: admin_authority_change, operator_authority: operator_authority_change, epochs_to_claim_settlement: epochs_to_claim_settlement_change, withdraw_lockup_epochs: withdraw_lockup_epochs_change, + minimum_stake_lamports: minimum_stake_lamports_change, }); Ok(()) diff --git a/programs/validator-bonds/src/instructions/config/init_config.rs b/programs/validator-bonds/src/instructions/config/init_config.rs index 6b37dc38..1eed3d1e 100644 --- a/programs/validator-bonds/src/instructions/config/init_config.rs +++ b/programs/validator-bonds/src/instructions/config/init_config.rs @@ -1,3 +1,4 @@ +use crate::constants::MIN_STAKE_LAMPORTS; use crate::events::config::InitConfigEvent; use crate::state::config::{find_bonds_withdrawer_authority, Config}; use anchor_lang::prelude::*; @@ -42,9 +43,6 @@ impl<'info> InitConfig<'info> { withdraw_lockup_epochs, }: InitConfigArgs, ) -> Result<()> { - // TODO: are there some limitations about values for claim_settlement and withdraw_lockup? - - // TODO: may we use just first bump, ignoring to save the bump in the account, without a danger? let (bonds_withdrawer_authority, bonds_withdrawer_authority_bump) = find_bonds_withdrawer_authority(&self.config.key()); self.config.set_inner(Config { @@ -52,6 +50,7 @@ impl<'info> InitConfig<'info> { operator_authority, epochs_to_claim_settlement, withdraw_lockup_epochs, + minimum_stake_lamports: MIN_STAKE_LAMPORTS, bonds_withdrawer_authority_bump, reserved: [0; 512], }); @@ -61,6 +60,7 @@ impl<'info> InitConfig<'info> { operator_authority: self.config.operator_authority, epochs_to_claim_settlement: self.config.epochs_to_claim_settlement, withdraw_lockup_epochs: self.config.withdraw_lockup_epochs, + minimum_stake_lamports: self.config.minimum_stake_lamports, bonds_withdrawer_authority_bump: self.config.bonds_withdrawer_authority_bump, bonds_withdrawer_authority, }); diff --git a/programs/validator-bonds/src/instructions/settlement/claim_settlement.rs b/programs/validator-bonds/src/instructions/settlement/claim_settlement.rs index 9e3a9962..6a5ab2f7 100644 --- a/programs/validator-bonds/src/instructions/settlement/claim_settlement.rs +++ b/programs/validator-bonds/src/instructions/settlement/claim_settlement.rs @@ -1,4 +1,6 @@ -use crate::checks::{check_stake_is_initialized_with_authority, check_stake_valid_delegation}; +use crate::checks::{ + check_stake_is_initialized_with_withdrawer_authority, check_stake_valid_delegation, +}; use crate::constants::BONDS_AUTHORITY_SEED; use crate::error::ErrorCode; use crate::events::settlement_claim::ClaimSettlementEvent; @@ -17,8 +19,10 @@ use anchor_spl::stake::{withdraw, Stake, StakeAccount, Withdraw}; pub struct ClaimSettlementArgs { pub amount: u64, pub proof: Vec<[u8; 32]>, - pub stake_authority: Pubkey, - pub withdraw_authority: Pubkey, // claim holder + // staker authority + pub staker: Pubkey, + /// claim holder, withdrawer_authority + pub withdrawer: Pubkey, pub vote_account: Pubkey, pub claim: u64, } @@ -50,6 +54,7 @@ pub struct ClaimSettlement<'info> { b"settlement_account", bond.key().as_ref(), settlement.merkle_root.as_ref(), + settlement.epoch_created_at.to_le_bytes().as_ref(), ], bump = settlement.bumps.pda, )] @@ -63,8 +68,8 @@ pub struct ClaimSettlement<'info> { seeds = [ b"claim_account", settlement.key().as_ref(), - params.stake_authority.as_ref(), - params.withdraw_authority.as_ref(), + params.staker.as_ref(), + params.withdrawer.as_ref(), params.vote_account.as_ref(), params.claim.to_le_bytes().as_ref(), ], @@ -80,9 +85,9 @@ pub struct ClaimSettlement<'info> { /// account that will receive the funds on this claim #[account( mut, - constraint = params.withdraw_authority == withdraw_authority.key(), + constraint = params.withdrawer == withdrawer_authority.key(), )] - withdraw_authority: UncheckedAccount<'info>, + withdrawer_authority: UncheckedAccount<'info>, /// CHECK: PDA /// authority that manages (owns == being withdrawer authority) all stakes account under the bonds program @@ -120,8 +125,8 @@ impl<'info> ClaimSettlement<'info> { ClaimSettlementArgs { amount, proof, - stake_authority, - withdraw_authority, + staker: staker_authority, + withdrawer: withdrawer_authority, vote_account, claim, }: ClaimSettlementArgs, @@ -133,21 +138,21 @@ impl<'info> ClaimSettlement<'info> { .with_values(("max_total_claim", self.settlement.max_total_claim))); } if self.settlement.num_nodes_claimed + 1 > self.settlement.max_num_nodes { - return Err(error!(ErrorCode::ClaimAmountExceedsMaxNumNodes) + return Err(error!(ErrorCode::ClaimCountExceedsMaxNumNodes) .with_values(("settlement", self.settlement.key())) .with_values(("num_nodes_claimed", self.settlement.num_nodes_claimed)) .with_values(("max_num_nodes", self.settlement.max_num_nodes))); } // stake account is managed by bonds program - let stake_meta = check_stake_is_initialized_with_authority( + let stake_meta = check_stake_is_initialized_with_withdrawer_authority( &self.stake_account, &self.bonds_withdrawer_authority.key(), "stake_account", )?; // stake account is delegated (deposited by) the bond validator check_stake_valid_delegation(&self.stake_account, &self.bond.validator_vote_account)?; - // provided stake account must be funded + // provided stake account must be funded; staker == settlement staker authority require_keys_eq!( stake_meta.authorized.staker, self.settlement.settlement_authority, @@ -157,31 +162,33 @@ impl<'info> ClaimSettlement<'info> { // provided stake account has to be big enough to cover the claim and still be valid to exist // it's responsibility of the SDK to merge the stake accounts if needed // - the invariant here is that the stake account will be always rent exempt + min size - // this has to be ensured by fund_settlement instruction) - if self.stake_account.get_lamports() < amount + minimal_size_stake_account(&stake_meta) { + // this has to be ensured by fund_settlement instruction + if self.stake_account.get_lamports() + < amount + minimal_size_stake_account(&stake_meta, &self.config) + { return Err(error!(ErrorCode::ClaimingStakeAccountLamportsInsufficient) .with_account_name("stake_account") .with_values(("stake_account_lamports", self.stake_account.get_lamports())) .with_values(("claiming_amount", amount)) .with_values(( "minimal_size_stake_account", - minimal_size_stake_account(&stake_meta), + minimal_size_stake_account(&stake_meta, &self.config), ))); } let merkle_tree_node = - merkle_proof::tree_node(stake_authority, withdraw_authority, vote_account, claim); + merkle_proof::tree_node(staker_authority, withdrawer_authority, vote_account, claim); if !merkle_proof::verify(proof, self.settlement.merkle_root, merkle_tree_node) { return Err(error!(ErrorCode::ClaimSettlementProofFailed) .with_values(("claiming_amount", amount)) - .with_values(("withdraw_authority", withdraw_authority.key()))); + .with_values(("withdrawer_authority", withdrawer_authority.key()))); } self.settlement_claim.set_inner(SettlementClaim { settlement: self.settlement.key(), - stake_authority, - withdraw_authority, + staker_authority, + withdrawer_authority, vote_account, claim, bump: settlement_claim_bump, @@ -195,7 +202,7 @@ impl<'info> ClaimSettlement<'info> { Withdraw { stake: self.stake_account.to_account_info(), withdrawer: self.bonds_withdrawer_authority.to_account_info(), - to: self.withdraw_authority.to_account_info(), + to: self.withdrawer_authority.to_account_info(), clock: self.clock.to_account_info(), stake_history: self.stake_history.to_account_info(), }, @@ -215,9 +222,9 @@ impl<'info> ClaimSettlement<'info> { emit!(ClaimSettlementEvent { settlement: self.settlement_claim.settlement, settlement_claim: self.settlement_claim.key(), - stake_authority: self.settlement_claim.stake_authority, + staker_authority: self.settlement_claim.staker_authority, vote_account: self.settlement_claim.vote_account, - withdraw_authority: self.settlement_claim.withdraw_authority, + withdrawer_authority: self.settlement_claim.withdrawer_authority, claim: self.settlement_claim.claim, rent_collector: self.settlement_claim.rent_collector, bump: settlement_claim_bump, diff --git a/programs/validator-bonds/src/instructions/settlement/close_settlement.rs b/programs/validator-bonds/src/instructions/settlement/close_settlement.rs index 0eb98a56..56a57018 100644 --- a/programs/validator-bonds/src/instructions/settlement/close_settlement.rs +++ b/programs/validator-bonds/src/instructions/settlement/close_settlement.rs @@ -1,4 +1,6 @@ -use crate::checks::{check_stake_is_initialized_with_authority, check_stake_valid_delegation}; +use crate::checks::{ + check_stake_is_initialized_with_withdrawer_authority, check_stake_valid_delegation, +}; use crate::constants::BONDS_AUTHORITY_SEED; use crate::error::ErrorCode; use crate::events::settlement::CloseSettlementEvent; @@ -9,14 +11,8 @@ use anchor_lang::prelude::*; use anchor_lang::solana_program::sysvar::stake_history; use anchor_spl::stake::{withdraw, Stake, StakeAccount, Withdraw}; -#[derive(AnchorDeserialize, AnchorSerialize)] -pub struct CloseSettlementArgs { - pub merkle_root: [u8; 32], -} - /// Closes the settlement account, whoever can close it when the epoch expires #[derive(Accounts)] -#[instruction(params: CloseSettlementArgs)] pub struct CloseSettlement<'info> { #[account()] config: Account<'info, Config>, @@ -43,7 +39,8 @@ pub struct CloseSettlement<'info> { seeds = [ b"settlement_account", bond.key().as_ref(), - params.merkle_root.as_ref(), + settlement.merkle_root.as_ref(), + settlement.epoch_created_at.to_le_bytes().as_ref(), ], bump = settlement.bumps.pda, )] @@ -84,14 +81,14 @@ impl<'info> CloseSettlement<'info> { pub fn process(&mut self) -> Result<()> { if self.settlement.split_rent_collector.is_some() { // stake account is managed by bonds program - let stake_meta = check_stake_is_initialized_with_authority( + let stake_meta = check_stake_is_initialized_with_withdrawer_authority( &self.stake_account, &self.bonds_withdrawer_authority.key(), "stake_account", )?; // stake account is delegated (deposited by) the bond validator check_stake_valid_delegation(&self.stake_account, &self.bond.validator_vote_account)?; - // provided stake account must be funded + // provided stake account must be funded; staker == settlement staker authority require_keys_eq!( stake_meta.authorized.staker, self.settlement.settlement_authority, diff --git a/programs/validator-bonds/src/instructions/settlement/fund_settlement.rs b/programs/validator-bonds/src/instructions/settlement/fund_settlement.rs index 5bdefd69..7bc70ca9 100644 --- a/programs/validator-bonds/src/instructions/settlement/fund_settlement.rs +++ b/programs/validator-bonds/src/instructions/settlement/fund_settlement.rs @@ -1,4 +1,6 @@ -use crate::checks::{check_stake_is_initialized_with_authority, check_stake_valid_delegation}; +use crate::checks::{ + check_stake_is_initialized_with_withdrawer_authority, check_stake_valid_delegation, +}; use crate::constants::BONDS_AUTHORITY_SEED; use crate::error::ErrorCode; use crate::events::settlement::FundSettlementEvent; @@ -18,7 +20,7 @@ use anchor_spl::stake::{ }; /// Funding settlement by providing stake account delegated to particular validator vote account based on the merkle proof. -/// The settlement has been previously created by operator to fulfil some insurance event (e.g., slashing) +/// The settlement has been previously created by operator to fulfil some protected event (e.g., slashing) /// Currently permission-ed. #[derive(Accounts)] pub struct FundSettlement<'info> { @@ -45,7 +47,8 @@ pub struct FundSettlement<'info> { seeds = [ b"settlement_account", bond.key().as_ref(), - settlement.merkle_root.as_ref() + settlement.merkle_root.as_ref(), + settlement.epoch_created_at.to_le_bytes().as_ref(), ], bump = settlement.bumps.pda, )] @@ -95,8 +98,8 @@ pub struct FundSettlement<'info> { /// This is an account used to prefund the split stake account. /// If a split stake account is not needed then rent payer is fully refunded at the end of the transaction. - /// If a split stake account is created for the settlement, the payer needs to manually call the claim_settlement - /// instruction to get the rent back (call will succeed only when the stake account is already deactivated). + /// If a split stake account is created for the settlement, the payer needs to manually close the claim_settlement + /// instruction to get the rent back (success only when the stake account is already deactivated). #[account( mut, owner = system_program::ID @@ -124,14 +127,15 @@ impl<'info> FundSettlement<'info> { } // stake account is managed by bonds program - let stake_meta = check_stake_is_initialized_with_authority( + let stake_meta = check_stake_is_initialized_with_withdrawer_authority( &self.stake_account, &self.bonds_withdrawer_authority.key(), "stake_account", )?; // settlement funding may accept only stake account delegated to (i.e., deposited by) the bond validator check_stake_valid_delegation(&self.stake_account, &self.bond.validator_vote_account)?; - // provided stake account must NOT be funded (not deposited); deposited == bonds withdrawer authority, funded == settlement authority + // provided stake account must NOT have been used to fund settlement (but must be owned by bonds program) + // funded to bond account -> staker == bonds withdrawer authority, funded to settlement -> staker == settlement staker authority require_keys_eq!( stake_meta.authorized.staker, self.bonds_withdrawer_authority.key(), @@ -139,11 +143,11 @@ impl<'info> FundSettlement<'info> { ); let split_stake_rent_exempt = self.split_stake_account.to_account_info().lamports(); - let stake_account_min_size = minimal_size_stake_account(&stake_meta); + let stake_account_min_size = minimal_size_stake_account(&stake_meta, &self.config); // note: we can over-fund the settlement when the stake account is in shape to not being possible to split it let amount_available = self.stake_account.get_lamports(); - // note: needed rent exempt + minimal stake size to ensure stake account can exist after claiming all + // amount needed: "amount + rent exempt + minimal stake size" -> ensuring stake account may exist let amount_needed = self.settlement.max_total_claim - self.settlement.total_funded + stake_account_min_size; // the left-over stake account has to be capable to exist after splitting diff --git a/programs/validator-bonds/src/instructions/settlement/init_settlement.rs b/programs/validator-bonds/src/instructions/settlement/init_settlement.rs index d1e5d668..752eb4bf 100644 --- a/programs/validator-bonds/src/instructions/settlement/init_settlement.rs +++ b/programs/validator-bonds/src/instructions/settlement/init_settlement.rs @@ -36,9 +36,6 @@ pub struct InitSettlement<'info> { )] bond: Account<'info, Bond>, - // TODO: can we need to create a new settlement for the same merkle root? - // probably yes as a merkle roots collision can happen? - // if yes, what should be the discriminator, could that be epoch? or should we use a random pubkey rather? #[account( init, payer = rent_payer, @@ -47,6 +44,7 @@ pub struct InitSettlement<'info> { b"settlement_account", bond.key().as_ref(), params.merkle_root.as_ref(), + clock.epoch.to_le_bytes().as_ref(), ], bump, )] diff --git a/programs/validator-bonds/src/instructions/stake/merge.rs b/programs/validator-bonds/src/instructions/stake/merge.rs index 922d9f60..bb51d7fd 100644 --- a/programs/validator-bonds/src/instructions/stake/merge.rs +++ b/programs/validator-bonds/src/instructions/stake/merge.rs @@ -53,6 +53,7 @@ impl<'info> Merge<'info> { .meta() .ok_or(error!(ErrorCode::UninitializedStake).with_account_name("source_stake"))?; + // staker authorities has to match each other, verification if it belongs to bond is down in switch statement if destination_meta.authorized.staker != self.staker_authority.key() { return Err(error!(ErrorCode::StakerAuthorityMismatch) .with_pubkeys(( @@ -67,13 +68,23 @@ impl<'info> Merge<'info> { .with_account_name("source_stake")); } - // this program provides only limited set of staker authorities to sign - let (bonds_authority, _) = find_bonds_withdrawer_authority(&self.config.key()); - let (settlement_authority, settlement_bump) = find_settlement_authority(&settlement); + // withdrawer authorities must belongs to the bonds program (bonds program ownership) + let (bonds_withdrawer_authority, _) = find_bonds_withdrawer_authority(&self.config.key()); + if source_meta.authorized.withdrawer != bonds_withdrawer_authority + || destination_meta.authorized.withdrawer != bonds_withdrawer_authority + { + return Err(error!(ErrorCode::StakerAuthorityMismatch) + .with_values(("bonds_withdrawer_authority", bonds_withdrawer_authority)) + .with_values(("source_stake_withdrawer", source_meta.authorized.withdrawer)) + .with_values(( + "destination_stake_withdrawer", + destination_meta.authorized.withdrawer, + ))); + } let destination_delegation = self.destination_stake.delegation(); let source_delegation = self.source_stake.delegation(); - // by design the stakes have to be delegated and to the same validator vote accounts + // the stakes have to be delegated to the same validator vote accounts if destination_delegation.is_none() || source_delegation.is_none() || destination_delegation.unwrap().voter_pubkey @@ -98,6 +109,8 @@ impl<'info> Merge<'info> { ))); } + let (settlement_authority, settlement_bump) = find_settlement_authority(&settlement); + let merge_instruction = &merge( &self.destination_stake.key(), &self.source_stake.key(), @@ -111,7 +124,7 @@ impl<'info> Merge<'info> { self.stake_history.to_account_info(), self.staker_authority.to_account_info(), ]; - if self.staker_authority.key() == bonds_authority { + if self.staker_authority.key() == bonds_withdrawer_authority { invoke_signed( merge_instruction, merge_account_infos, @@ -135,7 +148,7 @@ impl<'info> Merge<'info> { return Err(error!(ErrorCode::StakerAuthorityMismatch) .with_account_name("staker_authority") .with_values(("staker_authority", self.staker_authority.key())) - .with_values(("bonds_authority", bonds_authority)) + .with_values(("bonds_authority", bonds_withdrawer_authority)) .with_values(("settlement_authority", settlement_authority))); }; diff --git a/programs/validator-bonds/src/instructions/stake/reset.rs b/programs/validator-bonds/src/instructions/stake/reset.rs index 6895d4f2..0506c550 100644 --- a/programs/validator-bonds/src/instructions/stake/reset.rs +++ b/programs/validator-bonds/src/instructions/stake/reset.rs @@ -1,24 +1,21 @@ -use crate::checks::{check_stake_is_initialized_with_authority, check_stake_valid_delegation}; +use crate::checks::{ + check_stake_is_initialized_with_withdrawer_authority, check_stake_valid_delegation, +}; use crate::constants::BONDS_AUTHORITY_SEED; use crate::error::ErrorCode; use crate::events::stake::ResetEvent; use crate::state::bond::Bond; use crate::state::config::Config; use anchor_lang::prelude::*; +use anchor_lang::solana_program::program::invoke_signed; +use anchor_lang::solana_program::stake; use anchor_lang::solana_program::stake::state::StakeAuthorize; use anchor_lang::solana_program::sysvar::stake_history; use anchor_spl::stake::{authorize, Authorize, Stake, StakeAccount}; -// TODO: considering here that settlement_seed pubkey and bumps will be provided from outside by bot -#[derive(AnchorDeserialize, AnchorSerialize)] -pub struct ResetStateArgs { - pub settlement_stake_authority_bump: u8, -} - /// Resetting stake authority of a funded stake account belonging to removed settlement. /// I.e., for provided stake account it changes the stake authority from settlement stake authority to bonds withdrawer authority. #[derive(Accounts)] -#[instruction(params: ResetStateArgs)] pub struct ResetStake<'info> { /// the config root account under which the bond was created #[account()] @@ -42,18 +39,11 @@ pub struct ResetStake<'info> { #[account(mut)] stake_account: Account<'info, StakeAccount>, - /// CHECK: PDA - #[account( - seeds = [ - b"settlement_authority", - settlement.key().as_ref(), - ], - bump = params.settlement_stake_authority_bump, - )] + /// CHECK: CPI calls of stake authorize permits to change the staker only with correct settlement authority settlement_authority: UncheckedAccount<'info>, /// CHECK: PDA - /// authority that manages (owns being withdrawer authority) all stakes account under the bonds program + /// authority that owns (withdrawer authority) all stakes account under the bonds program #[account( seeds = [ b"bonds_authority", @@ -63,10 +53,17 @@ pub struct ResetStake<'info> { )] bonds_withdrawer_authority: UncheckedAccount<'info>, + /// CHECK: the validator vote account to which the stake account is delegated, check in code + validator_vote_account: UncheckedAccount<'info>, + /// CHECK: have no CPU budget to parse #[account(address = stake_history::ID)] stake_history: UncheckedAccount<'info>, + /// CHECK: CPI + #[account(address = stake::config::ID)] + stake_config: UncheckedAccount<'info>, + clock: Sysvar<'info, Clock>, stake_program: Program<'info, Stake>, @@ -82,13 +79,14 @@ impl<'info> ResetStake<'info> { ); // stake account is managed by bonds program and belongs under bond validator - let stake_meta = check_stake_is_initialized_with_authority( + let stake_meta = check_stake_is_initialized_with_withdrawer_authority( &self.stake_account, &self.bonds_withdrawer_authority.key(), "stake_account", )?; // one bond can be created for a validator vote account, this stake account belongs to bond check_stake_valid_delegation(&self.stake_account, &self.bond.validator_vote_account)?; + check_stake_valid_delegation(&self.stake_account, &self.validator_vote_account.key())?; // stake account is funded to particular settlement require_eq!( stake_meta.authorized.staker, @@ -96,13 +94,14 @@ impl<'info> ResetStake<'info> { ErrorCode::SettlementAuthorityMismatch ); - // moving the stake account under the bonds authority (withdrawer (owner) authority and staker will be the same) + // moving the stake account under the bonds authority (withdrawer and staker will be the same) + // https://github.com/solana-labs/solana/blob/v1.17.10/sdk/program/src/stake/state.rs#L312 authorize( CpiContext::new_with_signer( self.stake_program.to_account_info(), Authorize { stake: self.stake_account.to_account_info(), - authorized: self.bonds_withdrawer_authority.to_account_info(), + authorized: self.settlement_authority.to_account_info(), new_authorized: self.bonds_withdrawer_authority.to_account_info(), clock: self.clock.to_account_info(), }, @@ -116,11 +115,36 @@ impl<'info> ResetStake<'info> { None, )?; + // activate the stake, i.e., resetting is delegating to the validator again + let delegate_instruction = &stake::instruction::delegate_stake( + &self.stake_account.key(), + &self.bonds_withdrawer_authority.key(), + &self.bond.validator_vote_account, + ); + invoke_signed( + delegate_instruction, + &[ + self.stake_program.to_account_info(), + self.stake_account.to_account_info(), + self.bonds_withdrawer_authority.to_account_info(), + self.validator_vote_account.to_account_info(), + self.clock.to_account_info(), + self.stake_history.to_account_info(), + self.stake_config.to_account_info(), + ], + &[&[ + BONDS_AUTHORITY_SEED, + &self.config.key().as_ref(), + &[self.config.bonds_withdrawer_authority_bump], + ]], + )?; + emit!(ResetEvent { config: self.config.key(), bond: self.bond.key(), settlement: self.settlement.key(), stake_account: self.stake_account.key(), + validator_vote_acount: self.validator_vote_account.key(), settlement_authority: self.settlement_authority.key(), bonds_withdrawer_authority: self.bonds_withdrawer_authority.key(), }); diff --git a/programs/validator-bonds/src/instructions/withdraw/withdraw_deposit.rs b/programs/validator-bonds/src/instructions/withdraw/claim_withdraw_request.rs similarity index 71% rename from programs/validator-bonds/src/instructions/withdraw/withdraw_deposit.rs rename to programs/validator-bonds/src/instructions/withdraw/claim_withdraw_request.rs index 5fd776c7..490b7dc8 100644 --- a/programs/validator-bonds/src/instructions/withdraw/withdraw_deposit.rs +++ b/programs/validator-bonds/src/instructions/withdraw/claim_withdraw_request.rs @@ -1,6 +1,6 @@ use crate::checks::{ - check_stake_is_initialized_with_authority, check_stake_valid_delegation, - check_validator_vote_account_owner, + check_stake_is_initialized_with_withdrawer_authority, check_stake_valid_delegation, + check_validator_vote_account_withdrawer_authority, }; use crate::constants::BONDS_AUTHORITY_SEED; use crate::error::ErrorCode; @@ -18,8 +18,9 @@ use anchor_spl::stake::{authorize, Authorize, Stake, StakeAccount}; /// Withdrawing funds from a bond account, to proceed the withdraw one must create a withdraw request first. /// Withdrawal takes StakeAccount that associated with bonds program and changes owner back to validator vote withdrawer. +// TODO: AquireWithdrawRequest ? #[derive(Accounts)] -pub struct WithdrawDeposit<'info> { +pub struct ClaimWithdrawRequest<'info> { /// the config root configuration account #[account()] config: Account<'info, Config>, @@ -105,18 +106,21 @@ pub struct WithdrawDeposit<'info> { clock: Sysvar<'info, Clock>, } -impl<'info> WithdrawDeposit<'info> { +impl<'info> ClaimWithdrawRequest<'info> { pub fn process(&mut self) -> Result<()> { - // vote account owner matches the authority where the funds will be withdrawn + // vote account owner matches the authority where the funds will be withdrawn to // i.e., the address where who will be new owner (withdrawer authority) of the stake account - check_validator_vote_account_owner(&self.validator_vote_account, &self.withdrawer.key())?; + check_validator_vote_account_withdrawer_authority( + &self.validator_vote_account, + &self.withdrawer.key(), + )?; // stake account is delegated to the validator vote account associated with the bond let stake_delegation = check_stake_valid_delegation(&self.stake_account, &self.bond.validator_vote_account)?; // stake account belongs under the bonds program - let stake_meta = check_stake_is_initialized_with_authority( + let stake_meta = check_stake_is_initialized_with_withdrawer_authority( &self.stake_account, &self.bonds_withdrawer_authority.key(), "stake_account", @@ -136,57 +140,59 @@ impl<'info> WithdrawDeposit<'info> { // when the stake account is bigger to the non withdrawn amount of the withdrawal request // we need to split the stake account to parts and withdraw only the non withdrawn amount - let (withdrawing_amount, is_split) = - if self.stake_account.get_lamports() > amount_to_fulfill_withdraw { - // ensuring that splitting means stake accounts will be big enough - // note: the rent exempt of the newly created split account has been already paid by the tx caller - if self.stake_account.get_lamports() - amount_to_fulfill_withdraw - >= minimal_size_stake_account(&stake_meta) - { - return Err(error!(ErrorCode::StakeAccountNotBigEnoughToSplit) - .with_account_name("stake_account") - .with_values(("stake_account_lamports", self.stake_account.get_lamports())) - .with_values(("amount_to_fulfill_withdraw", amount_to_fulfill_withdraw))); - } - - let withdraw_split_leftover = - self.stake_account.get_lamports() - amount_to_fulfill_withdraw; - let split_instruction = stake::instruction::split( - &self.stake_account.key(), - &self.withdrawer.key(), - withdraw_split_leftover, - &self.split_stake_account.key(), - ) - .last() - .unwrap() - .clone(); - invoke_signed( - &split_instruction, - &[ - self.stake_program.to_account_info(), - self.stake_account.to_account_info(), - self.split_stake_account.to_account_info(), - self.bonds_withdrawer_authority.to_account_info(), - ], - &[&[ - BONDS_AUTHORITY_SEED, - &self.config.key().as_ref(), - &[self.config.bonds_withdrawer_authority_bump], - ]], - )?; - // withdrawal amount is the rest to fulfil the withdrawal request - (amount_to_fulfill_withdraw, true) - } else { - return_unused_split_stake_account_rent( - &self.stake_program, - &self.split_stake_account, - &self.split_stake_rent_payer, - &self.clock, - &self.stake_history, - )?; - // withdrawal amount is full stake account - (stake_delegation.stake, false) - }; + let (withdrawing_amount, is_split) = if self.stake_account.get_lamports() + > amount_to_fulfill_withdraw + { + // ensuring that splitting means stake accounts will be big enough + // note: the rent exempt of the newly created split account has been already paid by the tx caller + let minimal_stake_size = minimal_size_stake_account(&stake_meta, &self.config); + if self.stake_account.get_lamports() - amount_to_fulfill_withdraw < minimal_stake_size + || amount_to_fulfill_withdraw < minimal_stake_size + { + return Err(error!(ErrorCode::StakeAccountNotBigEnoughToSplit) + .with_account_name("stake_account") + .with_values(("stake_account_lamports", self.stake_account.get_lamports())) + .with_values(("amount_to_fulfill_withdraw", amount_to_fulfill_withdraw))); + } + + let withdraw_split_leftover = + self.stake_account.get_lamports() - amount_to_fulfill_withdraw; + let split_instruction = stake::instruction::split( + &self.stake_account.key(), + &self.withdrawer.key(), + withdraw_split_leftover, + &self.split_stake_account.key(), + ) + .last() + .unwrap() + .clone(); + invoke_signed( + &split_instruction, + &[ + self.stake_program.to_account_info(), + self.stake_account.to_account_info(), + self.split_stake_account.to_account_info(), + self.bonds_withdrawer_authority.to_account_info(), + ], + &[&[ + BONDS_AUTHORITY_SEED, + &self.config.key().as_ref(), + &[self.config.bonds_withdrawer_authority_bump], + ]], + )?; + // withdrawal amount is the rest to fulfil the withdrawal request + (amount_to_fulfill_withdraw, true) + } else { + return_unused_split_stake_account_rent( + &self.stake_program, + &self.split_stake_account, + &self.split_stake_rent_payer, + &self.clock, + &self.stake_history, + )?; + // withdrawal amount is full stake account + (stake_delegation.stake, false) + }; let old_withdrawn_amount = self.withdraw_request.withdrawn_amount; self.withdraw_request.withdrawn_amount = self @@ -210,7 +216,7 @@ impl<'info> WithdrawDeposit<'info> { &[self.config.bonds_withdrawer_authority_bump], ]], ), - // withdraw authority (owner) is now the withdrawer authority defined by ix + // withdrawer authority (owner) is now the withdrawer authority defined by ix StakeAuthorize::Withdrawer, None, )?; diff --git a/programs/validator-bonds/src/instructions/withdraw/init_withdraw_request.rs b/programs/validator-bonds/src/instructions/withdraw/init_withdraw_request.rs index ca97811e..a527b199 100644 --- a/programs/validator-bonds/src/instructions/withdraw/init_withdraw_request.rs +++ b/programs/validator-bonds/src/instructions/withdraw/init_withdraw_request.rs @@ -9,13 +9,13 @@ use anchor_lang::prelude::*; use anchor_lang::solana_program::system_program; #[derive(AnchorDeserialize, AnchorSerialize)] -pub struct CreateWithdrawRequestArgs { +pub struct InitWithdrawRequestArgs { pub amount: u64, } /// Creates a withdraw request of bond funds for a validator vote account #[derive(Accounts)] -pub struct CreateWithdrawRequest<'info> { +pub struct InitWithdrawRequest<'info> { /// the config root account under which the bond was created #[account()] config: Account<'info, Config>, @@ -64,10 +64,10 @@ pub struct CreateWithdrawRequest<'info> { clock: Sysvar<'info, Clock>, } -impl<'info> CreateWithdrawRequest<'info> { +impl<'info> InitWithdrawRequest<'info> { pub fn process( &mut self, - CreateWithdrawRequestArgs { amount }: CreateWithdrawRequestArgs, + InitWithdrawRequestArgs { amount }: InitWithdrawRequestArgs, withdraw_request_bump: u8, ) -> Result<()> { require!( diff --git a/programs/validator-bonds/src/instructions/withdraw/mod.rs b/programs/validator-bonds/src/instructions/withdraw/mod.rs index bf1ca955..61a5d2f4 100644 --- a/programs/validator-bonds/src/instructions/withdraw/mod.rs +++ b/programs/validator-bonds/src/instructions/withdraw/mod.rs @@ -1,7 +1,7 @@ pub mod cancel_withdraw_request; +pub mod claim_withdraw_request; pub mod init_withdraw_request; -pub mod withdraw_deposit; pub use cancel_withdraw_request::*; +pub use claim_withdraw_request::*; pub use init_withdraw_request::*; -pub use withdraw_deposit::*; diff --git a/programs/validator-bonds/src/lib.rs b/programs/validator-bonds/src/lib.rs index 33952fdd..22b30728 100644 --- a/programs/validator-bonds/src/lib.rs +++ b/programs/validator-bonds/src/lib.rs @@ -29,8 +29,7 @@ security_txt! { source_release: default_env!("GIT_REV_NAME", "GIT_REV_NAME_MISSING") } -// TODO: need to grind an address -declare_id!("vbondsKbsC4QSLQQnn6ngZvkqfywn6KgEeQbkGSpk1V"); +declare_id!("vBoNdEvzMrSai7is21XgVYik65mqtaKXuSdMBJ1xkW4"); // TODO: General TODOs: // - verify that errors are used and error codes matches @@ -79,14 +78,14 @@ pub mod validator_bonds { ctx.accounts.process(configure_bond_args) } - pub fn deposit_bond(ctx: Context) -> Result<()> { + pub fn fund_bond(ctx: Context) -> Result<()> { check_context(&ctx)?; ctx.accounts.process() } - pub fn create_withdraw_request( - ctx: Context, - create_withdraw_request_args: CreateWithdrawRequestArgs, + pub fn init_withdraw_request( + ctx: Context, + create_withdraw_request_args: InitWithdrawRequestArgs, ) -> Result<()> { check_context(&ctx)?; ctx.accounts @@ -98,7 +97,7 @@ pub mod validator_bonds { ctx.accounts.process() } - pub fn withdraw_deposit(ctx: Context) -> Result<()> { + pub fn claim_withdraw_request(ctx: Context) -> Result<()> { check_context(&ctx)?; ctx.accounts.process() } @@ -117,7 +116,7 @@ pub mod validator_bonds { ctx.accounts.process() } - pub fn fund_settlement(ctx: Context) -> Result<()> { + pub fn fund_settlement(ctx: Context) -> Result<()> { check_context(&ctx)?; ctx.accounts.process() } diff --git a/programs/validator-bonds/src/state/bond.rs b/programs/validator-bonds/src/state/bond.rs index fc292a13..ec64ca6c 100644 --- a/programs/validator-bonds/src/state/bond.rs +++ b/programs/validator-bonds/src/state/bond.rs @@ -21,8 +21,8 @@ pub struct Bond { /// The same powers has got the owner of the validator vote account // https://github.com/solana-labs/solana/blob/master/vote/src/vote_account.rs pub authority: Pubkey, - /// Revenue that is distributed from the bond to the protocol - pub revenue_share_config: HundredthBasisPoint, + /// Revenue that is distributed from the bond (from validator) to the protocol + pub revenue_share: HundredthBasisPoint, /// PDA Bond address bump seed pub bump: u8, /// reserve space for future extensions diff --git a/programs/validator-bonds/src/state/config.rs b/programs/validator-bonds/src/state/config.rs index 5e1c12bc..2eed184c 100644 --- a/programs/validator-bonds/src/state/config.rs +++ b/programs/validator-bonds/src/state/config.rs @@ -15,6 +15,8 @@ pub struct Config { pub epochs_to_claim_settlement: u64, /// How many epochs before withdraw is allowed pub withdraw_lockup_epochs: u64, + /// Minimum amount of lamports to be considered for a stake account operations (e.g., split) + pub minimum_stake_lamports: u64, /// PDA bonds bonds stake accounts authority bump seed pub bonds_withdrawer_authority_bump: u8, /// reserved space for future changes @@ -28,6 +30,7 @@ impl Default for Config { operator_authority: Pubkey::default(), epochs_to_claim_settlement: u64::default(), withdraw_lockup_epochs: u64::default(), + minimum_stake_lamports: u64::default(), bonds_withdrawer_authority_bump: u8::default(), reserved: [0; 512], } diff --git a/programs/validator-bonds/src/state/settlement.rs b/programs/validator-bonds/src/state/settlement.rs index a4f16d8f..cc821fed 100644 --- a/programs/validator-bonds/src/state/settlement.rs +++ b/programs/validator-bonds/src/state/settlement.rs @@ -5,7 +5,7 @@ use crate::ID; use anchor_lang::prelude::*; /// Settlement account for a particular config and merkle root -/// Settlement defines an insurance event happens and it's needed to be settled +/// Settlement defines that a protected event happened and it will be settled #[account] #[derive(Debug, Default)] pub struct Settlement { @@ -51,6 +51,7 @@ impl Settlement { SETTLEMENT_SEED, &self.bond.key().as_ref(), &self.merkle_root, + &self.epoch_created_at.to_le_bytes(), &[self.bumps.pda], ], &ID, diff --git a/programs/validator-bonds/src/state/settlement_claim.rs b/programs/validator-bonds/src/state/settlement_claim.rs index 58a13b46..a4212632 100644 --- a/programs/validator-bonds/src/state/settlement_claim.rs +++ b/programs/validator-bonds/src/state/settlement_claim.rs @@ -11,10 +11,10 @@ use anchor_lang::prelude::*; pub struct SettlementClaim { /// settlement account this claim belongs under pub settlement: Pubkey, - /// stake authority as part of the merkle proof for this claim - pub stake_authority: Pubkey, - /// withdraw authority that has got permission to withdraw the claim - pub withdraw_authority: Pubkey, + /// staker authority as part of the merkle proof for this claim + pub staker_authority: Pubkey, + /// withdrawer authority that has got permission to withdraw the claim + pub withdrawer_authority: Pubkey, /// vote account as part of the merkle proof for this claim pub vote_account: Pubkey, /// claim amount @@ -34,8 +34,8 @@ impl SettlementClaim { &[ SETTLEMENT_CLAIM_SEED, &self.settlement.key().as_ref(), - &self.stake_authority.as_ref(), - &self.withdraw_authority.as_ref(), + &self.staker_authority.as_ref(), + &self.withdrawer_authority.as_ref(), &self.vote_account.as_ref(), &self.claim.to_le_bytes().as_ref(), &[self.bump], diff --git a/programs/validator-bonds/src/state/withdraw_request.rs b/programs/validator-bonds/src/state/withdraw_request.rs index 5d9507bb..8aceaf4b 100644 --- a/programs/validator-bonds/src/state/withdraw_request.rs +++ b/programs/validator-bonds/src/state/withdraw_request.rs @@ -12,14 +12,14 @@ pub struct WithdrawRequest { pub validator_vote_account: Pubkey, /// Bond account that the withdraw request is for pub bond: Pubkey, - /// PDA account bump - pub bump: u8, /// Epoch when the withdraw was requested, i.e., when this "ticket" is created pub epoch: u64, /// Amount of lamports to withdraw pub requested_amount: u64, /// Amount of lamports withdrawn so far pub withdrawn_amount: u64, + /// PDA account bump + pub bump: u8, /// reserve space for future extensions pub reserved: Reserved150, } diff --git a/programs/validator-bonds/src/utils/merkle_proof.rs b/programs/validator-bonds/src/utils/merkle_proof.rs index a1687cb6..e4e4eafb 100644 --- a/programs/validator-bonds/src/utils/merkle_proof.rs +++ b/programs/validator-bonds/src/utils/merkle_proof.rs @@ -33,8 +33,8 @@ pub fn verify(proof: Vec<[u8; 32]>, root: [u8; 32], leaf: [u8; 32]) -> bool { #[derive(Default, Clone, Eq, Debug, Hash, PartialEq)] pub struct TreeNode { - pub stake_authority: String, - pub withdraw_authority: String, + pub staker_authority: String, + pub withdrawer_authority: String, pub vote_account: String, pub claim: u64, } @@ -42,8 +42,8 @@ pub struct TreeNode { impl TreeNode { fn hash(&self) -> Hash { let mut hasher = Hasher::default(); - hasher.hash(self.stake_authority.as_ref()); - hasher.hash(self.withdraw_authority.as_ref()); + hasher.hash(self.staker_authority.as_ref()); + hasher.hash(self.withdrawer_authority.as_ref()); hasher.hash(self.vote_account.as_ref()); hasher.hash(self.claim.to_le_bytes().as_ref()); hasher.result() @@ -51,14 +51,14 @@ impl TreeNode { } pub fn tree_node( - stake_authority: Pubkey, - withdraw_authority: Pubkey, + staker_authority: Pubkey, + withdrawer_authority: Pubkey, vote_account: Pubkey, claim: u64, ) -> [u8; 32] { let tree_node = TreeNode { - stake_authority: stake_authority.to_string(), - withdraw_authority: withdraw_authority.to_string(), + staker_authority: staker_authority.to_string(), + withdrawer_authority: withdrawer_authority.to_string(), vote_account: vote_account.to_string(), claim, }; @@ -131,9 +131,9 @@ mod tests { 21, 184, 12, 244, 6, 40, 200, 149, 249, 100, 177, 201, 234, ], ]; - let stake_authority = + let staker_authority = Pubkey::from_str("A8fPZYZYQ15achgZvxUn4pzxjWqUU5gNuwvKgwLCegCT").unwrap(); - let withdraw_authority = + let withdrawer_authority = Pubkey::from_str("A8fPZYZYQ15achgZvxUn4pzxjWqUU5gNuwvKgwLCegCT").unwrap(); let vote_account = Pubkey::from_str("DdCNGDpP7qMgoAy6paFzhhak2EeyCZcgjH7ak5u5v28m").unwrap(); @@ -141,7 +141,7 @@ mod tests { assert!(verify( proof, merkle_root, - tree_node(stake_authority, withdraw_authority, vote_account, claim) + tree_node(staker_authority, withdrawer_authority, vote_account, claim) )); } } diff --git a/programs/validator-bonds/src/utils/stake.rs b/programs/validator-bonds/src/utils/stake.rs index 4279c4f8..f0495297 100644 --- a/programs/validator-bonds/src/utils/stake.rs +++ b/programs/validator-bonds/src/utils/stake.rs @@ -1,4 +1,4 @@ -use crate::constants::MIN_STAKE_LAMPORTS; +use crate::state::config::Config; use anchor_lang::prelude::*; use anchor_lang::solana_program::stake::state::Meta; use anchor_spl::stake::{withdraw, Stake, StakeAccount, Withdraw}; @@ -6,7 +6,7 @@ use anchor_spl::stake::{withdraw, Stake, StakeAccount, Withdraw}; /// This method serves to close/remove the stake account that has been just created /// and it's not initialized. /// This returns back the rent reserve of unused stake account -/// when the spil stake account is not used for withdrawing, funding settlement... +/// when the split stake account is not used for withdrawing, funding settlement... pub fn return_unused_split_stake_account_rent<'info>( stake_program: &Program<'info, Stake>, split_stake_account: &Account<'info, StakeAccount>, @@ -31,6 +31,6 @@ pub fn return_unused_split_stake_account_rent<'info>( ) } -pub fn minimal_size_stake_account(stake_meta: &Meta) -> u64 { - stake_meta.rent_exempt_reserve + MIN_STAKE_LAMPORTS +pub fn minimal_size_stake_account(stake_meta: &Meta, config: &Config) -> u64 { + stake_meta.rent_exempt_reserve + config.minimum_stake_lamports }