Open1

KGF

ぽけなぽけな

初心者がKGFのコントラクトを読んだメモ

NFT Contract

mintNFTs

  • SaleContract以外からの呼び出しを拒否
  • MAX_SUPPLY以下だったらmintするよ
  • MAX_SUPPLYはimmutable変数で、コンストラクタで値を設定
  function mintNFTs(address to, uint256[] memory tokenIds) public virtual {
    require(msg.sender == saleContract, "Nice try lol");
    uint256 length = tokenIds.length;
    for (uint256 i; i < length; ++i) {
      require(tokenIds[i] != 0 && tokenIds[i] <= MAX_SUPPLY, "ID > MAX_SUPPLY");
    }
    _mintNFTs(to, tokenIds);
  }

prepareSale

  • コントラクトオーナーが一回だけSaleContractを変更できる
  function prepareSale(address _saleContract) public onlyOwner {
    require(saleContract == address(0), "Immutable");
    saleContract = _saleContract;
  }

SaleContract

変数

  • アドレスごとの購入数を管理する変数
  • 現状分かっている管理情報は、WaveごとtokenIdと購入数
  • 正確には違うかもだけど、イメージ的には最初の32ビット→Wave1の購入情報、次の32ビット→Wave2の購入情報みたいな感じ。32ビット×6Wave = 192ビット
  • 最後から64ビット目からはmintedBalance。最後の32ビットはbalance
  // Purchases are compressed into a single uint256, after 6 rounds the limit is simply removed anyways.
  // The last uint32 slot is reserved for their balance. (left-most bytes first)
 mapping(address => uint256) purchases;

buyKGF

  • ちゃんと在庫があるか確認してる
  • amoutForSaleは7717が指定されてる。amoutSoldについては後述。
  function buyKGF(uint256 count) external payable nonReentrant {
    uint256 _amountSold = amountSold;
    uint256 _amountForSale = amountForSale; // 7717
    uint256 remaining = _amountForSale - _amountSold;
    require(remaining != 0, "Sold out! Sorry!");
  • 販売開始時間がどうかチェック
  • EOAから直接このコントラクトをコールしているかチェック。めちゃ参考になる記事
  • mintする個数が0より大きいかチェック
    require(block.timestamp >= startTime, "Sale has not started");
    require(tx.origin == msg.sender, "Only direct calls pls");
    require(count > 0, "Cannot mint 0");
  • KGFのpublic saleでは時間経過ごとによってWaveが進み、それによってmint数が変更される仕組みが存在した。それ関連の実装。
  • ちゃんとETHを送ってるかチェック
    uint256 wave = currentWave();
    require(count <= maxPerTX(wave), "Max for TX in this wave");
    require(wave < MAX_WAVES, "Not in main sale");
    require(msg.value == count * buyPrice, "Not enough ETH");
  • 3個mintしようとしたけど在庫が2個しかなかった場合、2個の購入に切り替える処理。
    // Adjust for the last mint being incomplete.
    uint256 ethAmountOwed;
    if (count > remaining) {
      ethAmountOwed = buyPrice * (count - remaining);
      count = remaining;
    }
  • Wave内では、1アドレスにつき1回しか購入できないよう制限
    uint256 purchaseInfo = purchases[msg.sender];
    require(!hasDoneWave(purchaseInfo, wave), "Already purchased this wave");
  • startSupply: amountSold + devSupply + 1
  • amountSold: 売れた数(正確にはトランザクションはまだ)
  • _createNewPurchaseInfo()については後述
    uint256 startSupply = currentMintIndex();
    amountSold = _amountSold + count;
    purchases[msg.sender] = _createNewPurchaseInfo(purchaseInfo, wave, startSupply, count);
  • クライアント側へevent通知
  • 上の"3個mintしようとしたけど在庫が2個しかなかった場合"に返金する処理
    emit Reserved(msg.sender, count);

    if (ethAmountOwed > 0) {
      sendValue(payable(msg.sender), ethAmountOwed);
    }
  }
  • 変数purchaseで購入するtokenIdと購入数を管理する
  • 具体的には111101 00000001のような感じで、前半ビットがtokenId, 残りが購入数
  • 上の算出方法は、startingSupplyを二進数に変換して、8ビット分左シフト
  • 購入数の部分は|=で増やしてる
  function _createNewPurchaseInfo(uint256 purchaseInfo, uint256 wave, uint256 _startingSupply, uint256 count) internal pure returns (uint256) {
    require(wave < MAX_WAVES, "Not a wave index");
    uint256 purchase = _startingSupply<<8;
    purchase |= count;
    uint256 newWaveSlot = _writeWaveSlot(purchaseInfo, wave, purchase);
    uint256 newBalance = _getBalance(purchaseInfo) + count;
    return _writeDataSlot(newWaveSlot, BALANCE_SLOT_INDEX, newBalance);
  }
  • Waveごとの購入情報を書き込む
  function _writeWaveSlot(uint256 purchase, uint256 index, uint256 data) internal pure returns (uint256) {
    require(index < MAX_WAVES, "not valid index");
    uint256 writeIndex = 256 - ((index+1) * 32);
    require(uint32(purchase<<writeIndex) == 0, "Cannot write in wave slot twice");
    uint256 newSlot = data<<writeIndex;
    uint256 newPurchase = purchase | newSlot;
    return newPurchase;
  }