# 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