NFT交易市场(二)

news2025/1/13 10:20:46

编写脚本文件进行测试

  • nft合约代码
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

import "@openzeppelin/contracts/interfaces/IERC20.sol";
import "@openzeppelin/contracts/interfaces/IERC721.sol";

contract Market {
    //将erc20初始化为一个IERC20类型的实例,但具体的地址尚未赋值,后续的构造函数会使用传入的参数来设置erc20,从而与实际的ERC20代币合约进行交互
    IERC20 public erc20;
    IERC721 public erc721;

    bytes4 internal constant MAGIC_ON_ERC721_RECEIVED = 0x150b7a02;

    //包含代币售卖地址,代币Id,代币价格
    struct Order {
        address seller;
        uint256 tokenId;
        uint256 price;
    }

    mapping(uint256 => Order) public orderOfId; //根据tokenId查询Order
    Order[] public orders; //定义一个Order类型的数组存放所有的Order
    mapping(uint256 => uint256) public idToOrderIndex; //根据tokenId查询在数组中的索引

    event Deal(address seller, address buyer, uint256 tokenId, uint256 price); //交易信息
    event NewOrder(address seller, uint256 tokenId, uint256 price); //新的卖家信息
    event PriceChanged(
        address seller,
        uint256 tokenId,
        uint256 previousPrice,
        uint256 price
    ); //价格修改信息
    event OrderCancelled(address seller, uint256 tokenId); //卖家退出交易市场信息

    //获取ERC20和ERC721的合约地址,这俩合约地址在部署合约时,在详细信息里面可以找到,合约地址不能时零地址
    constructor(address _erc20, address _erc721) {
        require(_erc20 != address(0), "zero address");
        require(_erc721 != address(0), "zero address");
        //将传入的地址_erc20转换为IERC20类型的实例,并将其赋值给变量erc20
        erc20 = IERC20(_erc20);
        erc721 = IERC721(_erc721);
    }

    //购买函数
    function buy(uint256 _tokenId) external {
        //获取该tokenId的卖家地址
        address seller = orderOfId[_tokenId].seller;
        //获取该tokenI的售卖价格
        uint256 price = orderOfId[_tokenId].price;
        //买方地址即当前调用合约的地址
        address buyer = msg.sender;

        //调用ERC20里面的授权转账函数进行转账
        /*关于这个授权转账函数,最开始就有一点疑惑,按道理应该是购买者给市场合约授权才能有权限去转走购买者的币给卖家,具体这个该函数我在ERC20学习里面讲过
      假如A授权给B一定量的币允许它使用,B要给C转账,因此就是B调用合约,而里面的from就是A,to就是C,即使A给B授权,但是币仍然在A手中,因此这里的买家相当于
      A,市场合约相当于B,而卖家相当于C,因此买家2需要给市场合约授权才能完成购买
*/
        require(
            erc20.transferFrom(buyer, seller, price),
            "transfer not successful"
        );
        //调用了ERC721代币合约的safeTransferFrom函数,将指定的ERC721代币(由_tokenId标识)从当前合约地址(address(this))安全地转移到买家账户(buyer)。
        erc721.safeTransferFrom(address(this), buyer, _tokenId);

        emit Deal(seller, buyer, _tokenId, price); //释放交易信息
    }

    //下架函数
    function cancelOrder(uint256 _tokenId) external {
        //获取卖家地址
        address seller = orderOfId[_tokenId].seller;
        //查看当前地址是不是卖家,因为商品只能由卖家自己下架
        require(msg.sender == seller, "not seller");

        erc721.safeTransferFrom(address(this), seller, _tokenId);

        emit OrderCancelled(seller, _tokenId);
    }

    //改价函数
    function changePrice(uint256 _tokenId, uint256 _price) external {
        address seller = orderOfId[_tokenId].seller;
        require(msg.sender == seller, "not seller");

        uint256 previousPrice = orderOfId[_tokenId].price;
        orderOfId[_tokenId].price = _price;

        //注意,不仅要修改Order的价格,orders数组里面存储的该地址的代币价格也需要修改
        //memory修改不会存储在链上,用于存储临时数据,storage修改会存储在链上,用于永久保存
        Order storage order = orders[idToOrderIndex[_tokenId]];
        order.price = _price;

        emit PriceChanged(seller, _tokenId, previousPrice, _price);
    }

    //上架函数
    //用于处理ERC721代币的接收事件,当其它合约或用户向该合约发送ERC721代币时,会触发该函数,可以在该函数中编写逻辑来处理接收到的代币
    function onERC721Received(
        address operator, //执行操作的地址
        address from, //发送代币的地址
        uint256 tokenId, //接收到的代币的唯一标识符
        bytes calldata data //附加数据,可以是任意字节数组
    ) external returns (bytes4) {
        //调用格式转换函数得到价格
        uint256 price = toUint256(data, 0);
        require(price > 0, "price must be greater than 0");

        //上架
        orders.push(Order(from, tokenId, price));
        orderOfId[tokenId] = Order(from, tokenId, price);
        idToOrderIndex[tokenId] = orders.length - 1;

        emit NewOrder(from, tokenId, price);

        //返回该值,这样发送方的合约就可以指导接收方是否已经正确处理了代币转移
        return MAGIC_ON_ERC721_RECEIVED;
    }

    //下架函数
    function removeOrder(uint256 _tokenId) internal {
        //先从数组中把他删除掉
        uint256 index = idToOrderIndex[_tokenId];
        uint256 lastIndex = orders.length - 1;
        if (index != lastIndex) {
            Order storage lastOrder = orders[lastIndex];
            orders[index] = lastOrder;
            idToOrderIndex[lastOrder.tokenId] = index;
        }
        orders.pop();

        //从mapping里面删除
        delete orderOfId[_tokenId];
        delete idToOrderIndex[_tokenId];
    }

    //格式转换函数
    function toUint256(
        bytes memory _bytes,
        uint256 _start
    ) public pure returns (uint256) {
        require(_start + 32 >= _start, "Market: toUint256_overflow");
        require(_bytes.length >= _start + 32, "Market: toUint256_outOfBounds");
        uint256 tempUint;

        assembly {
            tempUint := mload(add(add(_bytes, 0x20), _start))
        }
        return tempUint;
    }

    //验证该tokenId的售卖地址不是零地址
    function isListed(uint256 _tokenId) public view returns (bool) {
        return orderOfId[_tokenId].seller != address(0);
    }

    //获取数组订单长度
    function getOrderLength() external view returns (uint256) {
        return orders.length;
    }
    //获取所有上架的NFT
    function getAllNFTs() external view returns (Order[] memory) {
        return orders;
    }

    //获取用户自己上架的NFT
    function getMyNFTs() external view returns (Order[] memory) {
        Order[] memory myOrders = new Order[](orders.length);
        uint256 count = 0;
        for (uint256 i = 0; i < orders.length; i++) {
            if (orders[i].seller == msg.sender) {
                myOrders[count] = orders[i];
                count++;
            }
        }
        return myOrders;
    }
}

  • 在hardhat框架下的test文件夹下新建一个名为market.js的文件,删除掉原有的js文件
const { expect } = require('chai');
const { ethers } = require('hardhat');

//测试的步骤就跟我们之前在remix里面编译部署测试类似
describe('Market', async function () {
  let usdt, nft, market, accountA, accountB;

  //测试框架中的钩子函数,每个测试用例执行之前都会运行该函数
  beforeEach(async () => {
    //使用ethers.getSigners()方法获取当前连接的以太坊网络中的前两个账户,也就是我们之前编译部署的时候用到的地址
    [accountA, accountB] = await ethers.getSigners();

    //获取erc20合约并部署
    const USDT = await ethers.getContractFactory('cUSDT');
    usdt = await USDT.deploy();
    //获取erc721合约并部署
    const NFT = await ethers.getContractFactory('NFTM');
    nft = await NFT.deploy(accountA.address);
    //获取nft合约并部署
    const Market = await ethers.getContractFactory('Market');
    market = await Market.deploy(usdt.target, nft.target);

    //给 B地址mint两个NFT
    await nft.safeMint(accountB.address);
    await nft.safeMint(accountB.address);

    //B允许A花费自己所有的NFT
    await nft.connect(accountB).setApprovalForAll(accountA.address, true);

    //调用erc20合约里面的approve函数给市场合约授权一亿个USDT币
    await usdt.approve(market.target, "100000000000000000000000");
  });

  //查看市场合约的erc20地址是否和erc20合约的地址一样
  it('its erc20 address should be usdt', async function () {
    expect(await market.erc20()).to.equal(usdt.target);
  });

  //查看市场合约的erc721地址是否和erc721合约的地址一样
  it('its erc721 address should be nft', async function () {
    expect(await market.erc721()).to.equal(nft.target);
  });

  //验证B账户是否有两个NFT
  it('accountB should have 2 nfts', async function () {
    expect(await nft.balanceOf(accountB.address)).to.equal(2);
  });

  //验证A账户有很多USDT
  it('accountA should have usdt', async function () {
    expect(await usdt.balanceOf(accountA.address)).to.equal("100000000000000000000000000");
  });

  //B账户可以上架两个NFT,
  it('accountB can list two nfts to market', async function () {
    const price = "0x0000000000000000000000000000000000000000000000000001c6bf52634000";


    //调用erc721的safeTransferFrom函数将B账户的两个NFT上架到市场合约,并释放新的卖家信息
    expect(await nft['safeTransferFrom(address, address, uint256, bytes)'](accountB.address, market.target, 0, price)).to.emit(market, "NewOrder");
    expect(await nft['safeTransferFrom(address, address, uint256, bytes)'](accountB.address, market.target, 1, price)).to.emit(market, "NewOrder");

    //已经上架了,查看他的余额是否正确
    expect(await nft.balanceOf(accountB.address)).to.equal(0);
    expect(await nft.balanceOf(market.target)).to.equal(2);
    expect(await market.isListed(0)).to.equal(true);
    expect(await market.isListed(1)).to.equal(true);

    //调用市场合约的getALLNFTs()函数验证该数组的对应位置的值是否与B账户上架的两个NFT的值相匹配
    //expect((await market.getALLNFTs())[0][0]).to.equal(accountB.address);  //验证地址
    //expect((await market.getALLNFTs())[0][1]).to.equal(0);  //验证tokenId
    //expect((await market.getALLNFTs())[0][2]).to.equal(price);  //代币价格

    //expect((await market.getALLNFTs())[1][0]).to.equal(accountB.address);  //验证地址
    //expect((await market.getALLNFTs())[1][1]).to.equal(1);  //验证tokenId
    //expect((await market.getALLNFTs())[1][2]).to.equal(price);  //代币价格

    //查看数组长度是否是2
    expect(await market.getOrderLength()).to.equal(2);

    expect((await market.connect(accountB).getMyNFTs())[0][0]).to.equal(accountB.address);
    expect((await market.connect(accountB).getMyNFTs())[0][1]).to.equal(0);
    expect((await market.connect(accountB).getMyNFTs())[0][2]).to.equal(price);
  })

});
  • 可以运行npx hardhat test命令测试一下
    在这里插入图片描述

  • 在这里查找abi

  • 安装abi插件
    npm install hardhat-abi-exporter

  • 然后在hardhat.config.js文件中添加
    在这里插入图片描述

  • 输入npx hardhat export-abi --no-compile命令运行,然后就可以看到abi文件夹如下图所示
    在这里插入图片描述

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

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

相关文章

C++进阶之路---手把手带你学习AVL树

顾得泉&#xff1a;个人主页 个人专栏&#xff1a;《Linux操作系统》 《C从入门到精通》 《LeedCode刷题》 键盘敲烂&#xff0c;年薪百万&#xff01; 一、AVL树的概念 二叉搜索树虽可以缩短查找的效率&#xff0c;但如果数据有序或接近有序二叉搜索树将退化为单支树&#…

2024年腾讯云新用户优惠活动4核8G12M配置15个月和一年费用

腾讯云轻量4核8G12M服务器配置446元一年&#xff0c;646元12个月&#xff0c;腾讯云轻量应用服务器具有100%CPU性能&#xff0c;系统盘为180GB SSD盘&#xff0c;12M带宽下载速度1536KB/秒&#xff0c;月流量2000GB&#xff0c;折合每天66.6GB流量&#xff0c;超出月流量包的流…

访问者模式(Visitor Pattern)

访问者模式 说明 访问者模式&#xff08;Visitor Pattern&#xff09;属于行为型模式&#xff0c;表示一个作用于某对象结构中的各元素的操作。它使你可以在不改变各元素的类的前提下定义作用于这些元素的新操作。 该模式是将数据结构与数据操作分离的设计模式&#xff0c;是…

蓝牙系列十三:协议栈L2CAP层

L2CAP 全称为逻辑链路控制与适配协议(Logical Link Control and Adaptation Protocol)&#xff0c;位于基带层之上&#xff0c;将基带层的数据分组交换为便于高层应用的数据分组格式&#xff0c;并提供协议复用和服务质量交换等功能。 该层属于主机的内容&#xff0c;位于HCI层…

3.3 ss-sp寄存器,栈的push和pop指令

汇编语言 1. 栈 栈是一种具有特殊的访问方式的存储空间它的特殊性就在于&#xff0c;最后进入这个空间的数据&#xff0c;最先出去。即先进后出 1.1 栈的基本操作 入栈&#xff1a;入栈就是将一个新的元素放到栈顶出栈&#xff1a;出栈就是从栈顶取出一个元素栈顶的元素总是…

汽车电子零部件(4):行泊一体ADAS

前言: 现阶段智能汽车行业正在大规模力推无限接近于L3的L2++或L2.9自动驾驶量产落地,类似于当初智能手机替换传统手机的行业机会期。智能汽车常见的智能驾驶功能包括: 行车场景:自适应巡航控制ACC;自动变道辅助ALC;交通拥堵辅助TJA;车道居中LCC;领航辅助NOA; 泊车场…

如何在代理的IP被封后立刻换下一个IP继续任务

目录 前言 1. IP池准备 2. 使用代理IP进行网络请求 3. 处理IP被封的情况 4. 完整代码示例 总结 前言 当进行某些网络操作时&#xff0c;使用代理服务器可以帮助我们隐藏真实IP地址以保护隐私&#xff0c;或者绕过一些限制。然而&#xff0c;经常遇到的问题是代理的IP可能…

AI新工具(20240313) 用户输入提示词创建任何GIF; 将任意人脸图片转换为另一幅图像的模型

✨ 1: GifShift 用户输入提示词创建任何GIF gifshift是一种工具&#xff0c;可以帮助用户创建任何GIF的新版本。使用gifshift的步骤如下&#xff1a; 上传一个GIF文件或者使用库中的一个GIF。 提供您想要的场景描述&#xff0c;最好选择一些具有代表性的角色&#xff0c;并进…

Android cmdline tools安装

打开AS 进入SDK Tools 看到了吗?那个打着勾的就是

从零开始搭建医保购药APP:技术选择与开发流程

医保购药APP作为一种创新的医疗服务工具&#xff0c;为用户提供了便捷的医保购药流程&#xff0c;同时也为医疗机构提供了更高效的管理和服务方式。今天小编将为大家讲解如何从零开始搭建一款医保购药APP&#xff0c;包括技术选择和开发流程。 一、技术选择 在搭建医保购药APP…

DS进阶:二叉搜索树

创作不易&#xff0c;感谢三连&#xff01; 一、二叉搜索树的概念 思考&#xff1a; 为什么二叉搜索树也叫做二叉查找树和二叉排序树呢&#xff1f;&#xff1f; 1、 本身树形结构用来存储数据相比顺序表和链表来说并不占有优势&#xff0c;他的最大优势就在于查找优势&…

Python面向对象构造函数:手把手教你如何玩转对象初始化

我们都知道&#xff0c;Python是一个面向对象的语言&#xff0c;这意味着我们可以用类来定义对象的属性和方法。而构造函数&#xff0c;就是当我们创建一个新的对象时&#xff0c;会自动调用的特殊方法。那么&#xff0c;如何玩转这个构造函数呢&#xff1f; 首先&#xff0c;…

DOM事件event/冒泡/委派/取消默认行为/dataset属性

1DOM获取CSS样式表里的样式: <head><meta charset"UTF-8"><title>Title</title><link rel"stylesheet" href"css/style.css"><style>body{color: red;}h1::after{content: hello;color: red;}</style&g…

微博热搜榜单采集,微博热搜榜单爬虫,微博热搜榜单解析,完整代码(话题榜+热搜榜+文娱榜和要闻榜)

文章目录 代码1. 话题榜2. 热搜榜3. 文娱榜和要闻榜 过程1. 话题榜2. 热搜榜3. 文娱榜和要闻榜 代码 1. 话题榜 import requests import pandas as pd import urllib from urllib import parse headers { authority: weibo.com, accept: application/json, text/pl…

《JAVA与模式》之工厂方法模式

系列文章目录 文章目录 系列文章目录前言一、工厂方法模式二、工厂方法模式的活动序列图三、工厂方法模式和简单工厂模式前言 前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。点击跳转到网站,这篇文章男女通用,看懂了就去分享给你的码…

SwiftU的组件 - TabView

SwiftU的组件 - TabView 记录一下SwiftU的组件 - TabView的两种style分别的使用方式 import SwiftUIstruct TabViewBootCamp: View {State var selectedIndex 0var body: some View {NavigationView {TabView(selection: $selectedIndex) {HomeView(selectedIndex: $selected…

【解读】Synopsys发布2024年开源安全和风险分析报告OSSRA

软件供应链管理中&#xff0c;许可证和安全合规性至关重要。开源组件和库可降低风险&#xff0c;但需了解许可证内容。Synopsys 2023年审计发现&#xff0c;超过一半的代码库存在许可证冲突。MIT许可证是最常用的宽松许可证&#xff0c;但也与其他许可证存在不兼容风险。点此获…

重学SpringBoot3-Problemdetails

更多SpringBoot3内容请关注我的专栏&#xff1a;《SpringBoot3》 期待您的点赞&#x1f44d;收藏⭐评论✍ 重学SpringBoot3-Problemdetails Problem Details的概念ProblemDetails配置类在Spring Boot 3中使用Problem Details未配置Problem Details配置Problem Details自定义异常…

外包就干了2个月,技术退步明显....

先说情况&#xff0c;大专毕业&#xff0c;18年通过校招进入湖南某软件公司&#xff0c;干了接近4年的功能测试&#xff0c;今年年初&#xff0c;感觉自己不能够在这样下去了&#xff0c;长时间呆在一个舒适的环境会让一个人堕落!而我已经在一个企业干了四年的功能测试&#xf…