Skip to content

Commit

Permalink
Add all edge-case unit tests and integration test for registry blackl…
Browse files Browse the repository at this point in the history
…ist (#72)

Co-authored-by: Robert Zaremba <[email protected]>
Co-authored-by: sczembor <[email protected]>
  • Loading branch information
3 people authored Aug 22, 2023
1 parent 3248cf1 commit 4ed7992
Show file tree
Hide file tree
Showing 5 changed files with 142 additions and 8 deletions.
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);

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();
}

#[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);

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);
}

#[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)]
#[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"]
// 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

0 comments on commit 4ed7992

Please sign in to comment.