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 魔法豆
每月下载量 335
34KB
497 行
Router-wasm-bindings
为在 Router 链上运行的 CosmWasm 智能合约提供的 wasm 绑定。
先决条件
开始之前,请确保您已安装 rustup 以及最新版本的 rustc
和 cargo
。目前,我们正在测试 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 是一个枚举,它有两种不同的消息类型。
- HandleIReceive
- 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_request和handle_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
请求中都有其自身的目的和含义。
- request_sender: 发送请求到Router链的源链上的应用程序合约地址。
- source_chain_id:发起向Router链的入站请求的链的链ID。
- request_identifier:请求标识符是源链网关合约添加的请求的唯一标识符。
- payload:来自源链合约的有效负载。
sudo消息HandleIAck
有4个参数。当目的地链上的合约执行了合约调用后,Router链上的中间件合约收到确认时,会调用此sudo函数。我们可以以任何可能的方式处理此sudo请求,甚至忽略它。如代码片段所示,已创建一个名为handle_sudo_ack
的函数来处理cosmwasm合约中的传入确认请求。每个字段在HandleIAck
请求中都有其自身的目的和含义。
- request_identifier:出站请求的唯一递增整数值。
- exec_flag:在目的地链上执行的合约调用的执行状态标志。
- exec_data:在目的地链上执行的所有请求的执行数据。
- refund_amount:退款金额是我们为目的地侧合约执行而传递的额外费用。
[RouterMsg]
RouterMsg是router-wasm-bindings中的枚举类型。它包含一种自定义消息类型。
- 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个参数。
- version:创建从Router链出发的出站请求的链的链类型。
- route_amount:需要在路由链上燃烧并在目的地链上铸造/解锁的路由代币数量。
- route_recipient:目的地链上路由代币的收款人地址。
- destination_chain_id:创建从Router链出发的出站请求的链的链ID。
- request_metadata:请求元数据是包含信息目的气限制和价格、确认气限制和价格、中继费、确认类型、是否读取调用和asm地址的编码打包信息。
- 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 调用(instantiate
、execute
、query
)接受 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