如何NFT交易市场合约以适应需求?

摘要:笔记对应课程内容为成都信息工程大学区块链产业学院老师梁培利的DApp 设计与开发 10-13 课 笔记中提到的名词不做过多解释 不懂就搜! 开发准备: 编辑IDE环境:Vscode+Hardhat 调试:Remix ERC
笔记对应课程内容为成都信息工程大学区块链产业学院老师梁培利的DApp 设计与开发 10-13 课 笔记中提到的名词不做过多解释 不懂就搜! 开发准备: 编辑IDE环境:Vscode+Hardhat 调试:Remix ERC标准智能合约生成巫师:https://docs.openzeppelin.com/contracts/5.x/wizard 动手学 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编译版本上有什么区别? solcjs 和 hardhat 都是与 Solidity 相关的工具,但在管理 Solidity 编译版本上有一些区别: solcjs: 独立的 Solidity 编译器:solcjs 是 Solidity 的官方编译器的 JavaScript 包装器。它允许你在 JavaScript 或 Node.js 环境中调用 Solidity 编译器,以便编译和部署智能合约。 版本管理:solcjs 可以通过 npm(Node.js 的包管理器)来安装不同版本的 Solidity 编译器。你可以选择安装并使用特定的 Solidity 版本来编译你的合约。 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); }); 一些小技巧 hardhat-abi-explore 安装 要安装 hardhat-abi-explore,可以使用 npm 或者 yarn: npm install --save-dev hardhat-abi-explore 或者 yarn add --dev hardhat-abi-explore 配置 在 Hardhat 项目中,你需要在 hardhat.config.js 文件中添加以下内容: require('hardhat-abi-explore'); 使用命令 安装并配置后,你可以使用以下命令来生成 ABI 目录: npx hardhat abi:explore 这将会根据你的合约代码自动创建一个 ABI 目录,并将每个合约的 ABI 存储在其中。 flatten 安装 同样地,使用 npm 或 yarn 来安装 truffle-flattener: npm install -g truffle-flattener 或者 yarn global add truffle-flattener 使用命令 安装完成后,你可以使用以下命令来扁平化你的合约代码: truffle-flattener <ContractName.sol> > FlatContract.sol 这将会将指定的合约及其所有依赖的代码全部合并到一个文件中,输出为 FlatContract.sol 文件。 通过这些步骤,你可以更方便地管理和部署 Solidity 合约代码。