Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Governance: Realm authority validation #2787

Merged
merged 5 commits into from Jan 24, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
16 changes: 11 additions & 5 deletions governance/program/src/instruction.rs
Expand Up @@ -410,10 +410,11 @@ pub enum GovernanceInstruction {
///
/// 0. `[writable]` Realm account
/// 1. `[signer]` Current Realm authority
/// 2. `[]` New realm authority. Must be one of the realm governances when set
SebastianBor marked this conversation as resolved.
Show resolved Hide resolved
SetRealmAuthority {
#[allow(dead_code)]
/// New realm authority or None to remove authority
new_realm_authority: Option<Pubkey>,
/// Indicates whether the realm authority should be removed and set to None
remove_authority: bool,
SebastianBor marked this conversation as resolved.
Show resolved Hide resolved
},

/// Sets realm config
Expand Down Expand Up @@ -1271,15 +1272,20 @@ pub fn set_realm_authority(
new_realm_authority: &Option<Pubkey>,
// Args
) -> Instruction {
let accounts = vec![
let mut accounts = vec![
AccountMeta::new(*realm, false),
AccountMeta::new_readonly(*realm_authority, true),
];

let instruction = GovernanceInstruction::SetRealmAuthority {
new_realm_authority: *new_realm_authority,
let remove_authority = if let Some(new_realm_authority) = new_realm_authority {
accounts.push(AccountMeta::new_readonly(*new_realm_authority, false));
false
} else {
true
};

let instruction = GovernanceInstruction::SetRealmAuthority { remove_authority };

Instruction {
program_id: *program_id,
accounts,
Expand Down
6 changes: 3 additions & 3 deletions governance/program/src/processor/mod.rs
Expand Up @@ -190,9 +190,9 @@ pub fn process_instruction(
GovernanceInstruction::FlagInstructionError {} => {
process_flag_instruction_error(program_id, accounts)
}
GovernanceInstruction::SetRealmAuthority {
new_realm_authority,
} => process_set_realm_authority(program_id, accounts, new_realm_authority),
GovernanceInstruction::SetRealmAuthority { remove_authority } => {
process_set_realm_authority(program_id, accounts, remove_authority)
}
GovernanceInstruction::SetRealmConfig { config_args } => {
process_set_realm_config(program_id, accounts, config_args)
}
Expand Down
19 changes: 17 additions & 2 deletions governance/program/src/processor/process_set_realm_authority.rs
Expand Up @@ -7,13 +7,16 @@ use solana_program::{
pubkey::Pubkey,
};

use crate::{error::GovernanceError, state::realm::get_realm_data_for_authority};
use crate::{
error::GovernanceError,
state::{governance::assert_governance_for_realm, realm::get_realm_data_for_authority},
};

/// Processes SetRealmAuthority instruction
pub fn process_set_realm_authority(
program_id: &Pubkey,
accounts: &[AccountInfo],
new_realm_authority: Option<Pubkey>,
remove_authority: bool,
) -> ProgramResult {
let account_info_iter = &mut accounts.iter();

Expand All @@ -27,6 +30,18 @@ pub fn process_set_realm_authority(
return Err(GovernanceError::RealmAuthorityMustSign.into());
}

let new_realm_authority = if remove_authority {
None
} else {
// Ensure the new realm authority is one of the governances from the realm
// Note: This is not a security feature because governance creation is only gated with min_community_tokens_to_create_governance
// The check is done to prevent scenarios where the authority could be accidentally set to a wrong or none existing account
let new_realm_authority_info = next_account_info(account_info_iter)?; // 2
assert_governance_for_realm(program_id, new_realm_authority_info, realm_info.key)?;

Some(*new_realm_authority_info.key)
};

realm_data.authority = new_realm_authority;

realm_data.serialize(&mut *realm_info.data.borrow_mut())?;
Expand Down
10 changes: 10 additions & 0 deletions governance/program/src/state/governance.rs
Expand Up @@ -127,6 +127,16 @@ pub fn get_governance_data_for_realm(
Ok(governance_data)
}

/// Checks the given account is a governance account and belongs to the given realm
pub fn assert_governance_for_realm(
program_id: &Pubkey,
governance_info: &AccountInfo,
realm: &Pubkey,
) -> Result<(), ProgramError> {
get_governance_data_for_realm(program_id, governance_info, realm)?;
Ok(())
}

/// Returns ProgramGovernance PDA seeds
pub fn get_program_governance_address_seeds<'a>(
realm: &'a Pubkey,
Expand Down
2 changes: 1 addition & 1 deletion governance/program/src/state/legacy.rs
Expand Up @@ -83,7 +83,7 @@ pub struct RealmV1 {
pub reserved: [u8; 8],

/// Realm authority. The authority must sign transactions which update the realm config
/// The authority can be transferer to Realm Governance and hence make the Realm self governed through proposals
/// The authority should be transferred to Realm Governance to make the Realm self governed through proposals
pub authority: Option<Pubkey>,

/// Governance Realm name
Expand Down
2 changes: 1 addition & 1 deletion governance/program/src/state/realm.rs
Expand Up @@ -70,7 +70,7 @@ pub struct Realm {
pub reserved: [u8; 8],

/// Realm authority. The authority must sign transactions which update the realm config
/// The authority can be transferer to Realm Governance and hence make the Realm self governed through proposals
/// The authority should be transferred to Realm Governance to make the Realm self governed through proposals
pub authority: Option<Pubkey>,

/// Governance Realm name
Expand Down
78 changes: 77 additions & 1 deletion governance/program/tests/process_set_realm_authority.rs
Expand Up @@ -7,6 +7,7 @@ mod program_test;

use program_test::*;
use spl_governance::error::GovernanceError;
use spl_governance_tools::error::GovernanceToolsError;

#[tokio::test]
async fn test_set_realm_authority() {
Expand All @@ -15,7 +16,23 @@ async fn test_set_realm_authority() {

let realm_cookie = governance_test.with_realm().await;

let new_realm_authority = Pubkey::new_unique();
let governed_account_cookie = governance_test.with_governed_account().await;

let token_owner_record_cookie = governance_test
.with_community_token_deposit(&realm_cookie)
.await
.unwrap();

let account_governance_cookie = governance_test
.with_account_governance(
&realm_cookie,
&governed_account_cookie,
&token_owner_record_cookie,
)
.await
.unwrap();

let new_realm_authority = account_governance_cookie.address;

// Act
governance_test
Expand All @@ -31,6 +48,26 @@ async fn test_set_realm_authority() {
assert_eq!(realm_account.authority, Some(new_realm_authority));
}

#[tokio::test]
async fn test_set_realm_authority_with_non_existing_new_authority_error() {
// Arrange
let mut governance_test = GovernanceProgramTest::start_new().await;

let realm_cookie = governance_test.with_realm().await;

let new_realm_authority = Pubkey::new_unique();

// Act
let err = governance_test
.set_realm_authority(&realm_cookie, &Some(new_realm_authority))
.await
.err()
.unwrap();

// Assert
assert_eq!(err, GovernanceToolsError::AccountDoesNotExist.into());
}

#[tokio::test]
async fn test_set_realm_authority_to_none() {
// Arrange
Expand Down Expand Up @@ -125,3 +162,42 @@ async fn test_set_realm_authority_with_authority_must_sign_error() {
// Assert
assert_eq!(err, GovernanceError::RealmAuthorityMustSign.into());
}

#[tokio::test]
async fn test_set_realm_authority_with_governance_from_other_realm_error() {
// Arrange
let mut governance_test = GovernanceProgramTest::start_new().await;

let realm_cookie = governance_test.with_realm().await;

// Setup other realm
let realm_cookie2 = governance_test.with_realm().await;

let governed_account_cookie2 = governance_test.with_governed_account().await;

let token_owner_record_cookie2 = governance_test
.with_community_token_deposit(&realm_cookie2)
.await
.unwrap();

let account_governance_cookie2 = governance_test
.with_account_governance(
&realm_cookie2,
&governed_account_cookie2,
&token_owner_record_cookie2,
)
.await
.unwrap();

let new_realm_authority = account_governance_cookie2.address;

// Act
let err = governance_test
.set_realm_authority(&realm_cookie, &Some(new_realm_authority))
.await
.err()
.unwrap();

// Assert
assert_eq!(err, GovernanceError::InvalidRealmForGovernance.into());
}