| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218 |
- use crate::msg::WrappedRegistryResponse;
- use cosmwasm_std::{
- coin,
- entry_point,
- to_binary,
- BankMsg,
- Binary,
- CanonicalAddr,
- Coin,
- CosmosMsg,
- Deps,
- DepsMut,
- Empty,
- Env,
- MessageInfo,
- Order,
- QueryRequest,
- Reply,
- Response,
- StdError,
- StdResult,
- SubMsg,
- Uint128,
- WasmMsg,
- WasmQuery,
- };
- use crate::{
- msg::{
- ExecuteMsg,
- InstantiateMsg,
- MigrateMsg,
- QueryMsg,
- },
- state::{
- bridge_contracts,
- bridge_contracts_read,
- bridge_deposit,
- config,
- config_read,
- receive_native,
- send_native,
- wrapped_asset,
- wrapped_asset_address,
- wrapped_asset_address_read,
- wrapped_asset_read,
- wrapped_asset_seq,
- wrapped_asset_seq_read,
- wrapped_transfer_tmp,
- Action,
- AssetMeta,
- ConfigInfo,
- RegisterChain,
- TokenBridgeMessage,
- TransferInfo,
- TransferState,
- UpgradeContract,
- },
- };
- use wormhole::{
- byte_utils::{
- extend_address_to_32,
- extend_string_to_32,
- get_string_from_32,
- ByteUtils,
- },
- error::ContractError,
- };
- use cw20_base::msg::{
- ExecuteMsg as TokenMsg,
- QueryMsg as TokenQuery,
- };
- use wormhole::msg::{
- ExecuteMsg as WormholeExecuteMsg,
- QueryMsg as WormholeQueryMsg,
- };
- use wormhole::state::{
- vaa_archive_add,
- vaa_archive_check,
- GovernancePacket,
- ParsedVAA,
- };
- use cw20::{
- BalanceResponse,
- TokenInfoResponse,
- };
- use cw20_wrapped::msg::{
- ExecuteMsg as WrappedMsg,
- InitHook,
- InstantiateMsg as WrappedInit,
- QueryMsg as WrappedQuery,
- WrappedAssetInfoResponse,
- };
- use terraswap::asset::{
- Asset,
- AssetInfo,
- };
- use sha3::{
- Digest,
- Keccak256,
- };
- use std::{
- cmp::{
- max,
- min,
- },
- str::FromStr,
- };
- type HumanAddr = String;
- // Chain ID of Terra
- const CHAIN_ID: u16 = 3;
- const WRAPPED_ASSET_UPDATING: &str = "updating";
- #[cfg_attr(not(feature = "library"), entry_point)]
- pub fn migrate(deps: DepsMut, _env: Env, _msg: MigrateMsg) -> StdResult<Response> {
- let bucket = wrapped_asset_address(deps.storage);
- let mut messages = vec![];
- for item in bucket.range(None, None, Order::Ascending) {
- let contract_address = item?.0;
- messages.push(CosmosMsg::Wasm(WasmMsg::Migrate {
- contract_addr: deps
- .api
- .addr_humanize(&contract_address.into())?
- .to_string(),
- new_code_id: 767,
- msg: to_binary(&MigrateMsg {})?,
- }));
- }
- let count = messages.len();
- Ok(Response::new()
- .add_messages(messages)
- .add_attribute("migrate", "upgrade cw20 wrappers")
- .add_attribute("count", count.to_string()))
- }
- #[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.as_slice().to_vec(),
- wormhole_contract: msg.wormhole_contract,
- wrapped_asset_code_id: msg.wrapped_asset_code_id,
- };
- 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()?;
- let mut info = TransferInfo::deserialize(&state.message)?;
- // Fetch CW20 Balance post-transfer.
- let new_balance: BalanceResponse = deps.querier.query(&QueryRequest::Wasm(WasmQuery::Smart {
- contract_addr: state.token_address.clone(),
- 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 info.fee.1 > real_amount.u128() {
- return Err(StdError::generic_err("fee greater than sent amount"));
- }
- // Update Wormhole message to correct amount.
- info.amount.1 = real_amount.u128();
- let token_bridge_message = TokenBridgeMessage {
- action: Action::TRANSFER,
- payload: info.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,
- })?,
- });
- send_native(deps.storage, &state.token_canonical, info.amount.1.into())?;
- Ok(Response::default()
- .add_message(message)
- .add_attribute("action", "reply_handler"))
- }
- pub fn coins_after_tax(deps: DepsMut, coins: Vec<Coin>) -> StdResult<Vec<Coin>> {
- let mut res = vec![];
- for coin in coins {
- let asset = Asset {
- amount: coin.amount.clone(),
- info: AssetInfo::NativeToken {
- denom: coin.denom.clone(),
- },
- };
- res.push(asset.deduct_tax(&deps.querier)?);
- }
- Ok(res)
- }
- pub fn parse_vaa(deps: DepsMut, 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.clone(),
- 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 {
- ExecuteMsg::RegisterAssetHook { asset_id } => {
- handle_register_asset(deps, env, info, &asset_id.as_slice())
- }
- ExecuteMsg::InitiateTransfer {
- asset,
- recipient_chain,
- recipient,
- fee,
- nonce,
- } => handle_initiate_transfer(
- deps,
- env,
- info,
- asset,
- recipient_chain,
- recipient.as_slice().to_vec(),
- fee,
- nonce,
- ),
- ExecuteMsg::DepositTokens {} => deposit_tokens(deps, env, info),
- ExecuteMsg::WithdrawTokens { asset } => withdraw_tokens(deps, env, info, asset),
- ExecuteMsg::SubmitVaa { data } => submit_vaa(deps, env, info, &data),
- ExecuteMsg::CreateAssetMeta { asset_info, nonce } => {
- handle_create_asset_meta(deps, env, info, asset_info, nonce)
- }
- }
- }
- 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(),
- amount: coins_after_tax(deps, vec![coin(deposited_amount, &denom)])?,
- }));
- }
- 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,
- asset_id: &[u8],
- ) -> StdResult<Response> {
- let mut bucket = wrapped_asset(deps.storage);
- let result = bucket.load(asset_id);
- let result = result.map_err(|_| ContractError::RegistrationForbidden.std())?;
- if result != HumanAddr::from(WRAPPED_ASSET_UPDATING) {
- return ContractError::AssetAlreadyRegistered.std_err();
- }
- bucket.save(asset_id, &info.sender.to_string())?;
- let contract_address: CanonicalAddr = deps.api.addr_canonicalize(&info.sender.as_str())?;
- wrapped_asset_address(deps.storage).save(contract_address.as_slice(), &asset_id.to_vec())?;
- Ok(Response::new()
- .add_attribute("action", "register_asset")
- .add_attribute("asset_id", format!("{:?}", asset_id))
- .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"));
- }
- if CHAIN_ID == meta.token_chain {
- return Err(StdError::generic_err(
- "this asset is native to this chain and should not be attested",
- ));
- }
- let cfg = config_read(deps.storage).load()?;
- let asset_id = build_asset_id(meta.token_chain, &meta.token_address.as_slice());
- // 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.
- let message = if let Ok(contract) = wrapped_asset_read(deps.storage).load(&asset_id) {
- // Prevent anyone from re-attesting with old VAAs.
- if sequence <= wrapped_asset_seq_read(deps.storage).load(&asset_id)? {
- return Err(StdError::generic_err(
- "this asset has already been attested",
- ));
- }
- CosmosMsg::Wasm(WasmMsg::Execute {
- contract_addr: contract,
- msg: to_binary(&WrappedMsg::UpdateMetadata {
- name: get_string_from_32(&meta.name),
- symbol: get_string_from_32(&meta.symbol),
- })?,
- funds: vec![],
- })
- } else {
- wrapped_asset(deps.storage).save(&asset_id, &HumanAddr::from(WRAPPED_ASSET_UPDATING))?;
- 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,
- asset_address: meta.token_address.to_vec().into(),
- decimals: min(meta.decimals, 8u8),
- mint: None,
- init_hook: Some(InitHook {
- contract_addr: env.contract.address.to_string(),
- msg: to_binary(&ExecuteMsg::RegisterAssetHook {
- asset_id: asset_id.to_vec().into(),
- })?,
- }),
- })?,
- funds: vec![],
- label: String::new(),
- })
- };
- wrapped_asset_seq(deps.storage).save(&asset_id, &sequence)?;
- 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 {})?,
- });
- let asset_canonical = deps.api.addr_canonicalize(&asset_address)?;
- let token_info: TokenInfoResponse = deps.querier.query(&request)?;
- let meta: AssetMeta = AssetMeta {
- token_chain: CHAIN_ID,
- token_address: extend_address_to_32(&asset_canonical),
- 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
- funds: coins_after_tax(deps, info.funds.clone())?,
- }))
- .add_attribute("meta.token_chain", CHAIN_ID.to_string())
- .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()?;
- 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(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
- funds: coins_after_tax(deps, info.funds.clone())?,
- }))
- .add_attribute("meta.token_chain", CHAIN_ID.to_string())
- .add_attribute("meta.symbol", symbol)
- .add_attribute("meta.asset_id", hex::encode(asset_id))
- .add_attribute("meta.nonce", nonce.to_string())
- .add_attribute("meta.block_time", env.block.time.seconds().to_string()))
- }
- fn submit_vaa(
- mut deps: DepsMut,
- env: Env,
- info: MessageInfo,
- data: &Binary,
- ) -> StdResult<Response> {
- let state = config_read(deps.storage).load()?;
- let vaa = parse_vaa(deps.branch(), env.block.time.seconds(), data)?;
- let data = vaa.payload;
- 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 state.gov_chain == vaa.emitter_chain && state.gov_address == vaa.emitter_address {
- return handle_governance_payload(deps, env, &data);
- }
- let message = TokenBridgeMessage::deserialize(&data)?;
- match message.action {
- Action::TRANSFER => handle_complete_transfer(
- deps,
- env,
- info,
- vaa.emitter_chain,
- vaa.emitter_address,
- &message.payload,
- ),
- Action::ATTEST_META => handle_attest_meta(
- deps,
- env,
- vaa.emitter_chain,
- vaa.emitter_address,
- vaa.sequence,
- &message.payload,
- ),
- _ => ContractError::InvalidVAAAction.std_err(),
- }
- }
- fn handle_governance_payload(deps: DepsMut, env: Env, data: &Vec<u8>) -> StdResult<Response> {
- let gov_packet = GovernancePacket::deserialize(&data)?;
- let module = get_string_from_32(&gov_packet.module);
- if module != "TokenBridge" {
- return Err(StdError::generic_err("this is not a valid module"));
- }
- if gov_packet.chain != 0 && gov_packet.chain != CHAIN_ID {
- 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> {
- let UpgradeContract { new_contract } = UpgradeContract::deserialize(&data)?;
- 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,
- } = RegisterChain::deserialize(&data)?;
- 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)))
- }
- fn handle_complete_transfer(
- deps: DepsMut,
- env: Env,
- info: MessageInfo,
- emitter_chain: u16,
- emitter_address: Vec<u8>,
- data: &Vec<u8>,
- ) -> StdResult<Response> {
- let transfer_info = TransferInfo::deserialize(&data)?;
- match transfer_info.token_address.as_slice()[0] {
- 1 => handle_complete_transfer_token_native(
- deps,
- env,
- info,
- emitter_chain,
- emitter_address,
- data,
- ),
- _ => handle_complete_transfer_token(deps, env, info, emitter_chain, emitter_address, data),
- }
- }
- fn handle_complete_transfer_token(
- deps: DepsMut,
- _env: Env,
- info: MessageInfo,
- emitter_chain: u16,
- emitter_address: Vec<u8>,
- data: &Vec<u8>,
- ) -> StdResult<Response> {
- 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::generic_err("invalid emitter"));
- }
- if transfer_info.recipient_chain != CHAIN_ID {
- return Err(StdError::generic_err(
- "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 (not_supported_amount, mut amount) = transfer_info.amount;
- let (not_supported_fee, mut 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();
- }
- if token_chain != CHAIN_ID {
- let asset_address = transfer_info.token_address;
- let asset_id = build_asset_id(token_chain, &asset_address);
- // Check if this asset is already deployed
- let contract_addr = wrapped_asset_read(deps.storage).load(&asset_id).ok();
- return if let Some(contract_addr) = contract_addr {
- // Asset already deployed, just mint
- let recipient = deps
- .api
- .addr_humanize(&target_address)
- .or_else(|_| ContractError::WrongTargetAddressFormat.std_err())?;
- 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: info.sender.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()))
- } else {
- Err(StdError::generic_err("Wrapped asset not deployed. To deploy, invoke CreateWrapped with the associated AssetMeta"))
- };
- } else {
- let token_address = transfer_info.token_address.as_slice().get_address(0);
- let recipient = deps.api.addr_humanize(&target_address)?;
- let contract_addr = deps.api.addr_humanize(&token_address)?;
- // note -- here the amount is the amount the recipient will receive;
- // amount + fee is the total sent
- receive_native(deps.storage, &token_address, Uint128::new(amount + fee))?;
- // undo normalization to 8 decimals
- let token_info: TokenInfoResponse =
- deps.querier.query(&QueryRequest::Wasm(WasmQuery::Smart {
- contract_addr: contract_addr.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_addr.to_string(),
- msg: to_binary(&TokenMsg::Transfer {
- recipient: recipient.to_string(),
- amount: Uint128::from(amount),
- })?,
- funds: vec![],
- })];
- if fee != 0 {
- messages.push(CosmosMsg::Wasm(WasmMsg::Execute {
- contract_addr: contract_addr.to_string(),
- msg: to_binary(&TokenMsg::Transfer {
- recipient: info.sender.to_string(),
- amount: Uint128::from(fee),
- })?,
- funds: vec![],
- }))
- }
- Ok(Response::new()
- .add_messages(messages)
- .add_attribute("action", "complete_transfer_native")
- .add_attribute("recipient", recipient)
- .add_attribute("contract", contract_addr)
- .add_attribute("amount", amount.to_string()))
- }
- }
- fn handle_complete_transfer_token_native(
- mut deps: DepsMut,
- _env: Env,
- _info: MessageInfo,
- emitter_chain: u16,
- emitter_address: Vec<u8>,
- data: &Vec<u8>,
- ) -> StdResult<Response> {
- 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::generic_err("invalid emitter"));
- }
- 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.addr_humanize(&target_address)?;
- let token_address = (&*token_address).get_address(0);
- receive_native(deps.storage, &token_address, Uint128::new(amount + fee))?;
- let mut messages = vec![CosmosMsg::Bank(BankMsg::Send {
- to_address: recipient.to_string(),
- amount: coins_after_tax(deps.branch(), vec![coin(amount, &denom)])?,
- })];
- if fee != 0 {
- messages.push(CosmosMsg::Bank(BankMsg::Send {
- to_address: recipient.to_string(),
- amount: coins_after_tax(deps, vec![coin(fee, &denom)])?,
- }));
- }
- 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()))
- }
- fn handle_initiate_transfer(
- deps: DepsMut,
- env: Env,
- info: MessageInfo,
- asset: Asset,
- recipient_chain: u16,
- recipient: Vec<u8>,
- fee: Uint128,
- 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,
- nonce,
- ),
- AssetInfo::NativeToken { ref denom } => handle_initiate_transfer_native_token(
- deps,
- env,
- info,
- denom.clone(),
- asset.amount,
- recipient_chain,
- recipient,
- fee,
- nonce,
- ),
- }
- }
- fn handle_initiate_transfer_token(
- mut deps: DepsMut,
- env: Env,
- info: MessageInfo,
- asset: HumanAddr,
- mut amount: Uint128,
- recipient_chain: u16,
- recipient: Vec<u8>,
- mut fee: Uint128,
- nonce: u32,
- ) -> StdResult<Response> {
- if recipient_chain == CHAIN_ID {
- return ContractError::SameSourceAndTarget.std_err();
- }
- if amount.is_zero() {
- return ContractError::AmountTooLow.std_err();
- }
- let asset_chain: u16;
- let asset_address: Vec<u8>;
- let cfg: ConfigInfo = config_read(deps.storage).load()?;
- let asset_canonical: CanonicalAddr = deps.api.addr_canonicalize(&asset)?;
- let mut messages: Vec<CosmosMsg> = vec![];
- let mut submessages: Vec<SubMsg> = vec![];
- match wrapped_asset_address_read(deps.storage).load(asset_canonical.as_slice()) {
- 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 {})?,
- });
- let wrapped_token_info: WrappedAssetInfoResponse =
- deps.querier.custom_query(&request)?;
- asset_chain = wrapped_token_info.asset_chain;
- asset_address = wrapped_token_info.asset_address.as_slice().to_vec();
- let transfer_info = TransferInfo {
- token_chain: asset_chain,
- token_address: asset_address.clone(),
- 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(&WormholeExecuteMsg::PostMessage {
- message: Binary::from(token_bridge_message.serialize()),
- nonce,
- })?,
- // forward coins sent to this message
- funds: coins_after_tax(deps.branch(), info.funds.clone())?,
- }));
- }
- Err(_) => {
- // 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
- 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(),
- );
- // 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,
- ));
- asset_address = extend_address_to_32(&asset_canonical);
- asset_chain = CHAIN_ID;
- // 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());
- let transfer_info = TransferInfo {
- token_chain: asset_chain,
- token_address: asset_address.clone(),
- amount: (0, amount.u128()),
- recipient_chain,
- recipient: recipient.clone(),
- fee: (0, fee.u128()),
- };
- // 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(),
- })?,
- }))?;
- // 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(),
- token_address: asset,
- token_canonical: asset_canonical.clone(),
- message: transfer_info.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(
- &deps.api.addr_canonicalize(&info.sender.as_str())?,
- )),
- )
- .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()))
- }
- /// 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_initiate_transfer_native_token(
- deps: DepsMut,
- env: Env,
- info: MessageInfo,
- denom: String,
- amount: Uint128,
- recipient_chain: u16,
- recipient: Vec<u8>,
- fee: Uint128,
- nonce: u32,
- ) -> StdResult<Response> {
- 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!("{}:{}", 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 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(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(),
- };
- let sender = deps.api.addr_canonicalize(&info.sender.as_str())?;
- messages.push(CosmosMsg::Wasm(WasmMsg::Execute {
- contract_addr: cfg.wormhole_contract,
- msg: to_binary(&WormholeExecuteMsg::PostMessage {
- message: Binary::from(token_bridge_message.serialize()),
- nonce,
- })?,
- funds: coins_after_tax(deps, info.funds.clone())?,
- }));
- Ok(Response::new()
- .add_messages(messages)
- .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(&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())?)
- }
- }
- }
- pub fn query_wrapped_registry(
- deps: Deps,
- chain: u16,
- address: &[u8],
- ) -> StdResult<WrappedRegistryResponse> {
- let asset_id = build_asset_id(chain, address);
- // Check if this asset is already deployed
- match wrapped_asset_read(deps.storage).load(&asset_id) {
- Ok(address) => Ok(WrappedRegistryResponse { address }),
- Err(_) => ContractError::AssetNotFound.std_err(),
- }
- }
- fn build_asset_id(chain: u16, address: &[u8]) -> Vec<u8> {
- let mut asset_id: Vec<u8> = vec![];
- asset_id.extend_from_slice(&chain.to_be_bytes());
- asset_id.extend_from_slice(address);
- let mut hasher = Keccak256::new();
- hasher.update(asset_id);
- 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)]
- mod tests {
- use cosmwasm_std::{
- to_binary,
- Binary,
- StdResult,
- };
- #[test]
- fn test_me() -> StdResult<()> {
- let x = vec![
- 1u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 96u8, 180u8, 94u8, 195u8, 0u8, 0u8,
- 0u8, 1u8, 0u8, 3u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 38u8,
- 229u8, 4u8, 215u8, 149u8, 163u8, 42u8, 54u8, 156u8, 236u8, 173u8, 168u8, 72u8, 220u8,
- 100u8, 90u8, 154u8, 159u8, 160u8, 215u8, 0u8, 91u8, 48u8, 44u8, 48u8, 44u8, 51u8, 44u8,
- 48u8, 44u8, 48u8, 44u8, 48u8, 44u8, 48u8, 44u8, 48u8, 44u8, 48u8, 44u8, 48u8, 44u8,
- 48u8, 44u8, 48u8, 44u8, 48u8, 44u8, 48u8, 44u8, 48u8, 44u8, 53u8, 55u8, 44u8, 52u8,
- 54u8, 44u8, 50u8, 53u8, 53u8, 44u8, 53u8, 48u8, 44u8, 50u8, 52u8, 51u8, 44u8, 49u8,
- 48u8, 54u8, 44u8, 49u8, 50u8, 50u8, 44u8, 49u8, 49u8, 48u8, 44u8, 49u8, 50u8, 53u8,
- 44u8, 56u8, 56u8, 44u8, 55u8, 51u8, 44u8, 49u8, 56u8, 57u8, 44u8, 50u8, 48u8, 55u8,
- 44u8, 49u8, 48u8, 52u8, 44u8, 56u8, 51u8, 44u8, 49u8, 49u8, 57u8, 44u8, 49u8, 50u8,
- 55u8, 44u8, 49u8, 57u8, 50u8, 44u8, 49u8, 52u8, 55u8, 44u8, 56u8, 57u8, 44u8, 48u8,
- 44u8, 48u8, 44u8, 48u8, 44u8, 48u8, 44u8, 48u8, 44u8, 48u8, 44u8, 48u8, 44u8, 48u8,
- 44u8, 48u8, 44u8, 48u8, 44u8, 48u8, 44u8, 48u8, 44u8, 48u8, 44u8, 48u8, 44u8, 48u8,
- 44u8, 48u8, 44u8, 48u8, 44u8, 48u8, 44u8, 48u8, 44u8, 48u8, 44u8, 48u8, 44u8, 48u8,
- 44u8, 48u8, 44u8, 48u8, 44u8, 48u8, 44u8, 48u8, 44u8, 48u8, 44u8, 48u8, 44u8, 48u8,
- 44u8, 48u8, 44u8, 51u8, 44u8, 50u8, 51u8, 50u8, 44u8, 48u8, 44u8, 51u8, 44u8, 48u8,
- 44u8, 48u8, 44u8, 48u8, 44u8, 48u8, 44u8, 48u8, 44u8, 48u8, 44u8, 48u8, 44u8, 48u8,
- 44u8, 48u8, 44u8, 48u8, 44u8, 48u8, 44u8, 48u8, 44u8, 53u8, 51u8, 44u8, 49u8, 49u8,
- 54u8, 44u8, 52u8, 56u8, 44u8, 49u8, 49u8, 54u8, 44u8, 49u8, 52u8, 57u8, 44u8, 49u8,
- 48u8, 56u8, 44u8, 49u8, 49u8, 51u8, 44u8, 56u8, 44u8, 48u8, 44u8, 50u8, 51u8, 50u8,
- 44u8, 52u8, 57u8, 44u8, 49u8, 53u8, 50u8, 44u8, 49u8, 44u8, 50u8, 56u8, 44u8, 50u8,
- 48u8, 51u8, 44u8, 50u8, 49u8, 50u8, 44u8, 50u8, 50u8, 49u8, 44u8, 50u8, 52u8, 49u8,
- 44u8, 56u8, 53u8, 44u8, 49u8, 48u8, 57u8, 93u8,
- ];
- let b = Binary::from(x.clone());
- let y = b.as_slice().to_vec();
- assert_eq!(x, y);
- Ok(())
- }
- }
|