From c832b123fcfb017d55086cb4d71241370ed270c6 Mon Sep 17 00:00:00 2001 From: Reisen Date: Wed, 27 Oct 2021 09:56:02 +0000 Subject: [PATCH] terra: cw20 migrations Change-Id: I3c75291a0d3016a197a15d9ee0717b16b3b479d3 --- terra/contracts/cw20-wrapped/src/contract.rs | 10 +- terra/contracts/cw20-wrapped/src/msg.rs | 4 + terra/contracts/token-bridge/src/contract.rs | 205 +++++++++++++++---- terra/contracts/token-bridge/src/state.rs | 21 ++ 4 files changed, 195 insertions(+), 45 deletions(-) diff --git a/terra/contracts/cw20-wrapped/src/contract.rs b/terra/contracts/cw20-wrapped/src/contract.rs index 52fea7d20..5d32f33ec 100644 --- a/terra/contracts/cw20-wrapped/src/contract.rs +++ b/terra/contracts/cw20-wrapped/src/contract.rs @@ -42,6 +42,7 @@ use crate::{ msg::{ ExecuteMsg, InstantiateMsg, + MigrateMsg, QueryMsg, WrappedAssetInfoResponse, }, @@ -219,11 +220,16 @@ pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { } } +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn migrate(deps: DepsMut, _env: Env, _msg: MigrateMsg) -> StdResult { + Ok(Response::new()) +} + pub fn query_token_info(deps: Deps) -> StdResult { let info = TOKEN_INFO.load(deps.storage)?; Ok(TokenInfoResponse { - name: String::from("Wormhole:") + info.name.as_str(), - symbol: String::from("wh") + info.symbol.as_str(), + name: info.name + " (Wormhole)", + symbol: info.symbol, decimals: info.decimals, total_supply: info.total_supply, }) diff --git a/terra/contracts/cw20-wrapped/src/msg.rs b/terra/contracts/cw20-wrapped/src/msg.rs index da5f64c8a..5e2cb9c3e 100644 --- a/terra/contracts/cw20-wrapped/src/msg.rs +++ b/terra/contracts/cw20-wrapped/src/msg.rs @@ -37,6 +37,10 @@ pub struct InitMint { pub amount: Uint128, } +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub struct MigrateMsg {} + #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] #[serde(rename_all = "snake_case")] pub enum ExecuteMsg { diff --git a/terra/contracts/token-bridge/src/contract.rs b/terra/contracts/token-bridge/src/contract.rs index c47937e0d..9d7f40e2e 100644 --- a/terra/contracts/token-bridge/src/contract.rs +++ b/terra/contracts/token-bridge/src/contract.rs @@ -13,10 +13,13 @@ use cosmwasm_std::{ Empty, Env, MessageInfo, + Order, QueryRequest, + Reply, Response, StdError, StdResult, + SubMsg, Uint128, WasmMsg, WasmQuery, @@ -43,12 +46,14 @@ use crate::{ wrapped_asset_read, wrapped_asset_seq, wrapped_asset_seq_read, + wrapped_transfer_tmp, Action, AssetMeta, ConfigInfo, RegisterChain, TokenBridgeMessage, TransferInfo, + TransferState, UpgradeContract, }, }; @@ -79,7 +84,10 @@ use wormhole::state::{ ParsedVAA, }; -use cw20::TokenInfoResponse; +use cw20::{ + BalanceResponse, + TokenInfoResponse, +}; use cw20_wrapped::msg::{ ExecuteMsg as WrappedMsg, @@ -97,9 +105,12 @@ use sha3::{ Digest, Keccak256, }; -use std::cmp::{ - max, - min, +use std::{ + cmp::{ + max, + min, + }, + str::FromStr, }; type HumanAddr = String; @@ -110,8 +121,27 @@ 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 { - Ok(Response::default()) +pub fn migrate(deps: DepsMut, _env: Env, _msg: MigrateMsg) -> StdResult { + 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: 3, + 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)] @@ -133,6 +163,58 @@ pub fn instantiate( 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 { + 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) -> StdResult> { let mut res = vec![]; for coin in coins { @@ -805,9 +887,6 @@ fn handle_initiate_transfer_token( if amount.is_zero() { return ContractError::AmountTooLow.std_err(); } - if fee > amount { - return Err(StdError::generic_err("fee greater than sent amount")); - } let asset_chain: u16; let asset_address: Vec; @@ -816,9 +895,15 @@ fn handle_initiate_transfer_token( let asset_canonical: CanonicalAddr = deps.api.addr_canonicalize(&asset)?; let mut messages: Vec = vec![]; + let mut submessages: Vec = 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(), @@ -836,6 +921,30 @@ fn handle_initiate_transfer_token( 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 @@ -847,6 +956,7 @@ fn handle_initiate_transfer_token( let decimals = token_info.decimals; let multiplier = 10u128.pow((max(decimals, 8u8) - 8u8) as u32); + // chop off dust amount = Uint128::new( amount @@ -854,6 +964,7 @@ fn handle_initiate_transfer_token( .checked_sub(amount.u128().checked_rem(multiplier).unwrap()) .unwrap(), ); + fee = Uint128::new( fee.u128() .checked_sub(fee.u128().checked_rem(multiplier).unwrap()) @@ -861,15 +972,19 @@ fn handle_initiate_transfer_token( ); // This is a regular asset, transfer its balance - messages.push(CosmosMsg::Wasm(WasmMsg::Execute { - contract_addr: asset, - msg: to_binary(&TokenMsg::TransferFrom { - owner: info.sender.to_string(), - recipient: env.contract.address.to_string(), - amount, - })?, - funds: vec![], - })); + 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; @@ -877,36 +992,40 @@ fn handle_initiate_transfer_token( amount = Uint128::new(amount.u128().checked_div(multiplier).unwrap()); fee = Uint128::new(fee.u128().checked_div(multiplier).unwrap()); - send_native(deps.storage, &asset_canonical, amount)?; + 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, + })?; } }; - 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())?, - })); - 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( diff --git a/terra/contracts/token-bridge/src/state.rs b/terra/contracts/token-bridge/src/state.rs index 680eb3c83..1142cea19 100644 --- a/terra/contracts/token-bridge/src/state.rs +++ b/terra/contracts/token-bridge/src/state.rs @@ -27,6 +27,7 @@ use wormhole::byte_utils::ByteUtils; type HumanAddr = String; pub static CONFIG_KEY: &[u8] = b"config"; +pub static TRANSFER_TMP_KEY: &[u8] = b"transfer_tmp"; pub static WRAPPED_ASSET_KEY: &[u8] = b"wrapped_asset"; pub static WRAPPED_ASSET_SEQ_KEY: &[u8] = b"wrapped_seq_asset"; pub static WRAPPED_ASSET_ADDRESS_KEY: &[u8] = b"wrapped_asset_address"; @@ -93,6 +94,25 @@ pub fn wrapped_asset_address_read(storage: &dyn Storage) -> ReadonlyBucket, + pub multiplier: Serialized128, + pub nonce: u32, + pub previous_balance: Serialized128, + pub token_address: HumanAddr, + pub token_canonical: CanonicalAddr, +} + +pub fn wrapped_transfer_tmp(storage: &mut dyn Storage) -> Singleton { + singleton(storage, TRANSFER_TMP_KEY) +} + pub fn send_native( storage: &mut dyn Storage, asset_address: &CanonicalAddr, @@ -161,6 +181,7 @@ impl TokenBridgeMessage { // 98 u16 recipient_chain // 100 u256 fee +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] pub struct TransferInfo { pub amount: (u128, u128), pub token_address: Vec,