Skip to content

Commit

Permalink
Add token registration for coreum and tests (#16)
Browse files Browse the repository at this point in the history
# Description

# Reviewers checklist:
- [ ] Try to write more meaningful comments with clear actions to be
taken.
- [ ] Nit-picking should be unblocking. Focus on core issues.

# Authors checklist
- [ ] Provide a concise and meaningful description
- [ ] Review the code yourself first, before making the PR.
- [ ] Annotate your PR in places that require explanation.
- [ ] Think and try to split the PR to smaller PR if it is big.

<!-- Reviewable:start -->
- - -
This change is [<img src="https://reviewable.io/review_button.svg"
height="34" align="absmiddle"
alt="Reviewable"/>](https://reviewable.io/reviews/CoreumFoundation/coreumbridge-xrpl/16)
<!-- Reviewable:end -->
  • Loading branch information
keyleu authored Sep 28, 2023
1 parent 5db2455 commit 4259c7c
Show file tree
Hide file tree
Showing 8 changed files with 235 additions and 60 deletions.
3 changes: 3 additions & 0 deletions contract/Cargo.lock

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

3 changes: 3 additions & 0 deletions contract/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,15 @@ incremental = false
overflow-checks = true

[dependencies]
base64 = "0.21.4"
coreum-wasm-sdk = "0.2.0"
cosmwasm-schema = "1.4.0"
cosmwasm-std = "1.4.0"
cw-ownable = "0.5.1"
cw-storage-plus = "1.1.0"
cw-utils = "1.0.1"
cw2 = "1.1.0"
sha2 = "0.10.7"
thiserror = "1.0.48"

[dev-dependencies]
Expand Down
2 changes: 1 addition & 1 deletion contract/README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Coreum-XPRL-Bridge
# Coreum-XRPL-Bridge

To compile the contract, you can run:

Expand Down
132 changes: 101 additions & 31 deletions contract/src/contract.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,29 +4,39 @@ use crate::{
CoreumTokenResponse, CoreumTokensResponse, ExecuteMsg, InstantiateMsg, QueryMsg,
XrplTokenResponse, XrplTokensResponse,
},
state::{Config, TokenCoreum, TokenXRP, CONFIG, TOKENS_COREUM, TOKENS_XRPL},
state::{
Config, ContractActions, TokenCoreum, TokenXRP, CONFIG, TOKENS_COREUM, TOKENS_XRPL,
XRPL_CURRENCIES,
},
};
use base64::{engine::general_purpose, Engine as _};
use coreum_wasm_sdk::{
assetft::{Msg::Issue, BURNING, IBC, MINTING},
core::{CoreumMsg, CoreumResult},
assetft::{Msg::Issue, ParamsResponse, Query, BURNING, IBC, MINTING},
core::{CoreumMsg, CoreumQueries, CoreumResult},
};
use cosmwasm_std::{
entry_point, to_binary, Binary, CosmosMsg, Deps, DepsMut, Env, MessageInfo, Order, Response,
StdResult, Uint128,
entry_point, to_binary, Addr, Binary, CosmosMsg, Deps, DepsMut, Empty, Env, MessageInfo, Order,
Response, StdResult, Uint128,
};
use cw2::set_contract_version;
use cw_ownable::{get_ownership, initialize_owner, Action, assert_owner};
use cw_ownable::{assert_owner, get_ownership, initialize_owner, Action};
use cw_utils::one_coin;
use sha2::{Digest, Sha256};
use std::str;

// version info for migration info
const CONTRACT_NAME: &str = env!("CARGO_PKG_NAME");
const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION");

const DEFAULT_MAX_LIMIT: u32 = 250;
const XRP_SYMBOL: &str = "xrp";
const XRP_SYMBOL: &str = "XRL";
const XRP_SUBUNIT: &str = "drop";

const COREUM_CURRENCY_PREFIX: &str = "coreum";

#[cfg_attr(not(feature = "library"), entry_point)]
pub fn instantiate(
deps: DepsMut,
deps: DepsMut<CoreumQueries>,
env: Env,
info: MessageInfo,
msg: InstantiateMsg,
Expand All @@ -35,13 +45,22 @@ pub fn instantiate(
initialize_owner(
deps.storage,
deps.api,
Some(deps.api.addr_validate(msg.admin.as_ref())?.as_ref()),
Some(deps.api.addr_validate(msg.owner.as_ref())?.as_ref()),
)?;

for address in msg.relayers.clone() {
deps.api.addr_validate(address.as_ref())?;
}

// We want to check that exactly the issue fee was sent, not more.
let query_params_res: ParamsResponse = deps
.querier
.query(&CoreumQueries::AssetFT(Query::Params {}).into())?;

if query_params_res.params.issue_fee != one_coin(&info)? {
return Err(ContractError::InvalidIssueFee {});
}

//Threshold can't be more than number of relayers
if msg.evidence_threshold > msg.relayers.len().try_into().unwrap() {
return Err(ContractError::InvalidThreshold {});
Expand All @@ -56,7 +75,7 @@ pub fn instantiate(

let xrp_issue_msg = CosmosMsg::from(CoreumMsg::AssetFT(Issue {
symbol: XRP_SYMBOL.to_string(),
subunit: XRP_SYMBOL.to_string(),
subunit: XRP_SUBUNIT.to_string(),
precision: 6,
initial_amount: Uint128::zero(),
description: None,
Expand All @@ -65,24 +84,20 @@ pub fn instantiate(
send_commission_rate: Some("0.0".to_string()),
}));

let xrp_in_coreum = format!("{}-{}", XRP_SYMBOL, env.contract.address).to_lowercase();
let xrp_in_coreum = format!("{}-{}", XRP_SUBUNIT, env.contract.address).to_lowercase();

//We save the link between the denom in the Coreum chain and the denom in XPRL, so that when we receive
//We save the link between the denom in the Coreum chain and the denom in XRPL, so that when we receive
//a token we can inform the relayers of what is being sent back.
let token = TokenXRP {
issuer: XRP_SYMBOL.to_string(),
currency: XRP_SYMBOL.to_string(),
issuer: None,
currency: None,
coreum_denom: xrp_in_coreum,
};

TOKENS_XRPL.save(
deps.storage,
format!("{}{}", XRP_SYMBOL, XRP_SYMBOL),
&token,
)?;
TOKENS_XRPL.save(deps.storage, XRP_SYMBOL.to_string(), &token)?;

Ok(Response::new()
.add_attribute("action", "bridge_instantiation")
.add_attribute("action", ContractActions::Instantiation.as_str())
.add_attribute("contract_name", CONTRACT_NAME)
.add_attribute("contract_version", CONTRACT_VERSION)
.add_attribute("admin", info.sender)
Expand All @@ -98,7 +113,9 @@ pub fn execute(
) -> Result<Response, ContractError> {
match msg {
ExecuteMsg::UpdateOwnership(action) => update_ownership(deps, env, info, action),
ExecuteMsg::RegisterCoreumToken { denom } => register_coreum_token(deps, denom, info),
ExecuteMsg::RegisterCoreumToken { denom, decimals } => {
register_coreum_token(deps, env, denom, decimals, info.sender)
}
}
}

Expand All @@ -114,11 +131,54 @@ fn update_ownership(

fn register_coreum_token(
deps: DepsMut,
_denom: String,
info: MessageInfo,
env: Env,
denom: String,
decimals: u32,
sender: Addr,
) -> Result<Response, ContractError> {
assert_owner(deps.storage, &info.sender)?;
Ok(Response::new())
assert_owner(deps.storage, &sender)?;

if TOKENS_COREUM.has(deps.storage, denom.clone()) {
return Err(ContractError::CoreumTokenAlreadyRegistered {
denom: denom.clone(),
});
}

// We generate a random currency creating a Sha256 hash of the denom, the decimals and the current time
let mut hasher = Sha256::new();

hasher
.update(format!("{}{}{}", denom.clone(), decimals, env.block.time.seconds()).into_bytes());

let output = hasher.finalize();

// We encode the hash in base64 and take the first 10 characters
let base64_string = general_purpose::STANDARD_NO_PAD
.encode(output)
.get(0..10)
.unwrap()
.to_string()
.to_lowercase();

let xrpl_currency = format!("{}{}", COREUM_CURRENCY_PREFIX, base64_string);

if XRPL_CURRENCIES.has(deps.storage, xrpl_currency.clone()) {
return Err(ContractError::RegistrationFailure {});
}
XRPL_CURRENCIES.save(deps.storage, xrpl_currency.clone(), &Empty {})?;

let token = TokenCoreum {
denom: denom.clone(),
decimals,
xrpl_currency: xrpl_currency.clone(),
};
TOKENS_COREUM.save(deps.storage, denom.clone(), &token)?;

Ok(Response::new()
.add_attribute("action", ContractActions::RegisterCoreumToken.as_str())
.add_attribute("denom", denom)
.add_attribute("decimals", decimals.to_string())
.add_attribute("xrpl_currency_for_denom", xrpl_currency))
}

// ********** Queries **********
Expand All @@ -127,13 +187,13 @@ pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult<Binary> {
match msg {
QueryMsg::Config {} => to_binary(&query_config(deps)?),
QueryMsg::XrplTokens { offset, limit } => {
to_binary(&query_xprl_tokens(deps, offset, limit)?)
to_binary(&query_xrpl_tokens(deps, offset, limit)?)
}
QueryMsg::CoreumTokens { offset, limit } => {
to_binary(&query_coreum_tokens(deps, offset, limit)?)
}
QueryMsg::XrplToken { issuer, currency } => {
to_binary(&query_xprl_token(deps, issuer, currency)?)
to_binary(&query_xrpl_token(deps, issuer, currency)?)
}
QueryMsg::CoreumToken { denom } => to_binary(&query_coreum_token(deps, denom)?),
QueryMsg::Ownership {} => to_binary(&get_ownership(deps.storage)?),
Expand All @@ -145,7 +205,7 @@ fn query_config(deps: Deps) -> StdResult<Config> {
Ok(config)
}

fn query_xprl_tokens(
fn query_xrpl_tokens(
deps: Deps,
offset: Option<u64>,
limit: Option<u32>,
Expand Down Expand Up @@ -181,9 +241,19 @@ fn query_coreum_tokens(
Ok(CoreumTokensResponse { tokens })
}

fn query_xprl_token(deps: Deps, issuer: String, currency: String) -> StdResult<XrplTokenResponse> {
let mut key = issuer;
key.push_str(&currency);
fn query_xrpl_token(
deps: Deps,
issuer: Option<String>,
currency: Option<String>,
) -> StdResult<XrplTokenResponse> {
let mut key;
if issuer.is_none() && currency.is_none() {
key = XRP_SYMBOL.to_string();
} else {
key = issuer.unwrap();
key.push_str(&currency.unwrap());
}

let token = TOKENS_XRPL.load(deps.storage, key)?;

Ok(XrplTokenResponse { token })
Expand Down
12 changes: 11 additions & 1 deletion contract/src/error.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use cosmwasm_std::StdError;
use cw_ownable::OwnershipError;
use cw_utils::PaymentError;
use thiserror::Error;

#[derive(Error, Debug)]
Expand All @@ -10,9 +11,18 @@ pub enum ContractError {
#[error(transparent)]
Ownership(#[from] OwnershipError),

#[error("Payment error: {0}")]
Payment(#[from] PaymentError),

#[error("Threshold can not be higher than amount of relayers")]
InvalidThreshold {},

#[error("Token {} already registered", denom)]
TokenAlreadyRegistered { denom: String },
CoreumTokenAlreadyRegistered { denom: String },

#[error("Need to send exactly the issue fee amount")]
InvalidIssueFee {},

#[error("Random XRPL currency generated already exists, please try again")]
RegistrationFailure {},
}
10 changes: 4 additions & 6 deletions contract/src/msg.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,17 @@ use crate::state::{Config, TokenCoreum, TokenXRP};

#[cw_serde]
pub struct InstantiateMsg {
pub admin: Addr,
pub owner: Addr,
//Addresses allowed to relay messages
pub relayers: Vec<Addr>,
//How many relayers need to provide evidence for a message.
//How many relayers need to provide evidence for a message
pub evidence_threshold: u32,
//If ticket count goes under this amount, contract will ask for more tickets.
pub min_tickets: u32,
}

#[cw_ownable_execute]
#[cw_serde]
pub enum ExecuteMsg {
RegisterCoreumToken { denom: String },
RegisterCoreumToken { denom: String, decimals: u32 },
}

#[cw_ownable_query]
Expand All @@ -38,7 +36,7 @@ pub enum QueryMsg {
limit: Option<u32>,
},
#[returns(XrplTokenResponse)]
XrplToken { issuer: String, currency: String },
XrplToken { issuer: Option<String>, currency: Option<String> },
#[returns(CoreumTokenResponse)]
CoreumToken { denom: String },
}
Expand Down
22 changes: 17 additions & 5 deletions contract/src/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,8 @@ pub struct Config {

#[cw_serde]
pub struct TokenXRP {
pub issuer: String,
pub currency: String,
pub issuer: Option<String>,
pub currency: Option<String>,
pub coreum_denom: String,
}

Expand All @@ -46,9 +46,21 @@ pub struct TokenCoreum {
pub const CONFIG: Item<Config> = Item::new(TopKey::Config.as_str());
//Tokens registered from Coreum side - key is denom on Coreum chain
pub const TOKENS_COREUM: Map<String, TokenCoreum> = Map::new(TopKey::TokensCoreum.as_str());
//Tokens registered from XPRL side - key is issuer+currency on XPRL
//Tokens registered from XRPL side - key is issuer+currency on XRPL
pub const TOKENS_XRPL: Map<String, TokenXRP> = Map::new(TopKey::TokensXRPL.as_str());
// XRPL-Currencies used
pub const XRPL_CURRENCIES: Map<String, Empty> = Map::new(TopKey::XRPLCurrencies.as_str());
// Coreum denoms used
pub const COREUM_DENOMS: Map<String, Empty> = Map::new(TopKey::CoreumDenoms.as_str());

pub enum ContractActions {
Instantiation,
RegisterCoreumToken,
}

impl ContractActions {
pub fn as_str(&self) -> &'static str {
match self {
ContractActions::Instantiation => "bridge_instantiation",
ContractActions::RegisterCoreumToken => "register_coreum_token",
}
}
}
Loading

0 comments on commit 4259c7c

Please sign in to comment.