# [Bunzz Decipher] Nounsの『NounsAuctionHouse』コントラクトを理解しよう！

> NounsAuctionHouse（名詞オークションハウス） コントラクトは、Nounsエコシステムの中で大切な部分で、デジタルアセットの所有権を表す特別なERC721トークンである「Nouns」をオークション形式で取引するための仕組みを提

- URL: https://cardene.dev/articles/c7998dc19e5ae9
- 公開日: 2023-09-01
- 更新日: 2023-09-01
- トピック: Blockchain

# はじめに

初めまして。
**CryptoGames**というブロックチェーンゲーム企業でエンジニアをしている **cardene（かるでね）** です！
スマートコントラクトを書いたり、フロントエンド・バックエンド・インフラと幅広く触れています。

https://cryptogames.co.jp/

代表的なゲームは**クリプトスペルズ**というブロックチェーンゲームです。

https://cryptospells.jp/

今回はBunzzの新機能『**DeCipher**』を使用して、Nounsの「**NounsSeeder**」コントラクトを見てみようと思います。

『**DeCipher**』はAIを使用してコントラクトのドキュメントを自動生成してくれるサービスです。

https://www.bunzz.dev/decipher

詳しい使い方に関しては以下の記事を参考にしてください！

https://zenn.dev/heku/articles/33266f0c19d523

:::message
この記事はあくまで個人が「DeCipher」を使用してみての記事になります。
間違った部分などもあるかと思いますが、その際はコメントなどで教えてください。
:::

今回使用する『**DeCipher**』のリンクは以下になります。

https://app.bunzz.dev/decipher/chains/1/addresses/0xF15a943787014461d94da08aD4040f79Cd7c124e

Etherscanのリンクは以下になります。

https://etherscan.io/address/0xF15a943787014461d94da08aD4040f79Cd7c124

# 概要

**NounsAuctionHouse（名詞オークションハウス）** コントラクトは、Nounsエコシステムの中で大切な部分で、デジタルアセットの所有権を表す特別な**ERC721**トークンである「**Nouns**」をオークション形式で取引するための仕組みを提供します。
このコントラクトは、買い手と売り手の両方にとって、公平で安全なオークションプロセスを実現するために設計されています。

## 目的
**NounsAuctionHouse**コントラクトのメインの目的は、ユーザーが**Nouns**をオークションを通じて取引できる分散型のマーケットプレイスを提供することです。
これによって、**Noun**の所有者は自分のトークンを売りたい場合に、それを購入したい潜在的な買い手と繋げる役割を果たします。

## 責務
- **オークションの管理**
**NounsAuctionHouse**コントラクトは、新しいオークションを開始したり、入札を受け付けたり、最高額の入札を決定するなど、オークションの全体的なプロセスを担当し、オークションが公正で透明なものとなるようにします。

- **入札の処理**
コントラクトは入札プロセスを管理し、ユーザーが興味を持っている**Nouns**に対して入札を行う仕組みを提供します。
最高入札額を記録し、新たな高額入札がある場合にはそれを更新します。

- **オークションの完了**
オークションが終了すると、コントラクトは最高入札者を決定し、その最高額で**Noun**の所有権を移転させます。
また、オークションの収益を売り手や関係者に適切に分配する役割も果たします。

- **セキュリティと保護**
コントラクトにはセキュリティのための対策が組み込まれており、**PausableUpgradeable**や**ReentrancyGuardUpgradeable**などのライブラリを用いて、許可されていないアクセスや攻撃から守られます。

- **所有権とアップグレード可能性**
**NounsAuctionHouse**コントラクトは、**OwnableUpgradeable**や**Initializable**などのライブラリを使って、機能の変更が認められた個人のみが行えるように設計されています。
これにより、将来的なアップグレードや改善が可能となります。

- **他のコントラクトとの統合**
**NounsAuctionHouse**コントラクトは、**INounsToken**、**IERC20**、**IERC721**、**IWETH**、**INounsDescriptor**、**INounsSeeder**などの他のコントラクトと連携しています。
これにより、円滑な相互作用が実現され、**Nouns**エコシステム全体の運用がスムーズに行われます。

# 使い方

**NounsAuctionHouse**コントラクトは、特別な**ERC721**トークンである「**Nouns**」のオークションを行うためのスマートコントラクトです。
このコントラクトは、ユーザーが**Nouns**のオークションを自分で作成して参加できる仕組みを提供し、最高額の入札をしたユーザーがオークションに勝ち、その**Noun**トークンの所有権を得ることができます。

## 目標
**NounsAuctionHouse**コントラクトの目標は、**Nouns**をオークションを通じて取引するための分散型のマーケットプレイスを提供することです。
ユーザーは自分の持つ**Nouns**に対してオークションを作成し、開始価格や期間などの設定を行い、他のユーザーはそのオークションに参加して**Nouns**に対して入札を行うことができます。
そしてオークションが終了すると、最高額の入札者が**Noun**トークンの所有権を獲得します。

## 手順
**Noun**のオークションの分散型マーケットプレイスを実現するためには、次の手順が必要です。

1. **NounsAuctionHouse**コントラクトをデプロイします。
2. コントラクトを必要な設定で初期化します。
**NounsToken**コントラクトのアドレスや**WETH**コントラクトのアドレスなどを指定します。
3. `createAuction`関数を呼び出して、**Noun**トークンのオークションを作成します。
この際、**Noun**のトークンIDや開始価格、オークションの期間などのパラメータを指定します。
4. `bid`関数を呼び出して、オークションに参加して**Nouns**に対して入札額とオークションIDを指定して入札を行います。
5. オークションが終了するのを待ちます。
6. もし最高額の入札者に対して、`claim`関数を呼び出して**Noun**トークンを送ります。
7. オークションの作成者がオークションをキャンセルする場合は、`cancelAuction`関数を呼び出してオークションを取り消し、**Noun**トークンを戻します。

## 関数

### createAuction
**Noun**トークンの新しいオークションを作成します。

**パラメータ**
- `tokenId`
	- オークションにかける**Noun**トークンのID。
- `duration`
	- オークションの期間（秒単位）。
- `reservePrice`
	- オークションの開始価格。
- `extensionDuration`
	-オークション終了間際に入札がある場合に延長する期間。
- `auctionCurrency`
	- 入札に使用される**ERC20**トークンのアドレス。

**戻り値**
- auctionId
	- 作成されたオークションのID。

### bid
既存のオークションに入札を行います。

**パラメータ**
- `auctionId`
	- 入札するオークションのID。
- `amount`
	- 入札額（オークション通貨で指定）。
**戻り値**
なし。

### claim`
オークションに勝利した後、**Noun**トークンを落札者に送ります。

**パラメータ**
- `auctionId`
	- 送る**Noun**トークンのオークションID。

**戻り値**
なし。

### cancelAuction
既存のオークションをキャンセルし、**Noun**トークンを取り戻します。

**パラメータ**
- `auctionId`
	- キャンセルするオークションのID。

**戻り値**
なし。

## イベント

### AuctionCreated
新しいオークションが作成された時に発行されるイベントです。

**パラメータ**
- `auctionId`
	- 作成されたオークションのID。
- `tokenId`
	- オークション中の**Noun**トークンのID。
- `seller`
	- オークションを作成した売り手のアドレス。
- `reservePrice`
	- オークションの開始価格。
- `duration`
	- オークションの期間（秒単位）。
- `extensionDuration`
	- オークション終了間際に入札がある場合に延長される期間。
- `auctionCurrency`
	- 入札に使用されるERC20トークンのアドレス。

### AuctionBid
オークションに入札が行われた時に発行されるイベントです。

**パラメータ**
- `auctionId`
	- オークションのID。
- `bidder`
	- 入札者のアドレス。
- `amount`
	- 入札額（オークション通貨で指定）。

### AuctionExtended
オークションが延長された時に発行されるイベントです。

**パラメータ**
- `auctionId`
	- オークションのID。
- `extensionDuration`
	- 延長された期間（秒単位）。

### AuctionEnded
オークションが終了した時に発行されるイベントです。

**パラメータ**
- `auctionId`
	- オークションのID。
- `tokenId`
	- オークション中の**Noun**トークンのID。
- `winner`
	- 最高額の入札者のアドレス。
- `amount`
	- 最高額の入札額（オークション通貨で指定）。

### AuctionCanceled
オークションがキャンセルされた時に発行されるイベントです。

**パラメータ**
- `auctionId`
	- キャンセルされたオークションのID。
- `tokenId`
	- キャンセルされたオークション中の**Noun**トークンのID。
- `canceller`
	- オークションをキャンセルした作成者のアドレス。

## 関連EIP/ERC

以下は各情報を日本語に翻訳したリストです。

- **ERC721**
	- オークション中の**Noun**トークンは**ERC721**トークンです。
- **ERC20**
	- 入札に使用される通貨は任意の**ERC20**トークンを使用できます。
- **EIP165**
	- コントラクトは**ERC165**インターフェースを実装してインターフェース検出をサポートしています。
- **EIP721**
	- コントラクトは**Noun**トークンとのやり取りに**ERC721**インターフェースを実装しています。
- **EIP1155**
	- コントラクトは**Noun**トークンとのやり取りに**ERC1155**インターフェースを実装しています。
- **EIP2981**
	- コントラクトは**Noun**トークンのロイヤルティ情報のために**ERC2981**インターフェースを実装しています。

# パラメーター

なし。

# コントラクト

## NounsAuctionHouse

### nouns

```solidity
INounsToken public nouns;
```

**概要**
**ERC721**トークンコントラクトへのインターフェース。

**詳細**
**ERC721**トークンコントラクトへのインターフェースを保持します。
**Nouns**トークンは、オークションで取引される**ERC721**トークンであり、このインターフェースを通じてトークンの情報や操作にアクセスできます。

---

### weth

```solidity
address public weth;
```

**概要**
**WETH**コントラクトのアドレス。

**詳細**
**WETH（Wrapped Ether）** トークンのコントラクトアドレスを保持します。
**WETH**は、**Ether**を**ERC20**トークンとして扱うための標準的な方法です。
このアドレスを使用して、入札に**WETH**を使用することができます。

---

### timeBuffer

```solidity
uint256 public timeBuffer;
```

**概要**
新しい入札が作成された後、オークションの終了までに残る最小の時間。

**詳細**
オークションの最後の入札が作成された後、オークションの終了までに最低限残るべき時間を示します。
これにより、入札競争が終了する前に新しい入札が行われる可能性を確保します。

---

### reservePrice

```solidity
uint256 public reservePrice;
```

**概要**
オークションで受け入れられる最小価格。

**詳細**
オークションで受け入れられる最小価格を示します。
入札がこの価格より低い場合、オークションは成立しません。

---

### minBidIncrementPercentage

```solidity
uint8 public minBidIncrementPercentage;
```

**概要**
直前の入札額と現在の入札額の最小パーセンテージ差。

**詳細**
直前の入札額と現在の入札額との最小パーセンテージ差を示します。
入札額がこの差よりも少ない場合、新しい入札は受け入れられません。

---

### duration

```solidity
uint256 public duration;
```

**概要**
単一のオークションの期間。

**詳細**
単一のオークションの期間を示します。
オークションはこの期間内に終了する必要があります。

---

### auction

```solidity
INounsAuctionHouse.Auction public auction;
```

**概要**
アクティブなオークションの情報を保持する構造体。

**詳細**
アクティブなオークションの情報を保持するための構造体です。
アクティブなオークションとは、現在進行中のオークションを指します。
この構造体には、オークションの詳細な情報が含まれています。


---

### initialize

:::details initialize
```solidity
function initialize(
        INounsToken _nouns,
        address _weth,
        uint256 _timeBuffer,
        uint256 _reservePrice,
        uint8 _minBidIncrementPercentage,
        uint256 _duration
    ) external initializer {
        __Pausable_init();
        __ReentrancyGuard_init();
        __Ownable_init();

        _pause();

        nouns = _nouns;
        weth = _weth;
        timeBuffer = _timeBuffer;
        reservePrice = _reservePrice;
        minBidIncrementPercentage = _minBidIncrementPercentage;
        duration = _duration;
}
```
:::

**概要**
オークションハウスとベースコントラクトを初期化し、設定値を設定してコントラクトを一時停止する関数。
一度だけ呼び出すことができます。

**詳細**
この関数はコントラクトの初期化手続きを行います。
具体的には、**Pausable**、**ReentrancyGuard**、**Ownable**の初期化を行い、コントラクトを一時停止状態にします。

**引数**
- `_nouns`
	- **Nouns**トークンを表すコントラクトアドレス。
- `_weth`
	- **Wrapped Ether（WETH）** のコントラクトアドレス。
- `_timeBuffer`
	- オークション終了前の時間バッファ。
- `_reservePrice`
	- 最低落札価格。
- `_minBidIncrementPercentage`
	- 最低入札増加率（%）。
- `_duration`
	- オークションの期間。

---

### settleCurrentAndCreateNewAuction

:::details settleCurrentAndCreateNewAuction
```solidity
function settleCurrentAndCreateNewAuction() external override nonReentrant whenNotPaused {
        _settleAuction();
        _createAuction();
}
```
:::

**概要**
現在のオークションを決済し、新しい**Noun**を作成してオークションに出品する関数。

**詳細**
この関数は以下の手順で実行されます。
1. `_settleAuction()`関数を呼び出して現在のオークションを決済します。
2. `_createAuction()`関数を呼び出して新しい**Noun**を作成し、オークションに出品します。

---

### settleAuction

:::details settleAuction
```solidity
function settleAuction() external override whenPaused nonReentrant {
        _settleAuction();
}
```
:::

**概要**
現在のオークションを決済する関数。

**詳細**
この関数は一時停止状態のコントラクト内でのみ呼び出すことができます。
以下の手順で実行されます。
1. `_settleAuction()`関数を呼び出して現在のオークションを決済します。

---

### createBid

:::details createBid
```solidity
function createBid(uint256 nounId) external payable override nonReentrant {
        INounsAuctionHouse.Auction memory _auction = auction;

        require(_auction.nounId == nounId, 'Noun not up for auction');
        require(block.timestamp < _auction.endTime, 'Auction expired');
        require(msg.value >= reservePrice, 'Must send at least reservePrice');
        require(
            msg.value >= _auction.amount + ((_auction.amount * minBidIncrementPercentage) / 100),
            'Must send more than last bid by minBidIncrementPercentage amount'
        );

        address payable lastBidder = _auction.bidder;

        // Refund the last bidder, if applicable
        if (lastBidder != address(0)) {
            _safeTransferETHWithFallback(lastBidder, _auction.amount);
        }

        auction.amount = msg.value;
        auction.bidder = payable(msg.sender);

        // Extend the auction if the bid was received within `timeBuffer` of the auction end time
        bool extended = _auction.endTime - block.timestamp < timeBuffer;
        if (extended) {
            auction.endTime = _auction.endTime = block.timestamp + timeBuffer;
        }

        emit AuctionBid(_auction.nounId, msg.sender, msg.value, extended);

        if (extended) {
            emit AuctionExtended(_auction.nounId, _auction.endTime);
        }
}
```
:::

**概要**
指定された**Noun**に対して入札を行う関数。
指定された金額でのみ**ETH**による支払いを受け付けます。

**詳細**
この関数は以下の手順で実行されます。
1. 現在のオークション情報を`_auction`変数から取得します。
2. 入札対象の**Noun**が指定された**Noun**であることを確認します。
3. オークションの終了時間が過ぎていないことを確認します。
4. 送信された**ETH**の量が最低落札価格以上であることを確認します。
5. 送信された**ETH**の量が前回の入札額に最低入札増加率を適用して計算された金額以上であることを確認します。
6. 前回の入札者に**ETH**を返金します（前回の入札者がいる場合）。
7. `_auction`の金額と入札者を更新します。
8. 入札がオークション終了時間から`timeBuffer`以内に行われた場合、オークションの終了時間を延長します。
9. 適切なイベントを発行します。

**引数**
- `nounId`
	- 入札対象の**Noun**のID。

---

### pause

:::details pause
```solidity
function pause() external override onlyOwner {
        _pause();
}
```
:::

**概要**
この関数は**Nouns**オークションハウスを一時停止する関数。

**詳細**
オーナーによって呼び出されることができます。
実行すると、コントラクトの状態を一時停止状態に変更します。
一時停止中でも、既存のオークションの決済は誰でも行えますが、新しいオークションの開始はできません。

---

### unpause

:::details unpause
```solidity
function unpause() external override onlyOwner {
        _unpause();

        if (auction.startTime == 0 || auction.settled) {
            _createAuction();
        }
}
```
:::

**概要**
**Nouns**オークションハウスの一時停止を解除し、必要に応じて新しいオークションを開始する関数。

**詳細**
オーナーによって呼び出されることができます。
実行すると、コントラクトの状態を一時停止解除状態に変更します。
また、現在のオークション情報を確認し、オークションが開始されていない場合や既に決済済みの場合は、新しいオークションを開始します。

---

### setTimeBuffer

:::details setTimeBuffer
```solidity
function setTimeBuffer(uint256 _timeBuffer) external override onlyOwner {
        timeBuffer = _timeBuffer;

        emit AuctionTimeBufferUpdated(_timeBuffer);
}
```
:::

**概要**
オークションの時間バッファを設定する関数。

**詳細**
オーナーによって呼び出されることができます。
指定された時間バッファを変数`timeBuffer`に設定します。

**引数**
- `_timeBuffer`
	- 設定するオークションの時間バッファ。

---

### setReservePrice

:::details setReservePrice
```solidity
function setReservePrice(uint256 _reservePrice) external override onlyOwner {
        reservePrice = _reservePrice;

        emit AuctionReservePriceUpdated(_reservePrice);
}
```
:::

**概要**
オークションの最低落札価格を設定する関数。

**詳細**
オーナーによって呼び出されることができます。
指定された最低落札価格を変数`reservePrice`に設定します。

**引数**
- `_reservePrice`
	- 設定する最低落札価格。

---

### setMinBidIncrementPercentage

:::details setMinBidIncrementPercentage
```solidity
function setMinBidIncrementPercentage(uint8 _minBidIncrementPercentage) external override onlyOwner {
        minBidIncrementPercentage = _minBidIncrementPercentage;

        emit AuctionMinBidIncrementPercentageUpdated(_minBidIncrementPercentage);
}
```
:::

**概要**
オークションの最低入札増加率を設定する関数。

**詳細**
オーナーによって呼び出されることができます。
指定された最低入札増加率を変数`minBidIncrementPercentage`に設定します。

**引数**
- `_minBidIncrementPercentage`
	- 設定する最低入札増加率（%）。

---

### _createAuction

:::details _createAuction
```solidity
function _createAuction() internal {
        try nouns.mint() returns (uint256 nounId) {
            uint256 startTime = block.timestamp;
            uint256 endTime = startTime + duration;

            auction = Auction({
                nounId: nounId,
                amount: 0,
                startTime: startTime,
                endTime: endTime,
                bidder: payable(0),
                settled: false
            });

            emit AuctionCreated(nounId, startTime, endTime);
        } catch Error(string memory) {
            _pause();
        }
}
```
:::

**概要**
オークションを作成する関数。

**詳細**
この関数は以下の手順で実行されます。
1. `nouns.mint()`関数を呼び出して**Noun**を新たに作成し、そのIDを取得します。
2. オークションの開始時刻と終了時刻を計算します。
3. オークションの詳細情報を`auction`変数に設定し、`AuctionCreated`イベントを発行します。
4. もし`mint`関数が失敗した場合（たとえば、ミンターがコントラクトの一時停止を行わずに更新された場合）、コントラクトを一時停止状態に変更します。

---

### _settleAuction

:::details _settleAuction
```solidity
function _settleAuction() internal {
        INounsAuctionHouse.Auction memory _auction = auction;

        require(_auction.startTime != 0, "Auction hasn't begun");
        require(!_auction.settled, 'Auction has already been settled');
        require(block.timestamp >= _auction.endTime, "Auction hasn't completed");

        auction.settled = true;

        if (_auction.bidder == address(0)) {
            nouns.burn(_auction.nounId);
        } else {
            nouns.transferFrom(address(this), _auction.bidder, _auction.nounId);
        }

        if (_auction.amount > 0) {
            _safeTransferETHWithFallback(owner(), _auction.amount);
        }

        emit AuctionSettled(_auction.nounId, _auction.bidder, _auction.amount);
}
```
:::

**概要**
オークションを決済し、入札を確定させてオーナー送金する関数。

**詳細**
この関数は以下の手順で実行されます。
1. 現在のオークション情報を`_auction`変数から取得します。
2. オークションが開始されていることを確認します。
3. オークションが既に決済済みでないことを確認します。
4. オークションの終了時刻が過ぎていることを確認します。
5. オークションの状態を決済済みに更新します。
6. もし入札者がいない場合、**Noun**を焼却します。
7. 入札者がいる場合、**Noun**を入札者に転送します。
8. 入札者に支払われる金額が`0`より大きい場合、オーナーに支払います。
9. 適切なイベントを発行します。

---

### _safeTransferETHWithFallback

:::details _safeTransferETHWithFallback
```solidity
function _safeTransferETHWithFallback(address to, uint256 amount) internal {
        if (!_safeTransferETH(to, amount)) {
            IWETH(weth).deposit{ value: amount }();
            IERC20(weth).transfer(to, amount);
        }
}
```
:::

**概要**
**ETH**を転送する関数。
**ETH**の転送に失敗した場合、**ETH**を**WETH**に変換して再試行します。

**詳細**
この関数は以下の手順で実行されます。
1. `_safeTransferETH`関数を使用して**ETH**を指定されたアドレスに転送します。
2. もし**ETH**の転送が失敗した場合、**ETH**を**WETH**に変換して再試行します。

**引数**
- `to`
	- **ETH**を転送する対象のアドレス。
- `amount`
	- 転送する**ETH**の量。

---

### _safeTransferETH

:::details _safeTransferETH
```solidity
function _safeTransferETH(address to, uint256 value) internal returns (bool) {
        (bool success, ) = to.call{ value: value, gas: 30_000 }(new bytes(0));
        return success;
}
```
:::

**概要**
**ETH**を転送し、その成功ステータスを返す関数。

**詳細**
この関数は以下の手順で実行されます。
1. 指定されたアドレスに対して指定された量の**ETH**を転送します。
2. 転送の成功ステータスを返します。

**引数**
- `to`
	- **ETH**を転送する対象のアドレス。
- `value`
	- 転送する**ETH**の量。

**戻り値**
**ETH**の転送が成功した場合は`true`、失敗した場合は`false`を返します。

---

# イベント

### AuctionCreated

:::details AuctionCreated
```solidity
event AuctionCreated(uint256 indexed nounId, uint256 startTime, uint256 endTime);
```
:::

**概要**
新しいオークションが作成されたときに発行されるイベント。

**パラメータ**
- `nounId`
	- オークションに出品された**Noun**のID。
- `startTime`
	- オークションの開始時刻。
- `endTime`
	- オークションの終了時刻。

---

### AuctionBid

:::details AuctionBid
```solidity
event AuctionBid(uint256 indexed nounId, address sender, uint256 value, bool extended);
```
:::

**概要**
**Noun**に対して入札が行われたときに発行されるイベント。

**パラメータ詳細**
- `nounId`
	- 入札された**Noun**のID。
- `sender`
	- 入札を行ったアドレス。
- `value`
	- 入札された**ETH**の金額。
- `extended`
	- 入札が時間バッファ内で行われたかどうか。

---

### AuctionExtended

:::details AuctionExtended
```solidity
event AuctionExtended(uint256 indexed nounId, uint256 endTime);
```
:::

**概要**
オークションの終了時間が時間バッファ内で延長されたときに発行されるイベント。

**パラメータ**
- `nounId`
	- 対象となる**Noun**のID。
- `endTime`
	- 延長された後のオークションの終了時刻。

---

### AuctionSettled

:::details AuctionSettled
```solidity
event AuctionSettled(uint256 indexed nounId, address winner, uint256 amount);
```
:::

**概要**
オークションが決済されたときに発行されるイベント。

**パラメータ**
- `nounId`
	- 決済された**Noun**のID。
- `winner`
	- オークションに勝利したアドレス。
- `amount`
	- 支払われる金額。

---

### AuctionTimeBufferUpdated

:::details AuctionTimeBufferUpdated
```solidity
event AuctionTimeBufferUpdated(uint256 timeBuffer);
```
:::

**概要**
オークションの時間バッファが更新されたときに発行されるイベント。

**詳細**
- `timeBuffer`
	- 更新された時間バッファ。

---

### AuctionReservePriceUpdated

:::details AuctionReservePriceUpdated
```solidity
event AuctionReservePriceUpdated(uint256 reservePrice);
```
:::

**概要**
オークションの最低落札価格が更新されたときに発行されるイベント。

**パラメータ**
- `reservePrice`
	- 更新された最低落札価格。
---

### AuctionMinBidIncrementPercentageUpdated

:::details AuctionMinBidIncrementPercentageUpdated
```solidity
event AuctionMinBidIncrementPercentageUpdated(uint256 minBidIncrementPercentage);
```
:::

**概要**
オークションの最低入札増加率が更新されたときに発行されるイベント。

**詳細**
- `minBidIncrementPercentage`
	- 更新された最低入札増加率。

---

# コード

## インターフェース

- `/contracts/interfaces`

### INounsAuctionHouse.sol

:::details INounsAuctionHouse.sol
```solidity
// SPDX-License-Identifier: GPL-3.0

/// @title Interface for Noun Auction Houses

/*********************************
 * ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ *
 * ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ *
 * ░░░░░░█████████░░█████████░░░ *
 * ░░░░░░██░░░████░░██░░░████░░░ *
 * ░░██████░░░████████░░░████░░░ *
 * ░░██░░██░░░████░░██░░░████░░░ *
 * ░░██░░██░░░████░░██░░░████░░░ *
 * ░░░░░░█████████░░█████████░░░ *
 * ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ *
 * ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ *
 *********************************/

pragma solidity ^0.8.6;

interface INounsAuctionHouse {
    struct Auction {
        // ID for the Noun (ERC721 token ID)
        uint256 nounId;
        // The current highest bid amount
        uint256 amount;
        // The time that the auction started
        uint256 startTime;
        // The time that the auction is scheduled to end
        uint256 endTime;
        // The address of the current highest bid
        address payable bidder;
        // Whether or not the auction has been settled
        bool settled;
    }

    event AuctionCreated(uint256 indexed nounId, uint256 startTime, uint256 endTime);

    event AuctionBid(uint256 indexed nounId, address sender, uint256 value, bool extended);

    event AuctionExtended(uint256 indexed nounId, uint256 endTime);

    event AuctionSettled(uint256 indexed nounId, address winner, uint256 amount);

    event AuctionTimeBufferUpdated(uint256 timeBuffer);

    event AuctionReservePriceUpdated(uint256 reservePrice);

    event AuctionMinBidIncrementPercentageUpdated(uint256 minBidIncrementPercentage);

    function settleAuction() external;

    function settleCurrentAndCreateNewAuction() external;

    function createBid(uint256 nounId) external payable;

    function pause() external;

    function unpause() external;

    function setTimeBuffer(uint256 timeBuffer) external;

    function setReservePrice(uint256 reservePrice) external;

    function setMinBidIncrementPercentage(uint8 minBidIncrementPercentage) external;
}
```
:::

### INounsToken.sol

:::details INounsToken.sol
```solidity
// SPDX-License-Identifier: GPL-3.0

/// @title Interface for NounsToken

/*********************************
 * ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ *
 * ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ *
 * ░░░░░░█████████░░█████████░░░ *
 * ░░░░░░██░░░████░░██░░░████░░░ *
 * ░░██████░░░████████░░░████░░░ *
 * ░░██░░██░░░████░░██░░░████░░░ *
 * ░░██░░██░░░████░░██░░░████░░░ *
 * ░░░░░░█████████░░█████████░░░ *
 * ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ *
 * ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ *
 *********************************/

pragma solidity ^0.8.6;

import { IERC721 } from '@openzeppelin/contracts/token/ERC721/IERC721.sol';
import { INounsDescriptor } from './INounsDescriptor.sol';
import { INounsSeeder } from './INounsSeeder.sol';

interface INounsToken is IERC721 {
    event NounCreated(uint256 indexed tokenId, INounsSeeder.Seed seed);

    event NounBurned(uint256 indexed tokenId);

    event NoundersDAOUpdated(address noundersDAO);

    event MinterUpdated(address minter);

    event MinterLocked();

    event DescriptorUpdated(INounsDescriptor descriptor);

    event DescriptorLocked();

    event SeederUpdated(INounsSeeder seeder);

    event SeederLocked();

    function mint() external returns (uint256);

    function burn(uint256 tokenId) external;

    function dataURI(uint256 tokenId) external returns (string memory);

    function setNoundersDAO(address noundersDAO) external;

    function setMinter(address minter) external;

    function lockMinter() external;

    function setDescriptor(INounsDescriptor descriptor) external;

    function lockDescriptor() external;

    function setSeeder(INounsSeeder seeder) external;

    function lockSeeder() external;
}
```
:::

### IWETH.sol

:::details IWETH.sol
```solidity
// SPDX-License-Identifier: GPL-3.0

pragma solidity ^0.8.6;

interface IWETH {
    function deposit() external payable;

    function withdraw(uint256 wad) external;

    function transfer(address to, uint256 value) external returns (bool);
}
```
:::

### INounsDescriptor.sol

:::details INounsDescriptor.sol
```solidity
// SPDX-License-Identifier: GPL-3.0

/// @title Interface for NounsDescriptor

/*********************************
 * ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ *
 * ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ *
 * ░░░░░░█████████░░█████████░░░ *
 * ░░░░░░██░░░████░░██░░░████░░░ *
 * ░░██████░░░████████░░░████░░░ *
 * ░░██░░██░░░████░░██░░░████░░░ *
 * ░░██░░██░░░████░░██░░░████░░░ *
 * ░░░░░░█████████░░█████████░░░ *
 * ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ *
 * ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ *
 *********************************/

pragma solidity ^0.8.6;

import { INounsSeeder } from './INounsSeeder.sol';

interface INounsDescriptor {
    event PartsLocked();

    event DataURIToggled(bool enabled);

    event BaseURIUpdated(string baseURI);

    function arePartsLocked() external returns (bool);

    function isDataURIEnabled() external returns (bool);

    function baseURI() external returns (string memory);

    function palettes(uint8 paletteIndex, uint256 colorIndex) external view returns (string memory);

    function backgrounds(uint256 index) external view returns (string memory);

    function bodies(uint256 index) external view returns (bytes memory);

    function accessories(uint256 index) external view returns (bytes memory);

    function heads(uint256 index) external view returns (bytes memory);

    function glasses(uint256 index) external view returns (bytes memory);

    function backgroundCount() external view returns (uint256);

    function bodyCount() external view returns (uint256);

    function accessoryCount() external view returns (uint256);

    function headCount() external view returns (uint256);

    function glassesCount() external view returns (uint256);

    function addManyColorsToPalette(uint8 paletteIndex, string[] calldata newColors) external;

    function addManyBackgrounds(string[] calldata backgrounds) external;

    function addManyBodies(bytes[] calldata bodies) external;

    function addManyAccessories(bytes[] calldata accessories) external;

    function addManyHeads(bytes[] calldata heads) external;

    function addManyGlasses(bytes[] calldata glasses) external;

    function addColorToPalette(uint8 paletteIndex, string calldata color) external;

    function addBackground(string calldata background) external;

    function addBody(bytes calldata body) external;

    function addAccessory(bytes calldata accessory) external;

    function addHead(bytes calldata head) external;

    function addGlasses(bytes calldata glasses) external;

    function lockParts() external;

    function toggleDataURIEnabled() external;

    function setBaseURI(string calldata baseURI) external;

    function tokenURI(uint256 tokenId, INounsSeeder.Seed memory seed) external view returns (string memory);

    function dataURI(uint256 tokenId, INounsSeeder.Seed memory seed) external view returns (string memory);

    function genericDataURI(
        string calldata name,
        string calldata description,
        INounsSeeder.Seed memory seed
    ) external view returns (string memory);

    function generateSVGImage(INounsSeeder.Seed memory seed) external view returns (string memory);
}
```
:::

### INounsSeeder.sol

:::details INounsSeeder.sol
```solidity
// SPDX-License-Identifier: GPL-3.0

/// @title Interface for NounsSeeder

/*********************************
 * ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ *
 * ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ *
 * ░░░░░░█████████░░█████████░░░ *
 * ░░░░░░██░░░████░░██░░░████░░░ *
 * ░░██████░░░████████░░░████░░░ *
 * ░░██░░██░░░████░░██░░░████░░░ *
 * ░░██░░██░░░████░░██░░░████░░░ *
 * ░░░░░░█████████░░█████████░░░ *
 * ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ *
 * ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ *
 *********************************/

pragma solidity ^0.8.6;

import { INounsDescriptor } from './INounsDescriptor.sol';

interface INounsSeeder {
    struct Seed {
        uint48 background;
        uint48 body;
        uint48 accessory;
        uint48 head;
        uint48 glasses;
    }

    function generateSeed(uint256 nounId, INounsDescriptor descriptor) external view returns (Seed memory);
}
```
:::

## NounsAuctionHouse.sol

- `/contracts/NounsAuctionHouse.sol`

### NounsAuctionHouse.sol

:::details NounsAuctionHouse.sol
```solidity
// SPDX-License-Identifier: GPL-3.0

/// @title The Nouns DAO auction house

/*********************************
 * ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ *
 * ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ *
 * ░░░░░░█████████░░█████████░░░ *
 * ░░░░░░██░░░████░░██░░░████░░░ *
 * ░░██████░░░████████░░░████░░░ *
 * ░░██░░██░░░████░░██░░░████░░░ *
 * ░░██░░██░░░████░░██░░░████░░░ *
 * ░░░░░░█████████░░█████████░░░ *
 * ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ *
 * ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ *
 *********************************/

// LICENSE
// NounsAuctionHouse.sol is a modified version of Zora's AuctionHouse.sol:
// https://github.com/ourzora/auction-house/blob/54a12ec1a6cf562e49f0a4917990474b11350a2d/contracts/AuctionHouse.sol
//
// AuctionHouse.sol source code Copyright Zora licensed under the GPL-3.0 license.
// With modifications by Nounders DAO.

pragma solidity ^0.8.6;

import { PausableUpgradeable } from '@openzeppelin/contracts-upgradeable/security/PausableUpgradeable.sol';
import { ReentrancyGuardUpgradeable } from '@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol';
import { OwnableUpgradeable } from '@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol';
import { IERC20 } from '@openzeppelin/contracts/token/ERC20/IERC20.sol';
import { INounsAuctionHouse } from './interfaces/INounsAuctionHouse.sol';
import { INounsToken } from './interfaces/INounsToken.sol';
import { IWETH } from './interfaces/IWETH.sol';

contract NounsAuctionHouse is INounsAuctionHouse, PausableUpgradeable, ReentrancyGuardUpgradeable, OwnableUpgradeable {
    // The Nouns ERC721 token contract
    INounsToken public nouns;

    // The address of the WETH contract
    address public weth;

    // The minimum amount of time left in an auction after a new bid is created
    uint256 public timeBuffer;

    // The minimum price accepted in an auction
    uint256 public reservePrice;

    // The minimum percentage difference between the last bid amount and the current bid
    uint8 public minBidIncrementPercentage;

    // The duration of a single auction
    uint256 public duration;

    // The active auction
    INounsAuctionHouse.Auction public auction;

    /**
     * @notice Initialize the auction house and base contracts,
     * populate configuration values, and pause the contract.
     * @dev This function can only be called once.
     */
    function initialize(
        INounsToken _nouns,
        address _weth,
        uint256 _timeBuffer,
        uint256 _reservePrice,
        uint8 _minBidIncrementPercentage,
        uint256 _duration
    ) external initializer {
        __Pausable_init();
        __ReentrancyGuard_init();
        __Ownable_init();

        _pause();

        nouns = _nouns;
        weth = _weth;
        timeBuffer = _timeBuffer;
        reservePrice = _reservePrice;
        minBidIncrementPercentage = _minBidIncrementPercentage;
        duration = _duration;
    }

    /**
     * @notice Settle the current auction, mint a new Noun, and put it up for auction.
     */
    function settleCurrentAndCreateNewAuction() external override nonReentrant whenNotPaused {
        _settleAuction();
        _createAuction();
    }

    /**
     * @notice Settle the current auction.
     * @dev This function can only be called when the contract is paused.
     */
    function settleAuction() external override whenPaused nonReentrant {
        _settleAuction();
    }

    /**
     * @notice Create a bid for a Noun, with a given amount.
     * @dev This contract only accepts payment in ETH.
     */
    function createBid(uint256 nounId) external payable override nonReentrant {
        INounsAuctionHouse.Auction memory _auction = auction;

        require(_auction.nounId == nounId, 'Noun not up for auction');
        require(block.timestamp < _auction.endTime, 'Auction expired');
        require(msg.value >= reservePrice, 'Must send at least reservePrice');
        require(
            msg.value >= _auction.amount + ((_auction.amount * minBidIncrementPercentage) / 100),
            'Must send more than last bid by minBidIncrementPercentage amount'
        );

        address payable lastBidder = _auction.bidder;

        // Refund the last bidder, if applicable
        if (lastBidder != address(0)) {
            _safeTransferETHWithFallback(lastBidder, _auction.amount);
        }

        auction.amount = msg.value;
        auction.bidder = payable(msg.sender);

        // Extend the auction if the bid was received within `timeBuffer` of the auction end time
        bool extended = _auction.endTime - block.timestamp < timeBuffer;
        if (extended) {
            auction.endTime = _auction.endTime = block.timestamp + timeBuffer;
        }

        emit AuctionBid(_auction.nounId, msg.sender, msg.value, extended);

        if (extended) {
            emit AuctionExtended(_auction.nounId, _auction.endTime);
        }
    }

    /**
     * @notice Pause the Nouns auction house.
     * @dev This function can only be called by the owner when the
     * contract is unpaused. While no new auctions can be started when paused,
     * anyone can settle an ongoing auction.
     */
    function pause() external override onlyOwner {
        _pause();
    }

    /**
     * @notice Unpause the Nouns auction house.
     * @dev This function can only be called by the owner when the
     * contract is paused. If required, this function will start a new auction.
     */
    function unpause() external override onlyOwner {
        _unpause();

        if (auction.startTime == 0 || auction.settled) {
            _createAuction();
        }
    }

    /**
     * @notice Set the auction time buffer.
     * @dev Only callable by the owner.
     */
    function setTimeBuffer(uint256 _timeBuffer) external override onlyOwner {
        timeBuffer = _timeBuffer;

        emit AuctionTimeBufferUpdated(_timeBuffer);
    }

    /**
     * @notice Set the auction reserve price.
     * @dev Only callable by the owner.
     */
    function setReservePrice(uint256 _reservePrice) external override onlyOwner {
        reservePrice = _reservePrice;

        emit AuctionReservePriceUpdated(_reservePrice);
    }

    /**
     * @notice Set the auction minimum bid increment percentage.
     * @dev Only callable by the owner.
     */
    function setMinBidIncrementPercentage(uint8 _minBidIncrementPercentage) external override onlyOwner {
        minBidIncrementPercentage = _minBidIncrementPercentage;

        emit AuctionMinBidIncrementPercentageUpdated(_minBidIncrementPercentage);
    }

    /**
     * @notice Create an auction.
     * @dev Store the auction details in the `auction` state variable and emit an AuctionCreated event.
     * If the mint reverts, the minter was updated without pausing this contract first. To remedy this,
     * catch the revert and pause this contract.
     */
    function _createAuction() internal {
        try nouns.mint() returns (uint256 nounId) {
            uint256 startTime = block.timestamp;
            uint256 endTime = startTime + duration;

            auction = Auction({
                nounId: nounId,
                amount: 0,
                startTime: startTime,
                endTime: endTime,
                bidder: payable(0),
                settled: false
            });

            emit AuctionCreated(nounId, startTime, endTime);
        } catch Error(string memory) {
            _pause();
        }
    }

    /**
     * @notice Settle an auction, finalizing the bid and paying out to the owner.
     * @dev If there are no bids, the Noun is burned.
     */
    function _settleAuction() internal {
        INounsAuctionHouse.Auction memory _auction = auction;

        require(_auction.startTime != 0, "Auction hasn't begun");
        require(!_auction.settled, 'Auction has already been settled');
        require(block.timestamp >= _auction.endTime, "Auction hasn't completed");

        auction.settled = true;

        if (_auction.bidder == address(0)) {
            nouns.burn(_auction.nounId);
        } else {
            nouns.transferFrom(address(this), _auction.bidder, _auction.nounId);
        }

        if (_auction.amount > 0) {
            _safeTransferETHWithFallback(owner(), _auction.amount);
        }

        emit AuctionSettled(_auction.nounId, _auction.bidder, _auction.amount);
    }

    /**
     * @notice Transfer ETH. If the ETH transfer fails, wrap the ETH and try send it as WETH.
     */
    function _safeTransferETHWithFallback(address to, uint256 amount) internal {
        if (!_safeTransferETH(to, amount)) {
            IWETH(weth).deposit{ value: amount }();
            IERC20(weth).transfer(to, amount);
        }
    }

    /**
     * @notice Transfer ETH and return the success status.
     * @dev This function only forwards 30,000 gas to the callee.
     */
    function _safeTransferETH(address to, uint256 value) internal returns (bool) {
        (bool success, ) = to.call{ value: value, gas: 30_000 }(new bytes(0));
        return success;
    }
}
```
:::

# 最後に

今回の記事では、Bunzzの新機能『**DeCipher**』を使用して、**Nouns**の「**NounsAuctionHouse**」のコントラクトを見てきました。
いかがだったでしょうか？
今後も特定のNFTやコントラクトをピックアップしてまとめて行きたいと思います。

普段はブログやQiitaでブロックチェーンやAIに関する記事を挙げているので、よければ見ていってください！

https://chaldene.net/

https://qiita.com/cardene
