diff --git a/staking/programs/staking/src/context.rs b/staking/programs/staking/src/context.rs index c31d1c16..33f9599f 100644 --- a/staking/programs/staking/src/context.rs +++ b/staking/programs/staking/src/context.rs @@ -67,6 +67,16 @@ pub struct UpdateGovernanceAuthority<'info> { pub config: Account<'info, global_config::GlobalConfig>, } +#[derive(Accounts)] +#[instruction(new_authority : Pubkey)] +pub struct UpdatePdaAuthority<'info> { + #[account(address = config.pda_authority)] + pub governance_signer: Signer<'info>, + #[account(mut, seeds = [CONFIG_SEED.as_bytes()], bump = config.bump)] + pub config: Account<'info, global_config::GlobalConfig>, +} + + #[derive(Accounts)] #[instruction(freeze : bool)] pub struct UpdateFreeze<'info> { diff --git a/staking/programs/staking/src/lib.rs b/staking/programs/staking/src/lib.rs index 7a407888..44b55b0a 100644 --- a/staking/programs/staking/src/lib.rs +++ b/staking/programs/staking/src/lib.rs @@ -87,6 +87,15 @@ pub mod staking { Ok(()) } + pub fn update_pda_authority( + ctx: Context, + new_authority: Pubkey, + ) -> Result<()> { + let config = &mut ctx.accounts.config; + config.pda_authority = new_authority; + Ok(()) + } + pub fn update_freeze(ctx: Context, freeze: bool) -> Result<()> { let config = &mut ctx.accounts.config; config.freeze = freeze; diff --git a/staking/target/idl/staking.json b/staking/target/idl/staking.json index 3d83c968..716d63a8 100644 --- a/staking/target/idl/staking.json +++ b/staking/target/idl/staking.json @@ -74,6 +74,36 @@ } ] }, + { + "name": "updatePdaAuthority", + "accounts": [ + { + "name": "governanceSigner", + "isMut": false, + "isSigner": true + }, + { + "name": "config", + "isMut": true, + "isSigner": false, + "pda": { + "seeds": [ + { + "kind": "const", + "type": "string", + "value": "config" + } + ] + } + } + ], + "args": [ + { + "name": "newAuthority", + "type": "publicKey" + } + ] + }, { "name": "updateFreeze", "accounts": [ diff --git a/staking/target/types/staking.ts b/staking/target/types/staking.ts index be475a00..8afd5133 100644 --- a/staking/target/types/staking.ts +++ b/staking/target/types/staking.ts @@ -74,6 +74,36 @@ export type Staking = { } ] }, + { + "name": "updatePdaAuthority", + "accounts": [ + { + "name": "governanceSigner", + "isMut": false, + "isSigner": true + }, + { + "name": "config", + "isMut": true, + "isSigner": false, + "pda": { + "seeds": [ + { + "kind": "const", + "type": "string", + "value": "config" + } + ] + } + } + ], + "args": [ + { + "name": "newAuthority", + "type": "publicKey" + } + ] + }, { "name": "updateFreeze", "accounts": [ @@ -1981,6 +2011,36 @@ export const IDL: Staking = { } ] }, + { + "name": "updatePdaAuthority", + "accounts": [ + { + "name": "governanceSigner", + "isMut": false, + "isSigner": true + }, + { + "name": "config", + "isMut": true, + "isSigner": false, + "pda": { + "seeds": [ + { + "kind": "const", + "type": "string", + "value": "config" + } + ] + } + } + ], + "args": [ + { + "name": "newAuthority", + "type": "publicKey" + } + ] + }, { "name": "updateFreeze", "accounts": [ diff --git a/staking/tests/config.ts b/staking/tests/config.ts index 3448f42a..6b5a9ab8 100644 --- a/staking/tests/config.ts +++ b/staking/tests/config.ts @@ -31,8 +31,9 @@ describe("config", async () => { const pythMintAuthority = new Keypair(); const zeroPubkey = new PublicKey(0); + const pdaAuthorityKeypair = new Keypair(); const config = readAnchorConfig(ANCHOR_CONFIG_PATH); - const pdaAuthority = PublicKey.unique(); + const pdaAuthority = pdaAuthorityKeypair.publicKey; const governanceProgram = new PublicKey(config.programs.localnet.governance); let errMap: Map; @@ -416,4 +417,77 @@ describe("config", async () => { errMap ); }); + + it("updates pda authority", async () => { + // governance authority can't update pda authority + await expectFail( + program.methods.updatePdaAuthority(program.provider.wallet.publicKey), + "An address constraint was violated", + errMap + ); + + const pdaConnection = await StakeConnection.createStakeConnection( + program.provider.connection, + new Wallet(pdaAuthorityKeypair), + program.programId + ); + + await pdaConnection.program.provider.connection.requestAirdrop( + pdaAuthorityKeypair.publicKey, + 1_000_000_000_000 + ); + + // Airdrops are not instant unfortunately, wait + await new Promise((resolve) => setTimeout(resolve, 2000)); + + // pda_authority updates pda_authority to the holder of governance_authority + await pdaConnection.program.methods + .updatePdaAuthority(program.provider.wallet.publicKey) + .rpc(); + + let configAccountData = await program.account.globalConfig.fetch( + configAccount + ); + + assert.equal( + JSON.stringify(configAccountData), + JSON.stringify({ + bump, + governanceAuthority: program.provider.wallet.publicKey, + pythTokenMint: pythMintAccount.publicKey, + pythGovernanceRealm: zeroPubkey, + unlockingDuration: 2, + epochDuration: new BN(3600), + freeze: true, + pdaAuthority: program.provider.wallet.publicKey, + governanceProgram, + pythTokenListTime: null, + agreementHash: getDummyAgreementHash(), + mockClockTime: new BN(30), + }) + ); + + // the authority gets returned to the original pda_authority + await program.methods.updatePdaAuthority(pdaAuthority).rpc(); + + configAccountData = await program.account.globalConfig.fetch(configAccount); + + assert.equal( + JSON.stringify(configAccountData), + JSON.stringify({ + bump, + governanceAuthority: program.provider.wallet.publicKey, + pythTokenMint: pythMintAccount.publicKey, + pythGovernanceRealm: zeroPubkey, + unlockingDuration: 2, + epochDuration: new BN(3600), + freeze: true, + pdaAuthority: pdaAuthority, + governanceProgram, + pythTokenListTime: null, + agreementHash: getDummyAgreementHash(), + mockClockTime: new BN(30), + }) + ); + }); });