# 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

```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];
}
}
```