initial terra contracts for wormhole v2

Change-Id: Ie28f1e7ce381fcbf33de609bc6c8465679cd2a43
This commit is contained in:
Alwin 2021-05-31 09:13:59 -04:00
parent e1c2f73e29
commit 16cc520187
19 changed files with 1672 additions and 411 deletions

150
terra/Cargo.lock generated
View File

@ -2,11 +2,11 @@
# It is not intended for manual editing.
[[package]]
name = "addr2line"
version = "0.14.1"
version = "0.15.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a55f82cfe485775d02112886f4169bde0c5894d75e79ead7eafe7e40a25e45f7"
checksum = "03345e98af8f3d786b6d9f656ccfa6ac316d954e92bc4841f0bba20789d5fb5a"
dependencies = [
"gimli 0.23.0",
"gimli 0.24.0",
]
[[package]]
@ -35,11 +35,12 @@ checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
[[package]]
name = "backtrace"
version = "0.3.56"
version = "0.3.59"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9d117600f438b1707d4e4ae15d3595657288f8235a0eb593e80ecc98ab34e1bc"
checksum = "4717cfcbfaa661a0fd48f8453951837ae7e8f81e481fbb136e3202d72805a744"
dependencies = [
"addr2line",
"cc",
"cfg-if 1.0.0",
"libc",
"miniz_oxide",
@ -54,12 +55,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b41b7ea54a0c9d92199de89e20e58d49f02f8e699814ef3fdf266f6f748d15c7"
[[package]]
name = "bincode"
version = "1.3.1"
name = "bigint"
version = "4.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f30d3a39baa26f9651f17b375061f3233dde33424a8b72b0dbe93a68a0bc896d"
checksum = "c0e8c8a600052b52482eff2cf4d810e462fdff1f656ac1ecb6232132a1ed7def"
dependencies = [
"byteorder",
"crunchy",
]
[[package]]
name = "bincode"
version = "1.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad"
dependencies = [
"serde",
]
@ -82,9 +92,9 @@ dependencies = [
[[package]]
name = "blake3"
version = "0.3.7"
version = "0.3.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e9ff35b701f3914bdb8fad3368d822c766ef2858b2583198e41639b936f09d3f"
checksum = "b64485778c4f16a6a5a9d335e80d449ac6c70cdd6a06d2af18a6f6f775a125b3"
dependencies = [
"arrayref",
"arrayvec",
@ -119,9 +129,9 @@ checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610"
[[package]]
name = "cc"
version = "1.0.67"
version = "1.0.68"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e3c69b077ad434294d3ce9f1f6143a2a4b89a8a2d54ef813d85003a4fd1137fd"
checksum = "4a72c244c1ff497a746a7e1fb3d14bd08420ecda70c8f25c7112f2781652d787"
[[package]]
name = "cfg-if"
@ -194,10 +204,13 @@ dependencies = [
]
[[package]]
name = "cpuid-bool"
version = "0.1.2"
name = "cpufeatures"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8aebca1129a03dc6dc2b127edd729435bbc4a37e1d5f4d7513165089ceb02634"
checksum = "ed00c67cb5d0a7d64a44f6ad2668db7e7530311dd53ea79bcd4fb022c64911c8"
dependencies = [
"libc",
]
[[package]]
name = "cranelift-bforest"
@ -261,9 +274,9 @@ dependencies = [
[[package]]
name = "crossbeam-channel"
version = "0.5.0"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dca26ee1f8d361640700bde38b2c37d8c22b3ce2d360e1fc1c74ea4b0aa7d775"
checksum = "06ed27e177f16d65f0f0c22a213e17c696ace5dd64b14258b52f9417ccb52db4"
dependencies = [
"cfg-if 1.0.0",
"crossbeam-utils",
@ -282,9 +295,9 @@ dependencies = [
[[package]]
name = "crossbeam-epoch"
version = "0.9.3"
version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2584f639eb95fea8c798496315b297cf81b9b58b6d30ab066a75455333cf4b12"
checksum = "52fb27eab85b17fbb9f6fd667089e07d6a2eb8743d02639ee7f6a7a7729c9c94"
dependencies = [
"cfg-if 1.0.0",
"crossbeam-utils",
@ -295,15 +308,21 @@ dependencies = [
[[package]]
name = "crossbeam-utils"
version = "0.8.3"
version = "0.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e7e9d99fa91428effe99c5c6d4634cdeba32b8cf784fc428a2a687f61a952c49"
checksum = "4feb231f0d4d6af81aed15928e58ecf5816aa62a2393e2c82f46973e92a9a278"
dependencies = [
"autocfg",
"cfg-if 1.0.0",
"lazy_static",
]
[[package]]
name = "crunchy"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2f4a431c5c9f662e1200b7c7f02c34e91361150e382089a8f2dec3ba680cbda"
[[package]]
name = "crypto-mac"
version = "0.8.0"
@ -521,9 +540,9 @@ dependencies = [
[[package]]
name = "gimli"
version = "0.23.0"
version = "0.24.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f6503fe142514ca4799d4c26297c4248239fe8838d827db6bd6065c6ed29a6ce"
checksum = "0e4075386626662786ddb0ec9081e7c7eeb1ba31951f447ca780ef9f5d568189"
[[package]]
name = "group"
@ -609,9 +628,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]]
name = "libc"
version = "0.2.91"
version = "0.2.95"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8916b1f6ca17130ec6568feccee27c156ad12037880833a3b842a823236502e7"
checksum = "789da6d93f1b866ffe175afc5322a4d76c038605a1c3319bb57b06967ca98a36"
[[package]]
name = "lock_api"
@ -643,9 +662,9 @@ dependencies = [
[[package]]
name = "memoffset"
version = "0.6.1"
version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "157b4208e3059a8f9e78d559edc658e13df41410cb3ae03979c83130067fdd87"
checksum = "59accc507f1338036a0477ef61afdae33cde60840f4dfe481319ce3ad116ddf9"
dependencies = [
"autocfg",
]
@ -685,9 +704,9 @@ dependencies = [
[[package]]
name = "object"
version = "0.23.0"
version = "0.24.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a9a7ab5d64814df0fe4a4b5ead45ed6c5f181ee3ff04ba344313a6c80446c5d4"
checksum = "1a5b3dd1c072ee7963717671d1ca129f1048fda25edea6b752bfc71ac8854170"
[[package]]
name = "opaque-debug"
@ -737,9 +756,9 @@ dependencies = [
[[package]]
name = "proc-macro2"
version = "1.0.24"
version = "1.0.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e0704ee1a7e00d7bb417d0770ea303c1bccbabf0ef1667dae92b5967f5f8a71"
checksum = "f0d8caf72986c1a598726adc988bb5984792ef84f5ee5aa50209145ee8077038"
dependencies = [
"unicode-xid",
]
@ -778,9 +797,9 @@ dependencies = [
[[package]]
name = "rayon"
version = "1.5.0"
version = "1.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b0d8e0819fadc20c74ea8373106ead0600e3a67ef1fe8da56e39b9ae7275674"
checksum = "c06aca804d41dbc8ba42dfd964f0d01334eceb64314b9ecf7c5fad5188a06d90"
dependencies = [
"autocfg",
"crossbeam-deque",
@ -790,9 +809,9 @@ dependencies = [
[[package]]
name = "rayon-core"
version = "1.9.0"
version = "1.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ab346ac5921dc62ffa9f89b7a773907511cdfa5490c572ae9be1be33e8afa4a"
checksum = "d78120e2c850279833f1dd3582f730c4ab53ed95aeaaaa862a2a5c71b1656d8e"
dependencies = [
"crossbeam-channel",
"crossbeam-deque",
@ -809,9 +828,9 @@ checksum = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce"
[[package]]
name = "rustc-demangle"
version = "0.1.18"
version = "0.1.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6e3bad0ee36814ca07d7968269dd4b7ec89ec2da10c4bb613928d3077083c232"
checksum = "410f7acf3cb3a44527c5d9546bad4bf4e6c460915d5f9f2fc524498bfe8f70ce"
[[package]]
name = "rustc_version"
@ -874,9 +893,9 @@ checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3"
[[package]]
name = "serde"
version = "1.0.125"
version = "1.0.126"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "558dc50e1a5a5fa7112ca2ce4effcb321b0300c0d4ccf0776a9f60cd89031171"
checksum = "ec7505abeacaec74ae4778d9d9328fe5a5d04253220a85c4ee022239fc996d03"
dependencies = [
"serde_derive",
]
@ -911,9 +930,9 @@ dependencies = [
[[package]]
name = "serde_derive"
version = "1.0.125"
version = "1.0.126"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b093b7a2bb58203b5da3056c05b4ec1fed827dcfdb37347a8841695263b3d06d"
checksum = "963a7dbc9895aeac7ac90e74f34a5d5261828f79df35cbed41e10189d3804d43"
dependencies = [
"proc-macro2",
"quote",
@ -944,13 +963,13 @@ dependencies = [
[[package]]
name = "sha2"
version = "0.9.3"
version = "0.9.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fa827a14b29ab7f44778d14a88d3cb76e949c45083f7dbfa507d0cb699dc12de"
checksum = "b362ae5752fd2137731f9fa25fd4d9058af34666ca1966fb969119cc35719f12"
dependencies = [
"block-buffer",
"cfg-if 1.0.0",
"cpuid-bool",
"cpufeatures",
"digest 0.9.0",
"opaque-debug",
]
@ -1013,9 +1032,9 @@ checksum = "1e81da0851ada1f3e9d4312c704aa4f8806f0f9d69faaf8df2f3464b4a9437c2"
[[package]]
name = "syn"
version = "1.0.64"
version = "1.0.72"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3fd9d1e9976102a03c542daa2eff1b43f9d72306342f3f8b3ed5fb8908195d6f"
checksum = "a1e8cdbefb79a9a5a65e0db8b47b723ee907b7c7f8496c76a1770b5c310bab82"
dependencies = [
"proc-macro2",
"quote",
@ -1030,24 +1049,47 @@ checksum = "ab0e7238dcc7b40a7be719a25365910f6807bd864f4cce6b2e6b873658e2b19d"
[[package]]
name = "thiserror"
version = "1.0.24"
version = "1.0.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e0f4a65597094d4483ddaed134f409b2cb7c1beccf25201a9f73c719254fa98e"
checksum = "fa6f76457f59514c7eeb4e59d891395fab0b2fd1d40723ae737d64153392e9c6"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "1.0.24"
version = "1.0.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7765189610d8241a44529806d6fd1f2e0a08734313a35d5b3a556f92b381f3c0"
checksum = "8a36768c0fbf1bb15eca10defa29526bda730a2376c2ab4393ccfa16fb1a318d"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "token-bridge"
version = "0.1.0"
dependencies = [
"bigint",
"cosmwasm-std",
"cosmwasm-storage",
"cosmwasm-vm",
"cw20",
"cw20-base",
"cw20-wrapped",
"generic-array 0.14.4",
"hex",
"k256",
"lazy_static",
"schemars",
"serde",
"serde_json",
"sha3",
"thiserror",
"wormhole",
]
[[package]]
name = "typenum"
version = "1.13.0"
@ -1056,9 +1098,9 @@ checksum = "879f6906492a7cd215bfa4cf595b600146ccfac0c79bcbd1f3000162af5e8b06"
[[package]]
name = "unicode-xid"
version = "0.2.1"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564"
checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3"
[[package]]
name = "version_check"
@ -1231,6 +1273,6 @@ checksum = "85e60b0d1b5f99db2556934e21937020776a5d31520bf169e851ac44e6420214"
[[package]]
name = "zeroize"
version = "1.2.0"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "81a974bcdd357f0dca4d41677db03436324d45a4c9ed2d0b873a5a360ce41c36"
checksum = "4756f7db3f7b5574938c3eb1c117038b8e07f95ee6718c0efad4ac21508f1efd"

View File

@ -1,5 +1,5 @@
[workspace]
members = ["contracts/cw20-wrapped", "contracts/wormhole"]
members = ["contracts/cw20-wrapped", "contracts/wormhole", "contracts/token-bridge"]
[profile.release]
opt-level = 3

6
terra/build.sh Executable file
View File

@ -0,0 +1,6 @@
#!/bin/bash
docker run --rm -v "$(pwd)":/code \
--mount type=volume,source="$(basename "$(pwd)")_cache",target=/code/target \
--mount type=volume,source=registry_cache,target=/usr/local/cargo/registry \
cosmwasm/workspace-optimizer:0.10.7

View File

@ -7,7 +7,7 @@ use cw20::Expiration;
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
pub struct InitMsg {
pub asset_chain: u8,
pub asset_chain: u16,
pub asset_address: Binary,
pub decimals: u8,
pub mint: Option<InitMint>,
@ -105,7 +105,7 @@ pub enum QueryMsg {
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
pub struct WrappedAssetInfoResponse {
pub asset_chain: u8, // Asset chain id
pub asset_chain: u16, // Asset chain id
pub asset_address: Binary, // Asset smart contract address in the original chain
pub bridge: HumanAddr, // Bridge address, authorized to mint and burn wrapped tokens
}

View File

@ -9,7 +9,7 @@ pub const KEY_WRAPPED_ASSET: &[u8] = b"wrappedAsset";
// Created at initialization and reference original asset and bridge address
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
pub struct WrappedAssetInfo {
pub asset_chain: u8, // Asset chain id
pub asset_chain: u16, // Asset chain id
pub asset_address: Binary, // Asset smart contract address on the original chain
pub bridge: CanonicalAddr, // Bridge address, authorized to mint and burn wrapped tokens
}

View File

@ -0,0 +1,5 @@
[alias]
wasm = "build --release --target wasm32-unknown-unknown"
wasm-debug = "build --target wasm32-unknown-unknown"
unit-test = "test --lib --features backtraces"
integration-test = "test --test integration"

View File

@ -0,0 +1,36 @@
[package]
name = "token-bridge"
version = "0.1.0"
authors = ["Yuriy Savchenko <yuriy.savchenko@gmail.com>"]
edition = "2018"
description = "Wormhole token bridge"
[lib]
crate-type = ["cdylib", "rlib"]
[features]
backtraces = ["cosmwasm-std/backtraces"]
# use library feature to disable all init/handle/query exports
library = []
[dependencies]
cosmwasm-std = { version = "0.10.0" }
cosmwasm-storage = { version = "0.10.0" }
schemars = "0.7"
serde = { version = "1.0.103", default-features = false, features = ["derive"] }
cw20 = "0.2.2"
cw20-base = { version = "0.2.2", features = ["library"] }
cw20-wrapped = { path = "../cw20-wrapped", features = ["library"] }
wormhole = { path = "../wormhole", features = ["library"] }
thiserror = { version = "1.0.20" }
k256 = { version = "0.5.9", default-features = false, features = ["ecdsa"] }
sha3 = { version = "0.9.1", default-features = false }
generic-array = { version = "0.14.4" }
hex = "0.4.2"
lazy_static = "1.4.0"
bigint = "4"
[dev-dependencies]
cosmwasm-vm = { version = "0.10.0", default-features = false, features = ["default-cranelift"] }
serde_json = "1.0"

View File

@ -0,0 +1,67 @@
use cosmwasm_std::{CanonicalAddr, StdError, StdResult};
pub trait ByteUtils {
fn get_u8(&self, index: usize) -> u8;
fn get_u16(&self, index: usize) -> u16;
fn get_u32(&self, index: usize) -> u32;
fn get_u64(&self, index: usize) -> u64;
fn get_u128_be(&self, index: usize) -> u128;
/// High 128 then low 128
fn get_u256(&self, index: usize) -> (u128, u128);
fn get_address(&self, index: usize) -> CanonicalAddr;
fn get_bytes32(&self, index: usize) -> &[u8];
}
impl ByteUtils for &[u8] {
fn get_u8(&self, index: usize) -> u8 {
self[index]
}
fn get_u16(&self, index: usize) -> u16 {
let mut bytes: [u8; 16 / 8] = [0; 16 / 8];
bytes.copy_from_slice(&self[index..index + 2]);
u16::from_be_bytes(bytes)
}
fn get_u32(&self, index: usize) -> u32 {
let mut bytes: [u8; 32 / 8] = [0; 32 / 8];
bytes.copy_from_slice(&self[index..index + 4]);
u32::from_be_bytes(bytes)
}
fn get_u64(&self, index: usize) -> u64 {
let mut bytes: [u8; 64 / 8] = [0; 64 / 8];
bytes.copy_from_slice(&self[index..index + 8]);
u64::from_be_bytes(bytes)
}
fn get_u128_be(&self, index: usize) -> u128 {
let mut bytes: [u8; 128 / 8] = [0; 128 / 8];
bytes.copy_from_slice(&self[index..index + 128 / 8]);
u128::from_be_bytes(bytes)
}
fn get_u256(&self, index: usize) -> (u128, u128) {
(self.get_u128_be(index), self.get_u128_be(index + 128 / 8))
}
fn get_address(&self, index: usize) -> CanonicalAddr {
// 32 bytes are reserved for addresses, but only the last 20 bytes are taken by the actual address
CanonicalAddr::from(&self[index + 32 - 20..index + 32])
}
fn get_bytes32(&self, index: usize) -> &[u8] {
&self[index..index + 32]
}
}
pub fn extend_address_to_32(addr: &CanonicalAddr) -> Vec<u8> {
let mut result: Vec<u8> = vec![0; 12];
result.extend(addr.as_slice());
result
}
pub fn extend_string_to_32(s: &String) -> StdResult<Vec<u8>> {
let bytes = s.as_bytes();
if bytes.len() > 32 {
return Err(StdError::generic_err("string more than 32 "))
}
let mut result = vec![0; 32 - bytes.len()];
result.extend(bytes);
Ok(result)
}

View File

@ -0,0 +1,548 @@
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 crate::byte_utils::ByteUtils;
use crate::byte_utils::{extend_address_to_32, extend_string_to_32};
use crate::error::ContractError;
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, TokenBridgeMessage, TransferInfo,
};
use cw20_base::msg::HandleMsg as TokenMsg;
use cw20_base::msg::QueryMsg as TokenQuery;
use wormhole::msg::HandleMsg as WormholeHandleMsg;
use wormhole::msg::QueryMsg as WormholeQueryMsg;
use wormhole::state::ParsedVAA;
use cw20::TokenInfoResponse;
use cw20_wrapped::msg::HandleMsg as WrappedMsg;
use cw20_wrapped::msg::InitMsg as WrappedInit;
use cw20_wrapped::msg::QueryMsg as WrappedQuery;
use cw20_wrapped::msg::{InitHook, WrappedAssetInfoResponse};
use sha3::{Digest, Keccak256};
// Chain ID of Terra
const CHAIN_ID: u16 = 3;
const WRAPPED_ASSET_UPDATING: &str = "updating";
pub fn init<S: Storage, A: Api, Q: Querier>(
deps: &mut Extern<S, A, Q>,
_env: Env,
msg: InitMsg,
) -> StdResult<InitResponse> {
// Save general wormhole info
let state = ConfigInfo {
owner: msg.owner,
wormhole_contract: msg.wormhole_contract,
wrapped_asset_code_id: msg.wrapped_asset_code_id,
};
config(&mut deps.storage).save(&state)?;
Ok(InitResponse::default())
}
pub fn parse_vaa<S: Storage, A: Api, Q: Querier>(
deps: &mut Extern<S, A, Q>,
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)
}
pub fn handle<S: Storage, A: Api, Q: Querier>(
deps: &mut Extern<S, A, Q>,
env: Env,
msg: HandleMsg,
) -> StdResult<HandleResponse> {
match msg {
HandleMsg::RegisterAssetHook { asset_id } => {
handle_register_asset(deps, env, &asset_id.as_slice())
}
HandleMsg::InitiateTransfer {
asset,
amount,
recipient_chain,
recipient,
nonce,
} => handle_initiate_transfer(
deps,
env,
asset,
amount,
recipient_chain,
recipient.as_slice().to_vec(),
nonce,
),
HandleMsg::SubmitVaa { data } => submit_vaa(deps, env, &data),
HandleMsg::RegisterChain {
chain_id,
chain_address,
} => handle_register_chain(deps, env, chain_id, chain_address.as_slice().to_vec()),
HandleMsg::CreateAssetMeta {
asset_address,
nonce,
} => handle_create_asset_meta(deps, env, &asset_address, nonce),
}
}
fn handle_register_chain<S: Storage, A: Api, Q: Querier>(
deps: &mut Extern<S, A, Q>,
env: Env,
chain_id: u16,
chain_address: Vec<u8>,
) -> StdResult<HandleResponse> {
let cfg = config_read(&deps.storage).load()?;
if env.message.sender != cfg.owner {
return Err(StdError::unauthorized());
}
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(&mut deps.storage);
bucket.save(&chain_id.to_be_bytes(), &chain_address)?;
Ok(HandleResponse {
messages: vec![],
log: vec![
log("chain_id", chain_id),
log("chain_address", hex::encode(chain_address)),
],
data: None,
})
}
/// Handle wrapped asset registration messages
fn handle_register_asset<S: Storage, A: Api, Q: Querier>(
deps: &mut Extern<S, A, Q>,
env: Env,
asset_id: &[u8],
) -> StdResult<HandleResponse> {
let mut bucket = wrapped_asset(&mut 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, &env.message.sender)?;
let contract_address: CanonicalAddr = deps.api.canonical_address(&env.message.sender)?;
wrapped_asset_address(&mut deps.storage)
.save(contract_address.as_slice(), &asset_id.to_vec())?;
Ok(HandleResponse {
messages: vec![],
log: vec![
log("action", "register_asset"),
log("asset_id", format!("{:?}", asset_id)),
log("contract_addr", env.message.sender),
],
data: None,
})
}
fn handle_attest_meta<S: Storage, A: Api, Q: Querier>(
deps: &mut Extern<S, A, Q>,
env: Env,
data: &Vec<u8>,
) -> StdResult<HandleResponse> {
let meta = AssetMeta::deserialize(data)?;
if CHAIN_ID == meta.token_chain {
return Err(StdError::generic_err("matching chain id, kinda cringe"));
}
let cfg = config_read(&deps.storage).load()?;
let asset_id = build_asset_id(meta.token_chain, &meta.token_address.as_slice());
wrapped_asset(&mut deps.storage).save(&asset_id, &HumanAddr::from(WRAPPED_ASSET_UPDATING))?;
Ok(HandleResponse {
messages: vec![CosmosMsg::Wasm(WasmMsg::Instantiate {
code_id: cfg.wrapped_asset_code_id,
msg: to_binary(&WrappedInit {
asset_chain: meta.token_chain,
asset_address: meta.token_address.to_vec().into(),
decimals: meta.decimals,
mint: None,
init_hook: Some(InitHook {
contract_addr: env.contract.address,
msg: to_binary(&HandleMsg::RegisterAssetHook {
asset_id: asset_id.to_vec().into(),
})?,
}),
})?,
send: vec![],
label: None,
})],
log: vec![],
data: None,
})
}
fn handle_create_asset_meta<S: Storage, A: Api, Q: Querier>(
deps: &mut Extern<S, A, Q>,
env: Env,
asset_address: &HumanAddr,
nonce: u32,
) -> StdResult<HandleResponse> {
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.canonical_address(asset_address)?;
let token_info: TokenInfoResponse = deps.querier.custom_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(HandleResponse {
messages: vec![CosmosMsg::Wasm(WasmMsg::Execute {
contract_addr: cfg.wormhole_contract,
msg: to_binary(&WormholeHandleMsg::PostMessage {
message: Binary::from(token_bridge_message.serialize()),
nonce,
})?,
// forward coins sent to this message
send: env.message.sent_funds.clone(),
})],
log: vec![
log("meta.token_chain", CHAIN_ID),
log("meta.token", asset_address),
log("meta.nonce", nonce),
log("meta.block_time", env.block.time),
],
data: None,
})
}
fn submit_vaa<S: Storage, A: Api, Q: Querier>(
deps: &mut Extern<S, A, Q>,
env: Env,
data: &Binary,
) -> StdResult<HandleResponse> {
let vaa = parse_vaa(deps, env.block.time, data)?;
let data = vaa.payload;
let message = TokenBridgeMessage::deserialize(&data)?;
let result = match message.action {
Action::TRANSFER => handle_complete_transfer(
deps,
env,
vaa.emitter_chain,
vaa.emitter_address,
&message.payload,
),
Action::ATTEST_META => handle_attest_meta(deps, env, &message.payload),
_ => ContractError::InvalidVAAAction.std_err(),
};
return result;
}
fn handle_complete_transfer<S: Storage, A: Api, Q: Querier>(
deps: &mut Extern<S, A, Q>,
env: Env,
emitter_chain: u16,
emitter_address: Vec<u8>,
data: &Vec<u8>,
) -> StdResult<HandleResponse> {
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::unauthorized());
}
if transfer_info.recipient_chain != CHAIN_ID {
return Err(StdError::generic_err(
"you sent the message to the wrong chain, idiot",
));
}
let token_chain = transfer_info.token_chain;
let target_address = (&transfer_info.recipient.as_slice()).get_address(0);
let (not_supported_amount, amount) = transfer_info.amount;
// Check high 128 bit of amount value to be empty
if not_supported_amount != 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
.human_address(&target_address)
.or_else(|_| ContractError::WrongTargetAddressFormat.std_err())?;
Ok(HandleResponse {
messages: vec![CosmosMsg::Wasm(WasmMsg::Execute {
contract_addr: contract_addr.clone(),
msg: to_binary(&WrappedMsg::Mint {
recipient: recipient.clone(),
amount: Uint128::from(amount),
})?,
send: vec![],
})],
log: vec![
log("action", "complete_transfer_wrapped"),
log("contract", contract_addr),
log("recipient", recipient),
log("amount", amount),
],
data: None,
})
} 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.human_address(&target_address)?;
let contract_addr = deps.api.human_address(&token_address)?;
Ok(HandleResponse {
messages: vec![CosmosMsg::Wasm(WasmMsg::Execute {
contract_addr: contract_addr.clone(),
msg: to_binary(&TokenMsg::Transfer {
recipient: recipient.clone(),
amount: Uint128::from(amount),
})?,
send: vec![],
})],
log: vec![
log("action", "complete_transfer_native"),
log("recipient", recipient),
log("contract", contract_addr),
log("amount", amount),
],
data: None,
})
}
}
fn handle_initiate_transfer<S: Storage, A: Api, Q: Querier>(
deps: &mut Extern<S, A, Q>,
env: Env,
asset: HumanAddr,
amount: Uint128,
recipient_chain: u16,
recipient: Vec<u8>,
nonce: u32,
) -> StdResult<HandleResponse> {
// 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.canonical_address(&asset)?;
let mut messages: Vec<CosmosMsg> = vec![];
match wrapped_asset_address_read(&deps.storage).load(asset_canonical.as_slice()) {
Ok(_) => {
// This is a deployed wrapped asset, burn it
messages.push(CosmosMsg::Wasm(WasmMsg::Execute {
contract_addr: asset.clone(),
msg: to_binary(&WrappedMsg::Burn {
account: env.message.sender.clone(),
amount,
})?,
send: vec![],
}));
let request = QueryRequest::<()>::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();
}
Err(_) => {
// This is a regular asset, transfer its balance
messages.push(CosmosMsg::Wasm(WasmMsg::Execute {
contract_addr: asset,
msg: to_binary(&TokenMsg::TransferFrom {
owner: env.message.sender.clone(),
recipient: env.contract.address.clone(),
amount,
})?,
send: vec![],
}));
asset_address = extend_address_to_32(&asset_canonical);
asset_chain = CHAIN_ID;
}
};
let transfer_info = TransferInfo {
token_chain: asset_chain,
token_address: asset_address.clone(),
amount: (0, amount.u128()),
recipient_chain,
recipient: recipient.clone(),
};
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(&WormholeHandleMsg::PostMessage {
message: Binary::from(token_bridge_message.serialize()),
nonce,
})?,
// forward coins sent to this message
send: env.message.sent_funds.clone(),
}));
Ok(HandleResponse {
messages,
log: vec![
log("transfer.token_chain", asset_chain),
log("transfer.token", hex::encode(asset_address)),
log(
"transfer.sender",
hex::encode(extend_address_to_32(
&deps.api.canonical_address(&env.message.sender)?,
)),
),
log("transfer.recipient_chain", recipient_chain),
log("transfer.recipient", hex::encode(recipient)),
log("transfer.amount", amount),
log("transfer.nonce", nonce),
log("transfer.block_time", env.block.time),
],
data: None,
})
}
pub fn query<S: Storage, A: Api, Q: Querier>(
deps: &Extern<S, A, Q>,
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<S: Storage, A: Api, Q: Querier>(
deps: &Extern<S, A, Q>,
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()
}
#[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(())
}
}

View File

@ -0,0 +1,114 @@
use cosmwasm_std::StdError;
use thiserror::Error;
#[derive(Error, Debug)]
pub enum ContractError {
/// Invalid VAA version
#[error("InvalidVersion")]
InvalidVersion,
/// Guardian set with this index does not exist
#[error("InvalidGuardianSetIndex")]
InvalidGuardianSetIndex,
/// Guardian set expiration date is zero or in the past
#[error("GuardianSetExpired")]
GuardianSetExpired,
/// Not enough signers on the VAA
#[error("NoQuorum")]
NoQuorum,
/// Wrong guardian index order, order must be ascending
#[error("WrongGuardianIndexOrder")]
WrongGuardianIndexOrder,
/// Some problem with signature decoding from bytes
#[error("CannotDecodeSignature")]
CannotDecodeSignature,
/// Some problem with public key recovery from the signature
#[error("CannotRecoverKey")]
CannotRecoverKey,
/// Recovered pubkey from signature does not match guardian address
#[error("GuardianSignatureError")]
GuardianSignatureError,
/// VAA action code not recognized
#[error("InvalidVAAAction")]
InvalidVAAAction,
/// VAA guardian set is not current
#[error("NotCurrentGuardianSet")]
NotCurrentGuardianSet,
/// Only 128-bit amounts are supported
#[error("AmountTooHigh")]
AmountTooHigh,
/// Amount should be higher than zero
#[error("AmountTooLow")]
AmountTooLow,
/// Source and target chain ids must be different
#[error("SameSourceAndTarget")]
SameSourceAndTarget,
/// Target chain id must be the same as the current CHAIN_ID
#[error("WrongTargetChain")]
WrongTargetChain,
/// Wrapped asset init hook sent twice for the same asset id
#[error("AssetAlreadyRegistered")]
AssetAlreadyRegistered,
/// Guardian set must increase in steps of 1
#[error("GuardianSetIndexIncreaseError")]
GuardianSetIndexIncreaseError,
/// VAA was already executed
#[error("VaaAlreadyExecuted")]
VaaAlreadyExecuted,
/// Message sender not permitted to execute this operation
#[error("PermissionDenied")]
PermissionDenied,
/// Could not decode target address from canonical to human-readable form
#[error("WrongTargetAddressFormat")]
WrongTargetAddressFormat,
/// More signatures than active guardians found
#[error("TooManySignatures")]
TooManySignatures,
/// Wrapped asset not found in the registry
#[error("AssetNotFound")]
AssetNotFound,
/// Generic error when there is a problem with VAA structure
#[error("InvalidVAA")]
InvalidVAA,
/// Thrown when fee is enabled for the action, but was not sent with the transaction
#[error("FeeTooLow")]
FeeTooLow,
/// Registering asset outside of the wormhole
#[error("RegistrationForbidden")]
RegistrationForbidden,
}
impl ContractError {
pub fn std(&self) -> StdError {
StdError::GenericErr {
msg: format!("{}", self),
backtrace: None,
}
}
pub fn std_err<T>(&self) -> Result<T, StdError> {
Err(self.std())
}
}

View File

@ -0,0 +1,14 @@
#[cfg(test)]
#[macro_use]
extern crate lazy_static;
mod byte_utils;
pub mod contract;
mod error;
pub mod msg;
pub mod state;
pub use crate::error::ContractError;
#[cfg(all(target_arch = "wasm32", not(feature = "library")))]
cosmwasm_std::create_entry_points!(contract);

View File

@ -0,0 +1,60 @@
use cosmwasm_std::{Binary, HumanAddr, Uint128};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
pub struct InitMsg {
pub owner: HumanAddr,
pub wormhole_contract: HumanAddr,
pub wrapped_asset_code_id: u64,
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum HandleMsg {
RegisterAssetHook {
asset_id: Binary,
},
InitiateTransfer {
asset: HumanAddr,
amount: Uint128,
recipient_chain: u16,
recipient: Binary,
nonce: u32,
},
SubmitVaa {
data: Binary,
},
RegisterChain {
chain_id: u16,
chain_address: Binary,
},
CreateAssetMeta {
asset_address: HumanAddr,
nonce: u32,
}
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum QueryMsg {
WrappedRegistry { chain: u16, address: Binary },
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub struct WrappedRegistryResponse {
pub address: HumanAddr,
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum WormholeQueryMsg {
VerifyVAA { vaa: Binary, block_time: u64 },
}

View File

@ -0,0 +1,186 @@
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use cosmwasm_std::{HumanAddr, StdResult, Storage};
use cosmwasm_storage::{
bucket, bucket_read, singleton, singleton_read, Bucket, ReadonlyBucket, ReadonlySingleton,
Singleton,
};
use crate::byte_utils::ByteUtils;
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";
// Guardian set information
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
pub struct ConfigInfo {
// Current active guardian set
pub owner: HumanAddr,
pub wormhole_contract: HumanAddr,
pub wrapped_asset_code_id: u64,
}
pub fn config<S: Storage>(storage: &mut S) -> Singleton<S, ConfigInfo> {
singleton(storage, CONFIG_KEY)
}
pub fn config_read<S: Storage>(storage: &S) -> ReadonlySingleton<S, ConfigInfo> {
singleton_read(storage, CONFIG_KEY)
}
pub fn bridge_contracts<S: Storage>(storage: &mut S) -> Bucket<S, Vec<u8>> {
bucket(BRIDGE_CONTRACTS, storage)
}
pub fn bridge_contracts_read<S: Storage>(storage: &S) -> ReadonlyBucket<S, Vec<u8>> {
bucket_read(BRIDGE_CONTRACTS, storage)
}
pub fn wrapped_asset<S: Storage>(storage: &mut S) -> Bucket<S, HumanAddr> {
bucket(WRAPPED_ASSET_KEY, storage)
}
pub fn wrapped_asset_read<S: Storage>(storage: &S) -> ReadonlyBucket<S, HumanAddr> {
bucket_read(WRAPPED_ASSET_KEY, storage)
}
pub fn wrapped_asset_address<S: Storage>(storage: &mut S) -> Bucket<S, Vec<u8>> {
bucket(WRAPPED_ASSET_ADDRESS_KEY, storage)
}
pub fn wrapped_asset_address_read<S: Storage>(storage: &S) -> ReadonlyBucket<S, Vec<u8>> {
bucket_read(WRAPPED_ASSET_ADDRESS_KEY, storage)
}
pub struct Action;
impl Action {
pub const TRANSFER: u8 = 0;
pub const ATTEST_META: u8 = 1;
}
// 0 u8 action
// 1 [u8] payload
pub struct TokenBridgeMessage {
pub action: u8,
pub payload: Vec<u8>,
}
impl TokenBridgeMessage {
pub fn deserialize(data: &Vec<u8>) -> StdResult<Self> {
let data = data.as_slice();
let action = data.get_u8(0);
let payload = &data[1..];
Ok(TokenBridgeMessage {
action,
payload: payload.to_vec(),
})
}
pub fn serialize(&self) ->Vec<u8> {
[self.action.to_be_bytes().to_vec(), self.payload.clone()].concat()
}
}
// 0 u16 token_chain
// 2 [u8; 32] token_address
// 34 u256 amount
// 66 u16 recipient_chain
// 68 [u8; 32] recipient
pub struct TransferInfo {
pub token_chain: u16,
pub token_address: Vec<u8>,
pub amount: (u128, u128),
pub recipient_chain: u16,
pub recipient: Vec<u8>,
}
impl TransferInfo {
pub fn deserialize(data: &Vec<u8>) -> StdResult<Self> {
let data = data.as_slice();
let token_chain = data.get_u16(0);
let token_address = data.get_bytes32(2).to_vec();
let amount = data.get_u256(34);
let recipient_chain = data.get_u16(66);
let recipient = data.get_bytes32(68).to_vec();
Ok(TransferInfo {
token_chain,
token_address,
amount,
recipient_chain,
recipient,
})
}
pub fn serialize(&self) -> Vec<u8> {
[
self.token_chain.to_be_bytes().to_vec(),
self.token_address.clone(),
self.amount.0.to_be_bytes().to_vec(),
self.amount.1.to_be_bytes().to_vec(),
self.recipient_chain.to_be_bytes().to_vec(),
self.recipient.to_vec(),
]
.concat()
}
}
//PayloadID uint8 = 2
// // Address of the token. Left-zero-padded if shorter than 32 bytes
// TokenAddress [32]uint8
// // Chain ID of the token
// TokenChain uint16
// // Number of decimals of the token (big-endian uint256)
// Decimals [32]uint8
// // Symbol of the token (UTF-8)
// Symbol [32]uint8
// // Name of the token (UTF-8)
// Name [32]uint8
pub struct AssetMeta {
pub token_chain: u16,
pub token_address: Vec<u8>,
pub decimals: u8,
pub symbol: Vec<u8>,
pub name: Vec<u8>,
}
impl AssetMeta {
pub fn deserialize(data: &Vec<u8>) -> StdResult<Self> {
let data = data.as_slice();
let token_chain = data.get_u16(0);
let token_address = data.get_bytes32(2).to_vec();
let decimals = data.get_u8(34);
let symbol = data.get_bytes32(35).to_vec();
let name = data.get_bytes32(67).to_vec();
Ok(AssetMeta {
token_chain,
token_address,
decimals,
symbol,
name,
})
}
pub fn serialize(&self) -> Vec<u8> {
[
self.token_chain.to_be_bytes().to_vec(),
self.token_address.clone(),
self.decimals.to_be_bytes().to_vec(),
self.symbol.clone(),
self.name.clone(),
]
.concat()
}
}

View File

@ -0,0 +1,90 @@
static WASM: &[u8] = include_bytes!("../../../target/wasm32-unknown-unknown/release/wormhole.wasm");
use cosmwasm_std::{from_slice, Env, HumanAddr, InitResponse, Coin};
use cosmwasm_storage::to_length_prefixed;
use cosmwasm_vm::testing::{init, mock_env, mock_instance, MockApi, MockQuerier, MockStorage};
use cosmwasm_vm::{Api, Instance, Storage};
use wormhole::msg::InitMsg;
use wormhole::state::{ConfigInfo, GuardianAddress, GuardianSetInfo, CONFIG_KEY};
use hex;
enum TestAddress {
INITIALIZER,
}
impl TestAddress {
fn value(&self) -> HumanAddr {
match self {
TestAddress::INITIALIZER => HumanAddr::from("initializer"),
}
}
}
fn mock_env_height(signer: &HumanAddr, height: u64, time: u64) -> Env {
let mut env = mock_env(signer, &[]);
env.block.height = height;
env.block.time = time;
env
}
fn get_config_info<S: Storage>(storage: &S) -> ConfigInfo {
let key = to_length_prefixed(CONFIG_KEY);
let data = storage
.get(&key)
.0
.expect("error getting data")
.expect("data should exist");
from_slice(&data).expect("invalid data")
}
fn do_init(
height: u64,
guardians: &Vec<GuardianAddress>,
) -> Instance<MockStorage, MockApi, MockQuerier> {
let mut deps = mock_instance(WASM, &[]);
let init_msg = InitMsg {
initial_guardian_set: GuardianSetInfo {
addresses: guardians.clone(),
expiration_time: 100,
},
guardian_set_expirity: 50,
wrapped_asset_code_id: 999,
};
let env = mock_env_height(&TestAddress::INITIALIZER.value(), height, 0);
let owner = deps
.api
.canonical_address(&TestAddress::INITIALIZER.value())
.0
.unwrap();
let res: InitResponse = init(&mut deps, env, init_msg).unwrap();
assert_eq!(0, res.messages.len());
// query the store directly
deps.with_storage(|storage| {
assert_eq!(
get_config_info(storage),
ConfigInfo {
guardian_set_index: 0,
guardian_set_expirity: 50,
wrapped_asset_code_id: 999,
owner,
fee: Coin::new(10000, "uluna"),
}
);
Ok(())
})
.unwrap();
deps
}
#[test]
fn init_works() {
let guardians = vec![GuardianAddress::from(GuardianAddress {
bytes: hex::decode("beFA429d57cD18b7F8A4d91A2da9AB4AF05d0FBe")
.expect("Decoding failed")
.into(),
})];
let _deps = do_init(111, &guardians);
}

View File

@ -1,8 +1,11 @@
use cosmwasm_std::CanonicalAddr;
use cosmwasm_std::{CanonicalAddr, StdError, StdResult};
pub trait ByteUtils {
fn get_u8(&self, index: usize) -> u8;
fn get_u16(&self, index: usize) -> u16;
fn get_u32(&self, index: usize) -> u32;
fn get_u64(&self, index: usize) -> u64;
fn get_u128_be(&self, index: usize) -> u128;
/// High 128 then low 128
fn get_u256(&self, index: usize) -> (u128, u128);
@ -14,11 +17,21 @@ impl ByteUtils for &[u8] {
fn get_u8(&self, index: usize) -> u8 {
self[index]
}
fn get_u16(&self, index: usize) -> u16 {
let mut bytes: [u8; 16 / 8] = [0; 16 / 8];
bytes.copy_from_slice(&self[index..index + 2]);
u16::from_be_bytes(bytes)
}
fn get_u32(&self, index: usize) -> u32 {
let mut bytes: [u8; 32 / 8] = [0; 32 / 8];
bytes.copy_from_slice(&self[index..index + 4]);
u32::from_be_bytes(bytes)
}
fn get_u64(&self, index: usize) -> u64 {
let mut bytes: [u8; 64 / 8] = [0; 64 / 8];
bytes.copy_from_slice(&self[index..index + 8]);
u64::from_be_bytes(bytes)
}
fn get_u128_be(&self, index: usize) -> u128 {
let mut bytes: [u8; 128 / 8] = [0; 128 / 8];
bytes.copy_from_slice(&self[index..index + 128 / 8]);
@ -41,3 +54,14 @@ pub fn extend_address_to_32(addr: &CanonicalAddr) -> Vec<u8> {
result.extend(addr.as_slice());
result
}
pub fn extend_string_to_32(s: &String) -> StdResult<Vec<u8>> {
let bytes = s.as_bytes();
if bytes.len() > 32 {
return Err(StdError::generic_err("string more than 32 "))
}
let mut result = vec![0; 32 - bytes.len()];
result.extend(bytes);
Ok(result)
}

View File

@ -1,29 +1,19 @@
use crate::msg::WrappedRegistryResponse;
use cosmwasm_std::{
log, to_binary, Api, Binary, CanonicalAddr, CosmosMsg, BankMsg, Env, Extern, HandleResponse, HumanAddr,
InitResponse, Querier, QueryRequest, StdResult, Storage, Uint128, WasmMsg, WasmQuery, Coin, has_coins,
has_coins, log, to_binary, Api, BankMsg, Binary, Coin, CosmosMsg, Env, Extern, HandleResponse,
HumanAddr, InitResponse, Querier, StdError, StdResult, Storage,
};
use crate::byte_utils::extend_address_to_32;
use crate::byte_utils::ByteUtils;
use crate::error::ContractError;
use crate::msg::{GuardianSetInfoResponse, HandleMsg, InitMsg, QueryMsg, GetStateResponse};
use crate::msg::{
GetAddressHexResponse, GetStateResponse, GuardianSetInfoResponse, HandleMsg, InitMsg, QueryMsg,
};
use crate::state::{
config, config_read, guardian_set_get, guardian_set_set, vaa_archive_add, vaa_archive_check,
wrapped_asset, wrapped_asset_address, wrapped_asset_address_read, wrapped_asset_read,
ConfigInfo, GuardianAddress, GuardianSetInfo, ParsedVAA,
ConfigInfo, GuardianAddress, GuardianSetInfo, ParsedVAA, WormholeGovernance,
};
use cw20_base::msg::HandleMsg as TokenMsg;
use cw20_base::msg::QueryMsg as TokenQuery;
use cw20::TokenInfoResponse;
use cw20_wrapped::msg::HandleMsg as WrappedMsg;
use cw20_wrapped::msg::InitMsg as WrappedInit;
use cw20_wrapped::msg::QueryMsg as WrappedQuery;
use cw20_wrapped::msg::{InitHook, InitMint, WrappedAssetInfoResponse};
use k256::ecdsa::recoverable::Id as RecoverableId;
use k256::ecdsa::recoverable::Signature as RecoverableSignature;
use k256::ecdsa::Signature;
@ -36,14 +26,12 @@ use generic_array::GenericArray;
use std::convert::TryFrom;
// Chain ID of Terra
const CHAIN_ID: u8 = 3;
const CHAIN_ID: u16 = 3;
// Lock assets fee amount and denomination
const FEE_AMOUNT: u128 = 10000;
const FEE_DENOMINATION: &str = "uluna";
const WRAPPED_ASSET_UPDATING: &str = "updating";
pub fn init<S: Storage, A: Api, Q: Querier>(
deps: &mut Extern<S, A, Q>,
env: Env,
@ -53,9 +41,8 @@ pub fn init<S: Storage, A: Api, Q: Querier>(
let state = ConfigInfo {
guardian_set_index: 0,
guardian_set_expirity: msg.guardian_set_expirity,
wrapped_asset_code_id: msg.wrapped_asset_code_id,
owner: deps.api.canonical_address(&env.message.sender)?,
fee: Coin::new(FEE_AMOUNT, FEE_DENOMINATION), // 0.01 Luna (or 10000 uluna) fee by default
fee: Coin::new(FEE_AMOUNT, FEE_DENOMINATION), // 0.01 Luna (or 10000 uluna) fee by default
};
config(&mut deps.storage).save(&state)?;
@ -75,26 +62,14 @@ pub fn handle<S: Storage, A: Api, Q: Querier>(
msg: HandleMsg,
) -> StdResult<HandleResponse> {
match msg {
HandleMsg::SubmitVAA { vaa } => handle_submit_vaa(deps, env, &vaa.as_slice()),
HandleMsg::RegisterAssetHook { asset_id } => {
handle_register_asset(deps, env, &asset_id.as_slice())
HandleMsg::PostMessage { message, nonce } => {
handle_post_message(deps, env, &message.as_slice(), nonce)
}
HandleMsg::LockAssets {
asset,
recipient,
amount,
target_chain,
nonce,
} => handle_lock_assets(
deps,
env,
asset,
amount,
recipient.as_slice(),
target_chain,
nonce,
),
HandleMsg::TransferFee { amount, recipient } => handle_transfer_fee(deps, env, amount, recipient),
// HandleMsg::SubmitVAA { vaa } => handle_submit_vaa(deps, env, &vaa.as_slice()),
HandleMsg::TransferFee { amount, recipient } => {
handle_transfer_fee(deps, env, amount, recipient)
}
HandleMsg::SubmitVAA { vaa } => handle_submit_vaa(deps, env, vaa.as_slice()),
}
}
@ -105,17 +80,24 @@ fn handle_submit_vaa<S: Storage, A: Api, Q: Querier>(
data: &[u8],
) -> StdResult<HandleResponse> {
let state = config_read(&deps.storage).load()?;
let vaa = parse_and_verify_vaa(&deps.storage, data, env.block.time)?;
let result = match vaa.action {
0x01 => {
let vaa = parse_and_verify_vaa(&deps.storage, data, env.block.time)?;
if vaa.emitter_chain != 0u16 {
// chain 0 is the wormhole chain ?
return Err(StdError::generic_err(
"governance actions may only come from chain 0",
));
}
let gov = WormholeGovernance::deserialize(&vaa.payload)?;
let result = match gov.action {
0u8 => {
if vaa.guardian_set_index != state.guardian_set_index {
return ContractError::NotCurrentGuardianSet.std_err();
}
vaa_update_guardian_set(deps, env, vaa.payload.as_slice())
vaa_update_guardian_set(deps, env, gov.payload.as_slice())
}
0x10 => vaa_transfer(deps, env, vaa.payload.as_slice()),
_ => ContractError::InvalidVAAAction.std_err(),
};
@ -152,7 +134,7 @@ fn parse_and_verify_vaa<S: Storage>(
if guardian_set.expiration_time != 0 && guardian_set.expiration_time < block_time {
return ContractError::GuardianSetExpired.std_err();
}
if vaa.len_signers < guardian_set.quorum() {
if (vaa.len_signers as usize) < guardian_set.quorum() {
return ContractError::NoQuorum.std_err();
}
@ -197,37 +179,6 @@ fn parse_and_verify_vaa<S: Storage>(
Ok(vaa)
}
/// Handle wrapped asset registration messages
fn handle_register_asset<S: Storage, A: Api, Q: Querier>(
deps: &mut Extern<S, A, Q>,
env: Env,
asset_id: &[u8],
) -> StdResult<HandleResponse> {
let mut bucket = wrapped_asset(&mut 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, &env.message.sender)?;
let contract_address: CanonicalAddr =
deps.api.canonical_address(&env.message.sender)?;
wrapped_asset_address(&mut deps.storage)
.save(contract_address.as_slice(), &asset_id.to_vec())?;
Ok(HandleResponse {
messages: vec![],
log: vec![
log("action", "register_asset"),
log("asset_id", format!("{:?}", asset_id)),
log("contract_addr", env.message.sender),
],
data: None,
})
}
fn vaa_update_guardian_set<S: Storage, A: Api, Q: Querier>(
deps: &mut Extern<S, A, Q>,
env: Env,
@ -298,227 +249,32 @@ fn vaa_update_guardian_set<S: Storage, A: Api, Q: Querier>(
})
}
fn vaa_transfer<S: Storage, A: Api, Q: Querier>(
fn handle_post_message<S: Storage, A: Api, Q: Querier>(
deps: &mut Extern<S, A, Q>,
env: Env,
data: &[u8],
) -> StdResult<HandleResponse> {
/* Payload format:
0 uint32 nonce
4 uint8 source_chain
5 uint8 target_chain
6 [32]uint8 source_address
38 [32]uint8 target_address
70 uint8 token_chain
71 [32]uint8 token_address
103 uint8 decimals
104 uint256 amount */
const SOURCE_CHAIN_POS: usize = 4;
const TARGET_CHAIN_POS: usize = 5;
const TARGET_ADDRESS_POS: usize = 38;
const TOKEN_CHAIN_POS: usize = 70;
const TOKEN_ADDRESS_POS: usize = 71;
const DECIMALS_POS: usize = 103;
const AMOUNT_POS: usize = 104;
const PAYLOAD_LEN: usize = 136;
if PAYLOAD_LEN > data.len() {
return ContractError::InvalidVAA.std_err();
}
let source_chain = data.get_u8(SOURCE_CHAIN_POS);
let target_chain = data.get_u8(TARGET_CHAIN_POS);
let target_address = data.get_address(TARGET_ADDRESS_POS);
let token_chain = data.get_u8(TOKEN_CHAIN_POS);
let (not_supported_amount, amount) = data.get_u256(AMOUNT_POS);
// Check high 128 bit of amount value to be empty
if not_supported_amount != 0 {
return ContractError::AmountTooHigh.std_err();
}
// Check if source and target chains are different
if source_chain == target_chain {
return ContractError::SameSourceAndTarget.std_err();
}
// Check if transfer is incoming
if target_chain != CHAIN_ID {
return ContractError::WrongTargetChain.std_err();
}
if token_chain != CHAIN_ID {
let asset_address = data.get_bytes32(TOKEN_ADDRESS_POS);
let asset_id = build_asset_id(token_chain, asset_address);
let mut messages: Vec<CosmosMsg> = vec![];
// Check if this asset is already deployed
let contract_addr = wrapped_asset_read(&deps.storage).load(&asset_id).ok();
let contract_addr = contract_addr.filter(|addr| addr != &HumanAddr::from(WRAPPED_ASSET_UPDATING));
if let Some(contract_addr) = contract_addr {
// Asset already deployed, just mint
messages.push(CosmosMsg::Wasm(WasmMsg::Execute {
contract_addr,
msg: to_binary(&WrappedMsg::Mint {
recipient: deps
.api
.human_address(&target_address)
.or_else(|_| ContractError::WrongTargetAddressFormat.std_err())?,
amount: Uint128::from(amount),
})?,
send: vec![],
}));
} else {
// Asset is not deployed yet, deploy and mint
wrapped_asset(&mut deps.storage).save(&asset_id, &HumanAddr::from(WRAPPED_ASSET_UPDATING))?;
let state = config_read(&deps.storage).load()?;
messages.push(CosmosMsg::Wasm(WasmMsg::Instantiate {
code_id: state.wrapped_asset_code_id,
msg: to_binary(&WrappedInit {
asset_chain: token_chain,
asset_address: asset_address.to_vec().into(),
decimals: data.get_u8(DECIMALS_POS),
mint: Some(InitMint {
recipient: deps
.api
.human_address(&target_address)
.or_else(|_| ContractError::WrongTargetAddressFormat.std_err())?,
amount: Uint128::from(amount),
}),
init_hook: Some(InitHook {
contract_addr: env.contract.address,
msg: to_binary(&HandleMsg::RegisterAssetHook {
asset_id: asset_id.to_vec().into(),
})?,
}),
})?,
send: vec![],
label: None,
}));
}
Ok(HandleResponse {
messages,
log: vec![],
data: None,
})
} else {
let token_address = data.get_address(TOKEN_ADDRESS_POS);
Ok(HandleResponse {
messages: vec![CosmosMsg::Wasm(WasmMsg::Execute {
contract_addr: deps.api.human_address(&token_address)?,
msg: to_binary(&TokenMsg::Transfer {
recipient: deps.api.human_address(&target_address)?,
amount: Uint128::from(amount),
})?,
send: vec![],
})],
log: vec![],
data: None,
})
}
}
fn handle_lock_assets<S: Storage, A: Api, Q: Querier>(
deps: &mut Extern<S, A, Q>,
env: Env,
asset: HumanAddr,
amount: Uint128,
recipient: &[u8],
target_chain: u8,
message: &[u8],
nonce: u32,
) -> StdResult<HandleResponse> {
if target_chain == CHAIN_ID {
return ContractError::SameSourceAndTarget.std_err();
}
if amount.is_zero() {
return ContractError::AmountTooLow.std_err();
}
let state = config_read(&deps.storage).load()?;
// Check fee
if !has_coins(env.message.sent_funds.as_ref(), &state.fee) {
return ContractError::FeeTooLow.std_err();
}
let asset_chain: u8;
let asset_address: Vec<u8>;
// Query asset details
let request = QueryRequest::<()>::Wasm(WasmQuery::Smart {
contract_addr: asset.clone(),
msg: to_binary(&TokenQuery::TokenInfo {})?,
});
let token_info: TokenInfoResponse = deps.querier.custom_query(&request)?;
let decimals: u8 = token_info.decimals;
let asset_canonical: CanonicalAddr = deps.api.canonical_address(&asset)?;
let mut messages: Vec<CosmosMsg> = vec![];
match wrapped_asset_address_read(&deps.storage).load(asset_canonical.as_slice()) {
Ok(_) => {
// This is a deployed wrapped asset, burn it
messages.push(CosmosMsg::Wasm(WasmMsg::Execute {
contract_addr: asset.clone(),
msg: to_binary(&WrappedMsg::Burn {
account: env.message.sender.clone(),
amount,
})?,
send: vec![],
}));
let request = QueryRequest::<()>::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();
}
Err(_) => {
// This is a regular asset, transfer its balance
messages.push(CosmosMsg::Wasm(WasmMsg::Execute {
contract_addr: asset,
msg: to_binary(&TokenMsg::TransferFrom {
owner: env.message.sender.clone(),
recipient: env.contract.address.clone(),
amount,
})?,
send: vec![],
}));
asset_address = extend_address_to_32(&asset_canonical);
asset_chain = CHAIN_ID;
}
};
Ok(HandleResponse {
messages,
messages: vec![],
log: vec![
log("locked.target_chain", target_chain),
log("locked.token_chain", asset_chain),
log("locked.token_decimals", decimals),
log("locked.token", hex::encode(asset_address)),
log("message.message", hex::encode(message)),
log(
"locked.sender",
"message.sender",
hex::encode(extend_address_to_32(
&deps.api.canonical_address(&env.message.sender)?,
)),
),
log("locked.recipient", hex::encode(recipient)),
log("locked.amount", amount),
log("locked.nonce", nonce),
log("locked.block_time", env.block.time),
log("message.chain_id", CHAIN_ID),
log("message.nonce", nonce),
log("message.block_time", env.block.time),
],
data: None,
})
@ -537,7 +293,7 @@ pub fn handle_transfer_fee<S: Storage, A: Api, Q: Querier>(
}
Ok(HandleResponse {
messages: vec![CosmosMsg::Bank(BankMsg::Send{
messages: vec![CosmosMsg::Bank(BankMsg::Send {
from_address: env.contract.address,
to_address: recipient,
amount: vec![amount],
@ -553,13 +309,13 @@ pub fn query<S: Storage, A: Api, Q: Querier>(
) -> StdResult<Binary> {
match msg {
QueryMsg::GuardianSetInfo {} => to_binary(&query_guardian_set_info(deps)?),
QueryMsg::WrappedRegistry { chain, address } => {
to_binary(&query_wrapped_registry(deps, chain, address.as_slice())?)
}
QueryMsg::VerifyVAA { vaa, block_time } => {
to_binary(&query_parse_and_verify_vaa(deps, &vaa.as_slice(), block_time)?)
},
QueryMsg::VerifyVAA { vaa, block_time } => to_binary(&query_parse_and_verify_vaa(
deps,
&vaa.as_slice(),
block_time,
)?),
QueryMsg::GetState {} => to_binary(&query_state(deps)?),
QueryMsg::QueryAddressHex { address } => to_binary(&query_address_hex(deps, &address)?),
}
}
@ -575,19 +331,6 @@ pub fn query_guardian_set_info<S: Storage, A: Api, Q: Querier>(
Ok(res)
}
pub fn query_wrapped_registry<S: Storage, A: Api, Q: Querier>(
deps: &Extern<S, A, Q>,
chain: u8,
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(),
}
}
pub fn query_parse_and_verify_vaa<S: Storage, A: Api, Q: Querier>(
deps: &Extern<S, A, Q>,
data: &[u8],
@ -596,13 +339,21 @@ pub fn query_parse_and_verify_vaa<S: Storage, A: Api, Q: Querier>(
parse_and_verify_vaa(&deps.storage, data, block_time)
}
// returns the hex of the 32 byte address we use for some address on this chain
pub fn query_address_hex<S: Storage, A: Api, Q: Querier>(
deps: &Extern<S, A, Q>,
address: &HumanAddr,
) -> StdResult<GetAddressHexResponse> {
Ok(GetAddressHexResponse {
hex: hex::encode(extend_address_to_32(&deps.api.canonical_address(&address)?)),
})
}
pub fn query_state<S: Storage, A: Api, Q: Querier>(
deps: &Extern<S, A, Q>,
) -> StdResult<GetStateResponse> {
let state = config_read(&deps.storage).load()?;
let res = GetStateResponse {
fee: state.fee,
};
let res = GetStateResponse { fee: state.fee };
Ok(res)
}
@ -631,16 +382,6 @@ fn keys_equal(a: &VerifyKey, b: &GuardianAddress) -> bool {
true
}
fn build_asset_id(chain: u8, address: &[u8]) -> Vec<u8> {
let mut asset_id: Vec<u8> = vec![];
asset_id.push(chain);
asset_id.extend_from_slice(address);
let mut hasher = Keccak256::new();
hasher.update(asset_id);
hasher.finalize().to_vec()
}
#[cfg(test)]
mod tests {
use super::*;
@ -1009,7 +750,7 @@ mod tests {
const LOCK_AMOUNT: u128 = 10000000000;
const LOCK_RECIPIENT: &str = "0000000000000000000011223344556677889900";
const LOCK_TARGET: u8 = 1;
const LOCK_WRAPPED_CHAIN: u8 = 2;
const LOCK_WRAPPED_CHAIN: u16 = 2;
const LOCK_WRAPPED_ASSET: &str = "112233445566ff";
const LOCKED_DECIMALS: u8 = 11;
const ADDRESS_EXTENSION: &str = "000000000000000000000000";
@ -1096,7 +837,8 @@ mod tests {
};
do_init_with_guardians(&mut deps, 1);
let result = submit_msg_with_fee(&mut deps, MSG_LOCK.clone(), Coin::new(10000, "uluna")).unwrap();
let result =
submit_msg_with_fee(&mut deps, MSG_LOCK.clone(), Coin::new(10000, "uluna")).unwrap();
let expected_logs = vec![
log("locked.target_chain", LOCK_TARGET),
@ -1160,7 +902,7 @@ mod tests {
let register_msg = HandleMsg::RegisterAssetHook {
asset_id: Binary::from(LOCK_ASSET_ID),
};
let result = submit_msg_with_sender(
&mut deps,
register_msg.clone(),
@ -1185,7 +927,9 @@ mod tests {
asset_id: Binary::from(LOCK_ASSET_ID),
};
wrapped_asset(&mut deps.storage).save(&LOCK_ASSET_ID, &HumanAddr::from(WRAPPED_ASSET_UPDATING)).unwrap();
wrapped_asset(&mut deps.storage)
.save(&LOCK_ASSET_ID, &HumanAddr::from(WRAPPED_ASSET_UPDATING))
.unwrap();
let result = submit_msg_with_sender(
&mut deps,
@ -1196,7 +940,8 @@ mod tests {
assert!(result.is_ok());
let result = submit_msg_with_fee(&mut deps, MSG_LOCK.clone(), Coin::new(10000, "uluna")).unwrap();
let result =
submit_msg_with_fee(&mut deps, MSG_LOCK.clone(), Coin::new(10000, "uluna")).unwrap();
let expected_logs = vec![
log("locked.target_chain", LOCK_TARGET),
@ -1305,7 +1050,13 @@ mod tests {
let decoded_vaa: Binary = hex::decode(VAA_VALID_TRANSFER_3_SIGS)
.expect("Decoding failed")
.into();
let result = query(&deps, QueryMsg::VerifyVAA { vaa: decoded_vaa, block_time: env.block.time });
let result = query(
&deps,
QueryMsg::VerifyVAA {
vaa: decoded_vaa,
block_time: env.block.time,
},
);
assert!(result.is_ok());
}
@ -1328,7 +1079,13 @@ mod tests {
let decoded_vaa: Binary = hex::decode(VAA_VALID_TRANSFER_3_SIGS)
.expect("Decoding failed")
.into();
let result = query(&deps, QueryMsg::VerifyVAA { vaa: decoded_vaa, block_time: env.block.time });
let result = query(
&deps,
QueryMsg::VerifyVAA {
vaa: decoded_vaa,
block_time: env.block.time,
},
);
assert_eq!(result, ContractError::GuardianSignatureError.std_err());
}

View File

@ -1,4 +1,4 @@
use cosmwasm_std::{Binary, HumanAddr, Uint128, Coin};
use cosmwasm_std::{Binary, HumanAddr, Coin};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
@ -8,7 +8,6 @@ use crate::state::{GuardianAddress, GuardianSetInfo};
pub struct InitMsg {
pub initial_guardian_set: GuardianSetInfo,
pub guardian_set_expirity: u64,
pub wrapped_asset_code_id: u64,
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
@ -17,15 +16,9 @@ pub enum HandleMsg {
SubmitVAA {
vaa: Binary,
},
RegisterAssetHook {
asset_id: Binary,
},
LockAssets {
asset: HumanAddr,
amount: Uint128,
recipient: Binary,
target_chain: u8,
nonce: u32,
PostMessage {
message: Binary,
nonce: u32
},
TransferFee {
amount: Coin,
@ -37,9 +30,9 @@ pub enum HandleMsg {
#[serde(rename_all = "snake_case")]
pub enum QueryMsg {
GuardianSetInfo {},
WrappedRegistry { chain: u8, address: Binary },
VerifyVAA { vaa: Binary, block_time: u64 },
GetState {},
QueryAddressHex { address: HumanAddr }
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
@ -60,3 +53,9 @@ pub struct WrappedRegistryResponse {
pub struct GetStateResponse {
pub fee: Coin,
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub struct GetAddressHexResponse {
pub hex: String,
}

View File

@ -26,9 +26,6 @@ pub struct ConfigInfo {
// Period for which a guardian set stays active after it has been replaced
pub guardian_set_expirity: u64,
// Code id for wrapped asset contract
pub wrapped_asset_code_id: u64,
// Contract owner address, it can make contract active/inactive
pub owner: CanonicalAddr,
@ -39,12 +36,18 @@ pub struct ConfigInfo {
// Validator Action Approval(VAA) data
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
pub struct ParsedVAA {
pub version: u8,
pub guardian_set_index: u32,
pub len_signers: usize,
pub hash: Vec<u8>,
pub action: u8,
pub timestamp: u64,
pub nonce: u32,
pub len_signers: u8,
pub emitter_chain: u16,
pub emitter_address: Vec<u8>,
pub payload: Vec<u8>,
pub hash: Vec<u8>
}
impl ParsedVAA {
@ -60,9 +63,12 @@ impl ParsedVAA {
1 [65]uint8 signature
body:
0 uint32 unix seconds
4 uint8 action
5 [payload_size]uint8 payload */
0 uint64 timestamp (unix in seconds)
8 uint32 nonce
12 uint16 emitter_chain
14 [32]uint8 emitter_address
46 []uint8 payload
*/
pub const HEADER_LEN: usize = 6;
pub const SIGNATURE_LEN: usize = 66;
@ -70,8 +76,10 @@ impl ParsedVAA {
pub const GUARDIAN_SET_INDEX_POS: usize = 1;
pub const LEN_SIGNER_POS: usize = 5;
pub const VAA_ACTION_POS: usize = 4;
pub const VAA_PAYLOAD_POS: usize = 5;
pub const VAA_NONCE_POS: usize = 8;
pub const VAA_EMITTER_CHAIN_POS: usize = 12;
pub const VAA_EMITTER_ADDRESS_POS: usize = 14;
pub const VAA_PAYLOAD_POS: usize = 46;
// Signature data offsets in the signature block
pub const SIG_DATA_POS: usize = 1;
@ -101,16 +109,23 @@ impl ParsedVAA {
if body_offset + Self::VAA_PAYLOAD_POS > data.len() {
return ContractError::InvalidVAA.std_err();
}
let action = data.get_u8(body_offset + Self::VAA_ACTION_POS);
let payload = &data[body_offset + Self::VAA_PAYLOAD_POS..];
let timestamp = data.get_u64(body_offset);
let nonce = data.get_u32(body_offset + Self::VAA_NONCE_POS);
let emitter_chain = data.get_u16(body_offset + Self::VAA_EMITTER_CHAIN_POS);
let emitter_address = data.get_bytes32(body_offset + Self::VAA_EMITTER_ADDRESS_POS).to_vec();
let payload = data[body_offset + Self::VAA_PAYLOAD_POS..].to_vec();
Ok(ParsedVAA {
version,
guardian_set_index,
len_signers,
timestamp,
nonce,
len_signers: len_signers as u8,
emitter_chain,
emitter_address,
payload,
hash,
action,
payload: payload.to_vec(),
})
}
}
@ -141,6 +156,10 @@ pub struct GuardianSetInfo {
impl GuardianSetInfo {
pub fn quorum(&self) -> usize {
// allow quorum of 0 for testing purposes...
if self.addresses.len() == 0 {
return 0;
}
((self.addresses.len() * 10 / 3) * 2) / 10 + 1
}
}
@ -165,11 +184,11 @@ pub fn guardian_set_set<S: Storage>(
index: u32,
data: &GuardianSetInfo,
) -> StdResult<()> {
bucket(GUARDIAN_SET_KEY, storage).save(&index.to_le_bytes(), data)
bucket(GUARDIAN_SET_KEY, storage).save(&index.to_be_bytes(), data)
}
pub fn guardian_set_get<S: Storage>(storage: &S, index: u32) -> StdResult<GuardianSetInfo> {
bucket_read(GUARDIAN_SET_KEY, storage).load(&index.to_le_bytes())
bucket_read(GUARDIAN_SET_KEY, storage).load(&index.to_be_bytes())
}
pub fn vaa_archive_add<S: Storage>(storage: &mut S, hash: &[u8]) -> StdResult<()> {
@ -199,6 +218,25 @@ pub fn wrapped_asset_address_read<S: Storage>(storage: &S) -> ReadonlyBucket<S,
bucket_read(WRAPPED_ASSET_ADDRESS_KEY, storage)
}
pub struct WormholeGovernance {
pub action: u8,
pub payload: Vec<u8>,
}
impl WormholeGovernance {
pub fn deserialize(data: &Vec<u8>) -> StdResult<Self> {
let data = data.as_slice();
let action = data.get_u8(0);
let payload = &data[1..];
Ok(WormholeGovernance {
action,
payload: payload.to_vec(),
})
}
}
#[cfg(test)]
mod tests {
use super::*;
@ -235,4 +273,10 @@ mod tests {
assert_eq!(build_guardian_set(25).quorum(), 17);
assert_eq!(build_guardian_set(100).quorum(), 67);
}
#[test]
fn test_deserialize() {
let x = vec![1u8,0u8,0u8,0u8,1u8,0u8,0u8,0u8,0u8,0u8,96u8,180u8,80u8,111u8,0u8,0u8,0u8,1u8,0u8,3u8,0u8,0u8,0u8,0u8,0u8,0u8,0u8,0u8,0u8,0u8,0u8,0u8,120u8,73u8,153u8,19u8,90u8,170u8,138u8,60u8,165u8,145u8,68u8,104u8,133u8,47u8,221u8,219u8,221u8,216u8,120u8,157u8,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,54u8,44u8,50u8,51u8,51u8,44u8,49u8,44u8,49u8,49u8,49u8,44u8,49u8,54u8,55u8,44u8,49u8,57u8,48u8,44u8,50u8,48u8,51u8,44u8,49u8,54u8,44u8,49u8,55u8,54u8,44u8,50u8,49u8,56u8,44u8,50u8,53u8,49u8,44u8,49u8,51u8,49u8,44u8,51u8,57u8,44u8,49u8,54u8,44u8,49u8,57u8,53u8,44u8,50u8,50u8,55u8,44u8,49u8,52u8,57u8,44u8,50u8,51u8,54u8,44u8,49u8,57u8,48u8,44u8,50u8,49u8,50u8,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];
ParsedVAA::deserialize(x.as_slice());
}
}

269
terra/deploy.py Normal file
View File

@ -0,0 +1,269 @@
from terra_sdk.client.localterra import AsyncLocalTerra
from terra_sdk.core.auth import StdFee
import asyncio
from terra_sdk.core.wasm import (
MsgStoreCode,
MsgInstantiateContract,
MsgExecuteContract,
)
from terra_sdk.util.contract import get_code_id, get_contract_address, read_file_as_b64
import os
import base64
import pprint
lt = AsyncLocalTerra(gas_prices={"uusd": "0.15"})
terra = lt
deployer = lt.wallets["test1"]
sequence = asyncio.get_event_loop().run_until_complete(deployer.sequence())
async def sign_and_broadcast(*msgs):
global sequence
try:
tx = await deployer.create_and_sign_tx(
msgs=msgs, fee=StdFee(30000000, "20000000uusd"), sequence=sequence
)
result = await terra.tx.broadcast(tx)
sequence += 1
if result.is_tx_error():
raise Exception(result.raw_log)
return result
except:
sequence = await deployer.sequence()
raise
async def store_contract(contract_name):
parent_dir = os.path.dirname(__file__)
contract_bytes = read_file_as_b64(f"{parent_dir}/artifacts/{contract_name}.wasm")
store_code = MsgStoreCode(deployer.key.acc_address, contract_bytes)
result = await sign_and_broadcast(store_code)
code_id = get_code_id(result)
print(f"Code id for {contract_name} is {code_id}")
return code_id
async def store_contracts():
parent_dir = os.path.dirname(__file__)
contract_names = [
i[:-5] for i in os.listdir(f"{parent_dir}/artifacts") if i.endswith(".wasm")
]
return {
contract_name: await store_contract(contract_name)
for contract_name in contract_names
}
class ContractQuerier:
def __init__(self, address):
self.address = address
def __getattr__(self, item):
async def result_fxn(**kwargs):
kwargs = convert_contracts_to_addr(kwargs)
return await terra.wasm.contract_query(self.address, {item: kwargs})
return result_fxn
class Contract:
@staticmethod
async def create(code_id, **kwargs):
kwargs = convert_contracts_to_addr(kwargs)
instantiate = MsgInstantiateContract(deployer.key.acc_address, code_id, kwargs)
result = await sign_and_broadcast(instantiate)
return Contract(get_contract_address(result))
def __init__(self, address):
self.address = address
def __getattr__(self, item):
async def result_fxn(coins=None, **kwargs):
kwargs = convert_contracts_to_addr(kwargs)
execute = MsgExecuteContract(
deployer.key.acc_address, self.address, {item: kwargs}, coins=coins
)
return await sign_and_broadcast(execute)
return result_fxn
@property
def query(self):
return ContractQuerier(self.address)
def convert_contracts_to_addr(obj):
if type(obj) == dict:
return {k: convert_contracts_to_addr(v) for k, v in obj.items()}
if type(obj) in {list, tuple}:
return [convert_contracts_to_addr(i) for i in obj]
if type(obj) == Contract:
return obj.address
return obj
def to_bytes(n, length, byteorder="big"):
return int(n).to_bytes(length, byteorder=byteorder)
def assemble_vaa(emitter_chain, emitter_address, payload):
import time
# version, guardian set index, len signatures
header = to_bytes(1, 1) + to_bytes(0, 4) + to_bytes(0, 1)
# timestamp, nonce, emitter_chain
body = to_bytes(time.time(), 8) + to_bytes(1, 4) + to_bytes(emitter_chain, 2)
# emitter_address, vaa payload
body += emitter_address + payload
return header + body
async def main():
code_ids = await store_contracts()
print(code_ids)
wormhole = await Contract.create(
code_id=code_ids["wormhole"],
guardian_set_expirity=10 ** 15,
initial_guardian_set={"addresses": [], "expiration_time": 10 ** 15},
)
token_bridge = await Contract.create(
code_id=code_ids["token_bridge"],
owner=deployer.key.acc_address,
wormhole_contract=wormhole,
wrapped_asset_code_id=int(code_ids["cw20_wrapped"]),
)
mock_token = await Contract.create(
code_id=code_ids["cw20_base"],
name="MOCK",
symbol="MCK",
decimals=6,
initial_balances=[{"address": deployer.key.acc_address, "amount": "100000000"}],
mint=None,
)
raw_addr = deployer.key.raw_address
recipient = b"\0" * 12 + raw_addr
recipient = base64.b64encode(recipient)
print(
"Balance before initiate transfer",
await mock_token.query.balance(address=deployer.key.acc_address),
)
await mock_token.increase_allowance(spender=token_bridge, amount="1000")
bridge_canonical = bytes.fromhex(
(await wormhole.query.query_address_hex(address=token_bridge))["hex"]
)
await token_bridge.register_chain(
chain_id=3, chain_address=base64.b64encode(bridge_canonical).decode("utf-8")
)
resp = await token_bridge.initiate_transfer(
asset=mock_token,
amount="1000",
recipient_chain=3,
recipient=recipient.decode("utf-8"),
nonce=0,
coins={"uluna": "10000"},
)
print(
"Balance after initiate transfer",
await mock_token.query.balance(address=deployer.key.acc_address),
)
logs = resp.logs[0].events_by_type
transfer_data = {
k: v[0] for k, v in logs["from_contract"].items() if k.startswith("message")
}
vaa = assemble_vaa(
transfer_data["message.chain_id"],
bytes.fromhex(transfer_data["message.sender"]),
bytes.fromhex(transfer_data["message.message"]),
)
await token_bridge.submit_vaa(data=base64.b64encode(vaa).decode("utf-8"))
print(
"Balance after complete transfer",
await mock_token.query.balance(address=deployer.key.acc_address),
)
# pretend there exists another bridge contract with the same address but on solana
await token_bridge.register_chain(
chain_id=1, chain_address=base64.b64encode(bridge_canonical).decode("utf-8")
)
resp = await token_bridge.create_asset_meta(
asset_address=mock_token,
nonce=1,
coins={"uluna": "10000"},
)
logs = resp.logs[0].events_by_type
create_meta_data = {
k: v[0] for k, v in logs["from_contract"].items() if k.startswith("message")
}
message_bytes = bytes.fromhex(create_meta_data["message.message"])
# switch the chain of the asset meta to say its from solana
message_bytes = message_bytes[:1] + to_bytes(1, 2) + message_bytes[3:]
vaa = assemble_vaa(
1, # totally came from solana
bytes.fromhex(create_meta_data["message.sender"]),
message_bytes,
)
# attest this metadata and make a wrapped asset from solana
resp = await token_bridge.submit_vaa(data=base64.b64encode(vaa).decode("utf-8"))
wrapped_token = Contract(get_contract_address(resp))
# now send from solana...
message_bytes = bytes.fromhex(transfer_data["message.message"])
message_bytes = message_bytes[:1] + to_bytes(1, 2) + message_bytes[3:]
vaa = assemble_vaa(
1, # totally came from solana
bytes.fromhex(transfer_data["message.sender"]),
message_bytes,
)
print(
"Balance before completing transfer from solana",
await wrapped_token.query.balance(address=deployer.key.acc_address),
)
await token_bridge.submit_vaa(data=base64.b64encode(vaa).decode("utf-8"))
print(
"Balance after completing transfer from solana",
await wrapped_token.query.balance(address=deployer.key.acc_address),
)
await wrapped_token.increase_allowance(spender=token_bridge, amount="1000")
resp = await token_bridge.initiate_transfer(
asset=wrapped_token,
amount="1000",
recipient_chain=1,
recipient=recipient.decode("utf-8"),
nonce=0,
coins={"uluna": "10000"},
)
print(
"Balance after completing transfer to solana",
await wrapped_token.query.balance(address=deployer.key.acc_address),
)
if __name__ == "__main__":
asyncio.get_event_loop().run_until_complete(main())