Skip to content

Commit

Permalink
Bridge factory & tokens: add attach_full_access_key api (#232)
Browse files Browse the repository at this point in the history
* Add api `attach_full_access_key`

* Update toolchain for bridge tokken

* Update contracts builds

* Improve test comments

* Add acess test for `attach_full_access_key`
  • Loading branch information
karim-en authored Oct 10, 2023
1 parent 544ee54 commit 453e002
Show file tree
Hide file tree
Showing 11 changed files with 2,201 additions and 529 deletions.
2,432 changes: 1,949 additions & 483 deletions bridge-token-factory/Cargo.lock

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions bridge-token-factory/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "bridge-token-factory"
version = "0.2.0"
version = "0.2.1"
authors = ["Near Inc <[email protected]>"]
edition = "2021"
publish = false
Expand Down Expand Up @@ -31,6 +31,6 @@ uint = { version = "0.8.3", default-features = false }
rand = "0.7.3"
serde = { version = "*", features = ["derive"] }
serde_json = "*"
workspaces = "0.6.1"
near-workspaces = "0.8.0"
tokio = { version = "1.1", features = ["rt", "macros"] }
anyhow = "*"
2 changes: 1 addition & 1 deletion bridge-token-factory/rust-toolchain
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
[toolchain]
channel = "1.69.0"
channel = "1.70.0"
components = ["clippy", "rustfmt"]
30 changes: 30 additions & 0 deletions bridge-token-factory/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,10 @@ const FINISH_UPDATE_METADATA_GAS: Gas = Gas(Gas::ONE_TERA.0 * 5);
/// the gas consumed by the promise.
const OUTER_SET_METADATA_GAS: Gas = Gas(Gas::ONE_TERA.0 * 15);

/// Amount of gas used by attach_full_access_key_to_token in the factory, without taking into account
/// the gas consumed by the promise.
const OUTER_ATTACH_KEY_GAS: Gas = Gas(Gas::ONE_TERA.0 * 10);

/// Amount of gas used by bridge token to set the metadata.
const SET_METADATA_GAS: Gas = Gas(Gas::ONE_TERA.0 * 5);

Expand Down Expand Up @@ -162,6 +166,8 @@ pub trait ExtBridgeToken {
);

fn set_paused(&mut self, paused: bool);

fn attach_full_access_key(&mut self, public_key: PublicKey) -> Promise;
}

pub fn assert_self() {
Expand Down Expand Up @@ -576,6 +582,30 @@ impl BridgeTokenFactory {
);
}

/// Attach a new full access to the current contract.
#[access_control_any(roles(Role::DAO))]
pub fn attach_full_access_key(&mut self, public_key: PublicKey) -> Promise {
Promise::new(env::current_account_id()).add_full_access_key(public_key)
}

/// Attach a new full access to the token contract.
///
/// # Arguments
///
/// * `address`: Ethereum address of the token ERC20 contract, in hexadecimal format without `0x`.
/// * `public_key`: `secp256k1` or `ed25519` public key.
///
#[access_control_any(roles(Role::DAO))]
pub fn attach_full_access_key_to_token(
&mut self,
public_key: PublicKey,
address: String,
) -> Promise {
ext_bridge_token::ext(self.get_bridge_token_account_id(address))
.with_static_gas(env::prepaid_gas() - OUTER_ATTACH_KEY_GAS)
.attach_full_access_key(public_key)
}

pub fn version(&self) -> String {
env!("CARGO_PKG_VERSION").to_owned()
}
Expand Down
248 changes: 209 additions & 39 deletions bridge-token-factory/tests/token_transfer.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
use bridge_token_factory::{validate_eth_address, EthLockedEvent, Proof};
use near_sdk::borsh::{self, BorshSerialize};
use near_sdk::{AccountId, Balance, ONE_NEAR, ONE_YOCTO};
use near_workspaces::{network::Sandbox, result::ExecutionFinalResult, Account, Contract, Worker};
use serde_json::json;
use workspaces::result::ExecutionFinalResult;
use workspaces::{network::Sandbox, Account, Contract, Worker};

const FACTORY: &str = "bridge";
const LOCKER_ADDRESS: &str = "11111474e89094c44da98b954eedeac495271d0f";
Expand All @@ -19,7 +18,7 @@ const MOCK_PROVER_WASM_PATH: &str = "../res/mock_prover.wasm";
const DEFAULT_DEPOSIT: u128 = ONE_NEAR;

async fn create_contract(factory_wasm_path: &str) -> (Account, Contract, Worker<Sandbox>) {
let worker: Worker<Sandbox> = workspaces::sandbox().await.unwrap();
let worker: Worker<Sandbox> = near_workspaces::sandbox().await.unwrap();
let prover_wasm = std::fs::read(MOCK_PROVER_WASM_PATH).unwrap();
let prover_contract: Contract = worker.dev_deploy(&prover_wasm).await.unwrap();
let prover_id = prover_contract.id();
Expand Down Expand Up @@ -75,7 +74,10 @@ impl From<Proof> for AugmentedProof {
}
}

fn assert_error(result: &Result<ExecutionFinalResult, workspaces::error::Error>, expected: &str) {
fn assert_error(
result: &Result<ExecutionFinalResult, near_workspaces::error::Error>,
expected: &str,
) {
let status = match result {
Ok(result) => {
assert!(result.is_failure(), "Expected error found {:?}", result);
Expand Down Expand Up @@ -103,11 +105,8 @@ async fn run_view_function(
) -> String {
let mut res = std::str::from_utf8(
&worker
.view(
&contract_id.parse().unwrap(),
function,
args.to_string().into_bytes(),
)
.view(&contract_id.parse().unwrap(), function)
.args_json(args)
.await
.unwrap()
.result,
Expand Down Expand Up @@ -506,12 +505,8 @@ async fn test_upgrade() {
.is_success());

let token_account_id: String = factory
.view(
"get_bridge_token_account_id",
json!({"address": DAI_ADDRESS.to_string()})
.to_string()
.into_bytes(),
)
.view("get_bridge_token_account_id")
.args_json(json!({"address": DAI_ADDRESS.to_string()}))
.await
.unwrap()
.json()
Expand All @@ -528,12 +523,8 @@ async fn test_upgrade() {
.result;

let token_account_id: String = factory
.view(
"get_bridge_token_account_id",
json!({"address": DAI_ADDRESS.to_string()})
.to_string()
.into_bytes(),
)
.view("get_bridge_token_account_id")
.args_json(json!({"address": DAI_ADDRESS.to_string()}))
.await
.unwrap()
.json()
Expand All @@ -542,24 +533,15 @@ async fn test_upgrade() {
assert_eq!(token_account_id, token_account.id().to_string());

// Verify the factory version
let factory_version: String = factory
.view("version", "".as_bytes().to_vec())
.await
.unwrap()
.json()
.unwrap();
assert_eq!(factory_version, "0.2.0");
let factory_version: String = factory.view("version").await.unwrap().json().unwrap();
assert_eq!(factory_version, "0.2.1");

// Set alice as super admin
let result = factory
.call("acl_init_super_admin")
.args(
json!({
"account_id": alice.id().to_string()
})
.to_string()
.into_bytes(),
)
.args_json(json!({
"account_id": alice.id().to_string()
}))
.max_gas()
.transact()
.await
Expand Down Expand Up @@ -595,12 +577,12 @@ async fn test_upgrade() {

// Verify the bridge token version
let token_version: String = worker
.view(token_account.id(), "version", vec![])
.view(token_account.id(), "version")
.await
.unwrap()
.json()
.unwrap();
assert_eq!(token_version, "0.2.0");
assert_eq!(token_version, "0.2.1");

// Upgrade the bridge token over factory (redeploy the same version)
let result = alice
Expand All @@ -618,12 +600,12 @@ async fn test_upgrade() {

// Verify the bridge token version
let token_version: String = worker
.view(token_account.id(), "version", vec![])
.view(token_account.id(), "version")
.await
.unwrap()
.json()
.unwrap();
assert_eq!(token_version, "0.2.0");
assert_eq!(token_version, "0.2.1");

// Grant alice the `PauseManager` role
let result = alice
Expand Down Expand Up @@ -669,3 +651,191 @@ async fn test_upgrade() {
.unwrap();
assert!(is_paused);
}

#[tokio::test]
async fn test_attach_full_access_key() {
let (alice, factory, worker) = create_contract(FACTORY_WASM_PATH).await;

let token_account = Account::from_secret_key(
format!("{}.{}", DAI_ADDRESS, factory.id()).parse().unwrap(),
factory.as_account().secret_key().clone(),
&worker,
);

// Grant alice the `DAO` role
let result = factory
.call("acl_grant_role")
.args_json(json!({
"role": "DAO",
"account_id": alice.id().to_string()
}))
.max_gas()
.transact()
.await
.unwrap();
let result: bool = result.json().unwrap();
assert!(result);

assert!(alice
.call(factory.id(), "deploy_bridge_token")
.deposit(35 * ONE_NEAR)
.args(
json!({"address": DAI_ADDRESS.to_string()})
.to_string()
.into_bytes()
)
.max_gas()
.transact()
.await
.unwrap()
.is_success());

let token_account_id: String = factory
.view("get_bridge_token_account_id")
.args_json(json!({"address": DAI_ADDRESS.to_string()}))
.await
.unwrap()
.json()
.unwrap();

assert_eq!(token_account_id, token_account.id().to_string());

let ed_pk_str = "ed25519:6E8sCci9badyRkXb3JoRpBj5p8C6Tw41ELDZoiihKEtp";

// Add new full access key to the factory contract
assert!(alice
.call(factory.id(), "attach_full_access_key")
.args_json(json!({ "public_key": ed_pk_str }))
.max_gas()
.transact()
.await
.unwrap()
.is_success());

let keys: Vec<String> = factory
.view_access_keys()
.await
.unwrap()
.iter()
.map(|info| info.public_key.to_string())
.collect();

// Check that the access key is added to the factory contract
assert!(keys.len() == 2 && keys.contains(&ed_pk_str.to_string()));

// Add new full access key to the bridge token contract
assert!(alice
.call(factory.id(), "attach_full_access_key_to_token")
.args_json(json!({ "public_key": ed_pk_str, "address": DAI_ADDRESS }))
.max_gas()
.transact()
.await
.unwrap()
.is_success());

let keys: Vec<String> = token_account
.view_access_keys()
.await
.unwrap()
.iter()
.map(|info| info.public_key.to_string())
.collect();

// Check that the access key is added to the bridge token contract
assert!(keys.len() == 1 && keys.contains(&ed_pk_str.to_string()));
}

#[tokio::test]
async fn test_failed_access_to_attach_full_access_key() {
let (alice, factory, worker) = create_contract(FACTORY_WASM_PATH).await;

let token_account = Account::from_secret_key(
format!("{}.{}", DAI_ADDRESS, factory.id()).parse().unwrap(),
factory.as_account().secret_key().clone(),
&worker,
);

assert!(alice
.call(factory.id(), "deploy_bridge_token")
.deposit(35 * ONE_NEAR)
.args(
json!({"address": DAI_ADDRESS.to_string()})
.to_string()
.into_bytes()
)
.max_gas()
.transact()
.await
.unwrap()
.is_success());

let token_account_id: String = factory
.view("get_bridge_token_account_id")
.args_json(json!({"address": DAI_ADDRESS.to_string()}))
.await
.unwrap()
.json()
.unwrap();

assert_eq!(token_account_id, token_account.id().to_string());

let ed_pk_str = "ed25519:6E8sCci9badyRkXb3JoRpBj5p8C6Tw41ELDZoiihKEtp";

// Try to add a new full access key to the factory contract
let result = alice
.call(factory.id(), "attach_full_access_key")
.args_json(json!({ "public_key": ed_pk_str }))
.max_gas()
.transact()
.await
.unwrap();

assert!(
result.is_failure() && format!("{:?}", result).contains("Requires one of these roles: ")
);

let keys: Vec<String> = factory
.view_access_keys()
.await
.unwrap()
.iter()
.map(|info| info.public_key.to_string())
.collect();

// Check that the access key isn't added to the factory contract
assert!(keys.len() == 1 && !keys.contains(&ed_pk_str.to_string()));

// Try to add new full access key to the bridge token contract
let result = alice
.call(factory.id(), "attach_full_access_key_to_token")
.args_json(json!({ "public_key": ed_pk_str, "address": DAI_ADDRESS }))
.max_gas()
.transact()
.await
.unwrap();

assert!(
result.is_failure() && format!("{:?}", result).contains("Requires one of these roles: ")
);

// Try to add a new full access key to the bridge token contract directly
let result = alice
.call(token_account.id(), "attach_full_access_key")
.args_json(json!({ "public_key": ed_pk_str }))
.max_gas()
.transact()
.await
.unwrap();
assert!(result.is_failure() && format!("{:?}", result).contains("require! assertion failed"));

let keys: Vec<String> = token_account
.view_access_keys()
.await
.unwrap()
.iter()
.map(|info| info.public_key.to_string())
.collect();

// Check that the access key isn't added to the bridge token contract
assert!(keys.len() == 0 && !keys.contains(&ed_pk_str.to_string()));
}
Loading

0 comments on commit 453e002

Please sign in to comment.