---
# System prepended metadata

title: 5/1

---

# 5/1


團體作業：目標做出“一個合約＆一個測試”

ticket.sol

```
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "@openzeppelin/contracts/utils/math/SafeMath.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "./Ticket.sol";

contract Ticket is Ownable {
    // Ticket struct
	struct Ticket {
        uint expireTime;// the time when the ticket expires
        string name; // the name of the ticket
    }
	
    mapping(address => Ticket[]) public ticketsOwned;

    constructor() {
        // Every time new ticket and push to the list
        ticketsOwned[msg.sender].push(Ticket (block.timestamp + 1 days, "1 day tickect")); // record a list of tickets for each user
        ticketsOwned[msg.sender].pop(); // remove the ticket due to expiration or use
        for(uint256 i = 0; i < ticketsOwned[msg.sender].length; i++) { //checkOwner if the user has the ticket
            if(keccak256(bytes[ticketsOwned[msg.sender][i].name]) == keccak256(bytes["myTickect"])) {// use string compare library // if the user has the ticket
                // do something
                break;
              
            }
        }
    }

}
```

--

實行MVP：看哪部分要先完成最少需要完成的功能

1.先for一個活動
2.


---


erc1155

setUri

---


ticket.sol

<!-- function mint(address _to, uint256 _quantity) external payable 
     -->
     
```
import "@openzeppelin/contracts/token/ERC1155/ERC1155.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/utils/Strings.sol";
import "@openzeppelin/contracts/utils/Counters.sol";


contract Ticket is Ownable, ERC1155("xxx.json") {

    uint public constant MAX_SUPPLY = 1000; //NFT 的最大發行量
    uint public constant MAX_PER_MINT = 1; //每次最多可以買幾張
    uint public constant PRICE = 0.01 ether; //每張的價格
    using Counters for Counters.Counter; //使用 Counters library
    Counters.Counter private _tokenIds; 
    
    mapping(address => Ticket[]) public ticketsOwned;

    // Ticket struct
    struct Ticket {
        uint expireTime;// the time when the ticket expires
        string name; // the name of the ticket
    }
    
     
    <!-- function -->
     
    // 購買
    function mintNfts(uint _count) public payable {

        uint nextId = _tokenIds.current();

        require(nextId + _count < MAX_SUPPLY, "Not enough NFT tickets left!");
        require(_count > 0 && _count <= MAX_PER_MINT, "Cannot mint specified number of NFT tickets.");
        require(msg.value >= price * _count, "Not enough ether to purchase NFTs.");

        for (uint i = 0; i < _count; i++) {
            string memory metadata = generateMetadata(nextId + i);
            _mintSingleNft(msg.sender, metadata);
        }
    }
    
     // Mint a single NFT ticket
    function _mintSingleNft(address _wAddress, string memory _tokenURI) private {
        // Sanity check for absolute worst case scenario
        require(totalSupply() == _tokenIds.current(), "Indexing has broken down!");
        uint newTokenID = _tokenIds.current();
        _safeMint(_wAddress, newTokenID);
        _tokenIds.increment();
    }
    
    
     // Withdraw ether
    function withdraw() public payable onlyOwner {
        uint balance = address(this).balance;
        require(balance > 0, "No ether left to withdraw");

        (bool success, ) = (msg.sender).call{value: balance}("");
        require(success, "Transfer failed.");
    }
    
    // 兌換
    function _burn(uint256 tokenId) internal override(ERC1155) {
        super._burn(tokenId);
    }
    
    function supportsInterface(bytes4 interfaceId)
        public
        view
        override(ERC1155)
        returns (bool)
    {
        return super.supportsInterface(interfaceId);
    }
}

```



test.js

<!-- 
  it("Name should be as expected", async function () {
    expect(await token.name()).to.equal("MyTicket");
  });



 it("Should be able to mint to specific account", async function () {
    await token.mint(owner.address, 0, {
      value: ethers.utils.parseEther("0.1"),
    });
    expect(await token.ownerOf(0)).to.equal(owner.address);

    await token.batchMint(acc1.address, [1, 2], {
      value: ethers.utils.parseEther("0.2"),
    });

    expect(await token.ownerOf(1)).to.equal(acc1.address);
    expect(await token.ownerOf(2)).to.equal(acc1.address);
  });

  it("Should revert when not enough funds", async function () {
    expect(token.mint(owner.address, 0)).to.be.revertedWith(
      "Not enough funds to mint."
    );

    expect(token.batchMint(acc2.address, [0, 1])).to.be.revertedWith(
      "Not enough funds to mint."
    );
  });
 -->
 
 
```
const { expect } = require("chai");

describe("MyTicket", async function () {
  const [owner,] = await ethers.getSigners();
  let token;
   
  beforeEach('Setup Contract', async () => {
        const Ticket = await ethers.getContractFactory('Ticket')
        nft = await Ticket.deploy()
        await nft.deployed()
        nftContractAddress = await nft.address
  })
    
  it("Should support interface", async function () {
    expect(await token.supportsInterface("0x00000001"), false);
  });
});
```


--
### Ticket.sol

<!--         // Mint a ticket and add it to the user's list
        _mintSingleNft(msg.sender, "1 day ticket");
        _ticketsOwned[msg.sender].push(Ticket(block.timestamp + 1 days, "1 day ticket"));
    } -->
```
pragma solidity ^0.8.0;

import "@openzeppelin/contracts/token/ERC1155/ERC1155.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/utils/Strings.sol";
import "@openzeppelin/contracts/utils/Counters.sol";

contract Ticket is Ownable, ERC1155("https://donexttime.com/{id}.json") {
    using Counters for Counters.Counter;
    Counters.Counter private _tokenIds; // 建立一個 _tokenIds 的計數器
    
    uint256 public constant MAX_SUPPLY = 1000;
    address[MAX_SUPPLY] private _owners;
    uint256 public constant MAX_PER_MINT = 2;
    uint256 public constant PRICE = 0.01 ether;
    
    mapping(address => Ticket[]) private _ticketsOwned;

    struct Ticket {
        uint256 expireTime;
        string name;
    }

    event TicketUsed(address indexed owner, string name);

    constructor(string memory uri) ERC1155(uri) {
        setTokenURI(newTokenID, _tokenURI);
    }

    function mintNfts(uint256 count) external payable {
        uint256 nextId = _tokenIds.current(); // get the next token id

        require(nextId + count < MAX_SUPPLY, "Supply limit exceeded");
        require(count > 0 && count <= MAX_PER_MINT, "Can only mint one NFT per address");
        require(msg.value >= PRICE * count, "Insufficient ether sent to purchase tickets");

        for (uint256 i = 0; i < count; i++) {
            string memory metadata = uri(nextId + i);
            _mintSingleNft(msg.sender, metadata);
            _ticketsOwned[msg.sender].push(Ticket(block.timestamp + 1 days, "1 day ticket"));
        }
    }

    function useTicket(string memory name) external {
        for (uint256 i = 0; i < _ticketsOwned[msg.sender].length; i++) {
            if (keccak256(bytes(_ticketsOwned[msg.sender][i].name)) == keccak256(bytes(name))) {
                delete _ticketsOwned[msg.sender][i];
                emit TicketUsed(msg.sender, name);
                return;
            }
        }
        revert("No ticket found");
    }

    function withdraw() external onlyOwner {
        uint256 balance = address(this).balance;
        require(balance > 0, "No ether left to withdraw");

        (bool success, ) = payable(msg.sender).call{value: balance}("");
        require(success, "Transfer failed");
    }

    function _burn(address account, uint256 id, uint256 amount) internal virtual override {
        super._burn(account, id, amount);
    }

    function uri(uint256 tokenId) public pure override returns (string memory) {
        return string(abi.encodePacked("https://donexttime.com/", Strings.toString(tokenId), ".json"));
    }

    function _mintSingleNft(address owner, string memory tokenURI) private {
        require(totalSupply() == _tokenIds.current, "Indexing has broken down!");
        uint newTokenID = _tokenIds.current(); 
        _setTokenURI(newTokenID, _tokenURI); 
        _tokenIds.increment(); 
    }
    function supportsInterface(bytes4 interfaceId)public view
        override(ERC1155)
        returns (bool)
    {
        return super.supportsInterface(interfaceId);
    }
}
```


### test.js

```
const { expect } = require("chai");

describe("MyTicket", async function () {
  const [owner,] = await ethers.getSigners();
  let nft;
  let nftContractAddress;
   
  beforeEach('Ticket', async () => {
        const Ticket = await ethers.getContractFactory('Ticket')
        nft = await Ticket.deploy()
        await nft.deployed()
        nftContractAddress = await nft.address
  })
    
it("Should mint NFTs", async function () {
  const count = 1;
  const price = ethers.utils.parseEther("0.01");
  const tokenId = await nft.tokenIds();
  await expect(nft.mintNfts(count, {value: price}))
    .to.emit(nft, "TransferSingle")
    .withArgs(owner.address, ethers.constants.AddressZero, owner.address, tokenId, count);
  expect(await nft.balanceOf(owner.address, tokenId)).to.equal(count);
});

  it("Should support interface", async function () {
    expect(await nft.supportsInterface("ERC1155 "), false);
  });
});

```

測試案例中，使用了Mocha的beforeEach函數在每個測試執行之前設置智能合約。它使用ethers.js的getContractFactory和deploy函數創建新的Ticket合約實例，然後獲取其地址並存儲到變數nftContractAddress中。此外，它還獲取簽署者帳戶的地址並存儲到變數owner中。另外還驗證兩個功能:
1. mintNfts：
用於創建和發行新的NFT代幣，並將它們分配給指定的帳戶。此測試案例使用ethers.js的parseEther將0.01 ETH轉換為相應的wei數量，並在呼叫mintNfts時將其作為value參數傳遞。接著，使用Chai的expect語法檢查呼叫mintNfts是否觸發了一個名為TransferSingle的事件，並且檢查事件觸發時的引數是否符合預期。withArgs語法用於確保事件引數與指定的值匹配。
1. supportsInterface：
用於檢查合約是否實現了指定的介面。此測試案例使用Chai的expect語法檢查合約是否未實現ERC1155介面。








---

待辦清單：
1.把 struct 加進去 code
2.測試再新增一些

---

參考資料:
https://docs.openzeppelin.com/test-helpers/0.5/api#expectEvent
https://github.com/z-institute/SBF-BONK/blob/main/contracts/CyberBonk.sol
https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC1155/ERC1155.sol
https://zhuanlan.zhihu.com/p/561647445
