#wasm-bindings #router #chain #blockchain #testing #sudo-msg #router-msg

router-wasm-bindings

为 Router 区块链的 CustomMsg 和 CustomQuery 提供绑定

31 个版本 (4 个稳定版)

新版本 1.0.3 2024年8月22日
1.0.0 2024年7月22日
0.4.0 2024年6月18日
0.3.2 2024年3月8日
0.1.11 2022年11月16日

#65 in 魔法豆

Download history 5/week @ 2024-05-25 6/week @ 2024-06-01 132/week @ 2024-06-08 190/week @ 2024-06-15 8/week @ 2024-06-22 135/week @ 2024-07-20 148/week @ 2024-07-27 65/week @ 2024-08-03 1/week @ 2024-08-10 115/week @ 2024-08-17

每月下载量 335

Apache-2.0

34KB
497

Router-wasm-bindings

为在 Router 链上运行的 CosmWasm 智能合约提供的 wasm 绑定。

先决条件

开始之前,请确保您已安装 rustup 以及最新版本的 rustccargo。目前,我们正在测试 1.62.1+。

并且您还需要安装 wasm32-unknown-unknown 目标。

您可以通过以下方式检查:

rustc --version
cargo --version
rustup target list --installed
# if wasm32 is not listed above, run this
rustup target add wasm32-unknown-unknown

上下文

在 Router 链上,我们可以构建两种类型的合约。

  • 不与跨链合约交互的合约。
  • 来回发送数据请求到跨链合约的合约。

要构建与其他链交互的第二种合约,用户/应用程序需要实现 router-wasm-binding crate。

# add the following line in the cargo.toml [dependencies] section
router-wasm-bindings = "1.0.3"

如何使用 Router-Wasm-Bindings

为了实现跨链互操作性,合约需要实现以下功能

  • HandleIReceive 用于处理来自其他链的请求
  • HandleIAck 向其他链发送请求。

合约可以在传入请求和传出请求之间写入中间业务逻辑。在编写中间业务逻辑时,开发人员可以将单个或多个传入请求转换为单个或多个传出请求。

此外,在创建发送到其他链的请求时,合约可以被开发成生成针对不同链的多个请求。

您可以在 cw-bridge-contracts 仓库中找到不同场景的示例。

[sudo消息]

SudoMsg 是一个枚举,它有两种不同的消息类型。

  1. HandleIReceive
  2. HandleIAck

在下面的代码片段中,我们在SudoMsg的字段级别添加了详细信息。这将帮助我们理解将要传入的入站请求或出站确认请求中的数据。

#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum SudoMsg {
    // Sudo msg to handle incoming requests from other chains
    HandleIReceive {
        // the inbound initiator application contract address 
        request_sender: String,
        // inbound request src chain id
        source_chain_id: String,
        // inbound request event nonce
        request_identifier: u64,
        // the inbound request instructions in base64 format
        payload: Binary,
    },
    // Sudo msg to handle outbound message acknowledgment
    HandleIAck {
        // cross-chain request nonce
        request_identifier: u64,
        // cross-chain request contract call execution status
        exec_flag: u64,
        // cross-chain request contract call execution 
        exec_data: Binary,
        // excess fee refunded amount
        refund_amount: Coin,
    },
}

sudo函数是cosmwasm合约中的一个入口点。它只能由链内部调用。在Router Chain中,开发者需要实现此sudo函数以接收传入的请求。以下代码片段展示了sudo函数的示例实现。

开发者可以在handle_sudo_requesthandle_sudo_ack函数中实现任何类型的业务逻辑。

// import router binding message
use router_wasm_bindings::{RouterMsg, SudoMsg};

#[cfg_attr(not(feature = "library"), entry_point)]
pub fn sudo(deps: DepsMut, _env: Env, msg: SudoMsg) -> StdResult<Response<RouterMsg>> {
    match msg {
        // Sudo msg to handle incoming requests from other chains
        SudoMsg::HandleIReceive {
            request_sender,
            src_chain_id,
            request_identifier,
            payload,
        } => handle_sudo_request(
            deps,
            env,
            request_sender,
            src_chain_id,
            request_identifier,
            payload,
        ),
        // Sudo msg to handle outbound message acknowledgment
        SudoMsg::HandleIAck {
            request_identifier,
            exec_flag,
            exec_data,
            refund_amount,
        } => handle_sudo_ack(
            deps,
            env,
            request_identifier,
            exec_flag,
            exec_data,
            refund_amount,
        ),
    }
}

sudo消息HandleIReceive包含4个参数。当有入站请求发送到您的中间件合约时,会调用此sudo函数。我们可以以任何可能的方式处理此sudo请求,甚至忽略它。如代码片段所示,已创建一个名为handle_sudo_request的函数来处理cosmwasm合约中的传入请求。在此函数内部,您可以在为目的地链创建请求之前,对来自入站请求的有效负载应用任何逻辑。每个字段在HandleIReceive请求中都有其自身的目的和含义。

  1. request_sender: 发送请求到Router链的源链上的应用程序合约地址。
  2. source_chain_id:发起向Router链的入站请求的链的链ID。
  3. request_identifier:请求标识符是源链网关合约添加的请求的唯一标识符。
  4. payload:来自源链合约的有效负载。

sudo消息HandleIAck有4个参数。当目的地链上的合约执行了合约调用后,Router链上的中间件合约收到确认时,会调用此sudo函数。我们可以以任何可能的方式处理此sudo请求,甚至忽略它。如代码片段所示,已创建一个名为handle_sudo_ack的函数来处理cosmwasm合约中的传入确认请求。每个字段在HandleIAck请求中都有其自身的目的和含义。

  1. request_identifier:出站请求的唯一递增整数值。
  2. exec_flag:在目的地链上执行的合约调用的执行状态标志。
  3. exec_data:在目的地链上执行的所有请求的执行数据。
  4. refund_amount:退款金额是我们为目的地侧合约执行而传递的额外费用。

[RouterMsg]

RouterMsg是router-wasm-bindings中的枚举类型。它包含一种自定义消息类型。

  1. CrosschainCall

在下面的代码片段中,我们添加了CrosschainCall的一个实现。此消息用于创建出站请求。在出站请求中,我们可以指定目的地链ID和类型、合约地址和指令、请求过期时间戳、原子性标志等。

// import router binding message
use router_wasm_bindings::{RouterMsg, SudoMsg};
use router_wasm_bindings::types::{
    AckType, RequestMetaData,
};
use cosmwasm_std::{SubMsg, SubMsgResult, Uint128};

let request_packet: Bytes = encode(&[
    Token::String(destination_address.clone()),
    Token::Bytes(payload),
]);
let request_metadata: RequestMetaData = RequestMetaData {
    dest_gas_limit: gas_limit,
    dest_gas_price: gas_price,
    ack_gas_limit: 300_000,
    ack_gas_price: 10_000_000,
    relayer_fee: Uint128::zero(),
    ack_type: AckType::AckOnBoth,
    is_read_call: false,
    asm_address: String::default(),
};

let i_send_request: RouterMsg = RouterMsg::CrosschainCall {
    version: 1,
    route_amount,
    route_recipient,
    dest_chain_id: destination_chain_id,
    request_metadata: request_metadata.get_abi_encoded_bytes(),
    request_packet,
};

let cross_chain_sub_msg: SubMsg<RouterMsg> = SubMsg {
    id: CREATE_OUTBOUND_REPLY_ID,
    msg: i_send_request.into(),
    gas_limit: None,
    reply_on: ReplyOn::Success,
};
let res = Response::new()
    .add_submessage(cross_chain_sub_msg.into())
Ok(res)

CrosschainCall是一种数据类型,帮助最终用户向任何目的地链创建跨链请求。它有6个参数。

  1. version:创建从Router链出发的出站请求的链的链类型。
  2. route_amount:需要在路由链上燃烧并在目的地链上铸造/解锁的路由代币数量。
  3. route_recipient:目的地链上路由代币的收款人地址。
  4. destination_chain_id:创建从Router链出发的出站请求的链的链ID。
  5. request_metadata:请求元数据是包含信息目的气限制和价格、确认气限制和价格、中继费、确认类型、是否读取调用和asm地址的编码打包信息。
  6. request_packet:请求包是包含目的地址和有效负载的编码信息。在示例中,我们可以看到我们如何编码这些信息。

由于应用程序开发人员正在编写应用程序中间件合约,他们将完全控制有效负载中接收到的数据类型。他们可以相应地定义数据的编码和解码,并对数据进行任何操作。

编译和运行测试

现在您已创建了自定义合约,在做出任何更改之前,请确保您能编译并运行它。进入存储库并执行

# this will produce a wasm build in ./target/wasm32-unknown-unknown/release/YOUR_NAME_HERE.wasm
cargo wasm

# this runs unit tests with helpful backtraces
RUST_BACKTRACE=1 cargo unit-test

# auto-generate json schema
cargo schema

理解测试

主要代码在 src/contract.rs 中,那里的单元测试在纯 Rust 中运行,这使得它们执行速度非常快,并在失败时提供很好的输出,尤其是在您执行 RUST_BACKTRACE=1 cargo unit-test

我们认为测试对区块链上的任何事物都至关重要,并建议始终保持测试更新。

生成 JSON Schema

虽然 Wasm 调用(instantiateexecutequery)接受 JSON,但这不足以使用。我们需要向客户端公开预期消息的架构。您可以通过调用 cargo schema 生成此架构,它将在 ./schema 中输出 3 个文件,对应于合约接受的 3 种消息类型。

这些文件是标准 json-schema 格式,应该可以被各种客户端工具使用,无论是用于自动生成编解码器,还是只是根据定义的架构验证传入的 JSON。

准备生产 Wasm 字节码

在我们将其上传到链之前,我们需要确保输出大小尽可能小,因为这将包含在事务体中。我们还希望有一个可重复的构建过程,以便第三方可以验证上传的 Wasm 代码确实来自声明的 Rust 代码。

为了解决这两个问题,我们已生成 rust-optimizer,一个用于产生极其小且一致构建输出的 Docker 镜像。建议的运行方式是

docker run --rm -v "$(pwd)":/code \
  --mount type=volume,source="$(basename "$(pwd)")_cache",target=/code/target \
  --mount type=volume,source=registry_cache,target=/usr/local/cargo/registry \
  cosmwasm/rust-optimizer:0.12.6

或者,如果您在 arm64 机器上,您应该使用用 arm64 构建的 Docker 镜像。

docker run --rm -v "$(pwd)":/code \
  --mount type=volume,source="$(basename "$(pwd)")_cache",target=/code/target \
  --mount type=volume,source=registry_cache,target=/usr/local/cargo/registry \
  cosmwasm/rust-optimizer-arm64:0.12.6

我们必须将合约代码挂载到 /code。如果您不想首先进入目录,可以使用绝对路径而不是 $(pwd)。其他两个卷对于加速很有用。将 /code/target 挂载尤其有用,可以避免 Docker 使用 root 权限覆盖您的本地开发文件。注意 /code/target 缓存对于每个编译的合约是唯一的,以限制干扰,而注册表缓存是全局的。

这与本地编译相比要慢得多,尤其是给定合约的第一个编译。使用两个卷缓存对于加速同一合约的后续编译非常有用。

这将生成一个包含 artifacts 目录,其中包含 PROJECT_NAME.wasm 文件,以及包含 wasm 文件 Sha256 哈希的 checksums.txt 文件。wasm 文件是确定性编译的(任何在相同 git 提交上运行相同 docker 的人都应该获得相同 Sha256 哈希的相同文件)。它还被精简和最小化,以便上传到区块链(我们还会在上传过程中使用 gzip 进行压缩,使其变得更小)。

依赖项

~3.5–5.5MB
~118K SLoC