Add ibc-translator contract to the cosmwasm workspace and it's governance packet to the rust sdk. (#3174)
* Add ibc-translator contract and it's governance packet to the rust sdk. * Remove unnecessary cargo script * rustfmt and clippy fixes * Address review comments * Use WormholeQuery instead of contract to verify vaa, remove wormhole core contract dependency, remove feature flag on anybuf. * Add cw20 token metadata to tokenfactory metadata * Change Simple/ContractControlled payloads to GatewayTransfer/GatewayTransferWithPayload * Change display and scaled denom to ensure x/bank conformance.
This commit is contained in:
parent
89e97fc366
commit
821a6e8752
|
@ -9,7 +9,7 @@ dependencies = [
|
|||
"anyhow",
|
||||
"cosmwasm-schema",
|
||||
"cosmwasm-std",
|
||||
"cw-storage-plus",
|
||||
"cw-storage-plus 0.13.4",
|
||||
"cw_transcode",
|
||||
"hex",
|
||||
"schemars",
|
||||
|
@ -44,6 +44,12 @@ dependencies = [
|
|||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anybuf"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f14538dd5b6befdc12b11aae1dddee64b670534d4fdbd28057bcc7c976c0e4eb"
|
||||
|
||||
[[package]]
|
||||
name = "anyhow"
|
||||
version = "1.0.71"
|
||||
|
@ -123,6 +129,12 @@ version = "0.2.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8d696c370c750c948ada61c69a0ee2cbbb9c50b1019ddb86d9317157a99c2cae"
|
||||
|
||||
[[package]]
|
||||
name = "bs58"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "771fe0050b883fcc3ea2359b1a96bcfbc090b7116eae7c3c512c7a083fdf23d3"
|
||||
|
||||
[[package]]
|
||||
name = "bstr"
|
||||
version = "1.4.0"
|
||||
|
@ -214,9 +226,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "cosmwasm-crypto"
|
||||
version = "1.2.5"
|
||||
version = "1.2.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "75836a10cb9654c54e77ee56da94d592923092a10b369cdb0dbd56acefc16340"
|
||||
checksum = "bb64554a91d6a9231127f4355d351130a0b94e663d5d9dc8b3a54ca17d83de49"
|
||||
dependencies = [
|
||||
"digest 0.10.6",
|
||||
"ed25519-zebra",
|
||||
|
@ -227,18 +239,18 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "cosmwasm-derive"
|
||||
version = "1.2.5"
|
||||
version = "1.2.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1c9f7f0e51bfc7295f7b2664fe8513c966428642aa765dad8a74acdab5e0c773"
|
||||
checksum = "a0fb2ce09f41a3dae1a234d56a9988f9aff4c76441cd50ef1ee9a4f20415b028"
|
||||
dependencies = [
|
||||
"syn 1.0.109",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cosmwasm-schema"
|
||||
version = "1.2.5"
|
||||
version = "1.2.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0f00b363610218eea83f24bbab09e1a7c3920b79f068334fdfcc62f6129ef9fc"
|
||||
checksum = "230e5d1cefae5331db8934763c81b9c871db6a2cd899056a5694fa71d292c815"
|
||||
dependencies = [
|
||||
"cosmwasm-schema-derive",
|
||||
"schemars",
|
||||
|
@ -249,9 +261,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "cosmwasm-schema-derive"
|
||||
version = "1.2.5"
|
||||
version = "1.2.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ae38f909b2822d32b275c9e2db9728497aa33ffe67dd463bc67c6a3b7092785c"
|
||||
checksum = "43dadf7c23406cb28079d69e6cb922c9c29b9157b0fe887e3b79c783b7d4bcb8"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
|
@ -260,9 +272,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "cosmwasm-std"
|
||||
version = "1.2.5"
|
||||
version = "1.2.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a49b85345e811c8e80ec55d0d091e4fcb4f00f97ab058f9be5f614c444a730cb"
|
||||
checksum = "4337eef8dfaf8572fe6b6b415d6ec25f9308c7bb09f2da63789209fb131363be"
|
||||
dependencies = [
|
||||
"base64",
|
||||
"cosmwasm-crypto",
|
||||
|
@ -482,8 +494,8 @@ dependencies = [
|
|||
"anyhow",
|
||||
"cosmwasm-std",
|
||||
"cosmwasm-storage",
|
||||
"cw-storage-plus",
|
||||
"cw-utils",
|
||||
"cw-storage-plus 0.13.4",
|
||||
"cw-utils 0.13.4",
|
||||
"derivative",
|
||||
"itertools",
|
||||
"prost",
|
||||
|
@ -503,6 +515,17 @@ dependencies = [
|
|||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cw-storage-plus"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3f0e92a069d62067f3472c62e30adedb4cab1754725c0f2a682b3128d2bf3c79"
|
||||
dependencies = [
|
||||
"cosmwasm-std",
|
||||
"schemars",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cw-utils"
|
||||
version = "0.13.4"
|
||||
|
@ -515,6 +538,21 @@ dependencies = [
|
|||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cw-utils"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c80e93d1deccb8588db03945016a292c3c631e6325d349ebb35d2db6f4f946f7"
|
||||
dependencies = [
|
||||
"cosmwasm-schema",
|
||||
"cosmwasm-std",
|
||||
"cw2 1.1.0",
|
||||
"schemars",
|
||||
"semver",
|
||||
"serde",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cw2"
|
||||
version = "0.13.4"
|
||||
|
@ -522,11 +560,25 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "04cf4639517490dd36b333bbd6c4fbd92e325fd0acf4683b41753bc5eb63bfc1"
|
||||
dependencies = [
|
||||
"cosmwasm-std",
|
||||
"cw-storage-plus",
|
||||
"cw-storage-plus 0.13.4",
|
||||
"schemars",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cw2"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "29ac2dc7a55ad64173ca1e0a46697c31b7a5c51342f55a1e84a724da4eb99908"
|
||||
dependencies = [
|
||||
"cosmwasm-schema",
|
||||
"cosmwasm-std",
|
||||
"cw-storage-plus 1.1.0",
|
||||
"schemars",
|
||||
"serde",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cw20"
|
||||
version = "0.13.4"
|
||||
|
@ -534,7 +586,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "4cb782b8f110819a4eb5dbbcfed25ffba49ec16bbe32b4ad8da50a5ce68fec05"
|
||||
dependencies = [
|
||||
"cosmwasm-std",
|
||||
"cw-utils",
|
||||
"cw-utils 0.13.4",
|
||||
"schemars",
|
||||
"serde",
|
||||
]
|
||||
|
@ -546,9 +598,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "0306e606581f4fb45e82bcbb7f0333179ed53dd949c6523f01a99b4bfc1475a0"
|
||||
dependencies = [
|
||||
"cosmwasm-std",
|
||||
"cw-storage-plus",
|
||||
"cw-utils",
|
||||
"cw2",
|
||||
"cw-storage-plus 0.13.4",
|
||||
"cw-utils 0.13.4",
|
||||
"cw2 0.13.4",
|
||||
"cw20",
|
||||
"schemars",
|
||||
"serde",
|
||||
|
@ -561,7 +613,7 @@ version = "0.1.0"
|
|||
dependencies = [
|
||||
"cosmwasm-std",
|
||||
"cosmwasm-storage",
|
||||
"cw2",
|
||||
"cw2 0.13.4",
|
||||
"cw20",
|
||||
"cw20-base",
|
||||
"schemars",
|
||||
|
@ -601,7 +653,7 @@ dependencies = [
|
|||
"ident_case",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.15",
|
||||
"syn 2.0.23",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -612,7 +664,7 @@ checksum = "29a358ff9f12ec09c3e61fef9b5a9902623a695a46a917b07f269bff1445611a"
|
|||
dependencies = [
|
||||
"darling_core",
|
||||
"quote",
|
||||
"syn 2.0.15",
|
||||
"syn 2.0.23",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -779,7 +831,7 @@ dependencies = [
|
|||
"darling",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.15",
|
||||
"syn 2.0.23",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -888,8 +940,8 @@ dependencies = [
|
|||
"cosmwasm-schema",
|
||||
"cosmwasm-std",
|
||||
"cw-multi-test",
|
||||
"cw-storage-plus",
|
||||
"cw2",
|
||||
"cw-storage-plus 0.13.4",
|
||||
"cw2 0.13.4",
|
||||
"cw_transcode",
|
||||
"hex",
|
||||
"schemars",
|
||||
|
@ -964,6 +1016,28 @@ dependencies = [
|
|||
"digest 0.10.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ibc-translator"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anybuf",
|
||||
"anyhow",
|
||||
"bs58",
|
||||
"cosmwasm-schema",
|
||||
"cosmwasm-std",
|
||||
"cw-storage-plus 0.13.4",
|
||||
"cw-utils 1.0.1",
|
||||
"cw20",
|
||||
"cw20-base",
|
||||
"cw20-wrapped-2",
|
||||
"serde-json-wasm 0.5.1",
|
||||
"serde_wormhole",
|
||||
"token-bridge-cosmwasm",
|
||||
"wormhole-bindings",
|
||||
"wormhole-cosmwasm",
|
||||
"wormhole-sdk",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ident_case"
|
||||
version = "1.0.1"
|
||||
|
@ -1273,9 +1347,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.56"
|
||||
version = "1.0.63"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2b63bdb0cd06f1f4dedf69b254734f9b45af66e4a031e42a7480257d9898b435"
|
||||
checksum = "7b368fba921b0dce7e60f5e04ec15e565b3303972b42bcfde1d0713b881959eb"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
@ -1334,9 +1408,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.26"
|
||||
version = "1.0.29"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4424af4bf778aae2051a77b60283332f386554255d722233d09fbfc7e30da2fc"
|
||||
checksum = "573015e8ab27661678357f27dc26460738fd2b6c86e46f386fde94cb5d913105"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
@ -1557,9 +1631,9 @@ checksum = "bebd363326d05ec3e2f532ab7660680f3b02130d780c299bca73469d521bc0ed"
|
|||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.160"
|
||||
version = "1.0.164"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bb2f3770c8bce3bcda7e149193a069a0f4365bda1fa5cd88e03bca26afc1216c"
|
||||
checksum = "9e8c8cf938e98f769bc164923b06dce91cea1751522f46f8466461af04c9027d"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
@ -1593,13 +1667,13 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.160"
|
||||
version = "1.0.164"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "291a097c63d8497e00160b166a967a4a79c64f3facdd01cbd7502231688d77df"
|
||||
checksum = "d9735b638ccc51c28bf6914d90a2e9725b377144fc612c49a611fddd1b631d68"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.15",
|
||||
"syn 2.0.23",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -1758,9 +1832,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.15"
|
||||
version = "2.0.23"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a34fcf3e8b60f57e6a14301a2e916d323af98b0ea63c599441eec8558660c822"
|
||||
checksum = "59fb7d6d8281a51045d62b8eb3a7d1ce347b76f312af50cd3dc0af39c87c1737"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
|
@ -1817,7 +1891,7 @@ checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f"
|
|||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.15",
|
||||
"syn 2.0.23",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -1876,7 +1950,7 @@ checksum = "0f57e3ca2a01450b1a921183a9c9cbfda207fd822cef4ccb00a65402cbba7a74"
|
|||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.15",
|
||||
"syn 2.0.23",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -2461,7 +2535,7 @@ dependencies = [
|
|||
"anyhow",
|
||||
"cosmwasm-schema",
|
||||
"cosmwasm-std",
|
||||
"cw-storage-plus",
|
||||
"cw-storage-plus 0.13.4",
|
||||
"semver",
|
||||
"serde_wormhole",
|
||||
"thiserror",
|
||||
|
@ -2506,7 +2580,7 @@ dependencies = [
|
|||
"anyhow",
|
||||
"cosmwasm-schema",
|
||||
"cosmwasm-std",
|
||||
"cw-storage-plus",
|
||||
"cw-storage-plus 0.13.4",
|
||||
"hex",
|
||||
"schemars",
|
||||
"semver",
|
||||
|
|
|
@ -11,7 +11,8 @@ members = [
|
|||
"packages/wormhole-bindings",
|
||||
"packages/cw_transcode",
|
||||
"contracts/wormhole-ibc",
|
||||
"contracts/wormchain-ibc-receiver"
|
||||
"contracts/wormchain-ibc-receiver",
|
||||
"contracts/ibc-translator"
|
||||
]
|
||||
|
||||
# Needed to prevent unwanted feature unification between normal builds and dev builds. See
|
||||
|
@ -40,4 +41,4 @@ wormhole-bindings = { path = "packages/wormhole-bindings" }
|
|||
wormhole-cosmwasm = { path = "contracts/wormhole" }
|
||||
wormhole-sdk = { path = "../sdk/rust/core" }
|
||||
wormchain-ibc-receiver = { path = "contracts/wormchain-ibc-receiver" }
|
||||
wormhole-ibc = { path = "contracts/wormhole-ibc" }
|
||||
wormhole-ibc = { path = "contracts/wormhole-ibc" }
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
# 1. The first stage builds the contracts
|
||||
# 2. The second is an empty image with only the wasm files (useful for exporting)
|
||||
# 3. The third creates a node.js environment to deploy the contracts to devnet
|
||||
FROM cosmwasm/workspace-optimizer:0.12.6@sha256:e6565a5e87c830ef3e8775a9035006b38ad0aaf0a96319158c802457b1dd1d08 AS builder
|
||||
FROM cosmwasm/workspace-optimizer:0.13.0@sha256:d868e239f73fb45ba98dd088c0a6a15effd0b87b7b193701f02c3913ecb8a196 AS builder
|
||||
|
||||
COPY cosmwasm/Cargo.lock /code/
|
||||
COPY cosmwasm/Cargo.toml /code/
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
[alias]
|
||||
wasm = "build --release --target wasm32-unknown-unknown"
|
||||
wasm-debug = "build --target wasm32-unknown-unknown"
|
||||
unit-test = "test --lib --features backtraces"
|
||||
integration-test = "test --test integration"
|
|
@ -0,0 +1,32 @@
|
|||
[package]
|
||||
name = "ibc-translator"
|
||||
version = "0.1.0"
|
||||
authors = ["Wormhole Project Contributors"]
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib", "rlib"]
|
||||
|
||||
[features]
|
||||
# for more explicit tests, cargo test --features=backtraces
|
||||
backtraces = ["cosmwasm-std/backtraces"]
|
||||
# use library feature to disable all instantiate/execute/query/reply exports
|
||||
library = []
|
||||
|
||||
[dependencies]
|
||||
anybuf = "0.1.0"
|
||||
anyhow = "1"
|
||||
bs58 = "0.4.0"
|
||||
cosmwasm-schema = "1.2.7"
|
||||
cosmwasm-std = {version="1.2.7", features = ["ibc3"] }
|
||||
cw-storage-plus = "0.13.2"
|
||||
cw-utils = "1.0.1"
|
||||
cw20 = "0.13.2"
|
||||
cw20-base = { version = "0.13.2", features = ["library"] }
|
||||
cw20-wrapped-2 = { version = "0.1.0", features = ["library"] }
|
||||
serde-json-wasm = "0.5.1"
|
||||
serde_wormhole = "0.1.0"
|
||||
token-bridge-cosmwasm = { version = "0.1.0", features = ["library"] }
|
||||
wormhole-bindings = "0.1.0"
|
||||
wormhole-cosmwasm = { version = "0.1.0", features = ["library"] }
|
||||
wormhole-sdk = { version = "0.1.0", features = ["schemars"] }
|
|
@ -0,0 +1,107 @@
|
|||
#[cfg(not(feature = "library"))]
|
||||
use cosmwasm_std::entry_point;
|
||||
|
||||
use anyhow::{bail, Context};
|
||||
use cosmwasm_std::{
|
||||
to_binary, Binary, Deps, DepsMut, Empty, Env, MessageInfo, Reply, Response, StdResult,
|
||||
};
|
||||
use wormhole_bindings::{tokenfactory::TokenFactoryMsg, WormholeQuery};
|
||||
|
||||
use crate::{
|
||||
execute::{
|
||||
complete_transfer_and_convert, convert_and_transfer, submit_update_chain_to_channel_map,
|
||||
TransferType,
|
||||
},
|
||||
msg::{ExecuteMsg, InstantiateMsg, QueryMsg, COMPLETE_TRANSFER_REPLY_ID},
|
||||
query::query_ibc_channel,
|
||||
reply::handle_complete_transfer_reply,
|
||||
state::TOKEN_BRIDGE_CONTRACT,
|
||||
};
|
||||
|
||||
#[cfg_attr(not(feature = "library"), entry_point)]
|
||||
pub fn instantiate(
|
||||
deps: DepsMut,
|
||||
_env: Env,
|
||||
info: MessageInfo,
|
||||
msg: InstantiateMsg,
|
||||
) -> Result<Response, anyhow::Error> {
|
||||
TOKEN_BRIDGE_CONTRACT
|
||||
.save(deps.storage, &msg.token_bridge_contract)
|
||||
.context("failed to save token bridge contract address to storage")?;
|
||||
|
||||
Ok(Response::new()
|
||||
.add_attribute("action", "instantiate")
|
||||
.add_attribute("owner", info.sender))
|
||||
}
|
||||
|
||||
#[cfg_attr(not(feature = "library"), entry_point)]
|
||||
pub fn migrate(_deps: DepsMut, _env: Env, _msg: Empty) -> Result<Response, anyhow::Error> {
|
||||
Ok(Response::default())
|
||||
}
|
||||
|
||||
#[cfg_attr(not(feature = "library"), entry_point)]
|
||||
pub fn execute(
|
||||
deps: DepsMut<WormholeQuery>,
|
||||
env: Env,
|
||||
info: MessageInfo,
|
||||
msg: ExecuteMsg,
|
||||
) -> Result<Response<TokenFactoryMsg>, anyhow::Error> {
|
||||
match msg {
|
||||
ExecuteMsg::CompleteTransferAndConvert { vaa } => {
|
||||
complete_transfer_and_convert(deps, env, info, vaa)
|
||||
}
|
||||
ExecuteMsg::GatewayConvertAndTransfer {
|
||||
recipient,
|
||||
chain,
|
||||
fee,
|
||||
nonce,
|
||||
} => convert_and_transfer(
|
||||
deps,
|
||||
info,
|
||||
env,
|
||||
recipient,
|
||||
chain,
|
||||
TransferType::Simple { fee },
|
||||
nonce,
|
||||
),
|
||||
ExecuteMsg::GatewayConvertAndTransferWithPayload {
|
||||
contract,
|
||||
chain,
|
||||
payload,
|
||||
nonce,
|
||||
} => convert_and_transfer(
|
||||
deps,
|
||||
info,
|
||||
env,
|
||||
contract,
|
||||
chain,
|
||||
TransferType::ContractControlled { payload },
|
||||
nonce,
|
||||
),
|
||||
ExecuteMsg::SubmitUpdateChainToChannelMap { vaa } => {
|
||||
submit_update_chain_to_channel_map(deps, vaa)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Reply handler for various kinds of replies
|
||||
#[cfg_attr(not(feature = "library"), entry_point)]
|
||||
pub fn reply(
|
||||
deps: DepsMut,
|
||||
env: Env,
|
||||
msg: Reply,
|
||||
) -> Result<Response<TokenFactoryMsg>, anyhow::Error> {
|
||||
if msg.id == COMPLETE_TRANSFER_REPLY_ID {
|
||||
return handle_complete_transfer_reply(deps, env, msg);
|
||||
}
|
||||
|
||||
// for safety, let's error out if we don't match a reply ID
|
||||
bail!("unmatched reply id {}", msg.id);
|
||||
}
|
||||
|
||||
#[cfg_attr(not(feature = "library"), entry_point)]
|
||||
pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult<Binary> {
|
||||
match msg {
|
||||
QueryMsg::IbcChannel { chain_id } => to_binary(&query_ibc_channel(deps, chain_id)?),
|
||||
}
|
||||
}
|
|
@ -0,0 +1,310 @@
|
|||
use anyhow::{bail, ensure, Context};
|
||||
use cosmwasm_std::{
|
||||
to_binary, Binary, Coin, CosmosMsg, Deps, DepsMut, Empty, Env, Event, MessageInfo,
|
||||
QueryRequest, Response, SubMsg, Uint128, WasmMsg, WasmQuery,
|
||||
};
|
||||
use cw_token_bridge::msg::{
|
||||
Asset, AssetInfo, ExecuteMsg as TokenBridgeExecuteMsg, QueryMsg as TokenBridgeQueryMsg,
|
||||
TransferInfoResponse,
|
||||
};
|
||||
use cw_wormhole::byte_utils::ByteUtils;
|
||||
|
||||
use cw20_wrapped_2::msg::ExecuteMsg as Cw20WrappedExecuteMsg;
|
||||
use serde_wormhole::RawMessage;
|
||||
use std::str;
|
||||
use wormhole_bindings::{
|
||||
tokenfactory::{TokenFactoryMsg, TokenMsg},
|
||||
WormholeQuery,
|
||||
};
|
||||
use wormhole_sdk::{
|
||||
ibc_translator::{Action, GovernancePacket},
|
||||
vaa::{Body, Header},
|
||||
Chain,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
msg::COMPLETE_TRANSFER_REPLY_ID,
|
||||
state::{
|
||||
CHAIN_TO_CHANNEL_MAP, CURRENT_TRANSFER, CW_DENOMS, TOKEN_BRIDGE_CONTRACT, VAA_ARCHIVE,
|
||||
},
|
||||
};
|
||||
|
||||
pub enum TransferType {
|
||||
Simple { fee: Uint128 },
|
||||
ContractControlled { payload: Binary },
|
||||
}
|
||||
|
||||
/// Calls into the wormhole token bridge to complete the payload3 transfer.
|
||||
pub fn complete_transfer_and_convert(
|
||||
deps: DepsMut<WormholeQuery>,
|
||||
env: Env,
|
||||
info: MessageInfo,
|
||||
vaa: Binary,
|
||||
) -> Result<Response<TokenFactoryMsg>, anyhow::Error> {
|
||||
// get the token bridge contract address from storage
|
||||
let token_bridge_contract = TOKEN_BRIDGE_CONTRACT
|
||||
.load(deps.storage)
|
||||
.context("could not load token bridge contract address")?;
|
||||
|
||||
// craft the token bridge execute message
|
||||
// this will be added as a submessage to the response
|
||||
let token_bridge_execute_msg = to_binary(&TokenBridgeExecuteMsg::CompleteTransferWithPayload {
|
||||
data: vaa.clone(),
|
||||
relayer: info.sender.to_string(),
|
||||
})
|
||||
.context("could not serialize token bridge execute msg")?;
|
||||
|
||||
let sub_msg = SubMsg::reply_on_success(
|
||||
CosmosMsg::Wasm(WasmMsg::Execute {
|
||||
contract_addr: token_bridge_contract.clone(),
|
||||
msg: token_bridge_execute_msg,
|
||||
funds: vec![],
|
||||
}),
|
||||
COMPLETE_TRANSFER_REPLY_ID,
|
||||
);
|
||||
|
||||
// craft the token bridge query message to parse the payload3 vaa
|
||||
let token_bridge_query_msg = to_binary(&TokenBridgeQueryMsg::TransferInfo { vaa })
|
||||
.context("could not serialize token bridge transfer_info query msg")?;
|
||||
|
||||
let transfer_info: TransferInfoResponse = deps
|
||||
.querier
|
||||
.query(&QueryRequest::Wasm(WasmQuery::Smart {
|
||||
contract_addr: token_bridge_contract,
|
||||
msg: token_bridge_query_msg,
|
||||
}))
|
||||
.context("could not parse token bridge payload3 vaa")?;
|
||||
|
||||
// DEFENSE IN-DEPTH CHECK FOR PAYLOAD3 VAAs
|
||||
// ensure that the transfer vaa recipient is this contract.
|
||||
// we should never process any VAAs that are not directed to this contract.
|
||||
let target_address = (&transfer_info.recipient.as_slice()).get_address(0);
|
||||
let recipient = deps.api.addr_humanize(&target_address)?;
|
||||
ensure!(
|
||||
recipient == env.contract.address,
|
||||
"vaa recipient must be this contract"
|
||||
);
|
||||
|
||||
// save interim state
|
||||
CURRENT_TRANSFER
|
||||
.save(deps.storage, &transfer_info)
|
||||
.context("failed to save current transfer to storage")?;
|
||||
|
||||
// return the response which will callback to the reply handler on success
|
||||
Ok(Response::new()
|
||||
.add_submessage(sub_msg)
|
||||
.add_attribute("action", "complete_transfer_with_payload")
|
||||
.add_attribute(
|
||||
"transfer_payload",
|
||||
Binary::from(transfer_info.payload).to_base64(),
|
||||
))
|
||||
}
|
||||
|
||||
pub fn convert_and_transfer(
|
||||
deps: DepsMut<WormholeQuery>,
|
||||
info: MessageInfo,
|
||||
env: Env,
|
||||
recipient: Binary,
|
||||
chain: u16,
|
||||
transfer_type: TransferType,
|
||||
nonce: u32,
|
||||
) -> Result<Response<TokenFactoryMsg>, anyhow::Error> {
|
||||
// load the token bridge contract address
|
||||
let token_bridge_contract = TOKEN_BRIDGE_CONTRACT
|
||||
.load(deps.storage)
|
||||
.context("could not load token bridge contract address")?;
|
||||
|
||||
ensure!(info.funds.len() == 1, "no bridging coin included");
|
||||
let bridging_coin = info.funds[0].clone();
|
||||
let cw20_contract_addr = parse_bank_token_factory_contract(deps, env, bridging_coin.clone())?;
|
||||
|
||||
// batch calls together
|
||||
let mut response: Response<TokenFactoryMsg> = Response::new();
|
||||
|
||||
// 1. tokenfactorymsg::burn for the bank tokens
|
||||
response = response.add_message(TokenMsg::BurnTokens {
|
||||
denom: bridging_coin.denom.clone(),
|
||||
amount: bridging_coin.amount.u128(),
|
||||
burn_from_address: "".to_string(),
|
||||
});
|
||||
|
||||
// 2. cw20::increaseAllowance to the contract address for the token bridge to spend the amount of tokens
|
||||
let increase_allowance_msg = to_binary(&Cw20WrappedExecuteMsg::IncreaseAllowance {
|
||||
spender: token_bridge_contract.clone(),
|
||||
amount: bridging_coin.amount,
|
||||
expires: None,
|
||||
})
|
||||
.context("could not serialize cw20 increase_allowance msg")?;
|
||||
response = response.add_message(CosmosMsg::Wasm(WasmMsg::Execute {
|
||||
contract_addr: cw20_contract_addr.clone(),
|
||||
msg: increase_allowance_msg,
|
||||
funds: vec![],
|
||||
}));
|
||||
|
||||
// 3. token_bridge::initiate_transfer -- the cw20 tokens will be either burned or transferred to the token_bridge
|
||||
let token_bridge_transfer: TokenBridgeExecuteMsg = match transfer_type {
|
||||
TransferType::Simple { fee } => TokenBridgeExecuteMsg::InitiateTransfer {
|
||||
asset: Asset {
|
||||
info: AssetInfo::Token {
|
||||
contract_addr: cw20_contract_addr,
|
||||
},
|
||||
amount: bridging_coin.amount,
|
||||
},
|
||||
recipient_chain: chain,
|
||||
recipient,
|
||||
fee,
|
||||
nonce,
|
||||
},
|
||||
TransferType::ContractControlled { payload } => {
|
||||
TokenBridgeExecuteMsg::InitiateTransferWithPayload {
|
||||
asset: Asset {
|
||||
info: AssetInfo::Token {
|
||||
contract_addr: cw20_contract_addr,
|
||||
},
|
||||
amount: bridging_coin.amount,
|
||||
},
|
||||
recipient_chain: chain,
|
||||
recipient,
|
||||
fee: Uint128::from(0u128),
|
||||
payload,
|
||||
nonce,
|
||||
}
|
||||
}
|
||||
};
|
||||
let initiate_transfer_msg = to_binary(&token_bridge_transfer)
|
||||
.context("could not serialize token bridge initiate_transfer msg")?;
|
||||
response = response.add_message(CosmosMsg::Wasm(WasmMsg::Execute {
|
||||
contract_addr: token_bridge_contract,
|
||||
msg: initiate_transfer_msg,
|
||||
funds: vec![],
|
||||
}));
|
||||
|
||||
Ok(response)
|
||||
}
|
||||
|
||||
pub fn parse_bank_token_factory_contract(
|
||||
deps: DepsMut<WormholeQuery>,
|
||||
env: Env,
|
||||
coin: Coin,
|
||||
) -> Result<String, anyhow::Error> {
|
||||
// extract the contract address from the denom of the token that was sent to us
|
||||
// if the token is not a factory token created by this contract, return error
|
||||
let parsed_denom = coin.denom.split('/').collect::<Vec<_>>();
|
||||
ensure!(
|
||||
parsed_denom.len() == 3
|
||||
&& parsed_denom[0] == "factory"
|
||||
&& parsed_denom[1] == env.contract.address,
|
||||
"coin is not from the token factory"
|
||||
);
|
||||
|
||||
// decode subdenom from base64 => encode as cosmos addr to get contract addr
|
||||
let cw20_contract_addr = contract_addr_from_base58(deps.as_ref(), parsed_denom[2])?;
|
||||
|
||||
// validate that the contract does indeed match the stored denom we have for it
|
||||
let stored_denom = CW_DENOMS
|
||||
.load(deps.storage, cw20_contract_addr.clone())
|
||||
.context(
|
||||
"a corresponding denom for the extracted contract addr is not contained in storage",
|
||||
)?;
|
||||
ensure!(
|
||||
stored_denom == coin.denom,
|
||||
"the stored denom for the contract does not match the actual coin denom"
|
||||
);
|
||||
|
||||
Ok(cw20_contract_addr)
|
||||
}
|
||||
|
||||
pub fn contract_addr_from_base58(
|
||||
deps: Deps<WormholeQuery>,
|
||||
subdenom: &str,
|
||||
) -> Result<String, anyhow::Error> {
|
||||
let decoded_addr = bs58::decode(subdenom)
|
||||
.into_vec()
|
||||
.context(format!("failed to decode base58 subdenom {subdenom}"))?;
|
||||
let canonical_addr = Binary::from(decoded_addr);
|
||||
deps.api
|
||||
.addr_humanize(&canonical_addr.into())
|
||||
.map(|a| a.to_string())
|
||||
.context(format!("failed to humanize cosmos address {subdenom}"))
|
||||
}
|
||||
|
||||
pub fn submit_update_chain_to_channel_map(
|
||||
deps: DepsMut<WormholeQuery>,
|
||||
vaa: Binary,
|
||||
) -> Result<Response<TokenFactoryMsg>, anyhow::Error> {
|
||||
// parse the VAA header and data
|
||||
let (header, data) = serde_wormhole::from_slice::<(Header, &RawMessage)>(&vaa)
|
||||
.context("failed to parse VAA header")?;
|
||||
|
||||
// Must be a version 1 VAA
|
||||
ensure!(header.version == 1, "unsupported VAA version");
|
||||
|
||||
// call into wormchain to verify the VAA
|
||||
deps.querier
|
||||
.query::<Empty>(&WormholeQuery::VerifyVaa { vaa: vaa.clone() }.into())
|
||||
.context("failed to verify vaa")?;
|
||||
|
||||
// parse the VAA body
|
||||
let body = serde_wormhole::from_slice::<Body<&RawMessage>>(data)
|
||||
.context("failed to parse VAA body")?;
|
||||
|
||||
// validate this is a governance VAA
|
||||
ensure!(
|
||||
body.emitter_chain == Chain::Solana
|
||||
&& body.emitter_address == wormhole_sdk::GOVERNANCE_EMITTER,
|
||||
"not a governance VAA"
|
||||
);
|
||||
|
||||
// parse the governance packet
|
||||
let govpacket: GovernancePacket =
|
||||
serde_wormhole::from_slice(body.payload).context("failed to parse governance packet")?;
|
||||
|
||||
// validate the governance VAA is directed to wormchain
|
||||
ensure!(
|
||||
govpacket.chain == Chain::Wormchain || govpacket.chain == Chain::Any,
|
||||
"this governance VAA is for another chain"
|
||||
);
|
||||
|
||||
// governance VAA replay protection
|
||||
let digest = body
|
||||
.digest()
|
||||
.context("failed to compute governance VAA digest")?;
|
||||
|
||||
if VAA_ARCHIVE.has(deps.storage, &digest.hash) {
|
||||
bail!("governance vaa already executed");
|
||||
}
|
||||
VAA_ARCHIVE
|
||||
.save(deps.storage, &digest.hash, &true)
|
||||
.context("failed to save governance VAA to archive")?;
|
||||
|
||||
// match the governance action and execute the corresponding logic
|
||||
match govpacket.action {
|
||||
Action::UpdateChannelChain {
|
||||
channel_id,
|
||||
chain_id,
|
||||
} => {
|
||||
ensure!(
|
||||
chain_id != Chain::Wormchain,
|
||||
"the ibc-translator contract should not maintain channel mappings to wormchain"
|
||||
);
|
||||
|
||||
let channel_id_str =
|
||||
str::from_utf8(&channel_id).context("failed to parse channel-id as utf-8")?;
|
||||
let channel_id_trimmed = channel_id_str.trim_start_matches(char::from(0));
|
||||
|
||||
// update storage with the mapping
|
||||
CHAIN_TO_CHANNEL_MAP
|
||||
.save(
|
||||
deps.storage,
|
||||
chain_id.into(),
|
||||
&channel_id_trimmed.to_string(),
|
||||
)
|
||||
.context("failed to save channel chain")?;
|
||||
Ok(Response::new().add_event(
|
||||
Event::new("UpdateChainToChannelMap")
|
||||
.add_attribute("chain_id", chain_id.to_string())
|
||||
.add_attribute("channel_id", channel_id_trimmed),
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
pub mod contract;
|
||||
pub mod execute;
|
||||
pub mod msg;
|
||||
pub mod query;
|
||||
pub mod reply;
|
||||
pub mod state;
|
|
@ -0,0 +1,86 @@
|
|||
use cosmwasm_schema::{cw_serde, QueryResponses};
|
||||
use cosmwasm_std::{Binary, Uint128};
|
||||
|
||||
pub const COMPLETE_TRANSFER_REPLY_ID: u64 = 1;
|
||||
|
||||
#[cw_serde]
|
||||
pub struct InstantiateMsg {
|
||||
pub token_bridge_contract: String,
|
||||
}
|
||||
|
||||
#[cw_serde]
|
||||
pub enum ExecuteMsg {
|
||||
/// Submit a VAA to complete a wormhole payload3 token bridge transfer.
|
||||
/// This function will:
|
||||
/// 1. complete the wormhole token bridge transfer.
|
||||
/// 2. Lock the newly minted cw20 tokens.
|
||||
/// 3. CreateDenom (if it doesn't already exist)
|
||||
/// 4. Mint an equivalent amount of bank tokens using the token factory.
|
||||
/// 5. Send the minted bank tokens to the destination address with contract payload if applicable.
|
||||
CompleteTransferAndConvert {
|
||||
/// VAA to submit. The VAA should be encoded in the standard wormhole
|
||||
/// wire format.
|
||||
vaa: Binary,
|
||||
},
|
||||
|
||||
/// Convert bank tokens into the equivalent (locked) cw20 tokens and trigger a wormhole token bridge transfer.
|
||||
/// This function will:
|
||||
/// 1. Validate that the bank tokens originated from cw20 tokens that are locked in this contract.
|
||||
/// 2. Burn the bank tokens using the token factory.
|
||||
/// 3. Unlock the equivalent cw20 tokens.
|
||||
/// 4. Cross-call into the wormhole token bridge to initiate a cross-chain transfer with a gateway transfer payload.
|
||||
GatewayConvertAndTransfer {
|
||||
recipient: Binary,
|
||||
chain: u16,
|
||||
fee: Uint128,
|
||||
nonce: u32,
|
||||
},
|
||||
|
||||
/// Convert bank tokens into the equivalent (locked) cw20 tokens and trigger a wormhole token bridge transfer.
|
||||
/// This function will:
|
||||
/// 1. Validate that the bank tokens originated from cw20 tokens that are locked in this contract.
|
||||
/// 2. Burn the bank tokens using the token factory.
|
||||
/// 3. Unlock the equivalent cw20 tokens.
|
||||
/// 4. Cross-call into the wormhole token bridge to initiate a cross-chain transfer with a gateway transfer-with-payload payload.
|
||||
GatewayConvertAndTransferWithPayload {
|
||||
contract: Binary,
|
||||
chain: u16,
|
||||
payload: Binary,
|
||||
nonce: u32,
|
||||
},
|
||||
|
||||
/// Submit a signed VAA to update the on-chain state.
|
||||
SubmitUpdateChainToChannelMap {
|
||||
/// VAA to submit. The VAA should be encoded in the standard wormhole
|
||||
/// wire format.
|
||||
vaa: Binary,
|
||||
},
|
||||
}
|
||||
|
||||
#[cw_serde]
|
||||
#[derive(QueryResponses)]
|
||||
pub enum QueryMsg {
|
||||
#[returns(ChannelResponse)]
|
||||
IbcChannel { chain_id: u16 },
|
||||
}
|
||||
|
||||
#[cw_serde]
|
||||
pub struct ChannelResponse {
|
||||
pub channel: String,
|
||||
}
|
||||
|
||||
#[cw_serde]
|
||||
pub enum GatewayIbcTokenBridgePayload {
|
||||
GatewayTransfer {
|
||||
chain: u16,
|
||||
recipient: Binary,
|
||||
fee: u128,
|
||||
nonce: u32,
|
||||
},
|
||||
GatewayTransferWithPayload {
|
||||
chain: u16,
|
||||
contract: Binary,
|
||||
payload: Binary,
|
||||
nonce: u32,
|
||||
},
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
use cosmwasm_std::{Deps, StdResult};
|
||||
|
||||
use crate::{msg::ChannelResponse, state::CHAIN_TO_CHANNEL_MAP};
|
||||
|
||||
pub fn query_ibc_channel(deps: Deps, chain_id: u16) -> StdResult<ChannelResponse> {
|
||||
let channel = CHAIN_TO_CHANNEL_MAP.load(deps.storage, chain_id)?;
|
||||
|
||||
Ok(ChannelResponse { channel })
|
||||
}
|
|
@ -0,0 +1,213 @@
|
|||
use crate::{
|
||||
msg::GatewayIbcTokenBridgePayload,
|
||||
state::{CHAIN_TO_CHANNEL_MAP, CURRENT_TRANSFER, CW_DENOMS},
|
||||
};
|
||||
use anybuf::Anybuf;
|
||||
use anyhow::{ensure, Context};
|
||||
use cosmwasm_std::{
|
||||
from_binary, to_binary, Binary, CosmosMsg::Stargate, Deps, DepsMut, Env, QueryRequest, Reply,
|
||||
Response, SubMsg, WasmQuery,
|
||||
};
|
||||
use cw20::TokenInfoResponse;
|
||||
use cw20_base::msg::QueryMsg as TokenQuery;
|
||||
use cw_token_bridge::msg::CompleteTransferResponse;
|
||||
use wormhole_bindings::tokenfactory::{DenomUnit, Metadata, TokenFactoryMsg, TokenMsg};
|
||||
|
||||
pub fn handle_complete_transfer_reply(
|
||||
deps: DepsMut,
|
||||
env: Env,
|
||||
msg: Reply,
|
||||
) -> Result<Response<TokenFactoryMsg>, anyhow::Error> {
|
||||
// we should only be replying on success
|
||||
ensure!(
|
||||
msg.result.is_ok(),
|
||||
"msg result is not okay, we should never get here"
|
||||
);
|
||||
|
||||
let res_data_raw = cw_utils::parse_reply_execute_data(msg)
|
||||
.context("failed to parse protobuf reply response_data")?
|
||||
.data
|
||||
.context("no data in the response, we should never get here")?;
|
||||
let res_data: CompleteTransferResponse =
|
||||
from_binary(&res_data_raw).context("failed to deserialize response data")?;
|
||||
let contract_addr = res_data
|
||||
.contract
|
||||
.context("no contract in response, we should never get here")?;
|
||||
|
||||
// load interim state
|
||||
let transfer_info = CURRENT_TRANSFER
|
||||
.load(deps.storage)
|
||||
.context("failed to load current transfer from storage")?;
|
||||
|
||||
// delete interim state
|
||||
CURRENT_TRANSFER.remove(deps.storage);
|
||||
|
||||
// deserialize payload into the type we expect
|
||||
let payload: GatewayIbcTokenBridgePayload = serde_json_wasm::from_slice(&transfer_info.payload)
|
||||
.context("failed to deserialize transfer payload")?;
|
||||
|
||||
match payload {
|
||||
GatewayIbcTokenBridgePayload::GatewayTransfer {
|
||||
chain,
|
||||
recipient,
|
||||
fee: _,
|
||||
nonce: _,
|
||||
} => {
|
||||
let recipient_decoded = String::from_utf8(recipient.to_vec())
|
||||
.context(format!("failed to convert {recipient} to utf8 string"))?;
|
||||
convert_cw20_to_bank_and_send(
|
||||
deps,
|
||||
env,
|
||||
recipient_decoded,
|
||||
res_data.amount.into(),
|
||||
contract_addr,
|
||||
chain,
|
||||
None,
|
||||
)
|
||||
}
|
||||
GatewayIbcTokenBridgePayload::GatewayTransferWithPayload {
|
||||
chain,
|
||||
contract,
|
||||
payload,
|
||||
nonce: _,
|
||||
} => {
|
||||
let contract_decoded = String::from_utf8(contract.to_vec())
|
||||
.context(format!("failed to convert {contract} to utf8 string"))?;
|
||||
convert_cw20_to_bank_and_send(
|
||||
deps,
|
||||
env,
|
||||
contract_decoded,
|
||||
res_data.amount.into(),
|
||||
contract_addr,
|
||||
chain,
|
||||
Some(payload),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
pub fn convert_cw20_to_bank_and_send(
|
||||
deps: DepsMut,
|
||||
env: Env,
|
||||
recipient: String,
|
||||
amount: u128,
|
||||
cw20_contract_addr: String,
|
||||
chain_id: u16,
|
||||
payload: Option<Binary>,
|
||||
) -> Result<Response<TokenFactoryMsg>, anyhow::Error> {
|
||||
deps.api
|
||||
.addr_validate(&cw20_contract_addr)
|
||||
.context(format!("invalid contract address {cw20_contract_addr}"))?;
|
||||
|
||||
// convert contract address into base64
|
||||
let subdenom = contract_addr_to_base58(deps.as_ref(), cw20_contract_addr.clone())?;
|
||||
// format the token factory denom
|
||||
let tokenfactory_denom = "factory/".to_string()
|
||||
+ env.contract.address.to_string().as_ref()
|
||||
+ "/"
|
||||
+ subdenom.as_ref();
|
||||
|
||||
let mut response: Response<TokenFactoryMsg> = Response::new();
|
||||
|
||||
// check contract storage see if we've created a denom for this cw20 token yet
|
||||
// if we haven't created the denom, then create the denom
|
||||
if !CW_DENOMS.has(deps.storage, cw20_contract_addr.clone()) {
|
||||
// call into the cw20 contract to get the token's metadata
|
||||
let request = QueryRequest::Wasm(WasmQuery::Smart {
|
||||
contract_addr: cw20_contract_addr.clone(),
|
||||
msg: to_binary(&TokenQuery::TokenInfo {})?,
|
||||
});
|
||||
let token_info: TokenInfoResponse = deps.querier.query(&request)?;
|
||||
|
||||
// Populate token factory token's metadata from cw20 token's metadata
|
||||
let tf_description = token_info.name.clone()
|
||||
+ ", "
|
||||
+ token_info.symbol.as_str()
|
||||
+ ", "
|
||||
+ tokenfactory_denom.as_str();
|
||||
let tf_denom_unit_base = DenomUnit {
|
||||
denom: tokenfactory_denom.clone(),
|
||||
exponent: 0,
|
||||
aliases: vec![],
|
||||
};
|
||||
let tf_scaled_denom = "wormhole/".to_string()
|
||||
+ subdenom.as_str()
|
||||
+ "/"
|
||||
+ token_info.decimals.to_string().as_str();
|
||||
let tf_denom_unit_scaled = DenomUnit {
|
||||
denom: tf_scaled_denom,
|
||||
exponent: u32::from(token_info.decimals),
|
||||
aliases: vec![],
|
||||
};
|
||||
let tf_metadata = Metadata {
|
||||
description: Some(tf_description),
|
||||
base: Some(tokenfactory_denom.clone()),
|
||||
denom_units: vec![tf_denom_unit_base, tf_denom_unit_scaled],
|
||||
display: Some(tokenfactory_denom.clone()),
|
||||
name: Some(token_info.name),
|
||||
symbol: Some(token_info.symbol),
|
||||
};
|
||||
|
||||
// call into token factory to create the denom
|
||||
let create_denom = SubMsg::new(TokenMsg::CreateDenom {
|
||||
subdenom,
|
||||
metadata: Some(tf_metadata),
|
||||
});
|
||||
response = response.add_submessage(create_denom);
|
||||
|
||||
// add the contract_addr => tokenfactory denom to storage
|
||||
CW_DENOMS
|
||||
.save(deps.storage, cw20_contract_addr, &tokenfactory_denom)
|
||||
.context("failed to save contract_addr => tokenfactory denom to storage")?;
|
||||
}
|
||||
|
||||
// add calls to mint and send bank tokens
|
||||
response = response.add_message(TokenMsg::MintTokens {
|
||||
denom: tokenfactory_denom.clone(),
|
||||
amount,
|
||||
mint_to_address: env.contract.address.to_string(),
|
||||
});
|
||||
|
||||
let channel = CHAIN_TO_CHANNEL_MAP
|
||||
.load(deps.storage, chain_id)
|
||||
.context("chain id does not have an allowed channel")?;
|
||||
|
||||
let payload_decoded = match payload {
|
||||
Some(payload) => String::from_utf8(payload.to_vec())
|
||||
.context(format!("failed to convert {payload} to utf8 string"))?,
|
||||
None => "".to_string(),
|
||||
};
|
||||
|
||||
// Create MsgTransfer protobuf message for Stargate
|
||||
// https://github.com/cosmos/ibc-go/blob/main/proto/ibc/applications/transfer/v1/tx.proto#L27
|
||||
// TimeoutTimestamp is 14 days from now which is the trusting period of the counterparty light client
|
||||
let ibc_msg_transfer = Anybuf::new()
|
||||
.append_string(1, "transfer") // source port
|
||||
.append_string(2, channel) // source channel
|
||||
.append_message(
|
||||
3,
|
||||
&Anybuf::new()
|
||||
.append_string(1, tokenfactory_denom)
|
||||
.append_string(2, amount.to_string()),
|
||||
) // Token
|
||||
.append_string(4, env.contract.address) // sender
|
||||
.append_string(5, recipient) // receiver
|
||||
.append_message(6, &Anybuf::new().append_uint64(1, 0).append_uint64(2, 0)) // TimeoutHeight
|
||||
.append_uint64(7, env.block.time.plus_days(14).nanos()) // TimeoutTimestamp
|
||||
.append_string(8, payload_decoded); // Memo
|
||||
|
||||
response = response.add_message(Stargate {
|
||||
type_url: "/ibc.applications.transfer.v1.MsgTransfer".to_string(),
|
||||
value: ibc_msg_transfer.into_vec().into(),
|
||||
});
|
||||
Ok(response)
|
||||
}
|
||||
|
||||
// Base58 allows the subdenom to be a maximum of 44 bytes (max subdenom length) for up to a 32 byte address
|
||||
fn contract_addr_to_base58(deps: Deps, contract_addr: String) -> Result<String, anyhow::Error> {
|
||||
// convert the contract address into bytes
|
||||
let contract_addr_bytes = deps.api.addr_canonicalize(&contract_addr).context(format!(
|
||||
"could not canonicalize contract address {contract_addr}"
|
||||
))?;
|
||||
let base_58_addr = bs58::encode(contract_addr_bytes.as_slice()).into_string();
|
||||
Ok(base_58_addr)
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
use cw_storage_plus::{Item, Map};
|
||||
use cw_token_bridge::msg::TransferInfoResponse;
|
||||
|
||||
pub const TOKEN_BRIDGE_CONTRACT: Item<String> = Item::new("token_bridge_contract");
|
||||
|
||||
// Holds temp state for the wormhole message that the contract is currently processing
|
||||
pub const CURRENT_TRANSFER: Item<TransferInfoResponse> = Item::new("current_transfer");
|
||||
|
||||
// Maps cw20 address -> bank token denom
|
||||
pub const CW_DENOMS: Map<String, String> = Map::new("cw_denoms");
|
||||
|
||||
pub const CHAIN_TO_CHANNEL_MAP: Map<u16, String> = Map::new("chain_to_channel_map");
|
||||
|
||||
pub const VAA_ARCHIVE: Map<&[u8], bool> = Map::new("vaa_archive");
|
|
@ -26,16 +26,14 @@ pub fn ibc_channel_open(
|
|||
|
||||
if channel.version.as_str() != IBC_APP_VERSION {
|
||||
return Err(StdError::generic_err(format!(
|
||||
"Must set version to `{}`",
|
||||
IBC_APP_VERSION
|
||||
"Must set version to `{IBC_APP_VERSION}`"
|
||||
)));
|
||||
}
|
||||
|
||||
if let Some(counter_version) = msg.counterparty_version() {
|
||||
if counter_version != IBC_APP_VERSION {
|
||||
return Err(StdError::generic_err(format!(
|
||||
"Counterparty version must be `{}`",
|
||||
IBC_APP_VERSION
|
||||
"Counterparty version must be `{IBC_APP_VERSION}`"
|
||||
)));
|
||||
}
|
||||
}
|
||||
|
@ -81,7 +79,7 @@ pub fn ibc_packet_receive(
|
|||
handle_packet_receive(msg).or_else(|e| {
|
||||
// we try to capture all app-level errors and convert them into
|
||||
// acknowledgement packets that contain an error code.
|
||||
let acknowledgement = encode_ibc_error(format!("invalid packet: {}", e));
|
||||
let acknowledgement = encode_ibc_error(format!("invalid packet: {e}"));
|
||||
Ok(IbcReceiveResponse::new()
|
||||
.set_ack(acknowledgement)
|
||||
.add_attribute("action", "ibc_packet_ack"))
|
||||
|
|
|
@ -27,16 +27,14 @@ pub fn ibc_channel_open(
|
|||
|
||||
if channel.version.as_str() != IBC_APP_VERSION {
|
||||
return Err(StdError::generic_err(format!(
|
||||
"Must set version to `{}`",
|
||||
IBC_APP_VERSION
|
||||
"Must set version to `{IBC_APP_VERSION}`"
|
||||
)));
|
||||
}
|
||||
|
||||
if let Some(counter_version) = msg.counterparty_version() {
|
||||
if counter_version != IBC_APP_VERSION {
|
||||
return Err(StdError::generic_err(format!(
|
||||
"Counterparty version must be `{}`",
|
||||
IBC_APP_VERSION
|
||||
"Counterparty version must be `{IBC_APP_VERSION}`"
|
||||
)));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
#[cfg(feature = "fake")]
|
||||
pub mod fake;
|
||||
mod query;
|
||||
pub mod tokenfactory;
|
||||
|
||||
pub use query::*;
|
||||
|
||||
|
|
|
@ -0,0 +1,134 @@
|
|||
use cosmwasm_schema::cw_serde;
|
||||
use cosmwasm_std::{CosmosMsg, CustomMsg};
|
||||
|
||||
/// A top-level Custom message for the token factory.
|
||||
/// It is embedded like this to easily allow adding other variants that are custom
|
||||
/// to your chain, or other "standardized" extensions along side it.
|
||||
#[cw_serde]
|
||||
pub enum TokenFactoryMsg {
|
||||
Token(TokenMsg),
|
||||
}
|
||||
|
||||
/// Special messages to be supported by any chain that supports token_factory
|
||||
#[cw_serde]
|
||||
pub enum TokenMsg {
|
||||
/// CreateDenom creates a new factory denom, of denomination:
|
||||
/// factory/{creating contract bech32 address}/{Subdenom}
|
||||
/// Subdenom can be of length at most 44 characters, in [0-9a-zA-Z./]
|
||||
/// Empty subdenoms are valid.
|
||||
/// The (creating contract address, subdenom) pair must be unique.
|
||||
/// The created denom's admin is the creating contract address,
|
||||
/// but this admin can be changed using the UpdateAdmin binding.
|
||||
///
|
||||
/// If you set an initial metadata here, this is equivalent
|
||||
/// to calling SetMetadata directly on the returned denom.
|
||||
CreateDenom {
|
||||
subdenom: String,
|
||||
metadata: Option<Metadata>,
|
||||
},
|
||||
/// ChangeAdmin changes the admin for a factory denom.
|
||||
/// Can only be called by the current contract admin.
|
||||
/// If the NewAdminAddress is empty, the denom will have no admin.
|
||||
ChangeAdmin {
|
||||
denom: String,
|
||||
new_admin_address: String,
|
||||
},
|
||||
/// Contracts can mint native tokens for an existing factory denom
|
||||
/// that they are the admin of.
|
||||
MintTokens {
|
||||
denom: String,
|
||||
amount: u128,
|
||||
mint_to_address: String,
|
||||
},
|
||||
/// Contracts can burn native tokens for an existing factory denom
|
||||
/// that they are the admin of.
|
||||
BurnTokens {
|
||||
denom: String,
|
||||
amount: u128,
|
||||
burn_from_address: String,
|
||||
},
|
||||
/// Contracts can force transfer tokens for an existing factory denom
|
||||
/// that they are the admin of.
|
||||
ForceTransfer {
|
||||
denom: String,
|
||||
amount: u128,
|
||||
from_address: String,
|
||||
to_address: String,
|
||||
},
|
||||
SetMetadata {
|
||||
denom: String,
|
||||
metadata: Metadata,
|
||||
},
|
||||
}
|
||||
|
||||
impl TokenMsg {
|
||||
pub fn mint_contract_tokens(denom: String, amount: u128, mint_to_address: String) -> Self {
|
||||
TokenMsg::MintTokens {
|
||||
denom,
|
||||
amount,
|
||||
mint_to_address,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn burn_contract_tokens(denom: String, amount: u128, burn_from_address: String) -> Self {
|
||||
TokenMsg::BurnTokens {
|
||||
denom,
|
||||
amount,
|
||||
burn_from_address,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn force_transfer_tokens(
|
||||
denom: String,
|
||||
amount: u128,
|
||||
from_address: String,
|
||||
to_address: String,
|
||||
) -> Self {
|
||||
TokenMsg::ForceTransfer {
|
||||
denom,
|
||||
amount,
|
||||
from_address,
|
||||
to_address,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<TokenMsg> for CosmosMsg<TokenFactoryMsg> {
|
||||
fn from(msg: TokenMsg) -> CosmosMsg<TokenFactoryMsg> {
|
||||
CosmosMsg::Custom(TokenFactoryMsg::Token(msg))
|
||||
}
|
||||
}
|
||||
|
||||
impl CustomMsg for TokenFactoryMsg {}
|
||||
|
||||
/// This maps to cosmos.bank.v1beta1.Metadata protobuf struct
|
||||
#[cw_serde]
|
||||
pub struct Metadata {
|
||||
pub description: Option<String>,
|
||||
/// denom_units represents the list of DenomUnit's for a given coin
|
||||
pub denom_units: Vec<DenomUnit>,
|
||||
/// base represents the base denom (should be the DenomUnit with exponent = 0).
|
||||
pub base: Option<String>,
|
||||
/// display indicates the suggested denom that should be displayed in clients.
|
||||
pub display: Option<String>,
|
||||
/// name defines the name of the token (eg: Cosmos Atom)
|
||||
pub name: Option<String>,
|
||||
/// symbol is the token symbol usually shown on exchanges (eg: ATOM). This can
|
||||
/// be the same as the display.
|
||||
pub symbol: Option<String>,
|
||||
}
|
||||
|
||||
/// This maps to cosmos.bank.v1beta1.DenomUnit protobuf struct
|
||||
#[cw_serde]
|
||||
pub struct DenomUnit {
|
||||
/// denom represents the string name of the given denom unit (e.g uatom).
|
||||
pub denom: String,
|
||||
/// exponent represents power of 10 exponent that one must
|
||||
/// raise the base_denom to in order to equal the given DenomUnit's denom
|
||||
/// 1 denom = 1^exponent base_denom
|
||||
/// (e.g. with a base_denom of uatom, one can create a DenomUnit of 'atom' with
|
||||
/// exponent = 6, thus: 1 atom = 10^6 uatom).
|
||||
pub exponent: u32,
|
||||
/// aliases is a list of string aliases for the given denom
|
||||
pub aliases: Vec<String>,
|
||||
}
|
|
@ -0,0 +1,332 @@
|
|||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::Chain;
|
||||
|
||||
/// Represents a governance action targeted at the wormchain ibc translator contract.
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub enum Action {
|
||||
#[serde(rename = "1")]
|
||||
UpdateChannelChain {
|
||||
// an existing IBC channel ID
|
||||
#[serde(with = "crate::serde_array")]
|
||||
channel_id: [u8; 64],
|
||||
// the chain associated with this IBC channel_id
|
||||
chain_id: Chain,
|
||||
},
|
||||
}
|
||||
|
||||
// MODULE = "IbcTranslator"
|
||||
pub const MODULE: [u8; 32] =
|
||||
*b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00IbcTranslator";
|
||||
|
||||
/// Represents the payload for a governance VAA targeted at the wormchain ibc translator contract.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub struct GovernancePacket {
|
||||
/// Describes the chain on which the governance action should be carried out.
|
||||
pub chain: Chain,
|
||||
|
||||
/// The actual governance action to be carried out.
|
||||
pub action: Action,
|
||||
}
|
||||
|
||||
mod governance_packet_impl {
|
||||
use std::fmt;
|
||||
|
||||
use serde::{
|
||||
de::{Error, MapAccess, SeqAccess, Visitor},
|
||||
ser::SerializeStruct,
|
||||
Deserialize, Deserializer, Serialize, Serializer,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
ibc_translator::{Action, GovernancePacket, MODULE},
|
||||
Chain,
|
||||
};
|
||||
|
||||
struct Module;
|
||||
|
||||
impl Serialize for Module {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
MODULE.serialize(serializer)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for Module {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
let arr = <[u8; 32]>::deserialize(deserializer)?;
|
||||
|
||||
if arr == MODULE {
|
||||
Ok(Module)
|
||||
} else {
|
||||
Err(Error::custom(
|
||||
"invalid governance module, expected \"IbcTranslator\"",
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// governance actions
|
||||
#[derive(Serialize, Deserialize)]
|
||||
struct UpdateChannelChain {
|
||||
#[serde(with = "crate::serde_array")]
|
||||
channel_id: [u8; 64],
|
||||
chain_id: Chain,
|
||||
}
|
||||
|
||||
impl Serialize for GovernancePacket {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
let mut seq = serializer.serialize_struct("GovernancePacket", 4)?;
|
||||
seq.serialize_field("module", &Module)?;
|
||||
|
||||
// The wire format encodes the action before the chain and then appends the actual
|
||||
// action payload.
|
||||
match self.action.clone() {
|
||||
Action::UpdateChannelChain {
|
||||
channel_id,
|
||||
chain_id,
|
||||
} => {
|
||||
seq.serialize_field("action", &1u8)?;
|
||||
seq.serialize_field("chain", &self.chain)?;
|
||||
seq.serialize_field(
|
||||
"payload",
|
||||
&UpdateChannelChain {
|
||||
channel_id,
|
||||
chain_id,
|
||||
},
|
||||
)?;
|
||||
}
|
||||
}
|
||||
|
||||
seq.end()
|
||||
}
|
||||
}
|
||||
|
||||
struct GovernancePacketVisitor;
|
||||
|
||||
impl<'de> Visitor<'de> for GovernancePacketVisitor {
|
||||
type Value = GovernancePacket;
|
||||
|
||||
fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
f.write_str("struct GovernancePacket")
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
|
||||
where
|
||||
A: SeqAccess<'de>,
|
||||
{
|
||||
static EXPECTING: &str = "struct GovernancePacket with 4 elements";
|
||||
|
||||
let _: Module = seq
|
||||
.next_element()?
|
||||
.ok_or_else(|| Error::invalid_length(0, &EXPECTING))?;
|
||||
let act: u8 = seq
|
||||
.next_element()?
|
||||
.ok_or_else(|| Error::invalid_length(1, &EXPECTING))?;
|
||||
let chain = seq
|
||||
.next_element()?
|
||||
.ok_or_else(|| Error::invalid_length(2, &EXPECTING))?;
|
||||
|
||||
let action = match act {
|
||||
1 => {
|
||||
let UpdateChannelChain {
|
||||
channel_id,
|
||||
chain_id,
|
||||
} = seq
|
||||
.next_element()?
|
||||
.ok_or_else(|| Error::invalid_length(3, &EXPECTING))?;
|
||||
|
||||
Action::UpdateChannelChain {
|
||||
channel_id,
|
||||
chain_id,
|
||||
}
|
||||
}
|
||||
v => {
|
||||
return Err(Error::custom(format_args!(
|
||||
"invalid value: {v}, expected 1"
|
||||
)))
|
||||
}
|
||||
};
|
||||
|
||||
Ok(GovernancePacket { chain, action })
|
||||
}
|
||||
|
||||
fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>
|
||||
where
|
||||
A: MapAccess<'de>,
|
||||
{
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
enum Field {
|
||||
Module,
|
||||
Action,
|
||||
Chain,
|
||||
Payload,
|
||||
}
|
||||
|
||||
let mut module = None;
|
||||
let mut chain = None;
|
||||
let mut action = None;
|
||||
let mut payload = None;
|
||||
|
||||
while let Some(key) = map.next_key::<Field>()? {
|
||||
match key {
|
||||
Field::Module => {
|
||||
if module.is_some() {
|
||||
return Err(Error::duplicate_field("module"));
|
||||
}
|
||||
|
||||
module = map.next_value::<Module>().map(Some)?;
|
||||
}
|
||||
Field::Action => {
|
||||
if action.is_some() {
|
||||
return Err(Error::duplicate_field("action"));
|
||||
}
|
||||
|
||||
action = map.next_value::<u8>().map(Some)?;
|
||||
}
|
||||
Field::Chain => {
|
||||
if chain.is_some() {
|
||||
return Err(Error::duplicate_field("chain"));
|
||||
}
|
||||
|
||||
chain = map.next_value().map(Some)?;
|
||||
}
|
||||
Field::Payload => {
|
||||
if payload.is_some() {
|
||||
return Err(Error::duplicate_field("payload"));
|
||||
}
|
||||
|
||||
let a = action.as_ref().copied().ok_or_else(|| {
|
||||
Error::custom("`action` must be known before deserializing `payload`")
|
||||
})?;
|
||||
|
||||
let p = match a {
|
||||
1 => {
|
||||
let UpdateChannelChain {
|
||||
channel_id,
|
||||
chain_id,
|
||||
} = map.next_value()?;
|
||||
|
||||
Action::UpdateChannelChain {
|
||||
channel_id,
|
||||
chain_id,
|
||||
}
|
||||
}
|
||||
v => {
|
||||
return Err(Error::custom(format_args!(
|
||||
"invalid action: {v}, expected one of: 1, 2"
|
||||
)))
|
||||
}
|
||||
};
|
||||
|
||||
payload = Some(p);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let _ = module.ok_or_else(|| Error::missing_field("module"))?;
|
||||
let chain = chain.ok_or_else(|| Error::missing_field("chain"))?;
|
||||
let action = payload.ok_or_else(|| Error::missing_field("payload"))?;
|
||||
|
||||
Ok(GovernancePacket { chain, action })
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for GovernancePacket {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
const FIELDS: &[&str] = &["module", "action", "chain", "payload"];
|
||||
deserializer.deserialize_struct("GovernancePacket", FIELDS, GovernancePacketVisitor)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use crate::{vaa::Signature, Chain, Vaa, GOVERNANCE_EMITTER};
|
||||
|
||||
use super::{Action, GovernancePacket};
|
||||
|
||||
#[test]
|
||||
fn happy_path() {
|
||||
let buf = [
|
||||
// version
|
||||
0x01, // guardian set index
|
||||
0x00, // signatures
|
||||
0x00, 0x00, 0x00, 0x01, 0x00, 0xb0, 0x72, 0x50, 0x5b, 0x5b, 0x99, 0x9c, 0x1d, 0x08,
|
||||
0x90, 0x5c, 0x02, 0xe2, 0xb6, 0xb2, 0x83, 0x2e, 0xf7, 0x2c, 0x0b, 0xa6, 0xc8, 0xdb,
|
||||
0x4f, 0x77, 0xfe, 0x45, 0x7e, 0xf2, 0xb3, 0xd0, 0x53, 0x41, 0x0b, 0x1e, 0x92, 0xa9,
|
||||
0x19, 0x4d, 0x92, 0x10, 0xdf, 0x24, 0xd9, 0x87, 0xac, 0x83, 0xd7, 0xb6, 0xf0, 0xc2,
|
||||
0x1c, 0xe9, 0x0f, 0x8b, 0xc1, 0x86, 0x9d, 0xe0, 0x89, 0x8b, 0xda, 0x7e, 0x98, 0x01,
|
||||
// timestamp
|
||||
0x00, 0x00, 0x00, 0x01, // nonce
|
||||
0x00, 0x00, 0x00, 0x01, // emitter chain
|
||||
0x00, 0x01, // emitter address
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x04, // sequence
|
||||
0x00, 0x00, 0x00, 0x00, 0x01, 0x3c, 0x1b, 0xfa, // consistency
|
||||
0x00, // module = "IbcTranslator"
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x49, 0x62, 0x63, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x6c,
|
||||
0x61, 0x74, 0x6f, 0x72, // action (IbcReceiverActionUpdateChannelChain)
|
||||
0x01, // target chain_id (unset)
|
||||
0x00, 0x00, // IBC channel_id for the mapping ("channel-0")
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x63,
|
||||
0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x2d, 0x30, // IBC chain_id for the mapping
|
||||
0x00, 0x13,
|
||||
];
|
||||
|
||||
let channel_id_bytes: [u8; 64] =
|
||||
*b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00channel-0";
|
||||
|
||||
let vaa = Vaa {
|
||||
version: 1,
|
||||
guardian_set_index: 0,
|
||||
signatures: vec![Signature {
|
||||
index: 0,
|
||||
signature: [
|
||||
0xb0, 0x72, 0x50, 0x5b, 0x5b, 0x99, 0x9c, 0x1d, 0x08, 0x90, 0x5c, 0x02, 0xe2,
|
||||
0xb6, 0xb2, 0x83, 0x2e, 0xf7, 0x2c, 0x0b, 0xa6, 0xc8, 0xdb, 0x4f, 0x77, 0xfe,
|
||||
0x45, 0x7e, 0xf2, 0xb3, 0xd0, 0x53, 0x41, 0x0b, 0x1e, 0x92, 0xa9, 0x19, 0x4d,
|
||||
0x92, 0x10, 0xdf, 0x24, 0xd9, 0x87, 0xac, 0x83, 0xd7, 0xb6, 0xf0, 0xc2, 0x1c,
|
||||
0xe9, 0x0f, 0x8b, 0xc1, 0x86, 0x9d, 0xe0, 0x89, 0x8b, 0xda, 0x7e, 0x98, 0x01,
|
||||
],
|
||||
}],
|
||||
timestamp: 1,
|
||||
nonce: 1,
|
||||
emitter_chain: Chain::Solana,
|
||||
emitter_address: GOVERNANCE_EMITTER,
|
||||
sequence: 20_716_538,
|
||||
consistency_level: 0,
|
||||
payload: GovernancePacket {
|
||||
chain: Chain::Any,
|
||||
action: Action::UpdateChannelChain {
|
||||
channel_id: channel_id_bytes,
|
||||
chain_id: Chain::Injective,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
assert_eq!(buf.as_ref(), &serde_wormhole::to_vec(&vaa).unwrap());
|
||||
assert_eq!(vaa, serde_wormhole::from_slice(&buf).unwrap());
|
||||
|
||||
let encoded = serde_json::to_string(&vaa).unwrap();
|
||||
assert_eq!(vaa, serde_json::from_str(&encoded).unwrap());
|
||||
}
|
||||
}
|
|
@ -20,6 +20,7 @@ mod arraystring;
|
|||
mod chain;
|
||||
pub mod core;
|
||||
pub mod ibc_receiver;
|
||||
pub mod ibc_translator;
|
||||
pub mod nft;
|
||||
mod serde_array;
|
||||
pub mod token;
|
||||
|
|
Loading…
Reference in New Issue