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 integration test for iah #90

Merged
merged 11 commits into from
Sep 26, 2023
6 changes: 6 additions & 0 deletions contracts/Cargo.lock

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

3 changes: 3 additions & 0 deletions contracts/oracle/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ serde_json.workspace = true
cost = { path = "../cost" }
sbt = { path = "../sbt" }


sczembor marked this conversation as resolved.
Show resolved Hide resolved
[dev-dependencies]
rand = "^0.7"
near-primitives.workspace = true
Expand All @@ -30,3 +31,5 @@ tokio.workspace = true
workspaces.workspace = true
near-units.workspace = true
tracing.workspace = true

test-util = { path = "../test-util" }
133 changes: 132 additions & 1 deletion contracts/oracle/tests/integration_test.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,16 @@
use std::str::FromStr;

use near_sdk::ONE_NEAR;
use serde_json::json;
use test_util::{
build_contract,
common::ExternalAccountId,
gen_user_account,
utils::{build_signed_claim, generate_keys},
};
use workspaces::{Account, AccountId, Contract, DevNetwork, Worker};

use oracle_sbt::MINT_TOTAL_COST;
use oracle_sbt::{MINT_TOTAL_COST, MINT_TOTAL_COST_WITH_KYC};
use sbt::ContractMetadata;

const AUTHORITY_KEY: &str = "zqMwV9fTRoBOLXwt1mHxBAF3d0Rh9E9xwSAXR3/KL5E=";
Expand Down Expand Up @@ -115,6 +123,129 @@ async fn check_arithmetic_exception_mainnet() -> anyhow::Result<()> {
Ok(())
}

#[tokio::test]
async fn test_mint_sbt() -> anyhow::Result<()> {
let worker = workspaces::sandbox().await?;

let (sec_key, pub_key) = generate_keys();

sczembor marked this conversation as resolved.
Show resolved Hide resolved
let authority = gen_user_account(&worker, "admin.test.near").await?;
let iah_issuer = gen_user_account(&worker, "iah_issuer.test.near").await?;
let flagger = gen_user_account(&worker, "flagger.test.near").await?;

let registry_contract = build_contract(
sczembor marked this conversation as resolved.
Show resolved Hide resolved
&worker,
"../registry/",
"new",
json!({"authority": authority.id(), "authorized_flaggers": [flagger.id()], "iah_issuer": iah_issuer.id(), "iah_classes": [1]})
)
.await?;

let oracle_contract = build_contract(
&worker,
"../oracle/",
"new",
json!({
"authority": near_sdk::base64::encode(pub_key.unwrap_as_ed25519().as_ref()),
"metadata": {
"spec": "v1.0.0",
"name": "test-sbt",
"symbol": "SBT"
},
"registry": registry_contract.id(),
"claim_ttl": 100000000000u64,
"admin": authority.id(),
}),
)
sczembor marked this conversation as resolved.
Show resolved Hide resolved
.await?;

let user_account = gen_user_account(&worker, "user.test.near").await?;
let signed_claim = build_signed_claim(
near_sdk::AccountId::from_str(user_account.id().as_str())?,
ExternalAccountId::gen(),
false,
&sec_key,
)?;

// TODO: add check for specific error text
let _ = user_account
.call(oracle_contract.id(), "sbt_mint")
.args_json(signed_claim)
.max_gas()
.transact()
.await?
.into_result()
.expect_err("only root and implicit accounts are allowed to get SBT");

let user_account = worker.root_account()?;
let signed_claim = build_signed_claim(
near_sdk::AccountId::from_str(user_account.id().as_str())?,
ExternalAccountId::gen(),
false,
&sec_key,
)?;

// TODO: add check for specific error text
let _ = user_account
.call(oracle_contract.id(), "sbt_mint")
.args_json(signed_claim)
.max_gas()
.transact()
.await?
.into_result()
.expect_err("Requires attached deposit of exactly 0.008 NEAR");
amityadav0 marked this conversation as resolved.
Show resolved Hide resolved

let signed_claim = build_signed_claim(
near_sdk::AccountId::from_str(user_account.id().as_str())?,
ExternalAccountId::gen(),
true,
&sec_key,
)?;

// TODO: add check for specific error text
let _ = user_account
.call(oracle_contract.id(), "sbt_mint")
.args_json(&signed_claim)
.max_gas()
.transact()
.await?
.into_result()
.expect_err("Requires attached deposit of exactly 0.015 NEAR");

// TODO: add check for specific error text
let _ = user_account
.call(oracle_contract.id(), "sbt_mint")
.args_json(json!({
"claim_b64": signed_claim.claim_b64,
"claim_sig": format!("a{}", &signed_claim.claim_sig),
}))
.deposit(MINT_TOTAL_COST_WITH_KYC)
.max_gas()
.transact()
.await?
.into_result()
.expect_err("can't base64-decode claim_sig");

let user_account = worker.root_account()?;
let signed_claim = build_signed_claim(
near_sdk::AccountId::from_str(user_account.id().as_str())?,
ExternalAccountId::gen(),
false,
&sec_key,
)?;

let _ = user_account
.call(oracle_contract.id(), "sbt_mint")
.args_json(signed_claim)
.deposit(MINT_TOTAL_COST)
.max_gas()
.transact()
.await?
.into_result()?;

Ok(())
}

async fn check_arithmetic_exception(oracle: Contract, alice: Account) -> anyhow::Result<()> {
//
// replicating claim_sig_and_sbt_mint unit test
Expand Down
5 changes: 4 additions & 1 deletion contracts/test-util/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,10 @@ serde_json.workspace = true
workspaces.workspace = true
anyhow.workspace = true
near-units.workspace = true

ed25519-dalek.workspace = true
sbt = { path = "../sbt" }
uuid = { version = "1.3.3", features = ["v4", "fast-rng"] }
near-crypto = "^0"
chrono = "0.4.26"

[dev-dependencies]
46 changes: 46 additions & 0 deletions contracts/test-util/src/common.rs
sczembor marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
use near_sdk::borsh::{self, BorshDeserialize, BorshSerialize};
use near_sdk::serde::Serialize;
use near_sdk::AccountId;
use uuid::Uuid;

/// External account id represented as hexadecimal string
#[derive(BorshSerialize, BorshDeserialize, Debug, Clone, PartialEq)]
pub struct ExternalAccountId(String);

impl std::fmt::Display for ExternalAccountId {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "{}", self.0)
}
}

impl From<Uuid> for ExternalAccountId {
fn from(value: Uuid) -> Self {
let mut buf = [0u8; uuid::fmt::Simple::LENGTH];
Self(value.as_simple().encode_lower(&mut buf).to_owned())
}
}

impl ExternalAccountId {
pub fn gen() -> Self {
Uuid::new_v4().into()
}
}

#[derive(Debug, Serialize)]
#[serde(crate = "near_sdk::serde")]
pub struct SignedClaim {
pub claim_b64: String,
pub claim_sig: String,
}

#[derive(BorshSerialize, BorshDeserialize)]
#[cfg_attr(not(target_arch = "wasm32"), derive(Debug, PartialEq))]
pub struct Claim {
amityadav0 marked this conversation as resolved.
Show resolved Hide resolved
pub claimer: AccountId,
/// external, Ethereum compatible address. Must be a hex string, can start with "0x".
pub external_id: String,
/// unix time (seconds) when the claim was signed
pub timestamp: u64,
/// indicates whether the user has passed a KYC or not
pub verified_kyc: bool,
}
3 changes: 3 additions & 0 deletions contracts/test-util/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ use workspaces::{
Account, Contract, DevNetwork, Worker,
};

pub mod common;
pub mod utils;

/// Generate user sub-account
pub async fn gen_user_account<T>(worker: &Worker<T>, account_id: &str) -> anyhow::Result<Account>
where
Expand Down
65 changes: 65 additions & 0 deletions contracts/test-util/src/utils.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
use crate::common::{Claim, ExternalAccountId, SignedClaim};
use chrono::Utc;
use ed25519_dalek::Signer;
use near_crypto::{PublicKey, SecretKey, Signature};
use near_sdk::{borsh::BorshSerialize, test_utils::VMContextBuilder, AccountId, Balance, Gas};

pub const MAX_GAS: Gas = Gas(300_000_000_000_000);

pub fn build_default_context(
predecessor_account_id: AccountId,
deposit: Option<Balance>,
prepaid_gas: Option<Gas>,
) -> VMContextBuilder {
let mut builder = VMContextBuilder::new();
builder
.signer_account_id(predecessor_account_id.clone())
.predecessor_account_id(predecessor_account_id)
.prepaid_gas(prepaid_gas.unwrap_or(MAX_GAS))
.attached_deposit(deposit.unwrap_or_default());
builder
}

pub fn build_signed_claim(
sczembor marked this conversation as resolved.
Show resolved Hide resolved
claimer: AccountId,
external_id: ExternalAccountId,
verified_kyc: bool,
sec_key: &SecretKey,
) -> anyhow::Result<SignedClaim> {
let claim_raw = Claim {
claimer,
external_id: external_id.to_string(),
verified_kyc,
timestamp: Utc::now().timestamp() as u64,
}
.try_to_vec()?;

let sign = sign_bytes(&claim_raw, sec_key);

assert!(
Signature::ED25519(ed25519_dalek::Signature::from_bytes(&sign)?)
.verify(&claim_raw, &sec_key.public_key())
);

Ok(SignedClaim {
claim_b64: near_sdk::base64::encode(claim_raw),
claim_sig: near_sdk::base64::encode(sign),
})
}

pub fn generate_keys() -> (SecretKey, PublicKey) {
let seckey = SecretKey::from_random(near_crypto::KeyType::ED25519);
let pubkey = seckey.public_key();

(seckey, pubkey)
}

pub fn sign_bytes(bytes: &[u8], sec_key: &SecretKey) -> Vec<u8> {
match sec_key {
SecretKey::ED25519(secret_key) => {
let keypair = ed25519_dalek::Keypair::from_bytes(&secret_key.0).unwrap();
keypair.sign(bytes).to_bytes().to_vec()
}
_ => unimplemented!(),
}
}