Skip to content

Commit

Permalink
registry: is_human_call_lock integration tests (#98)
Browse files Browse the repository at this point in the history
Co-authored-by: sczembor <[email protected]>
  • Loading branch information
robert-zaremba and sczembor committed Nov 2, 2023
1 parent babd065 commit db686fc
Show file tree
Hide file tree
Showing 5 changed files with 125 additions and 3 deletions.
1 change: 1 addition & 0 deletions contracts/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions contracts/human_checker/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ near-sdk.workspace = true
serde_json.workspace = true

sbt = { path = "../sbt" }
registry = { path = "../registry" }

[dev-dependencies]
anyhow.workspace = true
Expand Down
11 changes: 11 additions & 0 deletions contracts/human_checker/src/ext.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ use near_sdk::json_types::Base64VecU8;
use near_sdk::serde::Deserialize;
use near_sdk::{ext_contract, AccountId, PromiseOrValue};

use registry::errors::IsHumanCallErr;

// imports needed for conditional derive (required for tests)
#[allow(unused_imports)]
use near_sdk::serde::Serialize;
Expand All @@ -15,4 +17,13 @@ pub trait ExtSbtRegistry {
function: String,
payload: String,
) -> PromiseOrValue<bool>;

fn is_human_call_lock(
&mut self,
ctr: AccountId,
function: String,
payload: String,
lock_duration: u64,
with_proof: bool,
) -> Result<Promise, IsHumanCallErr>;
}
54 changes: 52 additions & 2 deletions contracts/human_checker/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
use near_sdk::borsh::{self, BorshDeserialize, BorshSerialize};
use near_sdk::collections::LookupMap;
use near_sdk::serde::{Deserialize, Serialize};
use near_sdk::{env, near_bindgen, require, AccountId, Balance, NearSchema, PanicOnDefault};
use near_sdk::{env, near_bindgen, require, AccountId, Balance, PanicOnDefault};

use sbt::*;

pub const MILI_NEAR: Balance = 1_000_000_000_000_000_000_000;
pub const REG_HUMAN_DEPOSIT: Balance = 3 * MILI_NEAR;
/// maximum time for proposal voting in milliseconds.
pub const VOTING_DURATION: u64 = 20_000;

#[near_bindgen]
#[derive(BorshDeserialize, BorshSerialize, PanicOnDefault)]
Expand Down Expand Up @@ -63,16 +65,64 @@ impl Contract {
pub fn recorded_sbts(&self, user: AccountId) -> Option<SBTs> {
self.used_tokens.get(&user)
}

/// Simulates a governance voting. Every valid human (as per IAH registry) can vote.
/// To avoid double voting by an account who is doing soul_transfer while a proposal is
/// active, we require that voing must be called through `iah_registry.is_human_call_lock`.
/// We check that the caller set enough `lock_duration` for soul transfers.
/// Arguments:
/// * `caller`: account ID making a vote (passed by `iah_registry.is_human_call`)
/// * `locked_until`: time in milliseconds, untile when the caller is locked for soul
/// transfers (reported by `iah_registry.is_human_call`).
/// * `iah_proof`: proof of humanity. It's not required and will be ignored.
/// * `payload`: the proposal ID and the vote (approve or reject).
#[payable]
pub fn vote(
&mut self,
caller: AccountId,
locked_until: u64,
#[allow(unused_variables)] iah_proof: Option<SBTs>,
payload: VotePayload,
) {
// for this simulation we imagine that every proposal ID is valid and it's finishing
// at "now" + VOTING_DURATION
require!(
env::predecessor_account_id() == self.registry,
"must be called by registry"
);
require!(
locked_until >= env::block_timestamp_ms() + VOTING_DURATION,
"account not locked for soul transfer for sufficient amount of time"
);
require!(payload.prop_id > 0, "invalid proposal id");
require!(
payload.vote == "approve" || payload.vote == "reject",
"invalid vote: must be either 'approve' or 'reject'"
);

env::log_str(&format!(
"VOTED: voter={}, proposal={}, vote={}",
caller, payload.prop_id, payload.vote,
));
}
}

#[derive(Serialize, Deserialize)]
#[cfg_attr(not(target_arch = "wasm32"), derive(Debug, NearSchema, Clone))]
#[cfg_attr(not(target_arch = "wasm32"), derive(Debug, Clone))]
#[serde(crate = "near_sdk::serde")]
pub struct RegisterHumanPayload {
pub memo: String,
pub numbers: Vec<u32>,
}

#[derive(Serialize, Deserialize)]
#[cfg_attr(not(target_arch = "wasm32"), derive(Debug, Clone))]
#[serde(crate = "near_sdk::serde")]
pub struct VotePayload {
pub prop_id: u32,
pub vote: String,
}

pub(crate) fn expected_vec_payload() -> Vec<u32> {
vec![2, 3, 5, 7, 11]
}
Expand Down
61 changes: 60 additions & 1 deletion contracts/human_checker/tests/workspaces.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,10 @@ use near_workspaces::{network::Sandbox, result::ExecutionFinalResult, Account, C
use sbt::{SBTs, TokenMetadata};
use serde_json::json;

use human_checker::RegisterHumanPayload;
use human_checker::{RegisterHumanPayload, VotePayload, VOTING_DURATION};

const REGISTER_HUMAN_TOKEN: &str = "register_human_token";
const MSECOND : u64 = 1000;

struct Suite {
registry: Contract,
Expand All @@ -29,6 +30,23 @@ impl Suite {
Ok(res)
}

pub async fn is_human_call_lock(
&self,
caller: &Account,
lock_duration: u64,
payload: &VotePayload,
) -> anyhow::Result<ExecutionFinalResult> {
let res = caller
.call(self.registry.id(), "is_human_call_lock")
.args_json(json!({"ctr": self.human_checker.id(), "function": "vote", "payload": serde_json::to_string(payload).unwrap(), "lock_duration": lock_duration, "with_proof": false}))
.max_gas()
.transact()
.await?;
println!(">>> is_human_call_lock logs {:?}\n", res.logs());
Ok(res)
}


pub async fn query_sbts(&self, user: &Account) -> anyhow::Result<Option<SBTs>> {
// check the key does not exists in human checker
let r = self
Expand Down Expand Up @@ -171,5 +189,46 @@ async fn is_human_call() -> anyhow::Result<()> {
tokens = suite.query_sbts(&john).await?;
assert_eq!(tokens, None);


//
// Test Vote with lock duration
//

//
// test1: too short lock duration: should fail
let mut payload = VotePayload{prop_id: 10, vote: "approve".to_string()};
let r = suite.is_human_call_lock(&alice, VOTING_DURATION / 3 *2, &payload).await?;
assert!(r.is_failure());
let failure_str = format!("{:?}",r.failures());
assert!(failure_str.contains("sufficient amount of time"), "{}", failure_str);

//
// test2: second call, should not change
let r = suite.is_human_call_lock(&alice, VOTING_DURATION / 3*2, &payload).await?;
assert!(r.is_failure());
let failure_str = format!("{:?}",r.failures());
assert!(failure_str.contains("sufficient amount of time"), "{}", failure_str);

//
// test3: longer call should be accepted, but should fail on wrong payload (vote option)
payload.vote = "wrong-wrong".to_string();
let r = suite.is_human_call_lock(&alice, VOTING_DURATION +MSECOND, &payload).await?;
assert!(r.is_failure());
let failure_str = format!("{:?}",r.failures());
assert!(failure_str.contains("invalid vote: must be either"), "{}", failure_str);

//
// test4: should work with correct input
payload.vote = "approve".to_string();
let r = suite.is_human_call_lock(&alice, VOTING_DURATION +MSECOND, &payload).await?;
assert!(r.is_success());

//
// test5: should fail with not a human
let r = suite.is_human_call_lock(&john, VOTING_DURATION + MSECOND, &payload).await?;
assert!(r.is_failure());
let failure_str = format!("{:?}",r.failures());
assert!(failure_str.contains("is not a human"), "{}", failure_str);

Ok(())
}

0 comments on commit db686fc

Please sign in to comment.