当前位置: 首页 > news >正文

在 PolkaVM 上用 Rust 实现 ERC20 合约的全流程开发指南

图片

PolkaVM 是 Polkadot 2.0 的关键组件,提供了一个在 RISC-V 上的执行环境。对于 Solidity 开发者来说,Polkadot 提供了 Revive,一个将 Solidity 代码编译成 PolkaVM 字节码的工具。我们已经成功测试了许多 Solidity 项目,包括 Uniswap V2,展示了成功的部署和测试。除了这个开发流程,也可以用任何语言编写合约,然后将它们编译成 PolkaVM 字节码。

本文使用这个 repo(🔗 链接:https://github.com/papermoonio/polkavm-erc20-in-rust) 作为示例,来说明如何在 Rust 中实现一个与 ERC20 接口兼容的 ERC20 合约。开发者在理解了仓库中的源代码后,可以用 Rust 实现合约。

1.png

环境安装

按照 README.md 中的说明安装所需的环境。首先,你需要安装 Rust 来构建代码。然后,polkatool 用于链接二进制文件。我们使用 TypeScript 来测试合约的部署和交互,因此 Node.js、TypeScript 和 Yarn 也是必需的。

2.png

项目结构

这是一个典型的 Rust 项目,但目标是 RISC-V。你应该检查 `.cargo` 文件夹中的 `config.toml` 文件。这个配置允许我们使用不同的目标来编译代码。

[build]target = "riscv64emac-unknown-none-polkavm.json"[unstable]build-std = ["core", "alloc"]build-std-features = ["panic_immediate_abort"]

`"riscv64emac-unknown-none-polkavm.json"` 目标文件位于仓库中,定义了编译参数,例如使用的 LLVM 和链接器。

`build.sh` 脚本用于构建项目并调用 `polkatool link` 来生成 PolkaVM 字节码。

用 TypeScript 编写的部署和测试代码位于 `ts` 文件夹中。

3.jpg

构造函数

让我们看一下 `erc20.rs`。有一个 `deploy` 函数,它充当 Solidity 中的构造函数。为了避免在合约中引入编码/解码代码,我们使用 `ethabi` 库来解码构造函数参数。这些参数包括两个字符串(名称和符号),以及两个 `Uint256` 值(小数位数和总供应量)。

#[no_mangle]#[polkavm_derive::polkavm_export]pub extern "C" fn deploy() {    input!(data: &[u8; 256],);    let mut sender = [0_u8; 20];    api::origin(&mut sender);    let param_types = &[        ParamType::String,        ParamType::String,        ParamType::Uint(256),        ParamType::Uint(256),    ];    let decode_result = decode(param_types, &data[..]).unwrap();    if let (        Token::String(name),        Token::String(symbol),        Token::Uint(decimals),        Token::Uint(total_supply),    ) = (        &decode_result,        &decode_result,                &decode_result,        &decode_result,    ) {        set_string(NAME_LENGTH, NAME, name.as_bytes());        set_string(SYMBOL_LENGTH, SYMBOL, symbol.as_bytes());        let mut data = [0_u8; 32];        decimals.to_big_endian(&mut data);        api::set_storage(StorageFlags::empty(), DECIMALS, &data);        let supply = U256::from(10).pow(*decimals).saturating_mul(*total_supply);        supply.to_big_endian(&mut data);        api::set_storage(StorageFlags::empty(), TOTAL_SUPPLY, &data);        api::set_storage(StorageFlags::empty(), &get_balance_key(&sender), &data);    } else {        panic!("Failed to decode input data");    }}

在解析了这四个参数后,合约通过调用 `api::set_storage` 将它们存储在区块链中。这些参数有四个键,因为区块链将所有内容存储为键值对。

const NAME: &[u8] = b"name";const SYMBOL: &[u8] = b"symbol";const NAME_LENGTH: &[u8] = b"name_length";const SYMBOL_LENGTH: &[u8] = b"symbol_length";const DECIMALS: &[u8] = b"decimals";const TOTAL_SUPPLY: &[u8] = b"total_supply";

还有两个额外的键用于存储名称和符号的长度。这允许我们在用户从合约获取数据时使用正确大小的缓冲区来检索字符串。

4.png

内存分配

在合约实现中,我们使用了像 `String` 和 `Vec` 这样的动态大小数据结构。为了避免从 `std` 中进行分配,合约使用了 PolkaVM 仓库中的一个简单分配器。这允许我们在合约中使用任意大小的字符串和 `Vec<u8>`。与基于固定 256 字节长度堆栈的 EVM 中的动态数据存储相比,PolkaVM 更加节省存储空间。

use simplealloc::SimpleAlloc;#[global_allocator]pub static mut GLOBAL: SimpleAlloc<{ 1024 * 10 }> = SimpleAlloc::new();[dependencies]simplealloc = { version = "0.23.0", git = "https://github.com/paritytech/polkavm.git" }

图片

Call

合约部署后,唯一的入口点是 `Call` 函数。为了模拟 Solidity 中的 Calldata,合约将前 4 个字节作为选择器。然后,它将这个选择器与不同函数(如 `name`、`transfer`、`allowance` 等)的选择器进行比较。如果一个函数有参数,合约会从输入中读取更多内容,并使用 `ethabi` 库中的 `decode` 函数来解析它们。

#[no_mangle]#[polkavm_derive::polkavm_export]pub extern "C" fn call() {    input!(selector: &[u8; 4],);    let length = api::call_data_size();    if length > 256 {        panic!("Input data too long");    }    let mut sender = [0_u8; 20];    api::origin(&mut sender);       match selector {        &NAME_SELECTOR => {            api::return_value(ReturnFlags::empty(), &get_string(NAME_LENGTH, NAME)[..])        }        &SYMBOL_SELECTOR => {            api::return_value(ReturnFlags::empty(), &get_string(SYMBOL_LENGTH, SYMBOL)[..])        }        &DECIMALS_SELECTOR => {            let mut data = [0_u8; 32];            let _ = api::get_storage(StorageFlags::empty(), DECIMALS, &mut &mut data[..]);            api::return_value(ReturnFlags::empty(), &data[..])        }        &TOTAL_SUPPLY_SELECTOR => {            let mut data = [0_u8; 32];            let _ = api::get_storage(StorageFlags::empty(), TOTAL_SUPPLY, &mut &mut data[..]);            api::return_value(ReturnFlags::empty(), &data[..])        }        &BALANCE_OF_SELECTOR => {            input!(buffer: &[u8; 4 + 32],);            let param_types = &[ParamType::Address];            let decode_result = decode(param_types, &buffer[4..]).unwrap();            if let Token::Address(address) = &decode_result {                let mut data = [0_u8; 32];                let _ = api::get_storage(                    StorageFlags::empty(),                    &get_balance_key(&address.to_fixed_bytes()),                    &mut &mut data[..],                );                api::return_value(ReturnFlags::empty(), &data[..])                                                  } else {                panic!("Failed to decode input data");            }        }        // ...    }}

图片

部署和测试

Rust 代码完成后,我们可以通过运行 `build.sh` 来生成 PolkaVM 代码。在 `ts` 文件夹中,有一个基于 ethers.js 的简单应用程序,它连接到 Westend Asset Hub ETH RPC 端点。这个应用程序部署合约并测试所有的 ERC20 接口。

连接和合约部署

const url = "https://westend-asset-hub-eth-rpc.polkadot.io";    const provider = new ethers.JsonRpcProvider(url);    config();    let privateKey = process.env.AH_PRIV_KEY || "";    const walletClient = new Wallet(privateKey, provider);    const contractAddress = await deploy(provider, walletClient)    const contract = new Contract(        contractAddress,        ABI,        walletClient,    );

检查 ERC20 中的所有存储,

例如 `name`、`symbol` 和 `balance` 等

const name = await contract.name();    const symbol = await contract.symbol();    const decimals = await contract.decimals();    const totalSupply = await contract.totalSupply();    const balance = await contract.balanceOf(walletAddress);    const allowance = await contract.allowance(walletAddress, recipientAddress);

调用 transfer、approve、

transferFrom 并检查余额

const transferAmount = BigInt(1e18);    const transferTx = await contract.transfer(recipientAddress, transferAmount);    await transferTx.wait();    const myBalanceAfterTransfer = await contract.balanceOf(walletAddress);const approveAmount = BigInt(2e18);    const approveTx = await contract.approve(recipientAddress, approveAmount);    await approveTx.wait();    const approveAllowance = await contract.allowance(        walletAddress,        recipientAddress,    );const transferFromTx = await contract2.transferFrom(        walletAddress,        walletAddress,        approveAmount / BigInt(2),    );    await transferFromTx.wait();    const allowanceAfterTransferFrom = await contract.allowance(           walletAddress,        recipientAddress,    );

在整个测试过程中,一切都与 Solidity 中的标准 ERC20 合约相同。

图片

总结

PolkaVM 作为一个通用的合约平台,可以使用很多语言来实现合约。这个 repo 是一个供开发者尝试用 Rust 实现合约的示例。

免责声明:由PaperMoon提供并包含在本文中的材料仅用于学习目的。它们不构成财务或投资建议,也不应被解读为任何商业决策的指导。我们建议读者在做出任何投资或商业相关的决定之前,进行独立研究并咨询专业人士。PaperMoon对根据本文内容采取的任何行动不承担任何责任。

http://www.dtcms.com/a/304306.html

相关文章:

  • 三维扫描相机:工业自动化的智慧之眼——迁移科技赋能智能制造新纪元
  • Element Plus常见基础组件(一)
  • 白玩 一 记录retrofit+okhttp+flow 及 kts的全局配置
  • Javaweb - 13 - AJAX
  • 《P5960 【模板】差分约束》
  • LeetCode Hot 100:11. 盛最多水的容器
  • Vulnhub 02-Breakout靶机渗透攻略详解
  • 牛顿拉夫逊法PQ分解法计算潮流MATLAB程序计算模型。
  • 【AI论文】Yume:一种交互式世界生成模型
  • Docker网络技术深度研究与实战手册
  • C++与C#实战:FFmpeg屏幕录制开发指南
  • 2025年KBS顶刊新算法-向光优化算法Phototropic growth algorithm-附Matlab免费代码
  • 从线下挂号到全流程智能问诊:智慧医院APP源码开发指南
  • MATLAB弹塑性固体有限元计算程序
  • 【LGR-234-Div.3】洛谷网校 7 月 CSP-J 模拟月赛 Cfz Round 6 「Cfz Round 6」Imaichi
  • 【PHP】通过IP获取IP所在地理位置(免费API接口)
  • Kruskal算法
  • gTest测试框架的安装与配置
  • HammerDB:一款免费开源的数据库基准测试工具
  • YOLOv11.pt 模型转换为 TFLite 和 NCNN 模型
  • PDF转Word免费工具!批量处理PDF压缩,合并, OCR识别, 去水印, 签名等全功能详解
  • CodeRush AI 助手进驻 Visual Studio:AiGen/AiFind 亮相(三)
  • Visual Studio的妙用
  • [极客大挑战 2019]FinalSQL
  • 如何查询并访问路由器的默认网关(IP地址)?
  • 大规模矩阵构建与高级算法应用
  • Unity 编辑器开发 之 Excel导表工具
  • Python爬虫01_Requests第一血获取响应数据
  • 香橙派One安装OctoPrint 实现控制3D打印机
  • WebRTC 2025全解析:从技术原理到商业落地