当前位置: 首页 > 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.lryc.cn/news/603200.html

相关文章:

  • 接口自动化测试pytest框架
  • c++-list
  • 【VOS虚拟操作系统】未来之窗打包工具在前端资源优化中的应用与优势分析——仙盟创梦IDE
  • Redis内存使用耗尽情况分析
  • 40+个常用的Linux指令——下
  • 艾利特机器人:光伏机器人如何重塑清洁能源制造新格局
  • 【CDH】CDH环境中升级ZooKeeper的实战记录
  • 基于KMeans、AgglomerativeClustering、DBSCAN、PCA的聚类分析的区域经济差异研究
  • 【Linux知识】Linux Shell 脚本中的 `set -ex` 命令深度解析
  • 复现cacti的RCE(CVE-2022-46169)
  • Go 客户端玩转 ES|QL API 直连与 Mapping Helpers 实战详解
  • upload-labs靶场通关(1-12)
  • 服务器之光:Nginx--反向代理模块详解及演练
  • 图论:Bellman_ford算法
  • 《汇编语言:基于X86处理器》第10章 结构和宏(3)
  • 【WRF-Chem 实例1】namelist.input 详解- 模拟CO2
  • 鸿蒙Harmony-自定义List组件,解决List组件手势滑动点击卡住问题
  • 【图像噪点消除】——图像预处理(OpenCV)
  • 创建型设计模式-工厂方法模式和抽象工厂方法模式
  • 社区老人健康信息管理系统|基于springboot社区老人健康信息管理系统设计与实现(源码+数据库+文档)
  • Gartner发布CTEM指南:使用持续威胁暴露管理来减少网络攻击
  • 智能体安全与可信AI:防护机制与伦理考量
  • 利用 C# 实现 Word 文档多维度统计(字数、字符数、页数、段落数、行数)
  • macOS “Sploitlight“漏洞曝光:攻击者可窃取Apple Intelligence缓存数据
  • FreeRTOS在中断上下文中设置事件组,调度很慢的的解决方法
  • JavaWeb 入门:CSS 基础与实战详解(Java 开发者视角)
  • 如何在在NPM发布一个React组件
  • pycharm中安装pythonocc
  • 队列算法之【用队列实现栈】
  • 【Android】三种弹窗 Fragment弹窗管理