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

Commit 6f15938

Browse files
authored
Governance: Add proposal and payer to ProposalDeposit account (#141)
* feat: Add proposal and payer to ProposalDeposit account * chore: Update tests * feat: Use referential fields to check ProposalDeposit * chore: Make Clippy happy * chore: Update Proposal deposit amount comments * chore: Update deposit amount comment
1 parent 3e4e2d6 commit 6f15938

File tree

12 files changed

+266
-67
lines changed

12 files changed

+266
-67
lines changed

governance/program/src/error.rs

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -447,9 +447,9 @@ pub enum GovernanceError {
447447
#[error("Cannot refund ProposalDeposit")]
448448
CannotRefundProposalDeposit, // 607
449449

450-
/// Invalid ProposalDeposit account address
451-
#[error("Invalid ProposalDeposit account address")]
452-
InvalidProposalDepositAccountAddress, // 608
450+
///Invalid Proposal for ProposalDeposit
451+
#[error("Invalid Proposal for ProposalDeposit")]
452+
InvalidProposalForProposalDeposit, // 608
453453

454454
/// Invalid deposit_exempt_proposal_count
455455
#[error("Invalid deposit_exempt_proposal_count")]
@@ -458,6 +458,10 @@ pub enum GovernanceError {
458458
/// GoverningTokenMint not allowed to vote
459459
#[error("GoverningTokenMint not allowed to vote")]
460460
GoverningTokenMintNotAllowedToVote, // 610
461+
462+
///Invalid deposit Payer for ProposalDeposit
463+
#[error("Invalid deposit Payer for ProposalDeposit")]
464+
InvalidDepositPayerForProposalDeposit, // 611
461465
}
462466

463467
impl PrintProgramError for GovernanceError {

governance/program/src/processor/process_create_proposal.rs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -189,7 +189,12 @@ pub fn process_create_proposal(
189189
let proposal_deposit_amount = governance_data.get_proposal_deposit_amount();
190190
if proposal_deposit_amount > 0 {
191191
let proposal_deposit_info = next_account_info(account_info_iter)?; // *10
192-
let proposal_deposit_data = ProposalDeposit {};
192+
let proposal_deposit_data = ProposalDeposit {
193+
account_type: GovernanceAccountType::ProposalDeposit,
194+
proposal: *proposal_info.key,
195+
deposit_payer: *payer_info.key,
196+
reserved: [0; 64],
197+
};
193198

194199
create_and_serialize_account_signed::<ProposalDeposit>(
195200
payer_info,

governance/program/src/processor/process_refund_proposal_deposit.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@ use solana_program::{
88
use spl_governance_tools::account::dispose_account;
99

1010
use crate::state::{
11-
proposal::get_proposal_data, proposal_deposit::assert_is_valid_proposal_deposit_account,
11+
proposal::get_proposal_data,
12+
proposal_deposit::get_proposal_deposit_data_for_proposal_and_deposit_payer,
1213
};
1314

1415
/// Processes RefundProposalDeposit instruction
@@ -28,7 +29,7 @@ pub fn process_refund_proposal_deposit(
2829
proposal_data.assert_can_refund_proposal_deposit()?;
2930

3031
// Assert we are disposing a deposit which belongs to the Proposal and the deposit payer
31-
assert_is_valid_proposal_deposit_account(
32+
let _proposal_deposit_data = get_proposal_deposit_data_for_proposal_and_deposit_payer(
3233
program_id,
3334
proposal_deposit_info,
3435
proposal_info.key,

governance/program/src/state/enums.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,9 @@ pub enum GovernanceAccountType {
8787
/// Proposal Signatory account
8888
/// V2 adds extra reserved space reserved_v2
8989
SignatoryRecordV2,
90+
91+
/// Proposal deposit account
92+
ProposalDeposit,
9093
}
9194

9295
impl Default for GovernanceAccountType {

governance/program/src/state/governance.rs

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,8 @@ pub fn is_governance_v2_account_type(account_type: &GovernanceAccountType) -> bo
145145
| GovernanceAccountType::ProposalTransactionV2
146146
| GovernanceAccountType::VoteRecordV1
147147
| GovernanceAccountType::VoteRecordV2
148-
| GovernanceAccountType::ProgramMetadata => false,
148+
| GovernanceAccountType::ProgramMetadata
149+
| GovernanceAccountType::ProposalDeposit => false,
149150
}
150151
}
151152

@@ -178,7 +179,8 @@ pub fn try_get_governance_v2_type_for_v1(
178179
| GovernanceAccountType::ProposalTransactionV2
179180
| GovernanceAccountType::VoteRecordV1
180181
| GovernanceAccountType::VoteRecordV2
181-
| GovernanceAccountType::ProgramMetadata => None,
182+
| GovernanceAccountType::ProgramMetadata
183+
| GovernanceAccountType::ProposalDeposit => None,
182184
}
183185
}
184186

@@ -222,6 +224,7 @@ impl GovernanceV2 {
222224
| GovernanceAccountType::ProposalTransactionV2
223225
| GovernanceAccountType::ProposalV2
224226
| GovernanceAccountType::ProgramMetadata
227+
| GovernanceAccountType::ProposalDeposit
225228
| GovernanceAccountType::RealmV2
226229
| GovernanceAccountType::TokenOwnerRecordV2
227230
| GovernanceAccountType::SignatoryRecordV2 => {
@@ -348,12 +351,20 @@ impl GovernanceV2 {
348351
Ok(vote_tipping)
349352
}
350353

351-
/// Returns the required deposit amount for creating a Proposal based on the number of active proposals
352-
/// and the configured number of proposals exempt from the deposits
353-
/// The deposit is not payed until there are more active Proposal than the exempt amount
354+
/// Returns the required deposit amount for creating Nth Proposal based on the number of active proposals
355+
/// where N equals to active_proposal_count - deposit_exempt_proposal_count
356+
/// The deposit is not payed unless there are more active Proposal than the exempt amount
357+
///
358+
/// Note: The exact deposit payed for Nth Proposal is N*SECURITY_DEPOSIT_BASE_LAMPORTS + min_rent_for(ProposalDeposit)
359+
///
360+
/// Note: Although the deposit amount payed for Nth proposal is linear the total deposit amount required to create N proposals is sum of arithmetic series
361+
/// Dn = N*r + d*N*(N+1)/2
362+
// where:
363+
// Dn - The total deposit amount required to create N proposals
364+
// N = active_proposal_count - deposit_exempt_proposal_count
365+
// d = SECURITY_DEPOSIT_BASE_LAMPORTS
366+
// r = min rent amount for ProposalDeposit
354367
pub fn get_proposal_deposit_amount(&self) -> u64 {
355-
// Charge a security deposit of n(n+1)/2 for n proposals to discourage spam
356-
// where n = active_proposal_count - deposit_exempt_proposal_count
357368
self.active_proposal_count
358369
.saturating_sub(self.config.deposit_exempt_proposal_count as u64)
359370
.saturating_mul(SECURITY_DEPOSIT_BASE_LAMPORTS)

governance/program/src/state/legacy.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -151,7 +151,8 @@ pub fn is_governance_v1_account_type(account_type: &GovernanceAccountType) -> bo
151151
| GovernanceAccountType::ProposalTransactionV2
152152
| GovernanceAccountType::VoteRecordV1
153153
| GovernanceAccountType::VoteRecordV2
154-
| GovernanceAccountType::ProgramMetadata => false,
154+
| GovernanceAccountType::ProgramMetadata
155+
| GovernanceAccountType::ProposalDeposit => false,
155156
}
156157
}
157158

governance/program/src/state/proposal_deposit.rs

Lines changed: 70 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,40 @@
11
//! Proposal deposit account
22
33
use borsh::{BorshDeserialize, BorshSchema, BorshSerialize};
4-
use solana_program::{account_info::AccountInfo, program_error::ProgramError, pubkey::Pubkey};
5-
use spl_governance_tools::{account::AccountMaxSize, error::GovernanceToolsError};
4+
use solana_program::{
5+
account_info::AccountInfo, program_error::ProgramError, program_pack::IsInitialized,
6+
pubkey::Pubkey,
7+
};
8+
use spl_governance_tools::account::{get_account_data, AccountMaxSize};
69

7-
use crate::error::GovernanceError;
10+
use crate::{error::GovernanceError, state::enums::GovernanceAccountType};
811

912
/// Proposal deposit account
10-
/// The account has no data and is used to limit spam of proposals
13+
/// The account is used to limit spam of proposals
1114
#[derive(Clone, Debug, PartialEq, BorshDeserialize, BorshSerialize, BorshSchema)]
12-
pub struct ProposalDeposit {}
15+
pub struct ProposalDeposit {
16+
/// Governance account type
17+
pub account_type: GovernanceAccountType,
18+
19+
/// The Proposal the deposit belongs to
20+
pub proposal: Pubkey,
21+
22+
/// The account which payed for the deposit
23+
pub deposit_payer: Pubkey,
24+
25+
/// Reserved
26+
pub reserved: [u8; 64],
27+
}
1328

1429
impl AccountMaxSize for ProposalDeposit {
1530
fn get_max_size(&self) -> Option<usize> {
16-
Some(0)
31+
Some(1 + 32 + 32 + 64)
32+
}
33+
}
34+
35+
impl IsInitialized for ProposalDeposit {
36+
fn is_initialized(&self) -> bool {
37+
self.account_type == GovernanceAccountType::ProposalDeposit
1738
}
1839
}
1940

@@ -42,23 +63,55 @@ pub fn get_proposal_deposit_address(
4263
.0
4364
}
4465

45-
/// Asserts the given ProposalDeposit account address is derived from to the Proposal and the deposit payer
46-
pub fn assert_is_valid_proposal_deposit_account(
66+
/// Deserializes ProposalDeposit account and checks owner program and account type
67+
pub fn get_proposal_deposit_data(
68+
program_id: &Pubkey,
69+
proposal_deposit_info: &AccountInfo,
70+
) -> Result<ProposalDeposit, ProgramError> {
71+
get_account_data::<ProposalDeposit>(program_id, proposal_deposit_info)
72+
}
73+
74+
/// Deserializes ProposalDeposit account
75+
/// 1) Checks owner program and account type
76+
/// 2) Asserts it belongs to the given Proposal and deposit Payer
77+
pub fn get_proposal_deposit_data_for_proposal_and_deposit_payer(
4778
program_id: &Pubkey,
4879
proposal_deposit_info: &AccountInfo,
4980
proposal: &Pubkey,
5081
proposal_deposit_payer: &Pubkey,
51-
) -> Result<(), ProgramError> {
52-
if proposal_deposit_info.owner != program_id {
53-
return Err(GovernanceToolsError::InvalidAccountOwner.into());
54-
}
82+
) -> Result<ProposalDeposit, ProgramError> {
83+
let proposal_deposit_data = get_proposal_deposit_data(program_id, proposal_deposit_info)?;
5584

56-
let proposal_deposit_address =
57-
get_proposal_deposit_address(program_id, proposal, proposal_deposit_payer);
85+
if proposal_deposit_data.proposal != *proposal {
86+
return Err(GovernanceError::InvalidProposalForProposalDeposit.into());
87+
}
5888

59-
if *proposal_deposit_info.key != proposal_deposit_address {
60-
return Err(GovernanceError::InvalidProposalDepositAccountAddress.into());
89+
if proposal_deposit_data.deposit_payer != *proposal_deposit_payer {
90+
return Err(GovernanceError::InvalidDepositPayerForProposalDeposit.into());
6191
}
6292

63-
Ok(())
93+
Ok(proposal_deposit_data)
94+
}
95+
96+
#[cfg(test)]
97+
mod test {
98+
99+
use super::*;
100+
101+
#[test]
102+
fn test_max_size() {
103+
// Arrange
104+
let proposal_deposit_data = ProposalDeposit {
105+
account_type: GovernanceAccountType::ProposalDeposit,
106+
proposal: Pubkey::new_unique(),
107+
deposit_payer: Pubkey::new_unique(),
108+
reserved: [0; 64],
109+
};
110+
111+
// Act
112+
let size = proposal_deposit_data.try_to_vec().unwrap().len();
113+
114+
// Assert
115+
assert_eq!(proposal_deposit_data.get_max_size(), Some(size));
116+
}
64117
}

governance/program/src/state/realm.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -190,7 +190,8 @@ pub fn is_realm_account_type(account_type: &GovernanceAccountType) -> bool {
190190
| GovernanceAccountType::ProposalTransactionV2
191191
| GovernanceAccountType::VoteRecordV1
192192
| GovernanceAccountType::VoteRecordV2
193-
| GovernanceAccountType::ProgramMetadata => false,
193+
| GovernanceAccountType::ProgramMetadata
194+
| GovernanceAccountType::ProposalDeposit => false,
194195
}
195196
}
196197

governance/program/tests/process_create_proposal.rs

Lines changed: 45 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ use spl_governance::{
1111
error::GovernanceError,
1212
state::{enums::VoteThreshold, governance::SECURITY_DEPOSIT_BASE_LAMPORTS},
1313
};
14+
use spl_governance_tools::account::AccountMaxSize;
1415

1516
#[tokio::test]
1617
async fn test_create_community_proposal() {
@@ -653,18 +654,26 @@ async fn test_create_proposal_with_security_deposit() {
653654

654655
// Assert
655656

656-
let _proposal_deposit_account = governance_test
657-
.get_proposal_deposit_account(&proposal_cookie.proposal_deposit)
657+
let proposal_deposit_account = governance_test
658+
.get_proposal_deposit_account(&proposal_cookie.proposal_deposit.address)
658659
.await;
659660

661+
assert_eq!(
662+
proposal_cookie.proposal_deposit.account,
663+
proposal_deposit_account
664+
);
665+
660666
let proposal_deposit_account_info = governance_test
661667
.bench
662-
.get_account(&proposal_cookie.proposal_deposit)
668+
.get_account(&proposal_cookie.proposal_deposit.address)
663669
.await
664670
.unwrap();
665671

666-
let expected_lamports =
667-
governance_test.bench.rent.minimum_balance(0) + SECURITY_DEPOSIT_BASE_LAMPORTS;
672+
let expected_lamports = governance_test
673+
.bench
674+
.rent
675+
.minimum_balance(proposal_deposit_account.get_max_size().unwrap())
676+
+ SECURITY_DEPOSIT_BASE_LAMPORTS;
668677

669678
assert_eq!(expected_lamports, proposal_deposit_account_info.lamports);
670679
}
@@ -715,32 +724,56 @@ async fn test_create_multiple_proposals_with_security_deposits() {
715724
// Assert
716725
let proposal_deposit_account_info1 = governance_test
717726
.bench
718-
.get_account(&proposal_cookie1.proposal_deposit)
727+
.get_account(&proposal_cookie1.proposal_deposit.address)
719728
.await;
720729

721730
assert_eq!(None, proposal_deposit_account_info1);
722731

723732
// Proposal 2
733+
let proposal_deposit_account2 = governance_test
734+
.get_proposal_deposit_account(&proposal_cookie2.proposal_deposit.address)
735+
.await;
736+
737+
assert_eq!(
738+
proposal_cookie2.proposal_deposit.account,
739+
proposal_deposit_account2
740+
);
741+
724742
let proposal_deposit_account_info2 = governance_test
725743
.bench
726-
.get_account(&proposal_cookie2.proposal_deposit)
744+
.get_account(&proposal_cookie2.proposal_deposit.address)
727745
.await
728746
.unwrap();
729747

730-
let expected_lamports =
731-
governance_test.bench.rent.minimum_balance(0) + SECURITY_DEPOSIT_BASE_LAMPORTS;
748+
let expected_lamports = governance_test
749+
.bench
750+
.rent
751+
.minimum_balance(proposal_deposit_account2.get_max_size().unwrap())
752+
+ SECURITY_DEPOSIT_BASE_LAMPORTS;
732753

733754
assert_eq!(expected_lamports, proposal_deposit_account_info2.lamports);
734755

735756
// Proposal 3
757+
let proposal_deposit_account3 = governance_test
758+
.get_proposal_deposit_account(&proposal_cookie3.proposal_deposit.address)
759+
.await;
760+
761+
assert_eq!(
762+
proposal_cookie3.proposal_deposit.account,
763+
proposal_deposit_account3
764+
);
765+
736766
let proposal_deposit_account_info3 = governance_test
737767
.bench
738-
.get_account(&proposal_cookie3.proposal_deposit)
768+
.get_account(&proposal_cookie3.proposal_deposit.address)
739769
.await
740770
.unwrap();
741771

742-
let expected_lamports =
743-
governance_test.bench.rent.minimum_balance(0) + 2 * SECURITY_DEPOSIT_BASE_LAMPORTS;
772+
let expected_lamports = governance_test
773+
.bench
774+
.rent
775+
.minimum_balance(proposal_deposit_account3.get_max_size().unwrap())
776+
+ 2 * SECURITY_DEPOSIT_BASE_LAMPORTS;
744777

745778
assert_eq!(expected_lamports, proposal_deposit_account_info3.lamports);
746779
}

0 commit comments

Comments
 (0)