From c8bc1b57cc46e66f79d9ce82b23c65f03f767dc5 Mon Sep 17 00:00:00 2001 From: Alwin Peng Date: Tue, 27 Jul 2021 15:00:00 -0400 Subject: [PATCH] decimal shifting + max outstanding in terra token bridge Change-Id: I830ea33abfd0b836ea5e69a77678f962cd771e01 --- terra/contracts/cw20-wrapped/src/contract.rs | 4 +- terra/contracts/cw20-wrapped/src/msg.rs | 2 + terra/contracts/token-bridge/src/contract.rs | 67 +++++++++++++++----- terra/contracts/token-bridge/src/state.rs | 31 ++++++++- terra/contracts/wormhole/src/byte_utils.rs | 9 +++ 5 files changed, 93 insertions(+), 20 deletions(-) diff --git a/terra/contracts/cw20-wrapped/src/contract.rs b/terra/contracts/cw20-wrapped/src/contract.rs index df427f87..8922ffbe 100644 --- a/terra/contracts/cw20-wrapped/src/contract.rs +++ b/terra/contracts/cw20-wrapped/src/contract.rs @@ -23,8 +23,8 @@ pub fn init( ) -> StdResult { // store token info using cw20-base format let data = TokenInfo { - name: String::from("Wormhole Wrapped"), - symbol: String::from("WWT"), + name: msg.name, + symbol: msg.symbol, decimals: msg.decimals, total_supply: Uint128(0), // set creator as minter diff --git a/terra/contracts/cw20-wrapped/src/msg.rs b/terra/contracts/cw20-wrapped/src/msg.rs index ec5f7175..30f36981 100644 --- a/terra/contracts/cw20-wrapped/src/msg.rs +++ b/terra/contracts/cw20-wrapped/src/msg.rs @@ -7,6 +7,8 @@ use cw20::Expiration; #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] pub struct InitMsg { + pub name: String, + pub symbol: String, pub asset_chain: u16, pub asset_address: Binary, pub decimals: u8, diff --git a/terra/contracts/token-bridge/src/contract.rs b/terra/contracts/token-bridge/src/contract.rs index 9a19c628..59106b6d 100644 --- a/terra/contracts/token-bridge/src/contract.rs +++ b/terra/contracts/token-bridge/src/contract.rs @@ -1,17 +1,14 @@ use crate::msg::WrappedRegistryResponse; -use cosmwasm_std::{ - log, to_binary, Api, Binary, CanonicalAddr, CosmosMsg, Env, Extern, HandleResponse, HumanAddr, - InitResponse, Querier, QueryRequest, StdError, StdResult, Storage, Uint128, WasmMsg, WasmQuery, -}; +use cosmwasm_std::{log, to_binary, Api, CanonicalAddr, CosmosMsg, Env, Extern, HandleResponse, HumanAddr, InitResponse, Querier, QueryRequest, StdError, StdResult, Storage, Uint128, WasmMsg, WasmQuery, Binary}; use crate::msg::{HandleMsg, InitMsg, QueryMsg}; use crate::state::{ - bridge_contracts, bridge_contracts_read, config, config_read, wrapped_asset, - wrapped_asset_address, wrapped_asset_address_read, wrapped_asset_read, Action, AssetMeta, - ConfigInfo, RegisterChain, TokenBridgeMessage, TransferInfo, + bridge_contracts, bridge_contracts_read, config, config_read, receive_native, send_native, + wrapped_asset, wrapped_asset_address, wrapped_asset_address_read, wrapped_asset_read, Action, + AssetMeta, ConfigInfo, RegisterChain, TokenBridgeMessage, TransferInfo, }; -use wormhole::byte_utils::ByteUtils; use wormhole::byte_utils::{extend_address_to_32, extend_string_to_32}; +use wormhole::byte_utils::{get_string_from_32, ByteUtils}; use wormhole::error::ContractError; use cw20_base::msg::HandleMsg as TokenMsg; @@ -30,6 +27,7 @@ use cw20_wrapped::msg::QueryMsg as WrappedQuery; use cw20_wrapped::msg::{InitHook, WrappedAssetInfoResponse}; use sha3::{Digest, Keccak256}; +use std::cmp::{min, max}; // Chain ID of Terra const CHAIN_ID: u16 = 3; @@ -163,9 +161,11 @@ fn handle_attest_meta( messages: vec![CosmosMsg::Wasm(WasmMsg::Instantiate { 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: meta.decimals, + decimals: min(meta.decimals, 8u8), mint: None, init_hook: Some(InitHook { contract_addr: env.contract.address, @@ -190,13 +190,13 @@ fn handle_create_asset_meta( ) -> StdResult { let cfg = config_read(&deps.storage).load()?; - let request = QueryRequest::<()>::Wasm(WasmQuery::Smart { + let request = QueryRequest::Wasm(WasmQuery::Smart { contract_addr: asset_address.clone(), msg: to_binary(&TokenQuery::TokenInfo {})?, }); let asset_canonical = deps.api.canonical_address(asset_address)?; - let token_info: TokenInfoResponse = deps.querier.custom_query(&request)?; + let token_info: TokenInfoResponse = deps.querier.query(&request)?; let meta: AssetMeta = AssetMeta { token_chain: CHAIN_ID, @@ -345,7 +345,7 @@ fn handle_complete_transfer( 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; + let (not_supported_fee, mut fee) = transfer_info.fee; amount -= fee; @@ -407,6 +407,20 @@ fn handle_complete_transfer( let recipient = deps.api.human_address(&target_address)?; let contract_addr = deps.api.human_address(&token_address)?; + receive_native(&mut deps.storage, &token_address, Uint128(amount + fee))?; + + // undo normalization to 8 decimals + let token_info: TokenInfoResponse = + deps.querier.query(&QueryRequest::Wasm(WasmQuery::Smart { + contract_addr: contract_addr.clone(), + msg: to_binary(&TokenQuery::TokenInfo {})?, + }))?; + + let decimals = token_info.decimals; + let multiplier = 10u128.pow((max(decimals, 8u8) - 8u8) as u32); + amount *= multiplier; + fee *= multiplier; + let mut messages = vec![CosmosMsg::Wasm(WasmMsg::Execute { contract_addr: contract_addr.clone(), msg: to_binary(&TokenMsg::Transfer { @@ -444,15 +458,15 @@ fn handle_initiate_transfer( deps: &mut Extern, env: Env, asset: HumanAddr, - amount: Uint128, + mut amount: Uint128, recipient_chain: u16, recipient: Vec, - fee: Uint128, + mut fee: Uint128, nonce: u32, ) -> StdResult { - // if recipient_chain == CHAIN_ID { - // return ContractError::SameSourceAndTarget.std_err(); - // } + if recipient_chain == CHAIN_ID { + return ContractError::SameSourceAndTarget.std_err(); + } if amount.is_zero() { return ContractError::AmountTooLow.std_err(); @@ -491,6 +505,19 @@ fn handle_initiate_transfer( asset_address = wrapped_token_info.asset_address.as_slice().to_vec(); } 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(amount.u128() - (amount.u128() % multiplier)); + fee = Uint128(fee.u128() - (fee.u128() % multiplier)); + // This is a regular asset, transfer its balance messages.push(CosmosMsg::Wasm(WasmMsg::Execute { contract_addr: asset, @@ -503,6 +530,12 @@ fn handle_initiate_transfer( })); asset_address = extend_address_to_32(&asset_canonical); asset_chain = CHAIN_ID; + + // convert to normalized amounts before recording & posting vaa + amount = Uint128(amount.u128() / multiplier); + fee = Uint128(fee.u128() / multiplier); + + send_native(&mut deps.storage, &asset_canonical, amount)?; } }; diff --git a/terra/contracts/token-bridge/src/state.rs b/terra/contracts/token-bridge/src/state.rs index 08896513..97dbd58e 100644 --- a/terra/contracts/token-bridge/src/state.rs +++ b/terra/contracts/token-bridge/src/state.rs @@ -1,7 +1,7 @@ use schemars::JsonSchema; use serde::{Deserialize, Serialize}; -use cosmwasm_std::{Binary, HumanAddr, StdResult, Storage}; +use cosmwasm_std::{CanonicalAddr, HumanAddr, StdError, StdResult, Storage, Uint128}; use cosmwasm_storage::{ bucket, bucket_read, singleton, singleton_read, Bucket, ReadonlyBucket, ReadonlySingleton, Singleton, @@ -13,6 +13,7 @@ pub static CONFIG_KEY: &[u8] = b"config"; pub static WRAPPED_ASSET_KEY: &[u8] = b"wrapped_asset"; pub static WRAPPED_ASSET_ADDRESS_KEY: &[u8] = b"wrapped_asset_address"; pub static BRIDGE_CONTRACTS: &[u8] = b"bridge_contracts"; +pub static NATIVE_COUNTER: &[u8] = b"native_counter"; // Guardian set information #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] @@ -57,6 +58,34 @@ pub fn wrapped_asset_address_read(storage: &S) -> ReadonlyBucket( + storage: &mut S, + asset_address: &CanonicalAddr, + amount: Uint128, +) -> StdResult<()> { + let mut counter_bucket = bucket(NATIVE_COUNTER, storage); + let new_total = amount + + counter_bucket + .load(asset_address.as_slice()) + .unwrap_or(Uint128::zero()); + if new_total > Uint128(u64::MAX as u128) { + return Err(StdError::generic_err( + "transfer exceeds max outstanding bridged token amount", + )); + } + counter_bucket.save(asset_address.as_slice(), &new_total) +} + +pub fn receive_native( + storage: &mut S, + asset_address: &CanonicalAddr, + amount: Uint128, +) -> StdResult<()> { + let mut counter_bucket = bucket(NATIVE_COUNTER, storage); + let total: Uint128 = counter_bucket.load(asset_address.as_slice())?; + counter_bucket.save(asset_address.as_slice(), &(total - amount)?) +} + pub struct Action; impl Action { diff --git a/terra/contracts/wormhole/src/byte_utils.rs b/terra/contracts/wormhole/src/byte_utils.rs index 6dfe9f2b..b12da86e 100644 --- a/terra/contracts/wormhole/src/byte_utils.rs +++ b/terra/contracts/wormhole/src/byte_utils.rs @@ -65,3 +65,12 @@ pub fn extend_string_to_32(s: &String) -> StdResult> { result.extend(bytes); Ok(result) } + +pub fn get_string_from_32(v: &Vec) -> StdResult { + let mut idx = 31usize; + while v[idx] == 0 { + idx -= 1 + } + String::from_utf8(v[..idx + 1].to_vec()) + .or_else(|_| Err(StdError::generic_err("could not parse string"))) +}