# [ERC7390] ERC1155を使った分割可能なオプション発行の仕組みを理解しよう！

> ERC7390は、バニラオプションを扱うための機能やイベントをまとめたものです。バニラオプションとは、特定の価格で資産を買うか売るかを選べる権利のことで、権利を使うかどうかは購入者が自由に決められます。

- URL: https://cardene.dev/articles/d7ac135e836c8b16df44
- 公開日: 2025-11-29
- 更新日: 2025-11-29
- トピック: Blockchain
- タグ: ethereum, solidity, smartcontract, ブロックチェーン

## はじめに

『DApps開発入門』という本や色々記事を書いているかるでねです。

https://amzn.asia/d/gxvJ0Pw

今回は、**ERC1155**を使ってオプションの分割購入や他者への譲渡を可能にする柔軟な仕組みを提案している**ERC7390**についてまとめていきます！

:::message
以下にまとめられているものを翻訳・要約・補足しながらまとめていきます。
:::

https://eips.ethereum.org/EIPS/eip-7390

他にも様々なEIP・BIP・SLIP・CAIP・ENSIP・RFC・ACPについてまとめています。

https://cardene.notion.site/ERC-EIP-2a03fa3ea33d43baa9ed82288f98d4a9?pvs=4

## 概要

**ERC7390**は、バニラオプションを扱うための機能やイベントをまとめたものです。
バニラオプションとは、特定の価格で資産を買うか売るかを選べる権利のことで、権利を使うかどうかは購入者が自由に決められます。

この標準では、期限が過ぎたら役目を終える単純なオプションではなく、必要なだけ発行（issue）を追加できるようになっています。
発行ごとに識別用のIDが割り当てられ、それぞれ独立して購入、権利行使、キャンセルなどができます。

発行は必ず担保によって保証されます。
担保とは、オプションの権利を提供する側（ライター）が事前に預ける資産のことで、購入者が権利を行使した場合に支払いを確実にするためのものです。
購入者が権利を行使しなかった場合、ライターは担保を引き出せます。

購入者は発行全体ではなく一部だけ購入できます。
また、購入すると**ERC1155**形式のトークンが発行されます。
これは発行の持分を示すもので、ここではリディームトークンと呼ばれます。
リディームトークンは他のユーザーに譲渡でき、権利を行使するときに使用します。
そのため、購入した分の一部だけ権利を行使することも可能です。

:::message
**ERC1155**については以下の記事を参考にしてください。
:::

ライターは、まだ誰も購入していない場合に限り発行をキャンセルできます。
また、プレミアム価格（オプションを購入する際に支払う料金）はいつでも更新できます。ただし、すでに購入された分には影響しません。

オプションの対象となる資産（アンダーライング）、権利を行使するときに使用する資産（ストライク）、そしてプレミアムの支払いに使う資産はいずれも**ERC20**トークンです。
また、「**オプション**」という複数形は、購入者が保有するリディームトークン量を指す場合があります。

## 動機

オプションは投資や取引で広く利用されている金融手段で、リスク管理や価格変動を狙った取引など、多くの用途があります。

分散型金融（DeFi）においてもオプションを販売する仕組みが増えていますが、それぞれのプロトコルが独自の仕様を持っているため互換性がありません。
本来、一般的な暗号資産トークンのように、別のアプリケーション同士で扱える形が望ましいにもかかわらず、共通の基準がないことで活用の幅が狭くなっています。

ERCとして標準化することで、オプションを扱うアプリケーションが同じ形式でやり取りできるようになり、ユーザーは異なるプラットフォーム間でオプションをスムーズに売買できるようになります。
また、標準はもっとも一般的なバニラオプションを対象としているため、より複雑なオプション（エキゾチックオプションなど）を構築する基盤としても活用できます。

## 仕様

```js:IERC7390.sol
interface IERC7390 {
    enum Side {
        Call,
        Put
    }

    struct VanillaOptionData {
        Side side;
        address underlyingToken;
        uint256 amount;
        address strikeToken;
        uint256 strike;
        address premiumToken;
        uint256 premium;
        uint256 exerciseWindowStart;
        uint256 exerciseWindowEnd;
        address[] allowed;
    }

    struct OptionIssuance {
        VanillaOptionData data;
        address writer;
        uint256 exercisedAmount;
        uint256 soldAmount;
    }

    error Forbidden();
    error TransferFailed();
    error TimeForbidden();
    error AmountForbidden();
    error InsufficientBalance();

    event Created(uint256 indexed id);
    event Bought(uint256 indexed id, uint256 amount, address indexed buyer);
    event Exercised(uint256 indexed id, uint256 amount);
    event Expired(uint256 indexed id);
    event Canceled(uint256 indexed id);
    event PremiumUpdated(uint256 indexed id, uint256 amount);
    event AllowedUpdated(uint256 indexed id, address[] allowed);

    function create(VanillaOptionData calldata optionData) external returns (uint256);

    function buy(uint256 id, uint256 amount) external;

    function exercise(uint256 id, uint256 amount) external;

    function retrieveExpiredTokens(uint256 id, address receiver) external;

    function cancel(uint256 id, address receiver) external;

    function updatePremium(uint256 id, uint256 amount) external;

    function updateAllowed(uint256 id, address[] memory allowed) external;

    function issuance(uint256 id) external view returns (OptionIssuance memory);
}
```

### イベント

#### `Created`

```javascript
event Created(uint256 id);
```

オプション発行が正常に作成された時に発行されるイベント。
ライターが `create()` を実行し、担保がコントラクトへ正しくロックされた時に通知されます。
発行されたオプションは ID によって識別されます。

**パラメータ**

* `id`
  * 発行されたオプション発行の識別子。

#### `Bought`

```javascript
event Bought(uint256 indexed id, uint256 amount, address indexed buyer);
```

オプションが購入された時に発行されるイベント。
購入者が `buy()` を実行し、指定量のリディームトークン（**ERC1155**トークン）がミントされたことを示します。
購入時にプレミアムがライターへ直接支払われます。

:::message
**ERC1155**については以下の記事を参考にしてください。
:::

**パラメータ**

* `id`
  * 対象となるオプション発行の識別子。
* `amount`
  * 購入されたオプション量。
* `buyer`
  * 購入者のアドレス。

#### `Exercised`

```javascript
event Exercised(uint256 indexed id, uint256 amount);
```

購入者がオプションを権利行使した時に発行されるイベント。
購入者が `exercise()` を実行し、該当するリディームトークンがバーンされた上で、コールの場合は `underlyingToken` を受け取り `strikeToken` を支払い、プットの場合は `underlyingToken` を渡して `strikeToken` を受け取ったことを示します。

**パラメータ**

* `id`
  * 対象となるオプション発行の識別子。
* `amount`
  * 権利行使されたオプション量。

#### `Expired`

```javascript
event Expired(uint256 indexed id);
```

未行使の担保がライターへ返却された時に発行されるイベント。
権利行使期間が終了した後、ライターが `retrieveExpiredTokens()` を実行して
未行使分の担保を引き取った時に通知されます。

**パラメータ**

* `id`
  * 担保返却対象となったオプション発行の識別子。

#### `Canceled`

```javascript
event Canceled(uint256 indexed id);
```

オプション発行がキャンセルされた時に発行されるイベント。
まだ誰もオプションを購入していない場合、ライターは `cancel()` により発行を無効化できます。
キャンセル後、担保はライターへ返却されます。

**パラメータ**

* `id`
  * キャンセルされたオプション発行の識別子。

#### `PremiumUpdated`

```javascript
event PremiumUpdated(uint256 indexed id, uint256 amount);
```

プレミアム価格が更新された時に発行されるイベント。
ライターが `updatePremium()` を実行し、未購入部分に対するプレミアムを更新した時に通知されます。
すでに購入した分のオプションには影響しません。

**パラメータ**

* `id`
  * 対象のオプション発行の識別子。
* `amount`
  * 更新されたプレミアムの値。

#### `AllowedUpdated`

```javascript
event AllowedUpdated(uint256 indexed id, address[] allowed);
```

購入を許可されたアドレスのリストが変更された時に発行されるイベント。
ライターが `updateAllowed()` を実行し、新しい許可リストを設定した時に通知されます。
すでに購入しているユーザーは、新しいリストに含まれていなくても権利行使できます。

**パラメータ**

* `allowed`
  * 更新後の許可アドレス一覧。

### 構造体

#### `VanillaOptionData`

```javascript
struct VanillaOptionData {
    Side side;
    address underlyingToken;
    uint256 amount;
    address strikeToken;
    uint256 strike;
    address premiumToken;
    uint256 premium;
    uint256 exerciseWindowStart;
    uint256 exerciseWindowEnd;
    address[] allowed;
}
```

オプション発行に必要なすべてのパラメータをまとめた構造体。
オプションの種類（コールまたはプット）、対象となるトークン、行使量、プレミアム、行使期間など、発行に必要な全情報を保持します。
`create()` 実行時にこの構造体を渡すことでオプションが構築されます。

**パラメータ**

* `side`
  * コールかプットかを表す値。
* `underlyingToken`
  * オプションの対象資産となるトークンのアドレス。
* `amount`
  * 行使可能な最大 `underlyingToken` 数。
* `strikeToken`
  * ストライク価格に使用されるトークン。
* `strike`
  * `underlyingToken` 1単位に対する `strikeToken` の量。
* `premiumToken`
  * プレミアム支払いに使用するトークン。
* `premium`
  * 発行全体に対するプレミアム価格。
* `exerciseWindowStart`
  * 権利行使開始時刻。
* `exerciseWindowEnd`
  * 権利行使終了時刻。
* `allowed`
  * 購入を許可するアドレス一覧。
  * 空配列の場合は誰でも購入可能。

#### `OptionIssuance`

```javascript
struct OptionIssuance {
    VanillaOptionData data;
    address writer;
    uint256 exercisedAmount;
    uint256 soldAmount;
}
```

発行されたオプションの状態をまとめた構造体。
発行時のパラメータ（`VanillaOptionData`）に加え、ライターのアドレス、売却済み量、権利行使済み量など、運用中に更新されるデータを保持します。

**パラメータ**

* `data`
  * 発行時に設定されたオプション情報。
* `writer`
  * オプションを発行したアドレス。
* `exercisedAmount`
  * すでに権利行使された `underlyingToken` の量。
* `soldAmount`
  * すでに購入された `underlyingToken` の量。

### enum

#### `Side`

```javascript
enum Side {
    Call,
    Put
}
```

オプションがコールかプットかを示すenum。
コールは「**指定価格で `underlyingToken` を買う権利**」、プットは「**指定価格で `underlyingToken` を売る権利**」を表します。

**値**

* `Call`
  コールオプションを表す値。
* `Put`
  プットオプションを表す値。

### 関数

#### `create`

```javascript
function create(VanillaOptionData calldata optionData) external returns (uint256);
```

新しいオプション発行を作成する関数。
ライターが担保を事前に承認した上で、オプションを新規発行する処理です。
コントラクトは担保を受け取り、完全に担保された状態でオプションを登録します。
発行が成功するとユニークなIDが返されます。

**引数**

* `optionData`
  * 発行に必要な設定情報をまとめた `VanillaOptionData`。

**戻り値**

* `uint256`
  * 作成されたオプション発行の ID。

#### `buy`

```javascript
function buy(uint256 id, uint256 amount) external;
```

指定量のオプションを購入する関数。
購入者は支払うべきプレミアムに対する承認を済ませた上で `buy()` を実行します。
成功するとリディームトークンが購入者へミントされます。

**引数**

* `id`
  * 購入対象のオプション発行ID。
* `amount`
  * 購入する `underlyingToken` の量。

#### `exercise`

```javascript
function exercise(uint256 id, uint256 amount) external;
```

購入済みオプションを権利行使する関数。
コールかプットかによって、受け渡しされるトークンが変わります。
成功すると、対応するリディームトークンはBurnされます。

**引数**

* `id`
  * 権利行使するオプションの発行ID。
* `amount`
  * 権利行使する量。

#### `retrieveExpiredTokens`

```javascript
function retrieveExpiredTokens(uint256 id, address receiver) external;
```

権利行使期間後にライターが残りの担保を回収する関数。
権利行使期間を過ぎた後、未行使の担保をライターへ返却します。
`receiver` が空アドレスの場合は呼び出し元が受け取ります。

**引数**

* `id`
  * 対象オプションの発行 ID。
* `receiver`
  * 担保を受け取るアドレス。

#### `cancel`

```javascript
function cancel(uint256 id, address receiver) external;
```

未購入のオプション発行をキャンセルする関数。
まだ誰も買っていない発行に限り、ライターは発行を無効化できます。
担保は返却され、場合によっては発行データが削除されます。

**引数**

* `id`
  * キャンセル対象の発行ID。
* `receiver`
  * 担保を受け取るアドレス。

#### `updatePremium`

```javascript
function updatePremium(uint256 id, uint256 amount) external;
```

未購入分オプションのプレミアム価格を更新する関数。
ライターが発行全体に対するプレミアム値を変更できます。
行使期間中や終了後は更新できません。

**引数**

* `id`
  * 対象オプションの発行 ID。
* `amount`
  * 新しいプレミアム値。

#### `updateAllowed`

```javascript
function updateAllowed(uint256 id, address[] memory allowed) external;
```

購入を許可するアドレスリストを更新する関数。
購入者制限を変更します。
すでに購入済みのユーザーはリストに入っていなくても権利行使できます。

**引数**

* `id`
  * 対象オプション発行の ID。
* `allowed`
  * 新しい許可リスト。

#### `issuance`

```javascript
function issuance(uint256 id) external view returns (OptionIssuance memory);
```

指定IDのオプション発行データを取得する関数。
発行時のデータおよび運用状態（売却量、行使量など）をすべて返します。

**引数**

* `id`
  * 対象オプション発行ID。

**戻り値**

* `OptionIssuance`
  * 発行に紐づく情報一式。

### コールオプションの例

ここでは、Bobがコールオプションを発行し、AliceとJohnがそれぞれ一部を購入し、さらにAliceが権利を行使する一連の流れを示します。

#### Bobがコールオプションを発行する流れ

Bobは、誰でも「**`8 TokenA` を1つあたり `25 TokenB` で購入できる権利**」を2023年7月14日〜16日の間に使えるようにします。
この権利を販売する対価として、Bobは `10 TokenC` のプレミアムを受け取りたいと考えています。

コールオプションの場合、ライターであるBobが担保として必要なのは権利行使時に購入者へ渡す `underlyingToken（TokenA）` です。
Bobは `8 TokenA` を渡す必要があるため、コントラクトがそれを引き出せるようにTokenAの `approve()` を実行して承認します。

承認が完了すると、Bobはコントラクトに対して `create()` を呼び出し、以下の内容でオプションを発行します。

| 項目                  | 値          |
| ------------------- | ---------- |
| `side`                | Call       |
| `underlyingToken`     | TokenA     |
| `amount`              | 8 TokenA 分 |
| `strikeToken`         | TokenB     |
| `strike`              | 25 TokenB  |
| `premiumToken`        | TokenC     |
| `premium`             | 10 TokenC  |
| `exerciseWindowStart` | 2023-07-14 |
| `exerciseWindowEnd`   | 2023-07-16 |
| `allowed`             | 全員購入可能     |

このオプションにはID `88` が割り当てられます。

#### Aliceがオプションの一部（`4 TokenA` 分）を購入する流れ

Aliceは `8 TokenA` ではなく、`4 TokenA` 分の権利だけを購入したいと考えます。
プレミアムは発行全体で `10 TokenC` のため、Aliceはその半分を支払います。

割合は以下で計算されます。

```
支払うプレミアム = (購入量 * premium) / amount
```

AliceはTokenCの `approve()` を実行して必要額を承認し、続けて `buy(88, 4 TokenA 分)` をコントラクトへ送ります。
すると、Aliceには `4 TokenA` 分のリディームトークン（**ERC1155**）が配布されます。

#### Johnも `2 TokenA` 分だけ購入する

Johnも同じ手順で `2 TokenA` 分を購入し、`2 TokenA` 分のリディームトークンを受け取ります。

#### Aliceが権利を行使する流れ

2023年7月15日になり、TokenAが `50 TokenB` の価格で取引されている状態を想定します。
この場合、 `25 TokenB` で買える権利は有利なので、Aliceは権利行使を選びます。

行使には、Aliceが以下のTokenBをコントラクトへ支払う必要があります。

```
支払う TokenB = (行使量 * strike) / TokenA の decimals 調整
```

AliceはTokenBの `approve()` を実行し、その後 `exercise(88, 4 TokenA 分)` を呼び出します。

結果として、以下の処理が行われます。

* Aliceは `4 TokenA` を受け取る
* Bobは `4 × 25 = 100 TokenB` を受け取る

#### Johnが権利をJimmyに譲渡してJimmyが一部だけ権利行使する場合

Johnは、保有していた `2 TokenA` 分のリディームトークンをJimmyに送ります。
リディームトークンは**ERC1155**であり、通常のトークン同様に譲渡できます。

Jimmyは `2 TokenA` 分のうち、`1 TokenA` 分だけ行使します。
必要な支払いは以下です。

```
1 TokenA 分の行使 → 25 TokenB の支払い
```

コントラクトはJimmyに `1 TokenA` を送り、Bobに `25 TokenB` を送ります。

### プットオプションの例

次はBobがプットオプションを発行する例です。

#### Bobがプットオプションを発行する流れ

Bobは、誰でも「**`8 TokenA` を1つあたり `25 TokenB` でBobに売る権利**」を2023年7月14日〜16日の間に行使できるようにします。
プレミアムとして `10 TokenC` を受け取りたいと考えています。

プットオプションの場合、ライターの担保は `strikeToken（TokenB）` です。
行使されたときに購入者へ支払う必要があるため、`8 × 25 = 200 TokenB` を預けます。

Bobは TokenB の `approve()` を実行した後、
`create()` に以下の内容を渡します。

| 項目                  | 値          |
| ------------------- | ---------- |
| `side`                | Put        |
| `underlyingToken`     | TokenA     |
| `amount`              | 8 TokenA 分 |
| `strikeToken`         | TokenB     |
| `strike`              | 25 TokenB  |
| `premiumToken`        | TokenC     |
| `premium`             | 10 TokenC  |
| `exerciseWindowStart` | 2023-07-14 |
| `exerciseWindowEnd`   | 2023-07-16 |
| `allowed`             | 全員購入可能     |

これも発行IDは `88` です。

#### Aliceが `4 TokenA` 分だけ売る権利を購入する

購入時の流れはコールオプションと同じです。
premiumは全体で `10 TokenC` のため、`4 TokenA` 分の場合は半分を支払います。

Aliceは `buy(88, 4 TokenA 分)` を実行し、`4 TokenA` 分のリディームトークンを受け取ります。

#### Aliceが権利行使する流れ

2023年7月15日、TokenAが `10 TokenB` で取引されているとします。
市場価格より高い（`25 TokenB`）で売れるため、Aliceは行使します。

AliceはTokenAをコントラクトへ送る必要があるため、TokenAを `approve()` し、`exercise(88, 4 TokenA 分)` を実行します。

結果として、以下を受け取ります。

* Aliceは `4 × 25 = 100 TokenB`
* Bobは `4 TokenA`

#### John → Jimmy の譲渡と Jimmy の部分行使

コールオプションと同じく、Johnは `2 TokenA` 分のリディームトークンをJimmyに送ります。
Jimmyはそのうち `1 TokenA` 分だけ行使し、TokenAをBobに渡し、代わりに `25 TokenB` を受け取ります。

### 担保の返却（`retrieveExpiredTokens` の例）

Aliceが権利を行使しなかった場合、Bobは権利行使期間（2023年7月16日）を過ぎた後で担保を取り戻せます。

Bobは `retrieveExpiredTokens(88)` を実行することで、以下を受け取ります。

* コールの場合 → 未行使分のTokenA
* プットの場合 → 未行使分のTokenB

## 補足

#### オラクル不要な設計

**ERC7390**は、価格オラクル（外部の価格フィード）に依存しない設計になっています。
理由はシンプルで、「**合理的な買い手は、自分にとって得になるときにしかオプションを行使しない**」と仮定しているからです。

コントラクト側は「**いま市場価格がいくらか**」という情報を知る必要がありません。
買い手が市場価格とオプションの条件（ストライク価格）を見て、得になると判断したときだけ `exercise` を呼び出します。
つまり、行使するかどうかの判断そのものを買い手に委ねることで、オラクル依存を避けています。

#### プレミアム価格の決め方とオフチェーン計算

プレミアム（オプションを買うときに支払う料金）は、**ERC7390**ではライターが自由に決める前提になっています。
ライターはプレミアム計算の方法として、有名なBlack-Scholesモデルのような金融工学のモデルを使っても良いですし、別のロジックを使っても構いません。

重要なのは、プレミアムの計算をオンチェーンでやる必要はないという点です。
オンチェーンで複雑な数式や統計計算を行うとガスコストが高くなるため、プレミアムの計算はオフチェーンで行い、結果だけをコントラクトに渡す方が効率的です。

また、ライターは `updatePremium` によってプレミアムをいつでも更新できます。
これにより、以下のような要素の変化に合わせてプレミアムを調整できます。

* アンダーライング（Underlying）の価格の変動。
* ボラティリティ（価格変動の大きさ）の変化。
* 満期までの残り時間の変化。
* その他、ライターが考慮したい要因。

#### バニラオプションを対象としつつ、エキゾチックオプションへの拡張も想定

**ERC7390**は、標準的なバニラオプションを表現することを目的としています。
ただし、それにとどまらず、「**この標準をベースにしてエキゾチックオプション（より複雑な条件を持つオプション）も構築できる**」ことを意識した設計になっています。

バニラオプションという、もっともベーシックな形をきちんと共通インターフェースとして定義しておけば、エキゾチックオプションは「**追加機能を持つ上位レイヤー**」として作りやすくなります。
基礎となる売買・権利行使・担保管理などを共通化しているため、エコシステム全体での再利用性が高くなります。

#### 単一のオプションではなく、複数発行（イシュー）をまとめて扱う設計

**ERC7390**は、「**1つのコントラクト = 1つのオプションで終わり**」という設計ではありません。
1つのコントラクトの中に、必要に応じていくらでも発行（issuance）を追加できます。

それぞれの発行はIDで区別されており、以下のような操作を互いに独立して行えます。

* 購入（buy）。
* 権利行使（exercise）。
* キャンセル（cancel）。
* 担保の回収（retrieveExpiredTokens）。

これにより、オプションごとに個別にコントラクトをデプロイする必要がなくなり、デプロイコストや管理の手間、ガスコストの面でも効率的です。
「**発行ごとに独立した状態を持つ**」モデルの方が、実務的な運用にも向いています。

#### ヨーロピアン / アメリカン両対応の行使期間設計

**ERC7390**は、`exerciseWindowStart` と `exerciseWindowEnd` という2つの時刻を持つことで、
ヨーロピアンオプションとアメリカンオプションの両方を表現できるようになっています。

買い手は、現在時刻が `exerciseWindowStart` 以上かつ `exerciseWindowEnd` 以下のときにだけオプションを行使できます。

ライターがオプションをヨーロピアンとして扱いたい場合は、満期日時を `exerciseWindowStart` に設定し、`exerciseWindowEnd` を「**満期 + 一定の猶予期間**」として設定します。
これにより、実質的に「**満期付近の短い時間帯だけ行使できるオプション**」として機能します。

一方で、アメリカンとして扱いたい場合は、`exerciseWindowStart` を現在時刻に設定し、
`exerciseWindowEnd` を満期日にすることで、発行時点から満期までいつでも権利行使できる設計にできます。

表にすると以下のようなイメージです。

| 種類 | `exerciseWindowStart` | `exerciseWindowEnd` | 特徴    |
| ----------- | --------- | ------- | ----------------- |
| ヨーロピアン的な使い方 | 満期日時 | 満期日時 + 短い猶予  | 満期時点のみ実質的に行使可能。   |
| アメリカン的な使い方  | 発行時刻（現在時刻） | 満期日時    | 発行から満期までいつでも行使可能。 |

#### ERC1155による複数購入者と部分行使

**ERC7390**は、1つのオプション発行に対して複数の購入者がいることを前提としています。
これは**ERC1155**トークンを使うことで実現しています。

買い手が発行の一部を購入すると、その割合に対応するリディームトークン（**ERC1155**トークン）が発行されます。
このトークンは、発行全体の中の自分の権利の「**持ち分**」を表し、次のような特徴があります。

* 他のユーザーへ送ることができる（権利の譲渡）。
* 保有量の一部だけ権利行使することができる（部分的な `exercise`）。

これにより、1つの発行を複数人でシェアしつつ、それぞれが別々のタイミング・別々の量で権利行使できる柔軟な設計になっています。

#### `allowed` 配列による購入制限

**ERC7390**は、`allowed` というアドレス配列を用意して、オプション発行ごとに
「**誰がその発行を購入できるか**」を制御できるようにしています。

`allowed` が空配列であれば、誰でもオプションを購入できます。
一方で、特定のアドレスだけをこの配列に入れておけば、そのアドレス以外は購入できません。

これは例えば「BobとAliceがオフチェーンでオプションの条件に合意していて、それをオンチェーンに載せたいが、他人に買われたくない」などのケースで有効です。

`allowed` を使ってAliceのアドレスだけに購入を許可しておけば、コントラクトを作成してからAliceが購入するまでの間に、別のアドレスが割り込んで買ってしまうリスクを減らせます。

### ERC20以外への拡張可能性

**ERC7390**は、基本的には**ERC20**トークンを扱う前提で設計されています。
しかし、この標準の考え方は**ERC721**など、他のトークン規格にも応用できます。

例えば、**ERC721**を扱う場合は「**`amount` で数量を扱う**」のではなく、NFT のトークンIDを扱うような形に変える必要があります。
その場合は、構造体のフィールドや関数のシグネチャを「**`amount` ではなく `id` を受け取る**」形に変更することになります。

それでも、以下のような基本的な考え方はそのまま流用できます。

* ライターが担保を預ける。
* 買い手がプレミアムを払って権利を取得する。
* 一定期間内に権利行使できる。
* 行使されなかった担保はライターに戻る。

「**ERC20前提の標準だけれど、他のトークン規格へのベースとしても使える**」ように意識された設計になっています。

## セキュリティ

#### 行使期間（`exerciseWindow`）による制御

**ERC7390**は、`exerciseWindowStart` と `exerciseWindowEnd` により、
買い手がオプションを行使できる時間帯を明確に制限します。

現在時刻が `exerciseWindowEnd` を過ぎると、買い手はもはやオプションを行使できなくなり、
ライターは残っている担保を `retrieveExpiredTokens` で回収できるようになります。

これにより、いつまで「**オプションとして有効**」なのかが明確になり、満期後に権利行使されるような不整合を防いでいます。

#### ヨーロピアン型での裁定取引とプレミアムの更新

ヨーロピアン的な使い方をする場合、行使期間が短い（満期周辺のみ）ため、
そのタイミングで市場価格とストライク価格の差が大きく開いていると、
明らかな裁定（アービトラージ）のチャンスが生まれやすくなります。

例えば、行使期間の開始直後にオプションが大きくインザマネーになっているのに、プレミアムが低いまま放置されていると、ボットがその瞬間にオプションを購入し、すぐに行使してほぼノーリスクで利益を取ることができます。

そのため **ERC7390**では、ヨーロピアン的な発行として扱う場合、行使窓が開いたタイミングで、ライターが `updatePremium` を呼び、プレミアムを大きく引き上げることが強く推奨されています。

こうすることで、「**行使窓が開いた直後に残りのオプションを安く買い叩かれ、
即座に権利行使されて利益を抜かれる**」というケースを減らせます。

なお、アメリカンオプションのように、発行からずっと行使可能な前提で設計する場合は、
行使期間の「**開始時点での急激な裁定**」は生じにくいため、このようなプレミアムの急激な調整は必須ではありません。

#### `updatePremium` によるフロントランリスク

**ERC7390**には、ライターがいつでもプレミアムを更新できる `updatePremium` が用意されていますが、
これには買い手側にとってのリスクがあります。

例えば、買い手が「**現在のプレミアム**」で `buy` を呼び出したとき、ライターがそのトランザクションを見てから、自分の `updatePremium` を先にマイナー（バリデータ）に拾わせることで、プレミアムを急に高く設定してしまう可能性があります。
これはいわゆるフロントラン的な問題です。

このリスクを軽減するために、仕様では買い手に対して次のような注意喚起をしています。

* `approve` でプレミアム支払いを許可するときは、「**合意した額ちょうど**」だけ許可し、それ以上は許可しないようにすること。

こうしておけば、仮にライターがフロントランでプレミアムを吊り上げたとしても、コントラクトは合意額以上のプレミアムを引き出せないため、買い手の損失を抑えることができます。

#### 整数演算による丸め誤差とその影響

**ERC7390**は、1つの発行を複数人でシェアできる設計のため、「**発行全体に対する一部の割合**」という計算が頻繁に発生します。
しかし、ブロックチェーン上では基本的に整数演算しか使えないため、割合の計算で小数点以下が切り捨てられる（floor される）ケースがあります。

特に `buy` で支払うべきプレミアムは、次のような形で計算されます。

```text
支払うプレミアム = (購入量 / 発行量) × 発行全体のプレミアム
```

この値がトークンの最小単位にきれいに割り切れない場合、少しだけ小さい値に切り捨てられます。
その結果、ライターが本来期待していたプレミアムより、わずかに少ない額しか受け取れない場合があります。

理論上は、悪意のある買い手が「**とても小さい量を何度も買う**」ことで、この切り捨て分だけ得をし続ける戦略も考えられます。
ただし、実際には以下の理由から、このリスクはかなり小さいと評価されています。

* 多くのトークンは十分に大きい桁数（decimals）を持っているため、
  切り捨てによる差が非常に小さいこと。
* その差額を得るために多くのトランザクションを投げるガスコストの方が
  通常は高くつくこと。

それでも、プレミアム計算に整数演算の丸めが絡むという点は、設計者・ライター双方が理解しておくべき注意点として明示されています。











## 引用

Ewan Humbert (@Xeway) <xeway@protonmail.com>, Lassi Maksimainen (@mlalma) <lassi.maksimainen@gmail.com>, "ERC-7390: Vanilla Options for ERC-20 Tokens [DRAFT]," Ethereum Improvement Proposals, no. 7390, September 2022. [Online serial]. Available: https://eips.ethereum.org/EIPS/eip-7390.

## 最後に

今回は「**ERC1155**を使ってオプションの分割購入や他者への譲渡を可能にする柔軟な仕組みを提案している**ERC7390**」についてまとめてきました！
いかがだったでしょうか？

質問などがある方は以下のTwitterのDMなどからお気軽に質問してください！

[Twitter @cardene777](https://twitter.com/cardene777)

他の媒体でも情報発信しているのでぜひ他も見ていってください！

https://zenn.dev/heku

https://cardene.substack.com/

https://mirror.xyz/0xcE77b9fCd390847627c84359fC1Bc02fC78f0e58

https://cardene.notion.site/ERC-EIP-2a03fa3ea33d43baa9ed82288f98d4a9?pvs=4
