terra: cw20 migrations

Change-Id: I3c75291a0d3016a197a15d9ee0717b16b3b479d3
This commit is contained in:
Reisen 2021-10-27 09:56:02 +00:00 committed by David Paryente
parent 3491a1370b
commit c832b123fc
4 changed files with 195 additions and 45 deletions

View File

@ -42,6 +42,7 @@ use crate::{
msg::{ msg::{
ExecuteMsg, ExecuteMsg,
InstantiateMsg, InstantiateMsg,
MigrateMsg,
QueryMsg, QueryMsg,
WrappedAssetInfoResponse, WrappedAssetInfoResponse,
}, },
@ -219,11 +220,16 @@ pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult<Binary> {
} }
} }
#[cfg_attr(not(feature = "library"), entry_point)]
pub fn migrate(deps: DepsMut, _env: Env, _msg: MigrateMsg) -> StdResult<Response> {
Ok(Response::new())
}
pub fn query_token_info(deps: Deps) -> StdResult<TokenInfoResponse> { pub fn query_token_info(deps: Deps) -> StdResult<TokenInfoResponse> {
let info = TOKEN_INFO.load(deps.storage)?; let info = TOKEN_INFO.load(deps.storage)?;
Ok(TokenInfoResponse { Ok(TokenInfoResponse {
name: String::from("Wormhole:") + info.name.as_str(), name: info.name + " (Wormhole)",
symbol: String::from("wh") + info.symbol.as_str(), symbol: info.symbol,
decimals: info.decimals, decimals: info.decimals,
total_supply: info.total_supply, total_supply: info.total_supply,
}) })

View File

@ -37,6 +37,10 @@ pub struct InitMint {
pub amount: Uint128, 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)] #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
#[serde(rename_all = "snake_case")] #[serde(rename_all = "snake_case")]
pub enum ExecuteMsg { pub enum ExecuteMsg {

View File

@ -13,10 +13,13 @@ use cosmwasm_std::{
Empty, Empty,
Env, Env,
MessageInfo, MessageInfo,
Order,
QueryRequest, QueryRequest,
Reply,
Response, Response,
StdError, StdError,
StdResult, StdResult,
SubMsg,
Uint128, Uint128,
WasmMsg, WasmMsg,
WasmQuery, WasmQuery,
@ -43,12 +46,14 @@ use crate::{
wrapped_asset_read, wrapped_asset_read,
wrapped_asset_seq, wrapped_asset_seq,
wrapped_asset_seq_read, wrapped_asset_seq_read,
wrapped_transfer_tmp,
Action, Action,
AssetMeta, AssetMeta,
ConfigInfo, ConfigInfo,
RegisterChain, RegisterChain,
TokenBridgeMessage, TokenBridgeMessage,
TransferInfo, TransferInfo,
TransferState,
UpgradeContract, UpgradeContract,
}, },
}; };
@ -79,7 +84,10 @@ use wormhole::state::{
ParsedVAA, ParsedVAA,
}; };
use cw20::TokenInfoResponse; use cw20::{
BalanceResponse,
TokenInfoResponse,
};
use cw20_wrapped::msg::{ use cw20_wrapped::msg::{
ExecuteMsg as WrappedMsg, ExecuteMsg as WrappedMsg,
@ -97,9 +105,12 @@ use sha3::{
Digest, Digest,
Keccak256, Keccak256,
}; };
use std::cmp::{ use std::{
max, cmp::{
min, max,
min,
},
str::FromStr,
}; };
type HumanAddr = String; type HumanAddr = String;
@ -110,8 +121,27 @@ const CHAIN_ID: u16 = 3;
const WRAPPED_ASSET_UPDATING: &str = "updating"; const WRAPPED_ASSET_UPDATING: &str = "updating";
#[cfg_attr(not(feature = "library"), entry_point)] #[cfg_attr(not(feature = "library"), entry_point)]
pub fn migrate(_deps: DepsMut, _env: Env, _msg: MigrateMsg) -> StdResult<Response> { pub fn migrate(deps: DepsMut, _env: Env, _msg: MigrateMsg) -> StdResult<Response> {
Ok(Response::default()) 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)] #[cfg_attr(not(feature = "library"), entry_point)]
@ -133,6 +163,58 @@ pub fn instantiate(
Ok(Response::default()) 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>> { pub fn coins_after_tax(deps: DepsMut, coins: Vec<Coin>) -> StdResult<Vec<Coin>> {
let mut res = vec![]; let mut res = vec![];
for coin in coins { for coin in coins {
@ -805,9 +887,6 @@ fn handle_initiate_transfer_token(
if amount.is_zero() { if amount.is_zero() {
return ContractError::AmountTooLow.std_err(); return ContractError::AmountTooLow.std_err();
} }
if fee > amount {
return Err(StdError::generic_err("fee greater than sent amount"));
}
let asset_chain: u16; let asset_chain: u16;
let asset_address: Vec<u8>; let asset_address: Vec<u8>;
@ -816,9 +895,15 @@ fn handle_initiate_transfer_token(
let asset_canonical: CanonicalAddr = deps.api.addr_canonicalize(&asset)?; let asset_canonical: CanonicalAddr = deps.api.addr_canonicalize(&asset)?;
let mut messages: Vec<CosmosMsg> = vec![]; let mut messages: Vec<CosmosMsg> = vec![];
let mut submessages: Vec<SubMsg> = vec![];
match wrapped_asset_address_read(deps.storage).load(asset_canonical.as_slice()) { match wrapped_asset_address_read(deps.storage).load(asset_canonical.as_slice()) {
Ok(_) => { 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 // This is a deployed wrapped asset, burn it
messages.push(CosmosMsg::Wasm(WasmMsg::Execute { messages.push(CosmosMsg::Wasm(WasmMsg::Execute {
contract_addr: asset.clone(), contract_addr: asset.clone(),
@ -836,6 +921,30 @@ fn handle_initiate_transfer_token(
deps.querier.custom_query(&request)?; deps.querier.custom_query(&request)?;
asset_chain = wrapped_token_info.asset_chain; asset_chain = wrapped_token_info.asset_chain;
asset_address = wrapped_token_info.asset_address.as_slice().to_vec(); 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(_) => { Err(_) => {
// normalize amount to 8 decimals when it sent over the wormhole // 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 decimals = token_info.decimals;
let multiplier = 10u128.pow((max(decimals, 8u8) - 8u8) as u32); let multiplier = 10u128.pow((max(decimals, 8u8) - 8u8) as u32);
// chop off dust // chop off dust
amount = Uint128::new( amount = Uint128::new(
amount amount
@ -854,6 +964,7 @@ fn handle_initiate_transfer_token(
.checked_sub(amount.u128().checked_rem(multiplier).unwrap()) .checked_sub(amount.u128().checked_rem(multiplier).unwrap())
.unwrap(), .unwrap(),
); );
fee = Uint128::new( fee = Uint128::new(
fee.u128() fee.u128()
.checked_sub(fee.u128().checked_rem(multiplier).unwrap()) .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 // This is a regular asset, transfer its balance
messages.push(CosmosMsg::Wasm(WasmMsg::Execute { submessages.push(SubMsg::reply_on_success(
contract_addr: asset, CosmosMsg::Wasm(WasmMsg::Execute {
msg: to_binary(&TokenMsg::TransferFrom { contract_addr: asset.clone(),
owner: info.sender.to_string(), msg: to_binary(&TokenMsg::TransferFrom {
recipient: env.contract.address.to_string(), owner: info.sender.to_string(),
amount, recipient: env.contract.address.to_string(),
})?, amount,
funds: vec![], })?,
})); funds: vec![],
}),
1,
));
asset_address = extend_address_to_32(&asset_canonical); asset_address = extend_address_to_32(&asset_canonical);
asset_chain = CHAIN_ID; asset_chain = CHAIN_ID;
@ -877,36 +992,40 @@ fn handle_initiate_transfer_token(
amount = Uint128::new(amount.u128().checked_div(multiplier).unwrap()); amount = Uint128::new(amount.u128().checked_div(multiplier).unwrap());
fee = Uint128::new(fee.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() Ok(Response::new()
.add_messages(messages) .add_messages(messages)
.add_submessages(submessages)
.add_attribute("transfer.token_chain", asset_chain.to_string()) .add_attribute("transfer.token_chain", asset_chain.to_string())
.add_attribute("transfer.token", hex::encode(asset_address)) .add_attribute("transfer.token", hex::encode(asset_address))
.add_attribute( .add_attribute(

View File

@ -27,6 +27,7 @@ use wormhole::byte_utils::ByteUtils;
type HumanAddr = String; type HumanAddr = String;
pub static CONFIG_KEY: &[u8] = b"config"; 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_KEY: &[u8] = b"wrapped_asset";
pub static WRAPPED_ASSET_SEQ_KEY: &[u8] = b"wrapped_seq_asset"; pub static WRAPPED_ASSET_SEQ_KEY: &[u8] = b"wrapped_seq_asset";
pub static WRAPPED_ASSET_ADDRESS_KEY: &[u8] = b"wrapped_asset_address"; 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<Vec<u
bucket_read(storage, WRAPPED_ASSET_ADDRESS_KEY) bucket_read(storage, WRAPPED_ASSET_ADDRESS_KEY)
} }
type Serialized128 = String;
/// Structure to keep track of an active CW20 transfer, required to pass state through to the reply
/// handler for submessages during a transfer.
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
pub struct TransferState {
pub account: String,
pub message: Vec<u8>,
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<TransferState> {
singleton(storage, TRANSFER_TMP_KEY)
}
pub fn send_native( pub fn send_native(
storage: &mut dyn Storage, storage: &mut dyn Storage,
asset_address: &CanonicalAddr, asset_address: &CanonicalAddr,
@ -161,6 +181,7 @@ impl TokenBridgeMessage {
// 98 u16 recipient_chain // 98 u16 recipient_chain
// 100 u256 fee // 100 u256 fee
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
pub struct TransferInfo { pub struct TransferInfo {
pub amount: (u128, u128), pub amount: (u128, u128),
pub token_address: Vec<u8>, pub token_address: Vec<u8>,