Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Relayer/Contract: Move decimal conversion from relayer code to contract code. #54

Merged
merged 16 commits into from
Dec 1, 2023
12 changes: 6 additions & 6 deletions contract/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion contract/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ incremental = false
overflow-checks = true

[dependencies]
coreum-wasm-sdk = "0.2.3"
coreum-wasm-sdk = "0.2.4"
cosmwasm-schema = "1.5.0"
cosmwasm-std = { version = "1.5.0", features = ["cosmwasm_1_1"] }
cw-ownable = "0.5.1"
Expand Down
114 changes: 85 additions & 29 deletions contract/src/contract.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use crate::{
error::ContractError,
evidence::{handle_evidence, hash_bytes, Evidence, OperationResult, TransactionResult},
msg::{
AvailableTicketsResponse, CoreumTokenResponse, CoreumTokensResponse, ExecuteMsg,
AvailableTicketsResponse, CoreumTokensResponse, ExecuteMsg,
InstantiateMsg, PendingOperationsResponse, QueryMsg, XRPLTokensResponse,
},
operation::{
Expand Down Expand Up @@ -228,6 +228,8 @@ fn register_coreum_token(
) -> CoreumResult<ContractError> {
assert_owner(deps.storage, &sender)?;

validate_sending_precision(sending_precision, decimals)?;

if COREUM_TOKENS.has(deps.storage, denom.clone()) {
return Err(ContractError::CoreumTokenAlreadyRegistered { denom });
}
Expand Down Expand Up @@ -285,10 +287,7 @@ fn register_xrpl_token(

validate_xrpl_issuer_and_currency(issuer.clone(), currency.clone())?;

// Minimum and maximum sending precisions we allow
if !(MIN_SENDING_PRECISION..=MAX_SENDING_PRECISION).contains(&sending_precision) {
return Err(ContractError::InvalidSendingPrecision {});
}
validate_sending_precision(sending_precision, XRPL_TOKENS_DECIMALS)?;

// We want to check that exactly the issue fee was sent, not more.
check_issue_fee(&deps, &info)?;
Expand Down Expand Up @@ -405,6 +404,7 @@ fn save_evidence(deps: DepsMut, sender: Addr, evidence: Evidence) -> CoreumResul
false => XRPL_TOKENS_DECIMALS,
};

// Here we simply truncate because the Coreum tokens corresponding to XRPL originated tokens have the same decimals as their corresponding Coreum tokens
let amount_to_send = truncate_amount(token.sending_precision, decimals, amount)?;

if amount_to_send
Expand Down Expand Up @@ -440,14 +440,18 @@ fn save_evidence(deps: DepsMut, sender: Addr, evidence: Evidence) -> CoreumResul
}
token
}
// In theory this will never happen because any token issued from the multisig address is a token that was bridged from Coreum so it will be registered.
// In practice this will never happen because any token issued from the multisig address is a token that was bridged from Coreum so it will be registered.
// This could theoretically happen if the multisig address on XRPL issued a token on its own and then tried to bridge it
None => return Err(ContractError::TokenNotRegistered {}),
};

// TODO(keyleu): add/update tests for it
// We build the amount_to_send here since it includes validation against zero amount after the truncation
let amount_to_send = truncate_amount(token.sending_precision, token.decimals, amount)?;
// We first convert the amount we receive with XRPL decimals to the corresponding decimals in Coreum and then we apply the truncation according to sending precision.
let amount_to_send = convert_and_truncate_amount(
token.sending_precision,
XRPL_TOKENS_DECIMALS,
token.decimals,
amount,
)?;

if threshold_reached {
// TODO(keyleu): for now we are SENDING back the entire amount but when fees are implemented this will not happen and part of the amount will be sent and funds will be collected
Expand Down Expand Up @@ -700,7 +704,9 @@ fn send_to_xrpl(
false => XRPL_TOKENS_DECIMALS,
};

// We don't need any decimal conversion because the token is an XRPL originated token and they are issued with same decimals
amount_to_send = truncate_amount(xrpl_token.sending_precision, decimals, funds.amount)?;

// Since tokens are being sent back we need to burn them in the contract
// TODO(keyleu): for now we are BURNING the entire amount but when fees are implemented this will not happen and part of the amount will be burned and fees will be collected
let burn_msg = CosmosMsg::from(CoreumMsg::AssetFT(assetft::Msg::Burn {
Expand All @@ -724,8 +730,15 @@ fn send_to_xrpl(
decimals = coreum_token.decimals;
issuer = config.bridge_xrpl_address;
currency = coreum_token.xrpl_currency;
amount_to_send =
truncate_amount(coreum_token.sending_precision, decimals, funds.amount)?;

// Since this is a Coreum originated token with different decimals, we are first going to truncate according to sending precision and then we will convert
// to corresponding XRPL decimals.
amount_to_send = truncate_and_convert_amount(
coreum_token.sending_precision,
decimals,
XRPL_TOKENS_DECIMALS,
funds.amount,
)?;

// For Coreum originated tokens we need to check that we are not going over max bridge amount.
if deps
Expand Down Expand Up @@ -770,10 +783,7 @@ pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult<Binary> {
}
QueryMsg::CoreumTokens { offset, limit } => {
to_json_binary(&query_coreum_tokens(deps, offset, limit)?)
}
QueryMsg::CoreumTokenByXRPLCurrency { xrpl_currency } => {
to_json_binary(&query_coreum_token_by_xrpl_currency(deps, xrpl_currency)?)
}
},
QueryMsg::Ownership {} => to_json_binary(&get_ownership(deps.storage)?),
QueryMsg::PendingOperations {} => to_json_binary(&query_pending_operations(deps)?),
QueryMsg::AvailableTickets {} => to_json_binary(&query_available_tickets(deps)?),
Expand Down Expand Up @@ -821,19 +831,6 @@ fn query_coreum_tokens(
Ok(CoreumTokensResponse { tokens })
}

fn query_coreum_token_by_xrpl_currency(
deps: Deps,
xrpl_currency: String,
) -> StdResult<CoreumTokenResponse> {
let token = COREUM_TOKENS
.idx
.xrpl_currency
.item(deps.storage, xrpl_currency)?
.map(|(_, ct)| ct);

Ok(CoreumTokenResponse { token })
}

fn query_pending_operations(deps: Deps) -> StdResult<PendingOperationsResponse> {
let operations: Vec<Operation> = PENDING_OPERATIONS
.range(deps.storage, None, None, Order::Ascending)
Expand Down Expand Up @@ -889,13 +886,29 @@ pub fn validate_xrpl_issuer_and_currency(
Ok(())
}

pub fn validate_sending_precision(
sending_precision: i32,
decimals: u32,
) -> Result<(), ContractError> {
// Minimum and maximum sending precisions we allow
if !(MIN_SENDING_PRECISION..=MAX_SENDING_PRECISION).contains(&sending_precision) {
return Err(ContractError::InvalidSendingPrecision {});
}

if sending_precision > decimals.try_into().unwrap() {
return Err(ContractError::TokenSendingPrecisionTooHigh {});
}
Ok(())
}

// Function used to truncate the amount to not send tokens over the sending precision.
fn truncate_amount(
sending_precision: i32,
decimals: u32,
amount: Uint128,
) -> Result<Uint128, ContractError> {
// To get exactly by how much we need to divide the original amount
// Example: if sending precision = -1. Exponent will be 15 - ( - 1) = 16 for XRPL tokens so we will divide the original amount by 1e16
// Example: if sending precision = -1. Exponent will be 15 - (-1) = 16 for XRPL tokens so we will divide the original amount by 1e16
// Example: if sending precision = 14. Exponent will be 15 - 14 = 1 for XRPL tokens so we will divide the original amount by 10
let exponent = decimals as i32 - sending_precision;

Expand All @@ -908,6 +921,49 @@ fn truncate_amount(
Ok(amount_to_send.checked_mul(Uint128::new(10u128.pow(exponent.unsigned_abs())))?)
}

// Function used to convert the amount received from XRPL with XRPL decimals to the Coreum amount with Coreum decimals
fn convert_amount_decimals(
from_decimals: u32,
to_decimals: u32,
amount: Uint128,
) -> Result<Uint128, ContractError> {
let converted_amount = match from_decimals.cmp(&to_decimals) {
std::cmp::Ordering::Less => amount.checked_mul(Uint128::new(
10u128.pow(to_decimals.saturating_sub(from_decimals)),
))?,
std::cmp::Ordering::Greater => amount.checked_div(Uint128::new(
10u128.pow(from_decimals.saturating_sub(to_decimals)),
))?,
std::cmp::Ordering::Equal => amount,
};

Ok(converted_amount)
}

// Helper function to combine the conversion and truncation of amounts.
fn convert_and_truncate_amount(
sending_precision: i32,
from_decimals: u32,
to_decimals: u32,
amount: Uint128,
) -> Result<Uint128, ContractError> {
let converted_amount = convert_amount_decimals(from_decimals, to_decimals, amount)?;
let truncated_amount = truncate_amount(sending_precision, to_decimals, converted_amount)?;
Ok(truncated_amount)
}

// Helper function to combine the truncation and conversion of amounts
fn truncate_and_convert_amount(
sending_precision: i32,
from_decimals: u32,
to_decimals: u32,
amount: Uint128,
) -> Result<Uint128, ContractError> {
let truncated_amount = truncate_amount(sending_precision, from_decimals, amount)?;
let converted_amount = convert_amount_decimals(from_decimals, to_decimals, truncated_amount)?;
Ok(converted_amount)
}

fn is_token_xrp(issuer: String, currency: String) -> bool {
issuer == XRP_ISSUER && currency == XRP_CURRENCY
}
Expand Down
5 changes: 5 additions & 0 deletions contract/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -145,4 +145,9 @@ pub enum ContractError {
"InvalidSendingPrecision: The sending precision can't be more than the token decimals or less than the negative token decimals"
)]
InvalidSendingPrecision {},

#[error(
"TokenSendingPrecisionTooHigh: The sending precision can't be more than the token decimals"
)]
TokenSendingPrecisionTooHigh {},
}
8 changes: 0 additions & 8 deletions contract/src/msg.rs
Original file line number Diff line number Diff line change
Expand Up @@ -76,9 +76,6 @@ pub enum QueryMsg {
offset: Option<u64>,
limit: Option<u32>,
},
#[returns(CoreumTokenResponse)]
#[serde(rename = "coreum_token_by_xrpl_currency")]
CoreumTokenByXRPLCurrency { xrpl_currency: String },
#[returns(PendingOperationsResponse)]
PendingOperations {},
#[returns(AvailableTicketsResponse)]
Expand All @@ -95,11 +92,6 @@ pub struct CoreumTokensResponse {
pub tokens: Vec<CoreumToken>,
}

#[cw_serde]
pub struct CoreumTokenResponse {
pub token: Option<CoreumToken>,
}

#[cw_serde]
pub struct PendingOperationsResponse {
pub operations: Vec<Operation>,
Expand Down
Loading
Loading