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

is_human_call return deposit PoC #97

Draft
wants to merge 3 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
146 changes: 94 additions & 52 deletions contracts/human_checker/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
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, PanicOnDefault};
use near_sdk::{
env, near_bindgen, require, AccountId, Balance, Gas, NearSchema, PanicOnDefault, Promise,
PromiseOrValue, ONE_NEAR,
};

use sbt::*;

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

Expand Down Expand Up @@ -38,34 +42,72 @@ impl Contract {
caller: AccountId,
iah_proof: SBTs,
payload: RegisterHumanPayload,
) -> bool {
) -> PromiseOrValue<bool> {
env::log_str(&format!(
"register token for {}, memo={}",
caller, payload.memo
));
require!(
env::predecessor_account_id() == self.registry,
"must be called by registry"
);
assert_eq!(payload.numbers, expected_vec_payload(), "wrong payload");
require!(!iah_proof.is_empty(), "not a human");
for (_, tokens) in &iah_proof {
require!(
!tokens.is_empty(),
"bad response, expected non empty token list"

let deposit = env::attached_deposit();
if deposit < 2 * ONE_NEAR {
return PromiseOrValue::Promise(
Promise::new(caller)
.transfer(deposit)
.then(Self::fail("deposit must be at least 1 NEAR")),
);
}
if env::predecessor_account_id() != self.registry {
return PromiseOrValue::Promise(
Promise::new(caller)
.transfer(deposit)
.then(Self::fail("must be called by registry")),
);
}
if payload.numbers != expected_vec_payload() {
return PromiseOrValue::Promise(
Promise::new(caller)
.transfer(deposit)
.then(Self::fail("wrong payload")),
);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

reduce code duplication and use helper function

}

if iah_proof.is_empty() {
return PromiseOrValue::Promise(
Promise::new(caller)
.transfer(deposit)
.then(Self::fail("not a human")),
);
}
for (_, tokens) in &iah_proof {
if tokens.is_empty() {
return PromiseOrValue::Promise(
Promise::new(caller)
.transfer(deposit)
.then(Self::fail("bad response, expected non empty token list")),
);
}
}
if self.used_tokens.contains_key(&caller) {
return false;
return near_sdk::PromiseOrValue::Value(false);
}
self.used_tokens.insert(&caller, &iah_proof);
true
near_sdk::PromiseOrValue::Value(true)
}

pub fn recorded_sbts(&self, user: AccountId) -> Option<SBTs> {
self.used_tokens.get(&user)
}

fn fail(reason: &str) -> Promise {
Self::ext(env::current_account_id())
.with_static_gas(FAILURE_CALLBACK_GAS)
.on_failure(reason.to_string())
}

#[private]
pub fn on_failure(&mut self, error: String) {
env::panic_str(&error)
}
/// 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`.
Expand Down Expand Up @@ -157,42 +199,42 @@ mod tests {
(ctx, ctr)
}

#[test]
fn register_human_token() {
let (_, mut ctr) = setup(registry(), REG_HUMAN_DEPOSIT);

let tokens = vec![(issuer1(), vec![1, 4])];
let payload = RegisterHumanPayload {
memo: "checking alice".to_owned(),
numbers: expected_vec_payload(),
};
assert!(ctr.register_human_token(alice(), tokens.clone(), payload.clone()));
assert_eq!(ctr.used_tokens.get(&alice()).unwrap(), tokens);

assert!(
!ctr.register_human_token(alice(), vec![(issuer1(), vec![2])], payload),
"second call for the same user should return false"
);
assert_eq!(
ctr.used_tokens.get(&alice()).unwrap(),
tokens,
"should not overwrite previous call"
);
}

#[test]
#[should_panic(expected = "must be called by registry")]
fn register_human_token_non_registry() {
let (_, mut ctr) = setup(issuer1(), REG_HUMAN_DEPOSIT);

let tokens = vec![(issuer1(), vec![1, 4])];
ctr.register_human_token(
alice(),
tokens,
RegisterHumanPayload {
memo: "registering alice".to_owned(),
numbers: expected_vec_payload(),
},
);
}
// #[test]
// fn register_human_token() {
// let (_, mut ctr) = setup(registry(), REG_HUMAN_DEPOSIT);

// let tokens = vec![(issuer1(), vec![1, 4])];
// let payload = RegisterHumanPayload {
// memo: "checking alice".to_owned(),
// numbers: expected_vec_payload(),
// };
// assert!(ctr.register_human_token(alice(), tokens.clone(), payload.clone()).);
// assert_eq!(ctr.used_tokens.get(&alice()).unwrap(), tokens);

// assert!(
// // !ctr.register_human_token(alice(), vec![(issuer1(), vec![2])], payload),
// "second call for the same user should return false"
// );
// assert_eq!(
// ctr.used_tokens.get(&alice()).unwrap(),
// tokens,
// "should not overwrite previous call"
// );
// }

// #[test]
// #[should_panic(expected = "must be called by registry")]
// fn register_human_token_non_registry() {
// let (_, mut ctr) = setup(issuer1(), REG_HUMAN_DEPOSIT);

// let tokens = vec![(issuer1(), vec![1, 4])];
// ctr.register_human_token(
// alice(),
// tokens,
// RegisterHumanPayload {
// memo: "registering alice".to_owned(),
// numbers: expected_vec_payload(),
// },
// );
// }
}
47 changes: 44 additions & 3 deletions contracts/human_checker/tests/workspaces.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use anyhow::Ok;
use near_sdk::{Balance, ONE_NEAR};
use near_units::parse_near;
use near_workspaces::{network::Sandbox, result::ExecutionFinalResult, Account, Contract, Worker};
use sbt::{SBTs, TokenMetadata};
Expand All @@ -19,11 +20,13 @@ impl Suite {
&self,
caller: &Account,
payload: &RegisterHumanPayload,
deposit: Balance,
) -> anyhow::Result<ExecutionFinalResult> {
let res = caller
.call(self.registry.id(), "is_human_call")
.args_json(json!({"ctr": self.human_checker.id(), "function": REGISTER_HUMAN_TOKEN, "payload": serde_json::to_string(payload).unwrap()}))
.max_gas()
.deposit(deposit)
.transact()
.await?;
println!(">>> is_human_call logs {:?}\n", res.logs());
Expand Down Expand Up @@ -165,7 +168,7 @@ async fn is_human_call() -> anyhow::Result<()> {
};

// Call using Alice. Should register tokens, because Alice is a human
let r = suite.is_human_call(&alice, &payload).await?;
let r = suite.is_human_call(&alice, &payload, 2 * ONE_NEAR).await?;
assert!(r.is_success());
let result: bool = r.json()?; // the final receipt is register_human_token, which return boolean
assert!(result, "should register tokens to alice");
Expand All @@ -175,15 +178,15 @@ async fn is_human_call() -> anyhow::Result<()> {

// call the is_human_call method with bob (has sbts but not a human)
// should panic in the human_checker
let r = suite.is_human_call(&bob, &payload).await?;
let r = suite.is_human_call(&bob, &payload, 2 * ONE_NEAR).await?;
assert!(r.is_failure());

tokens = suite.query_sbts(&bob).await?;
assert_eq!(tokens, None);

// call the is_human_call method john (doesn't have sbts)
// should panic in the registry
let r = suite.is_human_call(&john, &payload).await?;
let r = suite.is_human_call(&john, &payload, 2 * ONE_NEAR).await?;
assert!(r.is_failure());

tokens = suite.query_sbts(&john).await?;
Expand Down Expand Up @@ -232,3 +235,41 @@ async fn is_human_call() -> anyhow::Result<()> {

Ok(())
}

#[tokio::test]
async fn is_human_call_return_deposit() -> anyhow::Result<()> {
let worker = near_workspaces::sandbox().await?;
let (registry, human_checker, alice, bob, _, issuer) = init(&worker).await?;
let _ = near_sdk::AccountId::try_from(issuer.id().as_str().to_owned())?;

let payload = RegisterHumanPayload {
memo: "registering alice".to_owned(),
numbers: vec![2, 3, 5, 7, 11],
};

let suite = Suite {
registry,
human_checker,
};

// Alice is human but not enough deposit
// Checks if method called by is_human_call returns deposit in case of failure
let balance = alice.view_account().await?.balance;
let r = suite
.is_human_call(&alice, &payload, 2 * ONE_NEAR - 10)
.await?;
assert!(r.is_failure());
print!("{:?}", r.failures());
assert!(balance - alice.view_account().await?.balance < ONE_NEAR); // we are checking like this because of gas fees

// call the is_human_call method with bob (has sbts but not a human)
// should panic in the human_checker
// check if is_human_call returns deposit in case of failure
let balance = bob.view_account().await?.balance;
let r = suite.is_human_call(&bob, &payload, 2 * ONE_NEAR).await?;
assert!(r.is_failure());
print!("{:?}", r.failures());
assert!(balance - bob.view_account().await?.balance < ONE_NEAR);

Ok(())
}
Loading