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.mzph.cn/diannao/63166.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Python毕业设计选题:基于django的民族服饰数据分析系统的设计与实现_hadoop+spider

开发语言&#xff1a;Python框架&#xff1a;djangoPython版本&#xff1a;python3.7.7数据库&#xff1a;mysql 5.7数据库工具&#xff1a;Navicat11开发软件&#xff1a;PyCharm 系统展示 管理员登录 管理员功能界面 用户管理 民族服饰管理 看板展示 系统首页 民族服饰 服饰…

mac port 安装redis 并设置为系统服务 自定义配置方法

mac系统中&#xff0c;port 包管理工具比brew的速度快N倍&#xff0c;今天就给大家分享一下在macos系统中如何使用 port安装 redis数据库并配置为服务自动启动和自定义redis.conf配置的方法。 1. 安装redis sudo port install redis 2. 启动redis服务 sudo port load redis …

MySQL导入.sql文件后数据库乱码问题

问题分析&#xff1a; 当导入.sql文件后&#xff0c;发现数据库中的备注出现乱码&#xff0c;通常是由于一下原因导致&#xff1a; 字符集不匹配&#xff1a;.sql文件、MySQL服务器、客户端连接使用的字符集不一致。备注内容编码问题&#xff1a;备注内容本身的编码格式与数据…

RabbitMQ 架构介绍:深入理解与应用

RabbitMQ 是一个开源的消息代理&#xff08;Message Broker&#xff09;软件&#xff0c;它实现了高级消息队列协议&#xff08;AMQP&#xff09;&#xff0c;并提供了可靠的消息传递机制。RabbitMQ 广泛应用于分布式系统中&#xff0c;用于解耦系统组件、异步处理任务和实现消…

分布式事物各方案常见使用场景

2PC/3PC&#xff1a;依赖于数据库&#xff0c;能够很好的提供强一致性和强事务性&#xff0c;但延迟比较高&#xff0c;比较适合传统的单体应用&#xff0c;在同一个方法中存在跨库操作的情况&#xff0c;不适合高并发和高性能要求的场景。TCC&#xff1a;适用于执行时间确定且…

【西门子PLC.博途】——在S71200里写时间设置和读取功能块

之前我们在这篇文章中介绍过如何读取PLC的系统时间。我们来看看在西门子1200里面有什么区别。同时也欢迎关注gzh。 我们在S71200的帮助文档中搜索时间后找到这个数据类型 在博途中他是一个结构体&#xff0c;具体为 然后我们再看看它带的读取和写入时间块 读取时间&#xff1…

AI模型大概训练流程

使用语言&#xff1a;Python 1. 数据收集和准备 import numpy as np import pandas as pd from sklearn.model_selection import train_test_split from sklearn.preprocessing import StandardScaler# 假设我们已经有了数据集&#xff0c;以便于读取 data pd.read_csv(your_d…

UE5 Compile Plugins | Rebuild from Source Manually | Unreal Engine | Tutorial

Step 1 Open Engine Folder H:\UE5\UE_5.3\Engine\Build\BatchFiles Step 2 Hold "Shift""Mouse Right Click"in Empty Area Step 3 Select "Open PowerShell window here .\RunUAT.bat BuildPlugin -plugin"H:\projects\MetaHuman光照2\plu…

vue聊天对话语音消息播放动态特效

vue2写法&#xff0c;vue3也能用&#xff0c;粘之即走&#xff1a; 示例&#xff1a; <template><div class"voice-hidden"><divclass"voice-play-chat":class"[className, { animate-stop: !isPlaying }]"><div class&q…

【整理】nodejs框架技术选型

NodeJS 的 Web 框架有很多&#xff0c;目前常见的主要包括 ExpressJS、KoaJS、NestJS、MidwayJS 等&#xff0c;我们做个简单介绍&#xff0c;做个简单的对比。 Express&#xff08;59K Stars&#xff09; 基本介绍 Express 是 NodeJS 早期率先出现的一款框架&#xff0c;现…

Python 绘图工具详解:使用 Matplotlib、Seaborn 和 Pyecharts 绘制散点图

目录 数据可视化1.使用 matplotlib 库matplotlib 库 2 .使用 seaborn 库seaborn 库 3 .使用 pyecharts库pyecharts库 注意1. 确保安装了所有必要的库2. 检查Jupyter Notebook的版本3. 使用render()方法保存为HTML文件4. 使用IFrame在Notebook中显示HTML文件5. 检查是否有其他输…

处理一个MP4视频,检测里面出现的人脸,并根据特征值计算相似度来追踪和显示出每个人脸的ID

使用Mediapipe, opencv 处理一个MP4视频,检测里面出现的人脸,然后使用Mediapipe 计算每个出现的人脸的特征值,并根据特征值计算相似度来追踪和显示出每个人脸的ID import cv2 import mediapipe as mp import numpy as np# 初始化Mediapipe人脸检测和FaceMesh模型 mp_face_de…

【链表】力扣 141. 环形链表

一、题目 二、思路 龟兔进行赛跑 龟的速度是 1&#xff0c;兔的速度是 2龟兔从同一起点出发&#xff0c;若 龟追上兔 则说明 有环 存在&#xff1b;若追不上&#xff0c;则说明无环。 三、代码 /*** Definition for singly-linked list.* class ListNode {* int val;* …

Spring中使用Async进行异步功能开发实战-以大文件上传为例

目录 前言 一、场景再现 1、Event的同步机制 二、性能优化 1、异步支持配置 2、自定义处理线程池扩展 3、将线程池配置类绑定到异步方法 三、总结 前言 在之前的博客中&#xff0c;曾将讲了在SpringBoot中如何使用Event来进行大文件上传的解耦&#xff0c;原文地址&am…

PyTorch 深度学习框架简介:灵活、高效的 AI 开发工具

PyTorch 深度学习框架简介&#xff1a;灵活、高效的 AI 开发工具 PyTorch 作为一个深度学习框架&#xff0c;以其灵活性、可扩展性和高效性广受欢迎。无论是在研究领域进行创新实验&#xff0c;还是在工业界构建生产级的深度学习模型&#xff0c;PyTorch 都能提供所需的工具和…

面试技巧,金字塔原理,思考、表达和解决问题的逻辑方法

目录 一、基本结构 二、思考过程中的应用 三、表达过程中的应用 案例1&#xff1a;项目汇报 案例2&#xff1a;营销策划方案 案例3&#xff1a;员工培训课程设计 金字塔原理是一种思考、表达和解决问题的逻辑方法&#xff0c;由芭芭拉・明托&#xff08;Barbara Minto&am…

C++中的封装性

概念 在C中&#xff0c;封装性是面向对象编程(OOP)的一个核心特征&#xff0c;它主要通过类和对象来实现。封装性指的是将数据和操作这些数据的方法结合在一起&#xff0c;形成一个自给自足的实体&#xff0c;这种做法有助于提高代码的安全性和可维护性。下面是对C中封装性的详…

MATLAB在生态环境数据处理与分析中的应用

专题一 MATLAB编程入门 要点&#xff1a;介绍、案例演示、软件界面、语法基础、基本运算等 专题二&#xff08;试听&#xff09; MATLAB编程入门 要点&#xff1a;脚本编写、函数调用、循环控制、代码调试、文件读写等 专题三 MATLAB可视化与绘图 要点&#xff1a;交互式…

Qt 2D绘图之五:图形视图框架的结构、坐标系统和框架间的事件处理与传播

参考文章链接: Qt 2D绘图之五:图形视图框架的结构和坐标系统 Qt 2D绘图之六:图形视图框架的事件处理与传播 图形视图框架的结构 在前面讲的基本绘图中,我们可以自己绘制各种图形,并且控制它们。但是,如果需要同时绘制很多个相同或不同的图形,并且要控制它们的移动、…

Qt开发技巧(二十四)滚动部件的滑动问题,Qt设置时区问题,自定义窗体样式不生效问题,编码格式问题,给按钮左边加个图,最小化后的卡死假象

继续记录一些Qt开发中的技巧操作&#xff1a; 1.滚动部件的滑动问题 再Linux嵌入式设备上&#xff0c;有时候一个页面的子部件太多&#xff0c;一屏放不下是需要做页面滑动&#xff0c;可以使用“QScrollArea”控件&#xff0c;拖来一个“QScrollArea”控件&#xff0c;将子部件…