From 726c8cc8330544dfff497b5367e90edc5b5ec5ca Mon Sep 17 00:00:00 2001 From: Itshypen <075bct064.ranju@pcampus.edu.np> Date: Wed, 13 Nov 2024 21:21:23 +0545 Subject: [PATCH 1/2] fix: asset manager storage migration --- contracts/asset_manager/src/contract.rs | 130 ++--- contracts/asset_manager/src/lib.rs | 1 - contracts/asset_manager/src/states.rs | 132 +++-- contracts/asset_manager/src/storage_types.rs | 21 +- .../src/tests/asset_manager_test.rs | 21 +- contracts/asset_manager/src/tests/setup.rs | 12 +- contracts/asset_manager_int/Cargo.toml | 17 + .../src/config.rs | 0 contracts/asset_manager_int/src/contract.rs | 382 +++++++++++++ contracts/asset_manager_int/src/errors.rs | 22 + contracts/asset_manager_int/src/lib.rs | 9 + contracts/asset_manager_int/src/states.rs | 170 ++++++ .../asset_manager_int/src/storage_types.rs | 31 + .../src/tests/asset_manager_test.rs | 537 ++++++++++++++++++ contracts/asset_manager_int/src/tests/mod.rs | 2 + .../asset_manager_int/src/tests/setup.rs | 148 +++++ .../src/xcall_manager_interface.rs | 15 + 17 files changed, 1495 insertions(+), 155 deletions(-) create mode 100644 contracts/asset_manager_int/Cargo.toml rename contracts/{asset_manager => asset_manager_int}/src/config.rs (100%) create mode 100644 contracts/asset_manager_int/src/contract.rs create mode 100644 contracts/asset_manager_int/src/errors.rs create mode 100644 contracts/asset_manager_int/src/lib.rs create mode 100644 contracts/asset_manager_int/src/states.rs create mode 100644 contracts/asset_manager_int/src/storage_types.rs create mode 100644 contracts/asset_manager_int/src/tests/asset_manager_test.rs create mode 100644 contracts/asset_manager_int/src/tests/mod.rs create mode 100644 contracts/asset_manager_int/src/tests/setup.rs create mode 100644 contracts/asset_manager_int/src/xcall_manager_interface.rs diff --git a/contracts/asset_manager/src/contract.rs b/contracts/asset_manager/src/contract.rs index a144f2a..1f5e0b6 100644 --- a/contracts/asset_manager/src/contract.rs +++ b/contracts/asset_manager/src/contract.rs @@ -5,12 +5,10 @@ mod xcall { soroban_sdk::contractimport!(file = "../../wasm/xcall.wasm"); } use crate::errors::ContractError; -use crate::storage_types::TokenData; +use crate::states::{self}; use crate::{ - config::{self, get_config, set_config, ConfigData}, states::{ - extent_ttl, has_registry, read_administrator, read_token_data, read_tokens, - write_administrator, write_registry, write_token_data, write_tokens, + extent_ttl, has_upgrade_authority, read_administrator,write_administrator, }, storage_types::POINTS, xcall_manager_interface::XcallManagerClient, @@ -31,18 +29,27 @@ pub struct AssetManager; #[contractimpl] impl AssetManager { - pub fn initialize(env: Env, registry: Address, admin: Address, config: ConfigData) -> Result<(), ContractError> { - if has_registry(&env.clone()) { + pub fn initialize(env: Env, admin: Address, xcall: Address, xcall_manager: Address, native_address: Address, icon_asset_manager: String, upgrade_authority: Address) -> Result<(), ContractError> { + if has_upgrade_authority(&env.clone()) { return Err(ContractError::ContractAlreadyInitialized) } - write_registry(&env, ®istry); write_administrator(&env, &admin); - Self::configure(env, config); + states::write_xcall(&env, xcall); + states::write_xcall_manager(&env, xcall_manager); + states::write_icon_asset_manager(&env, icon_asset_manager); + states::write_upgrade_authority(&env, upgrade_authority); + states::write_native_address(&env, native_address); Ok(()) } - pub fn get_config(env: Env) -> ConfigData { - get_config(&env) + pub fn get_config(env: Env) -> (Address, Address, Address, String, Address) { + ( + states::read_xcall(&env), + states::read_xcall_manager(&env), + states::read_native_address(&env), + states::read_icon_asset_manager(&env), + states::read_upgrade_authority(&env), + ) } pub fn set_admin(env: Env, new_admin: Address) { @@ -56,13 +63,6 @@ impl AssetManager { read_administrator(&env) } - pub fn configure(env: Env, config: ConfigData) { - let admin = read_administrator(&env); - admin.require_auth(); - - set_config(&env, config); - } - pub fn configure_rate_limit( env: Env, token_address: Address, @@ -71,38 +71,22 @@ impl AssetManager { ) -> Result<(), ContractError> { let admin = read_administrator(&env); admin.require_auth(); - let tokens = read_tokens(&env); - if tokens.contains(&token_address) { - return Err(ContractError::TokenExists); - } else { - write_tokens(&env, token_address.clone()); - } - if percentage > POINTS as u32 { return Err(ContractError::PercentageShouldBeLessThanOrEqualToPOINTS); } - - write_token_data( - &env, - token_address, - TokenData { - period, - percentage, - last_update: env.ledger().timestamp(), - current_limit: 0, - }, - ); + states::write_period(&env, token_address.clone(), period); + states::write_percentage(&env, token_address.clone(), percentage); + states::write_last_update(&env, token_address.clone(), env.ledger().timestamp()); + states::write_current_limit(&env, token_address, 0); Ok(()) } pub fn get_rate_limit(env: Env, token_address: Address) -> Result<(u64, u32, u64, u64), ContractError> { - let data: TokenData = read_token_data(&env, token_address)?; - Ok(( - data.period, - data.percentage, - data.last_update, - data.current_limit, + states::read_period(&env, token_address.clone()), + states::read_percentage(&env, token_address.clone()), + states::read_last_update(&env, token_address.clone()), + states::read_current_limit(&env, token_address), )) } @@ -110,9 +94,9 @@ impl AssetManager { let admin = read_administrator(&env); admin.require_auth(); let balance = Self::get_token_balance(&env, token.clone()); - let mut data: TokenData = read_token_data(&env, token.clone())?; - data.current_limit = (balance * data.percentage as u128 / POINTS) as u64; - write_token_data(&env, token, data); + let percentage = states::read_percentage(&env, token.clone()); + let current_limit = (balance * percentage as u128 / POINTS) as u64; + states::write_current_limit(&env, token, current_limit); Ok(true) } @@ -132,10 +116,9 @@ impl AssetManager { if balance - amount < limit { return Err(ContractError::ExceedsWithdrawLimit); }; - let mut data: TokenData = read_token_data(&env, token.clone())?; - data.current_limit = limit as u64; - data.last_update = env.ledger().timestamp(); - write_token_data(&env, token, data); + + states::write_current_limit(&env, token.clone(), limit as u64); + states::write_last_update(&env, token.clone(), env.ledger().timestamp()); Ok(true) } @@ -144,9 +127,11 @@ impl AssetManager { balance: u128, token: Address, ) -> Result { - let data: TokenData = read_token_data(&env, token)?; - let period: u128 = data.period as u128; - let percentage: u128 = data.percentage as u128; + let period: u128 = states::read_period(&env, token.clone()) as u128; + let percentage: u128 = states::read_percentage(&env, token.clone()) as u128; + let last_update: u64 = states::read_last_update(&env, token.clone()); + let current_limit: u64 = states::read_current_limit(&env, token.clone()); + if period == 0 { return Ok(0); } @@ -154,11 +139,10 @@ impl AssetManager { let min_reserve = (balance * percentage) / POINTS; let max_withdraw = balance - min_reserve; - let last_update: u64 = data.last_update; let time_diff = &env.ledger().timestamp() - last_update; let allowed_withdrawal = (max_withdraw * time_diff as u128) / period; - let mut reserve: u128 = data.current_limit as u128; + let mut reserve: u128 = current_limit as u128; if reserve > allowed_withdrawal { reserve = reserve - allowed_withdrawal; @@ -223,12 +207,15 @@ impl AssetManager { data, ); + let xcall = states::read_xcall(&e); + let xcall_manager = states::read_xcall_manager(&e); + let icon_asset_manager = states::read_icon_asset_manager(&e); + let rollback: DepositRevert = DepositRevert::new(token, from.clone(), amount); - let config = get_config(&e); let rollback_bytes = rollback.encode(&e, String::from_str(&e, DEPOSIT_REVERT_NAME)); let message_bytes = xcall_message.encode(&e, String::from_str(&e, DEPOSIT_NAME)); let (sources, destinations) = - Self::xcall_manager(&e, &config.xcall_manager).get_protocols(); + Self::xcall_manager(&e, &xcall_manager).get_protocols(); let message = AnyMessage::CallMessageWithRollback(CallMessageWithRollback { data: message_bytes, rollback: rollback_bytes, @@ -239,11 +226,11 @@ impl AssetManager { sources, }; - Self::xcall_client(&e, &config.xcall).send_call( + Self::xcall_client(&e, &xcall).send_call( &from, ¤t_address, envelope, - &config.icon_asset_manager, + &icon_asset_manager, ); Ok(()) } @@ -263,13 +250,14 @@ impl AssetManager { data: Bytes, protocols: Vec, ) -> Result<(), ContractError> { - let config = get_config(&e); - let xcall = config.xcall; + let xcall = states::read_xcall(&e); + let xcall_manager = states::read_xcall_manager(&e); + let icon_asset_manager = states::read_icon_asset_manager(&e); + xcall.require_auth(); let method = Deposit::get_method(&e, data.clone()); - let icon_asset_manager = config.icon_asset_manager; let current_contract = e.current_contract_address(); if method == String::from_str(&e, &WITHDRAW_TO_NAME) { if from != icon_asset_manager { @@ -304,7 +292,7 @@ impl AssetManager { } else { return Err(ContractError::UnknownMessageType); } - if !Self::xcall_manager(&e, &config.xcall_manager).verify_protocols(&protocols) { + if !Self::xcall_manager(&e, &xcall_manager).verify_protocols(&protocols) { return Err(ContractError::ProtocolMismatch); } Ok(()) @@ -343,22 +331,20 @@ impl AssetManager { return token_client.balance(&e.current_contract_address()); } - pub fn has_registry(e: Env) -> bool { - has_registry(&e) + pub fn is_initialized(e: Env) -> bool { + states::has_upgrade_authority(&e) } - pub fn set_upgrade_authority(e: Env, upgrade_authority: Address) { - let mut config = config::get_config(&e); + pub fn set_upgrade_authority(e: Env, new_upgrade_authority: Address) { + let upgrade_authority = states::read_upgrade_authority(&e); + upgrade_authority.require_auth(); - config.upgrade_authority.require_auth(); - - config.upgrade_authority = upgrade_authority; - config::set_config(&e, config); + states::write_upgrade_authority(&e, new_upgrade_authority); } pub fn upgrade(e: Env, new_wasm_hash: BytesN<32>) { - let config = get_config(&e); - config.upgrade_authority.require_auth(); + let upgrade_authority = states::read_upgrade_authority(&e); + upgrade_authority.require_auth(); e.deployer().update_current_contract_wasm(new_wasm_hash); } @@ -366,6 +352,4 @@ impl AssetManager { pub fn extend_ttl(e: Env) { extent_ttl(&e); } - - } diff --git a/contracts/asset_manager/src/lib.rs b/contracts/asset_manager/src/lib.rs index b3ed8bd..73c66b4 100644 --- a/contracts/asset_manager/src/lib.rs +++ b/contracts/asset_manager/src/lib.rs @@ -4,6 +4,5 @@ pub mod contract; pub mod storage_types; pub mod tests; pub mod states; -mod config; mod errors; mod xcall_manager_interface; \ No newline at end of file diff --git a/contracts/asset_manager/src/states.rs b/contracts/asset_manager/src/states.rs index 0b387cf..9db042f 100644 --- a/contracts/asset_manager/src/states.rs +++ b/contracts/asset_manager/src/states.rs @@ -1,6 +1,6 @@ -use soroban_sdk::{Address, Env, Vec}; +use soroban_sdk::{Address, Env, String}; -use crate::{errors::ContractError, storage_types::{DataKey, TokenData}}; +use crate::storage_types::DataKey; pub(crate) const DAY_IN_LEDGERS: u32 = 17280; pub(crate) const INSTANCE_BUMP_AMOUNT: u32 = 30 * DAY_IN_LEDGERS; @@ -21,75 +21,103 @@ pub fn write_administrator(e: &Env, id: &Address) { e.storage().instance().set(&key, id); } -pub fn has_registry(e: &Env) -> bool { - let key = DataKey::Registry; +pub fn has_upgrade_authority(e: &Env) -> bool { + let key = DataKey::UpgradeAuthority; e.storage().instance().has(&key) } -pub fn read_registry(e: &Env) -> Address { - let key = DataKey::Registry; - e.storage().instance().get(&key).unwrap() +pub fn write_xcall(env: &Env, xcall: Address) { + let key = DataKey::XCall; + env.storage().instance().set(&key, &xcall); } -pub fn write_registry(e: &Env, id: &Address) { - let key = DataKey::Registry; - e.storage().instance().set(&key, id); +pub fn write_xcall_manager(env: &Env, xcall_manager: Address) { + let key = DataKey::XcallManager; + env.storage().instance().set(&key, &xcall_manager); } -pub fn write_token_data(env: &Env, token_address: Address, data: TokenData) { - let key = DataKey::TokenData(token_address); - env.storage().persistent().set(&key, &data); +pub fn write_native_address(env: &Env, native_address: Address) { + let key = DataKey::NativeAddress; + env.storage().instance().set(&key, &native_address); } -pub fn read_token_data(env: &Env, token_address: Address) -> Result { - let key = DataKey::TokenData(token_address); - let token_data: TokenData = env - .storage() - .persistent() - .get(&key) - .ok_or(ContractError::TokenDoesNotExists)?; - Ok(token_data) +pub fn write_icon_asset_manager(env: &Env, icon_asset_manager: String) { + let key = DataKey::IconAssetManager; + env.storage().instance().set(&key, &icon_asset_manager); } -pub fn write_tokens(e: &Env, token: Address) { - let key = DataKey::Tokens; - let mut tokens: Vec
= match e.storage().persistent().get(&key) { - Some(names) => names, - None => Vec::new(&e), - }; +pub fn write_upgrade_authority(env: &Env, upgrade_authority: Address) { + let key = DataKey::UpgradeAuthority; + env.storage().instance().set(&key, &upgrade_authority); +} - tokens.push_back(token); - e.storage().persistent().set(&key, &tokens); +pub fn read_upgrade_authority(env: &Env) -> Address { + let key = DataKey::UpgradeAuthority; + env.storage().instance().get(&key).unwrap() } -pub fn read_tokens(e: &Env) -> Vec
{ - let key = DataKey::Tokens; - let tokens: Vec
= match e.storage().persistent().get(&key) { - Some(names) => names, - None => Vec::new(&e), - }; +pub fn read_native_address(env: &Env) -> Address { + let key = DataKey::NativeAddress; + env.storage().instance().get(&key).unwrap() +} - tokens +pub fn read_icon_asset_manager(env: &Env) -> String { + let key = DataKey::IconAssetManager; + env.storage().instance().get(&key).unwrap() +} + +pub fn read_xcall_manager(env: &Env) -> Address { + let key = DataKey::XcallManager; + env.storage().instance().get(&key).unwrap() +} + +pub fn read_xcall(env: &Env) -> Address { + let key = DataKey::XCall; + env.storage().instance().get(&key).unwrap() +} + +pub fn write_period(env: &Env, token_address: Address, period: u64) { + let key = DataKey::Period(token_address); + env.storage().instance().set(&key, &period); +} + +pub fn read_period(env: &Env, token_address: Address) -> u64 { + let key = DataKey::Period(token_address); + env.storage().instance().get(&key).unwrap() +} + +pub fn write_percentage(env: &Env, token_address: Address, percentage: u32) { + let key = DataKey::Percentage(token_address); + env.storage().instance().set(&key, &percentage); +} + +pub fn read_percentage(env: &Env, token_address: Address) -> u32 { + let key = DataKey::Percentage(token_address); + env.storage().instance().get(&key).unwrap() +} + +pub fn write_last_update(env: &Env, token_address: Address, last_update: u64) { + let key = DataKey::LastUpdate(token_address); + env.storage().instance().set(&key, &last_update); +} + +pub fn read_last_update(env: &Env, token_address: Address) -> u64 { + let key = DataKey::LastUpdate(token_address); + env.storage().instance().get(&key).unwrap() +} + +pub fn write_current_limit(env: &Env, token_address: Address, current_limit: u64) { + let key = DataKey::CurrentLimit(token_address); + env.storage().instance().set(&key, ¤t_limit); +} + +pub fn read_current_limit(env: &Env, token_address: Address) -> u64 { + let key = DataKey::CurrentLimit(token_address); + env.storage().instance().get(&key).unwrap() } pub fn extent_ttl(e: &Env) { e.storage() .instance() .extend_ttl(INSTANCE_LIFETIME_THRESHOLD, INSTANCE_BUMP_AMOUNT); - - let tokens = read_tokens(&e); - e.storage().persistent().extend_ttl( - &DataKey::Tokens, - INSTANCE_LIFETIME_THRESHOLD, - INSTANCE_BUMP_AMOUNT, - ); - for token in tokens { - - e.storage().persistent().extend_ttl( - &DataKey::TokenData(token.clone()), - INSTANCE_LIFETIME_THRESHOLD, - INSTANCE_BUMP_AMOUNT, - ); - - } } diff --git a/contracts/asset_manager/src/storage_types.rs b/contracts/asset_manager/src/storage_types.rs index 2e1997b..7080536 100644 --- a/contracts/asset_manager/src/storage_types.rs +++ b/contracts/asset_manager/src/storage_types.rs @@ -5,18 +5,15 @@ pub(crate) const POINTS: u128 = 10000; #[derive(Clone)] #[contracttype] pub enum DataKey { - Registry, Admin, - Config, - Tokens, - TokenData(Address) + XCall, + XcallManager, + NativeAddress, + IconAssetManager, + UpgradeAuthority, + Period(Address), + Percentage(Address), + LastUpdate(Address), + CurrentLimit(Address), } -#[derive(Clone)] -#[contracttype] -pub struct TokenData { - pub period: u64, - pub percentage: u32, - pub last_update: u64, - pub current_limit: u64, -} diff --git a/contracts/asset_manager/src/tests/asset_manager_test.rs b/contracts/asset_manager/src/tests/asset_manager_test.rs index 29b2d5c..50e50e0 100644 --- a/contracts/asset_manager/src/tests/asset_manager_test.rs +++ b/contracts/asset_manager/src/tests/asset_manager_test.rs @@ -1,9 +1,9 @@ #![cfg(test)] extern crate std; -use crate::{config, contract::AssetManagerClient, storage_types::DataKey}; +use crate::{contract::AssetManagerClient, states}; use soroban_sdk::{ - testutils::{storage::Persistent, Address as _, AuthorizedFunction, AuthorizedInvocation}, + testutils::{Address as _, AuthorizedFunction, AuthorizedInvocation}, token, Address, Bytes, IntoVal, String, Symbol, Vec, }; @@ -17,8 +17,9 @@ fn test_initialize() { let client = AssetManagerClient::new(&ctx.env, &ctx.registry); ctx.init_context(&client); - let registry_exists = client.has_registry(); - assert_eq!(registry_exists, true) + + let initialized = client.is_initialized(); + assert_eq!(initialized, true) } #[test] @@ -469,15 +470,7 @@ fn test_extend_ttl() { ctx.init_context(&client); client.configure_rate_limit(&ctx.token, &300, &300); - let token = ctx.token; - client.extend_ttl(); - - ctx.env.as_contract(&client.address, || { - let key = DataKey::TokenData(token.clone()); - let before_ttl = ctx.env.storage().persistent().get_ttl(&key); - std::println!("before ttl is: {:?}", before_ttl); - }); } #[test] @@ -530,7 +523,7 @@ fn test_set_upgrade_authority() { ); ctx.env.as_contract(&client.address, || { - let config = config::get_config(&ctx.env); - assert_eq!(config.upgrade_authority, new_upgrade_authority) + let upgrade_authority = states::read_upgrade_authority(&ctx.env); + assert_eq!(upgrade_authority, new_upgrade_authority) }); } diff --git a/contracts/asset_manager/src/tests/setup.rs b/contracts/asset_manager/src/tests/setup.rs index 09b8bef..f289493 100644 --- a/contracts/asset_manager/src/tests/setup.rs +++ b/contracts/asset_manager/src/tests/setup.rs @@ -3,8 +3,6 @@ extern crate std; use crate::contract::{AssetManager, AssetManagerClient}; -use crate::config::ConfigData; - use soroban_sdk::Vec; use soroban_sdk::{testutils::Address as _, token, Address, Env, String}; @@ -42,6 +40,14 @@ pub struct TestContext { pub xcall_client: xcall::Client<'static>, } +pub struct ConfigData { + pub xcall: Address, + pub xcall_manager: Address, + pub native_address: Address, + pub icon_asset_manager: String, + pub upgrade_authority: Address, +} + impl TestContext { pub fn default() -> Self { let env = Env::default(); @@ -84,7 +90,7 @@ impl TestContext { icon_asset_manager: self.icon_asset_manager.clone(), upgrade_authority: self.upgrade_authority.clone(), }; - client.initialize(&self.registry, &self.admin, &config); + client.initialize( &self.admin, &config.xcall, &config.xcall_manager, &config.native_address, &config.icon_asset_manager, &config.upgrade_authority); } pub fn init_xcall_manager_context(&self) { diff --git a/contracts/asset_manager_int/Cargo.toml b/contracts/asset_manager_int/Cargo.toml new file mode 100644 index 0000000..08d6e88 --- /dev/null +++ b/contracts/asset_manager_int/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "asset-manager-int" +version = "0.1.0" +edition = "2021" + +[lib] +crate-type = ["cdylib", "rlib"] + +[features] +testutils = ["soroban-sdk/testutils"] + +[dependencies] +soroban-sdk = { workspace = true } +soroban-rlp = { path = "../../libs/soroban-rlp" } + +[dev-dependencies] +soroban-sdk = { workspace = true, features = ["testutils"] } \ No newline at end of file diff --git a/contracts/asset_manager/src/config.rs b/contracts/asset_manager_int/src/config.rs similarity index 100% rename from contracts/asset_manager/src/config.rs rename to contracts/asset_manager_int/src/config.rs diff --git a/contracts/asset_manager_int/src/contract.rs b/contracts/asset_manager_int/src/contract.rs new file mode 100644 index 0000000..457cbbd --- /dev/null +++ b/contracts/asset_manager_int/src/contract.rs @@ -0,0 +1,382 @@ +use soroban_sdk::{ + contract, contractimpl, token, Address, Bytes, BytesN, Env, String, Vec, +}; +mod xcall { + soroban_sdk::contractimport!(file = "../../wasm/xcall.wasm"); +} +use crate::errors::ContractError; +use crate::states::{self, write_xcall}; +use crate::storage_types::{DataKey, TokenData}; +use crate::{ + config::{self, get_config, set_config, ConfigData}, + states::{ + extent_ttl, has_upgrade_authority, read_administrator, read_token_data, read_tokens, + write_administrator, + }, + storage_types::POINTS, + xcall_manager_interface::XcallManagerClient, +}; +use soroban_rlp::balanced::address_utils::is_valid_string_address; +use soroban_rlp::balanced::messages::{ + deposit::Deposit, deposit_revert::DepositRevert, withdraw_to::WithdrawTo, +}; + +use xcall::{AnyMessage, CallMessageWithRollback, Client, Envelope}; + +const DEPOSIT_NAME: &str = "Deposit"; +const WITHDRAW_TO_NAME: &str = "WithdrawTo"; +const DEPOSIT_REVERT_NAME: &str = "DepositRevert"; + +#[contract] +pub struct AssetManager; + +#[contractimpl] +impl AssetManager { + pub fn initialize(env: Env, admin: Address, xcall: Address, xcall_manager: Address, native_address: Address, icon_asset_manager: String, upgrade_authority: Address) -> Result<(), ContractError> { + if has_upgrade_authority(&env.clone()) { + return Err(ContractError::ContractAlreadyInitialized) + } + write_administrator(&env, &admin); + states::write_xcall(&env, xcall); + states::write_xcall_manager(&env, xcall_manager); + states::write_icon_asset_manager(&env, icon_asset_manager); + states::write_upgrade_authority(&env, upgrade_authority); + states::write_native_address(&env, native_address); + Ok(()) + } + + pub fn get_config(env: Env) -> ConfigData { + get_config(&env) + } + + pub fn set_admin(env: Env, new_admin: Address) { + let admin = read_administrator(&env); + admin.require_auth(); + + write_administrator(&env, &new_admin); + } + + pub fn get_admin(env: Env) -> Address { + read_administrator(&env) + } + + pub fn configure_rate_limit( + env: Env, + token_address: Address, + period: u64, + percentage: u32, + ) -> Result<(), ContractError> { + let admin = read_administrator(&env); + admin.require_auth(); + if percentage > POINTS as u32 { + return Err(ContractError::PercentageShouldBeLessThanOrEqualToPOINTS); + } + + states::write_period(&env, token_address.clone(), period); + states::write_percentage(&env, token_address.clone(), percentage); + states::write_last_update(&env, token_address.clone(), env.ledger().timestamp()); + states::write_current_limit(&env, token_address, 0); + Ok(()) + } + + pub fn get_rate_limit(env: Env, token_address: Address) -> Result<(u64, u32, u64, u64), ContractError> { + Ok(( + states::read_period(&env, token_address.clone()), + states::read_percentage(&env, token_address.clone()), + states::read_last_update(&env, token_address.clone()), + states::read_current_limit(&env, token_address), + )) + } + + pub fn reset_limit(env: Env, token: Address) -> Result { + let admin = read_administrator(&env); + admin.require_auth(); + let balance = Self::get_token_balance(&env, token.clone()); + let percentage = states::read_percentage(&env, token.clone()); + let current_limit = (balance * percentage as u128 / POINTS) as u64; + states::write_current_limit(&env, token, current_limit); + Ok(true) + } + + pub fn get_withdraw_limit(env: Env, token: Address) -> Result { + let balance = Self::get_token_balance(&env, token.clone()); + return Ok(Self::calculate_limit(&env, balance, token)?); + } + + fn get_token_balance(env: &Env, token: Address) -> u128 { + let token_client = token::Client::new(env, &token); + return token_client.balance(&env.current_contract_address()) as u128; + } + + fn verify_withdraw(env: Env, token: Address, amount: u128) -> Result { + let balance = Self::get_token_balance(&env, token.clone()); + let limit = Self::calculate_limit(&env, balance, token.clone())?; + if balance - amount < limit { + return Err(ContractError::ExceedsWithdrawLimit); + }; + + states::write_current_limit(&env, token.clone(), limit as u64); + states::write_last_update(&env, token.clone(), env.ledger().timestamp()); + Ok(true) + } + + pub fn calculate_limit( + env: &Env, + balance: u128, + token: Address, + ) -> Result { + let period: u128 = states::read_period(&env, token.clone()) as u128; + let percentage: u128 = states::read_percentage(&env, token.clone()) as u128; + let last_update: u64 = states::read_last_update(&env, token.clone()); + let current_limit: u64 = states::read_current_limit(&env, token.clone()); + + if period == 0 { + return Ok(0); + } + + let min_reserve = (balance * percentage) / POINTS; + + let max_withdraw = balance - min_reserve; + let time_diff = &env.ledger().timestamp() - last_update; + + let allowed_withdrawal = (max_withdraw * time_diff as u128) / period; + let mut reserve: u128 = current_limit as u128; + + if reserve > allowed_withdrawal { + reserve = reserve - allowed_withdrawal; + } + + let reserve = if reserve > min_reserve { + reserve + } else { + min_reserve + }; + Ok(reserve) + } + + pub fn deposit( + e: Env, + from: Address, + token: Address, + amount: u128, + to: Option, + data: Option, + ) -> Result<(), ContractError> { + if amount <= 0{ + return Err(ContractError::AmountIsLessThanMinimumAmount); + } + + let deposit_to = to.unwrap_or(String::from_str(&e, "")); + let deposit_data = data.unwrap_or(Bytes::from_array(&e, &[0u8; 32])); + + Ok(Self::send_deposit_message( + e, + from, + token, + amount, + deposit_to, + deposit_data, + )?) + } + + fn send_deposit_message( + e: Env, + from: Address, + token: Address, + amount: u128, + to: String, + data: Bytes, + ) -> Result<(), ContractError> { + from.require_auth(); + let current_address = e.current_contract_address(); + Self::transfer_token_to( + &e, + from.clone(), + token.clone(), + current_address.clone(), + amount, + )?; + + let xcall_message: Deposit = Deposit::new( + token.to_string(), + from.to_string(), + to.clone(), + amount, + data, + ); + + let xcall = states::read_xcall(&e); + let xcall_manager = states::read_xcall_manager(&e); + let icon_asset_manager = states::read_icon_asset_manager(&e); + + let rollback: DepositRevert = DepositRevert::new(token, from.clone(), amount); + let rollback_bytes = rollback.encode(&e, String::from_str(&e, DEPOSIT_REVERT_NAME)); + let message_bytes = xcall_message.encode(&e, String::from_str(&e, DEPOSIT_NAME)); + let (sources, destinations) = + Self::xcall_manager(&e, &xcall_manager).get_protocols(); + let message = AnyMessage::CallMessageWithRollback(CallMessageWithRollback { + data: message_bytes, + rollback: rollback_bytes, + }); + let envelope: &Envelope = &Envelope { + destinations, + message, + sources, + }; + + Self::xcall_client(&e, &xcall).send_call( + &from, + ¤t_address, + envelope, + &icon_asset_manager, + ); + Ok(()) + } + + fn xcall_manager(e: &Env, xcall_manager: &Address) -> XcallManagerClient<'static> { + let client = XcallManagerClient::new(e, xcall_manager); + return client; + } + + fn xcall_client(e: &Env, xcall: &Address) -> Client<'static> { + return xcall::Client::new(e, xcall); + } + + pub fn handle_call_message( + e: Env, + from: String, + data: Bytes, + protocols: Vec, + ) -> Result<(), ContractError> { + let xcall = states::read_xcall(&e); + let xcall_manager = states::read_xcall_manager(&e); + let icon_asset_manager = states::read_icon_asset_manager(&e); + + xcall.require_auth(); + + let method = Deposit::get_method(&e, data.clone()); + + let current_contract = e.current_contract_address(); + if method == String::from_str(&e, &WITHDRAW_TO_NAME) { + if from != icon_asset_manager { + return Err(ContractError::OnlyICONAssetManager); + } + let message = WithdrawTo::decode(&e, data); + if !is_valid_string_address(&message.to) + || !is_valid_string_address(&message.token_address) + { + return Err(ContractError::InvalidAddress); + } + Self::withdraw( + &e, + current_contract, + Address::from_string(&message.token_address), + Address::from_string(&message.to), + message.amount, + )?; + } else if method == String::from_str(&e, &DEPOSIT_REVERT_NAME) { + let xcall_network_address = Self::xcall_client(&e, &xcall).get_network_address(); + if xcall_network_address != from { + return Err(ContractError::OnlyCallService); + } + let message: DepositRevert = DepositRevert::decode(&e.clone(), data); + Self::withdraw( + &e, + current_contract, + message.token_address, + message.to, + message.amount, + )?; + } else { + return Err(ContractError::UnknownMessageType); + } + if !Self::xcall_manager(&e, &xcall_manager).verify_protocols(&protocols) { + return Err(ContractError::ProtocolMismatch); + } + Ok(()) + } + + fn withdraw( + e: &Env, + from: Address, + token: Address, + to: Address, + amount: u128, + ) -> Result<(), ContractError> { + if amount <= 0 { + return Err(ContractError::AmountIsLessThanMinimumAmount); + } + + let verified = Self::verify_withdraw(e.clone(), token.clone(), amount)?; + if verified { + Self::transfer_token_to(e, from, token, to, amount)?; + } + Ok(()) + } + + fn transfer_token_to(e: &Env, from: Address, token: Address, to: Address, amount: u128) -> Result<(), ContractError> { + let token_client = token::Client::new(e, &token); + if amount <= i128::MAX as u128 { + token_client.transfer(&from, &to, &(amount as i128)); + } else { + return Err(ContractError::InvalidAmount) + } + Ok(()) + } + + pub fn balance_of(e: Env, token: Address) -> i128 { + let token_client = token::Client::new(&e, &token); + return token_client.balance(&e.current_contract_address()); + } + + pub fn is_initialized(e: Env) -> bool { + states::has_upgrade_authority(&e) + } + + pub fn set_upgrade_authority(e: Env, new_upgrade_authority: Address) { + let upgrade_authority = states::read_upgrade_authority(&e); + upgrade_authority.require_auth(); + + states::write_upgrade_authority(&e, new_upgrade_authority); + } + + pub fn migrate_db(e: Env) { + + let config = config::get_config(&e); + config.upgrade_authority.require_auth(); + + states::write_xcall(&e, config.xcall); + states::write_xcall_manager(&e, config.xcall_manager); + states::write_icon_asset_manager(&e, config.icon_asset_manager); + states::write_upgrade_authority(&e, config.upgrade_authority); + states::write_native_address(&e, config.native_address); + + let tokens = states::read_tokens(&e); + for token in tokens.clone() { + let token_data = states::read_token_data(&e, token.clone()).unwrap(); + states::write_period(&e, token.clone(), token_data.period); + states::write_percentage(&e, token.clone(), token_data.percentage); + states::write_last_update(&e, token.clone(), token_data.last_update); + states::write_current_limit(&e, token.clone(), token_data.current_limit); + } + + e.storage().instance().remove(&DataKey::Tokens); + for token in tokens { + e.storage().instance().remove(&DataKey::TokenData(token)); + } + e.storage().instance().remove(&DataKey::Config); + e.storage().instance().remove(&DataKey::Registry); + + } + + pub fn upgrade(e: Env, new_wasm_hash: BytesN<32>) { + let upgrade_authority = states::read_upgrade_authority(&e); + upgrade_authority.require_auth(); + + e.deployer().update_current_contract_wasm(new_wasm_hash); + } + + pub fn extend_ttl(e: Env) { + extent_ttl(&e); + } +} diff --git a/contracts/asset_manager_int/src/errors.rs b/contracts/asset_manager_int/src/errors.rs new file mode 100644 index 0000000..534fa4e --- /dev/null +++ b/contracts/asset_manager_int/src/errors.rs @@ -0,0 +1,22 @@ +use soroban_sdk::contracterror; + +#[contracterror] +#[repr(u32)] +#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)] +pub enum ContractError { + InvalidRlpLength = 1, + InvalidRollbackMessage = 2, + ContractAlreadyInitialized = 3, + PercentageShouldBeLessThanOrEqualToPOINTS = 4, + ExceedsWithdrawLimit = 5, + AmountIsLessThanMinimumAmount = 6, + ProtocolMismatch = 7, + OnlyICONAssetManager = 8, + OnlyCallService = 9, + UnknownMessageType = 10, + AdminRequired = 11, + TokenExists = 12, + InvalidAddress = 13, + TokenDoesNotExists = 14, + InvalidAmount = 15 +} diff --git a/contracts/asset_manager_int/src/lib.rs b/contracts/asset_manager_int/src/lib.rs new file mode 100644 index 0000000..b3ed8bd --- /dev/null +++ b/contracts/asset_manager_int/src/lib.rs @@ -0,0 +1,9 @@ +#![no_std] + +pub mod contract; +pub mod storage_types; +pub mod tests; +pub mod states; +mod config; +mod errors; +mod xcall_manager_interface; \ No newline at end of file diff --git a/contracts/asset_manager_int/src/states.rs b/contracts/asset_manager_int/src/states.rs new file mode 100644 index 0000000..3261ee3 --- /dev/null +++ b/contracts/asset_manager_int/src/states.rs @@ -0,0 +1,170 @@ +use soroban_sdk::{Address, Env, String, Vec}; + +use crate::{errors::ContractError, storage_types::{DataKey, TokenData}}; + +pub(crate) const DAY_IN_LEDGERS: u32 = 17280; +pub(crate) const INSTANCE_BUMP_AMOUNT: u32 = 30 * DAY_IN_LEDGERS; +pub(crate) const INSTANCE_LIFETIME_THRESHOLD: u32 = INSTANCE_BUMP_AMOUNT - DAY_IN_LEDGERS; + +pub fn has_administrator(e: &Env) -> bool { + let key: DataKey = DataKey::Admin; + e.storage().instance().has(&key) +} + +pub fn read_administrator(e: &Env) -> Address { + let key = DataKey::Admin; + e.storage().instance().get(&key).unwrap() +} + +pub fn write_administrator(e: &Env, id: &Address) { + let key = DataKey::Admin; + e.storage().instance().set(&key, id); +} + +pub fn has_upgrade_authority(e: &Env) -> bool { + let key = DataKey::UpgradeAuthority; + e.storage().instance().has(&key) +} + +pub fn write_xcall(env: &Env, xcall: Address) { + let key = DataKey::XCall; + env.storage().instance().set(&key, &xcall); +} + +pub fn write_xcall_manager(env: &Env, xcall_manager: Address) { + let key = DataKey::XcallManager; + env.storage().instance().set(&key, &xcall_manager); +} + +pub fn write_native_address(env: &Env, native_address: Address) { + let key = DataKey::NativeAddress; + env.storage().instance().set(&key, &native_address); +} + +pub fn write_icon_asset_manager(env: &Env, icon_asset_manager: String) { + let key = DataKey::IconAssetManager; + env.storage().instance().set(&key, &icon_asset_manager); +} + +pub fn write_upgrade_authority(env: &Env, upgrade_authority: Address) { + let key = DataKey::UpgradeAuthority; + env.storage().instance().set(&key, &upgrade_authority); +} + +pub fn read_upgrade_authority(env: &Env) -> Address { + let key = DataKey::UpgradeAuthority; + env.storage().instance().get(&key).unwrap() +} + +pub fn read_native_address(env: &Env) -> Address { + let key = DataKey::NativeAddress; + env.storage().instance().get(&key).unwrap() +} + +pub fn read_icon_asset_manager(env: &Env) -> String { + let key = DataKey::IconAssetManager; + env.storage().instance().get(&key).unwrap() +} + +pub fn read_xcall_manager(env: &Env) -> Address { + let key = DataKey::XcallManager; + env.storage().instance().get(&key).unwrap() +} + +pub fn read_xcall(env: &Env) -> Address { + let key = DataKey::XCall; + env.storage().instance().get(&key).unwrap() +} + +pub fn write_period(env: &Env, token_address: Address, period: u64) { + let key = DataKey::Period(token_address); + env.storage().instance().set(&key, &period); +} + +pub fn read_period(env: &Env, token_address: Address) -> u64 { + let key = DataKey::Period(token_address); + env.storage().instance().get(&key).unwrap() +} + +pub fn write_percentage(env: &Env, token_address: Address, percentage: u32) { + let key = DataKey::Percentage(token_address); + env.storage().instance().set(&key, &percentage); +} + +pub fn read_percentage(env: &Env, token_address: Address) -> u32 { + let key = DataKey::Percentage(token_address); + env.storage().instance().get(&key).unwrap() +} + +pub fn write_last_update(env: &Env, token_address: Address, last_update: u64) { + let key = DataKey::LastUpdate(token_address); + env.storage().instance().set(&key, &last_update); +} + +pub fn read_last_update(env: &Env, token_address: Address) -> u64 { + let key = DataKey::LastUpdate(token_address); + env.storage().instance().get(&key).unwrap() +} + +pub fn write_current_limit(env: &Env, token_address: Address, current_limit: u64) { + let key = DataKey::CurrentLimit(token_address); + env.storage().instance().set(&key, ¤t_limit); +} + +pub fn read_current_limit(env: &Env, token_address: Address) -> u64 { + let key = DataKey::CurrentLimit(token_address); + env.storage().instance().get(&key).unwrap() +} + +pub fn read_token_data(env: &Env, token_address: Address) -> Result { + let key = DataKey::TokenData(token_address); + let token_data: TokenData = env + .storage() + .persistent() + .get(&key) + .ok_or(ContractError::TokenDoesNotExists)?; + Ok(token_data) +} + +pub fn write_tokens(e: &Env, token: Address) { + let key = DataKey::Tokens; + let mut tokens: Vec
= match e.storage().persistent().get(&key) { + Some(names) => names, + None => Vec::new(&e), + }; + + tokens.push_back(token); + e.storage().persistent().set(&key, &tokens); +} + +pub fn read_tokens(e: &Env) -> Vec
{ + let key = DataKey::Tokens; + let tokens: Vec
= match e.storage().persistent().get(&key) { + Some(names) => names, + None => Vec::new(&e), + }; + + tokens +} + +pub fn extent_ttl(e: &Env) { + e.storage() + .instance() + .extend_ttl(INSTANCE_LIFETIME_THRESHOLD, INSTANCE_BUMP_AMOUNT); + + let tokens = read_tokens(&e); + e.storage().persistent().extend_ttl( + &DataKey::Tokens, + INSTANCE_LIFETIME_THRESHOLD, + INSTANCE_BUMP_AMOUNT, + ); + for token in tokens { + + e.storage().persistent().extend_ttl( + &DataKey::TokenData(token.clone()), + INSTANCE_LIFETIME_THRESHOLD, + INSTANCE_BUMP_AMOUNT, + ); + + } +} diff --git a/contracts/asset_manager_int/src/storage_types.rs b/contracts/asset_manager_int/src/storage_types.rs new file mode 100644 index 0000000..598a748 --- /dev/null +++ b/contracts/asset_manager_int/src/storage_types.rs @@ -0,0 +1,31 @@ +use soroban_sdk::{contracttype, Address}; + +pub(crate) const POINTS: u128 = 10000; + +#[derive(Clone)] +#[contracttype] +pub enum DataKey { + Registry, + Admin, + Config, + XCall, + XcallManager, + NativeAddress, + IconAssetManager, + UpgradeAuthority, + Tokens, + TokenData(Address), + Period(Address), + Percentage(Address), + LastUpdate(Address), + CurrentLimit(Address), +} + +#[derive(Clone)] +#[contracttype] +pub struct TokenData { + pub period: u64, + pub percentage: u32, + pub last_update: u64, + pub current_limit: u64, +} diff --git a/contracts/asset_manager_int/src/tests/asset_manager_test.rs b/contracts/asset_manager_int/src/tests/asset_manager_test.rs new file mode 100644 index 0000000..0c114eb --- /dev/null +++ b/contracts/asset_manager_int/src/tests/asset_manager_test.rs @@ -0,0 +1,537 @@ +#![cfg(test)] +extern crate std; + +use crate::{config, contract::AssetManagerClient, storage_types::DataKey}; +use soroban_sdk::{ + testutils::{storage::Persistent, Address as _, AuthorizedFunction, AuthorizedInvocation}, + token, Address, Bytes, IntoVal, String, Symbol, Vec, +}; + +use soroban_rlp::balanced::messages::{deposit_revert::DepositRevert, withdraw_to::WithdrawTo}; + +use super::setup::*; + +#[test] +fn test_initialize() { + let ctx = TestContext::default(); + let client = AssetManagerClient::new(&ctx.env, &ctx.registry); + + ctx.init_context(&client); + + let initialized = client.is_initialized(); + assert_eq!(initialized, true) +} + +#[test] +fn test_set_admin() { + let ctx = TestContext::default(); + let client = AssetManagerClient::new(&ctx.env, &ctx.registry); + ctx.init_context(&client); + + let new_admin: Address = Address::generate(&ctx.env); + client.set_admin(&new_admin); + + assert_eq!( + ctx.env.auths(), + std::vec![( + ctx.admin.clone(), + AuthorizedInvocation { + function: AuthorizedFunction::Contract(( + ctx.registry.clone(), + Symbol::new(&ctx.env, "set_admin"), + (&new_admin,).into_val(&ctx.env) + )), + sub_invocations: std::vec![] + } + )] + ); + assert_eq!(client.get_admin(), new_admin); +} + +#[test] +#[should_panic(expected = "HostError: Error(Contract, #4)")] +fn test_configure_rate_limit_panic() { + let ctx = TestContext::default(); + let client = AssetManagerClient::new(&ctx.env, &ctx.registry); + ctx.init_context(&client); + let period = &300; + let percentage = &10001; + client.configure_rate_limit(&ctx.token, period, percentage); +} + +#[test] +fn test_configure_rate_limit() { + let ctx = TestContext::default(); + let client = AssetManagerClient::new(&ctx.env, &ctx.registry); + ctx.init_context(&client); + let period = &300; + let percentage = &300; + client.configure_rate_limit(&ctx.token, period, percentage); + assert_eq!( + ctx.env.auths(), + std::vec![( + ctx.admin.clone(), + AuthorizedInvocation { + function: AuthorizedFunction::Contract(( + ctx.registry.clone(), + Symbol::new(&ctx.env, "configure_rate_limit"), + (&ctx.token, 300u64, 300u32).into_val(&ctx.env) + )), + sub_invocations: std::vec![] + } + )] + ); + let token_data = client.get_rate_limit(&ctx.token); + assert_eq!(token_data.3, 0); +} + +#[test] +fn test_deposit_without_to_and_data() { + let ctx = TestContext::default(); + let client = AssetManagerClient::new(&ctx.env, &ctx.registry); + ctx.init_context(&client); + + client.configure_rate_limit(&ctx.token, &300, &300); + let token_client = token::Client::new(&ctx.env, &ctx.token); + let stellar_asset_client: token::StellarAssetClient = + token::StellarAssetClient::new(&ctx.env, &ctx.token); + let amount_i128: i128 = 100000i128; + let amount = &(amount_i128 as u128); + let mint_amount = &(amount_i128 + amount_i128); + + stellar_asset_client.mint(&ctx.depositor, mint_amount); + + ctx.mint_native_token(&ctx.depositor, 500); + assert_eq!(ctx.get_native_token_balance(&ctx.depositor), 500); + + token_client.approve( + &ctx.depositor, + &ctx.registry, + &(amount_i128 + amount_i128), + &1312000, + ); + client.deposit( + &ctx.depositor, + &ctx.token, + &amount, + &Option::Some(String::from_str(&ctx.env, "")), + &Option::Some(Bytes::from_array(&ctx.env, &[0u8; 32])), + ); + + assert_eq!(ctx.get_native_token_balance(&ctx.depositor), 400); // why 300? +} + +#[test] +fn test_veryfy_rate_limit() { + let ctx = TestContext::default(); + let client = AssetManagerClient::new(&ctx.env, &ctx.registry); + ctx.init_context(&client); + let period = &300; + let percentage = &300; + client.configure_rate_limit(&ctx.token, period, percentage); + + let stellar_asset_client: token::StellarAssetClient = + token::StellarAssetClient::new(&ctx.env, &ctx.token); + let amount_i128: i128 = 100000i128; + let amount = &(amount_i128 as u128); + let mint_amount: &i128 = &(amount_i128 + amount_i128); + + stellar_asset_client.mint(&ctx.depositor, mint_amount); + + ctx.mint_native_token(&ctx.depositor, 500u128); + assert_eq!(ctx.get_native_token_balance(&ctx.depositor), 500u128); + + client.deposit( + &ctx.depositor, + &ctx.token, + &amount, + &Option::Some(String::from_str(&ctx.env, "")), + &Option::Some(Bytes::from_array(&ctx.env, &[0u8; 32])), + ); + + let limit = client.get_withdraw_limit(&ctx.token); + assert_eq!(limit, 3000); +} + +#[test] +fn test_deposit_with_to_and_without_data() { + let ctx = TestContext::default(); + let client = AssetManagerClient::new(&ctx.env, &ctx.registry); + ctx.init_context(&client); + + client.configure_rate_limit(&ctx.token, &300, &300); + let token_client = token::Client::new(&ctx.env, &ctx.token); + let stellar_asset_client: token::StellarAssetClient = + token::StellarAssetClient::new(&ctx.env, &ctx.token); + let amount_i128: i128 = 100000i128; + let amount = &(amount_i128 as u128); + let mint_amount = &(amount_i128 + amount_i128); + + stellar_asset_client.mint(&ctx.depositor, mint_amount); + + ctx.mint_native_token(&ctx.depositor, 500); + assert_eq!(ctx.get_native_token_balance(&ctx.depositor), 500); + + token_client.approve( + &ctx.depositor, + &ctx.registry, + &(amount_i128 + amount_i128), + &1312000, + ); + client.deposit( + &ctx.depositor, + &ctx.token, + &amount, + &Option::Some(String::from_str(&ctx.env, "icon01/hxjkdvhui")), + &Option::Some(Bytes::from_array(&ctx.env, &[0u8; 32])), + ); + + assert_eq!(ctx.get_native_token_balance(&ctx.depositor), 400) // why 300? +} + +#[test] +fn test_deposit_with_to_and_data() { + let ctx = TestContext::default(); + let client = AssetManagerClient::new(&ctx.env, &ctx.registry); + ctx.init_context(&client); + + client.configure_rate_limit(&ctx.token, &300, &300); + let token_client = token::Client::new(&ctx.env, &ctx.token); + let stellar_asset_client: token::StellarAssetClient = + token::StellarAssetClient::new(&ctx.env, &ctx.token); + let amount_i128: i128 = 100000i128; + let amount = &(amount_i128 as u128); + let mint_amount = &(amount_i128 + amount_i128); + + stellar_asset_client.mint(&ctx.depositor, mint_amount); + + ctx.mint_native_token(&ctx.depositor, 500); + assert_eq!(ctx.get_native_token_balance(&ctx.depositor), 500); + + token_client.approve( + &ctx.depositor, + &ctx.registry, + &(amount_i128 + amount_i128), + &1312000, + ); + + let data: [u8; 32] = [ + 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, + 0x1F, 0x20, + ]; + client.deposit( + &ctx.depositor, + &ctx.token, + &amount, + &Option::Some(String::from_str(&ctx.env, "icon01/hxjkdvhui")), + &Option::Some(Bytes::from_array(&ctx.env, &data)), + ); + assert_eq!(ctx.get_native_token_balance(&ctx.depositor), 400); // why 300? +} + +#[test] +fn test_handle_call_message_for_withdraw_to() { + let ctx = TestContext::default(); + let client = AssetManagerClient::new(&ctx.env, &ctx.registry); + ctx.env.mock_all_auths(); + + ctx.init_context(&client); + + let bnusd_amount = 100000u128; + let token_client = token::Client::new(&ctx.env, &ctx.token); + let stellar_asset_client: token::StellarAssetClient = + token::StellarAssetClient::new(&ctx.env, &ctx.token); + stellar_asset_client.mint(&ctx.registry, &((bnusd_amount * 2) as i128)); + client.configure_rate_limit(&ctx.token, &300, &300); + let data = WithdrawTo::new( + ctx.token.to_string(), + ctx.withdrawer.to_string(), + bnusd_amount, + ) + .encode(&ctx.env, String::from_str(&ctx.env, "WithdrawTo")); + let decoded = WithdrawTo::decode(&ctx.env, data.clone()); + assert_eq!(decoded.to, ctx.withdrawer.to_string()); + + assert_eq!(token_client.balance(&ctx.withdrawer), 0); + + let sources = Vec::from_array(&ctx.env, [ctx.centralized_connection.to_string()]); + client.handle_call_message(&ctx.icon_asset_manager, &data, &sources); + + assert_eq!(token_client.balance(&ctx.withdrawer), bnusd_amount as i128) +} + +#[test] +#[should_panic(expected = "HostError: Error(Contract, #13)")] +fn test_handle_call_message_for_withdraw_to_invalid_address() { + let ctx = TestContext::default(); + let client = AssetManagerClient::new(&ctx.env, &ctx.registry); + ctx.env.mock_all_auths(); + + ctx.init_context(&client); + client.configure_rate_limit(&ctx.token, &300, &300); + + let bnusd_amount = 100000u128; + let token_client = token::Client::new(&ctx.env, &ctx.token); + let stellar_asset_client: token::StellarAssetClient = + token::StellarAssetClient::new(&ctx.env, &ctx.token); + stellar_asset_client.mint(&ctx.registry, &((bnusd_amount * 2) as i128)); + + let data = WithdrawTo::new( + ctx.token.to_string(), + String::from_str(&ctx.env, "InvalidAddress"), + bnusd_amount, + ) + .encode(&ctx.env, String::from_str(&ctx.env, "WithdrawTo")); + let decoded = WithdrawTo::decode(&ctx.env, data.clone()); + assert_eq!(decoded.to, String::from_str(&ctx.env, "InvalidAddress")); + + assert_eq!(token_client.balance(&ctx.withdrawer), 0); + + let sources = Vec::from_array(&ctx.env, [ctx.centralized_connection.to_string()]); + client.handle_call_message(&ctx.icon_asset_manager, &data, &sources); + + assert_eq!(token_client.balance(&ctx.withdrawer), bnusd_amount as i128) +} + +#[test] +#[should_panic(expected = "HostError: Error(Contract, #7)")] +fn test_handle_call_message_for_withdraw_to_panic_with_protocal_mismatch() { + let ctx = TestContext::default(); + let client = AssetManagerClient::new(&ctx.env, &ctx.registry); + ctx.env.mock_all_auths(); + + ctx.init_context(&client); + client.configure_rate_limit(&ctx.token, &300, &300); + + let bnusd_amount = 100000u128; + let token_client = token::Client::new(&ctx.env, &ctx.token); + let stellar_asset_client: token::StellarAssetClient = + token::StellarAssetClient::new(&ctx.env, &ctx.token); + stellar_asset_client.mint(&ctx.registry, &((bnusd_amount * 2) as i128)); + + let data = WithdrawTo::new( + ctx.token.to_string(), + ctx.withdrawer.to_string(), + bnusd_amount, + ) + .encode(&ctx.env, String::from_str(&ctx.env, "WithdrawTo")); + let decoded = WithdrawTo::decode(&ctx.env, data.clone()); + assert_eq!(decoded.to, ctx.withdrawer.to_string()); + + assert_eq!(token_client.balance(&ctx.withdrawer), 0); + + let sources = Vec::from_array(&ctx.env, [ctx.xcall.to_string()]); + client.handle_call_message(&ctx.icon_asset_manager, &data, &sources); + + assert_eq!(token_client.balance(&ctx.withdrawer), bnusd_amount as i128) +} + +#[test] +#[should_panic(expected = "HostError: Error(Contract, #8)")] +fn test_handle_call_message_for_withdraw_to_panic_with_not_icon_asset_manager() { + let ctx = TestContext::default(); + let client = AssetManagerClient::new(&ctx.env, &ctx.registry); + ctx.env.mock_all_auths(); + + ctx.init_context(&client); + client.configure_rate_limit(&ctx.token, &300, &300); + + let bnusd_amount = 100000u128; + let token_client = token::Client::new(&ctx.env, &ctx.token); + let stellar_asset_client: token::StellarAssetClient = + token::StellarAssetClient::new(&ctx.env, &ctx.token); + stellar_asset_client.mint(&ctx.registry, &((bnusd_amount * 2) as i128)); + + let data = WithdrawTo::new( + ctx.token.to_string(), + ctx.withdrawer.to_string(), + bnusd_amount, + ) + .encode(&ctx.env, String::from_str(&ctx.env, "WithdrawTo")); + let decoded = WithdrawTo::decode(&ctx.env, data.clone()); + assert_eq!(decoded.to, ctx.withdrawer.to_string()); + + assert_eq!(token_client.balance(&ctx.withdrawer), 0); + + let sources = Vec::from_array(&ctx.env, [ctx.centralized_connection.to_string()]); + client.handle_call_message(&ctx.centralized_connection.to_string(), &data, &sources); + + assert_eq!(token_client.balance(&ctx.withdrawer), bnusd_amount as i128) +} + +#[test] +#[should_panic(expected = "HostError: Error(Contract, #10)")] +fn test_handle_call_message_for_withdraw_to_panic_with_unknown_message_type() { + let ctx = TestContext::default(); + let client = AssetManagerClient::new(&ctx.env, &ctx.registry); + ctx.env.mock_all_auths(); + + ctx.init_context(&client); + client.configure_rate_limit(&ctx.token, &300, &300); + + let bnusd_amount = 100000u128; + let token_client = token::Client::new(&ctx.env, &ctx.token); + let stellar_asset_client: token::StellarAssetClient = + token::StellarAssetClient::new(&ctx.env, &ctx.token); + stellar_asset_client.mint(&ctx.registry, &((bnusd_amount * 2) as i128)); + + let data = WithdrawTo::new( + ctx.token.to_string(), + ctx.withdrawer.to_string(), + bnusd_amount, + ) + .encode(&ctx.env, String::from_str(&ctx.env, "WithdrawToUnknown")); + let decoded = WithdrawTo::decode(&ctx.env, data.clone()); + assert_eq!(decoded.to, ctx.withdrawer.to_string()); + + assert_eq!(token_client.balance(&ctx.withdrawer), 0); + + let sources = Vec::from_array(&ctx.env, [ctx.centralized_connection.to_string()]); + client.handle_call_message(&ctx.icon_asset_manager, &data, &sources); + + assert_eq!(token_client.balance(&ctx.withdrawer), bnusd_amount as i128) +} + +#[test] +fn test_handle_call_message_for_deposit_rollback() { + let ctx = TestContext::default(); + let client = AssetManagerClient::new(&ctx.env, &ctx.registry); + ctx.env.mock_all_auths(); + + ctx.init_context(&client); + client.configure_rate_limit(&ctx.token, &300, &300); + + let bnusd_amount = 100000u128; + let token_client = token::Client::new(&ctx.env, &ctx.token); + let stellar_asset_client: token::StellarAssetClient = + token::StellarAssetClient::new(&ctx.env, &ctx.token); + stellar_asset_client.mint(&ctx.registry, &((bnusd_amount * 2) as i128)); + + let data = DepositRevert::new(ctx.token, ctx.withdrawer.clone(), bnusd_amount) + .encode(&ctx.env, String::from_str(&ctx.env, "DepositRevert")); + let decoded = DepositRevert::decode(&ctx.env, data.clone()); + assert_eq!(decoded.to, ctx.withdrawer); + + assert_eq!(token_client.balance(&ctx.withdrawer), 0); + + let sources = Vec::from_array(&ctx.env, [ctx.centralized_connection.to_string()]); + client.handle_call_message(&ctx.xcall_client.get_network_address(), &data, &sources); + + assert_eq!(token_client.balance(&ctx.withdrawer), bnusd_amount as i128) +} + +#[test] +#[should_panic(expected = "HostError: Error(Contract, #9)")] +fn test_handle_call_message_for_deposit_rollback_panic_with_only_call_service() { + let ctx = TestContext::default(); + let client = AssetManagerClient::new(&ctx.env, &ctx.registry); + ctx.env.mock_all_auths(); + + ctx.init_context(&client); + client.configure_rate_limit(&ctx.token, &300, &300); + + let bnusd_amount = 100000u128; + let token_client = token::Client::new(&ctx.env, &ctx.token); + let stellar_asset_client: token::StellarAssetClient = + token::StellarAssetClient::new(&ctx.env, &ctx.token); + stellar_asset_client.mint(&ctx.registry, &((bnusd_amount * 2) as i128)); + + let data = DepositRevert::new(ctx.token, ctx.withdrawer.clone(), bnusd_amount) + .encode(&ctx.env, String::from_str(&ctx.env, "DepositRevert")); + let decoded = DepositRevert::decode(&ctx.env, data.clone()); + assert_eq!(decoded.to, ctx.withdrawer); + + assert_eq!(token_client.balance(&ctx.withdrawer), 0); + + let sources = Vec::from_array(&ctx.env, [ctx.centralized_connection.to_string()]); + let wrong_network_address: String = String::from_str( + &ctx.env, + &std::format!( + "{}/{}", + "soroban", + "CBEPDNVYXQGWB5YUBXKJWYJA7OXTZW5LFLNO5JRRGE6Z6C5OSUZPCCEL" + ), + ); + + std::println!( + "{}", + std::string::ToString::to_string(&wrong_network_address) + ); + client.handle_call_message(&wrong_network_address, &data, &sources); + + assert_eq!(token_client.balance(&ctx.withdrawer), bnusd_amount as i128) +} + +#[test] +fn test_extend_ttl() { + let ctx = TestContext::default(); + let client = AssetManagerClient::new(&ctx.env, &ctx.registry); + ctx.init_context(&client); + + client.configure_rate_limit(&ctx.token, &300, &300); + let token = ctx.token; + + client.extend_ttl(); + + ctx.env.as_contract(&client.address, || { + let key = DataKey::TokenData(token.clone()); + let before_ttl = ctx.env.storage().persistent().get_ttl(&key); + std::println!("before ttl is: {:?}", before_ttl); + }); +} + +#[test] +fn test_reset_limit() { + let ctx = TestContext::default(); + let client = AssetManagerClient::new(&ctx.env, &ctx.registry); + ctx.init_context(&client); + + client.configure_rate_limit(&ctx.token, &300, &300); + client.reset_limit(&ctx.token); + + assert_eq!( + ctx.env.auths(), + std::vec![( + ctx.admin.clone(), + AuthorizedInvocation { + function: AuthorizedFunction::Contract(( + ctx.registry.clone(), + Symbol::new(&ctx.env, "reset_limit"), + (&ctx.token,).into_val(&ctx.env) + )), + sub_invocations: std::vec![] + } + )] + ); +} + +#[test] +fn test_set_upgrade_authority() { + let ctx = TestContext::default(); + let client = AssetManagerClient::new(&ctx.env, &ctx.registry); + ctx.init_context(&client); + + let new_upgrade_authority = Address::generate(&ctx.env); + client.set_upgrade_authority(&new_upgrade_authority); + + assert_eq!( + ctx.env.auths(), + std::vec![( + ctx.upgrade_authority.clone(), + AuthorizedInvocation { + function: AuthorizedFunction::Contract(( + ctx.registry.clone(), + Symbol::new(&ctx.env, "set_upgrade_authority"), + (&new_upgrade_authority,).into_val(&ctx.env) + )), + sub_invocations: std::vec![] + } + )] + ); + + ctx.env.as_contract(&client.address, || { + let config = config::get_config(&ctx.env); + assert_eq!(config.upgrade_authority, new_upgrade_authority) + }); +} diff --git a/contracts/asset_manager_int/src/tests/mod.rs b/contracts/asset_manager_int/src/tests/mod.rs new file mode 100644 index 0000000..06a7192 --- /dev/null +++ b/contracts/asset_manager_int/src/tests/mod.rs @@ -0,0 +1,2 @@ +pub mod setup; +pub mod asset_manager_test; \ No newline at end of file diff --git a/contracts/asset_manager_int/src/tests/setup.rs b/contracts/asset_manager_int/src/tests/setup.rs new file mode 100644 index 0000000..301f3b3 --- /dev/null +++ b/contracts/asset_manager_int/src/tests/setup.rs @@ -0,0 +1,148 @@ +#![cfg(test)] +extern crate std; + +use crate::contract::{AssetManager, AssetManagerClient}; + +use crate::config::ConfigData; + +use soroban_sdk::Vec; +use soroban_sdk::{testutils::Address as _, token, Address, Env, String}; + +mod xcall { + soroban_sdk::contractimport!(file = "../../wasm/xcall.wasm"); +} + +mod connection { + soroban_sdk::contractimport!(file = "../../wasm/centralized_connection.wasm"); +} + +mod xcall_manager { + soroban_sdk::contractimport!( + file = "../../target/wasm32-unknown-unknown/release/xcall_manager.wasm" + ); +} + +use xcall_manager::ConfigData as XcallManagerConfigData; + +pub struct TestContext { + pub env: Env, + pub registry: Address, + pub admin: Address, + pub depositor: Address, + pub withdrawer: Address, + pub upgrade_authority: Address, + pub xcall: Address, + pub xcall_manager: Address, + pub icon_asset_manager: String, + pub icon_governance: String, + pub token: Address, + pub centralized_connection: Address, + pub nid: String, + pub native_token: Address, + pub xcall_client: xcall::Client<'static>, +} + +impl TestContext { + pub fn default() -> Self { + let env = Env::default(); + let token_admin = Address::generate(&env); + let token = env.register_stellar_asset_contract_v2(token_admin.clone()); + let asset_manager = env.register_contract(None, AssetManager); + let centralized_connection = env.register_contract_wasm(None, connection::WASM); + let xcall_manager = env.register_contract_wasm(None, xcall_manager::WASM); + let xcall = env.register_contract_wasm(None, xcall::WASM); + + Self { + registry: asset_manager, + admin: Address::generate(&env), + depositor: Address::generate(&env), + withdrawer: Address::generate(&env), + upgrade_authority: Address::generate(&env), + xcall: xcall.clone(), + xcall_manager: xcall_manager, + icon_asset_manager: String::from_str(&env, "icon01/hxjnfh4u"), + icon_governance: String::from_str(&env, "icon01/kjdnoi"), + token: token.address(), + centralized_connection: centralized_connection, + nid: String::from_str(&env, "stellar"), + native_token: env + .register_stellar_asset_contract_v2(token_admin.clone()) + .address(), + xcall_client: xcall::Client::new(&env, &xcall), + env, + } + } + + pub fn init_context(&self, client: &AssetManagerClient<'static>) { + self.env.mock_all_auths(); + self.init_xcall_manager_context(); + self.init_xcall_state(); + let config = ConfigData { + xcall: self.xcall.clone(), + xcall_manager: self.xcall_manager.clone(), + native_address: self.native_token.clone(), + icon_asset_manager: self.icon_asset_manager.clone(), + upgrade_authority: self.upgrade_authority.clone(), + }; + client.initialize( &self.admin, &config.xcall, &config.xcall_manager, &config.native_address, &config.icon_asset_manager, &config.upgrade_authority); + } + + pub fn init_xcall_manager_context(&self) { + let client = self::xcall_manager::Client::new(&self.env, &self.xcall_manager); + let config = XcallManagerConfigData { + xcall: self.xcall.clone(), + icon_governance: self.icon_governance.clone(), + upgrade_authority: self.upgrade_authority.clone(), + }; + let sources = Vec::from_array(&self.env, [self.centralized_connection.to_string()]); + let destinations = + Vec::from_array(&self.env, [String::from_str(&self.env, "icon/address")]); + client.initialize( + &self.xcall_manager, + &self.admin, + &config, + &sources, + &destinations, + ); + } + + pub fn init_xcall_state(&self) { + self.xcall_client.initialize(&xcall::InitializeMsg { + sender: self.admin.clone(), + network_id: self.nid.clone(), + native_token: self.native_token.clone(), + }); + + self.init_connection_state(); + self.xcall_client.set_protocol_fee(&100); + self.xcall_client + .set_default_connection(&self.nid, &self.centralized_connection); + } + + pub fn init_connection_state(&self) { + let connection_client = connection::Client::new(&self.env, &self.centralized_connection); + + let initialize_msg = connection::InitializeMsg { + native_token: self.native_token.clone(), + relayer: self.admin.clone(), + xcall_address: self.xcall.clone(), + }; + connection_client.initialize(&initialize_msg); + + let message_fee = 100; + let response_fee = 100; + connection_client.set_fee(&self.nid, &message_fee, &response_fee); + } + + pub fn mint_native_token(&self, address: &Address, amount: u128) { + let native_token_client = token::StellarAssetClient::new(&self.env, &self.native_token); + native_token_client.mint(&address, &(*&amount as i128)); + } + + pub fn get_native_token_balance(&self, address: &Address) -> u128 { + let native_token_client = token::TokenClient::new(&self.env, &self.native_token); + let balance = native_token_client.balance(address); + + *&balance as u128 + } +} diff --git a/contracts/asset_manager_int/src/xcall_manager_interface.rs b/contracts/asset_manager_int/src/xcall_manager_interface.rs new file mode 100644 index 0000000..17c7698 --- /dev/null +++ b/contracts/asset_manager_int/src/xcall_manager_interface.rs @@ -0,0 +1,15 @@ +use soroban_sdk::{contractclient, Env, String, Vec}; + +use crate::errors::ContractError; + +#[contractclient(name = "XcallManagerClient")] +pub trait XcallManagerInterface { + + fn verify_protocols( + e: Env, + protocols: Vec + ) -> Result; + + fn get_protocols(e: Env) -> Result<(Vec, Vec), ContractError>; + +} \ No newline at end of file From cb532a151ab8de84578e311f90511377a1290e4d Mon Sep 17 00:00:00 2001 From: Itshypen <075bct064.ranju@pcampus.edu.np> Date: Wed, 13 Nov 2024 23:26:53 +0545 Subject: [PATCH 2/2] fix: return type defined --- contracts/asset_manager/src/contract.rs | 31 ++++++++++--------- contracts/asset_manager/src/storage_types.rs | 19 +++++++++++- .../src/tests/asset_manager_test.rs | 12 +------ 3 files changed, 35 insertions(+), 27 deletions(-) diff --git a/contracts/asset_manager/src/contract.rs b/contracts/asset_manager/src/contract.rs index 1f5e0b6..ac3ad5a 100644 --- a/contracts/asset_manager/src/contract.rs +++ b/contracts/asset_manager/src/contract.rs @@ -6,6 +6,7 @@ mod xcall { } use crate::errors::ContractError; use crate::states::{self}; +use crate::storage_types::{ConfigData, RateLimit}; use crate::{ states::{ extent_ttl, has_upgrade_authority, read_administrator,write_administrator, @@ -42,14 +43,14 @@ impl AssetManager { Ok(()) } - pub fn get_config(env: Env) -> (Address, Address, Address, String, Address) { - ( - states::read_xcall(&env), - states::read_xcall_manager(&env), - states::read_native_address(&env), - states::read_icon_asset_manager(&env), - states::read_upgrade_authority(&env), - ) + pub fn get_config(env: Env) -> ConfigData { + ConfigData { + xcall: states::read_xcall(&env), + xcall_manager: states::read_xcall_manager(&env), + native_address: states::read_native_address(&env), + icon_asset_manager: states::read_icon_asset_manager(&env), + upgrade_authority: states::read_upgrade_authority(&env), + } } pub fn set_admin(env: Env, new_admin: Address) { @@ -81,13 +82,13 @@ impl AssetManager { Ok(()) } - pub fn get_rate_limit(env: Env, token_address: Address) -> Result<(u64, u32, u64, u64), ContractError> { - Ok(( - states::read_period(&env, token_address.clone()), - states::read_percentage(&env, token_address.clone()), - states::read_last_update(&env, token_address.clone()), - states::read_current_limit(&env, token_address), - )) + pub fn get_rate_limit(env: Env, token_address: Address) -> RateLimit { + RateLimit { + period: states::read_period(&env, token_address.clone()), + percentage: states::read_percentage(&env, token_address.clone()), + last_update: states::read_last_update(&env, token_address.clone()), + current_limit: states::read_current_limit(&env, token_address), + } } pub fn reset_limit(env: Env, token: Address) -> Result { diff --git a/contracts/asset_manager/src/storage_types.rs b/contracts/asset_manager/src/storage_types.rs index 7080536..5a64e42 100644 --- a/contracts/asset_manager/src/storage_types.rs +++ b/contracts/asset_manager/src/storage_types.rs @@ -1,4 +1,4 @@ -use soroban_sdk::{contracttype, Address}; +use soroban_sdk::{contracttype, Address, String}; pub(crate) const POINTS: u128 = 10000; @@ -17,3 +17,20 @@ pub enum DataKey { CurrentLimit(Address), } +#[contracttype] +pub struct ConfigData { + pub xcall: Address, + pub xcall_manager: Address, + pub native_address: Address, + pub icon_asset_manager: String, + pub upgrade_authority: Address, +} + +#[contracttype] +pub struct RateLimit { + pub period: u64, + pub percentage: u32, + pub last_update: u64, + pub current_limit: u64, +} + diff --git a/contracts/asset_manager/src/tests/asset_manager_test.rs b/contracts/asset_manager/src/tests/asset_manager_test.rs index 50e50e0..451d47e 100644 --- a/contracts/asset_manager/src/tests/asset_manager_test.rs +++ b/contracts/asset_manager/src/tests/asset_manager_test.rs @@ -82,7 +82,7 @@ fn test_configure_rate_limit() { )] ); let token_data = client.get_rate_limit(&ctx.token); - assert_eq!(token_data.3, 0); + assert_eq!(token_data.last_update, 0); } #[test] @@ -463,16 +463,6 @@ fn test_handle_call_message_for_deposit_rollback_panic_with_only_call_service() assert_eq!(token_client.balance(&ctx.withdrawer), bnusd_amount as i128) } -#[test] -fn test_extend_ttl() { - let ctx = TestContext::default(); - let client = AssetManagerClient::new(&ctx.env, &ctx.registry); - ctx.init_context(&client); - - client.configure_rate_limit(&ctx.token, &300, &300); - client.extend_ttl(); -} - #[test] fn test_reset_limit() { let ctx = TestContext::default();