From 0d74f11d1a64c7085b326d2b0999d394faa40d14 Mon Sep 17 00:00:00 2001 From: veeso Date: Thu, 25 Jan 2024 16:42:04 +0100 Subject: [PATCH] feat: swap fee --- integration-tests/src/lib.rs | 1 + src/declarations/fly/fly.did.d.ts | 2 + src/declarations/fly/fly.did.js | 3 + src/did/src/fly.rs | 2 + src/fly/fly.did | 2 + src/fly/src/app.rs | 22 +++++++ src/fly/src/app/erc20_bridge.rs | 20 +++++++ src/fly/src/app/erc20_bridge/swap_fee.rs | 74 ++++++++++++++++++++++++ src/fly/src/app/memory.rs | 1 + src/fly/src/lib.rs | 6 ++ 10 files changed, 133 insertions(+) create mode 100644 src/fly/src/app/erc20_bridge.rs create mode 100644 src/fly/src/app/erc20_bridge/swap_fee.rs diff --git a/integration-tests/src/lib.rs b/integration-tests/src/lib.rs index 17bfc9c..2c9ad56 100644 --- a/integration-tests/src/lib.rs +++ b/integration-tests/src/lib.rs @@ -205,6 +205,7 @@ impl TestEnv { cketh_minter_canister, erc20_bridge_address: H160::from_hex_str("0x2CE04Fd64DB0372F6fb4B7a542f0F9196feE5663") .unwrap(), + erc20_swap_fee: 178_180_000_000_000, }; let init_arg = Encode!(&init_arg).unwrap(); diff --git a/src/declarations/fly/fly.did.d.ts b/src/declarations/fly/fly.did.d.ts index a1e7e6d..cb6c300 100644 --- a/src/declarations/fly/fly.did.d.ts +++ b/src/declarations/fly/fly.did.d.ts @@ -59,6 +59,7 @@ export interface FlyInitData { 'ckbtc_canister' : Principal, 'erc20_bridge_address' : string, 'initial_balances' : Array<[Account, bigint]>, + 'erc20_swap_fee' : bigint, 'swap_account' : Account, 'xrc_canister' : Principal, 'marketplace_canister' : Principal, @@ -154,6 +155,7 @@ export interface _SERVICE { 'admin_set_cketh_ledger_canister' : ActorMethod<[Principal], undefined>, 'admin_set_cketh_minter_canister' : ActorMethod<[Principal], undefined>, 'admin_set_erc20_bridge_address' : ActorMethod<[string], undefined>, + 'admin_set_erc20_swap_fee' : ActorMethod<[bigint], undefined>, 'admin_set_icp_ledger_canister' : ActorMethod<[Principal], undefined>, 'admin_set_role' : ActorMethod<[Principal, Role], undefined>, 'admin_set_swap_account' : ActorMethod<[Account], undefined>, diff --git a/src/declarations/fly/fly.did.js b/src/declarations/fly/fly.did.js index e447b36..e9272b4 100644 --- a/src/declarations/fly/fly.did.js +++ b/src/declarations/fly/fly.did.js @@ -11,6 +11,7 @@ export const idlFactory = ({ IDL }) => { 'ckbtc_canister' : IDL.Principal, 'erc20_bridge_address' : IDL.Text, 'initial_balances' : IDL.Vec(IDL.Tuple(Account, IDL.Nat)), + 'erc20_swap_fee' : IDL.Nat64, 'swap_account' : Account, 'xrc_canister' : IDL.Principal, 'marketplace_canister' : IDL.Principal, @@ -182,6 +183,7 @@ export const idlFactory = ({ IDL }) => { 'admin_set_cketh_ledger_canister' : IDL.Func([IDL.Principal], [], []), 'admin_set_cketh_minter_canister' : IDL.Func([IDL.Principal], [], []), 'admin_set_erc20_bridge_address' : IDL.Func([IDL.Text], [], []), + 'admin_set_erc20_swap_fee' : IDL.Func([IDL.Nat64], [], []), 'admin_set_icp_ledger_canister' : IDL.Func([IDL.Principal], [], []), 'admin_set_role' : IDL.Func([IDL.Principal, Role], [], []), 'admin_set_swap_account' : IDL.Func([Account], [], []), @@ -231,6 +233,7 @@ export const init = ({ IDL }) => { 'ckbtc_canister' : IDL.Principal, 'erc20_bridge_address' : IDL.Text, 'initial_balances' : IDL.Vec(IDL.Tuple(Account, IDL.Nat)), + 'erc20_swap_fee' : IDL.Nat64, 'swap_account' : Account, 'xrc_canister' : IDL.Principal, 'marketplace_canister' : IDL.Principal, diff --git a/src/did/src/fly.rs b/src/did/src/fly.rs index 1d77d00..55581a9 100644 --- a/src/did/src/fly.rs +++ b/src/did/src/fly.rs @@ -116,6 +116,8 @@ pub struct FlyInitData { pub cketh_minter_canister: Principal, /// The Ethereum address of the ERC20 bridge pub erc20_bridge_address: H160, + /// Initial ERC20 swap fee + pub erc20_swap_fee: u64, /// Total supply of $picofly tokens pub total_supply: PicoFly, /// Initial balances (wallet subaccount -> picofly) diff --git a/src/fly/fly.did b/src/fly/fly.did index 11e2bf2..3155022 100644 --- a/src/fly/fly.did +++ b/src/fly/fly.did @@ -52,6 +52,7 @@ type FlyInitData = record { ckbtc_canister : principal; erc20_bridge_address : text; initial_balances : vec record { Account; nat }; + erc20_swap_fee : nat64; swap_account : Account; xrc_canister : principal; marketplace_canister : principal; @@ -141,6 +142,7 @@ service : (FlyInitData) -> { admin_set_cketh_ledger_canister : (principal) -> (); admin_set_cketh_minter_canister : (principal) -> (); admin_set_erc20_bridge_address : (text) -> (); + admin_set_erc20_swap_fee : (nat64) -> (); admin_set_icp_ledger_canister : (principal) -> (); admin_set_role : (principal, Role) -> (); admin_set_swap_account : (Account) -> (); diff --git a/src/fly/src/app.rs b/src/fly/src/app.rs index fadc77d..3ad7eed 100644 --- a/src/fly/src/app.rs +++ b/src/fly/src/app.rs @@ -4,6 +4,7 @@ mod balance; mod configuration; +mod erc20_bridge; mod inspect; mod liquidity_pool; mod memory; @@ -28,6 +29,7 @@ use icrc::icrc2::{self, Icrc2}; use self::balance::Balance; use self::configuration::Configuration; +use self::erc20_bridge::Erc20Bridge; pub use self::inspect::Inspect; use self::liquidity_pool::LiquidityPool; use self::pool::Pool; @@ -55,6 +57,8 @@ impl FlyCanister { Configuration::set_cketh_minter_canister(data.cketh_minter_canister); // set ERC20 bridge address Configuration::set_erc20_bridge_address(data.erc20_bridge_address); + // Set initial swap fee + Erc20Bridge::set_swap_fee(data.erc20_swap_fee).unwrap(); // init liquidity pool LiquidityPool::init(); // set roles @@ -244,6 +248,14 @@ impl FlyCanister { } Configuration::set_erc20_bridge_address(address); } + + /// Set ERC20 swap fee + pub fn admin_set_erc20_swap_fee(fee: u64) { + if !Inspect::inspect_is_admin(utils::caller()) { + ic_cdk::trap("Unauthorized"); + } + Erc20Bridge::set_swap_fee(fee).unwrap() + } } impl Icrc1 for FlyCanister { @@ -511,6 +523,7 @@ mod test { use crate::utils::caller; const ERC20_BRIDGE_ADDRESS: &str = "0x2CE04Fd64DB0372F6fb4B7a542f0F9196feE5663"; + const ERC20_SWAP_FEE: u64 = 178_180_000_000_000; #[tokio::test] async fn test_should_init_canister() { @@ -558,6 +571,7 @@ mod test { Configuration::get_erc20_bridge_address(), H160::from_hex_str(ERC20_BRIDGE_ADDRESS).unwrap() ); + assert_eq!(Erc20Bridge::get_swap_fee(), ERC20_SWAP_FEE); } #[tokio::test] @@ -832,6 +846,13 @@ mod test { assert_eq!(Configuration::get_erc20_bridge_address(), address); } + #[test] + fn test_should_set_erc20_swap_fee() { + init_canister(); + FlyCanister::admin_set_erc20_swap_fee(10); + assert_eq!(Erc20Bridge::get_swap_fee(), 10); + } + #[tokio::test] async fn test_should_get_balance_of() { init_canister(); @@ -1211,6 +1232,7 @@ mod test { cketh_minter_canister: caller(), cketh_ledger_canister: caller(), erc20_bridge_address: H160::from_hex_str(ERC20_BRIDGE_ADDRESS).unwrap(), + erc20_swap_fee: ERC20_SWAP_FEE, }; FlyCanister::init(data); } diff --git a/src/fly/src/app/erc20_bridge.rs b/src/fly/src/app/erc20_bridge.rs new file mode 100644 index 0000000..590c8ce --- /dev/null +++ b/src/fly/src/app/erc20_bridge.rs @@ -0,0 +1,20 @@ +mod swap_fee; + +use did::fly::FlyResult; + +use self::swap_fee::SwapFee; + +/// ERC20 Bridge +pub struct Erc20Bridge; + +impl Erc20Bridge { + /// Returns the swap fee. + pub fn get_swap_fee() -> u64 { + SwapFee::get_swap_fee() + } + + /// Sets the swap fee. + pub fn set_swap_fee(swap_fee: u64) -> FlyResult<()> { + SwapFee::set_swap_fee(swap_fee) + } +} diff --git a/src/fly/src/app/erc20_bridge/swap_fee.rs b/src/fly/src/app/erc20_bridge/swap_fee.rs new file mode 100644 index 0000000..c8f5485 --- /dev/null +++ b/src/fly/src/app/erc20_bridge/swap_fee.rs @@ -0,0 +1,74 @@ +use std::cell::RefCell; + +use did::fly::{FlyError, FlyResult}; +use ic_stable_structures::memory_manager::VirtualMemory; +use ic_stable_structures::{DefaultMemoryImpl, StableCell}; + +use crate::app::memory::{ + ERC20_SWAP_FEE_LAST_UPDATE_MEMORY_ID, ERC20_SWAP_FEE_MEMORY_ID, MEMORY_MANAGER, +}; +use crate::utils::time; + +thread_local! { + + static SWAP_FEE: RefCell>> = + RefCell::new( + StableCell::new( + MEMORY_MANAGER.with(|mm| mm.get(ERC20_SWAP_FEE_MEMORY_ID)), + 0_u64, + ).unwrap() + ); + + static LAST_SWAP_FEE_UPDATE : RefCell>> = + RefCell::new( + StableCell::new( + MEMORY_MANAGER.with(|mm| mm.get(ERC20_SWAP_FEE_LAST_UPDATE_MEMORY_ID)), + 0_u64, + ).unwrap() + ); + +} + +/// Swap fee for ERC20 token swaps. +pub struct SwapFee; + +impl SwapFee { + /// Returns the swap fee. + pub fn get_swap_fee() -> u64 { + SWAP_FEE.with(|sf| *sf.borrow().get()) + } + + /// Sets the swap fee and update last swap fee update. + pub fn set_swap_fee(swap_fee: u64) -> FlyResult<()> { + SWAP_FEE + .with_borrow_mut(|sf| sf.set(swap_fee)) + .map_err(|_| FlyError::StorageError)?; + + LAST_SWAP_FEE_UPDATE + .with_borrow_mut(|lsfu| lsfu.set(time()).map_err(|_| FlyError::StorageError))?; + + Ok(()) + } +} + +#[cfg(test)] +mod test { + + use pretty_assertions::{assert_eq, assert_ne}; + + use super::*; + + #[test] + fn test_swap_fee() { + assert_eq!(SwapFee::get_swap_fee(), 0); + assert_eq!(LAST_SWAP_FEE_UPDATE.with(|lsfu| *lsfu.borrow().get()), 0); + + SwapFee::set_swap_fee(100).unwrap(); + assert_eq!(SwapFee::get_swap_fee(), 100); + + SwapFee::set_swap_fee(200).unwrap(); + assert_eq!(SwapFee::get_swap_fee(), 200); + + assert_ne!(LAST_SWAP_FEE_UPDATE.with(|lsfu| *lsfu.borrow().get()), 0); + } +} diff --git a/src/fly/src/app/memory.rs b/src/fly/src/app/memory.rs index 9aae7ac..a99ec1e 100644 --- a/src/fly/src/app/memory.rs +++ b/src/fly/src/app/memory.rs @@ -34,6 +34,7 @@ pub const LIQUIDITY_POOL_CKBTC_ACCOUNT_MEMORY_ID: MemoryId = MemoryId::new(41); pub const FLY_CANISTER_ETH_ADDRESS_MEMORY_ID: MemoryId = MemoryId::new(50); pub const ERC20_SWAP_POOL_ACCOUNT_MEMORY_ID: MemoryId = MemoryId::new(51); pub const ERC20_SWAP_FEE_MEMORY_ID: MemoryId = MemoryId::new(52); +pub const ERC20_SWAP_FEE_LAST_UPDATE_MEMORY_ID: MemoryId = MemoryId::new(53); thread_local! { /// Memory manager diff --git a/src/fly/src/lib.rs b/src/fly/src/lib.rs index 2546798..f190592 100644 --- a/src/fly/src/lib.rs +++ b/src/fly/src/lib.rs @@ -132,6 +132,12 @@ pub fn admin_set_erc20_bridge_address(address: H160) { FlyCanister::admin_set_erc20_bridge_address(address) } +#[update] +#[candid_method(update)] +pub fn admin_set_erc20_swap_fee(fee: u64) { + FlyCanister::admin_set_erc20_swap_fee(fee) +} + #[query] #[candid_method(query)] pub fn get_transaction(id: u64) -> FlyResult {