ethers.js与solidity智能合约交互(hardhat项目)

1、test脚本中如何获取合约中的状态变量

//合约中public类型的状态变量支持getter()特性,可以直接使用部署合约的实例调用如:vault.token()
contract Vault {//这里的token属性是public,自带getter()方法IERC20 public immutable token;uint256 public totalSupply;mapping(address => uint256) public balanceOf;constructor(address _token) {token = IERC20(_token);}

2、test脚本中环境设置(包括部署合约、获取账户信息及创建合约实例)

//这行代码是获取合约部署的相关信息,包含abi、address等等
const tokenDeployment = await deployments.get("Mytoken");

3、当前合约部署脚本获取之前合约的地址

//当前合约中设置变量,获取之前已经部署的合约的deployment
const tokenDeployment = await deployments.get("MyToken");
//通过deloyment.address获取合约地址
const tokenAddr = await tokenDeployment.address;

4、一个完整的部署脚本(参考用02_deploy_pool_lock_and_release.js)

const{ getNamedAccounts } = require("hardhat")
moudle.exports = async({getNamedAccounts, deployments}) => {const {firstAccount} = getNameAccounts()const {deploy,log} = deploymentslog("NFTPoolLockAndRelease contract deploying...")//合约部署需要参数_router、_link、_nftAddrconst ccipSimulatorDeployment = await deployments.get("CCIPLocalSimulator")//获得CCIP的对象(就是在0_deploy_ccip_simulator.js部署后才能获得),方便后面调用CCIP中的函数const ccipSimulator = await ethers.getContractAt("CCIPLocalSimulator",ccipSimulatorDeployment.address)//下面开始调用CCIP中的函数,获取需要的东西const ccipConfig = await ccipSimulator.configuretion()const sourceChainRouter = ccipConfig.sourceRouter_const linkTokenAddr = ccipConfig.linkToken_const nftDeployment = await deployments.get("MyToken")const nftAddr = nftDeployment.addressawait deploy("NFTPoolLockAndRelease",{cotract: "NFTPoolLockAndRelease",from: firstAccount,log: true,//这里的传参数_router、_link、_nftAddrargs:[sourceChainRouter,linkTokenAddr,nftAddr]})log("NFTPoolLockAndRelease contract deployed")
}moudle.exports.tags = ["sourcechain","all"]

5、一个完成的测试脚本

const { getNamedAccounts, ethers, deployments } = require("hardhat");
const { expect } = require("chai");//把变量提取出来,方便后面的测试函数调用
let firstAccount
let ccipSimulator
let nft
let NFTPoolLockAndRelease
let wnft
let NFTPoolBurnAndMint
let chainSelector
before(async function(){//准备变量--账号firstAccount = (await getNamedAccounts()).firstAccount//准备变量--合约,通过tag,部署所有合约await deployments.fixture(["all"])ccipSimulator = await ethers.getContract("CCIPLocalSimulator",firstAccount)nft = await ethers.getContract("MyToken",firstAccount)NFTPoolLockAndRelease = await ethers.getContract("NFTPoolLockAndRelease",firstAccount)wnft = await ethers.getContract("WrappedMyToken",firstAccount)NFTPoolBurnAndMint = await ethers.getContract("NFTPoolBurnAndMint",firstAccount)const ccipConfig = await ccipSimulator.configuration()console.log("ccipConfig:",ccipConfig)chainSelector = ccipConfig.chainSelector_console.log("chainSelector:",chainSelector)})//第一步:源链sourcechain--》目标链destchain
describe("source chain -> dest chain test", async function(){//test1--是否成功mintit("test if user can mint one nft from MyToken contract successfully",async function () {await nft.safeMint(firstAccount)const owner = await nft.ownerOf(0)expect(owner).to.equal(firstAccount)    })//test2--是否将nft已经lock在源链的pool中,并通过ccip将message发送给目标链it("test if nft has locked in source pool and send message to dest pool successfully",async function(){//await nft.transferFrom(firstAccount,NFTPoolLockAndRelease.target,0),不能直接这么用//这是在测试NFTPoolLockAndRelease合约中lockAndSendNFT()函数,该函数中使用的nft.transferFrom(),调用的是MyToken合约中的transferFrom()//所以NFTPoolLockAndRelease合约本身不具备转移nft的权限//先授权--将id为0的nft授权给NFTPoolLockAndRelease合约(执行lockAndSendNFT所需条件一)await nft.approve(NFTPoolLockAndRelease.target,0)console.log("nft's approval:",await nft.approve(NFTPoolLockAndRelease.target,0))//执行lockAndSentNFT需要fee(执行lockAndSendNFT所需条件二)await ccipSimulator.requestLinkFromFaucet(NFTPoolLockAndRelease, ethers.parseEther("10"))//参考合约中的入参进行赋值uint256 tokenId, newOwner, chainSelector, revceiver//lockAndSendNFT包含两个步骤:1.将nft从firstAccount转移到NFTPoolLockAndRelease合约;2.通过ccip发送消息console.log("newOwner:",firstAccount)console.log("chainSelector:",chainSelector)const receiverAddr = NFTPoolBurnAndMint.targetconsole.log("receiver:",receiverAddr)await NFTPoolLockAndRelease.lockAndSendNFT(0,firstAccount,chainSelector,receiverAddr)//检查是不是完成了第一步的转移const owner = await nft.ownerOf(0)console.log("newOwner:",owner)expect(owner).to.equal(NFTPoolLockAndRelease.target)})//test3--目标链接收到并mint新的wnftit("test if user can get a wrapped nft in dest chain",async function(){//当源链完成lockAndSendNFT后,会通过CCIP发送消息给目标链,目标链上就会mint一个wnft//所以只要验证目标链上是否有id为0的wnft存在,即owner不是空值,且owner为firstAccountconst owner = await wnft.ownerOf(0)expect(owner).to.equal(firstAccount)})
})//第二步:目标链destchain--》源链sourcechain
describe("dest chain->source chain test", async function(){//test4-目标链的wnft被burn掉,并通过ccip发送message给源链it("test if dest chain burn wnft and send message successfully",async function() {//wnft当前的owner是firstAccount,合约NFTPoolBurnAndMint想要burn掉wnft需要获取approveawait wnft.approve(NFTPoolBurnAndMint.target,0)//需要消耗feesawait ccipSimulator.requestLinkFromFaucet(NFTPoolBurnAndMint,ethers.parseEther("10"))//调用burnAndSendNFT(),传参为tokenId, newOwner, chainSelector, revceiverawait NFTPoolBurnAndMint.burnAndSendNFT(0,firstAccount,chainSelector,NFTPoolLockAndRelease.target)//执行完burnAndSendNFT后,目标链的池子中就没有wnft了,此时totalSupply应该为0const totalSupply = await wnft.totalSupply()expect(totalSupply).to.equal(0)})//test5-源链接收到信息后,nft被unlockit("test if source nft has unlocked", async function(){//检查源链当中的nft是否被unlock释放出来const owner = await nft.ownerOf(0)expect(owner).to.equal(firstAccount) })
})

6、部署脚本中的ethers.getContractAt()和测试脚本中的ethers.getCotract()有什么区别
ethers.getContractAt()是用于获取已经部署的合约实例args(name,address),与其进行交互,比如部署脚本中获取前一个部署合约的地址

//用于获取前面已经部署的MyToken合约,并填入传参args:合约名,合约地址
const nftDeployment = await deployments.get("MyToken")
const nft = await ethers.getContractAt("MyToken", nftDeployment.address)

ethers.getContract()是用于部署新的合约实例,相当于ethers.getContractFactory(),即通过合约工厂部署一个新的合约实例

//谁去部署的
const nft = await ethers.getContract("Mytoken", firstAccount)
//相当于
const contractFactory = await ethers.getContractFactory("MyContract");  
const contract = await contractFactory.deploy(); // 部署合约并获得实例

7、部署脚本deploy和测试脚本test中如何获取合约地址
部署脚本deploy

//先创建一个合约实例
const nftDeployment = await deployments.get("MyToken")
//获取合约地址
const nftAddr = nftDeployment.address

测试脚本test

//先创建一个合约实例
const nftDeployment = await ethers.getContract("MyToken",firstAccout)
const nftAddr = nftDeployment.target

8、获取当前用户的账户余额,检查是否够gas费用

const [account] = await ethers.getSigners()
const accountBalance = await ethers.provider.getBalance(account.address)
或者
const accountBalance = await ethers.provider.getBalance(firstAccount) -- 即账户的地址

9、如何获取mint的tokenId--在dev分支上尝试
问题:由于burn掉的代币tokenId没有被重置,所以再次mint时tokenId会进行累加
解决:如何获取tokenId
方案一:通过修改MyToken.sol合约中safemint方法return tokenId来实现,如:

    function safeMint(address to) public returns(uint256){uint256 tokenId = _nextTokenId++;_safeMint(to, tokenId);_setTokenURI(tokenId, META_DATA);isTokenIdExitStill[tokenId] = true;emit Minted(to,tokenId);return tokenId;}

对应js脚本的调用为:

//尝试获取tokenId
const tokenId = await nft.safeMint(firstAccount)
console(`mint出来的tokenId为${tokenId}`)

结果日志打印出来是:mint出来的tokenId为[object][object]
疑问:为什么mint函数返回的是个对象呢?
解答:
1)在智能合约中函数方法可以分为两种:状态改变型函数(写入函数)状态只读型函数
状态改变型函数(写入函数):如转账、铸币等。它们通常返回一个包含交易信息的对象(transactionresponse),而不是直接返回执行结果
2)智能合约的
写入型函数**调用涉及到区块链的交易处理
所以本合约中的safeMint函数返回类型是 uint256,它在只能合约中确实返回了tokenId。但是,智能合约函数的调用在 JavaScript 中通常是异步的,返回的是一个交易对象,而不是直接的返回值
由此引出另外两种获取tokenId的解决方案:1、交易日志中获取;2、合约中写一个读取tokenId的只读型函数
优化方案1:交易日志中获取

//尝试1:通过交易日志查询到tokenId
const mintTx = await nft.safeMint(firstAccount)
const mintReceipt = await mintTx.wait()
const mintReceiptString = JSON.stringify(mintReceipt,null,2)
console.log(`合约交易信息内容是:${mintReceiptString}`)
const tokenId = await mintReceiptString.logs[0].args.tokenId

2.1)问题:这个打印的mintReceiptString里面没有看到tokenId的相关信息,(即使追加event没有对应信息)

event Minted(address indexed to, uint256 indexed tokenId);function safeMint(address to) public returns(uint256){uint256 tokenId = _nextTokenId++;_safeMint(to, tokenId);_setTokenURI(tokenId, META_DATA);isTokenIdExitStill[tokenId] = true;emit Minted(to,tokenId);return tokenId;}

打印输出结果:

receipt的打印输出为:{"_type": "TransactionReceipt","blockHash": "0x7b712ac81f2ca097a18ce7167c49d670e10057169cf85fca38fd7f3746205405","blockNumber": 2,"contractAddress": null,"cumulativeGasUsed": "216130","from": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266","gasPrice": "1786313340","blobGasUsed": null,"blobGasPrice": null,"gasUsed": "216130","hash": "0x8ef224ccbe646fbc7c5bb89e5ab0a0e663abdb5174e212e2e78932d3ea762f0a","index": 0,"logs": [{"_type": "log","address": "0x5FbDB2315678afecb367f032d93F642f64180aa3","blockHash": "0x7b712ac81f2ca097a18ce7167c49d670e10057169cf85fca38fd7f3746205405","blockNumber": 2,"data": "0x","index": 0,"topics": ["0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef","0x0000000000000000000000000000000000000000000000000000000000000000","0x000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb92266","0x0000000000000000000000000000000000000000000000000000000000000000"],"transactionHash": "0x8ef224ccbe646fbc7c5bb89e5ab0a0e663abdb5174e212e2e78932d3ea762f0a","transactionIndex": 0},{"_type": "log","address": "0x5FbDB2315678afecb367f032d93F642f64180aa3","blockHash": "0x7b712ac81f2ca097a18ce7167c49d670e10057169cf85fca38fd7f3746205405","blockNumber": 2,"data": "0x0000000000000000000000000000000000000000000000000000000000000000","index": 1,"topics": ["0xf8e1a15aba9398e019f0b49df1a4fde98ee17ae345cb5f6b5e2c27f5033e8ce7"],"transactionHash": "0x8ef224ccbe646fbc7c5bb89e5ab0a0e663abdb5174e212e2e78932d3ea762f0a","transactionIndex": 0},{"_type": "log","address": "0x5FbDB2315678afecb367f032d93F642f64180aa3","blockHash": "0x7b712ac81f2ca097a18ce7167c49d670e10057169cf85fca38fd7f3746205405","blockNumber": 2,"data": "0x","index": 2,"topics": ["0x30385c845b448a36257a6a1716e6ad2e1bc2cbe333cde1e69fe849ad6511adfe","0x000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb92266","0x0000000000000000000000000000000000000000000000000000000000000000"],"transactionHash": "0x8ef224ccbe646fbc7c5bb89e5ab0a0e663abdb5174e212e2e78932d3ea762f0a","transactionIndex": 0}],"logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000040020000000000000100000800000000000000000080000010000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000002000000000000000000000000000008000000042000000200000000000000000000000002040000000000000000020000000000000000000200000000000000000000000000000000000000000000000","status": 1,"to": "0x5FbDB2315678afecb367f032d93F642f64180aa3"
}

原因:
hardhat测试框架中,默认情况下,交易回执只显示原始的日志(logs),不会自动解码事件。你需要手动解码事件日志。

    nft = await ethers.getContract("MyToken",firstAccount)contractABI = ["event Minted(address indexed to, uint256 indexed tokenId)","function safeMint(address to) public returns (uint256)"];// iface = new ethers.utils.Interface(contractABI);  由于导入包依赖的问题,这一步无法正确执行const filter = nft.filters.Minted(null, null); // 监听所有 Minted 事件const logs = await nft.queryFilter(filter);console.log("Minted事件的日志: ", logs);logs.forEach((log) => {const parsedLog = iface.parseLog(log);console.log("解析后的事件:", parsedLog);});

优化方案2:合约中写一个读取tokenId的只读型函数

// 新增函数以获取指定地址的所有 Token IDs function getTokenIdsByOwner(address owner) public view returns (uint256[] memory) { uint256 balance = balanceOf(owner); uint256[] memory tokenIds = new uint256[](balance); for (uint256 i = 0; i < balance; i++) { tokenIds[i] = tokenOfOwnerByIndex(owner, i); } return tokenIds; }


10、如何确认合约函数调用时链上交易所需的gas费用--待更新

11、会存在修改Mytoken.sol合约后需要重新部署,而重新部署后合约的地址就会更改,旧代币无法同步到新合约中,如何避免这个问题呢,https://t.me/gtokentool  。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.xdnf.cn/news/35592.html

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

深入理解 SQL 注入:原理、攻击流程与防御措施

深入理解 SQL 注入&#xff1a;原理、攻击流程与防御措施 在当今数字化的时代&#xff0c;数据安全已成为每个企业和开发者必须面对的重要课题。SQL 注入&#xff08;SQL Injection&#xff09;作为一种常见的网络攻击方式&#xff0c;给无数企业带来了巨大的损失。本文将深入…

市场上显卡型号需求分析

两个平台统计&#xff1a;&#xff08;关键词统计&#xff0c;仅做参考&#xff09; GPU型号&#xff5c;平台 github(提交量/万) huggingface&#xff08;模型量/个&#xff09; H100 6.6 210 A100 17.2 483 V100 14.4 484 4090 27.3 31 3090 11.1 92 在git…

C# WPF抽奖程序

C# WPF抽奖程序 using Microsoft.Win32; using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Text; using System.Threading; using System.Threading.Tasks; using System.Windows; using System.…

Master EDI 项目需求分析

Master Electronics 通过其全球分销网络&#xff0c;支持多种采购需求&#xff0c;确保能够为客户提供可靠的元件供应链解决方案&#xff0c;同时为快速高效的与全球伙伴建立合作&#xff0c;Master 选择通过EDI来实现与交易伙伴间的数据传输。 EDI为交易伙伴之间建立了一个安…

基于单片机的输液速度监控系统设计

本设计是以STM32F103C8T6单片机为控制核心&#xff0c;用户可通过按键模块来设置液体高度与点滴速度的阈值&#xff0c;采用液位传感器实时监测瓶内液体位置&#xff0c;若液位低于所设阈值&#xff0c;蜂鸣器进行声音报警提醒患者或医生。采用步进电机通过控制输液管直径大小从…

河工oj新生周赛第八周2024

A.小七的作业 小柒的作业 - 问题 - 软件学院OJ 代码 #include<bits/stdc.h> using namespace std;int main() {string s;cin >> s;int l, r;cin >> l >> r;string str s.substr(l,r-l1);cout << str;return 0; } B.小七的签到题 小柒的签到…

FPGA工作原理、架构及底层资源

FPGA工作原理、架构及底层资源 文章目录 FPGA工作原理、架构及底层资源前言一、FPGA工作原理二、FPGA架构及底层资源 1.FPGA架构2.FPGA底层资源 2.1可编程输入/输出单元简称&#xff08;IOB&#xff09;2.2可配置逻辑块2.3丰富的布线资源2.4数字时钟管理模块(DCM)2.5嵌入式块 …

低功耗蓝牙模块在高尔夫测距仪上的应用

在绿意盎然的高尔夫球场上&#xff0c;每一次挥杆都承载着球员对精准与完美的追求。随着科技的飞速发展&#xff0c;高尔夫运动也迎来了智能化的革新。一款集成了先进蓝牙模组的高尔夫测距仪&#xff0c;它不仅重新定义了高尔夫运动的测距精度&#xff0c;更以无线互联的便捷性…

如何在 cPanel 中创建子域名:分步指南

cPanel 是一个用于管理网站的工具&#xff0c;操作界面简单直观&#xff0c;常用于管理网站的各种功能&#xff0c;包括创建子域名。很多知名的网络服务提供商&#xff0c;如 Hostease&#xff0c;都提供了 cPanel 管理工具。 本文将详细介绍如何在 cPanel 中创建子域名&#x…

减少30%人工处理时间,AI OCR与表格识别助力医疗化验单快速处理

在医疗行业&#xff0c;化验单作为重要的诊断依据和数据来源&#xff0c;涉及大量的文字和表格信息&#xff0c;传统的手工输入和数据处理方式不仅繁琐&#xff0c;而且容易出错&#xff0c;给医院的运营效率和数据准确性带来较大挑战。随着人工智能技术的快速发展&#xff0c;…

Linux安装BellSoft JDK 17 LTS

原来使用的OpenJdk&#xff0c;看到SpringBoot官网推荐&#xff08;如下图&#xff09;贝尔实验室的JDK&#xff0c;打算换一下 官方下载链接 JKD下载 可以看到Win、Mac、Linux都提供了&#xff0c;并且还有x86架构和arm架构的 在Linux中我们可以使用 uname -a 查看当前操作系…

C++(九)

前言&#xff1a; 本文主要讲述运算符的优先顺序。 一&#xff0c;运算符的优先级。 请看以下表达式&#xff1a; a32*5 运算结果为&#xff1a;13. 可以看到&#xff0c;在此代码中&#xff0c;先运行了2*5的结果&#xff0c;在此基础上在进行3操作&#xff0c;因此结果…

学生公寓智能限电系统的功能和作用

学生公寓智能限电系统‌是一种用于管理和限制学生公寓用电的设备和技术&#xff0c;旨在确保用电安全、防止火灾事故&#xff0c;并促进节能减排。以下是关于学生公寓智能限电系统的详细介绍&#xff1a; 1、功能和作用 智能限电系统通过以下功能来管理和限制用电&#xff1a…

嵌入式入门Day25

数据结构Day 6,IO Day1 查找算法顺序查找折半查找&#xff08;二分查找&#xff09;哈希查找 IO概念标准IO创建递归索引&#xff08;用于查询结构体定义&#xff09; 文件IO标准IO缓冲区指针相关函数 查找算法 顺序查找 关键字&#xff1a;分为主关键字和次关键字主关键字&am…

内网代理转发工具

概念区分 端口转发 端口转发就是将一个端口&#xff0c;这个端口可以本机的端口也可以是本机可以访问到的任意主机的端口&#xff0c;转发到任意一台可以访问到的IP上&#xff0c;通常这个IP是公网IP。 适用端口转发的网络环境有以下几种&#xff1a; 服务器处于内网&#x…

MNIST_FC

前言 提醒&#xff1a; 文章内容为方便作者自己后日复习与查阅而进行的书写与发布&#xff0c;其中引用内容都会使用链接表明出处&#xff08;如有侵权问题&#xff0c;请及时联系&#xff09;。 其中内容多为一次书写&#xff0c;缺少检查与订正&#xff0c;如有问题或其他拓展…

掌握时间,从`datetime`开始

文章目录 掌握时间&#xff0c;从datetime开始第一部分&#xff1a;背景介绍第二部分&#xff1a;datetime库是什么&#xff1f;第三部分&#xff1a;如何安装这个库&#xff1f;第四部分&#xff1a;简单库函数使用方法1. 获取当前日期和时间2. 创建特定的日期3. 计算两个日期…

算法之括号匹配中最长有效字符串

目录 1. 题目2. 解释3. 思路4. 代码5. 总结 1. 题目 任何一个左括号都能找到和其正确配对的右括号任何一个右括号都能找到和其正确配对的左括号 求最长的有效的括号长度 2. 解释 例如&#xff0c;这里的括号 ((((()()()()()()()))()最长有效是&#xff1a;((()()()()()()(…

统信桌面专业版部署postgresql-14.2+postgis-3.2方法介绍

文章来源&#xff1a;统信桌面专业版部署postgresql-14.2postgis-3.2方法介绍 | 统信软件-知识分享平台 应用场景 CPU架构&#xff1a;X86&#xff08;海光C86-3G 3350&#xff09; OS版本信息&#xff1a;1070桌面专业版 软件信息&#xff1a;postgresql-14.2postgis-3.2 …

【书生大模型实战营】Python 基础知识-L0G2000

前言&#xff1a;本文是书生大模型实战营系列的第2篇文章&#xff0c;是入门岛的第二个任务&#xff0c;主题为&#xff1a;Python基础知识。 官方教程参考链接&#xff1a;Tutorial/docs/L0/Python at camp4 InternLM/Tutorial 1.任务概览 本关为Python基础关卡&#xff0…