Skip to content

Commit

Permalink
feat: add account recovery functionality (#424)
Browse files Browse the repository at this point in the history
* chore: Add .idea/ to .gitignore

In this commit, .idea/ was added to the .gitignore file. This is to ensure that IDE settings are not inadvertently committed to the repository.

* feat: Add RecoverAccount structure to staking context

The new structure RecoverAccount has been added to the staking context. This structure takes into account a variety of elements such as the native payer, token account, and stake program accounts. The addition of these elements enhances the functional capability of the staking context.

* feat: Add RecoverAccount function in staking program

A new RecoverAccount function has been added to the staking program, allowing an account's ownership to be updated. It modifies the stake account metadata, stake account positions and voter record to reflect the new owner.

* chore: generate staking.json

* feat: Export StakeTarget type in staking tests utils

The StakeTarget type, which is used in the staking tests utilities, was previously unreachable outside its module. By exporting it now, other modules that require it can gain access to it.

* feat: generate staking.ts

Introduced a new function called "recoverAccount" to the staking.ts file which is responsible for the payer, payerTokenAccount, stakeAccountPositions, stakeAccountMetadata, voterRecord, and config. The new addition will enhance the overall functionality and usage of the staking mechanism.

* feat: Add buildRecoverAccountInstruction function to StakeConnection

Implemented a new method named 'buildRecoverAccountInstruction' in the StakeConnection class. This function generates an instruction for account recovery, hence increasing the robustness of the staking mechanism. It dynamically accepts 'stakeAccountAddress' and 'governanceAuthorityAddress' as parameters and defines necessary roles including payer, payerTokenAccount, and stakeAccountPositions.

* feat: Implement account recovery test in staking application

A new test case has been added to the staking application which asserts the functionality of recovering staking accounts. This includes the implementation of 'buildRecoverAccountInstruction' in 'StakeConnection' which is integral to this recovery process. This test case bolsters the reliability and resilience of the staking system.

* feat: add failed account recovery test

This commit refactors the staking tests and adds a new test case for unsuccessful stake account recovery attempts. It also enhances imports with new functions from the utility modules and changes the name of the type for stake account owner updates. This change will provide more robust testing scenarios for our staking mechanism.

* fix: refactor recover account test to send the transaction to chain and expect it to fail

* fix: corrected typo in file name for 'recover_account.ts'

Renamed 'recouver_account.ts' to 'recover_account.ts' to correct a typo. This change enhances readability and eliminates the confusion that could have been caused by the incorrect name.

* fix: add assertion for stake account metadata owner in recover_account.ts

This commit implements an additional assertion in the recovery account test. The assertion checks if the stake account metadata owner is correctly set to the expected new owner.

* Fix: remove mutability from config account

The config account was previously mutable, but this has now been changed to be non-mutable. This modification is reflected in `context.rs`, `staking.ts`, and `staking.json`. The change enhances security by preventing unwanted modifications to the configuration.

* Fix: refine documentation and add documentation for account recovery function

Accept split documentation has been revised by removing a redundant accent mark. In addition, documents for a new function, recover_account, has been written.

* Update version in staking package.json file

The package.json file for staking was updated. The version has been changed from 2.1.0 to 2.2.0, reflecting the latest changes in the code. Updated the file to keep the metadata accurate and current.

* Update version in staking Cargo.toml file

The Cargo.toml file for the staking program was updated. The version has been changed from 1.0.0 to 1.1.0. This change keeps the package version aligned with recent code modifications.

* fix: update version in staking program

Both the Cargo.toml and Cargo.lock files for the staking program have been updated, changing the version from 1.1.0 and 1.0.0 respectively to 1.2.0. This keeps the package version in sync with the recent code modifications.

* fix: update test description in recover_account.ts

The description block in the recover account test was incorrectly labeled as "config". This commit corrects the test description to accurately reflect its purpose, changing it to "recover account".

* fix: update staking version and improve documentation

The version of staking has been updated to 1.2.0 from 1.0.0. Furthermore, corrections were made to the acceptSplit documentation to fix a typographical error. Additional documentation was also added to the recoverAccount function, providing a detailed explanation on how it can be used to recover a user's stake account ownership when mistakenly created using their token account address.

* fix: add tests for stake account ownership

The updated tests now retrieve and validate the ownership of a problematic stake account before proceeding with recovery operations. Through asserting that the owners of both the stake account positions and metadata match a new owner, this change aims to reinforce account recovery operations by confirming accurate ownership beforehand.

* fix: add check for staked tokens in recover account function

A check was added in the recover account function to verify if staked tokens are present. Accounts with staked tokens cannot be transferred due to potential double voting. The necessary error message "RecoverWithStake" has been added to warn users to unstake their tokens before recovering the account.
  • Loading branch information
keyvankhademi authored Mar 26, 2024
1 parent cce3e46 commit fe5f63f
Show file tree
Hide file tree
Showing 12 changed files with 604 additions and 12 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
.DS_Store
node_modules
.idea/
2 changes: 1 addition & 1 deletion staking/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

15 changes: 15 additions & 0 deletions staking/app/StakeConnection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1178,6 +1178,21 @@ export class StakeConnection {

await this.provider.sendAndConfirm(tx);
}

public async buildRecoverAccountInstruction(
stakeAccountAddress: PublicKey,
governanceAuthorityAddress: PublicKey
): Promise<TransactionInstruction> {
const stakeAccount = await this.loadStakeAccount(stakeAccountAddress);
return await this.program.methods
.recoverAccount()
.accounts({
payer: governanceAuthorityAddress,
payerTokenAccount: stakeAccount.stakeAccountPositionsJs.owner,
stakeAccountPositions: stakeAccountAddress,
})
.instruction();
}
}

export interface BalanceSummary {
Expand Down
2 changes: 1 addition & 1 deletion staking/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@pythnetwork/staking",
"version": "2.1.0",
"version": "2.2.0",
"description": "Pyth Network Staking SDK",
"main": "lib/app/index.js",
"types": "lib/app/index.d.ts",
Expand Down
2 changes: 1 addition & 1 deletion staking/programs/staking/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "pyth-staking-program"
version = "1.0.0"
version = "1.2.0"
description = "Created with Anchor"
edition = "2018"

Expand Down
35 changes: 35 additions & 0 deletions staking/programs/staking/src/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -405,3 +405,38 @@ pub struct AdvanceClock<'info> {
#[account(mut, seeds = [CONFIG_SEED.as_bytes()], bump = config.bump)]
pub config: Account<'info, global_config::GlobalConfig>,
}

#[derive(Accounts)]
pub struct RecoverAccount<'info> {
// Native payer:
#[account(address = config.governance_authority)]
pub payer: Signer<'info>,

// Token account:
#[account(address = stake_account_metadata.owner)]
pub payer_token_account: Account<'info, TokenAccount>,

// Stake program accounts:
#[account(mut)]
pub stake_account_positions: AccountLoader<'info, positions::PositionData>,

#[account(
mut,
seeds = [
STAKE_ACCOUNT_METADATA_SEED.as_bytes(),
stake_account_positions.key().as_ref()
],
bump = stake_account_metadata.metadata_bump
)]
pub stake_account_metadata: Account<'info, stake_account::StakeAccountMetadataV2>,

#[account(
mut,
seeds = [VOTER_RECORD_SEED.as_bytes(), stake_account_positions.key().as_ref()],
bump = stake_account_metadata.voter_bump
)]
pub voter_record: Account<'info, voter_weight_record::VoterWeightRecord>,

#[account(seeds = [CONFIG_SEED.as_bytes()], bump = config.bump)]
pub config: Account<'info, global_config::GlobalConfig>,
}
4 changes: 3 additions & 1 deletion staking/programs/staking/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,8 @@ pub enum ErrorCode {
SplitWithStake,
#[msg("The approval arguments do not match the split request.")] // 6034
InvalidApproval,
#[msg("Other")] //6035
#[msg("Can't recover account with staking positions. Unstake your tokens first.")] // 6035
RecoverWithStake,
#[msg("Other")] //6036
Other,
}
26 changes: 25 additions & 1 deletion staking/programs/staking/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -566,7 +566,7 @@ pub mod staking {


/**
* A split request can only be accepted by the `pda_authority`` from
* A split request can only be accepted by the `pda_authority` from
* the config account. If accepted, `amount` tokens are transferred to a new stake account
* owned by the `recipient` and the split request is reset (by setting `amount` to 0).
* The recipient of a transfer can't vote during the epoch of the transfer.
Expand Down Expand Up @@ -706,4 +706,28 @@ pub mod staking {
Some(ctx.accounts.config.agreement_hash);
Ok(())
}

/** Recovers a user's `stake account` ownership by transferring ownership
* from a token account to the `owner` of that token account.
*
* This functionality addresses the scenario where a user mistakenly
* created a stake account using their token account address as the owner.
*/
pub fn recover_account(ctx: Context<RecoverAccount>) -> Result<()> {
// Check that there aren't any positions (i.e., staked tokens) in the account.
// Transferring accounts with staked tokens might lead to double voting
require!(
ctx.accounts.stake_account_metadata.next_index == 0,
ErrorCode::SplitWithStake
);

let new_owner = ctx.accounts.payer_token_account.owner;

ctx.accounts.stake_account_metadata.owner = new_owner;
let stake_account_positions = &mut ctx.accounts.stake_account_positions.load_mut()?;
stake_account_positions.owner = new_owner;
ctx.accounts.voter_record.governing_token_owner = new_owner;

Ok(())
}
}
85 changes: 83 additions & 2 deletions staking/target/idl/staking.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"version": "1.0.0",
"version": "1.2.0",
"name": "staking",
"instructions": [
{
Expand Down Expand Up @@ -915,7 +915,7 @@
{
"name": "acceptSplit",
"docs": [
"* A split request can only be accepted by the `pda_authority`` from\n * the config account. If accepted, `amount` tokens are transferred to a new stake account\n * owned by the `recipient` and the split request is reset (by setting `amount` to 0).\n * The recipient of a transfer can't vote during the epoch of the transfer.\n *\n * The `pda_authority` must explicitly approve both the amount of tokens and recipient, and\n * these parameters must match the request (in the `split_request` account)."
"* A split request can only be accepted by the `pda_authority` from\n * the config account. If accepted, `amount` tokens are transferred to a new stake account\n * owned by the `recipient` and the split request is reset (by setting `amount` to 0).\n * The recipient of a transfer can't vote during the epoch of the transfer.\n *\n * The `pda_authority` must explicitly approve both the amount of tokens and recipient, and\n * these parameters must match the request (in the `split_request` account)."
],
"accounts": [
{
Expand Down Expand Up @@ -1198,6 +1198,82 @@
}
}
]
},
{
"name": "recoverAccount",
"docs": [
"Recovers a user's `stake account` ownership by transferring ownership\n * from a token account to the `owner` of that token account.\n *\n * This functionality addresses the scenario where a user mistakenly\n * created a stake account using their token account address as the owner."
],
"accounts": [
{
"name": "payer",
"isMut": false,
"isSigner": true
},
{
"name": "payerTokenAccount",
"isMut": false,
"isSigner": false
},
{
"name": "stakeAccountPositions",
"isMut": true,
"isSigner": false
},
{
"name": "stakeAccountMetadata",
"isMut": true,
"isSigner": false,
"pda": {
"seeds": [
{
"kind": "const",
"type": "string",
"value": "stake_metadata"
},
{
"kind": "account",
"type": "publicKey",
"path": "stake_account_positions"
}
]
}
},
{
"name": "voterRecord",
"isMut": true,
"isSigner": false,
"pda": {
"seeds": [
{
"kind": "const",
"type": "string",
"value": "voter_weight"
},
{
"kind": "account",
"type": "publicKey",
"path": "stake_account_positions"
}
]
}
},
{
"name": "config",
"isMut": false,
"isSigner": false,
"pda": {
"seeds": [
{
"kind": "const",
"type": "string",
"value": "config"
}
]
}
}
],
"args": []
}
],
"accounts": [
Expand Down Expand Up @@ -1963,6 +2039,11 @@
},
{
"code": 6035,
"name": "RecoverWithStake",
"msg": "Can't recover account with staking positions. Unstake your tokens first."
},
{
"code": 6036,
"name": "Other",
"msg": "Other"
}
Expand Down
Loading

0 comments on commit fe5f63f

Please sign in to comment.