Skip to content
This repository was archived by the owner on Mar 11, 2025. It is now read-only.

Commit f600c90

Browse files
authored
Governance: Allow token owner to revoke their own membership (#3976)
* wip: Allow token owner to revoke their own membership * chore: test_revoke_own_council_tokens * chore: test_revoke_own_council_tokens_with_owner_must_sign_error * chore: test_revoke_council_tokens_with_invalid_revoke_authority_error * chore: Make Clippy Happy
1 parent e3d2b99 commit f600c90

File tree

6 files changed

+120
-34
lines changed

6 files changed

+120
-34
lines changed

governance/program/src/instruction.rs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -505,7 +505,9 @@ pub enum GovernanceInstruction {
505505
/// 1. `[writable]` Governing Token Holding account. PDA seeds: ['governance',realm, governing_token_mint]
506506
/// 2. `[writable]` TokenOwnerRecord account. PDA seeds: ['governance',realm, governing_token_mint, governing_token_owner]
507507
/// 3. `[writable]` GoverningTokenMint
508-
/// 4. `[signer]` GoverningTokenMint mint_authority
508+
/// 4. `[signer]` Revoke authority which can be either of:
509+
/// 1) GoverningTokenMint mint_authority to forcefully revoke the membership tokens
510+
/// 2) GoverningTokenOwner who voluntarily revokes their own membership
509511
/// 5. `[]` RealmConfig account. PDA seeds: ['realm-config', realm]
510512
/// 6. `[]` SPL Token program
511513
RevokeGoverningTokens {
@@ -1566,7 +1568,7 @@ pub fn revoke_governing_tokens(
15661568
realm: &Pubkey,
15671569
governing_token_owner: &Pubkey,
15681570
governing_token_mint: &Pubkey,
1569-
governing_token_mint_authority: &Pubkey,
1571+
revoke_authority: &Pubkey,
15701572
// Args
15711573
amount: u64,
15721574
) -> Instruction {
@@ -1587,7 +1589,7 @@ pub fn revoke_governing_tokens(
15871589
AccountMeta::new(governing_token_holding_address, false),
15881590
AccountMeta::new(token_owner_record_address, false),
15891591
AccountMeta::new(*governing_token_mint, false),
1590-
AccountMeta::new_readonly(*governing_token_mint_authority, true),
1592+
AccountMeta::new_readonly(*revoke_authority, true),
15911593
AccountMeta::new_readonly(realm_config_address, false),
15921594
AccountMeta::new_readonly(spl_token::id(), false),
15931595
];

governance/program/src/processor/process_revoke_governing_tokens.rs

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -30,10 +30,9 @@ pub fn process_revoke_governing_tokens(
3030
let token_owner_record_info = next_account_info(account_info_iter)?; // 2
3131

3232
let governing_token_mint_info = next_account_info(account_info_iter)?; // 3
33-
let governing_token_mint_authority_info = next_account_info(account_info_iter)?; // 4
33+
let revoke_authority_info = next_account_info(account_info_iter)?; // 4
3434

3535
let realm_config_info = next_account_info(account_info_iter)?; // 5
36-
3736
let spl_token_info = next_account_info(account_info_iter)?; // 6
3837

3938
let realm_data = get_realm_data(program_id, realm_info)?;
@@ -45,12 +44,6 @@ pub fn process_revoke_governing_tokens(
4544
governing_token_holding_info.key,
4645
)?;
4746

48-
// The governing_token_mint_authority must sign the transaction to revoke Membership tokens
49-
assert_spl_token_mint_authority_is_signer(
50-
governing_token_mint_info,
51-
governing_token_mint_authority_info,
52-
)?;
53-
5447
let realm_config_data =
5548
get_realm_config_data_for_realm(program_id, realm_config_info, realm_info.key)?;
5649

@@ -64,6 +57,19 @@ pub fn process_revoke_governing_tokens(
6457
governing_token_mint_info.key,
6558
)?;
6659

60+
// If the governing token owner voluntarily revokes their own membership then the owner must sign the transaction
61+
if *revoke_authority_info.key == token_owner_record_data.governing_token_owner {
62+
if !revoke_authority_info.is_signer {
63+
return Err(GovernanceError::GoverningTokenOwnerMustSign.into());
64+
}
65+
} else {
66+
// If its a forceful membership revocation then the governing_token_mint authority must sign the transaction
67+
assert_spl_token_mint_authority_is_signer(
68+
governing_token_mint_info,
69+
revoke_authority_info,
70+
)?;
71+
}
72+
6773
token_owner_record_data.governing_token_deposit_amount = token_owner_record_data
6874
.governing_token_deposit_amount
6975
.checked_sub(amount)

governance/program/src/state/realm_config.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,7 @@ impl RealmConfigAccount {
124124
Ok(token_config)
125125
}
126126

127-
/// Assertes the given governing token can be revoked
127+
/// Asserts the given governing token can be revoked
128128
pub fn assert_can_revoke_governing_token(
129129
&self,
130130
realm_data: &RealmV2,

governance/program/tests/process_revoke_governing_tokens.rs

Lines changed: 94 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,87 @@ async fn test_revoke_council_tokens() {
9292
assert_eq!(holding_account.amount, 0);
9393
}
9494

95+
#[tokio::test]
96+
async fn test_revoke_own_council_tokens() {
97+
// Arrange
98+
let mut governance_test = GovernanceProgramTest::start_new().await;
99+
100+
let mut realm_config_args = RealmSetupArgs::default();
101+
realm_config_args.council_token_config_args.token_type = GoverningTokenType::Membership;
102+
103+
let realm_cookie = governance_test
104+
.with_realm_using_args(&realm_config_args)
105+
.await;
106+
107+
let token_owner_record_cookie = governance_test
108+
.with_council_token_deposit(&realm_cookie)
109+
.await
110+
.unwrap();
111+
112+
// Act
113+
governance_test
114+
.revoke_governing_tokens_using_instruction(
115+
&realm_cookie,
116+
&token_owner_record_cookie,
117+
&realm_cookie.account.config.council_mint.unwrap(),
118+
&token_owner_record_cookie.token_owner,
119+
token_owner_record_cookie
120+
.account
121+
.governing_token_deposit_amount,
122+
NopOverride,
123+
None,
124+
)
125+
.await
126+
.unwrap();
127+
128+
// Assert
129+
130+
let token_owner_record = governance_test
131+
.get_token_owner_record_account(&token_owner_record_cookie.address)
132+
.await;
133+
134+
assert_eq!(token_owner_record.governing_token_deposit_amount, 0);
135+
}
136+
137+
#[tokio::test]
138+
async fn test_revoke_own_council_tokens_with_owner_must_sign_error() {
139+
// Arrange
140+
let mut governance_test = GovernanceProgramTest::start_new().await;
141+
142+
let mut realm_config_args = RealmSetupArgs::default();
143+
realm_config_args.council_token_config_args.token_type = GoverningTokenType::Membership;
144+
145+
let realm_cookie = governance_test
146+
.with_realm_using_args(&realm_config_args)
147+
.await;
148+
149+
let token_owner_record_cookie = governance_test
150+
.with_council_token_deposit(&realm_cookie)
151+
.await
152+
.unwrap();
153+
154+
// Act
155+
let err = governance_test
156+
.revoke_governing_tokens_using_instruction(
157+
&realm_cookie,
158+
&token_owner_record_cookie,
159+
&realm_cookie.account.config.council_mint.unwrap(),
160+
&token_owner_record_cookie.token_owner,
161+
token_owner_record_cookie
162+
.account
163+
.governing_token_deposit_amount,
164+
|i| i.accounts[4].is_signer = false, // revoke_authority
165+
Some(&[]),
166+
)
167+
.await
168+
.err()
169+
.unwrap();
170+
171+
// Assert
172+
173+
assert_eq!(err, GovernanceError::GoverningTokenOwnerMustSign.into());
174+
}
175+
95176
#[tokio::test]
96177
async fn test_revoke_community_tokens_with_cannot_revoke_liquid_token_error() {
97178
// Arrange
@@ -171,6 +252,7 @@ async fn test_revoke_council_tokens_with_mint_authority_must_sign_error() {
171252
&realm_cookie,
172253
&token_owner_record_cookie,
173254
&realm_cookie.account.config.council_mint.unwrap(),
255+
realm_cookie.council_mint_authority.as_ref().unwrap(),
174256
1,
175257
|i| i.accounts[4].is_signer = false, // mint_authority
176258
Some(&[]),
@@ -185,7 +267,7 @@ async fn test_revoke_council_tokens_with_mint_authority_must_sign_error() {
185267
}
186268

187269
#[tokio::test]
188-
async fn test_revoke_council_tokens_with_invalid_mint_authority_error() {
270+
async fn test_revoke_council_tokens_with_invalid_revoke_authority_error() {
189271
// Arrange
190272
let mut governance_test = GovernanceProgramTest::start_new().await;
191273

@@ -201,18 +283,16 @@ async fn test_revoke_council_tokens_with_invalid_mint_authority_error() {
201283
.await
202284
.unwrap();
203285

204-
// Try to use fake authority
205-
let mint_authority = Keypair::new();
206-
207286
// Act
208287
let err = governance_test
209288
.revoke_governing_tokens_using_instruction(
210289
&realm_cookie,
211290
&token_owner_record_cookie,
212291
&realm_cookie.account.config.council_mint.unwrap(),
292+
&Keypair::new(), // Try to use fake authority
213293
1,
214-
|i| i.accounts[4].pubkey = mint_authority.pubkey(), // mint_authority
215-
Some(&[&mint_authority]),
294+
NopOverride,
295+
None,
216296
)
217297
.await
218298
.err()
@@ -253,6 +333,7 @@ async fn test_revoke_council_tokens_with_invalid_token_holding_error() {
253333
&realm_cookie,
254334
&token_owner_record_cookie,
255335
&realm_cookie.account.config.council_mint.unwrap(),
336+
realm_cookie.council_mint_authority.as_ref().unwrap(),
256337
1,
257338
|i| i.accounts[1].pubkey = governing_token_holding_address, // governing_token_holding_address
258339
None,
@@ -295,6 +376,7 @@ async fn test_revoke_council_tokens_with_other_realm_config_account_error() {
295376
&realm_cookie,
296377
&token_owner_record_cookie,
297378
&realm_cookie.account.config.council_mint.unwrap(),
379+
realm_cookie.council_mint_authority.as_ref().unwrap(),
298380
1,
299381
|i| i.accounts[5].pubkey = realm_cookie2.realm_config.address, //realm_config_address
300382
None,
@@ -334,6 +416,7 @@ async fn test_revoke_council_tokens_with_invalid_realm_config_account_address_er
334416
&realm_cookie,
335417
&token_owner_record_cookie,
336418
&realm_cookie.account.config.council_mint.unwrap(),
419+
realm_cookie.council_mint_authority.as_ref().unwrap(),
337420
1,
338421
|i| i.accounts[5].pubkey = realm_config_address, // realm_config_address
339422
None,
@@ -376,6 +459,7 @@ async fn test_revoke_council_tokens_with_token_owner_record_for_different_mint_e
376459
&realm_cookie,
377460
&token_owner_record_cookie,
378461
&realm_cookie.account.config.council_mint.unwrap(),
462+
realm_cookie.council_mint_authority.as_ref().unwrap(),
379463
1,
380464
|i| i.accounts[2].pubkey = token_owner_record_cookie2.address, // token_owner_record_address
381465
None,
@@ -415,6 +499,7 @@ async fn test_revoke_council_tokens_with_too_large_amount_error() {
415499
&realm_cookie,
416500
&token_owner_record_cookie,
417501
&realm_cookie.account.config.council_mint.unwrap(),
502+
realm_cookie.council_mint_authority.as_ref().unwrap(),
418503
200,
419504
NopOverride,
420505
None,
@@ -451,6 +536,7 @@ async fn test_revoke_council_tokens_with_partial_revoke_amount() {
451536
&realm_cookie,
452537
&token_owner_record_cookie,
453538
&realm_cookie.account.config.council_mint.unwrap(),
539+
realm_cookie.council_mint_authority.as_ref().unwrap(),
454540
5,
455541
NopOverride,
456542
None,
@@ -499,6 +585,7 @@ async fn test_revoke_council_tokens_with_community_mint_error() {
499585
&realm_cookie,
500586
&token_owner_record_cookie,
501587
&realm_cookie.account.config.council_mint.unwrap(),
588+
realm_cookie.council_mint_authority.as_ref().unwrap(),
502589
1,
503590
|i| {
504591
i.accounts[1].pubkey = governing_token_holding_address;
@@ -543,6 +630,7 @@ async fn test_revoke_council_tokens_with_not_matching_mint_and_authority_error()
543630
&realm_cookie,
544631
&token_owner_record_cookie,
545632
&realm_cookie.account.config.council_mint.unwrap(),
633+
realm_cookie.council_mint_authority.as_ref().unwrap(),
546634
1,
547635
|i| {
548636
i.accounts[3].pubkey = governing_token_mint;

governance/program/tests/program_test/cookies.rs

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -36,18 +36,6 @@ pub struct RealmCookie {
3636
pub realm_config: RealmConfigCookie,
3737
}
3838

39-
impl RealmCookie {
40-
pub fn get_mint_authority(&self, governing_token_mint: &Pubkey) -> &Keypair {
41-
if *governing_token_mint == self.account.community_mint {
42-
&self.community_mint_authority
43-
} else if Some(*governing_token_mint) == self.account.config.council_mint {
44-
self.council_mint_authority.as_ref().unwrap()
45-
} else {
46-
panic!("Invalid governing_token_mint")
47-
}
48-
}
49-
}
50-
5139
#[derive(Debug)]
5240
pub struct RealmConfigCookie {
5341
pub address: Pubkey,

governance/program/tests/program_test/mod.rs

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1276,6 +1276,7 @@ impl GovernanceProgramTest {
12761276
realm_cookie,
12771277
token_owner_record_cookie,
12781278
&realm_cookie.account.community_mint,
1279+
&realm_cookie.community_mint_authority,
12791280
token_owner_record_cookie
12801281
.account
12811282
.governing_token_deposit_amount,
@@ -1295,6 +1296,7 @@ impl GovernanceProgramTest {
12951296
realm_cookie,
12961297
token_owner_record_cookie,
12971298
&realm_cookie.account.config.council_mint.unwrap(),
1299+
realm_cookie.council_mint_authority.as_ref().unwrap(),
12981300
token_owner_record_cookie
12991301
.account
13001302
.governing_token_deposit_amount,
@@ -1305,29 +1307,29 @@ impl GovernanceProgramTest {
13051307
}
13061308

13071309
#[allow(dead_code)]
1310+
#[allow(clippy::too_many_arguments)]
13081311
pub async fn revoke_governing_tokens_using_instruction<F: Fn(&mut Instruction)>(
13091312
&mut self,
13101313
realm_cookie: &RealmCookie,
13111314
token_owner_record_cookie: &TokenOwnerRecordCookie,
13121315
governing_token_mint: &Pubkey,
1316+
revoke_authority: &Keypair,
13131317
amount: u64,
13141318
instruction_override: F,
13151319
signers_override: Option<&[&Keypair]>,
13161320
) -> Result<(), ProgramError> {
1317-
let governing_token_mint_authority = realm_cookie.get_mint_authority(governing_token_mint);
1318-
13191321
let mut revoke_governing_tokens_ix = revoke_governing_tokens(
13201322
&self.program_id,
13211323
&realm_cookie.address,
13221324
&token_owner_record_cookie.account.governing_token_owner,
13231325
governing_token_mint,
1324-
&governing_token_mint_authority.pubkey(),
1326+
&revoke_authority.pubkey(),
13251327
amount,
13261328
);
13271329

13281330
instruction_override(&mut revoke_governing_tokens_ix);
13291331

1330-
let default_signers = &[governing_token_mint_authority];
1332+
let default_signers = &[revoke_authority];
13311333
let signers = signers_override.unwrap_or(default_signers);
13321334

13331335
self.bench

0 commit comments

Comments
 (0)