diff --git a/Cargo.lock b/Cargo.lock index 27651ae..3c99636 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3786,7 +3786,7 @@ checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7" [[package]] name = "validator-voting" -version = "0.1.0" +version = "0.2.0" dependencies = [ "near-sdk", "near-workspaces", diff --git a/Cargo.toml b/Cargo.toml index bca4cb9..319fb99 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "validator-voting" description = "NEAR Validator Voting Contract" -version = "0.1.0" +version = "0.2.0" edition = "2021" # NEP-0330 is automatically implemented for all contracts built with https://github.com/near/cargo-near. diff --git a/README.md b/README.md index 435df04..80124f6 100644 --- a/README.md +++ b/README.md @@ -7,13 +7,13 @@ The purpose of this contract is for validators to vote on any specific proposal. Install [`cargo-near`](https://github.com/near/cargo-near) and run: ```bash -cargo near build +make ``` ## Test ```bash -cargo test +make test ``` ## Deploy diff --git a/makefile b/makefile index 03fdc4e..811bca2 100644 --- a/makefile +++ b/makefile @@ -1,6 +1,6 @@ RUSTFLAGS = "-C link-arg=-s" -all: lint validator-voting validator-voting-test +all: lint validator-voting lint: @cargo fmt --all diff --git a/src/events.rs b/src/events.rs index 4784a16..0c0a408 100644 --- a/src/events.rs +++ b/src/events.rs @@ -24,9 +24,21 @@ pub enum Event<'a> { proposal: &'a String, approval_timestamp_ms: &'a U64, deadline_timestamp_ms: &'a U64, + yes_stake: &'a U128, voted_stake: &'a U128, total_stake: &'a U128, - num_votes: &'a U64, + num_votes_yes: &'a U64, + num_votes_total: &'a U64, + }, + ProposalRejected { + proposal: &'a String, + rejection_timestamp_ms: &'a U64, + deadline_timestamp_ms: &'a U64, + yes_stake: &'a U128, + voted_stake: &'a U128, + total_stake: &'a U128, + num_votes_yes: &'a U64, + num_votes_total: &'a U64, }, } diff --git a/src/lib.rs b/src/lib.rs index 349d558..5f2bfdc 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -15,13 +15,20 @@ type Balance = u128; /// Timestamp in milliseconds type Timestamp = u64; -#[near(serializers = [json])] +#[derive(PartialEq, Clone, Debug)] +#[near(serializers = [borsh, json])] #[serde(rename_all = "lowercase")] pub enum Vote { Yes, No, } +#[near(serializers = [borsh, json])] +pub struct VotedStake { + vote: Vote, + stake: Balance, +} + const GET_OWNER_ID_GAS: Gas = Gas::from_tgas(5); #[ext_contract(ext_staking_pool)] @@ -36,9 +43,10 @@ pub trait StakingPoolContract { pub struct Contract { proposal: String, deadline_timestamp_ms: Timestamp, - votes: HashMap, - total_voted_stake: Balance, - result: Option, + votes: HashMap, + yes_stake: Balance, // stake voted for YES + total_voted_stake: Balance, // total stake voted for YES or NO + result: Option, // true if proposal is approved last_epoch_height: EpochHeight, } @@ -57,6 +65,7 @@ impl Contract { proposal, deadline_timestamp_ms, votes: HashMap::new(), + yes_stake: 0, total_voted_stake: 0, result: None, last_epoch_height: 0, @@ -65,22 +74,22 @@ impl Contract { /// Ping to update the votes according to current stake of validators. pub fn ping(&mut self) { - require!( - env::block_timestamp_ms() < self.deadline_timestamp_ms, - "Voting deadline has already passed" - ); require!(self.result.is_none(), "Voting has already ended"); let cur_epoch_height = env::epoch_height(); if cur_epoch_height != self.last_epoch_height { self.total_voted_stake = 0; - for (account_id, stake) in self.votes.iter_mut() { + self.yes_stake = 0; + for (account_id, voted_stake) in self.votes.iter_mut() { let account_current_stake = validator_stake(account_id); self.total_voted_stake += account_current_stake; - *stake = account_current_stake; + if voted_stake.vote == Vote::Yes { + self.yes_stake += account_current_stake; + } + voted_stake.stake = account_current_stake; } - self.check_result(); self.last_epoch_height = cur_epoch_height; } + self.check_result(); } /// Method for validators to vote with `Yes` or `No`. @@ -88,6 +97,7 @@ impl Contract { pub fn vote(&mut self, vote: Vote, staking_pool_id: AccountId) -> Promise { ext_staking_pool::ext(staking_pool_id.clone()) .with_static_gas(GET_OWNER_ID_GAS) + .with_unused_gas_weight(0) .get_owner_id() .then(Self::ext(env::current_account_id()).on_get_pool_owner_id( env::predecessor_account_id(), @@ -118,30 +128,53 @@ impl Contract { /// Internal method for voting. fn internal_vote(&mut self, vote: Vote, account_id: AccountId) { + require!( + env::block_timestamp_ms() < self.deadline_timestamp_ms, + "Voting deadline has already passed" + ); + self.ping(); - let stake = validator_stake(&account_id); - require!(stake > 0, format!("{} is not a validator", account_id)); + let account_stake = validator_stake(&account_id); + require!( + account_stake > 0, + format!("{} is not a validator", account_id) + ); - let account_stake = match vote { - Vote::Yes => stake, + let account_stake_yes = match vote { + Vote::Yes => account_stake, Vote::No => 0, }; - let voted_stake = self.votes.remove(&account_id).unwrap_or_default(); + let (prev_stake, prev_stake_yes) = if let Some(voted_stake) = self.votes.get(&account_id) { + ( + voted_stake.stake, + match voted_stake.vote { + Vote::Yes => voted_stake.stake, + Vote::No => 0, + }, + ) + } else { + (0, 0) + }; require!( - voted_stake <= self.total_voted_stake, + prev_stake <= self.total_voted_stake, format!( "invariant: voted stake {} is more than total voted stake {}", - voted_stake, self.total_voted_stake + prev_stake, self.total_voted_stake ) ); - self.total_voted_stake = self.total_voted_stake + account_stake - voted_stake; - if account_stake > 0 { - self.votes.insert(account_id.clone(), account_stake); - self.check_result(); - } - // emit event + self.total_voted_stake = self.total_voted_stake + account_stake - prev_stake; + self.yes_stake = self.yes_stake + account_stake_yes - prev_stake_yes; + self.votes.insert( + account_id.clone(), + VotedStake { + vote: vote.clone(), + stake: account_stake, + }, + ); + self.check_result(); + Event::Voted { validator_id: &account_id, vote: &vote, @@ -155,16 +188,41 @@ impl Contract { self.result.is_none(), "check result is called after result is already set" ); + if env::block_timestamp_ms() < self.deadline_timestamp_ms { + return; + } let total_stake = validator_total_stake(); - if self.total_voted_stake > total_stake * 2 / 3 { - self.result = Some(env::block_timestamp_ms()); + let num_votes_yes = self + .votes + .iter() + .filter(|(_, v)| v.vote == Vote::Yes) + .count() as u64; + if self.total_voted_stake > total_stake / 3 + && self.yes_stake > self.total_voted_stake * 2 / 3 + { + self.result = Some(true); Event::ProposalApproved { proposal: &self.proposal, approval_timestamp_ms: &U64::from(env::block_timestamp_ms()), deadline_timestamp_ms: &U64::from(self.deadline_timestamp_ms), + yes_stake: &U128::from(self.yes_stake), + voted_stake: &U128::from(self.total_voted_stake), + total_stake: &U128::from(total_stake), + num_votes_yes: &U64::from(num_votes_yes), + num_votes_total: &U64::from(self.votes.len() as u64), + } + .emit(); + } else { + self.result = Some(false); + Event::ProposalRejected { + proposal: &self.proposal, + rejection_timestamp_ms: &U64::from(env::block_timestamp_ms()), + deadline_timestamp_ms: &U64::from(self.deadline_timestamp_ms), + yes_stake: &U128::from(self.yes_stake), voted_stake: &U128::from(self.total_voted_stake), total_stake: &U128::from(total_stake), - num_votes: &U64::from(self.votes.len() as u64), + num_votes_yes: &U64::from(num_votes_yes), + num_votes_total: &U64::from(self.votes.len() as u64), } .emit(); } @@ -174,11 +232,12 @@ impl Contract { /// View methods #[near] impl Contract { - /// Returns a pair of `total_voted_stake` and the total stake. + /// Returns a triple of (`yes_stake`, `total_voted_stake`, the total stake). /// Note: as a view method, it doesn't recompute the active stake. May need to call `ping` to /// update the active stake. - pub fn get_total_voted_stake(&self) -> (U128, U128) { + pub fn get_total_voted_stake(&self) -> (U128, U128, U128) { ( + self.yes_stake.into(), self.total_voted_stake.into(), validator_total_stake().into(), ) @@ -187,15 +246,20 @@ impl Contract { /// Returns all active votes. /// Note: as a view method, it doesn't recompute the active stake. May need to call `ping` to /// update the active stake. - pub fn get_votes(&self) -> HashMap { + pub fn get_votes(&self) -> HashMap { self.votes .iter() - .map(|(account_id, stake)| (account_id.clone(), (*stake).into())) + .map(|(account_id, voted_stake)| { + ( + account_id.clone(), + (voted_stake.vote.clone(), voted_stake.stake.into()), + ) + }) .collect() } - /// Get the timestamp of when the voting finishes. `None` means the voting hasn't ended yet. - pub fn get_result(&self) -> Option { + /// Get the voting result. `None` means the voting hasn't ended yet. + pub fn get_result(&self) -> Option { self.result } @@ -211,6 +275,7 @@ impl Contract { } #[cfg(feature = "test")] +/// Test methods #[near] impl Contract { pub fn set_validator_stake(&mut self, validator_account_id: AccountId, amount: U128) { @@ -343,9 +408,9 @@ mod tests { #[test] #[should_panic(expected = "Voting has already ended")] - fn test_vote_again_after_voting_ends() { + fn test_ping_again_after_voting_ends() { let validator_id = validator(0); - let context = get_context(&voting_contract_id()); + let mut context = get_context(&voting_contract_id()); let validators = HashMap::from_iter(vec![( validator_id.to_string(), NearToken::from_yoctonear(100), @@ -354,9 +419,22 @@ mod tests { let mut contract = get_contract(); // vote vote(&mut contract, Vote::Yes, &validator_id); - assert!(contract.get_result().is_some()); - // vote again. should panic because voting has ended - vote(&mut contract, Vote::Yes, &validator_id); + // vote result is none because deadline has not passed, + // although all validators have voted + assert!(contract.get_result().is_none()); + + // ping at epoch 2 after deadline + set_context_and_validators( + context + .block_timestamp(env::block_timestamp_ms() + 2000 * 1_000_000) + .epoch_height(2), + &validators, + ); + contract.ping(); + assert!(contract.get_result().unwrap()); + + // ping again. should panic because voting has ended + contract.ping(); } #[test] @@ -383,6 +461,8 @@ mod tests { for i in 0..201 { // vote by each validator + let mut context = get_context(&voting_contract_id()); + set_context(&context); let voter = validator(i); vote(&mut contract, Vote::Yes, &voter); @@ -391,25 +471,35 @@ mod tests { set_context(&context); assert_eq!( contract.get_total_voted_stake(), - (U128::from(10 * (i + 1) as u128), U128::from(3000)) + ( + U128::from(10 * (i + 1) as u128), + U128::from(10 * (i + 1) as u128), + U128::from(3000) + ) ); // check votes - let expected_votes: HashMap = - (0..=i).map(|j| (validator(j), U128::from(10))).collect(); + let expected_votes: HashMap = (0..=i) + .map(|j| (validator(j), (Vote::Yes, U128::from(10)))) + .collect(); assert_eq!(contract.get_votes(), expected_votes); assert_eq!(contract.get_votes().len() as u64, i + 1); // check voting result - if i < 200 { - assert!(contract.get_result().is_none()); - } else { - assert!(contract.get_result().is_some()); - } + assert!(contract.get_result().is_none()); } + + // ping at epoch 2 after deadline + set_context( + context + .block_timestamp(env::block_timestamp_ms() + 2000 * 1_000_000) + .epoch_height(2), + ); + contract.ping(); + assert!(contract.get_result().unwrap()); } #[test] fn test_voting_with_epoch_change() { - let context = get_context(&voting_contract_id()); + let mut context = get_context(&voting_contract_id()); set_context(&context); let mut contract = get_contract(); @@ -420,13 +510,18 @@ mod tests { vote(&mut contract, Vote::Yes, &validator(i)); // check votes assert_eq!(contract.get_votes().len() as u64, i + 1); - // check voting result - if i < 200 { - assert!(contract.get_result().is_none()); - } else { - assert!(contract.get_result().is_some()); - } + // voting result is none since deadline has not passed + assert!(contract.get_result().is_none()); } + + // ping at epoch 2 after deadline + set_context( + context + .block_timestamp(env::block_timestamp_ms() + 2000 * 1_000_000) + .epoch_height(2), + ); + contract.ping(); + assert!(contract.get_result().unwrap()); } #[test] @@ -436,17 +531,32 @@ mod tests { (validator(2).to_string(), NearToken::from_yoctonear(10)), (validator(3).to_string(), NearToken::from_yoctonear(10)), ]); - // vote at epoch 1 + let context = get_context_with_epoch_height(&voting_contract_id(), 1); set_context_and_validators(&context, &validators); let mut contract = get_contract(); - vote(&mut contract, Vote::Yes, &validator(1)); + // validator 2 votes YES at epoch 1 + vote(&mut contract, Vote::Yes, &validator(2)); + // validator 3 votes NO at epoch 1 + vote(&mut contract, Vote::No, &validator(3)); + // ping at epoch 2 - validators.insert(validator(1).to_string(), NearToken::from_yoctonear(50)); - let context = get_context_with_epoch_height(&voting_contract_id(), 2); + validators.insert(validator(2).to_string(), NearToken::from_yoctonear(25)); + let mut context = get_context_with_epoch_height(&voting_contract_id(), 2); set_context_and_validators(&context, &validators); contract.ping(); - assert!(contract.get_result().is_some()); + // voting result is none since deadline has not passed + assert!(contract.get_result().is_none()); + + // ping at epoch 3 after deadline + set_context_and_validators( + context + .block_timestamp(env::block_timestamp_ms() + 2000 * 1_000_000) + .epoch_height(3), + &validators, + ); + contract.ping(); + assert!(contract.get_result().unwrap()); } #[test] @@ -458,19 +568,34 @@ mod tests { let context = get_context_with_epoch_height(&voting_contract_id(), 1); set_context_and_validators(&context, &validators); let mut contract = get_contract(); + // vote YES at epoch 1 vote(&mut contract, Vote::Yes, &validator(1)); assert_eq!(contract.get_votes().len(), 1); + assert_eq!( + contract.get_total_voted_stake(), + (U128(10), U128(10), U128(20)) + ); + // vote NO at epoch 2 let context = get_context_with_epoch_height(&voting_contract_id(), 2); set_context_and_validators(&context, &validators); vote(&mut contract, Vote::No, &validator(1)); - assert!(contract.get_votes().is_empty()); + assert_eq!(contract.get_votes().len(), 1); + assert_eq!( + contract.get_total_voted_stake(), + (U128(0), U128(10), U128(20)) + ); + // vote YES at epoch 3 let context = get_context_with_epoch_height(&voting_contract_id(), 3); set_context_and_validators(&context, &validators); vote(&mut contract, Vote::Yes, &validator(1)); assert_eq!(contract.get_votes().len(), 1); + assert_eq!( + contract.get_total_voted_stake(), + (U128(10), U128(10), U128(20)) + ); } #[test] @@ -483,25 +608,37 @@ mod tests { let context = get_context_with_epoch_height(&voting_contract_id(), 1); set_context_and_validators(&context, &validators); let mut contract = get_contract(); + // vote at epoch 1 vote(&mut contract, Vote::Yes, &validator(1)); - assert_eq!((contract.get_total_voted_stake().0).0, 40); + assert_eq!( + contract.get_total_voted_stake(), + (U128(40), U128(40), U128(60)) + ); assert_eq!(contract.get_votes().len(), 1); - // remove validator at epoch 2 + + // remove validator 1 at epoch 2, i.e. validator 1 is kicked out validators.remove(&validator(1).to_string()); let context = get_context_with_epoch_height(&voting_contract_id(), 2); set_context_and_validators(&context, &validators); // ping will update total voted stake contract.ping(); - assert_eq!((contract.get_total_voted_stake().0).0, 0); + assert_eq!( + contract.get_total_voted_stake(), + (U128(0), U128(0), U128(20)) + ); assert_eq!(contract.get_votes().len(), 1); + // validator(1) is back to validator set at epoch 3 validators.insert(validator(1).to_string(), NearToken::from_yoctonear(40)); let context = get_context_with_epoch_height(&voting_contract_id(), 3); set_context_and_validators(&context, &validators); // ping will update total voted stake after validator(1) is back contract.ping(); - assert_eq!((contract.get_total_voted_stake().0).0, 40); + assert_eq!( + contract.get_total_voted_stake(), + (U128(40), U128(40), U128(60)) + ); assert_eq!(contract.get_votes().len(), 1); } @@ -543,7 +680,6 @@ mod tests { } #[test] - #[should_panic(expected = "Voting deadline has already passed")] fn test_ping_after_deadline() { let mut contract = get_contract(); let mut context = get_context(&voting_contract_id()); @@ -559,5 +695,111 @@ mod tests { .epoch_height(2), ); contract.ping(); + + // has vote result + assert!(contract.get_result().is_some()); + } + + #[test] + fn test_proposal_rejected_since_not_enough_total_voted_stake() { + let validators: HashMap = HashMap::from_iter(vec![ + (validator(1).to_string(), NearToken::from_yoctonear(50)), + (validator(2).to_string(), NearToken::from_yoctonear(30)), + (validator(3).to_string(), NearToken::from_yoctonear(30)), + (validator(4).to_string(), NearToken::from_yoctonear(10)), + ]); + let mut context = get_context_with_epoch_height(&voting_contract_id(), 1); + set_context_and_validators(&context, &validators); + let mut contract = get_contract(); + + // vote at epoch 1 + set_context(&context); + vote(&mut contract, Vote::Yes, &validator(3)); + vote(&mut contract, Vote::No, &validator(4)); + + // ping at epoch 2 after deadline + set_context_and_validators( + context + .block_timestamp(env::block_timestamp_ms() + 2000 * 1_000_000) + .epoch_height(2), + &validators, + ); + contract.ping(); + assert!(!contract.get_result().unwrap()); + } + + #[test] + fn test_proposal_rejected_since_not_enough_yes_stake() { + let validators: HashMap = HashMap::from_iter(vec![ + (validator(1).to_string(), NearToken::from_yoctonear(50)), + (validator(2).to_string(), NearToken::from_yoctonear(30)), + (validator(3).to_string(), NearToken::from_yoctonear(30)), + (validator(4).to_string(), NearToken::from_yoctonear(10)), + ]); + let mut context = get_context_with_epoch_height(&voting_contract_id(), 1); + set_context_and_validators(&context, &validators); + let mut contract = get_contract(); + + // vote at epoch 1 + vote(&mut contract, Vote::Yes, &validator(1)); + vote(&mut contract, Vote::No, &validator(2)); + assert_eq!(contract.get_votes().len(), 2); + assert_eq!( + contract.get_total_voted_stake(), + (U128(50), U128(80), U128(120)) + ); + + // ping at epoch 2 after deadline + set_context_and_validators( + context + .block_timestamp(env::block_timestamp_ms() + 2000 * 1_000_000) + .epoch_height(2), + &validators, + ); + contract.ping(); + assert!(!contract.get_result().unwrap()); + } + + #[test] + fn test_proposal_approved() { + let validators: HashMap = HashMap::from_iter(vec![ + (validator(1).to_string(), NearToken::from_yoctonear(50)), + (validator(2).to_string(), NearToken::from_yoctonear(30)), + (validator(3).to_string(), NearToken::from_yoctonear(30)), + (validator(4).to_string(), NearToken::from_yoctonear(10)), + (validator(5).to_string(), NearToken::from_yoctonear(20)), + ]); + let mut context = get_context_with_epoch_height(&voting_contract_id(), 1); + set_context_and_validators(&context, &validators); + let mut contract = get_contract(); + + // vote at epoch 1 + vote(&mut contract, Vote::Yes, &validator(1)); + vote(&mut contract, Vote::Yes, &validator(2)); + vote(&mut contract, Vote::No, &validator(4)); + assert_eq!(contract.get_votes().len(), 3); + assert_eq!( + contract.get_total_voted_stake(), + (U128(80), U128(90), U128(140)) + ); + + // vote at epoch 2 + vote(&mut contract, Vote::No, &validator(3)); + vote(&mut contract, Vote::Yes, &validator(4)); + assert_eq!(contract.get_votes().len(), 4); + assert_eq!( + contract.get_total_voted_stake(), + (U128(90), U128(120), U128(140)) + ); + + // ping at epoch 3 after deadline + set_context_and_validators( + context + .block_timestamp(env::block_timestamp_ms() + 2000 * 1_000_000) + .epoch_height(3), + &validators, + ); + contract.ping(); + assert!(contract.get_result().unwrap()); } } diff --git a/tests/scripts/create-proposal.sh b/tests/scripts/create-proposal.sh index ed21d19..ba84cb4 100644 --- a/tests/scripts/create-proposal.sh +++ b/tests/scripts/create-proposal.sh @@ -1,10 +1,15 @@ #!/bin/bash -export VOTING_ACCOUNT_ID=reduce-inflation.testnet +export SUFFIX=delta +export MOCK_PROPOSAL_CONTRACT=./res/validator_voting.wasm +export VOTING_ACCOUNT_ID="mock-proposal-"$SUFFIX".testnet" export PROPOSAL_DESCRIPTION="reduce inflation rate" -export DEADLINE_TIMESTAMP=1749715200000 +export DEADLINE_TIMESTAMP=1753430400000 +export INIT_ARGS='{"proposal":"'$PROPOSAL_DESCRIPTION'","deadline_timestamp_ms":'$DEADLINE_TIMESTAMP'}' + +echo $ARGS # create account near account create-account sponsor-by-faucet-service $VOTING_ACCOUNT_ID autogenerate-new-keypair save-to-legacy-keychain network-config testnet create # deploy contract -near contract deploy $VOTING_ACCOUNT_ID use-file validator_voting.wasm with-init-call new json-args '{"proposal":"'$PROPOSAL_DESCRIPTION'","deadline_timestamp_ms":'$DEADLINE_TIMESTAMP'}' prepaid-gas '100.0 Tgas' attached-deposit '0 NEAR' network-config testnet sign-with-legacy-keychain send +near contract deploy $VOTING_ACCOUNT_ID use-file $MOCK_PROPOSAL_CONTRACT with-init-call new json-args "$INIT_ARGS" prepaid-gas '100.0 Tgas' attached-deposit '0 NEAR' network-config testnet sign-with-legacy-keychain send diff --git a/tests/scripts/create-validators.sh b/tests/scripts/create-validators.sh index b613fcc..62986f5 100644 --- a/tests/scripts/create-validators.sh +++ b/tests/scripts/create-validators.sh @@ -1,16 +1,18 @@ #!/bin/bash +export SUFFIX=delta +export MOCK_STAKING_POOL_CONTRACT=./contracts/mock-staking-pool/target/near/mock_staking_pool.wasm export OWNER_ID=mock-owner.testnet export STAKE_ACCOUNT_ID=mock-staker.testnet -export VOTING_ACCOUNT_ID=reduce-inflation.testnet +export VOTING_ACCOUNT_ID="mock-proposal-"$SUFFIX".testnet" export STAKE_PUBLIC_KEY=ed25519:6E8sCci9badyRkXb3JoRpBj5p8C6Tw41ELDZoiihKEtp -for i in {1..1}; do - VALIDATOR_ID="mock-node-"${i}".testnet" +for i in {1..10}; do + VALIDATOR_ID="mock-node-"$SUFFIX"-"${i}".testnet" # create validator account # near account create-account sponsor-by-faucet-service $VALIDATOR_ID autogenerate-new-keypair save-to-legacy-keychain network-config testnet create near account create-account fund-myself $VALIDATOR_ID '2 NEAR' autogenerate-new-keypair save-to-legacy-keychain sign-as $STAKE_ACCOUNT_ID network-config testnet sign-with-legacy-keychain send # deploy mock staking pool contract - near contract deploy $VALIDATOR_ID use-file mock_staking_pool.wasm with-init-call new json-args '{"owner_id":"'$OWNER_ID'","stake_public_key":"'$STAKE_PUBLIC_KEY'","voting_account_id":"'$VOTING_ACCOUNT_ID'"}' prepaid-gas '100.0 Tgas' attached-deposit '0 NEAR' network-config testnet sign-with-legacy-keychain send + near contract deploy $VALIDATOR_ID use-file $MOCK_STAKING_POOL_CONTRACT with-init-call new json-args '{"owner_id":"'$OWNER_ID'","stake_public_key":"'$STAKE_PUBLIC_KEY'","voting_account_id":"'$VOTING_ACCOUNT_ID'"}' prepaid-gas '100.0 Tgas' attached-deposit '0 NEAR' network-config testnet sign-with-legacy-keychain send # stake some NEAR to near contract call-function as-transaction $VALIDATOR_ID deposit_and_stake json-args {} prepaid-gas '200.0 Tgas' attached-deposit '1 NEAR' sign-as $STAKE_ACCOUNT_ID network-config testnet sign-with-legacy-keychain send done diff --git a/tests/scripts/vote.sh b/tests/scripts/vote.sh index 715ee7c..aa4cd73 100644 --- a/tests/scripts/vote.sh +++ b/tests/scripts/vote.sh @@ -1,11 +1,12 @@ #!/bin/bash +export SUFFIX=delta export OWNER_ID=mock-owner.testnet -export VOTING_ACCOUNT_ID=mock-proposal.testnet +export VOTING_ACCOUNT_ID="mock-proposal-"$SUFFIX".testnet" -for i in {101..200}; do - VALIDATOR_ID="mock-validator-"${i}".testnet" +for i in {1..1}; do + VALIDATOR_ID="mock-node-"$SUFFIX"-"${i}".testnet" # vote by validator - near contract call-function as-transaction $VALIDATOR_ID vote json-args '{"voting_account_id":"'$VOTING_ACCOUNT_ID'","is_vote":true}' prepaid-gas '200.0 Tgas' attached-deposit '0 NEAR' sign-as $OWNER_ID network-config testnet sign-with-legacy-keychain send + near contract call-function as-transaction $VOTING_ACCOUNT_ID vote json-args '{"staking_pool_id":"'$VALIDATOR_ID'","vote":"yes"}' prepaid-gas '200.0 Tgas' attached-deposit '0 NEAR' sign-as $OWNER_ID network-config testnet sign-with-legacy-keychain send done # get total voted stake diff --git a/tests/test_vote.rs b/tests/test_vote.rs index 50e455d..8e3cca3 100644 --- a/tests/test_vote.rs +++ b/tests/test_vote.rs @@ -1,7 +1,9 @@ +use near_sdk::json_types::U128; use near_sdk::{AccountId, Gas, NearToken}; use serde_json::json; use std::collections::HashMap; use std::time::{SystemTime, UNIX_EPOCH}; +use validator_voting::Vote; mod utils; use utils::*; @@ -128,6 +130,10 @@ async fn test_simple_vote() -> Result<(), Box> { "{:#?}", outcome.into_result().unwrap_err() ); + // print logs emitted by the contract + for log in outcome.logs() { + println!("contract log: {}", log); + } Ok(()) } @@ -157,7 +163,7 @@ async fn test_many_votes() -> Result<(), Box> { println!( "total staked: {}, {:#?}", alice.id(), - total_staked.json::()? + total_staked.json::()? ); } @@ -206,7 +212,7 @@ async fn test_many_votes() -> Result<(), Box> { voting_contract .view("get_votes") .await? - .json::>()? + .json::>()? ); } @@ -313,7 +319,7 @@ async fn test_vote_no_after_vote_yes() -> Result<(), Box> ); let votes = owner.view(voting_contract.id(), "get_votes").await?; - assert_eq!(votes.json::>()?.len(), 1); + assert_eq!(votes.json::>()?.len(), 1); let outcome = owner .call(voting_contract.id(), "vote") @@ -332,7 +338,7 @@ async fn test_vote_no_after_vote_yes() -> Result<(), Box> ); let votes = owner.view(voting_contract.id(), "get_votes").await?; - assert_eq!(votes.json::>()?.len(), 0); + assert_eq!(votes.json::>()?.len(), 1); Ok(()) } @@ -384,7 +390,7 @@ async fn test_vote_yes_after_vote_no() -> Result<(), Box> ); let votes = owner.view(voting_contract.id(), "get_votes").await?; - assert_eq!(votes.json::>()?.len(), 0); + assert_eq!(votes.json::>()?.len(), 1); let outcome = owner .call(voting_contract.id(), "vote") @@ -403,7 +409,7 @@ async fn test_vote_yes_after_vote_no() -> Result<(), Box> ); let votes = owner.view(voting_contract.id(), "get_votes").await?; - assert_eq!(votes.json::>()?.len(), 1); + assert_eq!(votes.json::>()?.len(), 1); Ok(()) } @@ -469,7 +475,7 @@ async fn test_unstake_after_voting() -> Result<(), Box> { ); let votes = owner.view(voting_contract.id(), "get_votes").await?; - let votes = votes.json::>()?; + let votes = votes.json::>()?; assert_eq!(votes.len(), 1); assert!(votes.contains_key(staking_pool_contracts[0].id())); @@ -492,7 +498,7 @@ async fn test_unstake_after_voting() -> Result<(), Box> { ); let votes = owner.view(voting_contract.id(), "get_votes").await?; - let votes = votes.json::>()?; + let votes = votes.json::>()?; assert_eq!(votes.len(), 2); assert!(votes.contains_key(staking_pool_contracts[1].id()));