diff --git a/contracts/Cargo.lock b/contracts/Cargo.lock index b52f675..2d8bdb7 100644 --- a/contracts/Cargo.lock +++ b/contracts/Cargo.lock @@ -979,6 +979,7 @@ dependencies = [ "pretty_assertions", "sbt", "serde_json", + "test-util", "tokio", "tracing", "uint", @@ -1612,6 +1613,7 @@ dependencies = [ "near-sandbox-utils", "near-sdk", "near-units", + "test-util", "tokio", "workspaces", ] @@ -3475,6 +3477,18 @@ dependencies = [ "windows-sys", ] +[[package]] +name = "test-util" +version = "1.0.0" +dependencies = [ + "anyhow", + "near-sdk", + "near-units", + "sbt", + "serde_json", + "workspaces", +] + [[package]] name = "thiserror" version = "1.0.46" diff --git a/contracts/Makefile b/contracts/Makefile index cc3995b..aa3be42 100644 --- a/contracts/Makefile +++ b/contracts/Makefile @@ -8,7 +8,7 @@ add-deps: rustup target add wasm32-unknown-unknown build: res - @RUSTFLAGS='-C link-arg=-s' cargo build --all --target wasm32-unknown-unknown --release + @RUSTFLAGS='-C link-arg=-s' cargo build --workspace --exclude test-util --target wasm32-unknown-unknown --release @cp target/wasm32-unknown-unknown/release/*.wasm res/ test: build diff --git a/contracts/Makefile-common.mk b/contracts/Makefile-common.mk index 32b2cff..396af70 100644 --- a/contracts/Makefile-common.mk +++ b/contracts/Makefile-common.mk @@ -15,7 +15,7 @@ build-abi: res build-all: res - @RUSTFLAGS='-C link-arg=-s' cargo build --all --target wasm32-unknown-unknown --release + @RUSTFLAGS='-C link-arg=-s' cargo build --workspace --exclude test-util --target wasm32-unknown-unknown --release @cp ../target/wasm32-unknown-unknown/release/*.wasm ../res/ @cargo near abi @cp ../target/near/*/*_abi.json ../res diff --git a/contracts/easy-poll/Cargo.toml b/contracts/easy-poll/Cargo.toml index ee3f46a..53492f7 100644 --- a/contracts/easy-poll/Cargo.toml +++ b/contracts/easy-poll/Cargo.toml @@ -28,3 +28,4 @@ workspaces.workspace = true near-primitives.workspace = true near-units.workspace = true tracing.workspace = true +test-util = { path = "../test-util" } diff --git a/contracts/easy-poll/tests/workspaces.rs b/contracts/easy-poll/tests/workspaces.rs new file mode 100644 index 0000000..5fc6da1 --- /dev/null +++ b/contracts/easy-poll/tests/workspaces.rs @@ -0,0 +1,133 @@ +use anyhow::Ok; +use easy_poll::{PollResult, Results, Status}; +use near_sdk::{serde_json::json, store::vec}; +use near_units::parse_near; +use sbt::TokenMetadata; +use test_util::{build_contract, get_block_timestamp, registry_default, registry_mint_iah_tokens}; +use workspaces::{network::Sandbox, Account, AccountId, Contract, Worker}; + +const IAH_CLASS: u64 = 1; + +async fn respond( + easy_poll_contract: &AccountId, + responder: &Account, + poll_id: u64, +) -> anyhow::Result<()> { + let res = responder + .call(easy_poll_contract, "respond") + .args_json(json!({"poll_id": poll_id, "answers": [{"YesNo": true}]})) + .deposit(parse_near!("1 N")) + .max_gas() + .transact() + .await?; + assert!(res.is_success(), "{:?}", res.receipt_failures()); + Ok(()) +} + +async fn init(worker: &Worker) -> anyhow::Result<(Contract, Account, Account)> { + let authority_acc = worker.dev_create_account().await?; + let flagger = worker.dev_create_account().await?; + let alice_acc = worker.dev_create_account().await?; + let bob_acc = worker.dev_create_account().await?; + + // Setup registry contract and issue iah to alice + let (registry_contract, _) = registry_default( + &worker, + authority_acc.id(), + vec![flagger.id()], + vec![alice_acc.id()], + ) + .await?; + + // Setup easy-poll contract + let easy_poll_contract = build_contract( + &worker, + "./", + "new", + json!({"sbt_registry": registry_contract.id()}), + ) + .await?; + + Ok((easy_poll_contract, alice_acc, bob_acc)) +} + +#[tokio::test] +async fn flow1() -> anyhow::Result<()> { + // 1. create non-human gated poll + // 2. create human gated poll + // 3. vote for both polls with a human verified account + // 4. vote for both polls with a non-human account + // 5. check the responds were recorded correctly + + // import the registry contract from mainnet with data + let worker = workspaces::sandbox().await?; + let (easy_poll_contract, alice, bob) = init(&worker).await?; + + let now_ms = get_block_timestamp(&worker).await? / 1_000_000; + // create a poll + let poll_id_non_human_gated: u64 = bob.call(easy_poll_contract.id(), "create_poll") + .args_json(json!({"iah_only": false, "questions": [{"question_type": {"YesNo": false}, "required": true, + "title": "non-human gated"}], "starts_at": now_ms + 20000, "ends_at": now_ms + 300000, + "title": "Testing Poll 1", "tags": ["test"], "description": "poll desc", "link": "test.io"})) + .max_gas() + .transact() + .await? + .json()?; + + // create a poll + let poll_id_human_gated: u64 = bob.call(easy_poll_contract.id(), "create_poll") + .args_json(json!({"iah_only": true, "questions": [{"question_type": {"YesNo": false}, "required": true, + "title": "human gated"}], "starts_at": now_ms + 5000, "ends_at": now_ms + 86400000, + "title": "Testing Poll 1", "tags": ["test"], "description": "poll desc", "link": "test.io"})) + .max_gas() + .transact() + .await? + .json()?; + + // fast forward + worker.fast_forward(100).await?; + + respond(easy_poll_contract.id(), &bob, poll_id_non_human_gated).await?; + respond(easy_poll_contract.id(), &alice, poll_id_non_human_gated).await?; + + // This vote should not be registered since the poll is human gated and bob is not human + respond(easy_poll_contract.id(), &bob, poll_id_human_gated).await?; + respond(easy_poll_contract.id(), &alice, poll_id_human_gated).await?; + + // assert the results are correct + let res: Option = bob + .call(easy_poll_contract.id(), "results") + .args_json(json!({ "poll_id": poll_id_non_human_gated })) + .max_gas() + .transact() + .await? + .json()?; + + assert_eq!( + res.unwrap(), + Results { + status: Status::NotStarted, + participants_num: 2, + results: vec![PollResult::YesNo((2, 0))] + } + ); + + let res: Option = bob + .call(easy_poll_contract.id(), "results") + .args_json(json!({ "poll_id": poll_id_human_gated })) + .max_gas() + .transact() + .await? + .json()?; + + assert_eq!( + res.unwrap(), + Results { + status: Status::NotStarted, + participants_num: 1, + results: vec![PollResult::YesNo((1, 0))] + } + ); + + Ok(()) +} diff --git a/contracts/kudos/Cargo.toml b/contracts/kudos/Cargo.toml index e1c40de..5295a91 100644 --- a/contracts/kudos/Cargo.toml +++ b/contracts/kudos/Cargo.toml @@ -23,3 +23,4 @@ near-sandbox-utils.workspace = true near-units.workspace = true tokio.workspace = true anyhow.workspace = true +test-util = { path = "../test-util" } diff --git a/contracts/kudos/tests/test_kudos.rs b/contracts/kudos/tests/test_kudos.rs index 6f3a3d1..412d081 100644 --- a/contracts/kudos/tests/test_kudos.rs +++ b/contracts/kudos/tests/test_kudos.rs @@ -1,10 +1,9 @@ mod types; mod utils; -mod workspaces; use crate::types::*; use crate::utils::*; -use crate::workspaces::{build_contract, gen_user_account, get_block_timestamp, transfer_near}; +use test_util::{build_contract, gen_user_account, get_block_timestamp, transfer_near}; use kudos_contract::WrappedCid; use kudos_contract::{utils::*, CommentId}; use kudos_contract::{Commentary, PROOF_OF_KUDOS_SBT_CLASS_ID}; diff --git a/contracts/kudos/tests/test_required_deposit.rs b/contracts/kudos/tests/test_required_deposit.rs index 8d34b01..c26bc80 100644 --- a/contracts/kudos/tests/test_required_deposit.rs +++ b/contracts/kudos/tests/test_required_deposit.rs @@ -1,9 +1,8 @@ mod types; mod utils; -mod workspaces; use crate::utils::*; -use crate::workspaces::{build_contract, gen_user_account, get_block_timestamp, transfer_near}; +use test_util::{build_contract, gen_user_account, get_block_timestamp, transfer_near}; use kudos_contract::{utils::*, WrappedCid}; use kudos_contract::{GIVE_KUDOS_COST, LEAVE_COMMENT_COST, UPVOTE_KUDOS_COST}; use near_sdk::serde_json::json; diff --git a/contracts/kudos/tests/test_social_db.rs b/contracts/kudos/tests/test_social_db.rs index 65e41c7..7b4172b 100644 --- a/contracts/kudos/tests/test_social_db.rs +++ b/contracts/kudos/tests/test_social_db.rs @@ -1,9 +1,8 @@ mod types; mod utils; -mod workspaces; use crate::utils::*; -use crate::workspaces::{build_contract, gen_user_account, transfer_near}; +use test_util::{build_contract, gen_user_account, transfer_near}; use kudos_contract::utils::*; use kudos_contract::SOCIAL_DB_GRANT_WRITE_PERMISSION_COST; use near_contract_standards::storage_management::{StorageBalance, StorageBalanceBounds}; diff --git a/contracts/oracle/README.md b/contracts/oracle/README.md index fa32282..a7c93f0 100644 --- a/contracts/oracle/README.md +++ b/contracts/oracle/README.md @@ -29,7 +29,7 @@ The goal of the oracle contract is to attest off chain fact, and mint SBT. The g 1. User has an external (off the NEAR blockchain) account, with properties we want to attest. Examples: - [GoodDollar verified account](https://help.gooddollar.org/kb/getting-started/how-to-complete-face-verification-process), used for verifying unique humans to receive $GD UBI - - Twitter [blue checkmark](https://help.twitter.com/en/managing-your-account/about-twitter-verified-accounts). + - Twitter blue checkmark - GitCoin passport - Fractal ID, attesting various verification levels: Face Scan, Proof of Address... diff --git a/contracts/test-util/Cargo.toml b/contracts/test-util/Cargo.toml new file mode 100644 index 0000000..e2b3843 --- /dev/null +++ b/contracts/test-util/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "test-util" +version = "1.0.0" +authors = [ + "NDC GWG (https://near.social/#/mob.near/widget/ProfilePage?accountId=govworkinggroup.near)", +] +edition = { workspace = true } +repository = { workspace = true } + +[dependencies] +near-sdk.workspace = true +serde_json.workspace = true +workspaces.workspace = true +anyhow.workspace = true +near-units.workspace = true + +sbt = { path = "../sbt" } + +[dev-dependencies] diff --git a/contracts/kudos/tests/workspaces.rs b/contracts/test-util/src/lib.rs similarity index 52% rename from contracts/kudos/tests/workspaces.rs rename to contracts/test-util/src/lib.rs index efe7c7e..95621a0 100644 --- a/contracts/kudos/tests/workspaces.rs +++ b/contracts/test-util/src/lib.rs @@ -1,6 +1,11 @@ +use anyhow::Ok; +use near_units::parse_near; +use sbt::TokenMetadata; +use serde_json::json; use std::str::FromStr; use workspaces::network::{NetworkClient, NetworkInfo, Sandbox}; use workspaces::result::ExecutionSuccess; +use workspaces::AccountId; use workspaces::{ types::{Balance, KeyType, SecretKey}, Account, Contract, DevNetwork, Worker, @@ -99,3 +104,79 @@ where { Ok(worker.view_block().await?.timestamp()) } + +/// Helper function to issue tokens to the users for testing purposes +pub async fn registry_mint_iah_tokens( + registry: &AccountId, + issuer: &Account, + class_id: u64, + accounts: Vec<&AccountId>, +) -> anyhow::Result<()> { + // populate registry with mocked data + let token_metadata = vec![TokenMetadata { + class: class_id, + issued_at: Some(0), + expires_at: None, + reference: None, + reference_hash: None, + }]; + let mut iah_token_spec = Vec::new(); + + for a in accounts { + iah_token_spec.push((a, token_metadata.clone())); + } + + let res = issuer + .call(registry, "sbt_mint") + .args_json(json!({ "token_spec": iah_token_spec })) + .deposit(parse_near!("5 N")) + .max_gas() + .transact() + .await?; + assert!(res.is_success(), "{:?}", res.receipt_failures()); + + Ok(()) +} + +/// Helper function to add issuers to the registry +pub async fn registry_add_issuer( + registry: &AccountId, + authority: &Account, + issuers: Vec<&AccountId>, +) -> anyhow::Result<()> { + for i in issuers { + let res = authority + .call(registry, "admin_add_sbt_issuer") + .args_json(json!({ "issuer": i })) + .max_gas() + .transact() + .await?; + assert!(res.is_success()); + } + Ok(()) +} + +// Helper function to deploy, initalize and mint iah sbts to the `iah_accounts`. +pub async fn registry_default( + worker: &Worker, + authority: &AccountId, + flaggers: Vec<&AccountId>, + iah_accounts: Vec<&AccountId>, +) -> anyhow::Result<(Account, Account)> +where + T: DevNetwork + Send + Sync, +{ + const IAH_CLASS: u64 = 1; + let iah_issuer = worker.dev_create_account().await?; + let registry_contract = build_contract( + &worker, + "./../registry", + "new", + json!({"authority": authority, "authorized_flaggers": flaggers, "iah_issuer": iah_issuer.id(), "iah_classes": [IAH_CLASS]}), + ).await?; + + // issue iah tokens to iah_accounts + registry_mint_iah_tokens(registry_contract.id(), &iah_issuer, 1, iah_accounts).await?; + + Ok((registry_contract.as_account().clone(), iah_issuer)) +}