terra: cw20 migrations
Change-Id: I3c75291a0d3016a197a15d9ee0717b16b3b479d3
This commit is contained in:
parent
3491a1370b
commit
c832b123fc
|
@ -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,
|
||||||
})
|
})
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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(
|
||||||
|
|
|
@ -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>,
|
||||||
|
|
Loading…
Reference in New Issue