---
# System prepended metadata

title: Solidity 101 開發實作課程講義

---

# Solidity 開發實作講義

### 多簽錢包

程式碼 [gist](https://gist.github.com/madeinfree/d345abdc9ee50308d1ff5c57b71dca0b)

### Rug Pull Token

```solidity!
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;

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

contract RugPullToken is ERC20 {
    constructor(uint256 initialSupply) ERC20("Rug Pull", "RP") {}

    function mint(uint256 _amount) public {
        _mint(msg.sender, _amount);
    }
}
```

### ERC721 KryptoToy NFT

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

import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts/utils/Counters.sol";

contract KryptoToy is ERC721 {
    using Counters for Counters.Counter;
    Counters.Counter private _tokenIds;

    constructor() ERC721("KryptoToy", "KT") {}

    function mint(address to) public {
        uint256 tokenId = _tokenIds.current();
        _mint(to, tokenId);

        _tokenIds.increment();
    }
}
```

### 擴充小抄

```solidity!
address private _owner;

_owner = msg.sender;

modifier onlyOwner {
    require(msg.sender == _owner, "not owner");
    _;
}

function ownerMint() public onlyOwner {
    uint256 tokenId = _tokenIds.current();
    _mint(msg.sender, tokenId);

    _tokenIds.increment();
}

function burn(uint256 _tokenId) public {
    _burn(_tokenId);
}

// -----------------------------------//

uint256 mintPrice = 0.03 ether;
```

### 荷蘭拍

[時間轉換器](https://www.epochconverter.com/)

```solidity!
struct Auction {
    uint256 startTime;
    uint256 timeStep;
    uint256 startPrice;
    uint256 endPrice;
    uint256 priceStep;
    uint256 stepNumber;
}

Auction public auction;

// "50000000000000000" -> 0.05 ether
function getAuctionPrice() public view returns (uint256) {
    Auction memory currentAuction = auction;
    if (block.timestamp < currentAuction.startTime) {
        return currentAuction.startPrice;
    }
    uint256 step = (block.timestamp - currentAuction.startTime) /
        currentAuction.timeStep;
    if (step > currentAuction.stepNumber) {
        step = currentAuction.stepNumber;
    }
    return
        currentAuction.startPrice > step * currentAuction.priceStep
            ? currentAuction.startPrice - step * currentAuction.priceStep
            : currentAuction.endPrice;
}

function setAuction(
    uint256 _startTime,
    uint256 _timeStep,
    uint256 _startPrice,
    uint256 _endPrice,
    uint256 _priceStep,
    uint256 _stepNumber
) public onlyOwner {
    auction.startTime = _startTime; // 開始時間
    auction.timeStep = _timeStep; // 5 多久扣一次
    auction.startPrice = _startPrice; // 50000000000000000 起始金額
    auction.endPrice = _endPrice; // 10000000000000000 最後金額
    auction.priceStep = _priceStep; // 10000000000000000 每次扣除多少金額
    auction.stepNumber = _stepNumber; // 5 幾個階段
}

function auctionMint() external payable {
    require(msg.value >= getAuctionPrice(), "not enough value");
    uint256 tokenId = _tokenIds.current();
    _mint(msg.sender, tokenId);
    _tokenIds.increment();
}
```

### 白名單機制

```solidity!
address[] public whitelistArray;
mapping(address => bool) public whitelistMapping;

/** 
["0x5B38Da6a701c568545dCfcB03FcB875f56beddC4",
    "0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2",
    "0x4B20993Bc481177ec7E8f571ceCaE8A9e22C02db"]
*/
for (uint256 i; i < whitelist.length; i++) {
    whitelistArray.push(whitelist[i]);
    whitelistMapping[whitelist[i]] = true;
}

function inWhitelistArray() private view returns (bool) {
    for (uint256 i; i < whitelistArray.length; i++) {
        if (whitelistArray[i] == msg.sender) {
            return true;
        }
    }
    return false;
}

function whitelistMintFromArray() external {
    require(inWhitelistArray(), "not in whitelist");
    uint256 tokenId = _tokenIds.current();
    _mint(msg.sender, tokenId);
    _tokenIds.increment();
}

function whitelistMintFromMapping() external {
    require(whitelistMapping[msg.sender], "not in whitelist");
    uint256 tokenId = _tokenIds.current();
    _mint(msg.sender, tokenId);
    _tokenIds.increment();
}
```

### Merkle Tree

![](https://i.imgur.com/kmqbVkk.png)

```bash!
npm install merkletreejs ethers
```

```javascript!
const { MerkleTree } = require("merkletreejs");
const { ethers } = require("ethers");

const leaves = [
  { address: "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266" },
  { address: "0x70997970C51812dc3A010C7d01b50e0d17dc79C8" },
  { address: "0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC" },
].map((x) =>
  ethers.utils.keccak256(ethers.utils.solidityPack(["address"], [x.address]))
);
const tree = new MerkleTree(leaves, ethers.utils.keccak256, {
  sortPairs: true,
});

const root = tree.getHexRoot();
const leaf = ethers.utils.keccak256(
  ethers.utils.solidityPack(
    ["address"],
    ["0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC"]
  )
);
console.log(tree.getHexProof(leaf));
console.log(tree.verify(tree.getHexProof(leaf), leaf, root));
```

### Merkle Proof 合約小抄

```solidity!
import "@openzeppelin/contracts/utils/cryptography/MerkleProof.sol";

bytes32 merkleRoot;

constructor(address[] memory whitelist, bytes32 _root)

merkleRoot = _root;

function whitelistMintFromMerkleProof(
    bytes32[] memory _proof
) external {
    require(MerkleProof.verify(_proof, merkleRoot, keccak256(abi.encodePacked(msg.sender))), "not in merkle proof whitelist");
    uint256 tokenId = _tokenIds.current();
    _mint(msg.sender, tokenId);
    _tokenIds.increment();
}

// root 0x78d3daf2836a57661b587d5f2723b86785c8c3c007b63606ed332cb4a69da702

// proof ["0xe9707d0e6171f728f7473c24cc0432a9b07eaaf1efed6a137a4a8c12c79552d9","0x343750465941b29921f50a28e0e43050e5e1c2611a3ea8d7fe1001090d5e1436"]
```


### signature 簽章機制

```javascript!
const { keccak256, toBuffer, ecsign, bufferToHex } = require('ethereumjs-util')
const { ethers } = require('ethers')

const createVoucher = function (hash, signerPvtKey) {
  const privateKey = new Buffer.from(signerPvtKey, 'hex')
  return ecsign(hash, privateKey)
}

const generateHashBuffer = function (typesArray, valueArray) {
  return keccak256(
    toBuffer(ethers.utils.defaultAbiCoder.encode(typesArray, valueArray))
  )
}

const serializeVoucher = function (voucher) {
  return {
    r: bufferToHex(voucher.r),
    s: bufferToHex(voucher.s),
    v: voucher.v,
  }
}

const serializeVoucherForInput = function (voucher) {
  return [bufferToHex(voucher.r), bufferToHex(voucher.s), voucher.v]
}

const hash = generateHashBuffer(
  ['address', 'uint256'],
  ['0x5B38Da6a701c568545dCfcB03FcB875f56beddC4', 0]
)

voucher = createVoucher(
  hash,
  Buffer.from(
    '0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d'.substring(
      2,
      66
    ),
    'hex'
  )
)

console.log(serializeVoucherForInput(voucher))
```

```solidity!
struct Voucher {
    bytes32 r;
    bytes32 s;
    uint8 v;
}

address _signer = 0x70997970C51812dc3A010C7d01b50e0d17dc79C8;

function signatureMint(
    uint256 tokenId,
    Voucher memory voucher
) public {
    bytes32 digest = keccak256(abi.encode(msg.sender, tokenId));

    require(_isVerifiedVoucher(digest, voucher), "signature failed..");

    _mint(msg.sender, tokenId);
}

function _isVerifiedVoucher(bytes32 digest, Voucher memory voucher) internal view returns (bool) {
    address signer = ecrecover(digest, voucher.v, voucher.r, voucher.s);
    require(signer != address(0), "ECDSA: invalid signature");
    return signer == _signer;
}
```

### 鏈上亂數
[Chainlink Goerli](https://vrf.chain.link/goerli)
`區塊資料 block.timestamp`

```solidity!
// SPDX-License-Identifier: MIT
pragma solidity 0.8.16;

contract Random {
    function generateRandom() private view returns (uint256) {
        return uint256(
            keccak256(abi.encodePacked(blockhash(block.number - 1), block.timestamp))
        );
    }
    
    function getAnswer() public view returns (uint256) {
        return generateRandom() % 1000;
    }
}
```

`Chainlink VRF`

```solidity=
// SPDX-License-Identifier: MIT
pragma solidity 0.8.16;

import "@chainlink/contracts/src/v0.8/interfaces/VRFCoordinatorV2Interface.sol";
import "@chainlink/contracts/src/v0.8/VRFConsumerBaseV2.sol";

contract Random is VRFConsumerBaseV2 {

    VRFCoordinatorV2Interface COORDINATOR;
    uint256 public random;

    uint64 s_subscriptionId;
    address vrfCoordinator = 0x2Ca8E0C643bDe4C2E08ab1fA0da3401AdAD7734D;
    bytes32 s_keyHash = 0x79d3d8832d904592c0bf9818b621522c988bb8b0c05cdc3b15aea1b6e8db0c15;
    uint16 requestConfirmations = 3;
    uint32 callbackGasLimit = 250000;
    uint32 numWords = 1;

    uint256[] public requestIds;

    constructor(uint64 subscriptionId) VRFConsumerBaseV2(vrfCoordinator) {
        COORDINATOR = VRFCoordinatorV2Interface(vrfCoordinator);
        s_subscriptionId = subscriptionId;
    }

    function generateRandom() private view returns (uint256) {
        return uint256(
            keccak256(abi.encodePacked(blockhash(block.number - 1), block.timestamp))
        );
    }

    function generateRandomFromChainlink() public returns (uint256 requestId) {
        requestId = COORDINATOR.requestRandomWords(
            s_keyHash,
            s_subscriptionId,
            requestConfirmations,
            callbackGasLimit,
            numWords
        );
    }

    function getAnswer() public view returns (uint256) {
        require(random != 0, "random not initialize.");
        return random % 1000;
    }

    function fulfillRandomWords(uint256 requestId, uint256[] memory randomWords) internal override {
        requestIds.push(requestId);
        random = randomWords[0];
    }
}
```