Governance: Change timestamps and time periods to UnixTimestamp (#1984)

* feat: change signing_off_at and closed_at to UnixTimestamp

* feat: change executing_at to UnixTimestamp

* feat: change voting_at and voting_completed_at to UnixTimestamp

* feat: change draft_at to UnixTimestamp

* chore: cleanup slot/timestamp comments

* chore: fix merge conflicts
This commit is contained in:
Sebastian Bor 2021-06-29 23:03:09 +01:00 committed by GitHub
parent 3c561381fd
commit 775e536842
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 192 additions and 154 deletions

View File

@ -133,7 +133,7 @@ pub enum GovernanceInstruction {
transfer_upgrade_authority: bool,
},
/// Creates Proposal account for Instructions that will be executed at various slots in the future
/// Creates Proposal account for Instructions that will be executed at some point in the future
///
/// 0. `[writable]` Proposal account. PDA seeds ['governance',governance, governing_token_mint, proposal_index]
/// 1. `[writable]` Governance account
@ -202,8 +202,8 @@ pub enum GovernanceInstruction {
/// Instruction index to be inserted at.
index: u16,
#[allow(dead_code)]
/// Slot waiting time between vote period ending and this being eligible for execution
hold_up_time: u64,
/// Waiting time (in seconds) between vote period ending and this being eligible for execution
hold_up_time: u32,
#[allow(dead_code)]
/// Instruction Data
@ -907,7 +907,7 @@ pub fn insert_instruction(
payer: &Pubkey,
// Args
index: u16,
hold_up_time: u64,
hold_up_time: u32,
instruction: InstructionData,
) -> Instruction {
let proposal_instruction_address =

View File

@ -37,7 +37,7 @@ pub fn process_cancel_proposal(program_id: &Pubkey, accounts: &[AccountInfo]) ->
token_owner_record_data.assert_token_owner_or_delegate_is_signer(governance_authority_info)?;
proposal_data.state = ProposalState::Cancelled;
proposal_data.closed_at = Some(clock.slot);
proposal_data.closed_at = Some(clock.unix_timestamp);
proposal_data.serialize(&mut *proposal_info.data.borrow_mut())?;

View File

@ -61,7 +61,7 @@ pub fn process_cast_vote(
governance_info.key,
governing_token_mint_info.key,
)?;
proposal_data.assert_can_cast_vote(&governance_data.config, clock.slot)?;
proposal_data.assert_can_cast_vote(&governance_data.config, clock.unix_timestamp)?;
let mut token_owner_record_data = get_token_owner_record_data_for_realm_and_governing_mint(
program_id,
@ -105,7 +105,11 @@ pub fn process_cast_vote(
};
let governing_token_supply = get_spl_token_mint_supply(governing_token_mint_info)?;
proposal_data.try_tip_vote(governing_token_supply, &governance_data.config, clock.slot);
proposal_data.try_tip_vote(
governing_token_supply,
&governance_data.config,
clock.unix_timestamp,
);
proposal_data.serialize(&mut *proposal_info.data.borrow_mut())?;

View File

@ -81,7 +81,7 @@ pub fn process_create_proposal(
name,
description_link,
draft_at: clock.slot,
draft_at: clock.unix_timestamp,
signing_off_at: None,
voting_at: None,
voting_completed_at: None,

View File

@ -39,7 +39,8 @@ pub fn process_execute_instruction(program_id: &Pubkey, accounts: &[AccountInfo]
proposal_info.key,
)?;
proposal_data.assert_can_execute_instruction(&proposal_instruction_data, clock.slot)?;
proposal_data
.assert_can_execute_instruction(&proposal_instruction_data, clock.unix_timestamp)?;
// Execute instruction with Governance PDA as signer
let instruction = Instruction::from(&proposal_instruction_data.instruction);
@ -59,7 +60,7 @@ pub fn process_execute_instruction(program_id: &Pubkey, accounts: &[AccountInfo]
// Update proposal and instruction accounts
if proposal_data.state == ProposalState::Succeeded {
proposal_data.executing_at = Some(clock.slot);
proposal_data.executing_at = Some(clock.unix_timestamp);
proposal_data.state = ProposalState::Executing;
}
@ -71,13 +72,13 @@ pub fn process_execute_instruction(program_id: &Pubkey, accounts: &[AccountInfo]
if proposal_data.state == ProposalState::Executing
&& proposal_data.instructions_executed_count == proposal_data.instructions_count
{
proposal_data.closed_at = Some(clock.slot);
proposal_data.closed_at = Some(clock.unix_timestamp);
proposal_data.state = ProposalState::Completed;
}
proposal_data.serialize(&mut *proposal_info.data.borrow_mut())?;
proposal_instruction_data.executed_at = Some(clock.slot);
proposal_instruction_data.executed_at = Some(clock.unix_timestamp);
proposal_instruction_data.serialize(&mut *proposal_instruction_info.data.borrow_mut())?;
Ok(())

View File

@ -41,7 +41,11 @@ pub fn process_finalize_vote(program_id: &Pubkey, accounts: &[AccountInfo]) -> P
let governing_token_supply = get_spl_token_mint_supply(governing_token_mint_info)?;
proposal_data.finalize_vote(governing_token_supply, &governance_data.config, clock.slot)?;
proposal_data.finalize_vote(
governing_token_supply,
&governance_data.config,
clock.unix_timestamp,
)?;
proposal_data.serialize(&mut *proposal_info.data.borrow_mut())?;

View File

@ -30,7 +30,7 @@ pub fn process_insert_instruction(
program_id: &Pubkey,
accounts: &[AccountInfo],
index: u16,
hold_up_time: u64,
hold_up_time: u32,
instruction: InstructionData,
) -> ProgramResult {
let account_info_iter = &mut accounts.iter();
@ -73,7 +73,7 @@ pub fn process_insert_instruction(
match index.cmp(&proposal_data.instructions_next_index) {
Ordering::Greater => return Err(GovernanceError::InvalidInstructionIndex.into()),
// If the index is the same as instructions_next_index then we are adding a new instruction
// If the index is below instructions_next_index then we are inserting into an existing empty slot
// If the index is below instructions_next_index then we are inserting into an existing empty space
Ordering::Equal => {
proposal_data.instructions_next_index = proposal_data
.instructions_next_index

View File

@ -41,7 +41,7 @@ pub fn process_sign_off_proposal(program_id: &Pubkey, accounts: &[AccountInfo])
signatory_record_data.serialize(&mut *signatory_record_info.data.borrow_mut())?;
if proposal_data.signatories_signed_off_count == 0 {
proposal_data.signing_off_at = Some(clock.slot);
proposal_data.signing_off_at = Some(clock.unix_timestamp);
proposal_data.state = ProposalState::SigningOff;
}
@ -52,7 +52,7 @@ pub fn process_sign_off_proposal(program_id: &Pubkey, accounts: &[AccountInfo])
// If all Signatories signed off we can start voting
if proposal_data.signatories_signed_off_count == proposal_data.signatories_count {
proposal_data.voting_at = Some(clock.slot);
proposal_data.voting_at = Some(clock.unix_timestamp);
proposal_data.state = ProposalState::Voting;
}

View File

@ -31,11 +31,11 @@ pub struct GovernanceConfig {
/// Minimum number of tokens a governance token owner must possess to be able to create a proposal
pub min_tokens_to_create_proposal: u16,
/// Minimum waiting time in slots for an instruction to be executed after proposal is voted on
pub min_instruction_hold_up_time: u64,
/// Minimum waiting time in seconds for an instruction to be executed after proposal is voted on
pub min_instruction_hold_up_time: u32,
/// Time limit in slots for proposal to be open for voting
pub max_voting_time: u64,
/// Time limit in seconds for proposal to be open for voting
pub max_voting_time: u32,
}
/// Governance Account

View File

@ -1,8 +1,9 @@
//! Proposal Account
use solana_program::clock::UnixTimestamp;
use solana_program::{
account_info::AccountInfo, epoch_schedule::Slot, program_error::ProgramError,
program_pack::IsInitialized, pubkey::Pubkey,
account_info::AccountInfo, program_error::ProgramError, program_pack::IsInitialized,
pubkey::Pubkey,
};
use crate::tools::account::get_account_data;
@ -54,22 +55,22 @@ pub struct Proposal {
pub no_votes_count: u64,
/// When the Proposal was created and entered Draft state
pub draft_at: Slot,
pub draft_at: UnixTimestamp,
/// When Signatories started signing off the Proposal
pub signing_off_at: Option<Slot>,
pub signing_off_at: Option<UnixTimestamp>,
/// When the Proposal began voting
pub voting_at: Option<Slot>,
pub voting_at: Option<UnixTimestamp>,
/// When the Proposal ended voting and entered either Succeeded or Defeated
pub voting_completed_at: Option<Slot>,
pub voting_completed_at: Option<UnixTimestamp>,
/// When the Proposal entered Executing state
pub executing_at: Option<Slot>,
pub executing_at: Option<UnixTimestamp>,
/// When the Proposal entered final state Completed or Cancelled and was closed
pub closed_at: Option<Slot>,
pub closed_at: Option<UnixTimestamp>,
/// The number of the instructions already executed
pub instructions_executed_count: u16,
@ -135,7 +136,7 @@ impl Proposal {
pub fn assert_can_cast_vote(
&self,
config: &GovernanceConfig,
current_slot: Slot,
current_unix_timestamp: UnixTimestamp,
) -> Result<(), ProgramError> {
self.assert_is_voting_state()
.map_err(|_| GovernanceError::InvalidStateCannotVote)?;
@ -144,9 +145,9 @@ impl Proposal {
if self
.voting_at
.unwrap()
.checked_add(config.max_voting_time)
.checked_add(config.max_voting_time as i64)
.unwrap()
< current_slot
< current_unix_timestamp
{
return Err(GovernanceError::ProposalVotingTimeExpired.into());
}
@ -158,7 +159,7 @@ impl Proposal {
pub fn assert_can_finalize_vote(
&self,
config: &GovernanceConfig,
current_slot: Slot,
current_unix_timestamp: UnixTimestamp,
) -> Result<(), ProgramError> {
self.assert_is_voting_state()
.map_err(|_| GovernanceError::InvalidStateCannotFinalize)?;
@ -167,9 +168,9 @@ impl Proposal {
if self
.voting_at
.unwrap()
.checked_add(config.max_voting_time)
.checked_add(config.max_voting_time as i64)
.unwrap()
>= current_slot
>= current_unix_timestamp
{
return Err(GovernanceError::CannotFinalizeVotingInProgress.into());
}
@ -183,12 +184,12 @@ impl Proposal {
&mut self,
governing_token_supply: u64,
config: &GovernanceConfig,
current_slot: Slot,
current_unix_timestamp: UnixTimestamp,
) -> Result<(), ProgramError> {
self.assert_can_finalize_vote(config, current_slot)?;
self.assert_can_finalize_vote(config, current_unix_timestamp)?;
self.state = self.get_final_vote_state(governing_token_supply, config);
self.voting_completed_at = Some(current_slot);
self.voting_completed_at = Some(current_unix_timestamp);
Ok(())
}
@ -219,11 +220,11 @@ impl Proposal {
&mut self,
governing_token_supply: u64,
config: &GovernanceConfig,
current_slot: Slot,
current_unix_timestamp: UnixTimestamp,
) {
if let Some(tipped_state) = self.try_get_tipped_vote_state(governing_token_supply, config) {
self.state = tipped_state;
self.voting_completed_at = Some(current_slot);
self.voting_completed_at = Some(current_unix_timestamp);
}
}
@ -282,7 +283,7 @@ impl Proposal {
pub fn assert_can_execute_instruction(
&self,
proposal_instruction_data: &ProposalInstruction,
current_slot: Slot,
current_unix_timestamp: UnixTimestamp,
) -> Result<(), ProgramError> {
match self.state {
ProposalState::Succeeded | ProposalState::Executing => {}
@ -299,9 +300,9 @@ impl Proposal {
if self
.voting_completed_at
.unwrap()
.checked_add(proposal_instruction_data.hold_up_time)
.checked_add(proposal_instruction_data.hold_up_time as i64)
.unwrap()
>= current_slot
>= current_unix_timestamp
{
return Err(GovernanceError::CannotExecuteInstructionWithinHoldUpTime.into());
}
@ -813,16 +814,16 @@ mod test {
let mut governance_config = create_test_governance_config();
governance_config.yes_vote_threshold_percentage = test_case.vote_threshold_percentage;
let current_slot = 15_u64;
let current_timestamp = 15_i64;
// Act
proposal.try_tip_vote(test_case.governing_token_supply, &governance_config,current_slot);
proposal.try_tip_vote(test_case.governing_token_supply, &governance_config,current_timestamp);
// Assert
assert_eq!(proposal.state,test_case.expected_tipped_state,"CASE: {:?}",test_case);
if test_case.expected_tipped_state != ProposalState::Voting {
assert_eq!(Some(current_slot),proposal.voting_completed_at)
assert_eq!(Some(current_timestamp),proposal.voting_completed_at)
}
}
@ -837,14 +838,14 @@ mod test {
let mut governance_config = create_test_governance_config();
governance_config.yes_vote_threshold_percentage = test_case.vote_threshold_percentage;
let current_slot = 16_u64;
let current_timestamp = 16_i64;
// Act
proposal.finalize_vote(test_case.governing_token_supply, &governance_config,current_slot).unwrap();
proposal.finalize_vote(test_case.governing_token_supply, &governance_config,current_timestamp).unwrap();
// Assert
assert_eq!(proposal.state,test_case.expected_finalized_state,"CASE: {:?}",test_case);
assert_eq!(Some(current_slot),proposal.voting_completed_at);
assert_eq!(Some(current_timestamp),proposal.voting_completed_at);
}
}
@ -879,11 +880,10 @@ mod test {
let mut governance_config = create_test_governance_config();
governance_config.yes_vote_threshold_percentage = yes_vote_threshold_percentage;
let current_slot = 15_u64;
let current_timestamp = 15_i64;
// Act
proposal.try_tip_vote(governing_token_supply, &governance_config,current_slot);
proposal.try_tip_vote(governing_token_supply, &governance_config,current_timestamp);
// Assert
let yes_vote_threshold_count = get_vote_threshold_count(yes_vote_threshold_percentage,governing_token_supply);
@ -916,10 +916,10 @@ mod test {
let mut governance_config = create_test_governance_config();
governance_config.yes_vote_threshold_percentage = yes_vote_threshold_percentage;
let current_slot = 16_u64;
let current_timestamp = 16_i64;
// Act
proposal.finalize_vote(governing_token_supply, &governance_config,current_slot).unwrap();
proposal.finalize_vote(governing_token_supply, &governance_config,current_timestamp).unwrap();
// Assert
let yes_vote_threshold_count = get_vote_threshold_count(yes_vote_threshold_percentage,governing_token_supply);
@ -940,11 +940,12 @@ mod test {
proposal.state = ProposalState::Voting;
let governance_config = create_test_governance_config();
let current_slot = proposal.voting_at.unwrap() + governance_config.max_voting_time;
let current_timestamp =
proposal.voting_at.unwrap() + governance_config.max_voting_time as i64;
// Act
let err = proposal
.finalize_vote(100, &governance_config, current_slot)
.finalize_vote(100, &governance_config, current_timestamp)
.err()
.unwrap();
@ -959,10 +960,11 @@ mod test {
proposal.state = ProposalState::Voting;
let governance_config = create_test_governance_config();
let current_slot = proposal.voting_at.unwrap() + governance_config.max_voting_time + 1;
let current_timestamp =
proposal.voting_at.unwrap() + governance_config.max_voting_time as i64 + 1;
// Act
let result = proposal.finalize_vote(100, &governance_config, current_slot);
let result = proposal.finalize_vote(100, &governance_config, current_timestamp);
// Assert
assert_eq!(result, Ok(()));
@ -975,11 +977,12 @@ mod test {
proposal.state = ProposalState::Voting;
let governance_config = create_test_governance_config();
let current_slot = proposal.voting_at.unwrap() + governance_config.max_voting_time + 1;
let current_timestamp =
proposal.voting_at.unwrap() + governance_config.max_voting_time as i64 + 1;
// Act
let err = proposal
.assert_can_cast_vote(&governance_config, current_slot)
.assert_can_cast_vote(&governance_config, current_timestamp)
.err()
.unwrap();
@ -994,10 +997,11 @@ mod test {
proposal.state = ProposalState::Voting;
let governance_config = create_test_governance_config();
let current_slot = proposal.voting_at.unwrap() + governance_config.max_voting_time;
let current_timestamp =
proposal.voting_at.unwrap() + governance_config.max_voting_time as i64;
// Act
let result = proposal.assert_can_cast_vote(&governance_config, current_slot);
let result = proposal.assert_can_cast_vote(&governance_config, current_timestamp);
// Assert
assert_eq!(result, Ok(()));

View File

@ -9,7 +9,7 @@ use crate::{
use borsh::{BorshDeserialize, BorshSchema, BorshSerialize};
use solana_program::{
account_info::AccountInfo,
epoch_schedule::Slot,
clock::UnixTimestamp,
instruction::{AccountMeta, Instruction},
program_error::ProgramError,
program_pack::IsInitialized,
@ -86,8 +86,8 @@ pub struct ProposalInstruction {
/// The Proposal the instruction belongs to
pub proposal: Pubkey,
/// Minimum waiting time in slots for the instruction to be executed once proposal is voted on
pub hold_up_time: u64,
/// Minimum waiting time in seconds for the instruction to be executed once proposal is voted on
pub hold_up_time: u32,
/// Instruction to execute
/// The instruction will be signed by Governance PDA the Proposal belongs to
@ -95,12 +95,12 @@ pub struct ProposalInstruction {
pub instruction: InstructionData,
/// Executed at flag
pub executed_at: Option<Slot>,
pub executed_at: Option<UnixTimestamp>,
}
impl AccountMaxSize for ProposalInstruction {
fn get_max_size(&self) -> Option<usize> {
Some(self.instruction.accounts.len() * 34 + self.instruction.data.len() + 90)
Some(self.instruction.accounts.len() * 34 + self.instruction.data.len() + 86)
}
}

View File

@ -29,6 +29,8 @@ async fn test_cancel_proposal() {
.await
.unwrap();
let clock = governance_test.get_clock().await;
// Act
governance_test
.cancel_proposal(&proposal_cookie, &token_owner_record_cookie)
@ -41,7 +43,7 @@ async fn test_cancel_proposal() {
.await;
assert_eq!(ProposalState::Cancelled, proposal_account.state);
assert_eq!(Some(1), proposal_account.closed_at);
assert_eq!(Some(clock.unix_timestamp), proposal_account.closed_at);
}
#[tokio::test]

View File

@ -29,6 +29,8 @@ async fn test_cast_vote() {
.await
.unwrap();
let clock = governance_test.get_clock().await;
// Act
let vote_record_cookie = governance_test
.with_cast_vote(&proposal_cookie, &token_owner_record_cookie, Vote::Yes)
@ -54,7 +56,10 @@ async fn test_cast_vote() {
);
assert_eq!(proposal_account.state, ProposalState::Succeeded);
assert_eq!(proposal_account.voting_completed_at, Some(1));
assert_eq!(
proposal_account.voting_completed_at,
Some(clock.unix_timestamp)
);
let token_owner_record = governance_test
.get_token_owner_record_account(&token_owner_record_cookie.address)
@ -501,13 +506,12 @@ async fn test_cast_vote_with_voting_time_expired_error() {
.get_proposal_account(&proposal_cookie.address)
.await;
let vote_expired_at_slot = account_governance_cookie.account.config.max_voting_time
+ proposal_account.voting_at.unwrap()
+ 1;
let vote_expired_at = proposal_account.voting_at.unwrap()
+ account_governance_cookie.account.config.max_voting_time as i64;
governance_test
.context
.warp_to_slot(vote_expired_at_slot)
.unwrap();
.advance_clock_past_timestamp(vote_expired_at)
.await;
// Act
@ -553,7 +557,7 @@ async fn test_cast_vote_with_cast_twice_error() {
.await
.unwrap();
governance_test.context.warp_to_slot(5).unwrap();
governance_test.advance_clock().await;
// Act
let err = governance_test

View File

@ -44,7 +44,6 @@ async fn test_community_proposal_created() {
.await;
assert_eq!(1, account_governance_account.proposals_count);
assert_eq!(proposal_account.draft_at, 1);
}
#[tokio::test]

View File

@ -108,7 +108,7 @@ async fn test_deposit_subsequent_community_tokens() {
.governing_token_deposit_amount
+ deposit_amount;
governance_test.context.warp_to_slot(5).unwrap();
governance_test.advance_clock().await;
// Act
governance_test
@ -154,7 +154,7 @@ async fn test_deposit_subsequent_council_tokens() {
.governing_token_deposit_amount
+ deposit_amount;
governance_test.context.warp_to_slot(5).unwrap();
governance_test.advance_clock().await;
// Act
governance_test

View File

@ -59,13 +59,12 @@ async fn test_execute_mint_instruction() {
.await
.unwrap();
// Advance slot past hold_up_time
let execute_at_slot = 1 + proposal_instruction_cookie.account.hold_up_time + 1;
// Advance timestamp past hold_up_time
governance_test
.context
.warp_to_slot(execute_at_slot)
.unwrap();
.advance_clock_by_min_timespan(proposal_instruction_cookie.account.hold_up_time as u64)
.await;
let clock = governance_test.get_clock().await;
// Act
governance_test
@ -81,15 +80,15 @@ async fn test_execute_mint_instruction() {
assert_eq!(1, proposal_account.instructions_executed_count);
assert_eq!(ProposalState::Completed, proposal_account.state);
assert_eq!(Some(execute_at_slot), proposal_account.closed_at);
assert_eq!(Some(execute_at_slot), proposal_account.executing_at);
assert_eq!(Some(clock.unix_timestamp), proposal_account.closed_at);
assert_eq!(Some(clock.unix_timestamp), proposal_account.executing_at);
let proposal_instruction_account = governance_test
.get_proposal_instruction_account(&proposal_instruction_cookie.address)
.await;
assert_eq!(
Some(execute_at_slot),
Some(clock.unix_timestamp),
proposal_instruction_account.executed_at
);
@ -147,13 +146,12 @@ async fn test_execute_transfer_instruction() {
.await
.unwrap();
// Advance slot past hold_up_time
let execute_at_slot = 1 + proposal_instruction_cookie.account.hold_up_time + 1;
// Advance timestamp past hold_up_time
governance_test
.context
.warp_to_slot(execute_at_slot)
.unwrap();
.advance_clock_by_min_timespan(proposal_instruction_cookie.account.hold_up_time as u64)
.await;
let clock = governance_test.get_clock().await;
// Act
governance_test
@ -169,15 +167,15 @@ async fn test_execute_transfer_instruction() {
assert_eq!(1, proposal_account.instructions_executed_count);
assert_eq!(ProposalState::Completed, proposal_account.state);
assert_eq!(Some(execute_at_slot), proposal_account.closed_at);
assert_eq!(Some(execute_at_slot), proposal_account.executing_at);
assert_eq!(Some(clock.unix_timestamp), proposal_account.closed_at);
assert_eq!(Some(clock.unix_timestamp), proposal_account.executing_at);
let proposal_instruction_account = governance_test
.get_proposal_instruction_account(&proposal_instruction_cookie.address)
.await;
assert_eq!(
Some(execute_at_slot),
Some(clock.unix_timestamp),
proposal_instruction_account.executed_at
);
@ -234,13 +232,10 @@ async fn test_execute_upgrade_program_instruction() {
.await
.unwrap();
// Advance slot past hold_up_time
let execute_at_slot = 1 + proposal_instruction_cookie.account.hold_up_time + 1;
// Advance timestamp past hold_up_time
governance_test
.context
.warp_to_slot(execute_at_slot)
.unwrap();
.advance_clock_by_min_timespan(proposal_instruction_cookie.account.hold_up_time as u64)
.await;
// Ensure we can invoke the governed program before upgrade
let governed_program_instruction = Instruction::new_with_bytes(
@ -262,6 +257,8 @@ async fn test_execute_upgrade_program_instruction() {
// solana_bpf_rust_upgradable returns CustomError == 42
assert_eq!(ProgramError::Custom(42), err);
let clock = governance_test.get_clock().await;
// Act
governance_test
.execute_instruction(&proposal_cookie, &proposal_instruction_cookie)
@ -276,23 +273,21 @@ async fn test_execute_upgrade_program_instruction() {
assert_eq!(1, proposal_account.instructions_executed_count);
assert_eq!(ProposalState::Completed, proposal_account.state);
assert_eq!(Some(execute_at_slot), proposal_account.closed_at);
assert_eq!(Some(execute_at_slot), proposal_account.executing_at);
assert_eq!(Some(clock.unix_timestamp), proposal_account.closed_at);
assert_eq!(Some(clock.unix_timestamp), proposal_account.executing_at);
let proposal_instruction_account = governance_test
.get_proposal_instruction_account(&proposal_instruction_cookie.address)
.await;
assert_eq!(
Some(execute_at_slot),
Some(clock.unix_timestamp),
proposal_instruction_account.executed_at
);
// Assert we can invoke the governed program after upgrade
governance_test
.context
.warp_to_slot(execute_at_slot + 10)
.unwrap();
governance_test.advance_clock().await;
let err = governance_test
.process_transaction(&[governed_program_instruction.clone()], None)
@ -411,7 +406,7 @@ async fn test_execute_instruction_with_invalid_state_errors() {
.await
.unwrap();
governance_test.context.warp_to_slot(5).unwrap();
governance_test.advance_clock().await;
// Act
let err = governance_test
@ -427,13 +422,10 @@ async fn test_execute_instruction_with_invalid_state_errors() {
);
// Arrange
// Advance slot past hold_up_time
let execute_at_slot = 1 + proposal_instruction_cookie.account.hold_up_time + 1;
// Advance timestamp past hold_up_time
governance_test
.context
.warp_to_slot(execute_at_slot)
.unwrap();
.advance_clock_by_min_timespan(proposal_instruction_cookie.account.hold_up_time as u64)
.await;
// Act
governance_test
@ -451,10 +443,7 @@ async fn test_execute_instruction_with_invalid_state_errors() {
// Arrange
governance_test
.context
.warp_to_slot(execute_at_slot + 10)
.unwrap();
governance_test.advance_clock().await;
// Act
let err = governance_test
@ -517,13 +506,11 @@ async fn test_execute_instruction_for_other_proposal_error() {
.await
.unwrap();
// Advance slot past hold_up_time
let execute_at_slot = 1 + proposal_instruction_cookie.account.hold_up_time + 1;
// Advance clock past hold_up_time
governance_test
.context
.warp_to_slot(execute_at_slot)
.unwrap();
.advance_clock_by_min_timespan(proposal_instruction_cookie.account.hold_up_time as u64)
.await;
let proposal_cookie2 = governance_test
.with_proposal(&token_owner_record_cookie, &mut mint_governance_cookie)
@ -596,23 +583,18 @@ async fn test_execute_mint_instruction_twice_error() {
.await
.unwrap();
// Advance slot past hold_up_time
let execute_at_slot = 1 + proposal_instruction_cookie.account.hold_up_time + 1;
// Advance clock past hold_up_time
governance_test
.context
.warp_to_slot(execute_at_slot)
.unwrap();
.advance_clock_by_min_timespan(proposal_instruction_cookie.account.hold_up_time as u64)
.await;
governance_test
.execute_instruction(&proposal_cookie, &proposal_instruction_cookie)
.await
.unwrap();
governance_test
.context
.warp_to_slot(execute_at_slot + 10)
.unwrap();
governance_test.advance_clock().await;
// Act

View File

@ -56,14 +56,15 @@ async fn test_finalize_vote_to_succeeded() {
assert_eq!(ProposalState::Voting, proposal_account.state);
// Advance slot past max_voting_time
let vote_expired_at_slot = account_governance_cookie.account.config.max_voting_time
+ proposal_account.voting_at.unwrap()
+ 1;
// Advance timestamp past max_voting_time
governance_test
.context
.warp_to_slot(vote_expired_at_slot)
.unwrap();
.advance_clock_past_timestamp(
account_governance_cookie.account.config.max_voting_time as i64
+ proposal_account.voting_at.unwrap(),
)
.await;
let clock = governance_test.get_clock().await;
// Act
@ -80,7 +81,7 @@ async fn test_finalize_vote_to_succeeded() {
assert_eq!(proposal_account.state, ProposalState::Succeeded);
assert_eq!(
Some(vote_expired_at_slot),
Some(clock.unix_timestamp),
proposal_account.voting_completed_at
);
}
@ -124,14 +125,13 @@ async fn test_finalize_vote_to_defeated() {
assert_eq!(ProposalState::Voting, proposal_account.state);
// Advance slot past max_voting_time
let vote_expired_at_slot = account_governance_cookie.account.config.max_voting_time
+ proposal_account.voting_at.unwrap()
+ 1;
// Advance clock past max_voting_time
governance_test
.context
.warp_to_slot(vote_expired_at_slot)
.unwrap();
.advance_clock_past_timestamp(
account_governance_cookie.account.config.max_voting_time as i64
+ proposal_account.voting_at.unwrap(),
)
.await;
// Act

View File

@ -34,6 +34,8 @@ async fn test_sign_off_proposal() {
.await
.unwrap();
let clock = governance_test.get_clock().await;
// Act
governance_test
.sign_off_proposal(&proposal_cookie, &signatory_record_cookie)
@ -48,8 +50,8 @@ async fn test_sign_off_proposal() {
assert_eq!(1, proposal_account.signatories_count);
assert_eq!(1, proposal_account.signatories_signed_off_count);
assert_eq!(ProposalState::Voting, proposal_account.state);
assert_eq!(Some(1), proposal_account.signing_off_at);
assert_eq!(Some(1), proposal_account.voting_at);
assert_eq!(Some(clock.unix_timestamp), proposal_account.signing_off_at);
assert_eq!(Some(clock.unix_timestamp), proposal_account.voting_at);
let signatory_record_account = governance_test
.get_signatory_record_account(&signatory_record_cookie.address)

View File

@ -4,12 +4,13 @@ use borsh::BorshDeserialize;
use solana_program::{
borsh::try_from_slice_unchecked,
bpf_loader_upgradeable::{self, UpgradeableLoaderState},
clock::{Clock, UnixTimestamp},
instruction::{AccountMeta, Instruction},
program_error::ProgramError,
program_pack::{IsInitialized, Pack},
pubkey::Pubkey,
rent::Rent,
system_instruction,
system_instruction, sysvar,
};
use bincode::deserialize;
@ -1036,6 +1037,8 @@ impl GovernanceProgramTest {
)
.await?;
let clock = self.get_clock().await;
let account = Proposal {
account_type: GovernanceAccountType::Proposal,
description_link,
@ -1045,7 +1048,7 @@ impl GovernanceProgramTest {
state: ProposalState::Draft,
signatories_count: 0,
// Clock always returns 1 when running under the test
draft_at: 1,
draft_at: clock.unix_timestamp,
signing_off_at: None,
voting_at: None,
voting_completed_at: None,
@ -1642,6 +1645,39 @@ impl GovernanceProgramTest {
.expect(format!("GET-TEST-ACCOUNT-ERROR: Account {}", address).as_str())
}
#[allow(dead_code)]
pub async fn get_clock(&mut self) -> Clock {
self.get_bincode_account::<Clock>(&sysvar::clock::id())
.await
}
#[allow(dead_code)]
pub async fn advance_clock_past_timestamp(&mut self, unix_timestamp: UnixTimestamp) {
let mut clock = self.get_clock().await;
let mut n = 1;
while clock.unix_timestamp <= unix_timestamp {
// Since the exact time is not deterministic keep wrapping by arbitrary 400 slots until we pass the requested timestamp
self.context.warp_to_slot(clock.slot + n * 400).unwrap();
n = n + 1;
clock = self.get_clock().await;
}
}
#[allow(dead_code)]
pub async fn advance_clock_by_min_timespan(&mut self, time_span: u64) {
let clock = self.get_clock().await;
self.advance_clock_past_timestamp(clock.unix_timestamp + (time_span as i64))
.await;
}
#[allow(dead_code)]
pub async fn advance_clock(&mut self) {
let clock = self.get_clock().await;
self.context.warp_to_slot(clock.slot + 2).unwrap();
}
#[allow(dead_code)]
pub async fn get_upgradable_loader_account(
&mut self,