terra/token_bridge: transfer native
Change-Id: I53fb27b467c96474f2980d495232ac955187775a
This commit is contained in:
parent
6076f9acc4
commit
a9e98247bc
|
@ -90,6 +90,7 @@ export async function transferFromTerra(
|
||||||
recipientAddress: Uint8Array
|
recipientAddress: Uint8Array
|
||||||
) {
|
) {
|
||||||
const nonce = Math.round(Math.random() * 100000);
|
const nonce = Math.round(Math.random() * 100000);
|
||||||
|
const isNativeAsset = ["uluna"].includes(tokenAddress);
|
||||||
return [
|
return [
|
||||||
new MsgExecuteContract(
|
new MsgExecuteContract(
|
||||||
walletAddress,
|
walletAddress,
|
||||||
|
@ -105,13 +106,41 @@ export async function transferFromTerra(
|
||||||
},
|
},
|
||||||
{ uluna: 10000 }
|
{ uluna: 10000 }
|
||||||
),
|
),
|
||||||
new MsgExecuteContract(
|
isNativeAsset
|
||||||
|
? new MsgExecuteContract(
|
||||||
walletAddress,
|
walletAddress,
|
||||||
tokenBridgeAddress,
|
tokenBridgeAddress,
|
||||||
{
|
{
|
||||||
initiate_transfer: {
|
initiate_transfer: {
|
||||||
asset: tokenAddress,
|
asset: {
|
||||||
amount: amount,
|
amount: amount,
|
||||||
|
info: {
|
||||||
|
native_token: {
|
||||||
|
denom: tokenAddress,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
recipient_chain: recipientChain,
|
||||||
|
recipient: Buffer.from(recipientAddress).toString("base64"),
|
||||||
|
fee: "0",
|
||||||
|
nonce: nonce,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{ uluna: 10000, [tokenAddress]: amount }
|
||||||
|
)
|
||||||
|
: new MsgExecuteContract(
|
||||||
|
walletAddress,
|
||||||
|
tokenBridgeAddress,
|
||||||
|
{
|
||||||
|
initiate_transfer: {
|
||||||
|
asset: {
|
||||||
|
amount: amount,
|
||||||
|
info: {
|
||||||
|
token: {
|
||||||
|
contract_addr: tokenAddress,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
recipient_chain: recipientChain,
|
recipient_chain: recipientChain,
|
||||||
recipient: Buffer.from(recipientAddress).toString("base64"),
|
recipient: Buffer.from(recipientAddress).toString("base64"),
|
||||||
fee: "0",
|
fee: "0",
|
||||||
|
|
|
@ -1,8 +1,10 @@
|
||||||
use crate::msg::WrappedRegistryResponse;
|
use crate::msg::WrappedRegistryResponse;
|
||||||
use cosmwasm_std::{
|
use cosmwasm_std::{
|
||||||
|
coin,
|
||||||
log,
|
log,
|
||||||
to_binary,
|
to_binary,
|
||||||
Api,
|
Api,
|
||||||
|
BankMsg,
|
||||||
Binary,
|
Binary,
|
||||||
CanonicalAddr,
|
CanonicalAddr,
|
||||||
Coin,
|
Coin,
|
||||||
|
@ -33,6 +35,7 @@ use crate::{
|
||||||
bridge_contracts_read,
|
bridge_contracts_read,
|
||||||
config,
|
config,
|
||||||
config_read,
|
config_read,
|
||||||
|
bridge_deposit,
|
||||||
receive_native,
|
receive_native,
|
||||||
send_native,
|
send_native,
|
||||||
wrapped_asset,
|
wrapped_asset,
|
||||||
|
@ -163,7 +166,6 @@ pub fn handle<S: Storage, A: Api, Q: Querier>(
|
||||||
}
|
}
|
||||||
HandleMsg::InitiateTransfer {
|
HandleMsg::InitiateTransfer {
|
||||||
asset,
|
asset,
|
||||||
amount,
|
|
||||||
recipient_chain,
|
recipient_chain,
|
||||||
recipient,
|
recipient,
|
||||||
fee,
|
fee,
|
||||||
|
@ -172,20 +174,76 @@ pub fn handle<S: Storage, A: Api, Q: Querier>(
|
||||||
deps,
|
deps,
|
||||||
env,
|
env,
|
||||||
asset,
|
asset,
|
||||||
amount,
|
|
||||||
recipient_chain,
|
recipient_chain,
|
||||||
recipient.as_slice().to_vec(),
|
recipient.as_slice().to_vec(),
|
||||||
fee,
|
fee,
|
||||||
nonce,
|
nonce,
|
||||||
),
|
),
|
||||||
|
HandleMsg::DepositTokens => deposit_tokens(deps, env),
|
||||||
|
HandleMsg::WithdrawTokens { asset } => withdraw_tokens(deps, env, asset),
|
||||||
HandleMsg::SubmitVaa { data } => submit_vaa(deps, env, &data),
|
HandleMsg::SubmitVaa { data } => submit_vaa(deps, env, &data),
|
||||||
HandleMsg::CreateAssetMeta {
|
HandleMsg::CreateAssetMeta {
|
||||||
asset_address,
|
asset_info,
|
||||||
nonce,
|
nonce,
|
||||||
} => handle_create_asset_meta(deps, env, &asset_address, nonce),
|
} => handle_create_asset_meta(deps, env, asset_info, nonce),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn deposit_tokens<S: Storage, A: Api, Q: Querier>(
|
||||||
|
deps: &mut Extern<S, A, Q>,
|
||||||
|
env: Env,
|
||||||
|
) -> StdResult<HandleResponse> {
|
||||||
|
for fund in env.message.sent_funds {
|
||||||
|
let deposit_key = format!("{}:{}", env.message.sender, fund.denom);
|
||||||
|
bridge_deposit(&mut deps.storage).update(deposit_key.as_bytes(), |amount: Option<Uint128>| {
|
||||||
|
match amount {
|
||||||
|
Some(v) => Ok(v + fund.amount),
|
||||||
|
None => Ok(fund.amount)
|
||||||
|
}
|
||||||
|
})?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(HandleResponse {
|
||||||
|
messages: vec![],
|
||||||
|
log: vec![
|
||||||
|
log("action", "deposit_tokens"),
|
||||||
|
],
|
||||||
|
data: None,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn withdraw_tokens<S: Storage, A: Api, Q: Querier>(
|
||||||
|
deps: &mut Extern<S, A, Q>,
|
||||||
|
env: Env,
|
||||||
|
data: AssetInfo,
|
||||||
|
) -> StdResult<HandleResponse> {
|
||||||
|
let mut messages: Vec<CosmosMsg> = vec![];
|
||||||
|
if let AssetInfo::NativeToken { denom } = data {
|
||||||
|
let deposit_key = format!("{}:{}", env.message.sender, denom);
|
||||||
|
bridge_deposit(&mut deps.storage).update(deposit_key.as_bytes(), |current: Option<Uint128>| {
|
||||||
|
match current {
|
||||||
|
Some(v) => {
|
||||||
|
messages.push(CosmosMsg::Bank(BankMsg::Send {
|
||||||
|
from_address: env.contract.address.clone(),
|
||||||
|
to_address: env.message.sender.clone(),
|
||||||
|
amount: vec![coin(v.u128(), &denom)],
|
||||||
|
}));
|
||||||
|
Ok(Uint128(0))
|
||||||
|
}
|
||||||
|
None => Err(StdError::generic_err("no deposit found to withdraw"))
|
||||||
|
}
|
||||||
|
})?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(HandleResponse {
|
||||||
|
messages: vec![],
|
||||||
|
log: vec![
|
||||||
|
log("action", "withdraw_tokens"),
|
||||||
|
],
|
||||||
|
data: None,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
/// Handle wrapped asset registration messages
|
/// Handle wrapped asset registration messages
|
||||||
fn handle_register_asset<S: Storage, A: Api, Q: Querier>(
|
fn handle_register_asset<S: Storage, A: Api, Q: Querier>(
|
||||||
deps: &mut Extern<S, A, Q>,
|
deps: &mut Extern<S, A, Q>,
|
||||||
|
@ -281,7 +339,29 @@ fn handle_attest_meta<S: Storage, A: Api, Q: Querier>(
|
||||||
fn handle_create_asset_meta<S: Storage, A: Api, Q: Querier>(
|
fn handle_create_asset_meta<S: Storage, A: Api, Q: Querier>(
|
||||||
deps: &mut Extern<S, A, Q>,
|
deps: &mut Extern<S, A, Q>,
|
||||||
env: Env,
|
env: Env,
|
||||||
asset_address: &HumanAddr,
|
asset_info: AssetInfo,
|
||||||
|
nonce: u32,
|
||||||
|
) -> StdResult<HandleResponse> {
|
||||||
|
match asset_info {
|
||||||
|
AssetInfo::Token { contract_addr } => handle_create_asset_meta_token(
|
||||||
|
deps,
|
||||||
|
env,
|
||||||
|
contract_addr,
|
||||||
|
nonce,
|
||||||
|
),
|
||||||
|
AssetInfo::NativeToken { ref denom } => handle_create_asset_meta_native_token(
|
||||||
|
deps,
|
||||||
|
env,
|
||||||
|
denom.clone(),
|
||||||
|
nonce,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_create_asset_meta_token<S: Storage, A: Api, Q: Querier>(
|
||||||
|
deps: &mut Extern<S, A, Q>,
|
||||||
|
env: Env,
|
||||||
|
asset_address: HumanAddr,
|
||||||
nonce: u32,
|
nonce: u32,
|
||||||
) -> StdResult<HandleResponse> {
|
) -> StdResult<HandleResponse> {
|
||||||
let cfg = config_read(&deps.storage).load()?;
|
let cfg = config_read(&deps.storage).load()?;
|
||||||
|
@ -291,7 +371,7 @@ fn handle_create_asset_meta<S: Storage, A: Api, Q: Querier>(
|
||||||
msg: to_binary(&TokenQuery::TokenInfo {})?,
|
msg: to_binary(&TokenQuery::TokenInfo {})?,
|
||||||
});
|
});
|
||||||
|
|
||||||
let asset_canonical = deps.api.canonical_address(asset_address)?;
|
let asset_canonical = deps.api.canonical_address(&asset_address)?;
|
||||||
let token_info: TokenInfoResponse = deps.querier.query(&request)?;
|
let token_info: TokenInfoResponse = deps.querier.query(&request)?;
|
||||||
|
|
||||||
let meta: AssetMeta = AssetMeta {
|
let meta: AssetMeta = AssetMeta {
|
||||||
|
@ -327,6 +407,63 @@ fn handle_create_asset_meta<S: Storage, A: Api, Q: Querier>(
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// All ISO-4217 currency codes are 3 letters, so we can safely slice anything that is not ULUNA.
|
||||||
|
/// https://www.xe.com/iso4217.php
|
||||||
|
fn format_native_denom_symbol(denom: &str) -> String {
|
||||||
|
if denom == "uluna" {
|
||||||
|
return "LUNA".to_string();
|
||||||
|
}
|
||||||
|
// UUSD -> US -> UST
|
||||||
|
denom.to_uppercase()[1..3].to_string() + "T"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_create_asset_meta_native_token<S: Storage, A: Api, Q: Querier>(
|
||||||
|
deps: &mut Extern<S, A, Q>,
|
||||||
|
env: Env,
|
||||||
|
denom: String,
|
||||||
|
nonce: u32,
|
||||||
|
) -> StdResult<HandleResponse> {
|
||||||
|
let cfg = config_read(&deps.storage).load()?;
|
||||||
|
|
||||||
|
let mut asset_id = extend_address_to_32(&build_native_id(&denom).into());
|
||||||
|
asset_id[0] = 1;
|
||||||
|
|
||||||
|
let symbol = format_native_denom_symbol(&denom);
|
||||||
|
|
||||||
|
let meta: AssetMeta = AssetMeta {
|
||||||
|
token_chain: CHAIN_ID,
|
||||||
|
token_address: asset_id.clone(),
|
||||||
|
decimals: 6,
|
||||||
|
symbol: extend_string_to_32(&symbol)?,
|
||||||
|
name: extend_string_to_32(&symbol)?,
|
||||||
|
};
|
||||||
|
|
||||||
|
let token_bridge_message = TokenBridgeMessage {
|
||||||
|
action: Action::ATTEST_META,
|
||||||
|
payload: meta.serialize().to_vec(),
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(HandleResponse {
|
||||||
|
messages: vec![CosmosMsg::Wasm(WasmMsg::Execute {
|
||||||
|
contract_addr: cfg.wormhole_contract,
|
||||||
|
msg: to_binary(&WormholeHandleMsg::PostMessage {
|
||||||
|
message: Binary::from(token_bridge_message.serialize()),
|
||||||
|
nonce,
|
||||||
|
})?,
|
||||||
|
// forward coins sent to this message
|
||||||
|
send: coins_after_tax(deps, env.message.sent_funds.clone())?,
|
||||||
|
})],
|
||||||
|
log: vec![
|
||||||
|
log("meta.token_chain", CHAIN_ID),
|
||||||
|
log("meta.symbol", symbol),
|
||||||
|
log("meta.asset_id", hex::encode(asset_id)),
|
||||||
|
log("meta.nonce", nonce),
|
||||||
|
log("meta.block_time", env.block.time),
|
||||||
|
],
|
||||||
|
data: None,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
fn submit_vaa<S: Storage, A: Api, Q: Querier>(
|
fn submit_vaa<S: Storage, A: Api, Q: Querier>(
|
||||||
deps: &mut Extern<S, A, Q>,
|
deps: &mut Extern<S, A, Q>,
|
||||||
env: Env,
|
env: Env,
|
||||||
|
@ -395,7 +532,7 @@ fn handle_governance_payload<S: Storage, A: Api, Q: Querier>(
|
||||||
|
|
||||||
fn handle_register_chain<S: Storage, A: Api, Q: Querier>(
|
fn handle_register_chain<S: Storage, A: Api, Q: Querier>(
|
||||||
deps: &mut Extern<S, A, Q>,
|
deps: &mut Extern<S, A, Q>,
|
||||||
env: Env,
|
_env: Env,
|
||||||
data: &Vec<u8>,
|
data: &Vec<u8>,
|
||||||
) -> StdResult<HandleResponse> {
|
) -> StdResult<HandleResponse> {
|
||||||
let RegisterChain {
|
let RegisterChain {
|
||||||
|
@ -432,6 +569,23 @@ fn handle_complete_transfer<S: Storage, A: Api, Q: Querier>(
|
||||||
) -> StdResult<HandleResponse> {
|
) -> StdResult<HandleResponse> {
|
||||||
let transfer_info = TransferInfo::deserialize(&data)?;
|
let transfer_info = TransferInfo::deserialize(&data)?;
|
||||||
|
|
||||||
|
// All terra token addresses are 20 bytes, and so start with 12 0's, if the address begins with
|
||||||
|
// a 1 we can identify it as a fully native token.
|
||||||
|
match transfer_info.token_address.as_slice()[0] {
|
||||||
|
1 => handle_complete_transfer_token_native(deps, env, emitter_chain, emitter_address, data),
|
||||||
|
_ => handle_complete_transfer_token(deps, env, emitter_chain, emitter_address, data),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_complete_transfer_token<S: Storage, A: Api, Q: Querier>(
|
||||||
|
deps: &mut Extern<S, A, Q>,
|
||||||
|
env: Env,
|
||||||
|
emitter_chain: u16,
|
||||||
|
emitter_address: Vec<u8>,
|
||||||
|
data: &Vec<u8>,
|
||||||
|
) -> StdResult<HandleResponse> {
|
||||||
|
let transfer_info = TransferInfo::deserialize(&data)?;
|
||||||
|
|
||||||
let expected_contract =
|
let expected_contract =
|
||||||
bridge_contracts_read(&deps.storage).load(&emitter_chain.to_be_bytes())?;
|
bridge_contracts_read(&deps.storage).load(&emitter_chain.to_be_bytes())?;
|
||||||
|
|
||||||
|
@ -439,15 +593,14 @@ fn handle_complete_transfer<S: Storage, A: Api, Q: Querier>(
|
||||||
if expected_contract != emitter_address {
|
if expected_contract != emitter_address {
|
||||||
return Err(StdError::unauthorized());
|
return Err(StdError::unauthorized());
|
||||||
}
|
}
|
||||||
|
|
||||||
if transfer_info.recipient_chain != CHAIN_ID {
|
if transfer_info.recipient_chain != CHAIN_ID {
|
||||||
return Err(StdError::generic_err(
|
return Err(StdError::generic_err(
|
||||||
"this transfer is not directed at this chain",
|
"this transfer is not directed at this chain",
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
let token_chain = transfer_info.token_chain;
|
|
||||||
let target_address = (&transfer_info.recipient.as_slice()).get_address(0);
|
let target_address = (&transfer_info.recipient.as_slice()).get_address(0);
|
||||||
|
let token_chain = transfer_info.token_chain;
|
||||||
|
|
||||||
let (not_supported_amount, mut amount) = transfer_info.amount;
|
let (not_supported_amount, mut amount) = transfer_info.amount;
|
||||||
let (not_supported_fee, mut fee) = transfer_info.fee;
|
let (not_supported_fee, mut fee) = transfer_info.fee;
|
||||||
|
@ -551,7 +704,7 @@ fn handle_complete_transfer<S: Storage, A: Api, Q: Querier>(
|
||||||
Ok(HandleResponse {
|
Ok(HandleResponse {
|
||||||
messages,
|
messages,
|
||||||
log: vec![
|
log: vec![
|
||||||
log("action", "complete_transfer_native"),
|
log("action", "complete_transfer_token"),
|
||||||
log("recipient", recipient),
|
log("recipient", recipient),
|
||||||
log("contract", contract_addr),
|
log("contract", contract_addr),
|
||||||
log("amount", amount),
|
log("amount", amount),
|
||||||
|
@ -561,7 +714,117 @@ fn handle_complete_transfer<S: Storage, A: Api, Q: Querier>(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn handle_complete_transfer_token_native<S: Storage, A: Api, Q: Querier>(
|
||||||
|
deps: &mut Extern<S, A, Q>,
|
||||||
|
env: Env,
|
||||||
|
emitter_chain: u16,
|
||||||
|
emitter_address: Vec<u8>,
|
||||||
|
data: &Vec<u8>,
|
||||||
|
) -> StdResult<HandleResponse> {
|
||||||
|
let transfer_info = TransferInfo::deserialize(&data)?;
|
||||||
|
|
||||||
|
let expected_contract =
|
||||||
|
bridge_contracts_read(&deps.storage).load(&emitter_chain.to_be_bytes())?;
|
||||||
|
|
||||||
|
// must be sent by a registered token bridge contract
|
||||||
|
if expected_contract != emitter_address {
|
||||||
|
return Err(StdError::unauthorized());
|
||||||
|
}
|
||||||
|
if transfer_info.recipient_chain != CHAIN_ID {
|
||||||
|
return Err(StdError::generic_err(
|
||||||
|
"this transfer is not directed at this chain",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
let target_address = (&transfer_info.recipient.as_slice()).get_address(0);
|
||||||
|
|
||||||
|
let (not_supported_amount, mut amount) = transfer_info.amount;
|
||||||
|
let (not_supported_fee, fee) = transfer_info.fee;
|
||||||
|
|
||||||
|
amount = amount.checked_sub(fee).unwrap();
|
||||||
|
|
||||||
|
// Check high 128 bit of amount value to be empty
|
||||||
|
if not_supported_amount != 0 || not_supported_fee != 0 {
|
||||||
|
return ContractError::AmountTooHigh.std_err();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wipe the native byte marker and extract the serialized denom.
|
||||||
|
let mut token_address = transfer_info.token_address.clone();
|
||||||
|
let token_address = token_address.as_mut_slice();
|
||||||
|
token_address[0] = 0;
|
||||||
|
|
||||||
|
let mut denom = token_address.to_vec();
|
||||||
|
denom.retain(|&c| c != 0);
|
||||||
|
let denom = String::from_utf8(denom).unwrap();
|
||||||
|
|
||||||
|
// note -- here the amount is the amount the recipient will receive;
|
||||||
|
// amount + fee is the total sent
|
||||||
|
let recipient = deps.api.human_address(&target_address)?;
|
||||||
|
let token_address = (&*token_address).get_address(0);
|
||||||
|
receive_native(&mut deps.storage, &token_address, Uint128(amount + fee))?;
|
||||||
|
|
||||||
|
let mut messages = vec![CosmosMsg::Bank(BankMsg::Send {
|
||||||
|
from_address: env.contract.address.clone(),
|
||||||
|
to_address: recipient.clone(),
|
||||||
|
amount: vec![coin(amount, &denom)],
|
||||||
|
})];
|
||||||
|
|
||||||
|
if fee != 0 {
|
||||||
|
messages.push(CosmosMsg::Bank(BankMsg::Send {
|
||||||
|
from_address: env.contract.address.clone(),
|
||||||
|
to_address: recipient.clone(),
|
||||||
|
amount: vec![coin(fee, &denom)],
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(HandleResponse {
|
||||||
|
messages,
|
||||||
|
log: vec![
|
||||||
|
log("action", "complete_transfer_token_native"),
|
||||||
|
log("recipient", recipient),
|
||||||
|
log("denom", denom),
|
||||||
|
log("amount", amount),
|
||||||
|
],
|
||||||
|
data: None,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
fn handle_initiate_transfer<S: Storage, A: Api, Q: Querier>(
|
fn handle_initiate_transfer<S: Storage, A: Api, Q: Querier>(
|
||||||
|
deps: &mut Extern<S, A, Q>,
|
||||||
|
env: Env,
|
||||||
|
asset: Asset,
|
||||||
|
recipient_chain: u16,
|
||||||
|
recipient: Vec<u8>,
|
||||||
|
fee: Uint128,
|
||||||
|
nonce: u32,
|
||||||
|
) -> StdResult<HandleResponse> {
|
||||||
|
match asset.info {
|
||||||
|
AssetInfo::Token { contract_addr } => handle_initiate_transfer_token(
|
||||||
|
deps,
|
||||||
|
env,
|
||||||
|
contract_addr,
|
||||||
|
asset.amount,
|
||||||
|
recipient_chain,
|
||||||
|
recipient,
|
||||||
|
fee,
|
||||||
|
nonce,
|
||||||
|
),
|
||||||
|
AssetInfo::NativeToken { ref denom } => {
|
||||||
|
handle_initiate_transfer_native_token(
|
||||||
|
deps,
|
||||||
|
env,
|
||||||
|
denom.clone(),
|
||||||
|
asset.amount,
|
||||||
|
recipient_chain,
|
||||||
|
recipient,
|
||||||
|
fee,
|
||||||
|
nonce,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_initiate_transfer_token<S: Storage, A: Api, Q: Querier>(
|
||||||
deps: &mut Extern<S, A, Q>,
|
deps: &mut Extern<S, A, Q>,
|
||||||
env: Env,
|
env: Env,
|
||||||
asset: HumanAddr,
|
asset: HumanAddr,
|
||||||
|
@ -574,11 +837,9 @@ fn handle_initiate_transfer<S: Storage, A: Api, Q: Querier>(
|
||||||
if recipient_chain == CHAIN_ID {
|
if recipient_chain == CHAIN_ID {
|
||||||
return ContractError::SameSourceAndTarget.std_err();
|
return ContractError::SameSourceAndTarget.std_err();
|
||||||
}
|
}
|
||||||
|
|
||||||
if amount.is_zero() {
|
if amount.is_zero() {
|
||||||
return ContractError::AmountTooLow.std_err();
|
return ContractError::AmountTooLow.std_err();
|
||||||
}
|
}
|
||||||
|
|
||||||
if fee > amount {
|
if fee > amount {
|
||||||
return Err(StdError::generic_err("fee greater than sent amount"));
|
return Err(StdError::generic_err("fee greater than sent amount"));
|
||||||
}
|
}
|
||||||
|
@ -700,6 +961,90 @@ fn handle_initiate_transfer<S: Storage, A: Api, Q: Querier>(
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn handle_initiate_transfer_native_token<S: Storage, A: Api, Q: Querier>(
|
||||||
|
deps: &mut Extern<S, A, Q>,
|
||||||
|
env: Env,
|
||||||
|
denom: String,
|
||||||
|
amount: Uint128,
|
||||||
|
recipient_chain: u16,
|
||||||
|
recipient: Vec<u8>,
|
||||||
|
fee: Uint128,
|
||||||
|
nonce: u32,
|
||||||
|
) -> StdResult<HandleResponse> {
|
||||||
|
if recipient_chain == CHAIN_ID {
|
||||||
|
return ContractError::SameSourceAndTarget.std_err();
|
||||||
|
}
|
||||||
|
if amount.is_zero() {
|
||||||
|
return ContractError::AmountTooLow.std_err();
|
||||||
|
}
|
||||||
|
if fee > amount {
|
||||||
|
return Err(StdError::generic_err("fee greater than sent amount"));
|
||||||
|
}
|
||||||
|
|
||||||
|
let deposit_key = format!("{}:{}", env.message.sender, denom);
|
||||||
|
bridge_deposit(&mut deps.storage).update(deposit_key.as_bytes(), |current: Option<Uint128>| {
|
||||||
|
match current {
|
||||||
|
Some(v) => Ok((v - amount)?),
|
||||||
|
None => Err(StdError::generic_err("no deposit found to transfer"))
|
||||||
|
}
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let cfg: ConfigInfo = config_read(&deps.storage).load()?;
|
||||||
|
let mut messages: Vec<CosmosMsg> = vec![];
|
||||||
|
|
||||||
|
let asset_chain: u16 = CHAIN_ID;
|
||||||
|
let mut asset_address: Vec<u8> = build_native_id(&denom);
|
||||||
|
|
||||||
|
send_native(&mut deps.storage, &asset_address[..].into(), amount)?;
|
||||||
|
|
||||||
|
// Mark the first byte of the address to distinguish it as native.
|
||||||
|
asset_address = extend_address_to_32(&asset_address.into());
|
||||||
|
asset_address[0] = 1;
|
||||||
|
|
||||||
|
let transfer_info = TransferInfo {
|
||||||
|
token_chain: asset_chain,
|
||||||
|
token_address: asset_address.to_vec(),
|
||||||
|
amount: (0, amount.u128()),
|
||||||
|
recipient_chain,
|
||||||
|
recipient: recipient.clone(),
|
||||||
|
fee: (0, fee.u128()),
|
||||||
|
};
|
||||||
|
|
||||||
|
let token_bridge_message = TokenBridgeMessage {
|
||||||
|
action: Action::TRANSFER,
|
||||||
|
payload: transfer_info.serialize(),
|
||||||
|
};
|
||||||
|
|
||||||
|
messages.push(CosmosMsg::Wasm(WasmMsg::Execute {
|
||||||
|
contract_addr: cfg.wormhole_contract,
|
||||||
|
msg: to_binary(&WormholeHandleMsg::PostMessage {
|
||||||
|
message: Binary::from(token_bridge_message.serialize()),
|
||||||
|
nonce,
|
||||||
|
})?,
|
||||||
|
send: coins_after_tax(deps, env.message.sent_funds.clone())?,
|
||||||
|
}));
|
||||||
|
|
||||||
|
Ok(HandleResponse {
|
||||||
|
messages,
|
||||||
|
log: vec![
|
||||||
|
log("transfer.token_chain", asset_chain),
|
||||||
|
log("transfer.token", hex::encode(asset_address)),
|
||||||
|
log(
|
||||||
|
"transfer.sender",
|
||||||
|
hex::encode(extend_address_to_32(
|
||||||
|
&deps.api.canonical_address(&env.message.sender)?,
|
||||||
|
)),
|
||||||
|
),
|
||||||
|
log("transfer.recipient_chain", recipient_chain),
|
||||||
|
log("transfer.recipient", hex::encode(recipient)),
|
||||||
|
log("transfer.amount", amount),
|
||||||
|
log("transfer.nonce", nonce),
|
||||||
|
log("transfer.block_time", env.block.time),
|
||||||
|
],
|
||||||
|
data: None,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
pub fn query<S: Storage, A: Api, Q: Querier>(
|
pub fn query<S: Storage, A: Api, Q: Querier>(
|
||||||
deps: &Extern<S, A, Q>,
|
deps: &Extern<S, A, Q>,
|
||||||
msg: QueryMsg,
|
msg: QueryMsg,
|
||||||
|
@ -734,6 +1079,16 @@ fn build_asset_id(chain: u16, address: &[u8]) -> Vec<u8> {
|
||||||
hasher.finalize().to_vec()
|
hasher.finalize().to_vec()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Produce a 20 byte asset "address" from a native terra denom.
|
||||||
|
fn build_native_id(denom: &str) -> Vec<u8> {
|
||||||
|
let mut asset_address: Vec<u8> = denom.clone().as_bytes().to_vec();
|
||||||
|
asset_address.reverse();
|
||||||
|
asset_address.extend(vec![0u8; 20 - denom.len()]);
|
||||||
|
asset_address.reverse();
|
||||||
|
assert_eq!(asset_address.len(), 20);
|
||||||
|
asset_address
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use cosmwasm_std::{
|
use cosmwasm_std::{
|
||||||
|
|
|
@ -1,9 +1,6 @@
|
||||||
use cosmwasm_std::{
|
use cosmwasm_std::{Binary, HumanAddr, Uint128};
|
||||||
Binary,
|
|
||||||
HumanAddr,
|
|
||||||
Uint128,
|
|
||||||
};
|
|
||||||
use schemars::JsonSchema;
|
use schemars::JsonSchema;
|
||||||
|
use terraswap::asset::{Asset, AssetInfo};
|
||||||
use serde::{
|
use serde::{
|
||||||
Deserialize,
|
Deserialize,
|
||||||
Serialize,
|
Serialize,
|
||||||
|
@ -26,9 +23,13 @@ pub enum HandleMsg {
|
||||||
asset_id: Binary,
|
asset_id: Binary,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
DepositTokens,
|
||||||
|
WithdrawTokens {
|
||||||
|
asset: AssetInfo,
|
||||||
|
},
|
||||||
|
|
||||||
InitiateTransfer {
|
InitiateTransfer {
|
||||||
asset: HumanAddr,
|
asset: Asset,
|
||||||
amount: Uint128,
|
|
||||||
recipient_chain: u16,
|
recipient_chain: u16,
|
||||||
recipient: Binary,
|
recipient: Binary,
|
||||||
fee: Uint128,
|
fee: Uint128,
|
||||||
|
@ -40,7 +41,7 @@ pub enum HandleMsg {
|
||||||
},
|
},
|
||||||
|
|
||||||
CreateAssetMeta {
|
CreateAssetMeta {
|
||||||
asset_address: HumanAddr,
|
asset_info: AssetInfo,
|
||||||
nonce: u32,
|
nonce: u32,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,6 +29,7 @@ pub static CONFIG_KEY: &[u8] = b"config";
|
||||||
pub static WRAPPED_ASSET_KEY: &[u8] = b"wrapped_asset";
|
pub static WRAPPED_ASSET_KEY: &[u8] = b"wrapped_asset";
|
||||||
pub static WRAPPED_ASSET_ADDRESS_KEY: &[u8] = b"wrapped_asset_address";
|
pub static WRAPPED_ASSET_ADDRESS_KEY: &[u8] = b"wrapped_asset_address";
|
||||||
pub static BRIDGE_CONTRACTS: &[u8] = b"bridge_contracts";
|
pub static BRIDGE_CONTRACTS: &[u8] = b"bridge_contracts";
|
||||||
|
pub static BRIDGE_DEPOSITS: &[u8] = b"bridge_deposits";
|
||||||
pub static NATIVE_COUNTER: &[u8] = b"native_counter";
|
pub static NATIVE_COUNTER: &[u8] = b"native_counter";
|
||||||
|
|
||||||
// Guardian set information
|
// Guardian set information
|
||||||
|
@ -50,6 +51,14 @@ pub fn config_read<S: Storage>(storage: &S) -> ReadonlySingleton<S, ConfigInfo>
|
||||||
singleton_read(storage, CONFIG_KEY)
|
singleton_read(storage, CONFIG_KEY)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn bridge_deposit<S: Storage>(storage: &mut S) -> Bucket<S, Uint128> {
|
||||||
|
bucket(BRIDGE_DEPOSITS, storage)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn bridge_deposit_read<S: Storage>(storage: &S) -> ReadonlyBucket<S, Uint128> {
|
||||||
|
bucket_read(BRIDGE_DEPOSITS, storage)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn bridge_contracts<S: Storage>(storage: &mut S) -> Bucket<S, Vec<u8>> {
|
pub fn bridge_contracts<S: Storage>(storage: &mut S) -> Bucket<S, Vec<u8>> {
|
||||||
bucket(BRIDGE_CONTRACTS, storage)
|
bucket(BRIDGE_CONTRACTS, storage)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue