Skip to content

Commit

Permalink
Contract: Bridge fees collection and claiming implementation (#60)
Browse files Browse the repository at this point in the history
- Implemented fee collection and claiming for all tokens. 
How it works: Every time a user sends a token it will collect fees into
the contract (part of the amount). The truncated amount counts towards
the fee (so that users can choose what they want to receive on the other
side).
For claiming, any relayer can claim when ever he wants and the fees will
be proportionally distributed to all of them.
  • Loading branch information
keyleu authored Dec 12, 2023
1 parent ecb3c0e commit b196cec
Show file tree
Hide file tree
Showing 10 changed files with 1,233 additions and 208 deletions.
237 changes: 152 additions & 85 deletions contract/Cargo.lock

Large diffs are not rendered by default.

153 changes: 125 additions & 28 deletions contract/src/contract.rs

Large diffs are not rendered by default.

3 changes: 3 additions & 0 deletions contract/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -153,4 +153,7 @@ pub enum ContractError {

#[error("InvalidOperationResult: OperationResult doesn't match a Pending Operation with the right Operation Type")]
InvalidOperationResult {},

#[error("CannotCoverBridgingFees: The amount sent is not enough to cover the bridging fees")]
CannotCoverBridgingFees {},
}
105 changes: 105 additions & 0 deletions contract/src/fees.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
use coreum_wasm_sdk::core::CoreumMsg;
use cosmwasm_std::{coin, BankMsg, Coin, Response, Storage, Uint128};

use crate::{
error::ContractError,
state::{CONFIG, FEES_COLLECTED},
};

pub fn amount_after_fees(
amount: Uint128,
bridging_fee: Uint128,
truncated_portion: Uint128,
) -> Result<Uint128, ContractError> {
let fee_to_collect = bridging_fee.saturating_sub(truncated_portion);

let amount_after_fees = amount
.checked_sub(fee_to_collect)
.map_err(|_| ContractError::CannotCoverBridgingFees {})?;

Ok(amount_after_fees)
}

pub fn handle_fee_collection(
storage: &mut dyn Storage,
bridging_fee: Uint128,
token_denom: String,
truncated_portion: Uint128,
) -> Result<Uint128, ContractError> {
// We substract the truncated portion from the bridging_fee. If truncated portion >= fee,
// then we already paid the fees and we collect the truncated portion instead of bridging fee (because it might be bigger than the bridging fee)
let fee_to_collect = bridging_fee.saturating_sub(truncated_portion);
let fee_collected = if fee_to_collect.is_zero() {
truncated_portion
} else {
bridging_fee
};

collect_fees(storage, coin(fee_collected.u128(), token_denom))?;
Ok(fee_collected)
}

pub fn collect_fees(storage: &mut dyn Storage, fee: Coin) -> Result<(), ContractError> {
// We only collect fees if there is something to collect
// If for some reason there is a coin that we are not charging fees for, we don't collect it
if !fee.amount.is_zero() {
let mut fees_collected = FEES_COLLECTED.load(storage)?;
// If we already have the coin in the fee collected array, we update the amount, if not, we add it as a new element.
match fees_collected.iter_mut().find(|c| c.denom == fee.denom) {
Some(coin) => coin.amount += fee.amount,
None => fees_collected.push(fee),
}
FEES_COLLECTED.save(storage, &fees_collected)?;
}

Ok(())
}

pub fn claim_fees_for_relayers(
storage: &mut dyn Storage,
) -> Result<Response<CoreumMsg>, ContractError> {
let mut fees_collected = FEES_COLLECTED.load(storage)?;
let relayers = CONFIG.load(storage)?.relayers;
let mut coins_for_each_relayer = vec![];

for fee in fees_collected.iter_mut() {
// For each token collected in fees, we will divide the amount by the number of relayers to know how much we need to send to each relayer
let amount_for_each_relayer = fee
.amount
.u128()
.checked_div(relayers.len() as u128)
.unwrap();

// If the amount is 0, we don't send it to the relayers
if amount_for_each_relayer != 0 {
coins_for_each_relayer.push(coin(amount_for_each_relayer, fee.denom.to_owned()));
}

// We substract the amount we are sending to the relayers from the total amount collected
// We can't simply remove it from the array because there might be small amounts left due to truncation when dividing
fee.amount = fee
.amount
.checked_sub(Uint128::from(
amount_for_each_relayer
.checked_mul(relayers.len() as u128)
.unwrap(),
))
.unwrap();
}

// We'll have 1 multi send message for each relayer
let mut send_messages = vec![];
for relayer in relayers.iter() {
send_messages.push(BankMsg::Send {
to_address: relayer.coreum_address.to_string(),
amount: coins_for_each_relayer.clone(),
});
}

// Last thing we do is to clean the fees collected array removing the coins that have 0 amount
// We need to do this step to avoid the posibility of iterating over them next claim
fees_collected.retain(|c| !c.amount.is_zero());
FEES_COLLECTED.save(storage, &fees_collected)?;

Ok(Response::new().add_messages(send_messages))
}
1 change: 1 addition & 0 deletions contract/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
pub mod contract;
pub mod error;
pub mod evidence;
pub mod fees;
pub mod msg;
pub mod operation;
pub mod relayer;
Expand Down
13 changes: 12 additions & 1 deletion contract/src/msg.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use cosmwasm_schema::{cw_serde, QueryResponses};
use cosmwasm_std::{Addr, Uint128};
use cosmwasm_std::{Addr, Coin, Uint128};
use cw_ownable::{cw_ownable_execute, cw_ownable_query};

#[allow(unused_imports)]
Expand Down Expand Up @@ -29,13 +29,15 @@ pub enum ExecuteMsg {
decimals: u32,
sending_precision: i32,
max_holding_amount: Uint128,
bridging_fee: Uint128,
},
#[serde(rename = "register_xrpl_token")]
RegisterXRPLToken {
issuer: String,
currency: String,
sending_precision: i32,
max_holding_amount: Uint128,
bridging_fee: Uint128,
},
RecoverTickets {
account_sequence: u64,
Expand All @@ -57,6 +59,8 @@ pub enum ExecuteMsg {
SendToXRPL {
recipient: String,
},
// Any relayer can claim fees at any point in time. They will be distributed proportionally among all of them.
ClaimFees {},
}

#[cw_ownable_query]
Expand All @@ -80,6 +84,8 @@ pub enum QueryMsg {
PendingOperations {},
#[returns(AvailableTicketsResponse)]
AvailableTickets {},
#[returns(FeesCollectedResponse)]
FeesCollected {},
}

#[cw_serde]
Expand All @@ -101,3 +107,8 @@ pub struct PendingOperationsResponse {
pub struct AvailableTicketsResponse {
pub tickets: Vec<u64>,
}

#[cw_serde]
pub struct FeesCollectedResponse {
pub fees_collected: Vec<Coin>,
}
9 changes: 8 additions & 1 deletion contract/src/state.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use std::collections::VecDeque;

use cosmwasm_schema::cw_serde;
use cosmwasm_std::{Empty, Uint128};
use cosmwasm_std::{Coin, Empty, Uint128};
use cw_storage_plus::{Index, IndexList, IndexedMap, Item, Map, UniqueIndex};

use crate::{evidence::Evidences, operation::Operation, relayer::Relayer};
Expand All @@ -20,6 +20,7 @@ pub enum TopKey {
UsedTickets = b'8',
PendingOperations = b'9',
PendingTicketUpdate = b'a',
FeesCollected = b'b',
}

impl TopKey {
Expand Down Expand Up @@ -49,6 +50,7 @@ pub struct XRPLToken {
pub sending_precision: i32,
pub max_holding_amount: Uint128,
pub state: TokenState,
pub bridging_fee: Uint128,
}

#[cw_serde]
Expand All @@ -71,6 +73,7 @@ pub struct CoreumToken {
pub sending_precision: i32,
pub max_holding_amount: Uint128,
pub state: TokenState,
pub bridging_fee: Uint128,
}

pub const CONFIG: Item<Config> = Item::new(TopKey::Config.as_str());
Expand Down Expand Up @@ -134,6 +137,8 @@ pub const USED_TICKETS_COUNTER: Item<u32> = Item::new(TopKey::UsedTickets.as_str
pub const PENDING_OPERATIONS: Map<u64, Operation> = Map::new(TopKey::PendingOperations.as_str());
// Flag to know if we are currently waiting for new_tickets to be allocated
pub const PENDING_TICKET_UPDATE: Item<bool> = Item::new(TopKey::PendingTicketUpdate.as_str());
// Fees collected that will be distributed to all relayers when they are claimed
pub const FEES_COLLECTED: Item<Vec<Coin>> = Item::new(TopKey::FeesCollected.as_str());

pub enum ContractActions {
Instantiation,
Expand All @@ -145,6 +150,7 @@ pub enum ContractActions {
XRPLTransactionResult,
SaveSignature,
SendToXRPL,
ClaimFees,
}

impl ContractActions {
Expand All @@ -159,6 +165,7 @@ impl ContractActions {
ContractActions::XRPLTransactionResult => "submit_xrpl_transaction_result",
ContractActions::SaveSignature => "save_signature",
ContractActions::SendToXRPL => "send_to_xrpl",
ContractActions::ClaimFees => "claim_fees",
}
}
}
Loading

0 comments on commit b196cec

Please sign in to comment.