terra: columbus-5 contracts by default

Change-Id: I007689b032c6182e35421b47b56787a657a6919c
This commit is contained in:
Reisen 2021-10-04 11:52:11 +00:00 committed by David Paryente
parent 4933f35213
commit b662de4efb
47 changed files with 1855 additions and 4473 deletions

1254
terra/Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -10,4 +10,4 @@ debug-assertions = false
codegen-units = 1
panic = 'abort'
incremental = false
overflow-checks = true
overflow-checks = true

View File

@ -1,6 +1,6 @@
# This is a multi-stage docker file, first stage builds contracts
# And the second one creates node.js environment to deploy them
FROM cosmwasm/workspace-optimizer:0.10.4@sha256:a976db4ee7add887a6af26724b804bbd9e9d534554506447e72ac57e65357db9 AS builder
FROM cosmwasm/workspace-optimizer:0.12.1@sha256:1508cf7545f4b656ecafa34e29c1acf200cdab47fced85c2bc076c0c158b1338 AS builder
ADD Cargo.lock /code/
ADD Cargo.toml /code/
ADD contracts /code/contracts

Binary file not shown.

View File

@ -1,3 +0,0 @@
# Terra Wormhole Contracts
The Wormhole Terra integration is developed and maintained by Everstake / @ysavchenko.

View File

@ -1,5 +0,0 @@
[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

@ -1,28 +0,0 @@
[package]
name = "cw20-wrapped"
version = "0.1.0"
authors = ["Yuriy Savchenko <yuriy.savchenko@gmail.com>"]
edition = "2018"
description = "Wrapped CW20 token contract"
[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.16.0" }
cosmwasm-storage = { version = "0.16.0" }
schemars = "0.8.1"
serde = { version = "1.0.103", default-features = false, features = ["derive"] }
cw2 = { version = "0.8.0" }
cw20 = { version = "0.8.0" }
cw20-legacy = { version = "0.2.0", features = ["library"]}
cw-storage-plus = { version = "0.8.0" }
thiserror = { version = "1.0.20" }
[dev-dependencies]
cosmwasm-vm = { version = "0.16.0", default-features = false }

View File

@ -1,374 +0,0 @@
use cosmwasm_std::{
entry_point,
to_binary,
Binary,
CosmosMsg,
Deps,
DepsMut,
Env,
MessageInfo,
Response,
StdError,
StdResult,
Uint128,
WasmMsg,
};
use cw2::set_contract_version;
use cw20_legacy::{
allowances::{
execute_burn_from,
execute_decrease_allowance,
execute_increase_allowance,
execute_send_from,
execute_transfer_from,
query_allowance,
},
contract::{
execute_mint,
execute_send,
execute_transfer,
query_balance,
},
state::{
MinterData,
TokenInfo,
TOKEN_INFO,
},
ContractError,
};
use crate::{
msg::{
ExecuteMsg,
InstantiateMsg,
QueryMsg,
WrappedAssetInfoResponse,
},
state::{
wrapped_asset_info,
wrapped_asset_info_read,
WrappedAssetInfo,
},
};
use cw20::TokenInfoResponse;
use std::string::String;
type HumanAddr = String;
// version info for migration info
const CONTRACT_NAME: &str = "crates.io:cw20-base";
const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION");
#[cfg_attr(not(feature = "library"), entry_point)]
pub fn instantiate(
deps: DepsMut,
env: Env,
info: MessageInfo,
msg: InstantiateMsg,
) -> StdResult<Response> {
set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?;
// store token info using cw20-base format
let data = TokenInfo {
name: msg.name,
symbol: msg.symbol,
decimals: msg.decimals,
total_supply: Uint128::new(0),
// set creator as minter
mint: Some(MinterData {
minter: deps.api.addr_canonicalize(&info.sender.as_str())?,
cap: None,
}),
};
TOKEN_INFO.save(deps.storage, &data)?;
// save wrapped asset info
let data = WrappedAssetInfo {
asset_chain: msg.asset_chain,
asset_address: msg.asset_address,
bridge: deps.api.addr_canonicalize(&info.sender.as_str())?,
};
wrapped_asset_info(deps.storage).save(&data)?;
if let Some(mint_info) = msg.mint {
execute_mint(deps, env, info, mint_info.recipient, mint_info.amount)
.map_err(|e| StdError::generic_err(format!("{}", e)))?;
}
if let Some(hook) = msg.init_hook {
Ok(
Response::new().add_message(CosmosMsg::Wasm(WasmMsg::Execute {
contract_addr: hook.contract_addr,
msg: hook.msg,
funds: vec![],
})),
)
} else {
Ok(Response::default())
}
}
#[cfg_attr(not(feature = "library"), entry_point)]
pub fn execute(
deps: DepsMut,
env: Env,
info: MessageInfo,
msg: ExecuteMsg,
) -> Result<Response, ContractError> {
match msg {
// these all come from cw20-base to implement the cw20 standard
ExecuteMsg::Transfer { recipient, amount } => {
Ok(execute_transfer(deps, env, info, recipient, amount)?)
}
ExecuteMsg::Burn { account, amount } => {
Ok(execute_burn_from(deps, env, info, account, amount)?)
}
ExecuteMsg::Send {
contract,
amount,
msg,
} => Ok(execute_send(deps, env, info, contract, amount, msg)?),
ExecuteMsg::Mint { recipient, amount } => {
execute_mint_wrapped(deps, env, info, recipient, amount)
}
ExecuteMsg::IncreaseAllowance {
spender,
amount,
expires,
} => Ok(execute_increase_allowance(
deps, env, info, spender, amount, expires,
)?),
ExecuteMsg::DecreaseAllowance {
spender,
amount,
expires,
} => Ok(execute_decrease_allowance(
deps, env, info, spender, amount, expires,
)?),
ExecuteMsg::TransferFrom {
owner,
recipient,
amount,
} => Ok(execute_transfer_from(
deps, env, info, owner, recipient, amount,
)?),
ExecuteMsg::BurnFrom { owner, amount } => {
Ok(execute_burn_from(deps, env, info, owner, amount)?)
}
ExecuteMsg::SendFrom {
owner,
contract,
amount,
msg,
} => Ok(execute_send_from(
deps, env, info, owner, contract, amount, msg,
)?),
}
}
fn execute_mint_wrapped(
deps: DepsMut,
env: Env,
info: MessageInfo,
recipient: HumanAddr,
amount: Uint128,
) -> Result<Response, ContractError> {
// Only bridge can mint
let wrapped_info = wrapped_asset_info_read(deps.storage).load()?;
if wrapped_info.bridge != deps.api.addr_canonicalize(&info.sender.as_str())? {
return Err(ContractError::Unauthorized {});
}
Ok(execute_mint(deps, env, info, recipient, amount)?)
}
pub fn query(deps: Deps, msg: QueryMsg) -> StdResult<Binary> {
match msg {
QueryMsg::WrappedAssetInfo {} => to_binary(&query_wrapped_asset_info(deps)?),
// inherited from cw20-base
QueryMsg::TokenInfo {} => to_binary(&query_token_info(deps)?),
QueryMsg::Balance { address } => to_binary(&query_balance(deps, address)?),
QueryMsg::Allowance { owner, spender } => {
to_binary(&query_allowance(deps, owner, spender)?)
}
}
}
pub fn query_token_info(deps: Deps) -> StdResult<TokenInfoResponse> {
let info = TOKEN_INFO.load(deps.storage)?;
Ok(TokenInfoResponse {
name: String::from("Wormhole:") + info.name.as_str(),
symbol: String::from("wh") + info.symbol.as_str(),
decimals: info.decimals,
total_supply: info.total_supply,
})
}
pub fn query_wrapped_asset_info(deps: Deps) -> StdResult<WrappedAssetInfoResponse> {
let info = wrapped_asset_info_read(deps.storage).load()?;
Ok(WrappedAssetInfoResponse {
asset_chain: info.asset_chain,
asset_address: info.asset_address,
bridge: deps.api.addr_humanize(&info.bridge)?,
})
}
#[cfg(test)]
mod tests {
use super::*;
use cosmwasm_std::testing::{
mock_dependencies,
mock_env,
mock_info,
};
use cw20::TokenInfoResponse;
const CANONICAL_LENGTH: usize = 20;
fn get_balance(deps: Deps, address: HumanAddr) -> Uint128 {
query_balance(deps, address.into()).unwrap().balance
}
fn do_init(mut deps: DepsMut, creator: &HumanAddr) {
let init_msg = InstantiateMsg {
name: "Integers".to_string(),
symbol: "INT".to_string(),
asset_chain: 1,
asset_address: vec![1; 32].into(),
decimals: 10,
mint: None,
init_hook: None,
};
let env = mock_env();
let info = mock_info(creator, &[]);
let res = instantiate(deps, env, info, init_msg).unwrap();
assert_eq!(0, res.messages.len());
assert_eq!(
query_token_info(deps.as_ref()).unwrap(),
TokenInfoResponse {
name: "Wormhole Wrapped".to_string(),
symbol: "WWT".to_string(),
decimals: 10,
total_supply: Uint128::from(0u128),
}
);
assert_eq!(
query_wrapped_asset_info(deps.as_ref()).unwrap(),
WrappedAssetInfoResponse {
asset_chain: 1,
asset_address: vec![1; 32].into(),
bridge: deps.api.addr_validate(creator).unwrap(),
}
);
}
fn do_init_and_mint(
mut deps: DepsMut,
creator: &HumanAddr,
mint_to: &HumanAddr,
amount: Uint128,
) {
do_init(deps, creator);
let msg = ExecuteMsg::Mint {
recipient: mint_to.clone(),
amount,
};
let env = mock_env();
let info = mock_info(creator, &[]);
let res = execute(deps.as_mut(), env, info, msg.clone()).unwrap();
assert_eq!(0, res.messages.len());
assert_eq!(get_balance(deps.as_ref(), mint_to.clone(),), amount);
assert_eq!(
query_token_info(deps.as_ref()).unwrap(),
TokenInfoResponse {
name: "Wormhole Wrapped".to_string(),
symbol: "WWT".to_string(),
decimals: 10,
total_supply: amount,
}
);
}
#[test]
fn can_mint_by_minter() {
let mut deps = mock_dependencies(&[]);
let minter = HumanAddr::from("minter");
let recipient = HumanAddr::from("recipient");
let amount = Uint128::new(222_222_222);
do_init_and_mint(deps.as_mut(), &minter, &recipient, amount);
}
#[test]
fn others_cannot_mint() {
let mut deps = mock_dependencies(&[]);
let minter = HumanAddr::from("minter");
let recipient = HumanAddr::from("recipient");
do_init(deps.as_mut(), &minter);
let amount = Uint128::new(222_222_222);
let msg = ExecuteMsg::Mint {
recipient: recipient.clone(),
amount,
};
let other_address = HumanAddr::from("other");
let env = mock_env();
let info = mock_info(&other_address, &[]);
let res = execute(deps.as_mut(), env, info, msg);
assert_eq!(
format!("{}", res.unwrap_err()),
format!("{}", crate::error::ContractError::Unauthorized {})
);
}
#[test]
fn transfer_balance_success() {
let mut deps = mock_dependencies(&[]);
let minter = HumanAddr::from("minter");
let owner = HumanAddr::from("owner");
let amount_initial = Uint128::new(222_222_222);
do_init_and_mint(deps.as_mut(), &minter, &owner, amount_initial);
// Transfer
let recipient = HumanAddr::from("recipient");
let amount_transfer = Uint128::new(222_222);
let msg = ExecuteMsg::Transfer {
recipient: recipient.clone(),
amount: amount_transfer,
};
let env = mock_env();
let info = mock_info(&owner, &[]);
let res = execute(deps.as_mut(), env, info, msg.clone()).unwrap();
assert_eq!(0, res.messages.len());
assert_eq!(get_balance(deps.as_ref(), owner), Uint128::new(222_000_000));
assert_eq!(get_balance(deps.as_ref(), recipient), amount_transfer);
}
#[test]
fn transfer_balance_not_enough() {
let mut deps = mock_dependencies(&[]);
let minter = HumanAddr::from("minter");
let owner = HumanAddr::from("owner");
let amount_initial = Uint128::new(222_221);
do_init_and_mint(deps.as_mut(), &minter, &owner, amount_initial);
// Transfer
let recipient = HumanAddr::from("recipient");
let amount_transfer = Uint128::new(222_222);
let msg = ExecuteMsg::Transfer {
recipient: recipient.clone(),
amount: amount_transfer,
};
let env = mock_env();
let info = mock_info(&owner, &[]);
let _ = execute(deps.as_mut(), env, info, msg.clone()).unwrap_err(); // Will panic if no error
}
}

View File

@ -1,27 +0,0 @@
use cosmwasm_std::StdError;
use thiserror::Error;
#[derive(Error, Debug)]
pub enum ContractError {
// CW20 errors
#[error("{0}")]
Std(#[from] StdError),
#[error("Unauthorized")]
Unauthorized {},
#[error("Cannot set to own account")]
CannotSetOwnAccount {},
#[error("Invalid zero amount")]
InvalidZeroAmount {},
#[error("Allowance is expired")]
Expired {},
#[error("No allowance for this account")]
NoAllowance {},
#[error("Minting cannot exceed the cap")]
CannotExceedCap {},
}

View File

@ -1,7 +0,0 @@
mod error;
pub mod contract;
pub mod msg;
pub mod state;
pub use crate::error::ContractError;

View File

@ -1,122 +0,0 @@
#![allow(clippy::field_reassign_with_default)]
use schemars::JsonSchema;
use serde::{
Deserialize,
Serialize,
};
use cosmwasm_std::{
Addr,
Binary,
Uint128,
};
use cw20::Expiration;
type HumanAddr = String;
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
pub struct InstantiateMsg {
pub name: String,
pub symbol: String,
pub asset_chain: u16,
pub asset_address: Binary,
pub decimals: u8,
pub mint: Option<InitMint>,
pub init_hook: Option<InitHook>,
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
pub struct InitHook {
pub msg: Binary,
pub contract_addr: HumanAddr,
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
pub struct InitMint {
pub recipient: HumanAddr,
pub amount: Uint128,
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum ExecuteMsg {
/// Implements CW20. Transfer is a base message to move tokens to another account without triggering actions
Transfer {
recipient: HumanAddr,
amount: Uint128,
},
/// Slightly different than CW20. Burn is a base message to destroy tokens forever
Burn { account: HumanAddr, amount: Uint128 },
/// Implements CW20. Send is a base message to transfer tokens to a contract and trigger an action
/// on the receiving contract.
Send {
contract: HumanAddr,
amount: Uint128,
msg: Binary,
},
/// Implements CW20 "mintable" extension. If authorized, creates amount new tokens
/// and adds to the recipient balance.
Mint {
recipient: HumanAddr,
amount: Uint128,
},
/// Implements CW20 "approval" extension. Allows spender to access an additional amount tokens
/// from the owner's (env.sender) account. If expires is Some(), overwrites current allowance
/// expiration with this one.
IncreaseAllowance {
spender: HumanAddr,
amount: Uint128,
expires: Option<Expiration>,
},
/// Implements CW20 "approval" extension. Lowers the spender's access of tokens
/// from the owner's (env.sender) account by amount. If expires is Some(), overwrites current
/// allowance expiration with this one.
DecreaseAllowance {
spender: HumanAddr,
amount: Uint128,
expires: Option<Expiration>,
},
/// Implements CW20 "approval" extension. Transfers amount tokens from owner -> recipient
/// if `env.sender` has sufficient pre-approval.
TransferFrom {
owner: HumanAddr,
recipient: HumanAddr,
amount: Uint128,
},
/// Implements CW20 "approval" extension. Sends amount tokens from owner -> contract
/// if `env.sender` has sufficient pre-approval.
SendFrom {
owner: HumanAddr,
contract: HumanAddr,
amount: Uint128,
msg: Binary,
},
/// Implements CW20 "approval" extension. Destroys tokens forever
BurnFrom { owner: HumanAddr, amount: Uint128 },
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum QueryMsg {
// Generic information about the wrapped asset
WrappedAssetInfo {},
/// Implements CW20. Returns the current balance of the given address, 0 if unset.
Balance {
address: HumanAddr,
},
/// Implements CW20. Returns metadata on the contract - name, decimals, supply, etc.
TokenInfo {},
/// Implements CW20 "allowance" extension.
/// Returns how much spender can use from owner account, 0 if unset.
Allowance {
owner: HumanAddr,
spender: HumanAddr,
},
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
pub struct WrappedAssetInfoResponse {
pub asset_chain: u16, // Asset chain id
pub asset_address: Binary, // Asset smart contract address in the original chain
pub bridge: Addr, // Bridge address, authorized to mint and burn wrapped tokens
}

View File

@ -1,37 +0,0 @@
use schemars::JsonSchema;
use serde::{
Deserialize,
Serialize,
};
use cosmwasm_std::{
Binary,
CanonicalAddr,
Storage,
};
use cosmwasm_storage::{
singleton,
singleton_read,
ReadonlySingleton,
Singleton,
};
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: 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
}
pub fn wrapped_asset_info(storage: &mut dyn Storage) -> Singleton<WrappedAssetInfo> {
singleton(storage, KEY_WRAPPED_ASSET)
}
pub fn wrapped_asset_info_read(
storage: &dyn Storage,
) -> ReadonlySingleton<WrappedAssetInfo> {
singleton_read(storage, KEY_WRAPPED_ASSET)
}

View File

@ -1,253 +0,0 @@
static WASM: &[u8] =
include_bytes!("../../../target/wasm32-unknown-unknown/release/cw20_wrapped.wasm");
use cosmwasm_std::{
from_slice,
Binary,
Env,
HandleResponse,
HandleResult,
HumanAddr,
InitResponse,
Uint128,
};
use cosmwasm_storage::to_length_prefixed;
use cosmwasm_vm::{
testing::{
handle,
init,
mock_env,
mock_instance,
query,
MockApi,
MockQuerier,
MockStorage,
},
Api,
Instance,
Storage,
};
use cw20_wrapped::{
msg::{
HandleMsg,
InitMsg,
QueryMsg,
},
state::{
WrappedAssetInfo,
KEY_WRAPPED_ASSET,
},
ContractError,
};
enum TestAddress {
INITIALIZER,
RECIPIENT,
SENDER,
}
impl TestAddress {
fn value(&self) -> HumanAddr {
match self {
TestAddress::INITIALIZER => HumanAddr::from("addr0000"),
TestAddress::RECIPIENT => HumanAddr::from("addr2222"),
TestAddress::SENDER => HumanAddr::from("addr3333"),
}
}
}
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_wrapped_asset_info<S: Storage>(storage: &S) -> WrappedAssetInfo {
let key = to_length_prefixed(KEY_WRAPPED_ASSET);
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) -> Instance<MockStorage, MockApi, MockQuerier> {
let mut deps = mock_instance(WASM, &[]);
let init_msg = InitMsg {
asset_chain: 1,
asset_address: vec![1; 32].into(),
decimals: 10,
mint: None,
init_hook: None,
};
let env = mock_env_height(&TestAddress::INITIALIZER.value(), height, 0);
let res: InitResponse = init(&mut deps, env, init_msg).unwrap();
assert_eq!(0, res.messages.len());
// query the store directly
let api = deps.api;
deps.with_storage(|storage| {
assert_eq!(
get_wrapped_asset_info(storage),
WrappedAssetInfo {
asset_chain: 1,
asset_address: vec![1; 32].into(),
bridge: api.canonical_address(&TestAddress::INITIALIZER.value()).0?,
}
);
Ok(())
})
.unwrap();
deps
}
fn do_mint(
deps: &mut Instance<MockStorage, MockApi, MockQuerier>,
height: u64,
recipient: &HumanAddr,
amount: &Uint128,
) {
let mint_msg = HandleMsg::Mint {
recipient: recipient.clone(),
amount: amount.clone(),
};
let env = mock_env_height(&TestAddress::INITIALIZER.value(), height, 0);
let handle_response: HandleResponse = handle(deps, env, mint_msg).unwrap();
assert_eq!(0, handle_response.messages.len());
}
fn do_transfer(
deps: &mut Instance<MockStorage, MockApi, MockQuerier>,
height: u64,
sender: &HumanAddr,
recipient: &HumanAddr,
amount: &Uint128,
) {
let transfer_msg = HandleMsg::Transfer {
recipient: recipient.clone(),
amount: amount.clone(),
};
let env = mock_env_height(sender, height, 0);
let handle_response: HandleResponse = handle(deps, env, transfer_msg).unwrap();
assert_eq!(0, handle_response.messages.len());
}
fn check_balance(
deps: &mut Instance<MockStorage, MockApi, MockQuerier>,
address: &HumanAddr,
amount: &Uint128,
) {
let query_response = query(
deps,
QueryMsg::Balance {
address: address.clone(),
},
)
.unwrap();
assert_eq!(
query_response.as_slice(),
format!("{{\"balance\":\"{}\"}}", amount.u128()).as_bytes()
);
}
fn check_token_details(deps: &mut Instance<MockStorage, MockApi, MockQuerier>, supply: &Uint128) {
let query_response = query(deps, QueryMsg::TokenInfo {}).unwrap();
assert_eq!(
query_response.as_slice(),
format!(
"{{\"name\":\"Wormhole Wrapped\",\
\"symbol\":\"WWT\",\
\"decimals\":10,\
\"total_supply\":\"{}\"}}",
supply.u128()
)
.as_bytes()
);
}
#[test]
fn init_works() {
let mut deps = do_init(111);
check_token_details(&mut deps, &Uint128(0));
}
#[test]
fn query_works() {
let mut deps = do_init(111);
let query_response = query(&mut deps, QueryMsg::WrappedAssetInfo {}).unwrap();
assert_eq!(
query_response.as_slice(),
format!(
"{{\"asset_chain\":1,\
\"asset_address\":\"{}\",\
\"bridge\":\"{}\"}}",
Binary::from(vec![1; 32]).to_base64(),
TestAddress::INITIALIZER.value().as_str()
)
.as_bytes()
);
}
#[test]
fn mint_works() {
let mut deps = do_init(111);
do_mint(
&mut deps,
112,
&TestAddress::RECIPIENT.value(),
&Uint128(123_123_123),
);
check_balance(
&mut deps,
&TestAddress::RECIPIENT.value(),
&Uint128(123_123_123),
);
check_token_details(&mut deps, &Uint128(123_123_123));
}
#[test]
fn others_cannot_mint() {
let mut deps = do_init(111);
let mint_msg = HandleMsg::Mint {
recipient: TestAddress::RECIPIENT.value(),
amount: Uint128(123_123_123),
};
let env = mock_env_height(&TestAddress::RECIPIENT.value(), 112, 0);
let handle_result: HandleResult<HandleResponse> = handle(&mut deps, env, mint_msg);
assert_eq!(
format!("{}", handle_result.unwrap_err()),
format!("{}", ContractError::Unauthorized {})
);
}
#[test]
fn transfer_works() {
let mut deps = do_init(111);
do_mint(
&mut deps,
112,
&TestAddress::SENDER.value(),
&Uint128(123_123_123),
);
do_transfer(
&mut deps,
113,
&TestAddress::SENDER.value(),
&TestAddress::RECIPIENT.value(),
&Uint128(123_123_000),
);
check_balance(&mut deps, &TestAddress::SENDER.value(), &Uint128(123));
check_balance(
&mut deps,
&TestAddress::RECIPIENT.value(),
&Uint128(123_123_000),
);
}

View File

@ -1,5 +0,0 @@
[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

@ -1,36 +0,0 @@
[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.16.0" }
cosmwasm-storage = { version = "0.16.0" }
schemars = "0.8.1"
serde = { version = "1.0.103", default-features = false, features = ["derive"] }
cw20 = "0.8.0"
cw20-base = { version = "0.8.0", features = ["library"] }
cw20-wrapped = { path = "../cw20-wrapped", features = ["library"] }
terraswap = "2.4.0"
wormhole = { path = "../wormhole", features = ["library"] }
thiserror = { version = "1.0.20" }
k256 = { version = "0.9.4", 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.16.0", default-features = false }
serde_json = "1.0"

View File

@ -1,733 +0,0 @@
use crate::msg::WrappedRegistryResponse;
use cosmwasm_std::{
entry_point,
to_binary,
Binary,
CanonicalAddr,
Coin,
CosmosMsg,
Deps,
DepsMut,
Empty,
Env,
MessageInfo,
QueryRequest,
Response,
StdError,
StdResult,
Uint128,
WasmMsg,
WasmQuery,
};
use crate::{
msg::{
ExecuteMsg,
InstantiateMsg,
QueryMsg,
},
state::{
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::{
extend_address_to_32,
extend_string_to_32,
get_string_from_32,
ByteUtils,
},
error::ContractError,
};
use cw20_base::msg::{
ExecuteMsg as TokenMsg,
QueryMsg as TokenQuery,
};
use wormhole::msg::{
ExecuteMsg as WormholeExecuteMsg,
QueryMsg as WormholeQueryMsg,
};
use wormhole::state::{
vaa_archive_add,
vaa_archive_check,
GovernancePacket,
ParsedVAA,
};
use cw20::TokenInfoResponse;
use cw20_wrapped::msg::{
ExecuteMsg as WrappedMsg,
InitHook,
InstantiateMsg as WrappedInit,
QueryMsg as WrappedQuery,
WrappedAssetInfoResponse,
};
use terraswap::asset::{
Asset,
AssetInfo,
};
use sha3::{
Digest,
Keccak256,
};
use std::cmp::{
max,
min,
};
type HumanAddr = String;
// Chain ID of Terra
const CHAIN_ID: u16 = 3;
const WRAPPED_ASSET_UPDATING: &str = "updating";
#[cfg_attr(not(feature = "library"), entry_point)]
pub fn instantiate(
deps: DepsMut,
_env: Env,
_info: MessageInfo,
msg: InstantiateMsg,
) -> StdResult<Response> {
// Save general wormhole info
let state = ConfigInfo {
gov_chain: msg.gov_chain,
gov_address: msg.gov_address.as_slice().to_vec(),
wormhole_contract: msg.wormhole_contract,
wrapped_asset_code_id: msg.wrapped_asset_code_id,
};
config(deps.storage).save(&state)?;
Ok(Response::default())
}
pub fn coins_after_tax(deps: DepsMut, coins: Vec<Coin>) -> StdResult<Vec<Coin>> {
let mut res = vec![];
for coin in coins {
let asset = Asset {
amount: coin.amount.clone(),
info: AssetInfo::NativeToken {
denom: coin.denom.clone(),
},
};
res.push(asset.deduct_tax(&deps.querier)?);
}
Ok(res)
}
pub fn parse_vaa(deps: DepsMut, 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)
}
#[cfg_attr(not(feature = "library"), entry_point)]
pub fn execute(deps: DepsMut, env: Env, info: MessageInfo, msg: ExecuteMsg) -> StdResult<Response> {
match msg {
ExecuteMsg::RegisterAssetHook { asset_id } => {
handle_register_asset(deps, env, info, &asset_id.as_slice())
}
ExecuteMsg::InitiateTransfer {
asset,
amount,
recipient_chain,
recipient,
fee,
nonce,
} => handle_initiate_transfer(
deps,
env,
info,
asset,
amount,
recipient_chain,
recipient.as_slice().to_vec(),
fee,
nonce,
),
ExecuteMsg::SubmitVaa { data } => submit_vaa(deps, env, info, &data),
ExecuteMsg::CreateAssetMeta {
asset_address,
nonce,
} => handle_create_asset_meta(deps, env, info, &asset_address, nonce),
}
}
/// Handle wrapped asset registration messages
fn handle_register_asset(
deps: DepsMut,
_env: Env,
info: MessageInfo,
asset_id: &[u8],
) -> StdResult<Response> {
let mut bucket = wrapped_asset(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, &info.sender.to_string())?;
let contract_address: CanonicalAddr = deps.api.addr_canonicalize(&info.sender.as_str())?;
wrapped_asset_address(deps.storage).save(contract_address.as_slice(), &asset_id.to_vec())?;
Ok(Response::new()
.add_attribute("action", "register_asset")
.add_attribute("asset_id", format!("{:?}", asset_id))
.add_attribute("contract_addr", info.sender))
}
fn handle_attest_meta(
deps: DepsMut,
env: Env,
emitter_chain: u16,
emitter_address: Vec<u8>,
data: &Vec<u8>,
) -> StdResult<Response> {
let meta = AssetMeta::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::generic_err("invalid emitter"));
}
if CHAIN_ID == meta.token_chain {
return Err(StdError::generic_err(
"this asset is native to this chain and should not be attested",
));
}
let cfg = config_read(deps.storage).load()?;
let asset_id = build_asset_id(meta.token_chain, &meta.token_address.as_slice());
if wrapped_asset_read(deps.storage).load(&asset_id).is_ok() {
return Err(StdError::generic_err(
"this asset has already been attested",
));
}
wrapped_asset(deps.storage).save(&asset_id, &HumanAddr::from(WRAPPED_ASSET_UPDATING))?;
Ok(
Response::new().add_message(CosmosMsg::Wasm(WasmMsg::Instantiate {
admin: None,
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: min(meta.decimals, 8u8),
mint: None,
init_hook: Some(InitHook {
contract_addr: env.contract.address.to_string(),
msg: to_binary(&ExecuteMsg::RegisterAssetHook {
asset_id: asset_id.to_vec().into(),
})?,
}),
})?,
funds: vec![],
label: String::new(),
})),
)
}
fn handle_create_asset_meta(
deps: DepsMut,
env: Env,
info: MessageInfo,
asset_address: &HumanAddr,
nonce: u32,
) -> StdResult<Response> {
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.addr_canonicalize(asset_address)?;
let token_info: TokenInfoResponse = deps.querier.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(Response::new()
.add_message(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, info.funds.clone())?,
}))
.add_attribute("meta.token_chain", CHAIN_ID.to_string())
.add_attribute("meta.token", asset_address)
.add_attribute("meta.nonce", nonce.to_string())
.add_attribute("meta.block_time", env.block.time.seconds().to_string()))
}
fn submit_vaa(
mut deps: DepsMut,
env: Env,
info: MessageInfo,
data: &Binary,
) -> StdResult<Response> {
let state = config_read(deps.storage).load()?;
let vaa = parse_vaa(deps.branch(), env.block.time.seconds(), data)?;
let data = vaa.payload;
if vaa_archive_check(deps.storage, vaa.hash.as_slice()) {
return ContractError::VaaAlreadyExecuted.std_err();
}
vaa_archive_add(deps.storage, vaa.hash.as_slice())?;
// check if vaa is from governance
if state.gov_chain == vaa.emitter_chain && state.gov_address == vaa.emitter_address {
return handle_governance_payload(deps, env, &data);
}
let message = TokenBridgeMessage::deserialize(&data)?;
match message.action {
Action::TRANSFER => handle_complete_transfer(
deps,
env,
info,
vaa.emitter_chain,
vaa.emitter_address,
&message.payload,
),
Action::ATTEST_META => handle_attest_meta(
deps,
env,
vaa.emitter_chain,
vaa.emitter_address,
&message.payload,
),
_ => ContractError::InvalidVAAAction.std_err(),
}
}
fn handle_governance_payload(deps: DepsMut, env: Env, data: &Vec<u8>) -> StdResult<Response> {
let gov_packet = GovernancePacket::deserialize(&data)?;
let module = get_string_from_32(&gov_packet.module)?;
if module != "TokenBridge" {
return Err(StdError::generic_err("this is not a valid module"));
}
if gov_packet.chain != 0 && gov_packet.chain != CHAIN_ID {
return Err(StdError::generic_err(
"the governance VAA is for another chain",
));
}
match gov_packet.action {
1u8 => handle_register_chain(deps, env, &gov_packet.payload),
_ => ContractError::InvalidVAAAction.std_err(),
}
}
fn handle_register_chain(deps: DepsMut, _env: Env, data: &Vec<u8>) -> StdResult<Response> {
let RegisterChain {
chain_id,
chain_address,
} = RegisterChain::deserialize(&data)?;
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(deps.storage);
bucket.save(&chain_id.to_be_bytes(), &chain_address)?;
Ok(Response::new()
.add_attribute("chain_id", chain_id.to_string())
.add_attribute("chain_address", hex::encode(chain_address)))
}
fn handle_complete_transfer(
deps: DepsMut,
_env: Env,
info: MessageInfo,
emitter_chain: u16,
emitter_address: Vec<u8>,
data: &Vec<u8>,
) -> StdResult<Response> {
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::generic_err("invalid emitter"));
}
if transfer_info.recipient_chain != CHAIN_ID {
return Err(StdError::generic_err(
"this transfer is not directed at this chain",
));
}
let token_chain = transfer_info.token_chain;
let target_address = (&transfer_info.recipient.as_slice()).get_address(0);
let (not_supported_amount, mut amount) = transfer_info.amount;
let (not_supported_fee, mut fee) = transfer_info.fee;
amount = amount.checked_sub(fee).unwrap();
// Check high 128 bit of amount value to be empty
if not_supported_amount != 0 || not_supported_fee != 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
.addr_humanize(&target_address)
.or_else(|_| ContractError::WrongTargetAddressFormat.std_err())?;
let mut messages = vec![CosmosMsg::Wasm(WasmMsg::Execute {
contract_addr: contract_addr.clone(),
msg: to_binary(&WrappedMsg::Mint {
recipient: recipient.to_string(),
amount: Uint128::from(amount),
})?,
funds: vec![],
})];
if fee != 0 {
messages.push(CosmosMsg::Wasm(WasmMsg::Execute {
contract_addr: contract_addr.clone(),
msg: to_binary(&WrappedMsg::Mint {
recipient: info.sender.to_string(),
amount: Uint128::from(fee),
})?,
funds: vec![],
}))
}
Ok(Response::new()
.add_messages(messages)
.add_attribute("action", "complete_transfer_wrapped")
.add_attribute("contract", contract_addr)
.add_attribute("recipient", recipient)
.add_attribute("amount", amount.to_string()))
} 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.addr_humanize(&target_address)?;
let contract_addr = deps.api.addr_humanize(&token_address)?;
// note -- here the amount is the amount the recipient will receive;
// amount + fee is the total sent
receive_native(deps.storage, &token_address, Uint128::new(amount + fee))?;
// undo normalization to 8 decimals
let token_info: TokenInfoResponse =
deps.querier.query(&QueryRequest::Wasm(WasmQuery::Smart {
contract_addr: contract_addr.to_string(),
msg: to_binary(&TokenQuery::TokenInfo {})?,
}))?;
let decimals = token_info.decimals;
let multiplier = 10u128.pow((max(decimals, 8u8) - 8u8) as u32);
amount = amount.checked_mul(multiplier).unwrap();
fee = fee.checked_mul(multiplier).unwrap();
let mut messages = vec![CosmosMsg::Wasm(WasmMsg::Execute {
contract_addr: contract_addr.to_string(),
msg: to_binary(&TokenMsg::Transfer {
recipient: recipient.to_string(),
amount: Uint128::from(amount),
})?,
funds: vec![],
})];
if fee != 0 {
messages.push(CosmosMsg::Wasm(WasmMsg::Execute {
contract_addr: contract_addr.to_string(),
msg: to_binary(&TokenMsg::Transfer {
recipient: info.sender.to_string(),
amount: Uint128::from(fee),
})?,
funds: vec![],
}))
}
Ok(Response::new()
.add_messages(messages)
.add_attribute("action", "complete_transfer_native")
.add_attribute("recipient", recipient)
.add_attribute("contract", contract_addr)
.add_attribute("amount", amount.to_string()))
}
}
fn handle_initiate_transfer(
mut deps: DepsMut,
env: Env,
info: MessageInfo,
asset: HumanAddr,
mut amount: Uint128,
recipient_chain: u16,
recipient: Vec<u8>,
mut fee: Uint128,
nonce: u32,
) -> StdResult<Response> {
if recipient_chain == CHAIN_ID {
return ContractError::SameSourceAndTarget.std_err();
}
if amount.is_zero() {
return ContractError::AmountTooLow.std_err();
}
if fee > amount {
return Err(StdError::generic_err("fee greater than sent amount"));
}
let asset_chain: u16;
let asset_address: Vec<u8>;
let cfg: ConfigInfo = config_read(deps.storage).load()?;
let asset_canonical: CanonicalAddr = deps.api.addr_canonicalize(&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: info.sender.to_string(),
amount,
})?,
funds: vec![],
}));
let request = QueryRequest::<Empty>::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(_) => {
// 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::new(
amount
.u128()
.checked_sub(amount.u128().checked_rem(multiplier).unwrap())
.unwrap(),
);
fee = Uint128::new(
fee.u128()
.checked_sub(fee.u128().checked_rem(multiplier).unwrap())
.unwrap(),
);
// This is a regular asset, transfer its balance
messages.push(CosmosMsg::Wasm(WasmMsg::Execute {
contract_addr: asset,
msg: to_binary(&TokenMsg::TransferFrom {
owner: info.sender.to_string(),
recipient: env.contract.address.to_string(),
amount,
})?,
funds: vec![],
}));
asset_address = extend_address_to_32(&asset_canonical);
asset_chain = CHAIN_ID;
// convert to normalized amounts before recording & posting vaa
amount = Uint128::new(amount.u128().checked_div(multiplier).unwrap());
fee = Uint128::new(fee.u128().checked_div(multiplier).unwrap());
send_native(deps.storage, &asset_canonical, amount)?;
}
};
let transfer_info = TransferInfo {
token_chain: asset_chain,
token_address: asset_address.clone(),
amount: (0, amount.u128()),
recipient_chain,
recipient: recipient.clone(),
fee: (0, fee.u128()),
};
let token_bridge_message = TokenBridgeMessage {
action: Action::TRANSFER,
payload: transfer_info.serialize(),
};
messages.push(CosmosMsg::Wasm(WasmMsg::Execute {
contract_addr: cfg.wormhole_contract,
msg: to_binary(&WormholeExecuteMsg::PostMessage {
message: Binary::from(token_bridge_message.serialize()),
nonce,
})?,
// forward coins sent to this message
funds: coins_after_tax(deps.branch(), info.funds.clone())?,
}));
Ok(Response::new()
.add_messages(messages)
.add_attribute("transfer.token_chain", asset_chain.to_string())
.add_attribute("transfer.token", hex::encode(asset_address))
.add_attribute(
"transfer.sender",
hex::encode(extend_address_to_32(
&deps.api.addr_canonicalize(&info.sender.as_str())?,
)),
)
.add_attribute("transfer.recipient_chain", recipient_chain.to_string())
.add_attribute("transfer.recipient", hex::encode(recipient))
.add_attribute("transfer.amount", amount.to_string())
.add_attribute("transfer.nonce", nonce.to_string())
.add_attribute("transfer.block_time", env.block.time.seconds().to_string()))
}
pub fn query(deps: Deps, 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(
deps: Deps,
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

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

View File

@ -1,71 +0,0 @@
use cosmwasm_std::{
Binary,
Uint128,
};
use terraswap::asset::{Asset, AssetInfo};
use schemars::JsonSchema;
use serde::{
Deserialize,
Serialize,
};
type HumanAddr = String;
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
pub struct InstantiateMsg {
// governance contract details
pub gov_chain: u16,
pub gov_address: Binary,
pub wormhole_contract: HumanAddr,
pub wrapped_asset_code_id: u64,
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum ExecuteMsg {
RegisterAssetHook {
asset_id: Binary,
},
DepositTokens {},
WithdrawTokens {
asset: AssetInfo,
},
InitiateTransfer {
asset: Asset,
recipient_chain: u16,
recipient: Binary,
fee: Uint128,
nonce: u32,
},
SubmitVaa {
data: Binary,
},
CreateAssetMeta {
asset_info: AssetInfo,
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

@ -1,249 +0,0 @@
use schemars::JsonSchema;
use serde::{
Deserialize,
Serialize,
};
use cosmwasm_std::{
CanonicalAddr,
StdError,
StdResult,
Storage,
Uint128,
};
use cosmwasm_storage::{
bucket,
bucket_read,
singleton,
singleton_read,
Bucket,
ReadonlyBucket,
ReadonlySingleton,
Singleton,
};
use wormhole::byte_utils::ByteUtils;
type HumanAddr = String;
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)]
pub struct ConfigInfo {
// governance contract details
pub gov_chain: u16,
pub gov_address: Vec<u8>,
pub wormhole_contract: HumanAddr,
pub wrapped_asset_code_id: u64,
}
pub fn config(storage: &mut dyn Storage) -> Singleton<ConfigInfo> {
singleton(storage, CONFIG_KEY)
}
pub fn config_read(storage: &dyn Storage) -> ReadonlySingleton<ConfigInfo> {
singleton_read(storage, CONFIG_KEY)
}
pub fn bridge_contracts(storage: &mut dyn Storage) -> Bucket<Vec<u8>> {
bucket(storage, BRIDGE_CONTRACTS)
}
pub fn bridge_contracts_read(storage: &dyn Storage) -> ReadonlyBucket<Vec<u8>> {
bucket_read(storage, BRIDGE_CONTRACTS)
}
pub fn wrapped_asset(storage: &mut dyn Storage) -> Bucket<HumanAddr> {
bucket(storage, WRAPPED_ASSET_KEY)
}
pub fn wrapped_asset_read(storage: &dyn Storage) -> ReadonlyBucket<HumanAddr> {
bucket_read(storage, WRAPPED_ASSET_KEY)
}
pub fn wrapped_asset_address(storage: &mut dyn Storage) -> Bucket<Vec<u8>> {
bucket(storage, WRAPPED_ASSET_ADDRESS_KEY)
}
pub fn wrapped_asset_address_read(storage: &dyn Storage) -> ReadonlyBucket<Vec<u8>> {
bucket_read(storage, WRAPPED_ASSET_ADDRESS_KEY)
}
pub fn send_native(
storage: &mut dyn Storage,
asset_address: &CanonicalAddr,
amount: Uint128,
) -> StdResult<()> {
let mut counter_bucket = bucket(storage, NATIVE_COUNTER);
let new_total = amount
+ counter_bucket
.load(asset_address.as_slice())
.unwrap_or(Uint128::zero());
if new_total > Uint128::new(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 dyn Storage,
asset_address: &CanonicalAddr,
amount: Uint128,
) -> StdResult<()> {
let mut counter_bucket = bucket(storage, NATIVE_COUNTER);
let total: Uint128 = counter_bucket.load(asset_address.as_slice())?;
let result = total.checked_sub(amount)?;
counter_bucket.save(asset_address.as_slice(), &result)
}
pub struct Action;
impl Action {
pub const TRANSFER: u8 = 1;
pub const ATTEST_META: u8 = 2;
}
// 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 u256 amount
// 32 [u8; 32] token_address
// 64 u16 token_chain
// 66 [u8; 32] recipient
// 98 u16 recipient_chain
// 100 u256 fee
pub struct TransferInfo {
pub amount: (u128, u128),
pub token_address: Vec<u8>,
pub token_chain: u16,
pub recipient: Vec<u8>,
pub recipient_chain: u16,
pub fee: (u128, u128),
}
impl TransferInfo {
pub fn deserialize(data: &Vec<u8>) -> StdResult<Self> {
let data = data.as_slice();
let amount = data.get_u256(0);
let token_address = data.get_bytes32(32).to_vec();
let token_chain = data.get_u16(64);
let recipient = data.get_bytes32(66).to_vec();
let recipient_chain = data.get_u16(98);
let fee = data.get_u256(100);
Ok(TransferInfo {
amount,
token_address,
token_chain,
recipient,
recipient_chain,
fee,
})
}
pub fn serialize(&self) -> Vec<u8> {
[
self.amount.0.to_be_bytes().to_vec(),
self.amount.1.to_be_bytes().to_vec(),
self.token_address.clone(),
self.token_chain.to_be_bytes().to_vec(),
self.recipient.to_vec(),
self.recipient_chain.to_be_bytes().to_vec(),
self.fee.0.to_be_bytes().to_vec(),
self.fee.1.to_be_bytes().to_vec(),
]
.concat()
}
}
// 0 [32]uint8 TokenAddress
// 32 uint16 TokenChain
// 34 uint8 Decimals
// 35 [32]uint8 Symbol
// 67 [32]uint8 Name
pub struct AssetMeta {
pub token_address: Vec<u8>,
pub token_chain: u16,
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_address = data.get_bytes32(0).to_vec();
let token_chain = data.get_u16(32);
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_address.clone(),
self.token_chain.to_be_bytes().to_vec(),
self.decimals.to_be_bytes().to_vec(),
self.symbol.clone(),
self.name.clone(),
]
.concat()
}
}
pub struct RegisterChain {
pub chain_id: u16,
pub chain_address: Vec<u8>,
}
impl RegisterChain {
pub fn deserialize(data: &Vec<u8>) -> StdResult<Self> {
let data = data.as_slice();
let chain_id = data.get_u16(0);
let chain_address = data[2..].to_vec();
Ok(RegisterChain {
chain_id,
chain_address,
})
}
}

View File

@ -1,114 +0,0 @@
static WASM: &[u8] = include_bytes!("../../../target/wasm32-unknown-unknown/release/wormhole.wasm");
use cosmwasm_std::{
from_slice,
Coin,
Env,
HumanAddr,
InitResponse,
};
use cosmwasm_storage::to_length_prefixed;
use cosmwasm_vm::{
testing::{
init,
mock_env,
mock_instance,
MockApi,
MockQuerier,
MockStorage,
},
Api,
Instance,
Storage,
};
use wormhole::{
msg::InitMsg,
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,5 +0,0 @@
[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

@ -1,33 +0,0 @@
[package]
name = "wormhole"
version = "0.1.0"
authors = ["Yuriy Savchenko <yuriy.savchenko@gmail.com>"]
edition = "2018"
description = "Wormhole contract"
[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.16.0" }
cosmwasm-storage = { version = "0.16.0" }
schemars = "0.8.1"
serde = { version = "1.0.103", default-features = false, features = ["derive"] }
cw20 = "0.8.0"
cw20-base = { version = "0.8.0", features = ["library"] }
cw20-wrapped = { path = "../cw20-wrapped", features = ["library"] }
thiserror = { version = "1.0.20" }
k256 = { version = "0.9.4", 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"
[dev-dependencies]
cosmwasm-vm = { version = "0.10.0", default-features = false, features = ["default-cranelift"] }
serde_json = "1.0"

View File

@ -1,76 +0,0 @@
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 result = vec![0; 32 - bytes.len()];
Ok([bytes.to_vec(), result].concat())
}
pub fn get_string_from_32(v: &Vec<u8>) -> StdResult<String> {
let s = String::from_utf8(v.clone())
.or_else(|_| Err(StdError::generic_err("could not parse string")))?;
Ok(s.chars().filter(|c| c != &'\0').collect())
}

View File

@ -1,387 +0,0 @@
use cosmwasm_std::{
entry_point,
has_coins,
to_binary,
BankMsg,
Binary,
Coin,
CosmosMsg,
Deps,
DepsMut,
Env,
MessageInfo,
Response,
StdError,
StdResult,
Storage,
};
use crate::{
byte_utils::{
extend_address_to_32,
ByteUtils,
},
error::ContractError,
msg::{
ExecuteMsg,
GetAddressHexResponse,
GetStateResponse,
GuardianSetInfoResponse,
InstantiateMsg,
QueryMsg,
},
state::{
config,
config_read,
guardian_set_get,
guardian_set_set,
sequence_read,
sequence_set,
vaa_archive_add,
vaa_archive_check,
ConfigInfo,
GovernancePacket,
GuardianAddress,
GuardianSetInfo,
GuardianSetUpgrade,
ParsedVAA,
SetFee,
TransferFee,
},
};
use k256::{
ecdsa::{
recoverable::{
Id as RecoverableId,
Signature as RecoverableSignature,
},
Signature,
VerifyingKey,
},
EncodedPoint,
};
use sha3::{
Digest,
Keccak256,
};
use generic_array::GenericArray;
use std::convert::TryFrom;
type HumanAddr = String;
// Chain ID of Terra
const CHAIN_ID: u16 = 3;
// Lock assets fee amount and denomination
const FEE_AMOUNT: u128 = 10000;
pub const FEE_DENOMINATION: &str = "uluna";
#[cfg_attr(not(feature = "library"), entry_point)]
pub fn instantiate(deps: DepsMut, _env: Env, msg: InstantiateMsg) -> StdResult<Response> {
// Save general wormhole info
let state = ConfigInfo {
gov_chain: msg.gov_chain,
gov_address: msg.gov_address.as_slice().to_vec(),
guardian_set_index: 0,
guardian_set_expirity: msg.guardian_set_expirity,
fee: Coin::new(FEE_AMOUNT, FEE_DENOMINATION), // 0.01 Luna (or 10000 uluna) fee by default
};
config(deps.storage).save(&state)?;
// Add initial guardian set to storage
guardian_set_set(
deps.storage,
state.guardian_set_index,
&msg.initial_guardian_set,
)?;
Ok(Response::default())
}
#[cfg_attr(not(feature = "library"), entry_point)]
pub fn execute(deps: DepsMut, env: Env, info: MessageInfo, msg: ExecuteMsg) -> StdResult<Response> {
match msg {
ExecuteMsg::PostMessage { message, nonce } => {
handle_post_message(deps, env, info, &message.as_slice(), nonce)
}
ExecuteMsg::SubmitVAA { vaa } => handle_submit_vaa(deps, env, info, vaa.as_slice()),
}
}
/// Process VAA message signed by quardians
fn handle_submit_vaa(
deps: DepsMut,
env: Env,
_info: MessageInfo,
data: &[u8],
) -> StdResult<Response> {
let state = config_read(deps.storage).load()?;
let vaa = parse_and_verify_vaa(deps.storage, data, env.block.time.seconds())?;
vaa_archive_add(deps.storage, vaa.hash.as_slice())?;
if state.gov_chain == vaa.emitter_chain && state.gov_address == vaa.emitter_address {
if state.guardian_set_index != vaa.guardian_set_index {
return Err(StdError::generic_err(
"governance VAAs must be signed by the current guardian set",
));
}
return handle_governance_payload(deps, env, &vaa.payload);
}
ContractError::InvalidVAAAction.std_err()
}
fn handle_governance_payload(deps: DepsMut, env: Env, data: &Vec<u8>) -> StdResult<Response> {
let gov_packet = GovernancePacket::deserialize(&data)?;
let module = String::from_utf8(gov_packet.module).unwrap();
let module: String = module.chars().filter(|c| c != &'\0').collect();
if module != "Core" {
return Err(StdError::generic_err("this is not a valid module"));
}
if gov_packet.chain != 0 && gov_packet.chain != CHAIN_ID {
return Err(StdError::generic_err(
"the governance VAA is for another chain",
));
}
match gov_packet.action {
// 1 is reserved for upgrade / migration
2u8 => vaa_update_guardian_set(deps, env, &gov_packet.payload),
3u8 => handle_set_fee(deps, env, &gov_packet.payload),
4u8 => handle_transfer_fee(deps, env, &gov_packet.payload),
_ => ContractError::InvalidVAAAction.std_err(),
}
}
/// Parses raw VAA data into a struct and verifies whether it contains sufficient signatures of an
/// active guardian set i.e. is valid according to Wormhole consensus rules
fn parse_and_verify_vaa(
storage: &dyn Storage,
data: &[u8],
block_time: u64,
) -> StdResult<ParsedVAA> {
let vaa = ParsedVAA::deserialize(data)?;
if vaa.version != 1 {
return ContractError::InvalidVersion.std_err();
}
// Check if VAA with this hash was already accepted
if vaa_archive_check(storage, vaa.hash.as_slice()) {
return ContractError::VaaAlreadyExecuted.std_err();
}
// Load and check guardian set
let guardian_set = guardian_set_get(storage, vaa.guardian_set_index);
let guardian_set: GuardianSetInfo =
guardian_set.or_else(|_| ContractError::InvalidGuardianSetIndex.std_err())?;
if guardian_set.expiration_time != 0 && guardian_set.expiration_time < block_time {
return ContractError::GuardianSetExpired.std_err();
}
if (vaa.len_signers as usize) < guardian_set.quorum() {
return ContractError::NoQuorum.std_err();
}
// Verify guardian signatures
let mut last_index: i32 = -1;
let mut pos = ParsedVAA::HEADER_LEN;
for _ in 0..vaa.len_signers {
if pos + ParsedVAA::SIGNATURE_LEN > data.len() {
return ContractError::InvalidVAA.std_err();
}
let index = data.get_u8(pos) as i32;
if index <= last_index {
return ContractError::WrongGuardianIndexOrder.std_err();
}
last_index = index;
let signature = Signature::try_from(
&data[pos + ParsedVAA::SIG_DATA_POS
..pos + ParsedVAA::SIG_DATA_POS + ParsedVAA::SIG_DATA_LEN],
)
.or_else(|_| ContractError::CannotDecodeSignature.std_err())?;
let id = RecoverableId::new(data.get_u8(pos + ParsedVAA::SIG_RECOVERY_POS))
.or_else(|_| ContractError::CannotDecodeSignature.std_err())?;
let recoverable_signature = RecoverableSignature::new(&signature, id)
.or_else(|_| ContractError::CannotDecodeSignature.std_err())?;
let verify_key = recoverable_signature
.recover_verify_key_from_digest_bytes(GenericArray::from_slice(vaa.hash.as_slice()))
.or_else(|_| ContractError::CannotRecoverKey.std_err())?;
let index = index as usize;
if index >= guardian_set.addresses.len() {
return ContractError::TooManySignatures.std_err();
}
if !keys_equal(&verify_key, &guardian_set.addresses[index]) {
return ContractError::GuardianSignatureError.std_err();
}
pos += ParsedVAA::SIGNATURE_LEN;
}
Ok(vaa)
}
fn vaa_update_guardian_set(deps: DepsMut, env: Env, data: &Vec<u8>) -> StdResult<Response> {
/* Payload format
0 uint32 new_index
4 uint8 len(keys)
5 [][20]uint8 guardian addresses
*/
let mut state = config_read(deps.storage).load()?;
let GuardianSetUpgrade {
new_guardian_set_index,
new_guardian_set,
} = GuardianSetUpgrade::deserialize(&data)?;
if new_guardian_set_index != state.guardian_set_index + 1 {
return ContractError::GuardianSetIndexIncreaseError.std_err();
}
let old_guardian_set_index = state.guardian_set_index;
state.guardian_set_index = new_guardian_set_index;
guardian_set_set(deps.storage, state.guardian_set_index, &new_guardian_set)?;
config(deps.storage).save(&state)?;
let mut old_guardian_set = guardian_set_get(deps.storage, old_guardian_set_index)?;
old_guardian_set.expiration_time = env.block.time.seconds() + state.guardian_set_expirity;
guardian_set_set(deps.storage, old_guardian_set_index, &old_guardian_set)?;
Ok(Response::new()
.add_attribute("action", "guardian_set_change")
.add_attribute("old", old_guardian_set_index.to_string())
.add_attribute("new", state.guardian_set_index.to_string()))
}
pub fn handle_set_fee(deps: DepsMut, _env: Env, data: &Vec<u8>) -> StdResult<Response> {
let set_fee_msg = SetFee::deserialize(&data)?;
// Save new fees
let mut state = config_read(deps.storage).load()?;
state.fee = set_fee_msg.fee;
config(deps.storage).save(&state)?;
Ok(Response::new()
.add_attribute("action", "fee_change")
.add_attribute("new_fee.amount", state.fee.amount.to_string())
.add_attribute("new_fee.denom", state.fee.denom.to_string()))
}
pub fn handle_transfer_fee(deps: DepsMut, _env: Env, data: &Vec<u8>) -> StdResult<Response> {
let transfer_msg = TransferFee::deserialize(&data)?;
Ok(Response::new().add_message(CosmosMsg::Bank(BankMsg::Send {
to_address: deps.api.addr_humanize(&transfer_msg.recipient)?.to_string(),
amount: vec![transfer_msg.amount],
})))
}
fn handle_post_message(
deps: DepsMut,
env: Env,
info: MessageInfo,
message: &[u8],
nonce: u32,
) -> StdResult<Response> {
let state = config_read(deps.storage).load()?;
let fee = state.fee;
// Check fee
if !has_coins(info.funds.as_ref(), &fee) {
return ContractError::FeeTooLow.std_err();
}
let emitter = extend_address_to_32(&deps.api.addr_canonicalize(&info.sender.as_str())?);
let sequence = sequence_read(deps.storage, emitter.as_slice());
sequence_set(deps.storage, emitter.as_slice(), sequence + 1)?;
Ok(Response::new()
.add_attribute("message.message", hex::encode(message))
.add_attribute("message.sender", hex::encode(emitter))
.add_attribute("message.chain_id", CHAIN_ID.to_string())
.add_attribute("message.nonce", nonce.to_string())
.add_attribute("message.sequence", sequence.to_string())
.add_attribute("message.block_time", env.block.time.seconds().to_string()))
}
pub fn query(deps: Deps, msg: QueryMsg) -> StdResult<Binary> {
match msg {
QueryMsg::GuardianSetInfo {} => to_binary(&query_guardian_set_info(deps)?),
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)?),
}
}
pub fn query_guardian_set_info(deps: Deps) -> StdResult<GuardianSetInfoResponse> {
let state = config_read(deps.storage).load()?;
let guardian_set = guardian_set_get(deps.storage, state.guardian_set_index)?;
let res = GuardianSetInfoResponse {
guardian_set_index: state.guardian_set_index,
addresses: guardian_set.addresses,
};
Ok(res)
}
pub fn query_parse_and_verify_vaa(
deps: Deps,
data: &[u8],
block_time: u64,
) -> StdResult<ParsedVAA> {
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(deps: Deps, address: &HumanAddr) -> StdResult<GetAddressHexResponse> {
Ok(GetAddressHexResponse {
hex: hex::encode(extend_address_to_32(&deps.api.addr_canonicalize(&address)?)),
})
}
pub fn query_state(deps: Deps) -> StdResult<GetStateResponse> {
let state = config_read(deps.storage).load()?;
let res = GetStateResponse { fee: state.fee };
Ok(res)
}
fn keys_equal(a: &VerifyingKey, b: &GuardianAddress) -> bool {
let mut hasher = Keccak256::new();
let point: EncodedPoint = EncodedPoint::from(a);
let point = point.decompress();
if bool::from(point.is_none()) {
return false;
}
let point = point.unwrap();
hasher.update(&point.as_bytes()[1..]);
let a = &hasher.finalize()[12..];
let b = &b.bytes;
if a.len() != b.len() {
return false;
}
for (ai, bi) in a.iter().zip(b.as_slice().iter()) {
if ai != bi {
return false;
}
}
true
}

View File

@ -1,113 +0,0 @@
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),
}
}
pub fn std_err<T>(&self) -> Result<T, StdError> {
Err(self.std())
}
}

View File

@ -1,7 +0,0 @@
pub mod byte_utils;
pub mod contract;
pub mod error;
pub mod msg;
pub mod state;
pub use crate::error::ContractError;

View File

@ -1,66 +0,0 @@
use cosmwasm_std::{
Binary,
Coin,
};
use schemars::JsonSchema;
use serde::{
Deserialize,
Serialize,
};
use crate::state::{
GuardianAddress,
GuardianSetInfo,
};
type HumanAddr = String;
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
pub struct InstantiateMsg {
pub gov_chain: u16,
pub gov_address: Binary,
pub initial_guardian_set: GuardianSetInfo,
pub guardian_set_expirity: u64,
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum ExecuteMsg {
SubmitVAA { vaa: Binary },
PostMessage { message: Binary, nonce: u32 },
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum QueryMsg {
GuardianSetInfo {},
VerifyVAA { vaa: Binary, block_time: u64 },
GetState {},
QueryAddressHex { address: HumanAddr },
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub struct GuardianSetInfoResponse {
pub guardian_set_index: u32, // Current guardian set index
pub addresses: Vec<GuardianAddress>, // List of querdian addresses
}
#[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 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

@ -1,444 +0,0 @@
use schemars::{
JsonSchema,
};
use serde::{
Deserialize,
Serialize,
};
use cosmwasm_std::{
Binary,
CanonicalAddr,
Coin,
StdResult,
Storage,
Uint128,
};
use cosmwasm_storage::{
bucket,
bucket_read,
singleton,
singleton_read,
Bucket,
ReadonlyBucket,
ReadonlySingleton,
Singleton,
};
use crate::{
byte_utils::ByteUtils,
error::ContractError,
};
use sha3::{
Digest,
Keccak256,
};
type HumanAddr = String;
pub static CONFIG_KEY: &[u8] = b"config";
pub static GUARDIAN_SET_KEY: &[u8] = b"guardian_set";
pub static SEQUENCE_KEY: &[u8] = b"sequence";
pub static WRAPPED_ASSET_KEY: &[u8] = b"wrapped_asset";
pub static WRAPPED_ASSET_ADDRESS_KEY: &[u8] = b"wrapped_asset_address";
// Guardian set information
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
pub struct ConfigInfo {
// Current active guardian set
pub guardian_set_index: u32,
// Period for which a guardian set stays active after it has been replaced
pub guardian_set_expirity: u64,
// governance contract details
pub gov_chain: u16,
pub gov_address: Vec<u8>,
// Message sending fee
pub fee: Coin,
}
// Validator Action Approval(VAA) data
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
pub struct ParsedVAA {
pub version: u8,
pub guardian_set_index: u32,
pub timestamp: u32,
pub nonce: u32,
pub len_signers: u8,
pub emitter_chain: u16,
pub emitter_address: Vec<u8>,
pub sequence: u64,
pub consistency_level: u8,
pub payload: Vec<u8>,
pub hash: Vec<u8>,
}
impl ParsedVAA {
/* VAA format:
header (length 6):
0 uint8 version (0x01)
1 uint32 guardian set index
5 uint8 len signatures
per signature (length 66):
0 uint8 index of the signer (in guardian keys)
1 [65]uint8 signature
body:
0 uint32 timestamp (unix in seconds)
4 uint32 nonce
8 uint16 emitter_chain
10 [32]uint8 emitter_address
42 uint64 sequence
50 uint8 consistency_level
51 []uint8 payload
*/
pub const HEADER_LEN: usize = 6;
pub const SIGNATURE_LEN: usize = 66;
pub const GUARDIAN_SET_INDEX_POS: usize = 1;
pub const LEN_SIGNER_POS: usize = 5;
pub const VAA_NONCE_POS: usize = 4;
pub const VAA_EMITTER_CHAIN_POS: usize = 8;
pub const VAA_EMITTER_ADDRESS_POS: usize = 10;
pub const VAA_SEQUENCE_POS: usize = 42;
pub const VAA_CONSISTENCY_LEVEL_POS: usize = 50;
pub const VAA_PAYLOAD_POS: usize = 51;
// Signature data offsets in the signature block
pub const SIG_DATA_POS: usize = 1;
// Signature length minus recovery id at the end
pub const SIG_DATA_LEN: usize = 64;
// Recovery byte is last after the main signature
pub const SIG_RECOVERY_POS: usize = Self::SIG_DATA_POS + Self::SIG_DATA_LEN;
pub fn deserialize(data: &[u8]) -> StdResult<Self> {
let version = data.get_u8(0);
// Load 4 bytes starting from index 1
let guardian_set_index: u32 = data.get_u32(Self::GUARDIAN_SET_INDEX_POS);
let len_signers = data.get_u8(Self::LEN_SIGNER_POS) as usize;
let body_offset: usize = Self::HEADER_LEN + Self::SIGNATURE_LEN * len_signers as usize;
// Hash the body
if body_offset >= data.len() {
return ContractError::InvalidVAA.std_err();
}
let body = &data[body_offset..];
let mut hasher = Keccak256::new();
hasher.update(body);
let hash = hasher.finalize().to_vec();
// Rehash the hash
let mut hasher = Keccak256::new();
hasher.update(hash);
let hash = hasher.finalize().to_vec();
// Signatures valid, apply VAA
if body_offset + Self::VAA_PAYLOAD_POS > data.len() {
return ContractError::InvalidVAA.std_err();
}
let timestamp = data.get_u32(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 sequence = data.get_u64(body_offset + Self::VAA_SEQUENCE_POS);
let consistency_level = data.get_u8(body_offset + Self::VAA_CONSISTENCY_LEVEL_POS);
let payload = data[body_offset + Self::VAA_PAYLOAD_POS..].to_vec();
Ok(ParsedVAA {
version,
guardian_set_index,
timestamp,
nonce,
len_signers: len_signers as u8,
emitter_chain,
emitter_address,
sequence,
consistency_level,
payload,
hash,
})
}
}
// Guardian address
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
pub struct GuardianAddress {
pub bytes: Binary, // 20-byte addresses
}
use crate::contract::FEE_DENOMINATION;
#[cfg(test)]
use hex;
#[cfg(test)]
impl GuardianAddress {
pub fn from(string: &str) -> GuardianAddress {
GuardianAddress {
bytes: hex::decode(string).expect("Decoding failed").into(),
}
}
}
// Guardian set information
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
pub struct GuardianSetInfo {
pub addresses: Vec<GuardianAddress>,
// List of guardian addresses
pub expiration_time: u64, // Guardian set expiration time
}
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
}
}
// Wormhole contract generic information
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
pub struct WormholeInfo {
// Period for which a guardian set stays active after it has been replaced
pub guardian_set_expirity: u64,
}
pub fn config(storage: &mut dyn Storage) -> Singleton<ConfigInfo> {
singleton(storage, CONFIG_KEY)
}
pub fn config_read(storage: &dyn Storage) -> ReadonlySingleton<ConfigInfo> {
singleton_read(storage, CONFIG_KEY)
}
pub fn guardian_set_set(
storage: &mut dyn Storage,
index: u32,
data: &GuardianSetInfo,
) -> StdResult<()> {
bucket(storage, GUARDIAN_SET_KEY).save(&index.to_be_bytes(), data)
}
pub fn guardian_set_get(storage: &dyn Storage, index: u32) -> StdResult<GuardianSetInfo> {
bucket_read(storage, GUARDIAN_SET_KEY).load(&index.to_be_bytes())
}
pub fn sequence_set(storage: &mut dyn Storage, emitter: &[u8], sequence: u64) -> StdResult<()> {
bucket(storage, SEQUENCE_KEY).save(emitter, &sequence)
}
pub fn sequence_read(storage: &dyn Storage, emitter: &[u8]) -> u64 {
bucket_read(storage, SEQUENCE_KEY)
.load(&emitter)
.or::<u64>(Ok(0))
.unwrap()
}
pub fn vaa_archive_add(storage: &mut dyn Storage, hash: &[u8]) -> StdResult<()> {
bucket(storage, GUARDIAN_SET_KEY).save(hash, &true)
}
pub fn vaa_archive_check(storage: &dyn Storage, hash: &[u8]) -> bool {
bucket_read(storage, GUARDIAN_SET_KEY)
.load(&hash)
.or::<bool>(Ok(false))
.unwrap()
}
pub fn wrapped_asset(storage: &mut dyn Storage) -> Bucket<HumanAddr> {
bucket(storage, WRAPPED_ASSET_KEY)
}
pub fn wrapped_asset_read(storage: &dyn Storage) -> ReadonlyBucket<HumanAddr> {
bucket_read(storage, WRAPPED_ASSET_KEY)
}
pub fn wrapped_asset_address(storage: &mut dyn Storage) -> Bucket<Vec<u8>> {
bucket(storage, WRAPPED_ASSET_ADDRESS_KEY)
}
pub fn wrapped_asset_address_read(storage: &dyn Storage) -> ReadonlyBucket<Vec<u8>> {
bucket_read(storage, WRAPPED_ASSET_ADDRESS_KEY)
}
pub struct GovernancePacket {
pub module: Vec<u8>,
pub action: u8,
pub chain: u16,
pub payload: Vec<u8>,
}
impl GovernancePacket {
pub fn deserialize(data: &Vec<u8>) -> StdResult<Self> {
let data = data.as_slice();
let module = data.get_bytes32(0).to_vec();
let action = data.get_u8(32);
let chain = data.get_u16(33);
let payload = data[35..].to_vec();
Ok(GovernancePacket {
module,
action,
chain,
payload,
})
}
}
// action 2
pub struct GuardianSetUpgrade {
pub new_guardian_set_index: u32,
pub new_guardian_set: GuardianSetInfo,
}
impl GuardianSetUpgrade {
pub fn deserialize(data: &Vec<u8>) -> StdResult<Self> {
const ADDRESS_LEN: usize = 20;
let data = data.as_slice();
let new_guardian_set_index = data.get_u32(0);
let n_guardians = data.get_u8(4);
let mut addresses = vec![];
for i in 0..n_guardians {
let pos = 5 + (i as usize) * ADDRESS_LEN;
if pos + ADDRESS_LEN > data.len() {
return ContractError::InvalidVAA.std_err();
}
addresses.push(GuardianAddress {
bytes: data[pos..pos + ADDRESS_LEN].to_vec().into(),
});
}
let new_guardian_set = GuardianSetInfo {
addresses,
expiration_time: 0,
};
return Ok(GuardianSetUpgrade {
new_guardian_set_index,
new_guardian_set,
});
}
}
// action 3
pub struct SetFee {
pub fee: Coin,
}
impl SetFee {
pub fn deserialize(data: &Vec<u8>) -> StdResult<Self> {
let data = data.as_slice();
let (_, amount) = data.get_u256(0);
let fee = Coin {
denom: String::from(FEE_DENOMINATION),
amount: Uint128::new(amount),
};
Ok(SetFee { fee })
}
}
// action 4
pub struct TransferFee {
pub amount: Coin,
pub recipient: CanonicalAddr,
}
impl TransferFee {
pub fn deserialize(data: &Vec<u8>) -> StdResult<Self> {
let data = data.as_slice();
let recipient = data.get_address(0);
let (_, amount) = data.get_u256(32);
let amount = Coin {
denom: String::from(FEE_DENOMINATION),
amount: Uint128::new(amount),
};
Ok(TransferFee { amount, recipient })
}
}
#[cfg(test)]
mod tests {
use super::*;
fn build_guardian_set(length: usize) -> GuardianSetInfo {
let mut addresses: Vec<GuardianAddress> = Vec::with_capacity(length);
for _ in 0..length {
addresses.push(GuardianAddress {
bytes: vec![].into(),
});
}
GuardianSetInfo {
addresses,
expiration_time: 0,
}
}
#[test]
fn quardian_set_quorum() {
assert_eq!(build_guardian_set(1).quorum(), 1);
assert_eq!(build_guardian_set(2).quorum(), 2);
assert_eq!(build_guardian_set(3).quorum(), 3);
assert_eq!(build_guardian_set(4).quorum(), 3);
assert_eq!(build_guardian_set(5).quorum(), 4);
assert_eq!(build_guardian_set(6).quorum(), 5);
assert_eq!(build_guardian_set(7).quorum(), 5);
assert_eq!(build_guardian_set(8).quorum(), 6);
assert_eq!(build_guardian_set(9).quorum(), 7);
assert_eq!(build_guardian_set(10).quorum(), 7);
assert_eq!(build_guardian_set(11).quorum(), 8);
assert_eq!(build_guardian_set(12).quorum(), 9);
assert_eq!(build_guardian_set(20).quorum(), 14);
assert_eq!(build_guardian_set(25).quorum(), 17);
assert_eq!(build_guardian_set(100).quorum(), 67);
}
#[test]
fn test_deserialize() {
let x = hex::decode("080000000901007bfa71192f886ab6819fa4862e34b4d178962958d9b2e3d9437338c9e5fde1443b809d2886eaa69e0f0158ea517675d96243c9209c3fe1d94d5b19866654c6980000000b150000000500020001020304000000000000000000000000000000000000000000000000000000000000000000000a0261626364").unwrap();
let v = ParsedVAA::deserialize(x.as_slice()).unwrap();
assert_eq!(
v,
ParsedVAA {
version: 8,
guardian_set_index: 9,
timestamp: 2837,
nonce: 5,
len_signers: 1,
emitter_chain: 2,
emitter_address: vec![
0, 1, 2, 3, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0
],
sequence: 10,
consistency_level: 2,
payload: vec![97, 98, 99, 100],
hash: vec![
195, 10, 19, 96, 8, 61, 218, 69, 160, 238, 165, 142, 105, 119, 139, 121, 212,
73, 238, 179, 13, 80, 245, 224, 75, 110, 163, 8, 185, 132, 55, 34
]
}
);
}
}

View File

@ -14,13 +14,15 @@ backtraces = ["cosmwasm-std/backtraces"]
library = []
[dependencies]
cosmwasm-std = { version = "0.10.0" }
cosmwasm-storage = { version = "0.10.0" }
schemars = "0.7"
cosmwasm-std = { version = "0.16.0" }
cosmwasm-storage = { version = "0.16.0" }
schemars = "0.8.1"
serde = { version = "1.0.103", default-features = false, features = ["derive"] }
cw20 = "0.2.0"
cw20-base = { version = "0.2.0", features = ["library"] }
cw2 = { version = "0.8.0" }
cw20 = { version = "0.8.0" }
cw20-legacy = { version = "0.2.0", features = ["library"]}
cw-storage-plus = { version = "0.8.0" }
thiserror = { version = "1.0.20" }
[dev-dependencies]
cosmwasm-vm = { version = "0.10.0", default-features = false, features = ["default-cranelift"] }
cosmwasm-vm = { version = "0.16.0", default-features = false }

View File

@ -1,48 +1,47 @@
use cosmwasm_std::{
entry_point,
to_binary,
Api,
Binary,
CosmosMsg,
Deps,
DepsMut,
Env,
Extern,
HandleResponse,
HumanAddr,
InitResponse,
Querier,
MessageInfo,
Response,
StdError,
StdResult,
Storage,
Uint128,
WasmMsg,
};
use cw20_base::{
use cw2::set_contract_version;
use cw20_legacy::{
allowances::{
handle_burn_from,
handle_decrease_allowance,
handle_increase_allowance,
handle_send_from,
handle_transfer_from,
execute_burn_from,
execute_decrease_allowance,
execute_increase_allowance,
execute_send_from,
execute_transfer_from,
query_allowance,
},
contract::{
handle_mint,
handle_send,
handle_transfer,
execute_mint,
execute_send,
execute_transfer,
query_balance,
},
state::{
token_info,
token_info_read,
MinterData,
TokenInfo,
TOKEN_INFO,
},
ContractError,
};
use crate::{
msg::{
HandleMsg,
InitMsg,
ExecuteMsg,
InstantiateMsg,
QueryMsg,
WrappedAssetInfoResponse,
},
@ -55,116 +54,137 @@ use crate::{
use cw20::TokenInfoResponse;
use std::string::String;
pub fn init<S: Storage, A: Api, Q: Querier>(
deps: &mut Extern<S, A, Q>,
type HumanAddr = String;
// version info for migration info
const CONTRACT_NAME: &str = "crates.io:cw20-base";
const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION");
#[cfg_attr(not(feature = "library"), entry_point)]
pub fn instantiate(
deps: DepsMut,
env: Env,
msg: InitMsg,
) -> StdResult<InitResponse> {
info: MessageInfo,
msg: InstantiateMsg,
) -> StdResult<Response> {
set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?;
// store token info using cw20-base format
let data = TokenInfo {
name: msg.name,
symbol: msg.symbol,
decimals: msg.decimals,
total_supply: Uint128(0),
total_supply: Uint128::new(0),
// set creator as minter
mint: Some(MinterData {
minter: deps.api.canonical_address(&env.message.sender)?,
minter: deps.api.addr_canonicalize(&info.sender.as_str())?,
cap: None,
}),
};
token_info(&mut deps.storage).save(&data)?;
TOKEN_INFO.save(deps.storage, &data)?;
// save wrapped asset info
let data = WrappedAssetInfo {
asset_chain: msg.asset_chain,
asset_address: msg.asset_address,
bridge: deps.api.canonical_address(&env.message.sender)?,
bridge: deps.api.addr_canonicalize(&info.sender.as_str())?,
};
wrapped_asset_info(&mut deps.storage).save(&data)?;
wrapped_asset_info(deps.storage).save(&data)?;
if let Some(mint_info) = msg.mint {
handle_mint(deps, env, mint_info.recipient, mint_info.amount)?;
execute_mint(deps, env, info, mint_info.recipient, mint_info.amount)
.map_err(|e| StdError::generic_err(format!("{}", e)))?;
}
if let Some(hook) = msg.init_hook {
Ok(InitResponse {
messages: vec![CosmosMsg::Wasm(WasmMsg::Execute {
Ok(
Response::new().add_message(CosmosMsg::Wasm(WasmMsg::Execute {
contract_addr: hook.contract_addr,
msg: hook.msg,
send: vec![],
})],
log: vec![],
})
funds: vec![],
})),
)
} else {
Ok(InitResponse::default())
Ok(Response::default())
}
}
pub fn handle<S: Storage, A: Api, Q: Querier>(
deps: &mut Extern<S, A, Q>,
#[cfg_attr(not(feature = "library"), entry_point)]
pub fn execute(
deps: DepsMut,
env: Env,
msg: HandleMsg,
) -> StdResult<HandleResponse> {
info: MessageInfo,
msg: ExecuteMsg,
) -> Result<Response, ContractError> {
match msg {
// these all come from cw20-base to implement the cw20 standard
HandleMsg::Transfer { recipient, amount } => {
Ok(handle_transfer(deps, env, recipient, amount)?)
ExecuteMsg::Transfer { recipient, amount } => {
Ok(execute_transfer(deps, env, info, recipient, amount)?)
}
HandleMsg::Burn { account, amount } => Ok(handle_burn_from(deps, env, account, amount)?),
HandleMsg::Send {
ExecuteMsg::Burn { account, amount } => {
Ok(execute_burn_from(deps, env, info, account, amount)?)
}
ExecuteMsg::Send {
contract,
amount,
msg,
} => Ok(handle_send(deps, env, contract, amount, msg)?),
HandleMsg::Mint { recipient, amount } => handle_mint_wrapped(deps, env, recipient, amount),
HandleMsg::IncreaseAllowance {
} => Ok(execute_send(deps, env, info, contract, amount, msg)?),
ExecuteMsg::Mint { recipient, amount } => {
execute_mint_wrapped(deps, env, info, recipient, amount)
}
ExecuteMsg::IncreaseAllowance {
spender,
amount,
expires,
} => Ok(handle_increase_allowance(
deps, env, spender, amount, expires,
} => Ok(execute_increase_allowance(
deps, env, info, spender, amount, expires,
)?),
HandleMsg::DecreaseAllowance {
ExecuteMsg::DecreaseAllowance {
spender,
amount,
expires,
} => Ok(handle_decrease_allowance(
deps, env, spender, amount, expires,
} => Ok(execute_decrease_allowance(
deps, env, info, spender, amount, expires,
)?),
HandleMsg::TransferFrom {
ExecuteMsg::TransferFrom {
owner,
recipient,
amount,
} => Ok(handle_transfer_from(deps, env, owner, recipient, amount)?),
HandleMsg::BurnFrom { owner, amount } => Ok(handle_burn_from(deps, env, owner, amount)?),
HandleMsg::SendFrom {
} => Ok(execute_transfer_from(
deps, env, info, owner, recipient, amount,
)?),
ExecuteMsg::BurnFrom { owner, amount } => {
Ok(execute_burn_from(deps, env, info, owner, amount)?)
}
ExecuteMsg::SendFrom {
owner,
contract,
amount,
msg,
} => Ok(handle_send_from(deps, env, owner, contract, amount, msg)?),
} => Ok(execute_send_from(
deps, env, info, owner, contract, amount, msg,
)?),
}
}
fn handle_mint_wrapped<S: Storage, A: Api, Q: Querier>(
deps: &mut Extern<S, A, Q>,
fn execute_mint_wrapped(
deps: DepsMut,
env: Env,
info: MessageInfo,
recipient: HumanAddr,
amount: Uint128,
) -> StdResult<HandleResponse> {
) -> Result<Response, ContractError> {
// Only bridge can mint
let wrapped_info = wrapped_asset_info_read(&deps.storage).load()?;
if wrapped_info.bridge != deps.api.canonical_address(&env.message.sender)? {
return Err(StdError::unauthorized());
let wrapped_info = wrapped_asset_info_read(deps.storage).load()?;
if wrapped_info.bridge != deps.api.addr_canonicalize(&info.sender.as_str())? {
return Err(ContractError::Unauthorized {});
}
Ok(handle_mint(deps, env, recipient, amount)?)
Ok(execute_mint(deps, env, info, recipient, amount)?)
}
pub fn query<S: Storage, A: Api, Q: Querier>(
deps: &Extern<S, A, Q>,
msg: QueryMsg,
) -> StdResult<Binary> {
#[cfg_attr(not(feature = "library"), entry_point)]
pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult<Binary> {
match msg {
QueryMsg::WrappedAssetInfo {} => to_binary(&query_wrapped_asset_info(deps)?),
// inherited from cw20-base
@ -176,66 +196,58 @@ pub fn query<S: Storage, A: Api, Q: Querier>(
}
}
pub fn query_token_info<S: Storage, A: Api, Q: Querier>(
deps: &Extern<S, A, Q>,
) -> StdResult<TokenInfoResponse> {
let info = token_info_read(&deps.storage).load()?;
let res = TokenInfoResponse {
pub fn query_token_info(deps: Deps) -> StdResult<TokenInfoResponse> {
let info = TOKEN_INFO.load(deps.storage)?;
Ok(TokenInfoResponse {
name: String::from("Wormhole:") + info.name.as_str(),
symbol: String::from("wh") + info.symbol.as_str(),
decimals: info.decimals,
total_supply: info.total_supply,
};
Ok(res)
})
}
pub fn query_wrapped_asset_info<S: Storage, A: Api, Q: Querier>(
deps: &Extern<S, A, Q>,
) -> StdResult<WrappedAssetInfoResponse> {
let info = wrapped_asset_info_read(&deps.storage).load()?;
let res = WrappedAssetInfoResponse {
pub fn query_wrapped_asset_info(deps: Deps) -> StdResult<WrappedAssetInfoResponse> {
let info = wrapped_asset_info_read(deps.storage).load()?;
Ok(WrappedAssetInfoResponse {
asset_chain: info.asset_chain,
asset_address: info.asset_address,
bridge: deps.api.human_address(&info.bridge)?,
};
Ok(res)
bridge: deps.api.addr_humanize(&info.bridge)?,
})
}
#[cfg(test)]
mod tests {
use super::*;
use cosmwasm_std::{
testing::{
mock_dependencies,
mock_env,
},
HumanAddr,
use cosmwasm_std::testing::{
mock_dependencies,
mock_env,
mock_info,
};
use cw20::TokenInfoResponse;
const CANONICAL_LENGTH: usize = 20;
fn get_balance<S: Storage, A: Api, Q: Querier, T: Into<HumanAddr>>(
deps: &Extern<S, A, Q>,
address: T,
) -> Uint128 {
query_balance(&deps, address.into()).unwrap().balance
fn get_balance(deps: Deps, address: HumanAddr) -> Uint128 {
query_balance(deps, address.into()).unwrap().balance
}
fn do_init<S: Storage, A: Api, Q: Querier>(deps: &mut Extern<S, A, Q>, creator: &HumanAddr) {
let init_msg = InitMsg {
fn do_init(mut deps: DepsMut, creator: &HumanAddr) {
let init_msg = InstantiateMsg {
name: "Integers".to_string(),
symbol: "INT".to_string(),
asset_chain: 1,
asset_address: vec![1; 32].into(),
decimals: 10,
mint: None,
init_hook: None,
};
let env = mock_env(creator, &[]);
let res = init(deps, env, init_msg).unwrap();
let env = mock_env();
let info = mock_info(creator, &[]);
let res = instantiate(deps, env, info, init_msg).unwrap();
assert_eq!(0, res.messages.len());
assert_eq!(
query_token_info(&deps).unwrap(),
query_token_info(deps.as_ref()).unwrap(),
TokenInfoResponse {
name: "Wormhole Wrapped".to_string(),
symbol: "WWT".to_string(),
@ -245,35 +257,36 @@ mod tests {
);
assert_eq!(
query_wrapped_asset_info(&deps).unwrap(),
query_wrapped_asset_info(deps.as_ref()).unwrap(),
WrappedAssetInfoResponse {
asset_chain: 1,
asset_address: vec![1; 32].into(),
bridge: creator.clone(),
bridge: deps.api.addr_validate(creator).unwrap(),
}
);
}
fn do_init_and_mint<S: Storage, A: Api, Q: Querier>(
deps: &mut Extern<S, A, Q>,
fn do_init_and_mint(
mut deps: DepsMut,
creator: &HumanAddr,
mint_to: &HumanAddr,
amount: Uint128,
) {
do_init(deps, creator);
let msg = HandleMsg::Mint {
let msg = ExecuteMsg::Mint {
recipient: mint_to.clone(),
amount,
};
let env = mock_env(&creator, &[]);
let res = handle(deps, env, msg.clone()).unwrap();
let env = mock_env();
let info = mock_info(creator, &[]);
let res = execute(deps.as_mut(), env, info, msg.clone()).unwrap();
assert_eq!(0, res.messages.len());
assert_eq!(get_balance(deps, mint_to), amount);
assert_eq!(get_balance(deps.as_ref(), mint_to.clone(),), amount);
assert_eq!(
query_token_info(&deps).unwrap(),
query_token_info(deps.as_ref()).unwrap(),
TokenInfoResponse {
name: "Wormhole Wrapped".to_string(),
symbol: "WWT".to_string(),
@ -285,29 +298,30 @@ mod tests {
#[test]
fn can_mint_by_minter() {
let mut deps = mock_dependencies(CANONICAL_LENGTH, &[]);
let mut deps = mock_dependencies(&[]);
let minter = HumanAddr::from("minter");
let recipient = HumanAddr::from("recipient");
let amount = Uint128(222_222_222);
do_init_and_mint(&mut deps, &minter, &recipient, amount);
let amount = Uint128::new(222_222_222);
do_init_and_mint(deps.as_mut(), &minter, &recipient, amount);
}
#[test]
fn others_cannot_mint() {
let mut deps = mock_dependencies(CANONICAL_LENGTH, &[]);
let mut deps = mock_dependencies(&[]);
let minter = HumanAddr::from("minter");
let recipient = HumanAddr::from("recipient");
do_init(&mut deps, &minter);
do_init(deps.as_mut(), &minter);
let amount = Uint128(222_222_222);
let msg = HandleMsg::Mint {
let amount = Uint128::new(222_222_222);
let msg = ExecuteMsg::Mint {
recipient: recipient.clone(),
amount,
};
let other_address = HumanAddr::from("other");
let env = mock_env(&other_address, &[]);
let res = handle(&mut deps, env, msg);
let env = mock_env();
let info = mock_info(&other_address, &[]);
let res = execute(deps.as_mut(), env, info, msg);
assert_eq!(
format!("{}", res.unwrap_err()),
format!("{}", crate::error::ContractError::Unauthorized {})
@ -316,44 +330,46 @@ mod tests {
#[test]
fn transfer_balance_success() {
let mut deps = mock_dependencies(CANONICAL_LENGTH, &[]);
let mut deps = mock_dependencies(&[]);
let minter = HumanAddr::from("minter");
let owner = HumanAddr::from("owner");
let amount_initial = Uint128(222_222_222);
do_init_and_mint(&mut deps, &minter, &owner, amount_initial);
let amount_initial = Uint128::new(222_222_222);
do_init_and_mint(deps.as_mut(), &minter, &owner, amount_initial);
// Transfer
let recipient = HumanAddr::from("recipient");
let amount_transfer = Uint128(222_222);
let msg = HandleMsg::Transfer {
let amount_transfer = Uint128::new(222_222);
let msg = ExecuteMsg::Transfer {
recipient: recipient.clone(),
amount: amount_transfer,
};
let env = mock_env(&owner, &[]);
let res = handle(&mut deps, env, msg.clone()).unwrap();
let env = mock_env();
let info = mock_info(&owner, &[]);
let res = execute(deps.as_mut(), env, info, msg.clone()).unwrap();
assert_eq!(0, res.messages.len());
assert_eq!(get_balance(&deps, owner), Uint128(222_000_000));
assert_eq!(get_balance(&deps, recipient), amount_transfer);
assert_eq!(get_balance(deps.as_ref(), owner), Uint128::new(222_000_000));
assert_eq!(get_balance(deps.as_ref(), recipient), amount_transfer);
}
#[test]
fn transfer_balance_not_enough() {
let mut deps = mock_dependencies(CANONICAL_LENGTH, &[]);
let mut deps = mock_dependencies(&[]);
let minter = HumanAddr::from("minter");
let owner = HumanAddr::from("owner");
let amount_initial = Uint128(222_221);
do_init_and_mint(&mut deps, &minter, &owner, amount_initial);
let amount_initial = Uint128::new(222_221);
do_init_and_mint(deps.as_mut(), &minter, &owner, amount_initial);
// Transfer
let recipient = HumanAddr::from("recipient");
let amount_transfer = Uint128(222_222);
let msg = HandleMsg::Transfer {
let amount_transfer = Uint128::new(222_222);
let msg = ExecuteMsg::Transfer {
recipient: recipient.clone(),
amount: amount_transfer,
};
let env = mock_env(&owner, &[]);
let _ = handle(&mut deps, env, msg.clone()).unwrap_err(); // Will panic if no error
let env = mock_env();
let info = mock_info(&owner, &[]);
let _ = execute(deps.as_mut(), env, info, msg.clone()).unwrap_err(); // Will panic if no error
}
}

View File

@ -1,9 +1,7 @@
pub mod contract;
mod error;
pub mod contract;
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

@ -6,14 +6,16 @@ use serde::{
};
use cosmwasm_std::{
Addr,
Binary,
HumanAddr,
Uint128,
};
use cw20::Expiration;
type HumanAddr = String;
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
pub struct InitMsg {
pub struct InstantiateMsg {
pub name: String,
pub symbol: String,
pub asset_chain: u16,
@ -37,7 +39,7 @@ pub struct InitMint {
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum HandleMsg {
pub enum ExecuteMsg {
/// Implements CW20. Transfer is a base message to move tokens to another account without triggering actions
Transfer {
recipient: HumanAddr,
@ -50,7 +52,7 @@ pub enum HandleMsg {
Send {
contract: HumanAddr,
amount: Uint128,
msg: Option<Binary>,
msg: Binary,
},
/// Implements CW20 "mintable" extension. If authorized, creates amount new tokens
/// and adds to the recipient balance.
@ -87,7 +89,7 @@ pub enum HandleMsg {
owner: HumanAddr,
contract: HumanAddr,
amount: Uint128,
msg: Option<Binary>,
msg: Binary,
},
/// Implements CW20 "approval" extension. Destroys tokens forever
BurnFrom { owner: HumanAddr, amount: Uint128 },
@ -116,5 +118,5 @@ pub enum QueryMsg {
pub struct WrappedAssetInfoResponse {
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
pub bridge: Addr, // Bridge address, authorized to mint and burn wrapped tokens
}

View File

@ -7,7 +7,6 @@ use serde::{
use cosmwasm_std::{
Binary,
CanonicalAddr,
ReadonlyStorage,
Storage,
};
use cosmwasm_storage::{
@ -27,12 +26,12 @@ pub struct WrappedAssetInfo {
pub bridge: CanonicalAddr, // Bridge address, authorized to mint and burn wrapped tokens
}
pub fn wrapped_asset_info<S: Storage>(storage: &mut S) -> Singleton<S, WrappedAssetInfo> {
pub fn wrapped_asset_info(storage: &mut dyn Storage) -> Singleton<WrappedAssetInfo> {
singleton(storage, KEY_WRAPPED_ASSET)
}
pub fn wrapped_asset_info_read<S: ReadonlyStorage>(
storage: &S,
) -> ReadonlySingleton<S, WrappedAssetInfo> {
pub fn wrapped_asset_info_read(
storage: &dyn Storage,
) -> ReadonlySingleton<WrappedAssetInfo> {
singleton_read(storage, KEY_WRAPPED_ASSET)
}

View File

@ -14,18 +14,17 @@ backtraces = ["cosmwasm-std/backtraces"]
library = []
[dependencies]
cosmwasm-std = { version = "0.10.0" }
cosmwasm-storage = { version = "0.10.0" }
schemars = "0.7"
cosmwasm-std = { version = "0.16.0" }
cosmwasm-storage = { version = "0.16.0" }
schemars = "0.8.1"
serde = { version = "1.0.103", default-features = false, features = ["derive"] }
cw20 = "0.2.2"
cw20-base = { version = "0.2.2", features = ["library"] }
cw20 = "0.8.0"
cw20-base = { version = "0.8.0", features = ["library"] }
cw20-wrapped = { path = "../cw20-wrapped", features = ["library"] }
terraswap = "1.1.0"
terraswap = "2.4.0"
wormhole = { path = "../wormhole", features = ["library"] }
thiserror = { version = "1.0.20" }
k256 = { version = "0.5.9", default-features = false, features = ["ecdsa"] }
k256 = { version = "0.9.4", default-features = false, features = ["ecdsa"] }
sha3 = { version = "0.9.1", default-features = false }
generic-array = { version = "0.14.4" }
hex = "0.4.2"
@ -33,5 +32,5 @@ 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"
cosmwasm-vm = { version = "0.16.0", default-features = false }
serde_json = "1.0"

File diff suppressed because it is too large Load Diff

View File

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

View File

@ -1,13 +1,18 @@
use cosmwasm_std::{Binary, HumanAddr, Uint128};
use schemars::JsonSchema;
use cosmwasm_std::{
Binary,
Uint128,
};
use terraswap::asset::{Asset, AssetInfo};
use schemars::JsonSchema;
use serde::{
Deserialize,
Serialize,
};
type HumanAddr = String;
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
pub struct InitMsg {
pub struct InstantiateMsg {
// governance contract details
pub gov_chain: u16,
pub gov_address: Binary,
@ -18,7 +23,7 @@ pub struct InitMsg {
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum HandleMsg {
pub enum ExecuteMsg {
RegisterAssetHook {
asset_id: Binary,
},
@ -28,6 +33,7 @@ pub enum HandleMsg {
asset: AssetInfo,
},
InitiateTransfer {
asset: Asset,
recipient_chain: u16,

View File

@ -6,7 +6,6 @@ use serde::{
use cosmwasm_std::{
CanonicalAddr,
HumanAddr,
StdError,
StdResult,
Storage,
@ -25,6 +24,8 @@ use cosmwasm_storage::{
use wormhole::byte_utils::ByteUtils;
type HumanAddr = String;
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";
@ -43,57 +44,57 @@ pub struct ConfigInfo {
pub wrapped_asset_code_id: u64,
}
pub fn config<S: Storage>(storage: &mut S) -> Singleton<S, ConfigInfo> {
pub fn config(storage: &mut dyn Storage) -> Singleton<ConfigInfo> {
singleton(storage, CONFIG_KEY)
}
pub fn config_read<S: Storage>(storage: &S) -> ReadonlySingleton<S, ConfigInfo> {
pub fn config_read(storage: &dyn Storage) -> ReadonlySingleton<ConfigInfo> {
singleton_read(storage, CONFIG_KEY)
}
pub fn bridge_deposit<S: Storage>(storage: &mut S) -> Bucket<S, Uint128> {
bucket(BRIDGE_DEPOSITS, storage)
pub fn bridge_deposit(storage: &mut dyn Storage) -> Bucket<Uint128> {
bucket(storage, BRIDGE_DEPOSITS)
}
pub fn bridge_deposit_read<S: Storage>(storage: &S) -> ReadonlyBucket<S, Uint128> {
bucket_read(BRIDGE_DEPOSITS, storage)
pub fn bridge_deposit_read(storage: &dyn Storage) -> ReadonlyBucket<Uint128> {
bucket_read(storage, BRIDGE_DEPOSITS)
}
pub fn bridge_contracts<S: Storage>(storage: &mut S) -> Bucket<S, Vec<u8>> {
bucket(BRIDGE_CONTRACTS, storage)
pub fn bridge_contracts(storage: &mut dyn Storage) -> Bucket<Vec<u8>> {
bucket(storage, BRIDGE_CONTRACTS)
}
pub fn bridge_contracts_read<S: Storage>(storage: &S) -> ReadonlyBucket<S, Vec<u8>> {
bucket_read(BRIDGE_CONTRACTS, storage)
pub fn bridge_contracts_read(storage: &dyn Storage) -> ReadonlyBucket<Vec<u8>> {
bucket_read(storage, BRIDGE_CONTRACTS)
}
pub fn wrapped_asset<S: Storage>(storage: &mut S) -> Bucket<S, HumanAddr> {
bucket(WRAPPED_ASSET_KEY, storage)
pub fn wrapped_asset(storage: &mut dyn Storage) -> Bucket<HumanAddr> {
bucket(storage, WRAPPED_ASSET_KEY)
}
pub fn wrapped_asset_read<S: Storage>(storage: &S) -> ReadonlyBucket<S, HumanAddr> {
bucket_read(WRAPPED_ASSET_KEY, storage)
pub fn wrapped_asset_read(storage: &dyn Storage) -> ReadonlyBucket<HumanAddr> {
bucket_read(storage, WRAPPED_ASSET_KEY)
}
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(storage: &mut dyn Storage) -> Bucket<Vec<u8>> {
bucket(storage, WRAPPED_ASSET_ADDRESS_KEY)
}
pub fn wrapped_asset_address_read<S: Storage>(storage: &S) -> ReadonlyBucket<S, Vec<u8>> {
bucket_read(WRAPPED_ASSET_ADDRESS_KEY, storage)
pub fn wrapped_asset_address_read(storage: &dyn Storage) -> ReadonlyBucket<Vec<u8>> {
bucket_read(storage, WRAPPED_ASSET_ADDRESS_KEY)
}
pub fn send_native<S: Storage>(
storage: &mut S,
pub fn send_native(
storage: &mut dyn Storage,
asset_address: &CanonicalAddr,
amount: Uint128,
) -> StdResult<()> {
let mut counter_bucket = bucket(NATIVE_COUNTER, storage);
let mut counter_bucket = bucket(storage, NATIVE_COUNTER);
let new_total = amount
+ counter_bucket
.load(asset_address.as_slice())
.unwrap_or(Uint128::zero());
if new_total > Uint128(u64::MAX as u128) {
if new_total > Uint128::new(u64::MAX as u128) {
return Err(StdError::generic_err(
"transfer exceeds max outstanding bridged token amount",
));
@ -101,14 +102,15 @@ pub fn send_native<S: Storage>(
counter_bucket.save(asset_address.as_slice(), &new_total)
}
pub fn receive_native<S: Storage>(
storage: &mut S,
pub fn receive_native(
storage: &mut dyn Storage,
asset_address: &CanonicalAddr,
amount: Uint128,
) -> StdResult<()> {
let mut counter_bucket = bucket(NATIVE_COUNTER, storage);
let mut counter_bucket = bucket(storage, NATIVE_COUNTER);
let total: Uint128 = counter_bucket.load(asset_address.as_slice())?;
counter_bucket.save(asset_address.as_slice(), &(total - amount)?)
let result = total.checked_sub(amount)?;
counter_bucket.save(asset_address.as_slice(), &result)
}
pub struct Action;

View File

@ -14,20 +14,21 @@ backtraces = ["cosmwasm-std/backtraces"]
library = []
[dependencies]
cosmwasm-std = { version = "0.10.0" }
cosmwasm-storage = { version = "0.10.0" }
schemars = "0.7"
cosmwasm-std = { version = "0.16.0" }
cosmwasm-storage = { version = "0.16.0" }
schemars = "0.8.1"
serde = { version = "1.0.103", default-features = false, features = ["derive"] }
cw20 = "0.2.2"
cw20-base = { version = "0.2.2", features = ["library"] }
cw20 = "0.8.0"
cw20-base = { version = "0.8.0", features = ["library"] }
cw20-wrapped = { path = "../cw20-wrapped", features = ["library"] }
thiserror = { version = "1.0.20" }
k256 = { version = "0.5.9", default-features = false, features = ["ecdsa"] }
k256 = { version = "0.9.4", default-features = false, features = ["ecdsa"] }
getrandom = { version = "0.2", features = ["custom"] }
sha3 = { version = "0.9.1", default-features = false }
generic-array = { version = "0.14.4" }
hex = "0.4.2"
lazy_static = "1.4.0"
[dev-dependencies]
cosmwasm-vm = { version = "0.10.0", default-features = false, features = ["default-cranelift"] }
serde_json = "1.0"
cosmwasm-vm = { version = "0.16.0", default-features = false }
serde_json = "1.0"

View File

@ -1,21 +1,20 @@
use cosmwasm_std::{
entry_point,
has_coins,
log,
to_binary,
Api,
BankMsg,
Binary,
Coin,
CosmosMsg,
Deps,
DepsMut,
Env,
Extern,
HandleResponse,
HumanAddr,
InitResponse,
Querier,
MessageInfo,
Response,
StdError,
StdResult,
Storage,
WasmMsg,
};
use crate::{
@ -25,11 +24,12 @@ use crate::{
},
error::ContractError,
msg::{
ExecuteMsg,
GetAddressHexResponse,
GetStateResponse,
GuardianSetInfoResponse,
HandleMsg,
InitMsg,
InstantiateMsg,
MigrateMsg,
QueryMsg,
},
state::{
@ -42,6 +42,7 @@ use crate::{
vaa_archive_add,
vaa_archive_check,
ConfigInfo,
ContractUpgrade,
GovernancePacket,
GuardianAddress,
GuardianSetInfo,
@ -59,7 +60,7 @@ use k256::{
Signature as RecoverableSignature,
},
Signature,
VerifyKey,
VerifyingKey,
},
EncodedPoint,
};
@ -71,6 +72,8 @@ use sha3::{
use generic_array::GenericArray;
use std::convert::TryFrom;
type HumanAddr = String;
// Chain ID of Terra
const CHAIN_ID: u16 = 3;
@ -78,11 +81,18 @@ const CHAIN_ID: u16 = 3;
const FEE_AMOUNT: u128 = 10000;
pub const FEE_DENOMINATION: &str = "uluna";
pub fn init<S: Storage, A: Api, Q: Querier>(
deps: &mut Extern<S, A, Q>,
#[cfg_attr(not(feature = "library"), entry_point)]
pub fn migrate(_deps: DepsMut, _env: Env, _msg: MigrateMsg) -> StdResult<Response> {
Ok(Response::default())
}
#[cfg_attr(not(feature = "library"), entry_point)]
pub fn instantiate(
deps: DepsMut,
_env: Env,
msg: InitMsg,
) -> StdResult<InitResponse> {
_info: MessageInfo,
msg: InstantiateMsg,
) -> StdResult<Response> {
// Save general wormhole info
let state = ConfigInfo {
gov_chain: msg.gov_chain,
@ -91,41 +101,39 @@ pub fn init<S: Storage, A: Api, Q: Querier>(
guardian_set_expirity: msg.guardian_set_expirity,
fee: Coin::new(FEE_AMOUNT, FEE_DENOMINATION), // 0.01 Luna (or 10000 uluna) fee by default
};
config(&mut deps.storage).save(&state)?;
config(deps.storage).save(&state)?;
// Add initial guardian set to storage
guardian_set_set(
&mut deps.storage,
deps.storage,
state.guardian_set_index,
&msg.initial_guardian_set,
)?;
Ok(InitResponse::default())
Ok(Response::default())
}
pub fn handle<S: Storage, A: Api, Q: Querier>(
deps: &mut Extern<S, A, Q>,
env: Env,
msg: HandleMsg,
) -> StdResult<HandleResponse> {
#[cfg_attr(not(feature = "library"), entry_point)]
pub fn execute(deps: DepsMut, env: Env, info: MessageInfo, msg: ExecuteMsg) -> StdResult<Response> {
match msg {
HandleMsg::PostMessage { message, nonce } => {
handle_post_message(deps, env, &message.as_slice(), nonce)
ExecuteMsg::PostMessage { message, nonce } => {
handle_post_message(deps, env, info, &message.as_slice(), nonce)
}
HandleMsg::SubmitVAA { vaa } => handle_submit_vaa(deps, env, vaa.as_slice()),
ExecuteMsg::SubmitVAA { vaa } => handle_submit_vaa(deps, env, info, vaa.as_slice()),
}
}
/// Process VAA message signed by quardians
fn handle_submit_vaa<S: Storage, A: Api, Q: Querier>(
deps: &mut Extern<S, A, Q>,
fn handle_submit_vaa(
deps: DepsMut,
env: Env,
_info: MessageInfo,
data: &[u8],
) -> StdResult<HandleResponse> {
let state = config_read(&deps.storage).load()?;
) -> StdResult<Response> {
let state = config_read(deps.storage).load()?;
let vaa = parse_and_verify_vaa(&deps.storage, data, env.block.time)?;
vaa_archive_add(&mut deps.storage, vaa.hash.as_slice())?;
let vaa = parse_and_verify_vaa(deps.storage, data, env.block.time.seconds())?;
vaa_archive_add(deps.storage, vaa.hash.as_slice())?;
if state.gov_chain == vaa.emitter_chain && state.gov_address == vaa.emitter_address {
if state.guardian_set_index != vaa.guardian_set_index {
@ -139,11 +147,7 @@ fn handle_submit_vaa<S: Storage, A: Api, Q: Querier>(
ContractError::InvalidVAAAction.std_err()
}
fn handle_governance_payload<S: Storage, A: Api, Q: Querier>(
deps: &mut Extern<S, A, Q>,
env: Env,
data: &Vec<u8>,
) -> StdResult<HandleResponse> {
fn handle_governance_payload(deps: DepsMut, env: Env, data: &Vec<u8>) -> StdResult<Response> {
let gov_packet = GovernancePacket::deserialize(&data)?;
let module = String::from_utf8(gov_packet.module).unwrap();
@ -160,7 +164,7 @@ fn handle_governance_payload<S: Storage, A: Api, Q: Querier>(
}
match gov_packet.action {
// 1 is reserved for upgrade / migration
1u8 => vaa_update_contract(deps, env, &gov_packet.payload),
2u8 => vaa_update_guardian_set(deps, env, &gov_packet.payload),
3u8 => handle_set_fee(deps, env, &gov_packet.payload),
4u8 => handle_transfer_fee(deps, env, &gov_packet.payload),
@ -170,8 +174,8 @@ fn handle_governance_payload<S: Storage, A: Api, Q: Querier>(
/// Parses raw VAA data into a struct and verifies whether it contains sufficient signatures of an
/// active guardian set i.e. is valid according to Wormhole consensus rules
fn parse_and_verify_vaa<S: Storage>(
storage: &S,
fn parse_and_verify_vaa(
storage: &dyn Storage,
data: &[u8],
block_time: u64,
) -> StdResult<ParsedVAA> {
@ -239,18 +243,14 @@ fn parse_and_verify_vaa<S: Storage>(
Ok(vaa)
}
fn vaa_update_guardian_set<S: Storage, A: Api, Q: Querier>(
deps: &mut Extern<S, A, Q>,
env: Env,
data: &Vec<u8>,
) -> StdResult<HandleResponse> {
fn vaa_update_guardian_set(deps: DepsMut, env: Env, data: &Vec<u8>) -> StdResult<Response> {
/* Payload format
0 uint32 new_index
4 uint8 len(keys)
5 [][20]uint8 guardian addresses
*/
let mut state = config_read(&deps.storage).load()?;
let mut state = config_read(deps.storage).load()?;
let GuardianSetUpgrade {
new_guardian_set_index,
@ -265,107 +265,89 @@ fn vaa_update_guardian_set<S: Storage, A: Api, Q: Querier>(
state.guardian_set_index = new_guardian_set_index;
guardian_set_set(
&mut deps.storage,
state.guardian_set_index,
&new_guardian_set,
)?;
guardian_set_set(deps.storage, state.guardian_set_index, &new_guardian_set)?;
config(&mut deps.storage).save(&state)?;
config(deps.storage).save(&state)?;
let mut old_guardian_set = guardian_set_get(&deps.storage, old_guardian_set_index)?;
old_guardian_set.expiration_time = env.block.time + state.guardian_set_expirity;
guardian_set_set(&mut deps.storage, old_guardian_set_index, &old_guardian_set)?;
let mut old_guardian_set = guardian_set_get(deps.storage, old_guardian_set_index)?;
old_guardian_set.expiration_time = env.block.time.seconds() + state.guardian_set_expirity;
guardian_set_set(deps.storage, old_guardian_set_index, &old_guardian_set)?;
Ok(HandleResponse {
messages: vec![],
log: vec![
log("action", "guardian_set_change"),
log("old", old_guardian_set_index),
log("new", state.guardian_set_index),
],
data: None,
})
Ok(Response::new()
.add_attribute("action", "guardian_set_change")
.add_attribute("old", old_guardian_set_index.to_string())
.add_attribute("new", state.guardian_set_index.to_string()))
}
pub fn handle_set_fee<S: Storage, A: Api, Q: Querier>(
deps: &mut Extern<S, A, Q>,
env: Env,
data: &Vec<u8>,
) -> StdResult<HandleResponse> {
fn vaa_update_contract(_deps: DepsMut, env: Env, data: &Vec<u8>) -> StdResult<Response> {
/* Payload format
0 [][32]uint8 new_contract
*/
let ContractUpgrade { new_contract } = ContractUpgrade::deserialize(&data)?;
Ok(Response::new()
.add_message(CosmosMsg::Wasm(WasmMsg::Migrate {
contract_addr: env.contract.address.to_string(),
new_code_id: new_contract,
msg: to_binary(&MigrateMsg {})?,
}))
.add_attribute("action", "contract_upgrade"))
}
pub fn handle_set_fee(deps: DepsMut, _env: Env, data: &Vec<u8>) -> StdResult<Response> {
let set_fee_msg = SetFee::deserialize(&data)?;
// Save new fees
let mut state = config_read(&mut deps.storage).load()?;
let mut state = config_read(deps.storage).load()?;
state.fee = set_fee_msg.fee;
config(&mut deps.storage).save(&state)?;
config(deps.storage).save(&state)?;
Ok(HandleResponse {
messages: vec![],
log: vec![
log("action", "fee_change"),
log("new_fee.amount", state.fee.amount),
log("new_fee.denom", state.fee.denom),
],
data: None,
})
Ok(Response::new()
.add_attribute("action", "fee_change")
.add_attribute("new_fee.amount", state.fee.amount.to_string())
.add_attribute("new_fee.denom", state.fee.denom.to_string()))
}
pub fn handle_transfer_fee<S: Storage, A: Api, Q: Querier>(
deps: &mut Extern<S, A, Q>,
env: Env,
data: &Vec<u8>,
) -> StdResult<HandleResponse> {
pub fn handle_transfer_fee(deps: DepsMut, _env: Env, data: &Vec<u8>) -> StdResult<Response> {
let transfer_msg = TransferFee::deserialize(&data)?;
Ok(HandleResponse {
messages: vec![CosmosMsg::Bank(BankMsg::Send {
from_address: env.contract.address,
to_address: deps.api.human_address(&transfer_msg.recipient)?,
amount: vec![transfer_msg.amount],
})],
log: vec![],
data: None,
})
Ok(Response::new().add_message(CosmosMsg::Bank(BankMsg::Send {
to_address: deps.api.addr_humanize(&transfer_msg.recipient)?.to_string(),
amount: vec![transfer_msg.amount],
})))
}
fn handle_post_message<S: Storage, A: Api, Q: Querier>(
deps: &mut Extern<S, A, Q>,
fn handle_post_message(
deps: DepsMut,
env: Env,
info: MessageInfo,
message: &[u8],
nonce: u32,
) -> StdResult<HandleResponse> {
let state = config_read(&deps.storage).load()?;
) -> StdResult<Response> {
let state = config_read(deps.storage).load()?;
let fee = state.fee;
// Check fee
if !has_coins(env.message.sent_funds.as_ref(), &fee) {
if !has_coins(info.funds.as_ref(), &fee) {
return ContractError::FeeTooLow.std_err();
}
let emitter = extend_address_to_32(&deps.api.canonical_address(&env.message.sender)?);
let emitter = extend_address_to_32(&deps.api.addr_canonicalize(&info.sender.as_str())?);
let sequence = sequence_read(deps.storage, emitter.as_slice());
sequence_set(deps.storage, emitter.as_slice(), sequence + 1)?;
let sequence = sequence_read(&deps.storage, emitter.as_slice());
sequence_set(&mut deps.storage, emitter.as_slice(), sequence + 1)?;
Ok(HandleResponse {
messages: vec![],
log: vec![
log("message.message", hex::encode(message)),
log("message.sender", hex::encode(emitter)),
log("message.chain_id", CHAIN_ID),
log("message.nonce", nonce),
log("message.sequence", sequence),
log("message.block_time", env.block.time),
],
data: None,
})
Ok(Response::new()
.add_attribute("message.message", hex::encode(message))
.add_attribute("message.sender", hex::encode(emitter))
.add_attribute("message.chain_id", CHAIN_ID.to_string())
.add_attribute("message.nonce", nonce.to_string())
.add_attribute("message.sequence", sequence.to_string())
.add_attribute("message.block_time", env.block.time.seconds().to_string()))
}
pub fn query<S: Storage, A: Api, Q: Querier>(
deps: &Extern<S, A, Q>,
msg: QueryMsg,
) -> StdResult<Binary> {
#[cfg_attr(not(feature = "library"), entry_point)]
pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult<Binary> {
match msg {
QueryMsg::GuardianSetInfo {} => to_binary(&query_guardian_set_info(deps)?),
QueryMsg::VerifyVAA { vaa, block_time } => to_binary(&query_parse_and_verify_vaa(
@ -378,11 +360,9 @@ pub fn query<S: Storage, A: Api, Q: Querier>(
}
}
pub fn query_guardian_set_info<S: Storage, A: Api, Q: Querier>(
deps: &Extern<S, A, Q>,
) -> StdResult<GuardianSetInfoResponse> {
let state = config_read(&deps.storage).load()?;
let guardian_set = guardian_set_get(&deps.storage, state.guardian_set_index)?;
pub fn query_guardian_set_info(deps: Deps) -> StdResult<GuardianSetInfoResponse> {
let state = config_read(deps.storage).load()?;
let guardian_set = guardian_set_get(deps.storage, state.guardian_set_index)?;
let res = GuardianSetInfoResponse {
guardian_set_index: state.guardian_set_index,
addresses: guardian_set.addresses,
@ -390,33 +370,28 @@ pub fn query_guardian_set_info<S: Storage, A: Api, Q: Querier>(
Ok(res)
}
pub fn query_parse_and_verify_vaa<S: Storage, A: Api, Q: Querier>(
deps: &Extern<S, A, Q>,
pub fn query_parse_and_verify_vaa(
deps: Deps,
data: &[u8],
block_time: u64,
) -> StdResult<ParsedVAA> {
parse_and_verify_vaa(&deps.storage, data, block_time)
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> {
pub fn query_address_hex(deps: Deps, address: &HumanAddr) -> StdResult<GetAddressHexResponse> {
Ok(GetAddressHexResponse {
hex: hex::encode(extend_address_to_32(&deps.api.canonical_address(&address)?)),
hex: hex::encode(extend_address_to_32(&deps.api.addr_canonicalize(&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()?;
pub fn query_state(deps: Deps) -> StdResult<GetStateResponse> {
let state = config_read(deps.storage).load()?;
let res = GetStateResponse { fee: state.fee };
Ok(res)
}
fn keys_equal(a: &VerifyKey, b: &GuardianAddress) -> bool {
fn keys_equal(a: &VerifyingKey, b: &GuardianAddress) -> bool {
let mut hasher = Keccak256::new();
let point: EncodedPoint = EncodedPoint::from(a);

View File

@ -104,7 +104,6 @@ impl ContractError {
pub fn std(&self) -> StdError {
StdError::GenericErr {
msg: format!("{}", self),
backtrace: None,
}
}

View File

@ -5,6 +5,3 @@ 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

@ -1,7 +1,6 @@
use cosmwasm_std::{
Binary,
Coin,
HumanAddr,
};
use schemars::JsonSchema;
use serde::{
@ -14,8 +13,10 @@ use crate::state::{
GuardianSetInfo,
};
type HumanAddr = String;
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
pub struct InitMsg {
pub struct InstantiateMsg {
pub gov_chain: u16,
pub gov_address: Binary,
@ -25,11 +26,16 @@ pub struct InitMsg {
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum HandleMsg {
pub enum ExecuteMsg {
SubmitVAA { vaa: Binary },
PostMessage { message: Binary, nonce: u32 },
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub struct MigrateMsg {
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum QueryMsg {

View File

@ -1,6 +1,5 @@
use schemars::{
JsonSchema,
Set,
};
use serde::{
Deserialize,
@ -11,7 +10,6 @@ use cosmwasm_std::{
Binary,
CanonicalAddr,
Coin,
HumanAddr,
StdResult,
Storage,
Uint128,
@ -37,6 +35,8 @@ use sha3::{
Keccak256,
};
type HumanAddr = String;
pub static CONFIG_KEY: &[u8] = b"config";
pub static GUARDIAN_SET_KEY: &[u8] = b"guardian_set";
pub static SEQUENCE_KEY: &[u8] = b"sequence";
@ -217,62 +217,62 @@ pub struct WormholeInfo {
pub guardian_set_expirity: u64,
}
pub fn config<S: Storage>(storage: &mut S) -> Singleton<S, ConfigInfo> {
pub fn config(storage: &mut dyn Storage) -> Singleton<ConfigInfo> {
singleton(storage, CONFIG_KEY)
}
pub fn config_read<S: Storage>(storage: &S) -> ReadonlySingleton<S, ConfigInfo> {
pub fn config_read(storage: &dyn Storage) -> ReadonlySingleton<ConfigInfo> {
singleton_read(storage, CONFIG_KEY)
}
pub fn guardian_set_set<S: Storage>(
storage: &mut S,
pub fn guardian_set_set(
storage: &mut dyn Storage,
index: u32,
data: &GuardianSetInfo,
) -> StdResult<()> {
bucket(GUARDIAN_SET_KEY, storage).save(&index.to_be_bytes(), data)
bucket(storage, GUARDIAN_SET_KEY).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_be_bytes())
pub fn guardian_set_get(storage: &dyn Storage, index: u32) -> StdResult<GuardianSetInfo> {
bucket_read(storage, GUARDIAN_SET_KEY).load(&index.to_be_bytes())
}
pub fn sequence_set<S: Storage>(storage: &mut S, emitter: &[u8], sequence: u64) -> StdResult<()> {
bucket(SEQUENCE_KEY, storage).save(emitter, &sequence)
pub fn sequence_set(storage: &mut dyn Storage, emitter: &[u8], sequence: u64) -> StdResult<()> {
bucket(storage, SEQUENCE_KEY).save(emitter, &sequence)
}
pub fn sequence_read<S: Storage>(storage: &S, emitter: &[u8]) -> u64 {
bucket_read(SEQUENCE_KEY, storage)
pub fn sequence_read(storage: &dyn Storage, emitter: &[u8]) -> u64 {
bucket_read(storage, SEQUENCE_KEY)
.load(&emitter)
.or::<u64>(Ok(0))
.unwrap()
}
pub fn vaa_archive_add<S: Storage>(storage: &mut S, hash: &[u8]) -> StdResult<()> {
bucket(GUARDIAN_SET_KEY, storage).save(hash, &true)
pub fn vaa_archive_add(storage: &mut dyn Storage, hash: &[u8]) -> StdResult<()> {
bucket(storage, GUARDIAN_SET_KEY).save(hash, &true)
}
pub fn vaa_archive_check<S: Storage>(storage: &S, hash: &[u8]) -> bool {
bucket_read(GUARDIAN_SET_KEY, storage)
pub fn vaa_archive_check(storage: &dyn Storage, hash: &[u8]) -> bool {
bucket_read(storage, GUARDIAN_SET_KEY)
.load(&hash)
.or::<bool>(Ok(false))
.unwrap()
}
pub fn wrapped_asset<S: Storage>(storage: &mut S) -> Bucket<S, HumanAddr> {
bucket(WRAPPED_ASSET_KEY, storage)
pub fn wrapped_asset(storage: &mut dyn Storage) -> Bucket<HumanAddr> {
bucket(storage, WRAPPED_ASSET_KEY)
}
pub fn wrapped_asset_read<S: Storage>(storage: &S) -> ReadonlyBucket<S, HumanAddr> {
bucket_read(WRAPPED_ASSET_KEY, storage)
pub fn wrapped_asset_read(storage: &dyn Storage) -> ReadonlyBucket<HumanAddr> {
bucket_read(storage, WRAPPED_ASSET_KEY)
}
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(storage: &mut dyn Storage) -> Bucket<Vec<u8>> {
bucket(storage, WRAPPED_ASSET_ADDRESS_KEY)
}
pub fn wrapped_asset_address_read<S: Storage>(storage: &S) -> ReadonlyBucket<S, Vec<u8>> {
bucket_read(WRAPPED_ASSET_ADDRESS_KEY, storage)
pub fn wrapped_asset_address_read(storage: &dyn Storage) -> ReadonlyBucket<Vec<u8>> {
bucket_read(storage, WRAPPED_ASSET_ADDRESS_KEY)
}
pub struct GovernancePacket {
@ -299,12 +299,27 @@ impl GovernancePacket {
}
}
// action 1
pub struct ContractUpgrade {
pub new_contract: u64,
}
// action 2
pub struct GuardianSetUpgrade {
pub new_guardian_set_index: u32,
pub new_guardian_set: GuardianSetInfo,
}
impl ContractUpgrade {
pub fn deserialize(data: &Vec<u8>) -> StdResult<Self> {
let data = data.as_slice();
let new_contract = data.get_u64(24);
Ok(ContractUpgrade {
new_contract,
})
}
}
impl GuardianSetUpgrade {
pub fn deserialize(data: &Vec<u8>) -> StdResult<Self> {
const ADDRESS_LEN: usize = 20;
@ -351,7 +366,7 @@ impl SetFee {
let (_, amount) = data.get_u256(0);
let fee = Coin {
denom: String::from(FEE_DENOMINATION),
amount: Uint128(amount),
amount: Uint128::new(amount),
};
Ok(SetFee { fee })
}
@ -371,7 +386,7 @@ impl TransferFee {
let (_, amount) = data.get_u256(32);
let amount = Coin {
denom: String::from(FEE_DENOMINATION),
amount: Uint128(amount),
amount: Uint128::new(amount),
};
Ok(TransferFee { amount, recipient })
}

View File

@ -1,5 +1,10 @@
import { Wallet, LCDClient, MnemonicKey } from "@terra-money/terra.js";
import { StdFee, MsgInstantiateContract, MsgExecuteContract, MsgStoreCode } from "@terra-money/terra.js";
import {
StdFee,
MsgInstantiateContract,
MsgExecuteContract,
MsgStoreCode,
} from "@terra-money/terra.js";
import { readFileSync, readdirSync } from "fs";
// TODO: Workaround /tx/estimate_fee errors.
@ -37,139 +42,191 @@ async function main() {
await wallet.sequence();
// Deploy WASM blobs.
const artifacts = readdirSync('../artifacts/');
// Read a list of files from directory containing compiled contracts.
const artifacts = readdirSync("../artifacts/");
// Sort them to get a determinstic list for consecutive code ids.
artifacts.sort();
artifacts.reverse();
const hardcodedGas = {
"cw20_base.wasm": 4000000,
"cw20_wrapped.wasm": 4000000,
"wormhole.wasm": 5000000,
"token_bridge.wasm": 6000000,
};
// Deploy all found WASM files and assign Code IDs.
const codeIds = {};
for (const artifact in artifacts) {
console.log(artifact);
console.log(artifacts.hasOwnProperty(artifact));
if(artifacts.hasOwnProperty(artifact) && artifacts[artifact].includes('.wasm')) {
const file = artifacts[artifact];
const contract_bytes = readFileSync(`../artifacts/${file}`);
console.log(`Storing Bytes, ${contract_bytes.length}, for ${file}`);
const store_code = new MsgStoreCode(
wallet.key.accAddress,
contract_bytes.toString('base64'),
);
if (
artifacts.hasOwnProperty(artifact) &&
artifacts[artifact].includes(".wasm")
) {
const file = artifacts[artifact];
const contract_bytes = readFileSync(`../artifacts/${file}`);
try {
const tx = await wallet.createAndSignTx({
msgs: [store_code],
memo: '',
fee: new StdFee(
3000000,
{ uluna: "100000" }
)
});
console.log(`Storing WASM: ${file} (${contract_bytes.length} bytes)`);
const rs = await terra.tx.broadcast(tx);
const store_code = new MsgStoreCode(
wallet.key.accAddress,
contract_bytes.toString("base64")
);
console.log(JSON.stringify(rs, null, 2));
await wallet.sequence();
} catch (e) {
console.log('Failed to Execute');
}
try {
const tx = await wallet.createAndSignTx({
msgs: [store_code],
memo: "",
fee: new StdFee(hardcodedGas[artifacts[artifact]], {
uluna: "100000",
}),
});
const rs = await terra.tx.broadcast(tx);
const ci = /"code_id","value":"([^"]+)/gm.exec(rs.raw_log)[1];
codeIds[file] = parseInt(ci);
} catch (e) {
console.log("Failed to Execute");
}
}
}
console.log(codeIds);
// Governance constants defined by the Wormhole spec.
const govChain = 1;
const govAddress = "0000000000000000000000000000000000000000000000000000000000000004";
const govAddress =
"0000000000000000000000000000000000000000000000000000000000000004";
const addresses = {};
//Instantiate Contracts
wallet.createAndSignTx({
// Instantiate Wormhole
console.log("Instantiating Wormhole");
await wallet
.createAndSignTx({
msgs: [
new MsgInstantiateContract(
wallet.key.accAddress,
undefined,
2,
{
gov_chain: govChain,
gov_address: Buffer.from(govAddress, 'hex').toString('base64'),
guardian_set_expirity: 86400,
initial_guardian_set: {
addresses: [
{
bytes: Buffer.from('beFA429d57cD18b7F8A4d91A2da9AB4AF05d0FBe', 'hex').toString('base64'),
}
],
expiration_time: 0
wallet.key.accAddress,
wallet.key.accAddress,
codeIds["wormhole.wasm"],
{
gov_chain: govChain,
gov_address: Buffer.from(govAddress, "hex").toString("base64"),
guardian_set_expirity: 86400,
initial_guardian_set: {
addresses: [
{
bytes: Buffer.from(
"beFA429d57cD18b7F8A4d91A2da9AB4AF05d0FBe",
"hex"
).toString("base64"),
},
],
expiration_time: 0,
},
)
],
memo:'',
})
.then(tx => terra.tx.broadcast(tx))
.then(rs => console.log(rs));
}
),
],
memo: "",
})
.then((tx) => terra.tx.broadcast(tx))
.then((rs) => {
const address = /"contract_address","value":"([^"]+)/gm.exec(
rs.raw_log
)[1];
addresses["wormhole.wasm"] = address;
});
wallet.createAndSignTx({
console.log("Instantiating Token Bridge");
await wallet
.createAndSignTx({
msgs: [
new MsgInstantiateContract(
wallet.key.accAddress,
undefined,
4,
{
owner: deployer.key.accAddress,
gov_chain: govChain,
gov_address: Buffer.from(govAddress, 'hex').toString('base64'),
wormhole_contract: "",
wrapped_asset_code_id: 2,
},
)
],
memo:'',
})
.then(tx => terra.tx.broadcast(tx))
.then(rs => console.log(rs));
wallet.key.accAddress,
wallet.key.accAddress,
codeIds["token_bridge.wasm"],
{
owner: wallet.key.accAddress,
gov_chain: govChain,
gov_address: Buffer.from(govAddress, "hex").toString("base64"),
wormhole_contract: addresses["wormhole.wasm"],
wrapped_asset_code_id: 2,
}
),
],
memo: "",
})
.then((tx) => terra.tx.broadcast(tx))
.then((rs) => {
const address = /"contract_address","value":"([^"]+)/gm.exec(
rs.raw_log
)[1];
addresses["token_bridge.wasm"] = address;
});
wallet.createAndSignTx({
await wallet
.createAndSignTx({
msgs: [
new MsgInstantiateContract(
wallet.key.accAddress,
undefined,
3,
{
name: "MOCK",
symbol: "MCK",
decimals: 6,
initial_balances: [
{
"address": deployer.key.acc_address,
"amount": "100000000"
}
],
mint: null,
},
)
],
memo:'',
})
.then(tx => terra.tx.broadcast(tx))
.then(rs => console.log(rs));
wallet.key.accAddress,
undefined,
codeIds["cw20_base.wasm"],
{
name: "MOCK",
symbol: "MCK",
decimals: 6,
initial_balances: [
{
address: wallet.key.accAddress,
amount: "100000000",
},
],
mint: null,
}
),
],
memo: "",
})
.then((tx) => terra.tx.broadcast(tx))
.then((rs) => {
const address = /"contract_address","value":"([^"]+)/gm.exec(
rs.raw_log
)[1];
addresses["mock.wasm"] = address;
});
console.log(addresses);
const registrations = [
'01000000000100c9f4230109e378f7efc0605fb40f0e1869f2d82fda5b1dfad8a5a2dafee85e033d155c18641165a77a2db6a7afbf2745b458616cb59347e89ae0c7aa3e7cc2d400000000010000000100010000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000546f6b656e4272696467650100000001c69a1b1a65dd336bf1df6a77afb501fc25db7fc0938cb08595a9ef473265cb4f',
'01000000000100e2e1975d14734206e7a23d90db48a6b5b6696df72675443293c6057dcb936bf224b5df67d32967adeb220d4fe3cb28be515be5608c74aab6adb31099a478db5c01000000010000000100010000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000546f6b656e42726964676501000000020000000000000000000000000290fb167208af455bb137780163b7b7a9a10c16'
"01000000000100c9f4230109e378f7efc0605fb40f0e1869f2d82fda5b1dfad8a5a2dafee85e033d155c18641165a77a2db6a7afbf2745b458616cb59347e89ae0c7aa3e7cc2d400000000010000000100010000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000546f6b656e4272696467650100000001c69a1b1a65dd336bf1df6a77afb501fc25db7fc0938cb08595a9ef473265cb4f",
"01000000000100e2e1975d14734206e7a23d90db48a6b5b6696df72675443293c6057dcb936bf224b5df67d32967adeb220d4fe3cb28be515be5608c74aab6adb31099a478db5c01000000010000000100010000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000546f6b656e42726964676501000000020000000000000000000000000290fb167208af455bb137780163b7b7a9a10c16",
];
registrations.forEach(registration => {
wallet.createAndSignTx({
msgs: [
new MsgExecuteContract(
for (const registration in registrations) {
if (registrations.hasOwnProperty(registration)) {
console.log('Registering');
await wallet
.createAndSignTx({
msgs: [
new MsgExecuteContract(
wallet.key.accAddress,
"",
addresses["token_bridge.wasm"],
{
submit_vaa: {
data: Buffer.from(registration, 'hex'),
},
submit_vaa: {
data: Buffer.from(registrations[registration], "hex").toString('base64'),
},
},
{ uluna: 1000 }
),
],
memo: '',
})
.then(tx => terra.tx.broadcast(tx))
.then(rs => console.log(rs));
});
),
],
memo: "",
fee: new StdFee(2000000, {
uluna: "100000",
}),
})
.then((tx) => terra.tx.broadcast(tx))
.then((rs) => console.log(rs));
}
}
}
main()
main();

View File

@ -11,5 +11,6 @@ done
sleep 2
npm ci && node deploy.js
echo "Going to sleep, interrupt if running manually"
sleep infinity

133
terra/tools/migrate.js Normal file
View File

@ -0,0 +1,133 @@
import { Wallet, LCDClient, MnemonicKey } from "@terra-money/terra.js";
import {
StdFee,
MsgExecuteContract,
MsgInstantiateContract,
MsgMigrateContract,
MsgStoreCode,
MsgUpdateContractAdmin,
} from "@terra-money/terra.js";
import { readFileSync, readdirSync } from "fs";
async function main() {
const terra = new LCDClient({
URL: "http://localhost:1317",
chainID: "localterra",
});
const wallet = terra.wallet(
new MnemonicKey({
mnemonic:
"notice oak worry limit wrap speak medal online prefer cluster roof addict wrist behave treat actual wasp year salad speed social layer crew genius",
})
);
const hardcodedGas = {
"wormhole.wasm": 5000000,
};
// Deploy Wormhole alone.
const file = "wormhole.wasm";
const contract_bytes = readFileSync(`../artifacts/${file}`);
console.log(`Storing WASM: ${file} (${contract_bytes.length} bytes)`);
// Get new code id.
const store_code = new MsgStoreCode(
wallet.key.accAddress,
contract_bytes.toString("base64")
);
const codeIds = {};
try {
const tx = await wallet.createAndSignTx({
msgs: [store_code],
memo: "",
fee: new StdFee(hardcodedGas["wormhole.wasm"], {
uluna: "100000",
}),
});
const rs = await terra.tx.broadcast(tx);
const ci = /"code_id","value":"([^"]+)/gm.exec(rs.raw_log)[1];
codeIds[file] = parseInt(ci);
} catch (e) {
console.log("Failed to Execute");
}
// Perform a Centralised update.
await wallet
.createAndSignTx({
msgs: [
new MsgMigrateContract(
wallet.key.accAddress,
"terra18vd8fpwxzck93qlwghaj6arh4p7c5n896xzem5",
codeIds["wormhole.wasm"],
{
"action": ""
},
{ uluna: 1000 }
),
],
memo: "",
})
.then((tx) => terra.tx.broadcast(tx))
.then((rs) => console.log(rs));
// Set the Admin to the contract.
await wallet
.createAndSignTx({
msgs: [
new MsgUpdateContractAdmin(
wallet.key.accAddress,
"terra18vd8fpwxzck93qlwghaj6arh4p7c5n896xzem5",
"terra18vd8fpwxzck93qlwghaj6arh4p7c5n896xzem5"
),
],
memo: "",
})
.then((tx) => terra.tx.broadcast(tx))
.then((rs) => console.log(rs));
// Deploy a new CodeID.
try {
const tx = await wallet.createAndSignTx({
msgs: [store_code],
memo: "",
fee: new StdFee(hardcodedGas["wormhole.wasm"], {
uluna: "100000",
}),
});
const rs = await terra.tx.broadcast(tx);
const ci = /"code_id","value":"([^"]+)/gm.exec(rs.raw_log)[1];
codeIds[file] = parseInt(ci);
} catch (e) {
console.log("Failed to Execute");
}
const upgradeVAA = '010000000001008928c70a029a924d334a24587e9d2ddbcfa7250d7ba61200e86b16966ef2bbd675fb759aa7a47c6392482ef073e9a6d7c4980dc53ed6f90fc84331486e284912000000000100000001000100000000000000000000000000000000000000000000000000000000000000040000000004e78c580000000000000000000000000000000000000000000000000000000000436f72650100030000000000000000000000000000000000000000000000000000000000000005';
// Perform a decentralised update with a signed VAA.
await wallet
.createAndSignTx({
msgs: [
new MsgExecuteContract(
wallet.key.accAddress,
"terra18vd8fpwxzck93qlwghaj6arh4p7c5n896xzem5",
{
submit_v_a_a: {
vaa: Buffer.from(upgradeVAA, "hex").toString(
"base64"
),
},
},
{ uluna: 1000 }
),
],
memo: "",
})
.then((tx) => terra.tx.broadcast(tx))
.then((rs) => console.log(rs));
}
main();