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.

2 changes: 2 additions & 0 deletions contracts/oracle/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,5 @@ tokio.workspace = true
workspaces.workspace = true
near-units.workspace = true
tracing.workspace = true

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

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

use oracle_sbt::MINT_TOTAL_COST;
use sbt::ContractMetadata;
Expand Down Expand Up @@ -115,6 +123,124 @@ 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,
)?;

sbt_mint(
&user_account,
oracle_contract.id(),
json!(signed_claim),
MINT_TOTAL_COST,
"only root and implicit accounts are allowed to get SBT",
)
.await?;

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,
)?;

sbt_mint(
&user_account,
oracle_contract.id(),
json!(signed_claim),
0,
"Requires attached deposit at least 9000000000000000000000 yoctoNEAR",
)
.await?;

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

sbt_mint(
&user_account,
oracle_contract.id(),
json!(signed_claim),
0,
"Requires attached deposit at least 18000000000000000000000 yoctoNEAR",
)
.await?;

sbt_mint(
&user_account,
oracle_contract.id(),
json!({
"claim_b64": signed_claim.claim_b64,
"claim_sig": format!("a{}", &signed_claim.claim_sig),
}),
0,
"can't base64-decode claim_sig",
)
.await?;

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 res = user_account
.call(oracle_contract.id(), "sbt_mint")
.args_json(signed_claim)
.deposit(MINT_TOTAL_COST)
.max_gas()
.transact()
.await?;
assert!(res.is_success());

Ok(())
}

async fn check_arithmetic_exception(oracle: Contract, alice: Account) -> anyhow::Result<()> {
//
// replicating claim_sig_and_sbt_mint unit test
Expand Down Expand Up @@ -148,3 +274,32 @@ async fn check_arithmetic_exception(oracle: Contract, alice: Account) -> anyhow:

Ok(())
}

async fn sbt_mint(
sczembor marked this conversation as resolved.
Show resolved Hide resolved
caller: &Account,
oracle: &AccountId,
args: serde_json::Value,
deposit: Balance,
expected_err: &str,
) -> anyhow::Result<()> {
match caller
.call(oracle, "sbt_mint")
.args_json(args)
.deposit(deposit)
.max_gas()
.transact()
.await?
.into_result()
{
Ok(_) => {
panic!("Expected: {}, got: Ok()", expected_err)
sczembor marked this conversation as resolved.
Show resolved Hide resolved
}
Err(e) => {
let e_string = e.to_string();
if !e_string.contains(expected_err) {
panic!("Expected: {}, got: {}", expected_err, e)
}
}
};
Ok(())
}
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!(),
}
}