首页 > 其他分享 >DApp设计与开发 课程笔记(四):NFT交易市场合约

DApp设计与开发 课程笔记(四):NFT交易市场合约

时间:2024-07-01 23:10:04浏览次数:1  
标签:tokenId uint256 price await 笔记 DApp NFT address market

笔记对应课程内容为成都信息工程大学区块链产业学院老师梁培利DApp 设计与开发 10-13 课

笔记中提到的名词不做过多解释 不懂就搜!

开发准备:

动手学

Windows 需要使用 Powershell,不能使用git bash

mkdir nft-market
cd nft-market/
npx hardhat init

npm install @openzeppelin/contracts
npm i @remix-project/remixd
remixd -s C:\Users\Administrator\Desktop\nft-market --remix-ide https://remix.ethereum.org

remix共享本地文件夹教程 - 走走停停走走 - 博客园 (cnblogs.com)

solcjs 和 hardhat在管理solidity编译版本上有什么区别?

solcjshardhat 都是与 Solidity 相关的工具,但在管理 Solidity 编译版本上有一些区别:

  1. solcjs:

    • 独立的 Solidity 编译器solcjs 是 Solidity 的官方编译器的 JavaScript 包装器。它允许你在 JavaScript 或 Node.js 环境中调用 Solidity 编译器,以便编译和部署智能合约。
    • 版本管理solcjs 可以通过 npm(Node.js 的包管理器)来安装不同版本的 Solidity 编译器。你可以选择安装并使用特定的 Solidity 版本来编译你的合约。
  2. Hardhat:

    • 开发框架Hardhat 是一个以太坊智能合约开发的工具套件和框架,它整合了多种功能,包括编译、测试、部署等。
    • 集成 solc:Hardhat 默认集成了 Solidity 编译器(通常使用的是 solc,不是 solcjs)。它通过自身的配置文件来管理 Solidity 版本和编译选项。
    • 版本管理:Hardhat 允许在项目配置中指定 Solidity 的版本,它会根据配置文件来选择合适的 Solidity 编译器版本进行编译。Hardhat 会自动下载和管理所需的 Solidity 版本,使得开发者可以在项目中轻松切换和管理不同的 Solidity 版本。

区别总结

  • solcjs 是一个单独的 JavaScript 包,主要用于直接调用 Solidity 编译器进行合约编译,需要手动安装和管理 Solidity 版本。
  • Hardhat 是一个完整的以太坊智能合约开发框架,集成了 Solidity 编译器,可以通过配置文件轻松管理 Solidity 版本,同时提供了测试、部署等功能,更适合大型和复杂的以太坊项目开发。

复制第三方模板合约库的智能合约代码:https://docs.openzeppelin.com/contracts/5.x/wizard

在你已经理解了NFT原理之后,就不用完全自己构建一个智能合约了,直接复制一个拿来就能用了

然后在remix部署合约

即可运行合约函数

但是交易市场的智能合约是没有拿来就能用的。

附上述代码:

erc20-usdt.sol

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.20;
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";

contract cUSDT is ERC20 {
    constructor() ERC20("fake usdt in oktc", "cUSDT") {
        _mint(msg.sender, 1*10*8*10**18);
    }
}

erc-721-nft.sol

// SPDX-License-Identifier: MIT
// Compatible with OpenZeppelin Contracts ^5.0.0
pragma solidity ^0.8.20;

import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts/token/ERC721/extensions/ERC721Enumerable.sol";
import "@openzeppelin/contracts/access/Ownable.sol";

contract NFTM is ERC721, ERC721Enumerable, Ownable {
    constructor(address initialOwner)
        ERC721("NFTM", "NFTM")
        Ownable(initialOwner)
    {}

    function _baseURI() internal pure override returns (string memory) {
        return "https://sample.onefly.top/";
    }

    function safeMint(address to, uint256 tokenId) public onlyOwner {
        _safeMint(to, tokenId);
    }

    // The following functions are overrides required by Solidity.

    function _update(address to, uint256 tokenId, address auth)
        internal
        override(ERC721, ERC721Enumerable)
        returns (address)
    {
        return super._update(to, tokenId, auth);
    }

    function _increaseBalance(address account, uint128 value)
        internal
        override(ERC721, ERC721Enumerable)
    {
        super._increaseBalance(account, value);
    }

    function supportsInterface(bytes4 interfaceId)
        public
        view
        override(ERC721, ERC721Enumerable)
        returns (bool)
    {
        return super.supportsInterface(interfaceId);
    }
}

老师和我都推荐使用 github copilot来编程,学生免费申请可以看我之前的文章:2023.3申请github copilot x 学生认证以及Jetbrain专业版学生教育免费教程 - 知乎 (zhihu.com)

nft-market.sol

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC721/IERC721.sol";

contract Market {
    IERC20 public erc20;
    IERC721 public erc721;

    bytes4 private constant Magic_On_Erc721_Received = 0x150b7a02;

    struct Order {
        address seller;
        uint256 tokenId;
        uint256 price;
    }
    mapping(uint256 => Order) public orderOfId; // token id => order
    Order[] public orders;
    mapping(uint256 => uint256) public idToOrderIndex; // token id => order id

    event Deal(address seller, address buyer, uint256 tokenId, uint256 price); //事件是合约与外部世界通信的唯一方式
    event NewOrder(uint256 tokenId, uint256 price);
    event PriceChanged(address seller, uint256 tokenId, uint256 previousPrice, uint256 price);
    event OrderCancled(address seller, uint256 tokenId);

    constructor(address _erc20, address _erc721) {
        require(_erc20 != address(0) && _erc721 != address(0), "invalid zero address");
        erc20 = IERC20(_erc20);
        erc721 = IERC721(_erc721);
    }

    function buy(uint256 _tokenId) external {
        address seller = orderOfId[_tokenId].seller;
        address buyer = msg.sender;
        uint256 price = orderOfId[_tokenId].price;

        require(erc20.transferFrom(buyer, seller, price), "transfer failed");//ierc20包装的erc20 transferFrom方法
        erc721.safeTransferFrom(address(this), buyer, _tokenId); //address(this)是合约本身地址
        
        //清除订单
        emit Deal(seller, buyer, _tokenId, price);//emit关键字用于触发事件
    }   

    function cancelOrder(uint256 _tokenId) external {
        address seller = orderOfId[_tokenId].seller;
        require(msg.sender == seller, "only seller can cancel order");

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

        uint256 orderId = idToOrderIndex[_tokenId];
        //清除订单

        emit OrderCancled(seller, _tokenId);
    }

    function changePrice(uint256 _tokenId, uint256 _price) external {
        address seller = orderOfId[_tokenId].seller;
        require(msg.sender == seller, "only seller can change price");

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

        Order storage order = orders[idToOrderIndex[_tokenId]]; //修改链上订单数据
        order.price = _price;

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

    function OnERC721Received( //ERC721回调函数 难点
        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(tokenId, price);
            return Magic_On_Erc721_Received;
        }

    function toUint256(
        bytes memory _bytes, 
        uint256 _start) internal pure returns (uint256) {
        require(_bytes.length >= (_start + 32), "toUint256 out of bounds");
        uint256 tempUint;

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

        return tempUint;
    }

    function removeOrder(uint256 _tokenId) internal {
        uint256 orderId = idToOrderIndex[_tokenId];
        uint256 lastOrderId = orders.length - 1;
        if (orderId != lastOrderId) {
            Order storage lastOrder = orders[lastOrderId];
            orders[orderId] = lastOrder;
            idToOrderIndex[lastOrder.tokenId] = orderId;
        }
        orders.pop();
        delete orderOfId[_tokenId];
        delete idToOrderIndex[_tokenId];
    }

}

测试

在remix提供的手动测试按钮测试基本功能后,我们可以进一步利用hardhat使用js代码进行测试。

const { expect } = require("chai");
const { ethers } = require("hardhat");

describe("Market", function () {
    let usdt, market, myNft, accountA, accountB;

    beforeEach(async () => {
        [accountA, accountB] = await ethers.getSigners();
        const USDT = await ethers.getContractFactory("cUSDT");
        usdt = await USDT.deploy();
        const MyNFT = await ethers.getContractFactory("MyNFT");
        myNft = await MyNFT.deploy(accountA.address);        
        const Market = await ethers.getContractFactory("Market");
        market = await Market.deploy(usdt.target, myNft.target);

        await myNft.safeMint(accountA.address);
        await myNft.safeMint(accountB.address);

        await usdt.approve(market.target, 10**18);
    });

    it("its erc20 address should be usdt", async() => {
        expect(await market.erc20()).to.equal(usdt.target);
    });

    it("its nft address should be myNft", async() => {
        expect(await market.erc721()).to.equal(myNft.target);
    });

    it("accountB shuold have 2 nfts", async() => {
        expect(await myNft.balanceOf(accountB.address)).to.equal(2);
    });

    it("accountA should have usdt", async() => {
        expect(await usdt.balanceOf(accountA.address)).to.equal(10**18);
    });

    // expect(await myNft['safeTransferFrom(address,address,uint256,bytes)'](accountB.address,market.target,0,price)).to.emit (market,"Neworder");
    // expect(await myNft['safeTransferFrom(address,address,uint256,bytes)'](accountB.address,market.target,1,price)).to.emit (market,"Neworder");
  
    // expect(await myNft.balanceOf(accountB.address)).to.equal(0);
    // expect(await myNft.balanceOf(market.target)).to.equal(2);
    // expect(await market.orders(0)).to.equal(true);
    // expect(await market.orders(1)).to.equal(true);

    // 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);

});    

标签:tokenId,uint256,price,await,笔记,DApp,NFT,address,market
From: https://www.cnblogs.com/ranxi169/p/18279014

相关文章

  • 算法笔记:模拟过程(螺旋遍历矩阵)
    1模拟过程“模拟过程题”通常指的是那些要求编程者通过编写代码来“模拟”或重现某个过程、系统或规则的题目。这类题目往往不涉及复杂的数据结构或高级算法,而是侧重于对给定规则的精确执行和逻辑的清晰表达。其中螺旋遍历矩阵的题目就是一类典型的模拟过程题,需要精心设......
  • 【鸿蒙学习笔记】基础组件Blank:空白填充组件
    Blank:空白填充组件Column({space:20}){Row(){Text('Bluetooth')Blank().color(Color.Yellow)Toggle({type:ToggleType.Switch}).margin({top:14,bottom:14,left:6,right:6})}.backgroundColor(Color.Pink).borderRadius(15).padd......
  • 【鸿蒙学习笔记】基础组件Progress:进度条组件
    官方文档:Progress目录标题作用最全属性迭代进度赋值风格样式作用进度条组件最全属性迭代Progress({value:20,total:100,type:ProgressType.Linear}).color(Color.Green)//颜色.width(200)//大小.height(50)//高度.value(50)//进度可更新,2......
  • #C语言基础 笔记二
    强制转换inta=5;floatb=a/2;//2.000000floatb=(float)a/2;//2.500000#include<stdio.h>intmain(intargc,charconst*argv[]){inta=5;floatb=(float)a/2;printf("%d%f\n",a,b);return0;}分支语句if......
  • 动手学深度学习5.6 GPU-笔记&练习(PyTorch)
    以下内容为结合李沐老师的课程和教材补充的学习笔记,以及对课后练习的一些思考,自留回顾,也供同学之人交流参考。本节课程地址:17使用和购买GPU【动手学深度学习v2】_哔哩哔哩_bilibili本节教材地址:5.6.GPU—动手学深度学习2.0.0documentation(d2l.ai)本节开源代码:...>d......
  • FFT 学习笔记
    \(\text{FFT}\)学习笔记多项式确定一个多项式,往往只需要知道每一次项前的系数是多少即可。众所周知,一个朴素的多项式往往可以被写成\[f(x)=\sum_{n\ge0}a_nx^n\]的形式,在这种形式下的两个多项式\(f,g\)的乘积\(h\)往往可以按照\[h(x)=(f*g)(x)=\sum_{n\ge0}(\sum_{i=0......
  • webdav协议及我的笔记方案(私有部署)
    背景用markdown用于文章写作,有几年时间了,不是很喜欢折腾,主要就是在电脑上写,用的笔记软件就是typora。由于里面有很多工作相关的,以及个人资料相关的(包含了各种账号、密码啥的),所以不敢往各种云服务上放,还是想着数据由自己来管着。自己管数据的话,就是数据存储到哪里的问题,有很多朋......
  • ESP32-点亮TFT2.4电阻触摸屏 学习笔记
    1、下载好arduinoIDE开发软件IDE(IntegratedDevelopmentEnvironment),译为集成开发环境,相当于编辑器编译器加连接器+其他。ArduinoIDE就是Arduino团队提供的一款专门为Arduino设计的编程软件,使用它,我们便能将程序从代码上传至Arduino主板。去官网下载:Software|Arduino,也......
  • Python学习笔记(二)
    目录while循环语句while循环的嵌套应用补充知识for循环函数猜数字游戏作业while循环语句练习:while循环的嵌套应用注:结束时i=i-1补充知识九九乘法表实例for循环练习:例如解决方案:再外部定义一个i=0for循环的嵌套break和continue的应用练......
  • Python学习笔记(一)
    目录 什么是变量​编辑数据类型转换语句标识符​编辑运算符​编辑字符串格式化数据输入python判断语句if语句​编辑if  elif  else常用的值类型:凡是被双引号包裹起来的都是字符串print输出,可以同时输出多个内容,用逗号隔开单行注释#  多行注释"""......