diff --git a/bridge-token-factory/Cargo.lock b/bridge-token-factory/Cargo.lock index a9cf6290..e4ccf466 100644 --- a/bridge-token-factory/Cargo.lock +++ b/bridge-token-factory/Cargo.lock @@ -467,6 +467,7 @@ version = "0.2.2" dependencies = [ "near-contract-standards", "near-sdk", + "serde", ] [[package]] diff --git a/bridge-token-factory/src/lib.rs b/bridge-token-factory/src/lib.rs index e2c30150..f1565dcf 100644 --- a/bridge-token-factory/src/lib.rs +++ b/bridge-token-factory/src/lib.rs @@ -6,8 +6,9 @@ use near_sdk::borsh::{self, BorshDeserialize, BorshSerialize}; use near_sdk::collections::{UnorderedMap, UnorderedSet}; use near_sdk::json_types::{Base64VecU8, U128}; use near_sdk::serde::{Deserialize, Serialize}; +use near_sdk::serde_json::json; use near_sdk::{ - env, ext_contract, near_bindgen, AccountId, Balance, Gas, PanicOnDefault, Promise, + env, ext_contract, near_bindgen, require, AccountId, Balance, Gas, PanicOnDefault, Promise, PromiseOrValue, PublicKey, ONE_NEAR, }; @@ -54,9 +55,12 @@ const SET_METADATA_GAS: Gas = Gas(Gas::ONE_TERA.0 * 5); /// Amount of gas used by bridge token to pause withdraw. const SET_PAUSED_GAS: Gas = Gas(Gas::ONE_TERA.0 * 5); +// Amount of gas used by bridge token to set new controller +const SET_CONTROLLER_GAS: Gas = Gas(Gas::ONE_TERA.0 * 5); + /// Amount of gas used by `upgrade_bridge_token` in the factory, without taking into account /// the gas consumed by the promise. -const OUTER_UPGRADE_TOKEN_GAS: Gas = Gas(Gas::ONE_TERA.0 * 15); +const OUTER_UPGRADE_TOKEN_GAS: Gas = Gas(Gas::ONE_TERA.0 * 20); /// Controller storage key. const CONTROLLER_STORAGE_KEY: &[u8] = b"aCONTROLLER"; @@ -71,6 +75,14 @@ const TOKEN_TIMESTAMP_MAP_PREFIX: &[u8] = b"aTT"; pub type Mask = u128; +#[derive(Serialize, Deserialize)] +#[serde(crate = "near_sdk::serde")] +pub struct BasicMetadata { + pub name: String, + pub symbol: String, + pub decimals: u8, +} + #[derive(Debug, Eq, PartialEq, BorshSerialize, BorshDeserialize)] pub enum ResultType { Withdraw { @@ -93,6 +105,7 @@ pub enum Role { UpgradableCodeStager, UpgradableCodeDeployer, ForceWithdrawer, + Controller, } #[near_bindgen] @@ -155,7 +168,12 @@ pub trait ExtBridgeTokenFactory { #[ext_contract(ext_bridge_token)] pub trait ExtBridgeToken { - fn mint(&self, account_id: AccountId, amount: U128); + fn mint( + &self, + account_id: AccountId, + amount: U128, + msg: Option, + ) -> PromiseOrValue; fn ft_transfer_call( &mut self, @@ -178,6 +196,8 @@ pub trait ExtBridgeToken { fn set_paused(&mut self, paused: bool); fn attach_full_access_key(&mut self, public_key: PublicKey) -> Promise; + + fn set_controller(&mut self, controller: AccountId); } pub fn assert_self() { @@ -399,22 +419,10 @@ impl BridgeTokenFactory { target, message )); - match message { - Some(message) => ext_bridge_token::ext(self.get_bridge_token_account_id(token.clone())) - .with_static_gas(MINT_GAS) - .with_attached_deposit(env::attached_deposit() - required_deposit) - .mint(env::current_account_id(), amount.into()) - .then( - ext_bridge_token::ext(self.get_bridge_token_account_id(token)) - .with_static_gas(FT_TRANSFER_CALL_GAS) - .with_attached_deposit(1) - .ft_transfer_call(target, amount.into(), None, message), - ), - None => ext_bridge_token::ext(self.get_bridge_token_account_id(token)) - .with_static_gas(MINT_GAS) - .with_attached_deposit(env::attached_deposit() - required_deposit) - .mint(target, amount.into()), - } + ext_bridge_token::ext(self.get_bridge_token_account_id(token)) + .with_static_gas(MINT_GAS + FT_TRANSFER_CALL_GAS) + .with_attached_deposit(env::attached_deposit() - required_deposit) + .mint(target, amount.into(), message) } /// Burn given amount of tokens and unlock it on the Ethereum side for the recipient address. @@ -447,6 +455,17 @@ impl BridgeTokenFactory { } } + #[allow(unused_variables)] + #[result_serializer(borsh)] + pub fn finish_withdraw_v2( + &mut self, + #[serializer(borsh)] sender_id: AccountId, + #[serializer(borsh)] amount: Balance, + #[serializer(borsh)] recipient: String, + ) -> ResultType { + self.finish_withdraw(amount, recipient) + } + #[access_control_any(roles(Role::DAO, Role::ForceWithdrawer))] #[result_serializer(borsh)] pub fn force_withdraw(&self, token: String, amount: U128, recipient: String) -> ResultType { @@ -492,6 +511,28 @@ impl BridgeTokenFactory { ) } + #[payable] + #[access_control_any(roles(Role::Controller))] + pub fn deploy_token(&mut self, account_id: AccountId, metadata: &BasicMetadata) -> Promise { + require!( + env::attached_deposit() >= BRIDGE_TOKEN_INIT_BALANCE, + "ERR_NOT_ENOUGH_ATTACHED_BALANCE" + ); + + Promise::new(account_id) + .create_account() + .transfer(BRIDGE_TOKEN_INIT_BALANCE) + .deploy_contract(BRIDGE_TOKEN_BINARY.to_vec()) + .function_call( + "new".to_string(), + json!({"controller": env::predecessor_account_id(), "metadata": metadata}) + .to_string() + .into_bytes(), + NO_DEPOSIT, + BRIDGE_TOKEN_NEW, + ) + } + /// Upgrades and migrates the bridge token contract to the current version. /// /// # Arguments @@ -508,6 +549,23 @@ impl BridgeTokenFactory { ) } + /// Set new controller for the provided tokens + /// + /// # Arguments + /// + /// * `tokens_account_id`: A list of tokens that need their controller updated. + /// * `new_controller`: New controller for tokens + /// + #[access_control_any(roles(Role::Controller))] + pub fn set_controller_for_tokens(&self, tokens_account_id: Vec) { + let new_controller = env::predecessor_account_id(); + for token_account_id in tokens_account_id { + ext_bridge_token::ext(token_account_id) + .with_static_gas(SET_CONTROLLER_GAS) + .set_controller(new_controller.clone()); + } + } + /// Pause the withdraw method in the bridge token contract. /// /// # Arguments diff --git a/bridge-token/Cargo.lock b/bridge-token/Cargo.lock index 58ad99cc..fc1b1404 100644 --- a/bridge-token/Cargo.lock +++ b/bridge-token/Cargo.lock @@ -156,6 +156,7 @@ version = "0.2.2" dependencies = [ "near-contract-standards", "near-sdk", + "serde", ] [[package]] diff --git a/bridge-token/Cargo.toml b/bridge-token/Cargo.toml index dd1c25cf..c95190f7 100644 --- a/bridge-token/Cargo.toml +++ b/bridge-token/Cargo.toml @@ -20,3 +20,4 @@ overflow-checks = true [dependencies] near-sdk = "4.0.0" near-contract-standards = "4.0.0" +serde = { version = "*", features = ["derive"] } diff --git a/bridge-token/src/lib.rs b/bridge-token/src/lib.rs index d242bc5e..20ce042a 100644 --- a/bridge-token/src/lib.rs +++ b/bridge-token/src/lib.rs @@ -4,6 +4,7 @@ use near_contract_standards::fungible_token::metadata::{ use near_contract_standards::fungible_token::FungibleToken; use near_sdk::borsh::{self, BorshDeserialize, BorshSerialize}; use near_sdk::json_types::{Base64VecU8, U128}; +use near_sdk::serde::{Deserialize, Serialize}; use near_sdk::serde_json::json; use near_sdk::{ assert_one_yocto, env, ext_contract, near_bindgen, require, AccountId, Balance, Gas, @@ -14,10 +15,17 @@ use near_sdk::{ const FINISH_WITHDRAW_GAS: Gas = Gas(Gas::ONE_TERA.0 * 50); const OUTER_UPGRADE_GAS: Gas = Gas(Gas::ONE_TERA.0 * 15); const NO_DEPOSIT: Balance = 0; -const CURRENT_STATE_VERSION: u32 = 1; +const CURRENT_STATE_VERSION: u32 = 2; pub type Mask = u128; +#[derive(Serialize, Deserialize, Default)] +pub struct BasicMetadata { + pub name: String, + pub symbol: String, + pub decimals: u8, +} + #[near_bindgen] #[derive(BorshDeserialize, BorshSerialize, PanicOnDefault)] pub struct BridgeToken { @@ -40,12 +48,19 @@ pub trait ExtBridgeTokenFactory { #[serializer(borsh)] amount: Balance, #[serializer(borsh)] recipient: String, ) -> Promise; + + fn finish_withdraw_v2( + &self, + #[serializer(borsh)] sender_id: AccountId, + #[serializer(borsh)] amount: Balance, + #[serializer(borsh)] recipient: String, + ) -> Promise; } #[near_bindgen] impl BridgeToken { #[init] - pub fn new() -> Self { + pub fn new(controller: Option, metadata: Option) -> Self { let current_account_id = env::current_account_id(); let (_eth_address, factory_account) = current_account_id .as_str() @@ -57,14 +72,15 @@ impl BridgeToken { "Only the factory account can init this contract" ); + let m = metadata.unwrap_or_default(); Self { - controller: env::predecessor_account_id(), + controller: controller.unwrap_or(env::predecessor_account_id()), token: FungibleToken::new(b"t".to_vec()), - name: String::default(), - symbol: String::default(), + name: m.name, + symbol: m.symbol, reference: String::default(), reference_hash: Base64VecU8(vec![]), - decimals: 0, + decimals: m.decimals, paused: Mask::default(), icon: None, } @@ -91,7 +107,12 @@ impl BridgeToken { } #[payable] - pub fn mint(&mut self, account_id: AccountId, amount: U128) { + pub fn mint( + &mut self, + account_id: AccountId, + amount: U128, + msg: Option, + ) -> PromiseOrValue { assert_eq!( env::predecessor_account_id(), self.controller, @@ -99,7 +120,26 @@ impl BridgeToken { ); self.storage_deposit(Some(account_id.clone()), None); - self.token.internal_deposit(&account_id, amount.into()); + if let Some(msg) = msg { + self.token + .internal_deposit(&env::predecessor_account_id(), amount.into()); + + self.ft_transfer_call(account_id, amount, None, msg) + } else { + self.token.internal_deposit(&account_id, amount.into()); + PromiseOrValue::Value(amount) + } + } + + pub fn burn(&mut self, amount: U128) { + assert_eq!( + env::predecessor_account_id(), + self.controller, + "Only controller can call burn" + ); + + self.token + .internal_withdraw(&env::predecessor_account_id(), amount.into()); } #[payable] @@ -113,7 +153,12 @@ impl BridgeToken { ext_bridge_token_factory::ext(self.controller.clone()) .with_static_gas(FINISH_WITHDRAW_GAS) - .finish_withdraw(amount.into(), recipient) + .finish_withdraw_v2(env::predecessor_account_id(), amount.into(), recipient) + } + + pub fn set_controller(&mut self, controller: AccountId) { + require!(self.controller_or_self()); + self.controller = controller; } pub fn account_storage_usage(&self) -> StorageUsage { diff --git a/res/bridge_token.wasm b/res/bridge_token.wasm index ae5a1ab0..cd6e3729 100755 Binary files a/res/bridge_token.wasm and b/res/bridge_token.wasm differ diff --git a/res/bridge_token_factory.wasm b/res/bridge_token_factory.wasm index a593e00b..4d9cbc09 100755 Binary files a/res/bridge_token_factory.wasm and b/res/bridge_token_factory.wasm differ