Skip to content

Commit

Permalink
Transfer Coreum originated tokens to XRPL (#47)
Browse files Browse the repository at this point in the history
# Description
- Added bridge multisig address as Instantiation parameter to use it as
issuer for any Coreum tokens we register in the contract.
- Added sending precision, max holding amount and default status Enabled
on Coreum registered tokens.
- Implemented the initial flow of sending coreum originated tokens to
XRPL (creating the pending operation for relayers).
- Add extra validation of XRPL Addresses.
- Check that we can only send account sequence instead of ticket
sequence for TicketAllocation operations.
  • Loading branch information
keyleu authored Nov 17, 2023
1 parent 4827b21 commit d1dbbe7
Show file tree
Hide file tree
Showing 11 changed files with 639 additions and 96 deletions.
127 changes: 86 additions & 41 deletions contract/src/contract.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ use crate::{
error::ContractError,
evidence::{handle_evidence, hash_bytes, Evidence, OperationResult, TransactionResult},
msg::{
AvailableTicketsResponse, CoreumTokenResponse, CoreumTokensResponse, ExecuteMsg,
InstantiateMsg, PendingOperationsResponse, QueryMsg, XRPLTokensResponse,
AvailableTicketsResponse, CoreumTokensResponse, ExecuteMsg, InstantiateMsg,
PendingOperationsResponse, QueryMsg, XRPLTokensResponse,
},
operation::{
check_operation_exists, create_pending_operation, handle_trust_set_confirmation, Operation,
Expand Down Expand Up @@ -75,6 +75,8 @@ pub fn instantiate(

validate_relayers(&deps, msg.relayers.clone())?;

validate_xrpl_address(msg.bridge_xrpl_address.to_owned())?;

// We want to check that exactly the issue fee was sent, not more.
check_issue_fee(&deps, &info)?;

Expand All @@ -98,6 +100,7 @@ pub fn instantiate(
evidence_threshold: msg.evidence_threshold,
used_ticket_sequence_threshold: msg.used_ticket_sequence_threshold,
trust_set_limit_amount: msg.trust_set_limit_amount,
bridge_xrpl_address: msg.bridge_xrpl_address,
};

CONFIG.save(deps.storage, &config)?;
Expand Down Expand Up @@ -151,9 +154,20 @@ pub fn execute(
ExecuteMsg::UpdateOwnership(action) => {
update_ownership(deps.into_empty(), env, info, action)
}
ExecuteMsg::RegisterCoreumToken { denom, decimals } => {
register_coreum_token(deps.into_empty(), env, denom, decimals, info.sender)
}
ExecuteMsg::RegisterCoreumToken {
denom,
decimals,
sending_precision,
max_holding_amount,
} => register_coreum_token(
deps.into_empty(),
env,
info.sender,
denom,
decimals,
sending_precision,
max_holding_amount,
),
ExecuteMsg::RegisterXRPLToken {
issuer,
currency,
Expand All @@ -162,11 +176,11 @@ pub fn execute(
} => register_xrpl_token(
deps,
env,
info,
issuer,
currency,
sending_precision,
max_holding_amount,
info,
),
ExecuteMsg::SaveEvidence { evidence } => {
save_evidence(deps.into_empty(), info.sender, evidence)
Expand All @@ -184,7 +198,9 @@ pub fn execute(
operation_id,
signature,
} => save_signature(deps.into_empty(), info.sender, operation_id, signature),
ExecuteMsg::SendToXRPL { recipient } => send_to_xrpl(deps.into_empty(), info, recipient),
ExecuteMsg::SendToXRPL { recipient } => {
send_to_xrpl(deps.into_empty(), env, info, recipient)
}
}
}

Expand All @@ -201,9 +217,11 @@ fn update_ownership(
fn register_coreum_token(
deps: DepsMut,
env: Env,
sender: Addr,
denom: String,
decimals: u32,
sender: Addr,
sending_precision: i32,
max_holding_amount: Uint128,
) -> CoreumResult<ContractError> {
assert_owner(deps.storage, &sender)?;

Expand Down Expand Up @@ -232,6 +250,10 @@ fn register_coreum_token(
denom: denom.clone(),
decimals,
xrpl_currency: xrpl_currency.clone(),
sending_precision,
max_holding_amount,
// All registered Coreum originated tokens will start as enabled because they don't need a TrustSet operation to be bridged because issuer for such tokens is bridge address
state: TokenState::Enabled,
};
COREUM_TOKENS.save(deps.storage, denom.clone(), &token)?;

Expand All @@ -245,11 +267,11 @@ fn register_coreum_token(
fn register_xrpl_token(
deps: DepsMut<CoreumQueries>,
env: Env,
info: MessageInfo,
issuer: String,
currency: String,
sending_precision: i32,
max_holding_amount: Uint128,
info: MessageInfo,
) -> CoreumResult<ContractError> {
assert_owner(deps.storage, &info.sender)?;

Expand Down Expand Up @@ -551,6 +573,7 @@ fn save_signature(

fn send_to_xrpl(
deps: DepsMut,
env: Env,
info: MessageInfo,
recipient: String,
) -> CoreumResult<ContractError> {
Expand All @@ -561,6 +584,10 @@ fn send_to_xrpl(
validate_xrpl_address(recipient.to_owned())?;

let mut response = Response::new();
let decimals;
let amount_to_send;
let issuer;
let currency;
// We check if the token we are sending is an XRPL originated token or not
match XRPL_TOKENS
.idx
Expand All @@ -574,41 +601,66 @@ fn send_to_xrpl(
return Err(ContractError::XRPLTokenNotEnabled {});
}

let decimals =
match is_token_xrp(xrpl_token.issuer.to_owned(), xrpl_token.currency.to_owned()) {
true => XRP_DECIMALS,
false => XRPL_TOKENS_DECIMALS,
};

let amount_to_send =
truncate_amount(xrpl_token.sending_precision, decimals, funds.amount)?;
issuer = xrpl_token.issuer;
currency = xrpl_token.currency;
decimals = match is_token_xrp(issuer.to_owned(), currency.to_owned()) {
true => XRP_DECIMALS,
false => XRPL_TOKENS_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 {
coin: coin(funds.amount.u128(), xrpl_token.coreum_denom),
}));
response = response.add_message(burn_msg);

// Get a ticket and store the pending operation
let ticket = allocate_ticket(deps.storage)?;
create_pending_operation(
deps.storage,
Some(ticket),
None,
OperationType::CoreumToXRPLTransfer {
issuer: xrpl_token.issuer,
currency: xrpl_token.currency,
amount: amount_to_send,
recipient: recipient.to_owned(),
},
)?;
}

// TODO(keyleu): Once we have the sending operation for coreum tokens we can implement the sending of coreum originated tokens here
// For now we will just return an error.
None => return Err(ContractError::TokenNotRegistered {}),
None => {
// If it's not an XRPL originated token we need to check that it's registered as a Coreum originated token
let coreum_token = COREUM_TOKENS
.load(deps.storage, funds.denom.to_owned())
.map_err(|_| ContractError::TokenNotRegistered {})?;

if coreum_token.state.ne(&TokenState::Enabled) {
return Err(ContractError::CoreumOriginatedTokenDisabled {});
}

let config = CONFIG.load(deps.storage)?;

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)?;

// For Coreum originated tokens we need to check that we are not going over max bridge amount.
if deps
.querier
.query_balance(env.contract.address, coreum_token.denom)?
.amount
.gt(&coreum_token.max_holding_amount)
{
return Err(ContractError::MaximumBridgedAmountReached {});
}
}
}

// Get a ticket and store the pending operation
let ticket = allocate_ticket(deps.storage)?;
create_pending_operation(
deps.storage,
Some(ticket),
None,
OperationType::CoreumToXRPLTransfer {
issuer,
currency,
amount: amount_to_send,
recipient: recipient.to_owned(),
},
)?;

Ok(response
.add_attribute("action", ContractActions::SendToXRPL.as_str())
.add_attribute("sender", info.sender)
Expand All @@ -627,7 +679,6 @@ 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::CoreumToken { denom } => to_json_binary(&query_coreum_token(deps, denom)?),
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 @@ -675,12 +726,6 @@ fn query_coreum_tokens(
Ok(CoreumTokensResponse { tokens })
}

fn query_coreum_token(deps: Deps, denom: String) -> StdResult<CoreumTokenResponse> {
let token = COREUM_TOKENS.load(deps.storage, denom)?;

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
5 changes: 4 additions & 1 deletion contract/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ pub enum ContractError {
#[error("InvalidThreshold: Threshold can not be higher than amount of relayers")]
InvalidThreshold {},

#[error("InvalidXRPLAddress: XRPL address {} is not valid, must start with r and have a length between 24 and 34", address)]
#[error("InvalidXRPLAddress: XRPL address {} is not valid", address)]
InvalidXRPLAddress { address: String },

#[error("DuplicatedRelayerXRPLAddress: All relayers must have different XRPL addresses")]
Expand Down Expand Up @@ -124,6 +124,9 @@ pub enum ContractError {
#[error("XRPLTokenNotEnabled: This token must be enabled to be bridged")]
XRPLTokenNotEnabled {},

#[error("CoreumOriginatedTokenDisabled: This token is currently disabled and can't be bridged")]
CoreumOriginatedTokenDisabled {},

#[error("XRPLTokenNotInProcessing: This token must be in processing state to be enabled")]
XRPLTokenNotInProcessing {},

Expand Down
12 changes: 10 additions & 2 deletions contract/src/evidence.rs
Original file line number Diff line number Diff line change
Expand Up @@ -143,8 +143,16 @@ impl Evidence {
return Err(ContractError::InvalidTicketAllocationEvidence {});
}
}
OperationResult::TrustSet { .. } => (),
OperationResult::CoreumToXRPLTransfer { .. } =>(),
OperationResult::TrustSet { .. } => {
if account_sequence.is_some() {
return Err(ContractError::InvalidTransactionResultEvidence {});
}
}
OperationResult::CoreumToXRPLTransfer { .. } => {
if account_sequence.is_some() {
return Err(ContractError::InvalidTransactionResultEvidence {});
}
}
}

Ok(())
Expand Down
11 changes: 4 additions & 7 deletions contract/src/msg.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ pub struct InstantiateMsg {
pub used_ticket_sequence_threshold: u32,
// Trust set limit amount that will be used when registering XRPL tokens
pub trust_set_limit_amount: Uint128,
// Address of multisig account on the XRPL
pub bridge_xrpl_address: String,
}

#[cw_ownable_execute]
Expand All @@ -25,6 +27,8 @@ pub enum ExecuteMsg {
RegisterCoreumToken {
denom: String,
decimals: u32,
sending_precision: i32,
max_holding_amount: Uint128,
},
#[serde(rename = "register_xrpl_token")]
RegisterXRPLToken {
Expand Down Expand Up @@ -66,8 +70,6 @@ pub enum QueryMsg {
offset: Option<u64>,
limit: Option<u32>,
},
#[returns(CoreumTokenResponse)]
CoreumToken { denom: String },
#[returns(PendingOperationsResponse)]
PendingOperations {},
#[returns(AvailableTicketsResponse)]
Expand All @@ -84,11 +86,6 @@ pub struct CoreumTokensResponse {
pub tokens: Vec<CoreumToken>,
}

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

#[cw_serde]
pub struct PendingOperationsResponse {
pub operations: Vec<Operation>,
Expand Down
8 changes: 7 additions & 1 deletion contract/src/relayer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,13 @@ pub fn validate_relayers(

pub fn validate_xrpl_address(address: String) -> Result<(), ContractError> {
// We validate that the length of the issuer is between 24 and 34 characters and starts with 'r'
if address.len() >= 24 && address.len() <= 34 && address.starts_with('r') {
if address.len() >= 25
&& address.len() <= 35
&& address.starts_with('r')
&& address
.chars()
.all(|c| c.is_alphanumeric() && c != '0' && c != 'O' && c != 'I' && c != 'l')
{
return Ok(());
}
Err(ContractError::InvalidXRPLAddress { address })
Expand Down
4 changes: 4 additions & 0 deletions contract/src/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ pub struct Config {
pub evidence_threshold: u32,
pub used_ticket_sequence_threshold: u32,
pub trust_set_limit_amount: Uint128,
pub bridge_xrpl_address: String,
}

#[cw_serde]
Expand Down Expand Up @@ -67,6 +68,9 @@ pub struct CoreumToken {
pub denom: String,
pub decimals: u32,
pub xrpl_currency: String,
pub sending_precision: i32,
pub max_holding_amount: Uint128,
pub state: TokenState,
}

pub const CONFIG: Item<Config> = Item::new(TopKey::Config.as_str());
Expand Down
Loading

0 comments on commit d1dbbe7

Please sign in to comment.