1 合约环境配置

学习视频:《DApp 设计与开发》课程简介_哔哩哔哩_bilibili

开发流程:

        1.分析需求

        2.本地开发

        3.测试审计

        4.部署测试网

        5.部署项目

用户与区块链交互

以太坊 API | IPFS API 和网关 | ETH 节点即服务 (infura.io)

环境配置

合约使用hardhat和remix进行编写运行调试

合约环境

前置要求:安装node.js [建议安装18及以上]

1.新建合约文件夹 nft-contract

2.在文件夹路径cmd 开始配置环境

命令行输入:

npm init
npm install hardhat

 命令行输入:

npx hardhat init

合约拷贝:

OpenZeppelin Contracts Wizard中使用封装好的合约,在contrasts中新建sol文件保存

 合约代码如下

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

import "@openzeppelin/contracts/token/ERC20/ERC20.sol";

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

    //没有approval,就会导致nftmarket的buy不成功,因为没有授权
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

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

contract MyNFT is ERC721, ERC721Enumerable, ERC721URIStorage, Ownable {
    uint256 private _nextTokenId;

    constructor() ERC721("MyNFT", "NFT") Ownable(msg.sender) {}

    function safeMint(address to, string memory uri) public onlyOwner {
        uint256 tokenId = _nextTokenId++;
        _safeMint(to, tokenId);
        _setTokenURI(tokenId, uri);
    }

    // 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 tokenURI(
        uint256 tokenId
    ) public view override(ERC721, ERC721URIStorage) returns (string memory) {
        return super.tokenURI(tokenId);
    }

    function supportsInterface(
        bytes4 interfaceId
    )
        public
        view
        override(ERC721, ERC721Enumerable, ERC721URIStorage)
        returns (bool)
    {
        return super.supportsInterface(interfaceId);
    }
}
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

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

/**
 * @title NFTMarket contract that allows atomic swaps of ERC20 and ERC721
 */
contract Market is IERC721Receiver {
    IERC20 public erc20;
    IERC721 public erc721;

    bytes4 internal constant MAGIC_ON_ERC721_RECEIVED = 0x150b7a02;

    struct Order {
        address seller;
        uint256 tokenId;
        uint256 price;
    }

    mapping(uint256 => Order) public orderOfId; // token id to order
    Order[] public orders;
    mapping(uint256 => uint256) public idToOrderIndex;

    event Deal(address buyer, address seller, uint256 tokenId, uint256 price);
    event NewOrder(address seller, uint256 tokenId, uint256 price);
    event CancelOrder(address seller, uint256 tokenId);
    event ChangePrice(
        address seller,
        uint256 tokenId,
        uint256 previousPrice,
        uint256 price
    );

    constructor(IERC20 _erc20, IERC721 _erc721) {
        require(
            address(_erc20) != address(0),
            "Market: IERC20 contract address must be non-null"
        );
        require(
            address(_erc721) != address(0),
            "Market: IERC721 contract address must be non-null"
        );
        erc20 = _erc20;
        erc721 = _erc721;
    }

    function buy(uint256 _tokenId) external {
        require(isListed(_tokenId), "Market: Token ID is not listed");

        address seller = orderOfId[_tokenId].seller;
        address buyer = msg.sender;
        uint256 price = orderOfId[_tokenId].price;

        require(
            erc20.transferFrom(buyer, seller, price),
            "Market: ERC20 transfer not successful"
        );
        erc721.safeTransferFrom(address(this), buyer, _tokenId);
        removeListing(_tokenId);

        emit Deal(buyer, seller, _tokenId, price);
    }

    function cancelOrder(uint256 _tokenId) external {
        require(isListed(_tokenId), "Market: Token ID is not listed");

        address seller = orderOfId[_tokenId].seller;
        require(seller == msg.sender, "Market: Sender is not seller");

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

        emit CancelOrder(seller, _tokenId);
    }

    function changePrice(uint256 _tokenId, uint256 _price) external {
        require(isListed(_tokenId), "Market: Token ID is not listed");
        address seller = orderOfId[_tokenId].seller;
        require(seller == msg.sender, "Market: Sender is not seller");

        uint256 previousPrice = orderOfId[_tokenId].price;
        orderOfId[_tokenId].price = _price;
        Order storage order = orders[idToOrderIndex[_tokenId]];
        order.price = _price;

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

    function getAllNFTs() public view returns (Order[] memory) {
        return orders;
    }

    function getMyNFTs() public view returns (Order[] memory) {
        Order[] memory myOrders = new Order[](orders.length);
        uint256 myOrdersCount = 0;

        for (uint256 i = 0; i < orders.length; i++) {
            if (orders[i].seller == msg.sender) {
                myOrders[myOrdersCount] = orders[i];
                myOrdersCount++;
            }
        }

        Order[] memory myOrdersTrimmed = new Order[](myOrdersCount);
        for (uint256 i = 0; i < myOrdersCount; i++) {
            myOrdersTrimmed[i] = myOrders[i];
        }

        return myOrdersTrimmed;
    }

    function isListed(uint256 _tokenId) public view returns (bool) {
        return orderOfId[_tokenId].seller != address(0);
    }

    function getOrderLength() public view returns (uint256) {
        return orders.length;
    }

    /**
     * @dev List a good using a ERC721 receiver hook
     * @param _operator the caller of this function
     * @param _seller the good seller
     * @param _tokenId the good id to list
     * @param _data contains the pricing data as the first 32 bytes
     */
    function onERC721Received(
        address _operator,
        address _seller,
        uint256 _tokenId,
        bytes calldata _data
    ) public override returns (bytes4) {
        require(_operator == _seller, "Market: Seller must be operator");
        uint256 _price = toUint256(_data, 0);
        placeOrder(_seller, _tokenId, _price);

        return MAGIC_ON_ERC721_RECEIVED;
    }

    // https://stackoverflow.com/questions/63252057/how-to-use-bytestouint-function-in-solidity-the-one-with-assembly
    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;
    }

    function placeOrder(
        address _seller,
        uint256 _tokenId,
        uint256 _price
    ) internal {
        require(_price > 0, "Market: Price must be greater than zero");

        orderOfId[_tokenId].seller = _seller;
        orderOfId[_tokenId].price = _price;
        orderOfId[_tokenId].tokenId = _tokenId;

        orders.push(orderOfId[_tokenId]);
        idToOrderIndex[_tokenId] = orders.length - 1;

        emit NewOrder(_seller, _tokenId, _price);
    }

    function removeListing(uint256 _tokenId) internal {
        delete orderOfId[_tokenId];

        uint256 orderToRemoveIndex = idToOrderIndex[_tokenId];
        uint256 lastOrderIndex = orders.length - 1;

        if (lastOrderIndex != orderToRemoveIndex) {
            Order memory lastOrder = orders[lastOrderIndex];
            orders[orderToRemoveIndex] = lastOrder;
            idToOrderIndex[lastOrder.tokenId] = orderToRemoveIndex;
        }

        orders.pop();
    }
}
 连接remix

首先在命令行中输入:

在remix-ide中点击

连接成功

测试

在test文件夹中新建一个market.js测试代码

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

describe("Market", function () {
  let usdt, market, myNft, account1, account2;
  let baseURI = "https://sameple.com/";

  beforeEach(async () => {
    [account1, account2] = await ethers.getSigners();
    // const MAX_ALLOWANCE = BigNumber.from(2).pow(256).sub(1);
    const USDT = await ethers.getContractFactory("cUSDT");
    usdt = await USDT.deploy();
    const MyNFT = await ethers.getContractFactory("MyNFT");
    myNft = await MyNFT.deploy();
    const Market = await ethers.getContractFactory("Market");
    market = await Market.deploy(usdt.target, myNft.target);

    await myNft.safeMint(account1.address, baseURI + "0");
    await myNft.safeMint(account1.address, baseURI + "1");
    await myNft.approve(market.target, 0);
    await myNft.approve(market.target, 1);
    await usdt.transfer(account2.address, "10000000000000000000000");
    await usdt
      .connect(account2)
      .approve(market.target, "1000000000000000000000000");
  });

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

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

  it("account1 should have 2 nfts", async function () {
    expect(await myNft.balanceOf(account1.address)).to.equal(2);
  });

  it("account2 should have 10000 USDT", async function () {
    expect(await usdt.balanceOf(account2.address)).to.equal(
      "10000000000000000000000"
    );
  });

  it("account2 should have 0 nfts", async function () {
    expect(await myNft.balanceOf(account2.address)).to.equal(0);
  });

  it("account1 can list two nfts to market", async function () {
    const price =
      "0x0000000000000000000000000000000000000000000000000001c6bf52634000";
    // let price = "0x0100"
    expect(
      await myNft["safeTransferFrom(address,address,uint256,bytes)"](
        account1.address,
        market.target,
        0,
        price
      )
    ).to.emit(market, "NewOrder");
    expect(
      await myNft["safeTransferFrom(address,address,uint256,bytes)"](
        account1.address,
        market.target,
        1,
        price
      )
    ).to.emit(market, "NewOrder");

    expect(await myNft.balanceOf(account1.address)).to.equal(0);
    expect(await myNft.balanceOf(market.target)).to.equal(2);
    expect(await market.isListed(0)).to.equal(true);
    expect(await market.isListed(1)).to.equal(true);

    expect((await market.getAllNFTs())[0][0]).to.equal(account1.address);
    expect((await market.getAllNFTs())[0][1]).to.equal(0);
    expect((await market.getAllNFTs())[0][2]).to.equal(price);
    expect((await market.getAllNFTs())[1][0]).to.equal(account1.address);
    expect((await market.getAllNFTs())[1][1]).to.equal(1);
    expect((await market.getAllNFTs())[1][2]).to.equal(price);
    expect(await market.getOrderLength()).to.equal(2);

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

  it("account1 can unlist one nft from market", async function () {
    const price =
      "0x0000000000000000000000000000000000000000000000000001c6bf52634000";
    // let price = "0x0100"
    expect(
      await myNft["safeTransferFrom(address,address,uint256,bytes)"](
        account1.address,
        market.target,
        0,
        price
      )
    ).to.emit(market, "NewOrder");
    expect(
      await myNft["safeTransferFrom(address,address,uint256,bytes)"](
        account1.address,
        market.target,
        1,
        price
      )
    ).to.emit(market, "NewOrder");

    expect(await market.cancelOrder(0)).to.emit(market, "CancelOrder");
    expect(await market.getOrderLength()).to.equal(1);
    expect(await market.isListed(0)).to.equal(false);
    expect((await market.getMyNFTs()).length).to.equal(1);
  });

  it("account1 can change price of nft from market", async function () {
    const price =
      "0x0000000000000000000000000000000000000000000000000001c6bf52634000";
    expect(
      await myNft["safeTransferFrom(address,address,uint256,bytes)"](
        account1.address,
        market.target,
        0,
        price
      )
    ).to.emit(market, "NewOrder");
    expect(
      await myNft["safeTransferFrom(address,address,uint256,bytes)"](
        account1.address,
        market.target,
        1,
        price
      )
    ).to.emit(market, "NewOrder");

    expect(await market.changePrice(1, "10000000000000000000000")).to.emit(
      market,
      "ChangePrice"
    );
    expect((await market.getMyNFTs()).length).to.equal(2);
    expect((await market.getMyNFTs())[1][2]).to.equal(
      "10000000000000000000000"
    );
  });

  it("account2 can buy nft from market", async function () {
    const price =
      "0x0000000000000000000000000000000000000000000000000001c6bf52634000";
    expect(
      await myNft["safeTransferFrom(address,address,uint256,bytes)"](
        account1.address,
        market.target,
        0,
        price
      )
    ).to.emit(market, "NewOrder");
    expect(
      await myNft["safeTransferFrom(address,address,uint256,bytes)"](
        account1.address,
        market.target,
        1,
        price
      )
    ).to.emit(market, "NewOrder");

    expect(await market.connect(account2).buy(1)).to.emit(market, "Deal");

    expect(await market.getOrderLength()).to.equal(1);
    expect(await usdt.balanceOf(account1.address)).to.equal(
      "99990000000500000000000000"
    );
    expect(await usdt.balanceOf(account2.address)).to.equal(
      "9999999500000000000000"
    );
    expect(await myNft.ownerOf(1)).to.equal(account2.address);
  });
});

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值