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

Add all edge-case unit tests and integration test for registry blacklist #72

Merged
merged 12 commits into from
Aug 22, 2023
Binary file modified contracts/deployed/registry.wasm
Binary file not shown.
5 changes: 5 additions & 0 deletions contracts/registry/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,3 +45,8 @@ The IAH Registry supports the following extra queries, which are not part of the
See the function documentation for more details and [integration test](https://github.com/near-ndc/i-am-human/blob/780e8cf8326fd0a7976c48afbbafd4553cc7b639/contracts/human_checker/tests/workspaces.rs#L131) for usage.

- `sbt_burn(issuer: AccountId, tokens: Vec<TokenId>, memo: Option<String>)` - every holder can burn some of his tokens.

## Soul transfer

- The registry enables atomic `soul_transfers`. It Transfers all SBT tokens from one account to another account.
It will fail if owner is `blacklisted`.
118 changes: 117 additions & 1 deletion contracts/registry/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -182,10 +182,11 @@ impl Contract {
/// continued by a subsequent call.
/// Emits `Ban` event for the caller at the beginning of the process.
/// Emits `SoulTransfer` event only once all the tokens from the caller were transferred
/// and at least one token was trasnfered (caller had at least 1 sbt).
/// and at least one token was transferred (caller had at least 1 sbt).
/// + User must keep calling the `sbt_soul_transfer` until `true` is returned.
/// + If caller does not have any tokens, nothing will be transfered, the caller
/// will be banned and `Ban` event will be emitted.
/// Fails if owner is blacklisted.
#[payable]
pub fn sbt_soul_transfer(
&mut self,
Expand All @@ -200,6 +201,7 @@ impl Contract {
// order to facilitate tests.
pub(crate) fn _sbt_soul_transfer(&mut self, recipient: AccountId, limit: usize) -> (u32, bool) {
let owner = env::predecessor_account_id();
self.assert_not_blacklisted(&owner);
amityadav0 marked this conversation as resolved.
Show resolved Hide resolved

let (resumed, start) = self.transfer_continuation(&owner, &recipient, true);

Expand Down Expand Up @@ -600,6 +602,13 @@ impl Contract {
);
}

#[inline]
pub(crate) fn assert_not_blacklisted(&self, owner: &AccountId) {
if self.flagged.get(owner) == Some(AccountFlag::Blacklisted) {
env::panic_str("account blacklisted");
}
}

/// note: use issuer_id() if you need issuer_id
pub(crate) fn assert_issuer(&self, issuer: &AccountId) -> IssuerId {
// TODO: use Result rather than panic
Expand Down Expand Up @@ -935,6 +944,7 @@ mod tests {
ctr.admin_add_sbt_issuer(issuer1());
ctr.admin_add_sbt_issuer(issuer2());
ctr.admin_add_sbt_issuer(issuer3());
ctr.admin_set_authorized_flaggers([predecessor.clone()].to_vec());
ctx.predecessor_account_id = predecessor.clone();
testing_env!(ctx.clone());
return (ctx, ctr);
Expand Down Expand Up @@ -2759,4 +2769,110 @@ mod tests {
"{}".to_string(),
);
}

#[test]
fn admin_set_authorized_flaggers() {
let (mut ctx, mut ctr) = setup(&admin(), MINT_DEPOSIT);

let flaggers = [dan()].to_vec();
ctr.admin_set_authorized_flaggers(flaggers);

ctx.predecessor_account_id = dan();
testing_env!(ctx);
ctr.assert_authorized_flagger();
amityadav0 marked this conversation as resolved.
Show resolved Hide resolved
}

#[test]
#[should_panic(expected = "not an admin")]
fn admin_set_authorized_flaggers_fail() {
let (mut ctx, mut ctr) = setup(&admin(), MINT_DEPOSIT);

ctx.predecessor_account_id = dan();
testing_env!(ctx.clone());

let flaggers = [dan()].to_vec();
ctr.admin_set_authorized_flaggers(flaggers);
}

#[test]
fn admin_flag_accounts() {
let (_, mut ctr) = setup(&alice(), MINT_DEPOSIT);

ctr.admin_flag_accounts(AccountFlag::Blacklisted, [dan(), issuer1()].to_vec(), "memo".to_owned());
ctr.admin_flag_accounts(AccountFlag::Verified, [issuer2()].to_vec(), "memo".to_owned());

let exp = r#"EVENT_JSON:{"standard":"i_am_human","version":"1.0.0","event":"flag_blacklisted","data":["dan.near","sbt.n"]}"#;
// check only flag event is emitted
assert_eq!(test_utils::get_logs().len(), 2);
assert_eq!(test_utils::get_logs()[0], exp);

assert_eq!(ctr.account_flagged(dan()), Some(AccountFlag::Blacklisted));
assert_eq!(ctr.account_flagged(issuer1()), Some(AccountFlag::Blacklisted));
assert_eq!(ctr.account_flagged(issuer2()), Some(AccountFlag::Verified));

ctr.admin_unflag_accounts([dan()].to_vec(), "memo".to_owned());

let exp = r#"EVENT_JSON:{"standard":"i_am_human","version":"1.0.0","event":"unflag","data":["dan.near"]}"#;
assert_eq!(test_utils::get_logs().len(), 3);
assert_eq!(test_utils::get_logs()[2], exp);

assert_eq!(ctr.account_flagged(dan()), None);
assert_eq!(ctr.account_flagged(issuer1()), Some(AccountFlag::Blacklisted));
}

#[test]
#[should_panic(expected = "not authorized")]
fn admin_flag_accounts_non_authorized() {
let (mut ctx, mut ctr) = setup(&alice(), MINT_DEPOSIT);
amityadav0 marked this conversation as resolved.
Show resolved Hide resolved

ctx.predecessor_account_id = dan();
testing_env!(ctx.clone());
ctr.admin_flag_accounts(AccountFlag::Blacklisted, [dan()].to_vec(), "memo".to_owned());
}

#[test]
#[should_panic(expected = "not authorized")]
fn admin_unflag_accounts_non_authorized() {
let (mut ctx, mut ctr) = setup(&alice(), MINT_DEPOSIT);

ctr.admin_flag_accounts(AccountFlag::Blacklisted, [dan(), issuer1()].to_vec(), "memo".to_owned());
assert_eq!(ctr.account_flagged(dan()), Some(AccountFlag::Blacklisted));

ctx.predecessor_account_id = dan();
testing_env!(ctx.clone());
ctr.admin_unflag_accounts([dan()].to_vec(), "memo".to_owned());
}

#[test]
fn is_human_flagged() {
let (_, mut ctr) = setup(&fractal_mainnet(), MINT_DEPOSIT);

let m1_1 = mk_metadata(1, Some(START));
ctr.sbt_mint(vec![(dan(), vec![m1_1])]);
let human_proof = vec![(fractal_mainnet(), vec![1])];
ctr.admin_flag_accounts(AccountFlag::Verified, [dan()].to_vec(), "memo".to_owned());
assert_eq!(ctr.is_human(dan()), human_proof.clone());

ctr.admin_flag_accounts(AccountFlag::Blacklisted, [dan()].to_vec(), "memo".to_owned());
assert_eq!(ctr.is_human(dan()), vec![]);

ctr.admin_unflag_accounts([dan()].to_vec(), "memo".to_owned());
assert_eq!(ctr.is_human(dan()), human_proof);
}
amityadav0 marked this conversation as resolved.
Show resolved Hide resolved

#[test]
#[should_panic(expected = "account blacklisted")]
fn black_listed_soul_transfer() {
let (mut ctx, mut ctr) = setup(&issuer1(), 2 * MINT_DEPOSIT);

let m1_1 = mk_metadata(1, Some(START + 10));
ctr.sbt_mint(vec![(alice(), vec![m1_1.clone()])]);

ctr.admin_flag_accounts(AccountFlag::Blacklisted, [alice()].to_vec(), "memo".to_owned());

// make soul transfer
ctx.predecessor_account_id = alice();
testing_env!(ctx.clone());
ctr.sbt_soul_transfer(alice2(), None);
}
}
2 changes: 1 addition & 1 deletion contracts/registry/src/storage.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ pub enum StorageKey {
AdminsFlagged,
}

#[derive(BorshSerialize, BorshDeserialize, BorshStorageKey, Serialize, Deserialize)]
#[derive(BorshSerialize, BorshDeserialize, BorshStorageKey, Serialize, Deserialize, PartialEq)]
amityadav0 marked this conversation as resolved.
Show resolved Hide resolved
#[serde(crate = "near_sdk::serde")]
#[cfg_attr(not(target_arch = "wasm32"), derive(Debug))]
pub enum AccountFlag {
Expand Down
25 changes: 19 additions & 6 deletions contracts/registry/tests/workspaces.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
use anyhow::Ok;
use near_sdk::serde_json::json;
use near_units::parse_near;
use sbt::TokenMetadata;
use sbt::{TokenMetadata, ClassSet};
use workspaces::{network::Sandbox, Account, AccountId, Contract, Worker};

const MAINNET_REGISTRY_ID: &str = "registry.i-am-human.near";
const BLOCK_HEIGHT: u64 = 90979963;
const BLOCK_HEIGHT: u64 = 92042705;
const IAH_CLASS: u64 = 1;
const OG_CLASS: u64 = 2;

Expand Down Expand Up @@ -89,6 +89,17 @@ async fn assert_data_consistency(
.json()?;
assert_eq!(bob_og_supply, 0);

let iah_class_set: ClassSet = registry
.call("iah_class_set")
.args_json(json!({}))
.max_gas()
.transact()
.await?
.json()?;

assert_eq!(iah_class_set[0].0.to_string(), iah_issuer.id().to_string());
assert_eq!(iah_class_set[0].1[0], 1);

Ok(())
}

Expand Down Expand Up @@ -116,7 +127,7 @@ async fn init(
// init the contract
let res = registry_contract
.call("new")
.args_json(json!({"authority": authority_acc.id() }))
.args_json(json!({"authority": authority_acc.id(), "iah_issuer": iah_issuer.id(), "iah_classes": [1]}))
.max_gas()
.transact()
.await?;
Expand Down Expand Up @@ -190,7 +201,7 @@ async fn init(
));
}

#[ignore = "this test is not valid after the migration"]
//#[ignore = "this test is not valid after the migration"]
#[tokio::test]
async fn migration_mainnet() -> anyhow::Result<()> {
let worker = workspaces::sandbox().await?;
Expand All @@ -216,7 +227,7 @@ async fn migration_mainnet() -> anyhow::Result<()> {
// call the migrate method
let res = new_registry_contract
.call("migrate")
.args_json(json!({"iah_issuer": "iah-issuer.testnet", "iah_classes": [1]}))
.args_json(json!({"authorized_flaggers": ["alice.near"]}))
.max_gas()
.transact()
.await?;
Expand All @@ -236,6 +247,8 @@ async fn migration_mainnet() -> anyhow::Result<()> {
}

#[ignore = "this test is not valid after the migration"]
amityadav0 marked this conversation as resolved.
Show resolved Hide resolved
// handler error: [State of contract registry.i-am-human.near is too large to be viewed]
// For current block 99,142,922
#[tokio::test]
async fn migration_mainnet_real_data() -> anyhow::Result<()> {
// import the registry contract from mainnet with data
Expand Down Expand Up @@ -269,7 +282,7 @@ async fn migration_mainnet_real_data() -> anyhow::Result<()> {
// call the migrate method
let res = new_registry_mainnet
.call("migrate")
.args_json(json!({"iah_issuer": "iah-issuer.testnet", "iah_classes": [1]}))
.args_json(json!({"authorized_flaggers": ["alice.near"]}))
.max_gas()
.transact()
.await?;
Expand Down