2022-09-30 00:01:09 -07:00
|
|
|
use cw20::{BalanceResponse, TokenInfoResponse};
|
|
|
|
use cw20_base::msg::{ExecuteMsg as TokenMsg, QueryMsg as TokenQuery};
|
2022-06-07 08:36:21 -07:00
|
|
|
use cw20_wrapped_2::msg::{
|
2022-09-30 00:01:09 -07:00
|
|
|
ExecuteMsg as WrappedMsg, InitHook, InstantiateMsg as WrappedInit, QueryMsg as WrappedQuery,
|
2022-06-16 09:48:01 -07:00
|
|
|
WrappedAssetInfoResponse,
|
|
|
|
};
|
|
|
|
use std::{
|
2022-09-30 00:01:09 -07:00
|
|
|
cmp::{max, min},
|
2022-06-16 09:48:01 -07:00
|
|
|
str::FromStr,
|
|
|
|
};
|
2022-09-30 00:01:09 -07:00
|
|
|
use terraswap::asset::{Asset, AssetInfo};
|
2022-06-16 09:48:01 -07:00
|
|
|
|
|
|
|
use wormhole::{
|
|
|
|
byte_utils::{
|
2022-09-30 00:01:09 -07:00
|
|
|
extend_address_to_32, extend_address_to_32_array, extend_string_to_32, get_string_from_32,
|
2022-06-16 09:48:01 -07:00
|
|
|
ByteUtils,
|
|
|
|
},
|
|
|
|
error::ContractError,
|
2022-09-30 00:01:09 -07:00
|
|
|
msg::{ExecuteMsg as WormholeExecuteMsg, QueryMsg as WormholeQueryMsg},
|
|
|
|
state::{vaa_archive_add, vaa_archive_check, GovernancePacket, ParsedVAA},
|
2022-06-16 09:48:01 -07:00
|
|
|
};
|
|
|
|
|
2022-06-07 08:36:21 -07:00
|
|
|
#[allow(unused_imports)]
|
|
|
|
use cosmwasm_std::entry_point;
|
|
|
|
|
2022-06-16 09:48:01 -07:00
|
|
|
use cosmwasm_std::{
|
2022-09-30 00:01:09 -07:00
|
|
|
coin, to_binary, BankMsg, Binary, CanonicalAddr, CosmosMsg, Deps, DepsMut, Empty, Env,
|
|
|
|
MessageInfo, QueryRequest, Reply, Response, StdError, StdResult, SubMsg, Uint128, WasmMsg,
|
2022-06-16 09:48:01 -07:00
|
|
|
WasmQuery,
|
|
|
|
};
|
|
|
|
|
|
|
|
use crate::{
|
|
|
|
msg::{
|
2022-09-30 00:01:09 -07:00
|
|
|
ExecuteMsg, ExternalIdResponse, InstantiateMsg, IsVaaRedeemedResponse, MigrateMsg,
|
|
|
|
QueryMsg, TransferInfoResponse, WrappedRegistryResponse,
|
2022-06-16 09:48:01 -07:00
|
|
|
},
|
|
|
|
state::{
|
2022-09-30 00:01:09 -07:00
|
|
|
bridge_contracts, bridge_contracts_read, bridge_deposit, config, config_read,
|
|
|
|
is_wrapped_asset, is_wrapped_asset_read, receive_native, send_native, wrapped_asset,
|
|
|
|
wrapped_asset_read, wrapped_asset_seq, wrapped_asset_seq_read, wrapped_transfer_tmp,
|
|
|
|
Action, AssetMeta, ConfigInfo, RegisterChain, TokenBridgeMessage, TransferInfo,
|
|
|
|
TransferState, TransferWithPayloadInfo, UpgradeContract,
|
2022-06-07 08:36:21 -07:00
|
|
|
},
|
2022-09-30 00:01:09 -07:00
|
|
|
token_address::{ContractId, ExternalTokenId, TokenId, WrappedCW20},
|
2022-06-16 09:48:01 -07:00
|
|
|
};
|
|
|
|
|
|
|
|
type HumanAddr = String;
|
|
|
|
|
|
|
|
pub enum TransferType<A> {
|
|
|
|
WithoutPayload,
|
|
|
|
WithPayload { payload: A },
|
|
|
|
}
|
|
|
|
|
2022-07-18 14:24:13 -07:00
|
|
|
/// Migration code that runs the next time the contract is upgraded.
|
|
|
|
/// This function will contain ephemeral code that we want to run once, and thus
|
|
|
|
/// can (and should be) safely deleted after the upgrade happened successfully.
|
|
|
|
///
|
|
|
|
/// Most upgrades won't require any special migration logic. In those cases,
|
|
|
|
/// this function can safely be implemented as:
|
|
|
|
/// ```ignore
|
|
|
|
/// Ok(Response::default())
|
|
|
|
/// ```
|
2022-06-16 09:48:01 -07:00
|
|
|
#[cfg_attr(not(feature = "library"), entry_point)]
|
2022-10-12 13:41:36 -07:00
|
|
|
pub fn migrate(_deps: DepsMut, _env: Env, _msg: MigrateMsg) -> StdResult<Response> {
|
|
|
|
// // This migration adds a new field to the [`ConfigInfo`] struct. The
|
|
|
|
// // state stored on chain has the old version, so we first parse it as
|
|
|
|
// // [`ConfigInfoLegacy`], then add the new fields, and write it back as [`ConfigInfo`].
|
|
|
|
// // Since the only place the contract with the legacy state is deployed is
|
|
|
|
// // terra2, we just hardcode the new value here for that chain.
|
2022-10-11 19:17:32 -07:00
|
|
|
|
2022-10-12 13:41:36 -07:00
|
|
|
// // 1. make sure this contract doesn't already have the new ConfigInfo struct
|
|
|
|
// // in storage. Note that this check is not strictly necessary, as the
|
|
|
|
// // upgrade will only be issued for terra2, and no new chains. However, it is
|
|
|
|
// // good practice to ensure that migration code cannot be run twice, which
|
|
|
|
// // this check achieves.
|
|
|
|
// if config_read(deps.storage).load().is_ok() {
|
|
|
|
// return Err(StdError::generic_err(
|
|
|
|
// "Can't migrate; this contract already has a new ConfigInfo struct",
|
|
|
|
// ));
|
|
|
|
// }
|
2022-10-11 19:17:32 -07:00
|
|
|
|
2022-10-12 13:41:36 -07:00
|
|
|
// // 2. parse old state
|
|
|
|
// let ConfigInfoLegacy {
|
|
|
|
// gov_chain,
|
|
|
|
// gov_address,
|
|
|
|
// wormhole_contract,
|
|
|
|
// wrapped_asset_code_id,
|
|
|
|
// } = config_read_legacy(deps.storage).load()?;
|
2022-09-30 00:01:09 -07:00
|
|
|
|
2022-10-12 13:41:36 -07:00
|
|
|
// // 3. store new state with terra2 values hardcoded
|
|
|
|
// let chain_id = 18;
|
|
|
|
// let native_denom = "uluna".to_string();
|
|
|
|
// let native_symbol = "LUNA".to_string();
|
|
|
|
// let native_decimals = 6;
|
2022-10-11 19:17:32 -07:00
|
|
|
|
2022-10-12 13:41:36 -07:00
|
|
|
// let config_info = ConfigInfo {
|
|
|
|
// gov_chain,
|
|
|
|
// gov_address,
|
|
|
|
// wormhole_contract,
|
|
|
|
// wrapped_asset_code_id,
|
|
|
|
// chain_id,
|
|
|
|
// native_denom,
|
|
|
|
// native_symbol,
|
|
|
|
// native_decimals
|
|
|
|
// };
|
2022-10-11 19:17:32 -07:00
|
|
|
|
2022-10-12 13:41:36 -07:00
|
|
|
// config(deps.storage).save(&config_info)?;
|
2022-07-18 14:24:13 -07:00
|
|
|
Ok(Response::default())
|
2022-06-16 09:48:01 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
#[cfg_attr(not(feature = "library"), entry_point)]
|
|
|
|
pub fn instantiate(
|
|
|
|
deps: DepsMut,
|
|
|
|
_env: Env,
|
|
|
|
_info: MessageInfo,
|
|
|
|
msg: InstantiateMsg,
|
|
|
|
) -> StdResult<Response> {
|
|
|
|
// Save general wormhole info
|
|
|
|
let state = ConfigInfo {
|
|
|
|
gov_chain: msg.gov_chain,
|
|
|
|
gov_address: msg.gov_address.into(),
|
|
|
|
wormhole_contract: msg.wormhole_contract,
|
|
|
|
wrapped_asset_code_id: msg.wrapped_asset_code_id,
|
2022-07-18 14:24:13 -07:00
|
|
|
chain_id: msg.chain_id,
|
2022-10-11 19:17:32 -07:00
|
|
|
native_denom: msg.native_denom,
|
|
|
|
native_symbol: msg.native_symbol,
|
|
|
|
native_decimals: msg.native_decimals,
|
2022-06-16 09:48:01 -07:00
|
|
|
};
|
|
|
|
config(deps.storage).save(&state)?;
|
|
|
|
|
|
|
|
Ok(Response::default())
|
|
|
|
}
|
|
|
|
|
|
|
|
// When CW20 transfers complete, we need to verify the actual amount that is being transferred out
|
|
|
|
// of the bridge. This is to handle fee tokens where the amount expected to be transferred may be
|
|
|
|
// less due to burns, fees, etc.
|
|
|
|
#[cfg_attr(not(feature = "library"), entry_point)]
|
|
|
|
pub fn reply(deps: DepsMut, env: Env, _msg: Reply) -> StdResult<Response> {
|
|
|
|
let cfg = config_read(deps.storage).load()?;
|
|
|
|
|
|
|
|
let state = wrapped_transfer_tmp(deps.storage).load()?;
|
|
|
|
// NOTE: Reentrancy protection. See note in `handle_initiate_transfer_token`
|
|
|
|
// for why this is necessary.
|
|
|
|
wrapped_transfer_tmp(deps.storage).remove();
|
|
|
|
|
|
|
|
let token_bridge_message = TokenBridgeMessage::deserialize(&state.message)?;
|
|
|
|
|
|
|
|
let (mut transfer_info, transfer_type) = match token_bridge_message.action {
|
|
|
|
Action::TRANSFER => {
|
|
|
|
let info = TransferInfo::deserialize(&token_bridge_message.payload)?;
|
|
|
|
Ok((info, TransferType::WithoutPayload))
|
|
|
|
}
|
|
|
|
Action::TRANSFER_WITH_PAYLOAD => {
|
|
|
|
let info = TransferWithPayloadInfo::deserialize(&token_bridge_message.payload)?;
|
|
|
|
Ok((
|
2022-06-21 14:30:33 -07:00
|
|
|
info.as_transfer_info(),
|
2022-06-16 09:48:01 -07:00
|
|
|
TransferType::WithPayload {
|
2022-06-21 14:30:33 -07:00
|
|
|
// put both the payload and sender_address into the payload
|
|
|
|
// field here (which we can do, since [`TransferType`] is
|
|
|
|
// parametric)
|
|
|
|
payload: (info.payload, info.sender_address),
|
2022-06-16 09:48:01 -07:00
|
|
|
},
|
|
|
|
))
|
|
|
|
}
|
|
|
|
_ => Err(StdError::generic_err("Unreachable")),
|
|
|
|
}?;
|
|
|
|
|
|
|
|
// Fetch CW20 Balance post-transfer.
|
|
|
|
let new_balance: BalanceResponse =
|
|
|
|
deps.querier.query(&QueryRequest::Wasm(WasmQuery::Smart {
|
2022-06-07 08:36:21 -07:00
|
|
|
contract_addr: state.token_address.to_string(),
|
2022-06-16 09:48:01 -07:00
|
|
|
msg: to_binary(&TokenQuery::Balance {
|
|
|
|
address: env.contract.address.to_string(),
|
|
|
|
})?,
|
|
|
|
}))?;
|
|
|
|
|
|
|
|
// Actual amount should be the difference in balance of the CW20 account in question to account
|
|
|
|
// for fee tokens.
|
|
|
|
let multiplier = Uint128::from_str(&state.multiplier)?;
|
|
|
|
let real_amount = new_balance.balance - Uint128::from_str(&state.previous_balance)?;
|
|
|
|
let real_amount = real_amount / multiplier;
|
|
|
|
|
|
|
|
// If the fee is too large the user would receive nothing.
|
|
|
|
if transfer_info.fee.1 > real_amount.u128() {
|
|
|
|
return Err(StdError::generic_err("fee greater than sent amount"));
|
|
|
|
}
|
|
|
|
|
|
|
|
// Update Wormhole message to correct amount.
|
|
|
|
transfer_info.amount.1 = real_amount.u128();
|
|
|
|
|
|
|
|
let token_bridge_message = match transfer_type {
|
|
|
|
TransferType::WithoutPayload => TokenBridgeMessage {
|
|
|
|
action: Action::TRANSFER,
|
|
|
|
payload: transfer_info.serialize(),
|
|
|
|
},
|
|
|
|
TransferType::WithPayload { payload } => TokenBridgeMessage {
|
|
|
|
action: Action::TRANSFER_WITH_PAYLOAD,
|
|
|
|
payload: TransferWithPayloadInfo {
|
2022-06-21 14:30:33 -07:00
|
|
|
amount: transfer_info.amount,
|
|
|
|
token_address: transfer_info.token_address,
|
|
|
|
token_chain: transfer_info.token_chain,
|
|
|
|
recipient: transfer_info.recipient,
|
|
|
|
recipient_chain: transfer_info.recipient_chain,
|
|
|
|
sender_address: payload.1,
|
|
|
|
payload: payload.0,
|
2022-06-16 09:48:01 -07:00
|
|
|
}
|
|
|
|
.serialize(),
|
|
|
|
},
|
|
|
|
};
|
|
|
|
|
|
|
|
// Post Wormhole Message
|
|
|
|
let message = CosmosMsg::Wasm(WasmMsg::Execute {
|
|
|
|
contract_addr: cfg.wormhole_contract,
|
|
|
|
funds: vec![],
|
|
|
|
msg: to_binary(&WormholeExecuteMsg::PostMessage {
|
|
|
|
message: Binary::from(token_bridge_message.serialize()),
|
|
|
|
nonce: state.nonce,
|
|
|
|
})?,
|
|
|
|
});
|
|
|
|
|
2022-06-07 08:36:21 -07:00
|
|
|
let external_id = ExternalTokenId::from_native_cw20(&state.token_address)?;
|
|
|
|
send_native(deps.storage, &external_id, real_amount)?;
|
2022-06-16 09:48:01 -07:00
|
|
|
Ok(Response::default()
|
|
|
|
.add_message(message)
|
|
|
|
.add_attribute("action", "reply_handler"))
|
|
|
|
}
|
|
|
|
|
|
|
|
fn parse_vaa(deps: Deps, block_time: u64, data: &Binary) -> StdResult<ParsedVAA> {
|
|
|
|
let cfg = config_read(deps.storage).load()?;
|
|
|
|
let vaa: ParsedVAA = deps.querier.query(&QueryRequest::Wasm(WasmQuery::Smart {
|
|
|
|
contract_addr: cfg.wormhole_contract,
|
|
|
|
msg: to_binary(&WormholeQueryMsg::VerifyVAA {
|
|
|
|
vaa: data.clone(),
|
|
|
|
block_time,
|
|
|
|
})?,
|
|
|
|
}))?;
|
|
|
|
Ok(vaa)
|
|
|
|
}
|
|
|
|
|
|
|
|
#[cfg_attr(not(feature = "library"), entry_point)]
|
|
|
|
pub fn execute(deps: DepsMut, env: Env, info: MessageInfo, msg: ExecuteMsg) -> StdResult<Response> {
|
|
|
|
match msg {
|
2022-09-14 16:29:39 -07:00
|
|
|
ExecuteMsg::SubmitVaa { data } => submit_vaa(deps, env, info, &data),
|
|
|
|
|
|
|
|
// The following actions are disabled in "shutdown" mode
|
|
|
|
#[cfg(feature = "full")]
|
2022-06-07 08:36:21 -07:00
|
|
|
ExecuteMsg::RegisterAssetHook {
|
|
|
|
chain,
|
|
|
|
token_address,
|
|
|
|
} => handle_register_asset(deps, env, info, chain, token_address),
|
2022-09-14 16:29:39 -07:00
|
|
|
|
|
|
|
#[cfg(feature = "full")]
|
2022-06-16 09:48:01 -07:00
|
|
|
ExecuteMsg::InitiateTransfer {
|
|
|
|
asset,
|
|
|
|
recipient_chain,
|
|
|
|
recipient,
|
|
|
|
fee,
|
|
|
|
nonce,
|
|
|
|
} => handle_initiate_transfer(
|
|
|
|
deps,
|
|
|
|
env,
|
|
|
|
info,
|
|
|
|
asset,
|
|
|
|
recipient_chain,
|
2022-06-07 08:36:21 -07:00
|
|
|
recipient.to_array()?,
|
2022-06-16 09:48:01 -07:00
|
|
|
fee,
|
|
|
|
TransferType::WithoutPayload,
|
|
|
|
nonce,
|
|
|
|
),
|
2022-09-14 16:29:39 -07:00
|
|
|
|
|
|
|
#[cfg(feature = "full")]
|
2022-06-16 09:48:01 -07:00
|
|
|
ExecuteMsg::InitiateTransferWithPayload {
|
|
|
|
asset,
|
|
|
|
recipient_chain,
|
|
|
|
recipient,
|
|
|
|
fee,
|
|
|
|
payload,
|
|
|
|
nonce,
|
|
|
|
} => handle_initiate_transfer(
|
|
|
|
deps,
|
|
|
|
env,
|
|
|
|
info,
|
|
|
|
asset,
|
|
|
|
recipient_chain,
|
2022-06-07 08:36:21 -07:00
|
|
|
recipient.to_array()?,
|
2022-06-16 09:48:01 -07:00
|
|
|
fee,
|
|
|
|
TransferType::WithPayload {
|
|
|
|
payload: payload.into(),
|
|
|
|
},
|
|
|
|
nonce,
|
|
|
|
),
|
2022-09-14 16:29:39 -07:00
|
|
|
|
|
|
|
#[cfg(feature = "full")]
|
2022-06-16 09:48:01 -07:00
|
|
|
ExecuteMsg::DepositTokens {} => deposit_tokens(deps, env, info),
|
2022-09-14 16:29:39 -07:00
|
|
|
|
|
|
|
#[cfg(feature = "full")]
|
2022-06-16 09:48:01 -07:00
|
|
|
ExecuteMsg::WithdrawTokens { asset } => withdraw_tokens(deps, env, info, asset),
|
2022-09-14 16:29:39 -07:00
|
|
|
|
|
|
|
#[cfg(feature = "full")]
|
2022-06-16 09:48:01 -07:00
|
|
|
ExecuteMsg::CreateAssetMeta { asset_info, nonce } => {
|
|
|
|
handle_create_asset_meta(deps, env, info, asset_info, nonce)
|
|
|
|
}
|
2022-09-14 16:29:39 -07:00
|
|
|
|
|
|
|
#[cfg(feature = "full")]
|
2022-06-16 09:48:01 -07:00
|
|
|
ExecuteMsg::CompleteTransferWithPayload { data, relayer } => {
|
|
|
|
handle_complete_transfer_with_payload(deps, env, info, &data, &relayer)
|
|
|
|
}
|
2022-09-14 16:29:39 -07:00
|
|
|
|
|
|
|
// When in "shutdown" mode, we reject any other action
|
|
|
|
#[cfg(not(feature = "full"))]
|
2022-09-30 00:01:09 -07:00
|
|
|
_ => Err(StdError::generic_err("Invalid during shutdown mode")),
|
2022-06-16 09:48:01 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn deposit_tokens(deps: DepsMut, _env: Env, info: MessageInfo) -> StdResult<Response> {
|
|
|
|
for coin in info.funds {
|
|
|
|
let deposit_key = format!("{}:{}", info.sender, coin.denom);
|
|
|
|
bridge_deposit(deps.storage).update(
|
|
|
|
deposit_key.as_bytes(),
|
|
|
|
|amount: Option<Uint128>| -> StdResult<Uint128> {
|
|
|
|
Ok(amount.unwrap_or(Uint128::new(0)) + coin.amount)
|
|
|
|
},
|
|
|
|
)?;
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok(Response::new().add_attribute("action", "deposit_tokens"))
|
|
|
|
}
|
|
|
|
|
|
|
|
fn withdraw_tokens(
|
|
|
|
deps: DepsMut,
|
|
|
|
_env: Env,
|
|
|
|
info: MessageInfo,
|
|
|
|
data: AssetInfo,
|
|
|
|
) -> StdResult<Response> {
|
|
|
|
let mut messages: Vec<CosmosMsg> = vec![];
|
|
|
|
if let AssetInfo::NativeToken { denom } = data {
|
|
|
|
let deposit_key = format!("{}:{}", info.sender, denom);
|
|
|
|
let mut deposited_amount: u128 = 0;
|
|
|
|
bridge_deposit(deps.storage).update(
|
|
|
|
deposit_key.as_bytes(),
|
|
|
|
|current: Option<Uint128>| match current {
|
|
|
|
Some(v) => {
|
|
|
|
deposited_amount = v.u128();
|
|
|
|
Ok(Uint128::new(0))
|
|
|
|
}
|
|
|
|
None => Err(StdError::generic_err("no deposit found to withdraw")),
|
|
|
|
},
|
|
|
|
)?;
|
|
|
|
messages.push(CosmosMsg::Bank(BankMsg::Send {
|
|
|
|
to_address: info.sender.to_string(),
|
2022-06-07 08:36:21 -07:00
|
|
|
amount: vec![coin(deposited_amount, &denom)],
|
2022-06-16 09:48:01 -07:00
|
|
|
}));
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok(Response::new()
|
|
|
|
.add_messages(messages)
|
|
|
|
.add_attribute("action", "withdraw_tokens"))
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Handle wrapped asset registration messages
|
|
|
|
fn handle_register_asset(
|
|
|
|
deps: DepsMut,
|
|
|
|
_env: Env,
|
|
|
|
info: MessageInfo,
|
2022-06-07 08:36:21 -07:00
|
|
|
chain: u16,
|
|
|
|
token_address: ExternalTokenId,
|
2022-06-16 09:48:01 -07:00
|
|
|
) -> StdResult<Response> {
|
2022-06-07 08:36:21 -07:00
|
|
|
// We need to ensure that this registration request was initiated by the
|
|
|
|
// token bridge contract. We do this by checking that the wrapped asset
|
|
|
|
// already has an associated sequence number, but no address entry yet.
|
|
|
|
// This is a necessary and sufficient condition, because having the sequence
|
|
|
|
// number means that [`handle_attest_meta`] has been called, and not having
|
|
|
|
// an address entry yet means precisely that the callback hasn't finished
|
|
|
|
// yet.
|
|
|
|
|
|
|
|
let _ = wrapped_asset_seq_read(deps.storage, chain)
|
|
|
|
.load(&token_address.serialize())
|
|
|
|
.map_err(|_| ContractError::RegistrationForbidden.std())?;
|
|
|
|
|
|
|
|
let mut bucket = wrapped_asset(deps.storage, chain);
|
|
|
|
let result = bucket.load(&token_address.serialize()).ok();
|
2022-06-21 14:30:33 -07:00
|
|
|
if result.is_some() {
|
2022-06-16 09:48:01 -07:00
|
|
|
return ContractError::AssetAlreadyRegistered.std_err();
|
|
|
|
}
|
|
|
|
|
2022-06-07 08:36:21 -07:00
|
|
|
bucket.save(
|
|
|
|
&token_address.serialize(),
|
|
|
|
&WrappedCW20 {
|
|
|
|
human_address: info.sender.clone(),
|
|
|
|
},
|
|
|
|
)?;
|
2022-06-16 09:48:01 -07:00
|
|
|
|
2022-06-21 14:30:33 -07:00
|
|
|
let contract_address: CanonicalAddr = deps.api.addr_canonicalize(info.sender.as_str())?;
|
2022-06-07 08:36:21 -07:00
|
|
|
is_wrapped_asset(deps.storage).save(contract_address.as_slice(), &())?;
|
2022-06-16 09:48:01 -07:00
|
|
|
|
|
|
|
Ok(Response::new()
|
|
|
|
.add_attribute("action", "register_asset")
|
2022-06-07 08:36:21 -07:00
|
|
|
.add_attribute("token_chain", format!("{:?}", chain))
|
|
|
|
.add_attribute("token_address", format!("{:?}", token_address))
|
2022-06-16 09:48:01 -07:00
|
|
|
.add_attribute("contract_addr", info.sender))
|
|
|
|
}
|
|
|
|
|
|
|
|
fn handle_attest_meta(
|
|
|
|
deps: DepsMut,
|
|
|
|
env: Env,
|
|
|
|
emitter_chain: u16,
|
|
|
|
emitter_address: Vec<u8>,
|
|
|
|
sequence: u64,
|
|
|
|
data: &Vec<u8>,
|
|
|
|
) -> StdResult<Response> {
|
|
|
|
let meta = AssetMeta::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::generic_err("invalid emitter"));
|
|
|
|
}
|
|
|
|
|
2022-06-07 08:36:21 -07:00
|
|
|
let token_id = meta
|
|
|
|
.token_address
|
|
|
|
.to_token_id(deps.storage, emitter_chain)?;
|
|
|
|
|
|
|
|
let token_address: [u8; 32] = match token_id {
|
|
|
|
TokenId::Bank { denom } => Err(StdError::generic_err(format!(
|
|
|
|
"{} is native to this chain and should not be attested",
|
|
|
|
denom
|
|
|
|
))),
|
|
|
|
TokenId::Contract(ContractId::NativeCW20 { contract_address }) => {
|
|
|
|
Err(StdError::generic_err(format!(
|
|
|
|
"Contract {} is native to this chain and should not be attested",
|
|
|
|
contract_address
|
|
|
|
)))
|
|
|
|
}
|
|
|
|
TokenId::Contract(ContractId::ForeignToken {
|
|
|
|
chain_id: _,
|
|
|
|
foreign_address,
|
|
|
|
}) => Ok(foreign_address),
|
|
|
|
}?;
|
2022-06-16 09:48:01 -07:00
|
|
|
|
|
|
|
let cfg = config_read(deps.storage).load()?;
|
|
|
|
// If a CW20 wrapped already exists and this message has a newer sequence ID
|
|
|
|
// we allow updating the metadata. If not, we create a brand new token.
|
2022-06-07 08:36:21 -07:00
|
|
|
let message = if let Ok(contract) =
|
|
|
|
wrapped_asset_read(deps.storage, meta.token_chain).load(token_address.as_slice())
|
|
|
|
{
|
2022-06-16 09:48:01 -07:00
|
|
|
// Prevent anyone from re-attesting with old VAAs.
|
2022-06-07 08:36:21 -07:00
|
|
|
if sequence
|
|
|
|
<= wrapped_asset_seq_read(deps.storage, meta.token_chain)
|
|
|
|
.load(token_address.as_slice())?
|
|
|
|
{
|
2022-06-16 09:48:01 -07:00
|
|
|
return Err(StdError::generic_err(
|
|
|
|
"this asset has already been attested",
|
|
|
|
));
|
|
|
|
}
|
|
|
|
CosmosMsg::Wasm(WasmMsg::Execute {
|
2022-06-07 08:36:21 -07:00
|
|
|
contract_addr: contract.into_string(),
|
2022-06-16 09:48:01 -07:00
|
|
|
msg: to_binary(&WrappedMsg::UpdateMetadata {
|
|
|
|
name: get_string_from_32(&meta.name),
|
|
|
|
symbol: get_string_from_32(&meta.symbol),
|
|
|
|
})?,
|
|
|
|
funds: vec![],
|
|
|
|
})
|
|
|
|
} else {
|
|
|
|
CosmosMsg::Wasm(WasmMsg::Instantiate {
|
|
|
|
admin: Some(env.contract.address.clone().into_string()),
|
|
|
|
code_id: cfg.wrapped_asset_code_id,
|
|
|
|
msg: to_binary(&WrappedInit {
|
|
|
|
name: get_string_from_32(&meta.name),
|
|
|
|
symbol: get_string_from_32(&meta.symbol),
|
|
|
|
asset_chain: meta.token_chain,
|
2022-06-07 08:36:21 -07:00
|
|
|
asset_address: token_address.into(),
|
2022-06-16 09:48:01 -07:00
|
|
|
decimals: min(meta.decimals, 8u8),
|
|
|
|
mint: None,
|
|
|
|
init_hook: Some(InitHook {
|
|
|
|
contract_addr: env.contract.address.to_string(),
|
|
|
|
msg: to_binary(&ExecuteMsg::RegisterAssetHook {
|
2022-06-07 08:36:21 -07:00
|
|
|
chain: meta.token_chain,
|
|
|
|
token_address: meta.token_address.clone(),
|
2022-06-16 09:48:01 -07:00
|
|
|
})?,
|
|
|
|
}),
|
|
|
|
})?,
|
|
|
|
funds: vec![],
|
2022-06-07 08:36:21 -07:00
|
|
|
label: "Wormhole Wrapped CW20".to_string(),
|
2022-06-16 09:48:01 -07:00
|
|
|
})
|
|
|
|
};
|
2022-06-07 08:36:21 -07:00
|
|
|
wrapped_asset_seq(deps.storage, meta.token_chain).save(&token_address, &sequence)?;
|
2022-06-16 09:48:01 -07:00
|
|
|
Ok(Response::new().add_message(message))
|
|
|
|
}
|
|
|
|
|
|
|
|
fn handle_create_asset_meta(
|
|
|
|
deps: DepsMut,
|
|
|
|
env: Env,
|
|
|
|
info: MessageInfo,
|
|
|
|
asset_info: AssetInfo,
|
|
|
|
nonce: u32,
|
|
|
|
) -> StdResult<Response> {
|
|
|
|
match asset_info {
|
|
|
|
AssetInfo::Token { contract_addr } => {
|
|
|
|
handle_create_asset_meta_token(deps, env, info, contract_addr, nonce)
|
|
|
|
}
|
|
|
|
AssetInfo::NativeToken { ref denom } => {
|
|
|
|
handle_create_asset_meta_native_token(deps, env, info, denom.clone(), nonce)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn handle_create_asset_meta_token(
|
|
|
|
deps: DepsMut,
|
|
|
|
env: Env,
|
|
|
|
info: MessageInfo,
|
|
|
|
asset_address: HumanAddr,
|
|
|
|
nonce: u32,
|
|
|
|
) -> StdResult<Response> {
|
|
|
|
let cfg = config_read(deps.storage).load()?;
|
|
|
|
|
|
|
|
let request = QueryRequest::Wasm(WasmQuery::Smart {
|
|
|
|
contract_addr: asset_address.clone(),
|
|
|
|
msg: to_binary(&TokenQuery::TokenInfo {})?,
|
|
|
|
});
|
|
|
|
|
2022-06-07 08:36:21 -07:00
|
|
|
let asset_human = deps.api.addr_validate(&asset_address)?;
|
|
|
|
let token_id = TokenId::Contract(ContractId::NativeCW20 {
|
|
|
|
contract_address: asset_human,
|
|
|
|
});
|
|
|
|
let external_id = token_id.store(deps.storage)?;
|
2022-06-16 09:48:01 -07:00
|
|
|
let token_info: TokenInfoResponse = deps.querier.query(&request)?;
|
|
|
|
|
|
|
|
let meta: AssetMeta = AssetMeta {
|
2022-07-18 14:24:13 -07:00
|
|
|
token_chain: cfg.chain_id,
|
2022-06-07 08:36:21 -07:00
|
|
|
token_address: external_id,
|
2022-06-16 09:48:01 -07:00
|
|
|
decimals: token_info.decimals,
|
|
|
|
symbol: extend_string_to_32(&token_info.symbol),
|
|
|
|
name: extend_string_to_32(&token_info.name),
|
|
|
|
};
|
|
|
|
|
|
|
|
let token_bridge_message = TokenBridgeMessage {
|
|
|
|
action: Action::ATTEST_META,
|
|
|
|
payload: meta.serialize().to_vec(),
|
|
|
|
};
|
|
|
|
|
|
|
|
Ok(Response::new()
|
|
|
|
.add_message(CosmosMsg::Wasm(WasmMsg::Execute {
|
|
|
|
contract_addr: cfg.wormhole_contract,
|
|
|
|
msg: to_binary(&WormholeExecuteMsg::PostMessage {
|
|
|
|
message: Binary::from(token_bridge_message.serialize()),
|
|
|
|
nonce,
|
|
|
|
})?,
|
|
|
|
// forward coins sent to this message
|
2022-06-21 14:30:33 -07:00
|
|
|
funds: info.funds,
|
2022-06-16 09:48:01 -07:00
|
|
|
}))
|
2022-07-18 14:24:13 -07:00
|
|
|
.add_attribute("meta.token_chain", cfg.chain_id.to_string())
|
2022-06-16 09:48:01 -07:00
|
|
|
.add_attribute("meta.token", asset_address)
|
|
|
|
.add_attribute("meta.nonce", nonce.to_string())
|
|
|
|
.add_attribute("meta.block_time", env.block.time.seconds().to_string()))
|
|
|
|
}
|
|
|
|
|
|
|
|
fn handle_create_asset_meta_native_token(
|
|
|
|
deps: DepsMut,
|
|
|
|
env: Env,
|
|
|
|
info: MessageInfo,
|
|
|
|
denom: String,
|
|
|
|
nonce: u32,
|
|
|
|
) -> StdResult<Response> {
|
|
|
|
let cfg = config_read(deps.storage).load()?;
|
2022-06-07 08:36:21 -07:00
|
|
|
|
2022-10-11 19:17:32 -07:00
|
|
|
if denom != cfg.native_denom {
|
|
|
|
return Err(StdError::generic_err("unsupported native token"));
|
|
|
|
}
|
|
|
|
|
2022-06-07 08:36:21 -07:00
|
|
|
let token_id = TokenId::Bank { denom };
|
|
|
|
let external_id = token_id.store(deps.storage)?;
|
|
|
|
|
2022-06-16 09:48:01 -07:00
|
|
|
let meta: AssetMeta = AssetMeta {
|
2022-07-18 14:24:13 -07:00
|
|
|
token_chain: cfg.chain_id,
|
2022-06-07 08:36:21 -07:00
|
|
|
token_address: external_id.clone(),
|
2022-10-11 19:17:32 -07:00
|
|
|
decimals: cfg.native_decimals,
|
|
|
|
symbol: extend_string_to_32(&cfg.native_symbol),
|
|
|
|
name: extend_string_to_32(&cfg.native_symbol),
|
2022-06-16 09:48:01 -07:00
|
|
|
};
|
|
|
|
let token_bridge_message = TokenBridgeMessage {
|
|
|
|
action: Action::ATTEST_META,
|
|
|
|
payload: meta.serialize().to_vec(),
|
|
|
|
};
|
|
|
|
Ok(Response::new()
|
|
|
|
.add_message(CosmosMsg::Wasm(WasmMsg::Execute {
|
|
|
|
contract_addr: cfg.wormhole_contract,
|
|
|
|
msg: to_binary(&WormholeExecuteMsg::PostMessage {
|
|
|
|
message: Binary::from(token_bridge_message.serialize()),
|
|
|
|
nonce,
|
|
|
|
})?,
|
|
|
|
// forward coins sent to this message
|
2022-06-21 14:30:33 -07:00
|
|
|
funds: info.funds,
|
2022-06-16 09:48:01 -07:00
|
|
|
}))
|
2022-07-18 14:24:13 -07:00
|
|
|
.add_attribute("meta.token_chain", cfg.chain_id.to_string())
|
2022-10-11 19:17:32 -07:00
|
|
|
.add_attribute("meta.symbol", cfg.native_symbol)
|
2022-06-07 08:36:21 -07:00
|
|
|
.add_attribute("meta.asset_id", hex::encode(external_id.serialize()))
|
2022-06-16 09:48:01 -07:00
|
|
|
.add_attribute("meta.nonce", nonce.to_string())
|
|
|
|
.add_attribute("meta.block_time", env.block.time.seconds().to_string()))
|
|
|
|
}
|
|
|
|
|
|
|
|
fn handle_complete_transfer_with_payload(
|
2022-06-07 08:36:21 -07:00
|
|
|
mut deps: DepsMut,
|
2022-06-16 09:48:01 -07:00
|
|
|
env: Env,
|
|
|
|
info: MessageInfo,
|
|
|
|
data: &Binary,
|
|
|
|
relayer_address: &HumanAddr,
|
|
|
|
) -> StdResult<Response> {
|
2022-06-07 08:36:21 -07:00
|
|
|
let (vaa, payload) = parse_and_archive_vaa(deps.branch(), env.clone(), data)?;
|
2022-06-16 09:48:01 -07:00
|
|
|
|
2022-06-07 08:36:21 -07:00
|
|
|
if let Either::Right(message) = payload {
|
|
|
|
match message.action {
|
|
|
|
Action::TRANSFER_WITH_PAYLOAD => handle_complete_transfer(
|
|
|
|
deps,
|
|
|
|
env,
|
|
|
|
info,
|
|
|
|
vaa.emitter_chain,
|
|
|
|
vaa.emitter_address,
|
|
|
|
TransferType::WithPayload { payload: () },
|
|
|
|
&message.payload,
|
|
|
|
relayer_address,
|
|
|
|
),
|
|
|
|
_ => ContractError::InvalidVAAAction.std_err(),
|
|
|
|
}
|
|
|
|
} else {
|
2022-06-21 14:30:33 -07:00
|
|
|
ContractError::InvalidVAAAction.std_err()
|
2022-06-16 09:48:01 -07:00
|
|
|
}
|
2022-06-07 08:36:21 -07:00
|
|
|
}
|
2022-06-16 09:48:01 -07:00
|
|
|
|
2022-06-07 08:36:21 -07:00
|
|
|
enum Either<A, B> {
|
|
|
|
Left(A),
|
|
|
|
Right(B),
|
2022-06-16 09:48:01 -07:00
|
|
|
}
|
|
|
|
|
2022-06-07 08:36:21 -07:00
|
|
|
fn parse_and_archive_vaa(
|
2022-06-16 09:48:01 -07:00
|
|
|
deps: DepsMut,
|
|
|
|
env: Env,
|
|
|
|
data: &Binary,
|
2022-06-07 08:36:21 -07:00
|
|
|
) -> StdResult<(ParsedVAA, Either<GovernancePacket, TokenBridgeMessage>)> {
|
2022-06-16 09:48:01 -07:00
|
|
|
let state = config_read(deps.storage).load()?;
|
|
|
|
|
|
|
|
let vaa = parse_vaa(deps.as_ref(), env.block.time.seconds(), data)?;
|
|
|
|
|
|
|
|
if vaa_archive_check(deps.storage, vaa.hash.as_slice()) {
|
|
|
|
return ContractError::VaaAlreadyExecuted.std_err();
|
|
|
|
}
|
|
|
|
vaa_archive_add(deps.storage, vaa.hash.as_slice())?;
|
|
|
|
|
|
|
|
// check if vaa is from governance
|
|
|
|
if is_governance_emitter(&state, vaa.emitter_chain, &vaa.emitter_address) {
|
2022-06-07 08:36:21 -07:00
|
|
|
let gov_packet = GovernancePacket::deserialize(&vaa.payload)?;
|
|
|
|
return Ok((vaa, Either::Left(gov_packet)));
|
2022-06-16 09:48:01 -07:00
|
|
|
}
|
|
|
|
|
2022-06-07 08:36:21 -07:00
|
|
|
let message = TokenBridgeMessage::deserialize(&vaa.payload)?;
|
2022-06-21 14:30:33 -07:00
|
|
|
Ok((vaa, Either::Right(message)))
|
2022-06-07 08:36:21 -07:00
|
|
|
}
|
2022-06-16 09:48:01 -07:00
|
|
|
|
2022-06-07 08:36:21 -07:00
|
|
|
fn submit_vaa(
|
|
|
|
mut deps: DepsMut,
|
|
|
|
env: Env,
|
|
|
|
info: MessageInfo,
|
|
|
|
data: &Binary,
|
|
|
|
) -> StdResult<Response> {
|
|
|
|
let (vaa, payload) = parse_and_archive_vaa(deps.branch(), env.clone(), data)?;
|
|
|
|
match payload {
|
2022-06-21 14:30:33 -07:00
|
|
|
Either::Left(governance_packet) => handle_governance_payload(deps, env, &governance_packet),
|
2022-09-14 16:29:39 -07:00
|
|
|
|
|
|
|
// In "shutdown" mode, we only handle governance payloads
|
|
|
|
#[cfg(feature = "full")]
|
2022-06-07 08:36:21 -07:00
|
|
|
Either::Right(message) => match message.action {
|
|
|
|
Action::TRANSFER => {
|
|
|
|
let sender = info.sender.to_string();
|
|
|
|
handle_complete_transfer(
|
|
|
|
deps,
|
|
|
|
env,
|
|
|
|
info,
|
|
|
|
vaa.emitter_chain,
|
|
|
|
vaa.emitter_address,
|
|
|
|
TransferType::WithoutPayload,
|
|
|
|
&message.payload,
|
|
|
|
&sender,
|
|
|
|
)
|
|
|
|
}
|
|
|
|
Action::ATTEST_META => handle_attest_meta(
|
2022-06-16 09:48:01 -07:00
|
|
|
deps,
|
|
|
|
env,
|
|
|
|
vaa.emitter_chain,
|
|
|
|
vaa.emitter_address,
|
2022-06-07 08:36:21 -07:00
|
|
|
vaa.sequence,
|
2022-06-16 09:48:01 -07:00
|
|
|
&message.payload,
|
2022-06-07 08:36:21 -07:00
|
|
|
),
|
|
|
|
_ => ContractError::InvalidVAAAction.std_err(),
|
|
|
|
},
|
2022-09-14 16:29:39 -07:00
|
|
|
|
|
|
|
#[cfg(not(feature = "full"))]
|
|
|
|
_ => ContractError::InvalidVAAAction.std_err(),
|
2022-06-16 09:48:01 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-06-07 08:36:21 -07:00
|
|
|
fn handle_governance_payload(
|
|
|
|
deps: DepsMut,
|
|
|
|
env: Env,
|
|
|
|
gov_packet: &GovernancePacket,
|
|
|
|
) -> StdResult<Response> {
|
2022-07-18 14:24:13 -07:00
|
|
|
let cfg = config_read(deps.storage).load()?;
|
2022-06-16 09:48:01 -07:00
|
|
|
let module = get_string_from_32(&gov_packet.module);
|
|
|
|
|
|
|
|
if module != "TokenBridge" {
|
|
|
|
return Err(StdError::generic_err("this is not a valid module"));
|
|
|
|
}
|
|
|
|
|
2022-07-18 14:24:13 -07:00
|
|
|
if gov_packet.chain != 0 && gov_packet.chain != cfg.chain_id {
|
2022-06-16 09:48:01 -07:00
|
|
|
return Err(StdError::generic_err(
|
|
|
|
"the governance VAA is for another chain",
|
|
|
|
));
|
|
|
|
}
|
|
|
|
|
|
|
|
match gov_packet.action {
|
|
|
|
1u8 => handle_register_chain(deps, env, &gov_packet.payload),
|
|
|
|
2u8 => handle_upgrade_contract(deps, env, &gov_packet.payload),
|
|
|
|
_ => ContractError::InvalidVAAAction.std_err(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn handle_upgrade_contract(_deps: DepsMut, env: Env, data: &Vec<u8>) -> StdResult<Response> {
|
2022-06-21 14:30:33 -07:00
|
|
|
let UpgradeContract { new_contract } = UpgradeContract::deserialize(data)?;
|
2022-06-16 09:48:01 -07:00
|
|
|
|
|
|
|
Ok(Response::new()
|
|
|
|
.add_message(CosmosMsg::Wasm(WasmMsg::Migrate {
|
|
|
|
contract_addr: env.contract.address.to_string(),
|
|
|
|
new_code_id: new_contract,
|
|
|
|
msg: to_binary(&MigrateMsg {})?,
|
|
|
|
}))
|
|
|
|
.add_attribute("action", "contract_upgrade"))
|
|
|
|
}
|
|
|
|
|
|
|
|
fn handle_register_chain(deps: DepsMut, _env: Env, data: &Vec<u8>) -> StdResult<Response> {
|
|
|
|
let RegisterChain {
|
|
|
|
chain_id,
|
|
|
|
chain_address,
|
2022-06-21 14:30:33 -07:00
|
|
|
} = RegisterChain::deserialize(data)?;
|
2022-06-16 09:48:01 -07:00
|
|
|
|
|
|
|
let existing = bridge_contracts_read(deps.storage).load(&chain_id.to_be_bytes());
|
|
|
|
if existing.is_ok() {
|
|
|
|
return Err(StdError::generic_err(
|
|
|
|
"bridge contract already exists for this chain",
|
|
|
|
));
|
|
|
|
}
|
|
|
|
|
|
|
|
let mut bucket = bridge_contracts(deps.storage);
|
|
|
|
bucket.save(&chain_id.to_be_bytes(), &chain_address)?;
|
|
|
|
|
|
|
|
Ok(Response::new()
|
|
|
|
.add_attribute("chain_id", chain_id.to_string())
|
|
|
|
.add_attribute("chain_address", hex::encode(chain_address)))
|
|
|
|
}
|
|
|
|
|
2022-06-21 14:30:33 -07:00
|
|
|
#[allow(clippy::too_many_arguments)]
|
2022-06-16 09:48:01 -07:00
|
|
|
fn handle_complete_transfer(
|
|
|
|
deps: DepsMut,
|
|
|
|
env: Env,
|
|
|
|
info: MessageInfo,
|
|
|
|
emitter_chain: u16,
|
|
|
|
emitter_address: Vec<u8>,
|
|
|
|
transfer_type: TransferType<()>,
|
|
|
|
data: &Vec<u8>,
|
|
|
|
relayer_address: &HumanAddr,
|
|
|
|
) -> StdResult<Response> {
|
2022-06-21 14:30:33 -07:00
|
|
|
let transfer_info = TransferInfo::deserialize(data)?;
|
2022-06-07 08:36:21 -07:00
|
|
|
let token_id = transfer_info
|
|
|
|
.token_address
|
|
|
|
.to_token_id(deps.storage, transfer_info.token_chain)?;
|
|
|
|
match token_id {
|
|
|
|
TokenId::Bank { denom } => handle_complete_transfer_token_native(
|
2022-06-16 09:48:01 -07:00
|
|
|
deps,
|
|
|
|
env,
|
|
|
|
info,
|
|
|
|
emitter_chain,
|
|
|
|
emitter_address,
|
2022-06-07 08:36:21 -07:00
|
|
|
denom,
|
2022-06-16 09:48:01 -07:00
|
|
|
transfer_type,
|
|
|
|
data,
|
|
|
|
relayer_address,
|
|
|
|
),
|
2022-06-07 08:36:21 -07:00
|
|
|
TokenId::Contract(contract) => handle_complete_transfer_token(
|
2022-06-16 09:48:01 -07:00
|
|
|
deps,
|
|
|
|
env,
|
|
|
|
info,
|
|
|
|
emitter_chain,
|
|
|
|
emitter_address,
|
2022-06-07 08:36:21 -07:00
|
|
|
contract,
|
2022-06-16 09:48:01 -07:00
|
|
|
transfer_type,
|
|
|
|
data,
|
|
|
|
relayer_address,
|
|
|
|
),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-06-21 14:30:33 -07:00
|
|
|
#[allow(clippy::too_many_arguments)]
|
|
|
|
#[allow(clippy::bind_instead_of_map)]
|
2022-06-16 09:48:01 -07:00
|
|
|
fn handle_complete_transfer_token(
|
|
|
|
deps: DepsMut,
|
|
|
|
_env: Env,
|
|
|
|
info: MessageInfo,
|
|
|
|
emitter_chain: u16,
|
|
|
|
emitter_address: Vec<u8>,
|
2022-06-07 08:36:21 -07:00
|
|
|
token_contract: ContractId,
|
2022-06-16 09:48:01 -07:00
|
|
|
transfer_type: TransferType<()>,
|
|
|
|
data: &Vec<u8>,
|
|
|
|
relayer_address: &HumanAddr,
|
|
|
|
) -> StdResult<Response> {
|
2022-07-18 14:24:13 -07:00
|
|
|
let cfg = config_read(deps.storage).load()?;
|
2022-06-16 09:48:01 -07:00
|
|
|
let transfer_info = match transfer_type {
|
2022-06-21 14:30:33 -07:00
|
|
|
TransferType::WithoutPayload => TransferInfo::deserialize(data)?,
|
2022-06-16 09:48:01 -07:00
|
|
|
TransferType::WithPayload { payload: _ } => {
|
2022-06-21 14:30:33 -07:00
|
|
|
TransferWithPayloadInfo::deserialize(data)?.as_transfer_info()
|
2022-06-16 09:48:01 -07:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
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::generic_err("invalid emitter"));
|
|
|
|
}
|
|
|
|
|
2022-07-18 14:24:13 -07:00
|
|
|
if transfer_info.recipient_chain != cfg.chain_id {
|
2022-06-16 09:48:01 -07:00
|
|
|
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 recipient = deps.api.addr_humanize(&target_address)?;
|
|
|
|
|
|
|
|
if let TransferType::WithPayload { payload: _ } = transfer_type {
|
|
|
|
if recipient != info.sender {
|
|
|
|
return Err(StdError::generic_err(
|
|
|
|
"transfers with payload can only be redeemed by the recipient",
|
|
|
|
));
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
let (not_supported_amount, mut amount) = transfer_info.amount;
|
|
|
|
let (not_supported_fee, mut fee) = transfer_info.fee;
|
|
|
|
|
2022-10-26 09:39:31 -07:00
|
|
|
amount = amount
|
|
|
|
.checked_sub(fee)
|
|
|
|
.ok_or(StdError::generic_err("Insufficient funds"))?;
|
2022-06-16 09:48:01 -07:00
|
|
|
|
|
|
|
// Check high 128 bit of amount value to be empty
|
|
|
|
if not_supported_amount != 0 || not_supported_fee != 0 {
|
|
|
|
return ContractError::AmountTooHigh.std_err();
|
|
|
|
}
|
|
|
|
|
2022-06-07 08:36:21 -07:00
|
|
|
let external_id = ExternalTokenId::from_token_id(&TokenId::Contract(token_contract.clone()))?;
|
2022-06-16 09:48:01 -07:00
|
|
|
|
2022-06-07 08:36:21 -07:00
|
|
|
match token_contract {
|
|
|
|
ContractId::ForeignToken {
|
|
|
|
chain_id,
|
|
|
|
foreign_address,
|
|
|
|
} => {
|
|
|
|
// Check if this asset is already deployed
|
|
|
|
let contract_addr = wrapped_asset_read(deps.storage, chain_id).load(&foreign_address).
|
|
|
|
or_else(|_| Err(StdError::generic_err("Wrapped asset not deployed. To deploy, invoke CreateWrapped with the associated AssetMeta")))?;
|
2022-06-16 09:48:01 -07:00
|
|
|
|
2022-06-07 08:36:21 -07:00
|
|
|
let contract_addr = contract_addr.into_string();
|
2022-06-16 09:48:01 -07:00
|
|
|
|
2022-06-07 08:36:21 -07:00
|
|
|
// Asset already deployed, just mint
|
2022-06-16 09:48:01 -07:00
|
|
|
let mut messages = vec![CosmosMsg::Wasm(WasmMsg::Execute {
|
|
|
|
contract_addr: contract_addr.clone(),
|
|
|
|
msg: to_binary(&WrappedMsg::Mint {
|
|
|
|
recipient: recipient.to_string(),
|
|
|
|
amount: Uint128::from(amount),
|
|
|
|
})?,
|
|
|
|
funds: vec![],
|
|
|
|
})];
|
|
|
|
if fee != 0 {
|
|
|
|
messages.push(CosmosMsg::Wasm(WasmMsg::Execute {
|
|
|
|
contract_addr: contract_addr.clone(),
|
|
|
|
msg: to_binary(&WrappedMsg::Mint {
|
|
|
|
recipient: relayer_address.to_string(),
|
|
|
|
amount: Uint128::from(fee),
|
|
|
|
})?,
|
|
|
|
funds: vec![],
|
|
|
|
}))
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok(Response::new()
|
|
|
|
.add_messages(messages)
|
|
|
|
.add_attribute("action", "complete_transfer_wrapped")
|
|
|
|
.add_attribute("contract", contract_addr)
|
|
|
|
.add_attribute("recipient", recipient)
|
|
|
|
.add_attribute("amount", amount.to_string())
|
|
|
|
.add_attribute("relayer", relayer_address)
|
|
|
|
.add_attribute("fee", fee.to_string()))
|
2022-06-07 08:36:21 -07:00
|
|
|
}
|
|
|
|
ContractId::NativeCW20 { contract_address } => {
|
|
|
|
// note -- here the amount is the amount the recipient will receive;
|
|
|
|
// amount + fee is the total sent
|
|
|
|
receive_native(deps.storage, &external_id, Uint128::new(amount + fee))?;
|
2022-06-16 09:48:01 -07:00
|
|
|
|
2022-06-07 08:36:21 -07:00
|
|
|
// undo normalization to 8 decimals
|
|
|
|
let token_info: TokenInfoResponse =
|
|
|
|
deps.querier.query(&QueryRequest::Wasm(WasmQuery::Smart {
|
|
|
|
contract_addr: contract_address.to_string(),
|
|
|
|
msg: to_binary(&TokenQuery::TokenInfo {})?,
|
|
|
|
}))?;
|
|
|
|
|
|
|
|
let decimals = token_info.decimals;
|
|
|
|
let multiplier = 10u128.pow((max(decimals, 8u8) - 8u8) as u32);
|
|
|
|
amount = amount.checked_mul(multiplier).unwrap();
|
|
|
|
fee = fee.checked_mul(multiplier).unwrap();
|
|
|
|
|
|
|
|
let mut messages = vec![CosmosMsg::Wasm(WasmMsg::Execute {
|
|
|
|
contract_addr: contract_address.to_string(),
|
2022-06-16 09:48:01 -07:00
|
|
|
msg: to_binary(&TokenMsg::Transfer {
|
2022-06-07 08:36:21 -07:00
|
|
|
recipient: recipient.to_string(),
|
|
|
|
amount: Uint128::from(amount),
|
2022-06-16 09:48:01 -07:00
|
|
|
})?,
|
|
|
|
funds: vec![],
|
2022-06-07 08:36:21 -07:00
|
|
|
})];
|
|
|
|
|
|
|
|
if fee != 0 {
|
|
|
|
messages.push(CosmosMsg::Wasm(WasmMsg::Execute {
|
|
|
|
contract_addr: contract_address.to_string(),
|
|
|
|
msg: to_binary(&TokenMsg::Transfer {
|
|
|
|
recipient: relayer_address.to_string(),
|
|
|
|
amount: Uint128::from(fee),
|
|
|
|
})?,
|
|
|
|
funds: vec![],
|
|
|
|
}))
|
|
|
|
}
|
2022-06-16 09:48:01 -07:00
|
|
|
|
2022-06-07 08:36:21 -07:00
|
|
|
Ok(Response::new()
|
|
|
|
.add_messages(messages)
|
|
|
|
.add_attribute("action", "complete_transfer_native")
|
|
|
|
.add_attribute("recipient", recipient)
|
|
|
|
.add_attribute("contract", contract_address)
|
|
|
|
.add_attribute("amount", amount.to_string())
|
|
|
|
.add_attribute("relayer", relayer_address)
|
|
|
|
.add_attribute("fee", fee.to_string()))
|
|
|
|
}
|
2022-06-16 09:48:01 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-06-21 14:30:33 -07:00
|
|
|
#[allow(clippy::too_many_arguments)]
|
2022-06-16 09:48:01 -07:00
|
|
|
fn handle_complete_transfer_token_native(
|
2022-06-07 08:36:21 -07:00
|
|
|
deps: DepsMut,
|
2022-06-16 09:48:01 -07:00
|
|
|
_env: Env,
|
|
|
|
info: MessageInfo,
|
|
|
|
emitter_chain: u16,
|
|
|
|
emitter_address: Vec<u8>,
|
2022-06-07 08:36:21 -07:00
|
|
|
denom: String,
|
2022-06-16 09:48:01 -07:00
|
|
|
transfer_type: TransferType<()>,
|
|
|
|
data: &Vec<u8>,
|
|
|
|
relayer_address: &HumanAddr,
|
|
|
|
) -> StdResult<Response> {
|
2022-07-18 14:24:13 -07:00
|
|
|
let cfg = config_read(deps.storage).load()?;
|
2022-06-16 09:48:01 -07:00
|
|
|
let transfer_info = match transfer_type {
|
2022-06-21 14:30:33 -07:00
|
|
|
TransferType::WithoutPayload => TransferInfo::deserialize(data)?,
|
2022-06-16 09:48:01 -07:00
|
|
|
TransferType::WithPayload { payload: () } => {
|
2022-06-21 14:30:33 -07:00
|
|
|
TransferWithPayloadInfo::deserialize(data)?.as_transfer_info()
|
2022-06-16 09:48:01 -07:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
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::generic_err("invalid emitter"));
|
|
|
|
}
|
|
|
|
|
2022-07-18 14:24:13 -07:00
|
|
|
if transfer_info.recipient_chain != cfg.chain_id {
|
2022-06-16 09:48:01 -07:00
|
|
|
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 recipient = deps.api.addr_humanize(&target_address)?;
|
|
|
|
|
|
|
|
if let TransferType::WithPayload { payload: _ } = transfer_type {
|
|
|
|
if recipient != info.sender {
|
|
|
|
return Err(StdError::generic_err(
|
|
|
|
"transfers with payload can only be redeemed by the recipient",
|
|
|
|
));
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
let (not_supported_amount, mut amount) = transfer_info.amount;
|
2022-10-11 19:17:32 -07:00
|
|
|
let (not_supported_fee, mut fee) = transfer_info.fee;
|
2022-06-16 09:48:01 -07:00
|
|
|
|
2022-10-26 09:39:31 -07:00
|
|
|
amount = amount
|
|
|
|
.checked_sub(fee)
|
|
|
|
.ok_or(StdError::generic_err("Insufficient funds"))?;
|
2022-06-16 09:48:01 -07:00
|
|
|
|
|
|
|
// Check high 128 bit of amount value to be empty
|
|
|
|
if not_supported_amount != 0 || not_supported_fee != 0 {
|
|
|
|
return ContractError::AmountTooHigh.std_err();
|
|
|
|
}
|
|
|
|
|
2022-06-07 08:36:21 -07:00
|
|
|
let external_address = ExternalTokenId::from_bank_token(&denom)?;
|
|
|
|
receive_native(deps.storage, &external_address, Uint128::new(amount + fee))?;
|
2022-06-16 09:48:01 -07:00
|
|
|
|
2022-10-11 19:17:32 -07:00
|
|
|
// undo normalization to 8 decimals
|
|
|
|
let decimals = cfg.native_decimals;
|
|
|
|
let multiplier = 10u128.pow((max(decimals, 8u8) - 8u8) as u32);
|
|
|
|
amount = amount.checked_mul(multiplier).unwrap();
|
|
|
|
fee = fee.checked_mul(multiplier).unwrap();
|
|
|
|
|
2022-06-16 09:48:01 -07:00
|
|
|
let mut messages = vec![CosmosMsg::Bank(BankMsg::Send {
|
|
|
|
to_address: recipient.to_string(),
|
2022-06-07 08:36:21 -07:00
|
|
|
amount: vec![coin(amount, &denom)],
|
2022-06-16 09:48:01 -07:00
|
|
|
})];
|
|
|
|
|
|
|
|
if fee != 0 {
|
|
|
|
messages.push(CosmosMsg::Bank(BankMsg::Send {
|
|
|
|
to_address: relayer_address.to_string(),
|
2022-06-07 08:36:21 -07:00
|
|
|
amount: vec![coin(fee, &denom)],
|
2022-06-16 09:48:01 -07:00
|
|
|
}));
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok(Response::new()
|
|
|
|
.add_messages(messages)
|
|
|
|
.add_attribute("action", "complete_transfer_terra_native")
|
|
|
|
.add_attribute("recipient", recipient)
|
|
|
|
.add_attribute("denom", denom)
|
|
|
|
.add_attribute("amount", amount.to_string())
|
|
|
|
.add_attribute("relayer", relayer_address)
|
|
|
|
.add_attribute("fee", fee.to_string()))
|
|
|
|
}
|
|
|
|
|
2022-06-21 14:30:33 -07:00
|
|
|
#[allow(clippy::too_many_arguments)]
|
2022-06-16 09:48:01 -07:00
|
|
|
fn handle_initiate_transfer(
|
|
|
|
deps: DepsMut,
|
|
|
|
env: Env,
|
|
|
|
info: MessageInfo,
|
|
|
|
asset: Asset,
|
|
|
|
recipient_chain: u16,
|
2022-06-07 08:36:21 -07:00
|
|
|
recipient: [u8; 32],
|
2022-06-16 09:48:01 -07:00
|
|
|
fee: Uint128,
|
|
|
|
transfer_type: TransferType<Vec<u8>>,
|
|
|
|
nonce: u32,
|
|
|
|
) -> StdResult<Response> {
|
|
|
|
match asset.info {
|
|
|
|
AssetInfo::Token { contract_addr } => handle_initiate_transfer_token(
|
|
|
|
deps,
|
|
|
|
env,
|
|
|
|
info,
|
|
|
|
contract_addr,
|
|
|
|
asset.amount,
|
|
|
|
recipient_chain,
|
|
|
|
recipient,
|
|
|
|
fee,
|
|
|
|
transfer_type,
|
|
|
|
nonce,
|
|
|
|
),
|
|
|
|
AssetInfo::NativeToken { ref denom } => handle_initiate_transfer_native_token(
|
|
|
|
deps,
|
|
|
|
env,
|
|
|
|
info,
|
|
|
|
denom.clone(),
|
|
|
|
asset.amount,
|
|
|
|
recipient_chain,
|
|
|
|
recipient,
|
|
|
|
fee,
|
|
|
|
transfer_type,
|
|
|
|
nonce,
|
|
|
|
),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-06-21 14:30:33 -07:00
|
|
|
#[allow(clippy::too_many_arguments)]
|
2022-06-16 09:48:01 -07:00
|
|
|
fn handle_initiate_transfer_token(
|
2022-06-07 08:36:21 -07:00
|
|
|
deps: DepsMut,
|
2022-06-16 09:48:01 -07:00
|
|
|
env: Env,
|
|
|
|
info: MessageInfo,
|
|
|
|
asset: HumanAddr,
|
|
|
|
mut amount: Uint128,
|
|
|
|
recipient_chain: u16,
|
2022-06-07 08:36:21 -07:00
|
|
|
recipient: [u8; 32],
|
2022-06-16 09:48:01 -07:00
|
|
|
mut fee: Uint128,
|
|
|
|
transfer_type: TransferType<Vec<u8>>,
|
|
|
|
nonce: u32,
|
|
|
|
) -> StdResult<Response> {
|
2022-07-18 14:24:13 -07:00
|
|
|
let cfg = config_read(deps.storage).load()?;
|
|
|
|
|
|
|
|
if recipient_chain == cfg.chain_id {
|
2022-06-16 09:48:01 -07:00
|
|
|
return ContractError::SameSourceAndTarget.std_err();
|
|
|
|
}
|
|
|
|
if amount.is_zero() {
|
|
|
|
return ContractError::AmountTooLow.std_err();
|
|
|
|
}
|
|
|
|
|
|
|
|
let asset_chain: u16;
|
2022-06-07 08:36:21 -07:00
|
|
|
let asset_address: [u8; 32];
|
2022-06-16 09:48:01 -07:00
|
|
|
|
|
|
|
let asset_canonical: CanonicalAddr = deps.api.addr_canonicalize(&asset)?;
|
|
|
|
|
|
|
|
let mut messages: Vec<CosmosMsg> = vec![];
|
|
|
|
let mut submessages: Vec<SubMsg> = vec![];
|
|
|
|
|
2022-06-21 14:30:33 -07:00
|
|
|
// we'll only need this for payload 3 transfers
|
|
|
|
let sender_address = deps.api.addr_canonicalize(&info.sender.to_string())?;
|
|
|
|
let sender_address = extend_address_to_32_array(&sender_address);
|
|
|
|
|
2022-06-07 08:36:21 -07:00
|
|
|
match is_wrapped_asset_read(deps.storage).load(asset_canonical.as_slice()) {
|
2022-06-16 09:48:01 -07:00
|
|
|
Ok(_) => {
|
|
|
|
// If the fee is too large the user will receive nothing.
|
|
|
|
if fee > amount {
|
|
|
|
return Err(StdError::generic_err("fee greater than sent amount"));
|
|
|
|
}
|
|
|
|
|
|
|
|
// This is a deployed wrapped asset, burn it
|
|
|
|
messages.push(CosmosMsg::Wasm(WasmMsg::Execute {
|
|
|
|
contract_addr: asset.clone(),
|
|
|
|
msg: to_binary(&WrappedMsg::Burn {
|
|
|
|
account: info.sender.to_string(),
|
|
|
|
amount,
|
|
|
|
})?,
|
|
|
|
funds: vec![],
|
|
|
|
}));
|
|
|
|
let request = QueryRequest::<Empty>::Wasm(WasmQuery::Smart {
|
|
|
|
contract_addr: asset,
|
|
|
|
msg: to_binary(&WrappedQuery::WrappedAssetInfo {})?,
|
|
|
|
});
|
2022-06-07 08:36:21 -07:00
|
|
|
let wrapped_token_info: WrappedAssetInfoResponse = deps.querier.query(&request)?;
|
2022-06-16 09:48:01 -07:00
|
|
|
asset_chain = wrapped_token_info.asset_chain;
|
2022-07-18 14:24:13 -07:00
|
|
|
assert!(asset_chain != cfg.chain_id, "Expected a foreign chain id.");
|
2022-06-07 08:36:21 -07:00
|
|
|
asset_address = wrapped_token_info.asset_address.to_array()?;
|
2022-06-16 09:48:01 -07:00
|
|
|
|
2022-07-18 14:24:13 -07:00
|
|
|
let external_id = ExternalTokenId::from_foreign_token(asset_address);
|
2022-06-16 09:48:01 -07:00
|
|
|
|
|
|
|
let token_bridge_message: TokenBridgeMessage = match transfer_type {
|
2022-06-21 14:30:33 -07:00
|
|
|
TransferType::WithoutPayload => {
|
|
|
|
let transfer_info = TransferInfo {
|
|
|
|
amount: (0, amount.u128()),
|
|
|
|
token_address: external_id,
|
|
|
|
token_chain: asset_chain,
|
|
|
|
recipient,
|
|
|
|
recipient_chain,
|
|
|
|
fee: (0, fee.u128()),
|
|
|
|
};
|
|
|
|
TokenBridgeMessage {
|
|
|
|
action: Action::TRANSFER,
|
|
|
|
payload: transfer_info.serialize(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
TransferType::WithPayload { payload } => {
|
|
|
|
let transfer_info = TransferWithPayloadInfo {
|
|
|
|
amount: (0, amount.u128()),
|
|
|
|
token_address: external_id,
|
|
|
|
token_chain: asset_chain,
|
|
|
|
recipient,
|
|
|
|
recipient_chain,
|
|
|
|
sender_address,
|
2022-06-16 09:48:01 -07:00
|
|
|
payload,
|
2022-06-21 14:30:33 -07:00
|
|
|
};
|
|
|
|
TokenBridgeMessage {
|
|
|
|
action: Action::TRANSFER_WITH_PAYLOAD,
|
|
|
|
payload: transfer_info.serialize(),
|
2022-06-16 09:48:01 -07:00
|
|
|
}
|
2022-06-21 14:30:33 -07:00
|
|
|
}
|
2022-06-16 09:48:01 -07:00
|
|
|
};
|
|
|
|
|
|
|
|
messages.push(CosmosMsg::Wasm(WasmMsg::Execute {
|
|
|
|
contract_addr: cfg.wormhole_contract,
|
|
|
|
msg: to_binary(&WormholeExecuteMsg::PostMessage {
|
|
|
|
message: Binary::from(token_bridge_message.serialize()),
|
|
|
|
nonce,
|
|
|
|
})?,
|
|
|
|
// forward coins sent to this message
|
2022-06-07 08:36:21 -07:00
|
|
|
funds: info.funds.clone(),
|
2022-06-16 09:48:01 -07:00
|
|
|
}));
|
|
|
|
}
|
|
|
|
Err(_) => {
|
2022-07-18 14:24:13 -07:00
|
|
|
asset_chain = cfg.chain_id;
|
|
|
|
|
2022-06-16 09:48:01 -07:00
|
|
|
// normalize amount to 8 decimals when it sent over the wormhole
|
|
|
|
let token_info: TokenInfoResponse =
|
|
|
|
deps.querier.query(&QueryRequest::Wasm(WasmQuery::Smart {
|
|
|
|
contract_addr: asset.clone(),
|
|
|
|
msg: to_binary(&TokenQuery::TokenInfo {})?,
|
|
|
|
}))?;
|
|
|
|
|
|
|
|
let decimals = token_info.decimals;
|
|
|
|
let multiplier = 10u128.pow((max(decimals, 8u8) - 8u8) as u32);
|
|
|
|
|
|
|
|
// chop off dust
|
2022-10-26 09:39:31 -07:00
|
|
|
amount = amount
|
|
|
|
.u128()
|
|
|
|
.checked_rem(multiplier)
|
|
|
|
.and_then(|rem| amount.u128().checked_sub(rem))
|
|
|
|
.map(Uint128::new)
|
|
|
|
.ok_or(StdError::generic_err("Insufficient funds"))?;
|
|
|
|
|
|
|
|
fee = fee
|
|
|
|
.u128()
|
|
|
|
.checked_rem(multiplier)
|
|
|
|
.and_then(|rem| fee.u128().checked_sub(rem))
|
|
|
|
.map(Uint128::new)
|
|
|
|
.ok_or(StdError::generic_err("Invalid fee"))?;
|
2022-06-16 09:48:01 -07:00
|
|
|
|
|
|
|
// This is a regular asset, transfer its balance
|
|
|
|
submessages.push(SubMsg::reply_on_success(
|
|
|
|
CosmosMsg::Wasm(WasmMsg::Execute {
|
|
|
|
contract_addr: asset.clone(),
|
|
|
|
msg: to_binary(&TokenMsg::TransferFrom {
|
|
|
|
owner: info.sender.to_string(),
|
|
|
|
recipient: env.contract.address.to_string(),
|
|
|
|
amount,
|
|
|
|
})?,
|
|
|
|
funds: vec![],
|
|
|
|
}),
|
|
|
|
1,
|
|
|
|
));
|
|
|
|
|
2022-06-07 08:36:21 -07:00
|
|
|
asset_address = extend_address_to_32_array(&asset_canonical);
|
|
|
|
let address_human = deps.api.addr_humanize(&asset_canonical)?;
|
|
|
|
// we store here just in case the token is transferred out before it's attested
|
|
|
|
let external_id = TokenId::Contract(ContractId::NativeCW20 {
|
|
|
|
contract_address: address_human,
|
|
|
|
})
|
|
|
|
.store(deps.storage)?;
|
2022-06-16 09:48:01 -07:00
|
|
|
|
|
|
|
// convert to normalized amounts before recording & posting vaa
|
|
|
|
amount = Uint128::new(amount.u128().checked_div(multiplier).unwrap());
|
|
|
|
fee = Uint128::new(fee.u128().checked_div(multiplier).unwrap());
|
|
|
|
|
|
|
|
// Fetch current CW20 Balance pre-transfer.
|
|
|
|
let balance: BalanceResponse =
|
|
|
|
deps.querier.query(&QueryRequest::Wasm(WasmQuery::Smart {
|
|
|
|
contract_addr: asset.to_string(),
|
|
|
|
msg: to_binary(&TokenQuery::Balance {
|
|
|
|
address: env.contract.address.to_string(),
|
|
|
|
})?,
|
|
|
|
}))?;
|
|
|
|
|
|
|
|
// NOTE: Reentrancy protection. It is crucial that there's no
|
|
|
|
// ongoing transfer in progress here, otherwise we would override
|
|
|
|
// its state. This could happen if the asset's TransferFrom handler
|
|
|
|
// sends us an InitiateTransfer message, which would be executed
|
|
|
|
// before the reply handler due the the depth-first semantics of
|
|
|
|
// message execution. A simple protection mechanism is to require
|
|
|
|
// that there's no execution in progress. The reply handler takes
|
|
|
|
// care of clearing out this temporary storage when done.
|
|
|
|
assert!(wrapped_transfer_tmp(deps.storage).load().is_err());
|
|
|
|
|
|
|
|
let token_bridge_message: TokenBridgeMessage = match transfer_type {
|
2022-06-21 14:30:33 -07:00
|
|
|
TransferType::WithoutPayload => {
|
|
|
|
let transfer_info = TransferInfo {
|
|
|
|
amount: (0, amount.u128()),
|
|
|
|
token_address: external_id,
|
|
|
|
token_chain: asset_chain,
|
|
|
|
recipient,
|
|
|
|
recipient_chain,
|
|
|
|
fee: (0, fee.u128()),
|
|
|
|
};
|
|
|
|
TokenBridgeMessage {
|
|
|
|
action: Action::TRANSFER,
|
|
|
|
payload: transfer_info.serialize(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
TransferType::WithPayload { payload } => {
|
|
|
|
let transfer_info = TransferWithPayloadInfo {
|
|
|
|
amount: (0, amount.u128()),
|
|
|
|
token_address: external_id,
|
|
|
|
token_chain: asset_chain,
|
|
|
|
recipient,
|
|
|
|
recipient_chain,
|
|
|
|
sender_address,
|
2022-06-16 09:48:01 -07:00
|
|
|
payload,
|
2022-06-21 14:30:33 -07:00
|
|
|
};
|
|
|
|
TokenBridgeMessage {
|
|
|
|
action: Action::TRANSFER_WITH_PAYLOAD,
|
|
|
|
payload: transfer_info.serialize(),
|
2022-06-16 09:48:01 -07:00
|
|
|
}
|
2022-06-21 14:30:33 -07:00
|
|
|
}
|
2022-06-16 09:48:01 -07:00
|
|
|
};
|
|
|
|
|
2022-06-07 08:36:21 -07:00
|
|
|
let token_address = deps.api.addr_validate(&asset)?;
|
|
|
|
|
2022-06-16 09:48:01 -07:00
|
|
|
// Wrap up state to be captured by the submessage reply.
|
|
|
|
wrapped_transfer_tmp(deps.storage).save(&TransferState {
|
|
|
|
previous_balance: balance.balance.to_string(),
|
|
|
|
account: info.sender.to_string(),
|
2022-06-07 08:36:21 -07:00
|
|
|
token_address,
|
2022-06-16 09:48:01 -07:00
|
|
|
message: token_bridge_message.serialize(),
|
|
|
|
multiplier: Uint128::new(multiplier).to_string(),
|
|
|
|
nonce,
|
|
|
|
})?;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
Ok(Response::new()
|
|
|
|
.add_messages(messages)
|
|
|
|
.add_submessages(submessages)
|
|
|
|
.add_attribute("transfer.token_chain", asset_chain.to_string())
|
|
|
|
.add_attribute("transfer.token", hex::encode(asset_address))
|
|
|
|
.add_attribute(
|
|
|
|
"transfer.sender",
|
|
|
|
hex::encode(extend_address_to_32(
|
2022-06-21 14:30:33 -07:00
|
|
|
&deps.api.addr_canonicalize(info.sender.as_str())?,
|
2022-06-16 09:48:01 -07:00
|
|
|
)),
|
|
|
|
)
|
|
|
|
.add_attribute("transfer.recipient_chain", recipient_chain.to_string())
|
|
|
|
.add_attribute("transfer.recipient", hex::encode(recipient))
|
|
|
|
.add_attribute("transfer.amount", amount.to_string())
|
|
|
|
.add_attribute("transfer.nonce", nonce.to_string())
|
|
|
|
.add_attribute("transfer.block_time", env.block.time.seconds().to_string()))
|
|
|
|
}
|
|
|
|
|
2022-06-21 14:30:33 -07:00
|
|
|
#[allow(clippy::too_many_arguments)]
|
2022-06-16 09:48:01 -07:00
|
|
|
fn handle_initiate_transfer_native_token(
|
|
|
|
deps: DepsMut,
|
|
|
|
env: Env,
|
|
|
|
info: MessageInfo,
|
|
|
|
denom: String,
|
2022-10-11 19:17:32 -07:00
|
|
|
mut amount: Uint128,
|
2022-06-16 09:48:01 -07:00
|
|
|
recipient_chain: u16,
|
2022-06-07 08:36:21 -07:00
|
|
|
recipient: [u8; 32],
|
2022-10-11 19:17:32 -07:00
|
|
|
mut fee: Uint128,
|
2022-06-16 09:48:01 -07:00
|
|
|
transfer_type: TransferType<Vec<u8>>,
|
|
|
|
nonce: u32,
|
|
|
|
) -> StdResult<Response> {
|
2022-07-18 14:24:13 -07:00
|
|
|
let cfg = config_read(deps.storage).load()?;
|
|
|
|
|
2022-10-11 19:17:32 -07:00
|
|
|
if denom != cfg.native_denom {
|
|
|
|
return Err(StdError::generic_err("unsupported native token"));
|
|
|
|
}
|
|
|
|
|
2022-07-18 14:24:13 -07:00
|
|
|
if recipient_chain == cfg.chain_id {
|
2022-06-16 09:48:01 -07:00
|
|
|
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"));
|
|
|
|
}
|
|
|
|
|
2022-10-11 19:17:32 -07:00
|
|
|
let decimals = cfg.native_decimals;
|
|
|
|
let multiplier = 10u128.pow((max(decimals, 8u8) - 8u8) as u32);
|
|
|
|
|
|
|
|
// chop off dust
|
|
|
|
amount = Uint128::new(
|
|
|
|
amount
|
|
|
|
.u128()
|
|
|
|
.checked_sub(amount.u128().checked_rem(multiplier).unwrap())
|
|
|
|
.unwrap(),
|
|
|
|
);
|
|
|
|
|
|
|
|
fee = Uint128::new(
|
|
|
|
fee.u128()
|
|
|
|
.checked_sub(fee.u128().checked_rem(multiplier).unwrap())
|
|
|
|
.unwrap(),
|
|
|
|
);
|
|
|
|
|
2022-06-16 09:48:01 -07:00
|
|
|
let deposit_key = format!("{}:{}", info.sender, denom);
|
|
|
|
bridge_deposit(deps.storage).update(deposit_key.as_bytes(), |current: Option<Uint128>| {
|
|
|
|
match current {
|
|
|
|
Some(v) => Ok(v.checked_sub(amount)?),
|
|
|
|
None => Err(StdError::generic_err("no deposit found to transfer")),
|
|
|
|
}
|
|
|
|
})?;
|
|
|
|
|
|
|
|
let mut messages: Vec<CosmosMsg> = vec![];
|
|
|
|
|
2022-07-18 14:24:13 -07:00
|
|
|
let asset_chain: u16 = cfg.chain_id;
|
2022-06-07 08:36:21 -07:00
|
|
|
// we store here just in case the token is transferred out before it's attested
|
|
|
|
let asset_address = TokenId::Bank { denom }.store(deps.storage)?;
|
2022-06-16 09:48:01 -07:00
|
|
|
|
2022-10-11 19:17:32 -07:00
|
|
|
// convert to normalized amounts before recording & posting vaa
|
|
|
|
amount = Uint128::new(amount.u128().checked_div(multiplier).unwrap());
|
|
|
|
fee = Uint128::new(fee.u128().checked_div(multiplier).unwrap());
|
|
|
|
|
2022-06-07 08:36:21 -07:00
|
|
|
send_native(deps.storage, &asset_address, amount)?;
|
2022-06-16 09:48:01 -07:00
|
|
|
|
|
|
|
let token_bridge_message: TokenBridgeMessage = match transfer_type {
|
2022-06-21 14:30:33 -07:00
|
|
|
TransferType::WithoutPayload => {
|
|
|
|
let transfer_info = TransferInfo {
|
|
|
|
amount: (0, amount.u128()),
|
|
|
|
token_address: asset_address.clone(),
|
|
|
|
token_chain: asset_chain,
|
|
|
|
recipient,
|
|
|
|
recipient_chain,
|
|
|
|
fee: (0, fee.u128()),
|
|
|
|
};
|
|
|
|
TokenBridgeMessage {
|
|
|
|
action: Action::TRANSFER,
|
|
|
|
payload: transfer_info.serialize(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
TransferType::WithPayload { payload } => {
|
|
|
|
let sender_address = deps.api.addr_canonicalize(&info.sender.to_string())?;
|
|
|
|
let sender_address = extend_address_to_32_array(&sender_address);
|
|
|
|
let transfer_info = TransferWithPayloadInfo {
|
|
|
|
amount: (0, amount.u128()),
|
|
|
|
token_address: asset_address.clone(),
|
|
|
|
token_chain: asset_chain,
|
|
|
|
recipient,
|
|
|
|
recipient_chain,
|
|
|
|
sender_address,
|
2022-06-16 09:48:01 -07:00
|
|
|
payload,
|
2022-06-21 14:30:33 -07:00
|
|
|
};
|
|
|
|
TokenBridgeMessage {
|
|
|
|
action: Action::TRANSFER_WITH_PAYLOAD,
|
|
|
|
payload: transfer_info.serialize(),
|
2022-06-16 09:48:01 -07:00
|
|
|
}
|
2022-06-21 14:30:33 -07:00
|
|
|
}
|
2022-06-16 09:48:01 -07:00
|
|
|
};
|
|
|
|
|
2022-06-21 14:30:33 -07:00
|
|
|
let sender = deps.api.addr_canonicalize(info.sender.as_str())?;
|
2022-06-16 09:48:01 -07:00
|
|
|
messages.push(CosmosMsg::Wasm(WasmMsg::Execute {
|
|
|
|
contract_addr: cfg.wormhole_contract,
|
|
|
|
msg: to_binary(&WormholeExecuteMsg::PostMessage {
|
|
|
|
message: Binary::from(token_bridge_message.serialize()),
|
|
|
|
nonce,
|
|
|
|
})?,
|
2022-06-21 14:30:33 -07:00
|
|
|
funds: info.funds,
|
2022-06-16 09:48:01 -07:00
|
|
|
}));
|
|
|
|
|
|
|
|
Ok(Response::new()
|
|
|
|
.add_messages(messages)
|
|
|
|
.add_attribute("transfer.token_chain", asset_chain.to_string())
|
2022-06-07 08:36:21 -07:00
|
|
|
.add_attribute("transfer.token", hex::encode(asset_address.serialize()))
|
2022-06-16 09:48:01 -07:00
|
|
|
.add_attribute(
|
|
|
|
"transfer.sender",
|
|
|
|
hex::encode(extend_address_to_32(&sender)),
|
|
|
|
)
|
|
|
|
.add_attribute("transfer.recipient_chain", recipient_chain.to_string())
|
|
|
|
.add_attribute("transfer.recipient", hex::encode(recipient))
|
|
|
|
.add_attribute("transfer.amount", amount.to_string())
|
|
|
|
.add_attribute("transfer.nonce", nonce.to_string())
|
|
|
|
.add_attribute("transfer.block_time", env.block.time.seconds().to_string()))
|
|
|
|
}
|
|
|
|
|
|
|
|
#[cfg_attr(not(feature = "library"), entry_point)]
|
|
|
|
pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> StdResult<Binary> {
|
|
|
|
match msg {
|
|
|
|
QueryMsg::WrappedRegistry { chain, address } => {
|
|
|
|
to_binary(&query_wrapped_registry(deps, chain, address.as_slice())?)
|
|
|
|
}
|
|
|
|
QueryMsg::TransferInfo { vaa } => to_binary(&query_transfer_info(deps, env, &vaa)?),
|
2022-06-07 08:36:21 -07:00
|
|
|
QueryMsg::ExternalId { external_id } => to_binary(&query_external_id(deps, external_id)?),
|
2022-08-14 11:19:58 -07:00
|
|
|
QueryMsg::IsVaaRedeemed { vaa } => to_binary(&query_is_vaa_redeemed(deps, env, &vaa)?),
|
2022-06-16 09:48:01 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-06-07 08:36:21 -07:00
|
|
|
fn query_external_id(deps: Deps, external_id: Binary) -> StdResult<ExternalIdResponse> {
|
2022-07-18 14:24:13 -07:00
|
|
|
let cfg = config_read(deps.storage).load()?;
|
2022-06-07 08:36:21 -07:00
|
|
|
let external_id = ExternalTokenId::deserialize(external_id.to_array()?);
|
|
|
|
Ok(ExternalIdResponse {
|
2022-07-18 14:24:13 -07:00
|
|
|
token_id: external_id.to_token_id(deps.storage, cfg.chain_id)?,
|
2022-06-07 08:36:21 -07:00
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2022-06-16 09:48:01 -07:00
|
|
|
pub fn query_wrapped_registry(
|
|
|
|
deps: Deps,
|
|
|
|
chain: u16,
|
|
|
|
address: &[u8],
|
|
|
|
) -> StdResult<WrappedRegistryResponse> {
|
|
|
|
// Check if this asset is already deployed
|
2022-06-21 14:30:33 -07:00
|
|
|
match wrapped_asset_read(deps.storage, chain).load(address) {
|
2022-06-07 08:36:21 -07:00
|
|
|
Ok(address) => Ok(WrappedRegistryResponse {
|
|
|
|
address: address.into_string(),
|
|
|
|
}),
|
2022-06-16 09:48:01 -07:00
|
|
|
Err(_) => ContractError::AssetNotFound.std_err(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn query_transfer_info(deps: Deps, env: Env, vaa: &Binary) -> StdResult<TransferInfoResponse> {
|
|
|
|
let cfg = config_read(deps.storage).load()?;
|
|
|
|
|
|
|
|
let parsed = parse_vaa(deps, env.block.time.seconds(), vaa)?;
|
|
|
|
let data = parsed.payload;
|
|
|
|
|
|
|
|
// check if vaa is from governance
|
|
|
|
if is_governance_emitter(&cfg, parsed.emitter_chain, &parsed.emitter_address) {
|
|
|
|
return ContractError::InvalidVAAAction.std_err();
|
|
|
|
}
|
|
|
|
|
|
|
|
let message = TokenBridgeMessage::deserialize(&data)?;
|
|
|
|
match message.action {
|
|
|
|
Action::ATTEST_META => ContractError::InvalidVAAAction.std_err(),
|
2022-06-21 14:30:33 -07:00
|
|
|
Action::TRANSFER => {
|
|
|
|
let core = TransferInfo::deserialize(&message.payload)?;
|
|
|
|
|
|
|
|
Ok(TransferInfoResponse {
|
|
|
|
amount: core.amount.1.into(),
|
|
|
|
token_address: core.token_address.serialize(),
|
|
|
|
token_chain: core.token_chain,
|
|
|
|
recipient: core.recipient,
|
|
|
|
recipient_chain: core.recipient_chain,
|
|
|
|
fee: core.fee.1.into(),
|
|
|
|
payload: vec![],
|
|
|
|
})
|
|
|
|
}
|
|
|
|
Action::TRANSFER_WITH_PAYLOAD => {
|
2022-06-16 09:48:01 -07:00
|
|
|
let info = TransferWithPayloadInfo::deserialize(&message.payload)?;
|
2022-06-21 14:30:33 -07:00
|
|
|
let core = info.as_transfer_info();
|
2022-06-16 09:48:01 -07:00
|
|
|
|
|
|
|
Ok(TransferInfoResponse {
|
|
|
|
amount: core.amount.1.into(),
|
2022-06-07 08:36:21 -07:00
|
|
|
token_address: core.token_address.serialize(),
|
2022-06-16 09:48:01 -07:00
|
|
|
token_chain: core.token_chain,
|
|
|
|
recipient: core.recipient,
|
|
|
|
recipient_chain: core.recipient_chain,
|
|
|
|
fee: core.fee.1.into(),
|
|
|
|
payload: info.payload,
|
|
|
|
})
|
|
|
|
}
|
2022-06-21 14:30:33 -07:00
|
|
|
other => Err(StdError::generic_err(format!("Invalid action: {}", other))),
|
2022-06-16 09:48:01 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-08-14 11:19:58 -07:00
|
|
|
fn query_is_vaa_redeemed(deps: Deps, _env: Env, vaa: &Binary) -> StdResult<IsVaaRedeemedResponse> {
|
|
|
|
let vaa = ParsedVAA::deserialize(vaa)?;
|
|
|
|
Ok(IsVaaRedeemedResponse {
|
|
|
|
is_redeemed: vaa_archive_check(deps.storage, vaa.hash.as_slice()),
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2022-06-21 14:30:33 -07:00
|
|
|
fn is_governance_emitter(cfg: &ConfigInfo, emitter_chain: u16, emitter_address: &[u8]) -> bool {
|
|
|
|
cfg.gov_chain == emitter_chain && cfg.gov_address == emitter_address
|
2022-06-16 09:48:01 -07:00
|
|
|
}
|