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

Commit 8e5d820

Browse files
Governance: TokenOwnerRecord locks (#6215)
* feat: Add TokenOwnerRecordLocks to TokenOwnerRecord * chore: Make fmt happy * wip: Stub SetTokenOwnerRecordLock and RemoveTokenOwnerRecordLock * wip: Store TOR locks * chore: fmt * wip: Trim existing locks * chore: Make Clippy happy * feat: Reject expired locks * wip: Read RealmConfig and assert TOR belong to the same Realm * feat: Remove TOR lock * feat: Assert tokens can't be withdrawn when active TOR locks * feat: Check configured lock authorities * fix: Fix get_max_size to include lock_authorities * feat: Implement SetRealmConfigItem * chore: Make Clippy happy * chore: Update comments * chore: SetRealmConfigItem tests * chore: test_set_realm_config_item_without_realm_config * chore: remove unused var * chore: test_set_realm_config_item_with_extended_account_size * fix: Accept None as non expiring lock * chore: SetTokenOwnerRecord tests * chore: SetTokenOwnerLock tests * chore: test_set_token_owner_record_lock_for_v1_account * chore: RemoveTokenOwnerRecordLock tests * chore: test_withdraw_governing_tokens_with_token_owner_record_lock_error * chore: Refactor TOR resize_and_serialize * chore: Make Clippy happy * chore: Refactor RealmConfigAccount serialisation with resizing * chore: Refactor trim_locks * chore: test_set_realm_config_without_existing_realm_config * chore: Make fmt happy * wip: Roundtrip lock authorities on SetRealmConfig * chore: test_set_realm_config_with_token_owner_record_lock_authorities * chore: Update comment Co-authored-by: Jon C <[email protected]> * chore: Rename lock_type to lock_id * fix: Do not trim non expiring locks set as expiry=None * chore: Update comments * chore: Fix test_add_token_owner_record_lock_authority_with_authority_already_exists_error * chore: Split trim_locks into remove_lock and remove_expired_locks * chore: Rename RemoveTokenOwnerLocks to Relinquish and make ids optional * chore: Clippy * fix: Remove lock only if type specified * chore: comments and cleanup Improve test_add_token_owner_record_lock_authority_with_authority_already_exist readability * feat: Throw error when removing TOR lock authority and it doesn't exist * feat: Support relinquishing multiple locks and for revoked authority without signature * chore: Clippy * chore: Fix merge issues * chore: Fix build warnings --------- Co-authored-by: Jon C <[email protected]>
1 parent 5345091 commit 8e5d820

23 files changed

+2746
-72
lines changed

governance/program/src/error.rs

Lines changed: 32 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -507,19 +507,47 @@ pub enum GovernanceError {
507507

508508
/// Invalid Governance for RequiredSignatory
509509
#[error("Invalid Governance for RequiredSignatory")]
510-
InvalidGovernanceForRequiredSignatory,
510+
InvalidGovernanceForRequiredSignatory, // 621
511511

512512
/// SignatoryRecord already exists
513513
#[error("Signatory Record has already been created")]
514-
SignatoryRecordAlreadyExists,
514+
SignatoryRecordAlreadyExists, // 622
515515

516516
/// Instruction has been removed
517517
#[error("Instruction has been removed")]
518-
InstructionDeprecated,
518+
InstructionDeprecated, // 623
519519

520520
/// Proposal is missing signatories required by its governance
521521
#[error("Proposal is missing required signatories")]
522-
MissingRequiredSignatories,
522+
MissingRequiredSignatories, // 624
523+
524+
/// TokenOwnerRecordLock authority must sign
525+
#[error("TokenOwnerRecordLock authority must sign")]
526+
TokenOwnerRecordLockAuthorityMustSign, // 625
527+
528+
/// TokenOwnerRecordLock is expired
529+
#[error("TokenOwnerRecordLock is expired ")]
530+
ExpiredTokenOwnerRecordLock, // 626
531+
532+
/// TokenOwnerRecord locked
533+
#[error("TokenOwnerRecord locked")]
534+
TokenOwnerRecordLocked, // 627
535+
536+
/// Invalid TokenOwnerRecordLockAuthority
537+
#[error("Invalid TokenOwnerRecordLockAuthority")]
538+
InvalidTokenOwnerRecordLockAuthority, // 628
539+
540+
/// TokenOwnerRecordLock authority already exists
541+
#[error("TokenOwnerRecordLock authority already exists")]
542+
TokenOwnerRecordLockAuthorityAlreadyExists, // 629
543+
544+
/// TokenOwnerRecordLock not found
545+
#[error("TokenOwnerRecordLock not found")]
546+
TokenOwnerRecordLockNotFound, // 630
547+
548+
/// TokenOwnerRecordLockAuthority not found
549+
#[error("TokenOwnerRecordLockAuthority not found")]
550+
TokenOwnerRecordLockAuthorityNotFound, // 631
523551
}
524552

525553
impl PrintProgramError for GovernanceError {

governance/program/src/instruction.rs

Lines changed: 153 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ use {
1616
realm::{
1717
get_governing_token_holding_address, get_realm_address,
1818
GoverningTokenConfigAccountArgs, GoverningTokenConfigArgs, RealmConfigArgs,
19-
SetRealmAuthorityAction,
19+
SetRealmAuthorityAction, SetRealmConfigItemArgs,
2020
},
2121
realm_config::get_realm_config_address,
2222
required_signatory::get_required_signatory_address,
@@ -29,6 +29,7 @@ use {
2929
borsh::{BorshDeserialize, BorshSchema, BorshSerialize},
3030
solana_program::{
3131
bpf_loader_upgradeable,
32+
clock::UnixTimestamp,
3233
instruction::{AccountMeta, Instruction},
3334
pubkey::Pubkey,
3435
system_program, sysvar,
@@ -663,6 +664,61 @@ pub enum GovernanceInstruction {
663664
/// 2. `[writable]` Beneficiary Account which would receive lamports from
664665
/// the disposed RequiredSignatory Account
665666
RemoveRequiredSignatory,
667+
668+
/// Sets TokenOwnerRecord lock for the given authority and lock id
669+
///
670+
/// 0. `[]` Realm
671+
/// 1. `[]` RealmConfig
672+
/// 2. `[writable]` TokenOwnerRecord the lock is set for
673+
/// 3. `[signer]` Lock authority issuing the lock
674+
/// 4. `[signer]` Payer
675+
/// 5. `[]` System
676+
SetTokenOwnerRecordLock {
677+
/// Custom lock id which can be used by the authority to issue
678+
/// different locks
679+
#[allow(dead_code)]
680+
lock_id: u8,
681+
682+
/// The timestamp when the lock expires or None if it never expires
683+
#[allow(dead_code)]
684+
expiry: Option<UnixTimestamp>,
685+
},
686+
687+
/// Removes all expired TokenOwnerRecord locks and if specified
688+
/// the locks identified by the given lock ids and authority
689+
///
690+
///
691+
/// 0. `[]` Realm
692+
/// 1. `[]` RealmConfig
693+
/// 2. `[writable]` TokenOwnerRecord the locks are removed from
694+
/// 3. `[signer]` Optional lock authority which issued the locks specified
695+
/// by lock_ids. If the authority is configured in RealmConfig then it
696+
/// must sign the transaction. If the authority is no longer configured
697+
/// then the locks are removed without the authority signature
698+
RelinquishTokenOwnerRecordLocks {
699+
/// Custom lock ids identifying the lock to remove
700+
/// If the lock_id is None then only expired locks are removed
701+
#[allow(dead_code)]
702+
lock_ids: Option<Vec<u8>>,
703+
},
704+
705+
/// Sets Realm config item
706+
/// Note:
707+
/// This instruction is used to set a single RealmConfig item at a time
708+
/// In the current version it only supports TokenOwnerRecordLockAuthority
709+
/// however eventually all Realm configuration items should be set using
710+
/// this instruction and SetRealmConfig instruction should be deprecated
711+
///
712+
/// 0. `[writable]` Realm account
713+
/// 1. `[writable]` RealmConfig account
714+
/// 2. `[signer]` Realm authority
715+
/// 3. `[signer]` Payer
716+
/// 4. `[]` System
717+
SetRealmConfigItem {
718+
#[allow(dead_code)]
719+
/// Config args
720+
args: SetRealmConfigItemArgs,
721+
},
666722
}
667723

668724
/// Creates CreateRealm instruction
@@ -1883,3 +1939,99 @@ pub fn complete_proposal(
18831939
data: borsh::to_vec(&instruction).unwrap(),
18841940
}
18851941
}
1942+
1943+
/// Creates SetTokenOwnerRecordLock instruction to issue TokenOwnerRecord lock
1944+
pub fn set_token_owner_record_lock(
1945+
program_id: &Pubkey,
1946+
// Accounts
1947+
realm: &Pubkey,
1948+
token_owner_record: &Pubkey,
1949+
token_owner_record_lock_authority: &Pubkey,
1950+
payer: &Pubkey,
1951+
// Args
1952+
lock_id: u8,
1953+
expiry: Option<UnixTimestamp>,
1954+
) -> Instruction {
1955+
let realm_config_address = get_realm_config_address(program_id, realm);
1956+
1957+
let accounts = vec![
1958+
AccountMeta::new_readonly(*realm, false),
1959+
AccountMeta::new_readonly(realm_config_address, false),
1960+
AccountMeta::new(*token_owner_record, false),
1961+
AccountMeta::new_readonly(*token_owner_record_lock_authority, true),
1962+
AccountMeta::new(*payer, true),
1963+
AccountMeta::new_readonly(system_program::id(), false),
1964+
];
1965+
1966+
let instruction = GovernanceInstruction::SetTokenOwnerRecordLock { lock_id, expiry };
1967+
1968+
Instruction {
1969+
program_id: *program_id,
1970+
accounts,
1971+
data: borsh::to_vec(&instruction).unwrap(),
1972+
}
1973+
}
1974+
1975+
/// Creates RelinquishTokenOwnerRecordLocks instruction to remove
1976+
/// TokenOwnerRecord locks
1977+
pub fn relinquish_token_owner_record_locks(
1978+
program_id: &Pubkey,
1979+
// Accounts
1980+
realm: &Pubkey,
1981+
token_owner_record: &Pubkey,
1982+
token_owner_record_lock_authority: Option<Pubkey>,
1983+
// Args
1984+
lock_ids: Option<Vec<u8>>,
1985+
) -> Instruction {
1986+
let realm_config_address = get_realm_config_address(program_id, realm);
1987+
1988+
let mut accounts = vec![
1989+
AccountMeta::new_readonly(*realm, false),
1990+
AccountMeta::new_readonly(realm_config_address, false),
1991+
AccountMeta::new(*token_owner_record, false),
1992+
];
1993+
1994+
if let Some(token_owner_record_lock_authority) = token_owner_record_lock_authority {
1995+
accounts.push(AccountMeta::new_readonly(
1996+
token_owner_record_lock_authority,
1997+
true,
1998+
));
1999+
}
2000+
2001+
let instruction = GovernanceInstruction::RelinquishTokenOwnerRecordLocks { lock_ids };
2002+
2003+
Instruction {
2004+
program_id: *program_id,
2005+
accounts,
2006+
data: borsh::to_vec(&instruction).unwrap(),
2007+
}
2008+
}
2009+
2010+
/// Creates SetRealmConfigItem instruction to set realm config
2011+
pub fn set_realm_config_item(
2012+
program_id: &Pubkey,
2013+
// Accounts
2014+
realm: &Pubkey,
2015+
realm_authority: &Pubkey,
2016+
payer: &Pubkey,
2017+
// Args
2018+
args: SetRealmConfigItemArgs,
2019+
) -> Instruction {
2020+
let realm_config_address = get_realm_config_address(program_id, realm);
2021+
2022+
let accounts = vec![
2023+
AccountMeta::new(*realm, false),
2024+
AccountMeta::new(realm_config_address, false),
2025+
AccountMeta::new_readonly(*realm_authority, true),
2026+
AccountMeta::new(*payer, true),
2027+
AccountMeta::new_readonly(system_program::id(), false),
2028+
];
2029+
2030+
let instruction = GovernanceInstruction::SetRealmConfigItem { args };
2031+
2032+
Instruction {
2033+
program_id: *program_id,
2034+
accounts,
2035+
data: borsh::to_vec(&instruction).unwrap(),
2036+
}
2037+
}

governance/program/src/processor/mod.rs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ mod process_finalize_vote;
1919
mod process_flag_transaction_error;
2020
mod process_insert_transaction;
2121
mod process_refund_proposal_deposit;
22+
mod process_relinquish_token_owner_record_locks;
2223
mod process_relinquish_vote;
2324
mod process_remove_required_signatory;
2425
mod process_remove_transaction;
@@ -27,6 +28,8 @@ mod process_set_governance_config;
2728
mod process_set_governance_delegate;
2829
mod process_set_realm_authority;
2930
mod process_set_realm_config;
31+
mod process_set_realm_config_item;
32+
mod process_set_token_owner_record_lock;
3033
mod process_sign_off_proposal;
3134
mod process_update_program_metadata;
3235
mod process_withdraw_governing_tokens;
@@ -52,6 +55,7 @@ use {
5255
process_flag_transaction_error::*,
5356
process_insert_transaction::*,
5457
process_refund_proposal_deposit::*,
58+
process_relinquish_token_owner_record_locks::*,
5559
process_relinquish_vote::*,
5660
process_remove_required_signatory::*,
5761
process_remove_transaction::*,
@@ -60,6 +64,8 @@ use {
6064
process_set_governance_delegate::*,
6165
process_set_realm_authority::*,
6266
process_set_realm_config::*,
67+
process_set_realm_config_item::*,
68+
process_set_token_owner_record_lock::*,
6369
process_sign_off_proposal::*,
6470
process_update_program_metadata::*,
6571
process_withdraw_governing_tokens::*,
@@ -243,5 +249,17 @@ pub fn process_instruction(
243249
GovernanceInstruction::RemoveRequiredSignatory => {
244250
process_remove_required_signatory(program_id, accounts)
245251
}
252+
253+
GovernanceInstruction::SetTokenOwnerRecordLock { lock_id, expiry } => {
254+
process_set_token_owner_record_lock(program_id, accounts, lock_id, expiry)
255+
}
256+
257+
GovernanceInstruction::RelinquishTokenOwnerRecordLocks { lock_ids } => {
258+
process_relinquish_token_owner_record_locks(program_id, accounts, lock_ids)
259+
}
260+
261+
GovernanceInstruction::SetRealmConfigItem { args } => {
262+
process_set_realm_config_item(program_id, accounts, args)
263+
}
246264
}
247265
}

governance/program/src/processor/process_create_realm.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,12 +95,14 @@ pub fn process_create_realm(
9595
let community_token_config = resolve_governing_token_config(
9696
account_info_iter,
9797
&realm_config_args.community_token_config_args,
98+
None,
9899
)?;
99100

100101
// 13, 14
101102
let council_token_config = resolve_governing_token_config(
102103
account_info_iter,
103104
&realm_config_args.council_token_config_args,
105+
None,
104106
)?;
105107

106108
let realm_config_data = RealmConfigAccount {

governance/program/src/processor/process_create_token_owner_record.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,8 @@ pub fn process_create_token_owner_record(
5555
outstanding_proposal_count: 0,
5656
version: TOKEN_OWNER_RECORD_LAYOUT_VERSION,
5757
reserved: [0; 6],
58-
reserved_v2: [0; 128],
58+
reserved_v2: [0; 124],
59+
locks: vec![],
5960
};
6061

6162
create_and_serialize_account_signed(

governance/program/src/processor/process_deposit_governing_tokens.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,8 @@ pub fn process_deposit_governing_tokens(
109109
outstanding_proposal_count: 0,
110110
version: TOKEN_OWNER_RECORD_LAYOUT_VERSION,
111111
reserved: [0; 6],
112-
reserved_v2: [0; 128],
112+
reserved_v2: [0; 124],
113+
locks: vec![],
113114
};
114115

115116
create_and_serialize_account_signed(
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
//! Program state processor
2+
3+
use {
4+
crate::{
5+
error::GovernanceError,
6+
state::{
7+
realm::get_realm_data, realm_config::get_realm_config_data_for_realm,
8+
token_owner_record::get_token_owner_record_data_for_realm,
9+
},
10+
},
11+
solana_program::{
12+
account_info::{next_account_info, AccountInfo},
13+
clock::Clock,
14+
entrypoint::ProgramResult,
15+
pubkey::Pubkey,
16+
sysvar::Sysvar,
17+
},
18+
};
19+
20+
/// Processes RelinquishTokenOwnerRecordLocks instruction
21+
pub fn process_relinquish_token_owner_record_locks(
22+
program_id: &Pubkey,
23+
accounts: &[AccountInfo],
24+
lock_ids: Option<Vec<u8>>,
25+
) -> ProgramResult {
26+
let account_info_iter = &mut accounts.iter();
27+
28+
let realm_info = next_account_info(account_info_iter)?; // 0
29+
let realm_config_info = next_account_info(account_info_iter)?; // 1
30+
let token_owner_record_info = next_account_info(account_info_iter)?; // 2
31+
32+
let realm_data = get_realm_data(program_id, realm_info)?;
33+
let realm_config_data =
34+
get_realm_config_data_for_realm(program_id, realm_config_info, realm_info.key)?;
35+
36+
let mut token_owner_record_data = get_token_owner_record_data_for_realm(
37+
program_id,
38+
token_owner_record_info,
39+
&realm_config_data.realm,
40+
)?;
41+
42+
if let Some(lock_ids) = lock_ids {
43+
let token_owner_record_lock_authority_info = next_account_info(account_info_iter)?; // 3
44+
45+
if realm_config_data
46+
.get_token_config(&realm_data, &token_owner_record_data.governing_token_mint)?
47+
.lock_authorities
48+
.contains(token_owner_record_lock_authority_info.key)
49+
{
50+
// If the authority is a configured lock authority it must sign the transaction
51+
if !token_owner_record_lock_authority_info.is_signer {
52+
return Err(GovernanceError::TokenOwnerRecordLockAuthorityMustSign.into());
53+
}
54+
}
55+
56+
// Remove the locks
57+
for lock_id in lock_ids {
58+
token_owner_record_data
59+
.remove_lock(lock_id, token_owner_record_lock_authority_info.key)?;
60+
}
61+
}
62+
63+
// Trim expired locks
64+
let clock = Clock::get()?;
65+
token_owner_record_data.remove_expired_locks(clock.unix_timestamp);
66+
67+
token_owner_record_data.serialize(&mut token_owner_record_info.data.borrow_mut()[..])?;
68+
69+
Ok(())
70+
}

0 commit comments

Comments
 (0)