Skip to content

Commit a781ee2

Browse files
committed
11: Let governance control hub proposal metadata
1 parent 825f598 commit a781ee2

File tree

8 files changed

+118
-5
lines changed

8 files changed

+118
-5
lines changed
File renamed without changes.

solana/app/deploy/3_update_HubProposalMetadata.ts

+7-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import * as anchor from "@coral-xyz/anchor";
22
import { AnchorProvider, Program, Wallet } from "@coral-xyz/anchor";
3-
import { Connection } from "@solana/web3.js";
3+
import { Connection, PublicKey, SystemProgram } from "@solana/web3.js";
44
import { hubProposalMetadataUint8Array } from "../constants";
55
import { DEPLOYER_AUTHORITY_KEYPAIR, RPC_NODE } from "./devnet";
66
import { Staking } from "../../target/types/staking";
@@ -22,9 +22,14 @@ async function main() {
2222
provider,
2323
);
2424

25+
const airlockPDA: PublicKey = PublicKey.findProgramAddressSync(
26+
[Buffer.from("airlock")],
27+
program.programId,
28+
)[0];
29+
2530
await program.methods
2631
.updateHubProposalMetadata(Array.from(hubProposalMetadataUint8Array))
27-
.accounts({ governanceAuthority: DEPLOYER_AUTHORITY_KEYPAIR.publicKey })
32+
.accounts({ payer: DEPLOYER_AUTHORITY_KEYPAIR.publicKey, airlock: airlockPDA })
2833
.rpc();
2934
} catch (err) {
3035
console.error("Error:", err);

solana/programs/staking/src/context.rs

+22
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,28 @@ pub struct InitializeSpokeMetadataCollector<'info> {
211211

212212
#[derive(Accounts)]
213213
pub struct UpdateHubProposalMetadata<'info> {
214+
#[account(mut)]
215+
pub payer: Signer<'info>,
216+
217+
#[account(
218+
seeds = [AIRLOCK_SEED.as_bytes()],
219+
bump = airlock.bump,
220+
)]
221+
pub airlock: Account<'info, SpokeAirlock>,
222+
223+
#[account(
224+
mut,
225+
seeds = [SPOKE_METADATA_COLLECTOR_SEED.as_bytes()],
226+
bump = spoke_metadata_collector.bump
227+
)]
228+
pub spoke_metadata_collector: Account<'info, SpokeMetadataCollector>,
229+
230+
#[account(seeds = [CONFIG_SEED.as_bytes()], bump = config.bump)]
231+
pub config: Box<Account<'info, global_config::GlobalConfig>>,
232+
}
233+
234+
#[derive(Accounts)]
235+
pub struct RelinquishAdminControlOverHubProposalMetadata<'info> {
214236
#[account(mut, address = config.governance_authority)]
215237
pub governance_authority: Signer<'info>,
216238

solana/programs/staking/src/error.rs

+4
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,10 @@ pub enum ErrorCode {
5353
InvalidCheckpointAccountLimit,
5454
#[msg("Zero withdrawals not permitted")]
5555
ZeroWithdrawal,
56+
#[msg("Not governance authority")]
57+
NotGovernanceAuthority,
58+
#[msg("Airlock is not a signer")]
59+
AirlockNotSigner,
5660
#[msg("Other")]
5761
Other,
5862
}

solana/programs/staking/src/lib.rs

+17
Original file line numberDiff line numberDiff line change
@@ -790,11 +790,28 @@ pub mod staking {
790790
new_hub_proposal_metadata: [u8; 20],
791791
) -> Result<()> {
792792
let spoke_metadata_collector = &mut ctx.accounts.spoke_metadata_collector;
793+
794+
if spoke_metadata_collector.updates_controlled_by_governance {
795+
require!(ctx.accounts.payer.key() == ctx.accounts.config.governance_authority, ErrorCode::NotGovernanceAuthority);
796+
}
797+
else {
798+
require!(ctx.accounts.airlock.to_account_info().is_signer, ErrorCode::AirlockNotSigner);
799+
}
800+
793801
let _ = spoke_metadata_collector.update_hub_proposal_metadata(new_hub_proposal_metadata);
794802

795803
Ok(())
796804
}
797805

806+
pub fn relinquish_admin_control_over_hub_proposal_metadata(
807+
ctx: Context<RelinquishAdminControlOverHubProposalMetadata>
808+
) -> Result<()> {
809+
let spoke_metadata_collector = &mut ctx.accounts.spoke_metadata_collector;
810+
spoke_metadata_collector.updates_controlled_by_governance = false;
811+
812+
Ok(())
813+
}
814+
798815
pub fn initialize_vote_weight_window_lengths(
799816
ctx: Context<InitializeVoteWeightWindowLengths>,
800817
initial_window_length: u64,

solana/programs/staking/src/state/spoke_metadata_collector.rs

+3
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ pub struct SpokeMetadataCollector {
2222
pub hub_proposal_metadata: [u8; 20],
2323
// Wormhole contract handling messages
2424
pub wormhole_core: Pubkey,
25+
// Updates to hub_proposal_metadata are governance controlled
26+
pub updates_controlled_by_governance: bool
2527
}
2628

2729
impl SpokeMetadataCollector {
@@ -39,6 +41,7 @@ impl SpokeMetadataCollector {
3941
self.hub_chain_id = hub_chain_id;
4042
self.hub_proposal_metadata = hub_proposal_metadata;
4143
self.wormhole_core = wormhole_core;
44+
self.updates_controlled_by_governance = true;
4245

4346
Ok(())
4447
}

solana/programs/staking/src/wasm.rs

+1
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,7 @@ reexport_seed_const!(CONFIG_SEED);
174174
reexport_seed_const!(PROPOSAL_SEED);
175175
reexport_seed_const!(VESTING_CONFIG_SEED);
176176
reexport_seed_const!(VESTING_BALANCE_SEED);
177+
reexport_seed_const!(AIRLOCK_SEED);
177178
reexport_seed_const!(VEST_SEED);
178179
reexport_seed_const!(SPOKE_METADATA_COLLECTOR_SEED);
179180
reexport_seed_const!(VOTE_WEIGHT_WINDOW_LENGTHS_SEED);

solana/tests/config.ts

+64-3
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ describe("config", async () => {
5555

5656
let program;
5757
let controller;
58+
let airlockAddress
5859

5960
let configAccount: PublicKey;
6061
let bump: number;
@@ -84,6 +85,27 @@ describe("config", async () => {
8485
}),
8586
];
8687
await program.provider.sendAndConfirm(tx, [program.provider.wallet.payer]);
88+
89+
airlockAddress =
90+
PublicKey.findProgramAddressSync(
91+
[
92+
utils.bytes.utf8.encode(
93+
wasm.Constants.AIRLOCK_SEED(),
94+
),
95+
],
96+
program.programId,
97+
)[0];
98+
99+
// Initialize the airlock account
100+
await program.methods
101+
.initializeSpokeAirlock()
102+
.accounts({
103+
payer: program.provider.wallet.payer,
104+
airlock: airlockAddress,
105+
systemProgram: SystemProgram.programId,
106+
})
107+
.signers([program.provider.wallet.payer])
108+
.rpc();
87109
});
88110

89111
it("initializes config", async () => {
@@ -278,20 +300,20 @@ describe("config", async () => {
278300
try {
279301
await program.methods
280302
.updateHubProposalMetadata(hubProposalMetadataUint8Array)
281-
.accounts({ governanceAuthority: randomUser.publicKey })
303+
.accounts({ payer: randomUser.publicKey, airlock: airlockAddress})
282304
.signers([randomUser])
283305
.rpc();
284306

285307
assert.fail("Expected error was not thrown");
286308
} catch (e) {
287-
assert((e as AnchorError).error?.errorCode?.code === "ConstraintAddress");
309+
assert((e as AnchorError).error?.errorCode?.code === "NotGovernanceAuthority");
288310
}
289311
});
290312

291313
it("should successfully update HubProposalMetadata", async () => {
292314
await program.methods
293315
.updateHubProposalMetadata(hubProposalMetadataUint8Array)
294-
.accounts({ governanceAuthority: program.provider.wallet.publicKey })
316+
.accounts({ payer: program.provider.wallet.publicKey, airlock: airlockAddress })
295317
.rpc({ skipPreflight: true });
296318

297319
const [spokeMetadataCollectorAccount, spokeMetadataCollectorBump] =
@@ -324,6 +346,45 @@ describe("config", async () => {
324346
);
325347
});
326348

349+
it("should revoke admin rights for updating HubProposalMetadata", async () => {
350+
await program.methods
351+
.relinquishAdminControlOverHubProposalMetadata()
352+
.accounts({ governanceAuthority: program.provider.wallet.publicKey })
353+
.rpc({ skipPreflight: true });
354+
355+
const [spokeMetadataCollectorAccount, spokeMetadataCollectorBump] =
356+
PublicKey.findProgramAddressSync(
357+
[
358+
utils.bytes.utf8.encode(
359+
wasm.Constants.SPOKE_METADATA_COLLECTOR_SEED(),
360+
),
361+
],
362+
program.programId,
363+
);
364+
365+
const spokeMetadataCollectorAccountData =
366+
await program.account.spokeMetadataCollector.fetch(
367+
spokeMetadataCollectorAccount,
368+
);
369+
370+
assert.equal(
371+
spokeMetadataCollectorAccountData.updatesControlledByGovernance,
372+
false,
373+
);
374+
375+
try {
376+
await program.methods
377+
.updateHubProposalMetadata(hubProposalMetadataUint8Array)
378+
.accounts({ payer: randomUser.publicKey, airlock: airlockAddress})
379+
.signers([randomUser])
380+
.rpc();
381+
382+
assert.fail("Expected error was not thrown");
383+
} catch (e) {
384+
assert((e as AnchorError).error?.errorCode?.code === "AirlockNotSigner");
385+
}
386+
});
387+
327388
it("create account", async () => {
328389
const configAccountData =
329390
await program.account.globalConfig.fetch(configAccount);

0 commit comments

Comments
 (0)