Alert Source Discuss
⚠️ Draft Standards Track: ERC

ERC-7390: ERC-20 代币的香草期权

用于创建、管理和执行简单的限时看涨/看跌(香草)期权的接口。

Authors Ewan Humbert (@Xeway) <xeway@protonmail.com>, Lassi Maksimainen (@mlalma) <lassi.maksimainen@gmail.com>
Created 2022-09-02
Discussion Link https://ethereum-magicians.org/t/erc-7390-vanilla-option-standard/15206
Requires EIP-20, EIP-1155

摘要

本标准定义了一套全面的函数和事件,用于促进香草期权的无缝交互(创建、管理、执行等)。

香草期权授予在指定时间内以设定价格买卖资产的权利,而非义务。

此标准不代表简单的期权,该期权在到期日后将毫无用处。相反,它可以根据需要存储任意数量的发行。每个发行都由一个 ID 标识,并且可以独立于其他发行进行购买、行使、取消等操作。
每个发行都有抵押品,这意味着卖方必须在买方购买期权之前向合约提供抵押品。如果买方未在行使窗口内行使期权,则卖方可以取回抵押品。
买方可以决定仅购买发行的一部分(这意味着可以有多个买方),并将相应地收到代表发行部分份额的代币(ERC-1155)。从现在开始,我们将这些代币称为赎回代币。这些代币可以在用户之间交换,并用于行使期权。通过这种机制,买方可以决定仅行使他所购买的部分期权。
此外,如果尚未购买任何期权,卖方可以决定取消发行。他也有权随时更新溢价。这不会影响已经购买的期权。
标的代币、行权代币和溢价代币均为 ERC-20 代币。

在下文中,将有时使用复数术语期。这可以指买方购买并可以行使的赎回代币的数量。

动机

期权是广泛使用的金融工具,对于投资者和交易者来说具有真正的实用性。它提供了多功能的风险管理工具和投机机会。
在去中心化金融中,涌现了许多期权出售平台,但这些协议中的每一个都实现了其自身的期权定义。这导致了不兼容性,这很遗憾,因为期权应该像可替代/不可替代代币一样具有互操作性。
通过引入香草期权合约的标准接口,我们的目标是促进一个更具包容性和互操作性的衍生品生态系统。该标准将增强用户体验,并促进去中心化期权平台的开发,使用户能够跨不同应用无缝交易期权。此外,该标准旨在代表香草期权,这是最常见的期权类型。该标准可以用作更复杂的期权的基础,例如奇异期权。

规范

此提案的实现必须同时实现 ERC-1155,以允许仅购买发行的一部分。

接口

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);
}

状态变量描述

在创建时,用户必须提供已填写的 VanillaOptionData 结构实例,其中包含用于初始化期权发行的所有关键信息。

side

类型:enum

期权的一方。可以取值 CallPutCall 期权赋予期权购买者行使任何获得的期权代币的权利,以使用 strikeToken 从期权卖方购买给定 strike 价格的 underlying 代币。同样,Put 期权赋予期权购买者以 strike 价格将 underlying 代币出售给期权卖方的权利。

underlyingToken

类型:address (ERC-20 合约)

标的代币。

amount

类型:uint256

可以行使的标的代币的最大数量。

请注意代币的小数位数!

strikeToken

类型:address (ERC-20 合约)

用作确定行权价格参考的代币。

strike

类型:uint256

行权价格。期权购买者可能只能行使发行的一部分,并且支付的行权价格必须由合约进行调整以反映这一点。

请注意,strike 旨在表示单个 underlyingTokenstrikeToken 价格。

请注意代币的小数位数!

premiumToken

类型:address (ERC-20 合约)

溢价代币。

premium

类型:uint256

溢价是期权购买者必须支付给期权卖方的价格,以补偿卖方发行期权所承担的风险。期权溢价根据各种因素而变化,最重要的因素是标的代币的波动性、行权价格和行使期权剩余的时间。

请注意,溢价是为行使发行的总 amount 而设置的。购买者可能只能购买部分期权代币,并且支付的溢价必须由合约进行调整以反映这一点。

请注意代币的小数位数!

exerciseWindowStart

类型:uint256
格式: 自 Unix 纪元以来的时间戳(以秒为单位)

期权行使窗口开始时间。当当前时间大于或等于 exerciseWindowStart 且小于或等于 exerciseWindowEnd 时,期权所有者可以行使它们。

exerciseWindowEnd

类型:uint256
格式: 自 Unix 纪元以来的时间戳(以秒为单位)

期权行使窗口结束时间。当当前时间大于或等于 exerciseWindowStart 且小于或等于 exerciseWindowEnd 时,期权所有者可以行使它们。当当前时间大于 exerciseWindowEnd 时,买方无法行使,卖方可以取回剩余的标的(看涨)或行权(看跌)代币。

allowed

类型:address[]

允许购买该发行的地址。如果数组为空,则允许所有地址购买该发行。

VanillaOptionData 存储在 OptionIssuance 结构中,该结构用于存储期权发行数据。它包含其他信息。

writer

类型:address

卖方的地址,即创建期权的地址。

exercisedAmount

类型:uint256

已行使的标的代币数量。

soldAmount

类型:uint256

已为此发行购买的标的代币数量。

transferredExerciseCost

类型:uint256

已转移到期权发行卖方(看涨)或买方(看跌)的 strikeToken 代币数量。
这是一个实用变量,用于不必总是计算已转移的总行权成本。它与 exercisedAmount 同时更新。计算公式为 (amount * selectedIssuance.data.strike) / (10**underlyingToken.decimals())

exerciseCost

类型:uint256

行权成本。它表示卖方必须存入合约的抵押品(看跌),或者如果所有买方都决定行使(看涨),卖方可以收到的 strikeToken 代币数量。
这是一个实用变量,用于不必总是计算行权成本。我们在期权创建时计算它。计算公式为 (strike * amount) / (10 ** underlyingToken.decimals())

函数描述

constructor

此标准不需要构造函数,但合约必须实现 ERC-1155 接口。因此,合约必须调用 ERC-1155 构造函数。

create

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

期权卖方创建新的期权代币,并使用 create() 定义期权参数。作为参数,期权卖方需要填写 VanillaOptionData 数据结构实例并将其传递给该方法。作为创建期权代币的一部分,该函数将抵押品从期权卖方转移到合约。

强烈建议将期权发行完全抵押化,以防止交易对手风险增加。对于创建看涨(看跌)期权发行,卖方需要允许将 underlyingToken (strikeToken) 的 amount (strike) 代币数量转移到期权合约,然后再调用 create()

请注意,本标准未定义期权卖方在期权合约允许抵押不足的情况下“重新抵押”抵押品的功能。合约需要相应地调整其 API 和实现。

如果 underlyingTokenstrikeToken 是零地址,则必须回滚。
如果 premium 不为 0 且 premiumToken 是零地址,则必须回滚。
如果 amountstrike 为 0,则必须回滚。
如果 exerciseWindowStart 小于当前时间,或者 exerciseWindowEnd 小于 exerciseWindowStart,则必须回滚。

如果期权发行成功,则返回一个 id 值,该值引用期权合约中创建的期权发行。 如果期权发行成功,则发出 Created 事件。

buy

function buy(uint256 id, uint256 amount) external;

允许买方从具有定义的 id 的期权发行中购买 amount 的期权代币。

买方必须允许代币合约将指定的 premiumToken 中的(总的)premium 的(一部分)转移给期权卖方。在调用该函数期间,溢价将直接转移给卖方。

如果 allowed 数组不为空,则买方的地址必须包含在此列表中。
如果 amount 为 0 或大于可供购买的剩余期权,则必须回滚。
如果当前时间大于 exerciseWindowEnd,则必须回滚。

如果购买成功,则将 amount 赎回代币铸造到买方的地址。 如果购买成功,则发出 Bought 事件。

exercise

function exercise(uint256 id, uint256 amount) external;

允许买方从具有定义的 id 的期权发行中行使 amount 的期权代币。

  • 如果期权是看涨期权,则买方以指定的行权价格支付给卖方,并获得指定的标的代币。
  • 如果期权是看跌期权,则买方将标的代币转移给卖方,并以指定的行权价格获得报酬。

买方必须允许在调用 exercise() 之前花费 strikeTokenunderlyingToken

仅当 exerciseWindowStart <= 当前时间 <= exerciseWindowEnd 时,才必须进行行使。
如果 amount 为 0 或买方没有必要的赎回代币来行使期权,则必须回滚。

如果行使成功,则从买方的地址燃烧 amount 赎回代币。 如果期权行使成功,则发出 Exercised 事件。

retrieveExpiredTokens

function retrieveExpiredTokens(uint256 id, address receiver) external;

允许卖方取回未行使的抵押代币。这些代币将转移到 receiver
如果期权是看涨期权,则 receiver 取回标的代币。如果期权是看跌期权,则 receiver 取回行权代币。

如果调用该函数的地址不是期权发行的卖方,则必须回滚。
如果 exerciseWindowEnd 大于或等于当前时间,则必须回滚。
如果等于零地址,则必须receiver 设置为调用者的地址。

将未行使的抵押品转移到卖方的地址。 如果取回成功,则可以从合约中删除期权发行。 如果取回成功,则发出 Expired 事件。

cancel

function cancel(uint256 id, address receiver) external;

允许卖方取消期权并取回用作抵押品的代币。这些代币将转移到 receiver
如果期权是看涨期权,则 receiver 取回标的代币。如果期权是看跌期权,则 receiver 取回行权代币。

如果调用该函数的地址不是期权发行的卖方,则必须回滚。
如果至少一个期权的一部分已被购买,则必须回滚。
如果等于零地址,则必须receiver 设置为调用者的地址。

将未行使的抵押品转移到卖方的地址。 如果取消成功,则可以从合约中删除期权发行。 如果取消成功,则发出 Canceled 事件。

updatePremium

function updatePremium(uint256 id, uint256 amount) external;

允许卖方更新买方购买期权需要提供的溢价。

请注意,amount 将用于整个标的数量,而不仅仅是可能仍可供购买的期权。

如果调用该函数的地址不是期权发行的卖方,则必须回滚。
如果当前时间大于 exerciseWindowEnd,则必须回滚。

成功处理函数调用时,发出 PremiumUpdated 事件。

updateAllowed

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

允许卖方更新可以购买期权发行的允许地址列表。
如果买方已经购买了期权,并且他的地址不在新列表中,他仍然可以行使他购买的期权。

如果调用该函数的地址不是期权发行的卖方,则必须回滚。
如果当前时间大于 exerciseWindowEnd,则必须回滚。

成功处理函数调用时,发出 AllowedUpdated 事件。

issuance

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

返回给定 id 的期权发行的所有关键信息。

事件

Created

event Created(uint256 id);

当卖方成功提供期权发行数据(并锁定合约的抵押品)时发出。给定的 id 标识特定的期权发行。

Bought

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

当期权已购买时发出。提供有关期权发行 idbuyer 的地址和购买的期权 amount 的信息。

Exercised

event Exercised(uint256 indexed id, uint256 amount);

当期权已从具有给定 id 和给定 amount 的期权发行中行使时发出。

Expired

event Expired(uint256 indexed id);

id 的期权发行的卖方已取回未行使的抵押品时发出。

Canceled

event Canceled(uint256 indexed id);

当给定 id 的期权发行已被卖方取消时发出。

PremiumUpdated

event PremiumUpdated(uint256 indexed id, uint256 amount);

当卖方将给定 id 的期权发行的溢价更新为 amount 时发出。请注意,更新后的溢价适用于总发行量。

AllowedUpdated

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

当卖方更新给定 id 的期权发行的允许地址列表时发出。

错误

Forbidden

当调用者不允许执行某些操作时回滚(通用)。

TransferFailed

当代币转移失败时回滚。

TimeForbidden

当执行的当前时间无效时回滚。

AmountForbidden

当金额无效时回滚。

InsufficientBalance

当调用者余额不足以执行操作时回滚。

具体示例

看涨期权

假设 Bob 出售一个看涨期权。
他授权任何人在 2023 年 7 月 14 日2023 年 7 月 16 日(午夜)之间以每个 25 TokenB 的价格购买 8 TokenA
对于这样的合约,他希望收到 10 TokenC 的溢价。

在创建期权之前,Bob 必须将抵押品转移到合约。此抵押品对应于如果期权被完全行使 (amount),他必须提供的代币。对于此期权,他必须提供 8 TokenA 作为抵押品。他通过在 TokenA 的合约上调用函数 approve(address spender, uint256 amount) 并将合约的地址 (spender) 作为参数,并为 amount 提供:8 * 10^(TokenA 的小数位数) 来做到这一点。然后,Bob 可以在合约上执行 create() 以发行期权,并提供以下参数:

  • side: Call
  • underlyingToken: TokenA 的地址
  • amount: 8 * 10^(TokenA 的小数位数)
  • strikeToken: TokenB 的地址
  • strike: 25 * 10^(TokenB 的小数位数)
  • premiumToken: TokenC 的地址
  • premium: 10 * 10^(TokenC 的小数位数)
  • exerciseWindowStart: 1689292800 (2023-07-14 时间戳)
  • exerciseWindowEnd: 1689465600 (2023-07-16 时间戳)
  • allowed: [] (对任何人开放)

该发行的 ID 为 88。

Alice 希望能够仅购买 4 TokenA。她将首先通过允许在 TokenC 的合约上调用 approve(address spender, uint256 amount) 并将合约的地址 (spender) 作为参数,以及为 amount 提供:4*10^(TokenA 的小数位数) * 10*10^(TokenC 的小数位数) / 8*10^(TokenA 的小数位数) (amountToBuy * premium / amount) 来支付溢价(与其份额成正比)。然后,她可以在合约上执行 buy(88, 4 * 10^(TokenA 的小数位数)),并将收到 4*10^(TokenA 的小数位数) 赎回代币。

John 也想购买 2 TokenA。他做了同样的事情,并收到 2*10^(TokenA 的小数位数) 赎回代币。

我们在 7 月 15 日,Alice 想要行使他的期权,因为 1 TokenA 的交易价格为 50 TokenB!她需要允许合约从她的帐户转移 4*10^(TokenA 的小数位数) * 25*10^(TokenB 的小数位数) / 10^(TokenA 的小数位数) (amountToExercise * strike / 10^(TokenA 的小数位数)) TokenB 才能行使期权。当她在合约上调用 exercise(88, 4 * 10^(TokenA 的小数位数)) 时,它会将 4 TokenA 转移给 Alice,并将 4*25 TokenB 转移给 Bob。

John 决定将他的行使权让给他的朋友 Jimmy。他只需将他的 2*10^(TokenA 的小数位数) 赎回代币转移到 Jimmy 的地址即可做到这一点。
Jimmy 决定仅使用该期权购买 1 TokenA。因此,他将(通过合约)给 Bob 1*10^(TokenA 的小数位数) * 25*10^(TokenB 的小数位数) / 10^(TokenA 的小数位数)

看跌期权

假设 Bob 出售一个看跌期权。
他授权任何人在 2023 年 7 月 14 日2023 年 7 月 16 日(午夜)之间以每个 25 TokenB 的价格卖给他 8 TokenA
对于这样的合约,他希望收到 10 TokenC 的溢价。

在创建期权之前,Bob 必须将抵押品转移到合约。此抵押品对应于如果期权被完全行使 (exerciseCost),他必须提供的代币。对于此期权,他必须提供 200 TokenB (8 * 25) 作为抵押品。他通过在 TokenB 的合约上调用函数 approve(address spender, uint256 amount) 并将合约的地址 (spender) 作为参数,并为 amount 提供:25*10^(Token B 的小数位数) * 8*10^(TokenB 的小数位数) / 10^(TokenA 的小数位数) (strike * amount / 10^(underlyingToken 的小数位数)) 来做到这一点。然后,Bob 可以在合约上执行 create() 以发行期权,并提供以下参数:

  • side: Put
  • underlyingToken: TokenA 的地址
  • amount: 8 * 10^(TokenA 的小数位数)
  • strikeToken: TokenB 的地址
  • strike: 25 * 10^(TokenB 的小数位数)
  • premiumToken: TokenC 的地址
  • premium: 10 * 10^(TokenC 的小数位数)
  • exerciseWindowStart: 1689292800 (2023-07-14 时间戳)
  • exerciseWindowEnd: 1689465600 (2023-07-16 时间戳)
  • allowed: [] (对任何人都开放)

该发行的 ID 为 88。

Alice 希望能够仅出售 4 TokenA。她将首先通过允许在 TokenC 的合约上调用 approve(address spender, uint256 amount) 并将合约的地址 (spender) 作为参数,以及为 amount 提供:4*10^(TokenA 的小数位数) * 10*10^(TokenC 的小数位数) / 8*10^(TokenA 的小数位数) (amountToSell * premium / amount) 来支付溢价(与其份额成正比)。然后,她可以在合约上执行 buy(88, 4 * 10^(TokenA 的小数位数)),并将收到 4*10^(TokenA 的小数位数) 赎回代币。

John 也想出售 2 TokenA。他做了同样的事情,并收到 2*10^(TokenA 的小数位数) 赎回代币。

我们在 7 月 15 日,Alice 想要行使他的期权,因为 1 TokenA 的交易价格仅为 10 TokenB!她需要允许合约从她的帐户转移 4 * 10^(TokenA 的小数位数) TokenA 才能行使期权。当她在合约上调用 exercise(88, 4 * 10^(TokenA 的小数位数)) 时,它会将 4*25 TokenB 转移给 Alice,并将 4 TokenA 转移给 Bob。

John 决定将他的行使权让给他的朋友 Jimmy。他只需将他的 2*10^(TokenA 的小数位数) 赎回代币转移到 Jimmy 的地址即可做到这一点。
Jimmy 决定仅使用该期权出售 1 TokenA。因此,他将(通过合约)给 Bob 1*10^(TokenA 的小数位数)

取回抵押品

假设 Alice 从未行使他的期权,因为这对她来说没有足够的利润。为了取回他的抵押品,Bob 必须等待当前时间大于 exerciseWindowEnd。在这些示例中,此特征设置为 2 天,因此他可以通过简单地调用 retrieveExpiredTokens() 从 7 月 16 日取回他的抵押品。

理由

此合约的概念是无预言机的,因为我们假设理性的购买者仅在对他有利时才行使他的期权。

溢价由期权卖方确定。卖方可以自由选择如何计算溢价,例如,使用 Black-Scholes 模型 或其他方法。卖方可以随意更新溢价,以便根据标的价格、波动性、期权到期时间和其他此类因素的变化进行调整。出于 gas 成本的目的,在链下计算溢价更好。

此 ERC 旨在代表香草期权。但是,奇异期权可以在此 ERC 的基础上构建。
此合约不是表示在到期日后将毫无用处的单个期权,而是可以根据需要存储任意数量的发行。每个发行都由一个 ID 标识,并且可以独立于其他发行进行购买、行使、取消等操作。这是一种更好的 gas 成本方法。

它被设计为通过引入 exerciseWindowStartexerciseWindowEnd 数据点使期权可以是欧式期权或美式期权。购买者只能在 exerciseWindowStartexerciseWindowEnd 之间行使。

  • 如果期权卖方认为期权是欧式期权,他可以将 exerciseWindowStart 设置为与到期日一致,并将 exerciseWindowEnd 设置为到期日 + 确定的时间范围,以便购买者有一段时间来行使。
  • 如果期权卖方认为期权是美式期权,他可以将 exerciseWindowStart 设置为当前时间,购买者将能够立即行使该期权。

该合约本质上支持单个期权发行的多个购买者。这是通过使用 ERC-1155 代币来表示期权来实现的。当购买者购买期权发行的一部分时,他会收到代表期权发行部分的 ERC-1155 代币。这些代币可以在用户之间交换,并用于行使期权。通过这种机制,购买者可以决定仅行使他所购买的一部分。

该合约实现了 allowed 数组,该数组可用于限制可以购买期权的发行的地址。如果两个用户在链下就期权达成一致,并且他们想在链上创建它,这将非常有用。这可以防止在合约创建和第二个用户购买之间,链上用户已经购买了合约的风险。

此 ERC 旨在处理 ERC-20 代币。但是,此标准可以用作处理其他类型的代币(例如 ERC-721 代币)的良好基础。某些属性和函数签名(例如,提供 id 而不是 amount)将不得不更改,但总体思路将保持不变。

安全注意事项

合约包含 exerciseWindowStartexerciseWindowEnd 数据点。这些定义了购买者行使期权的确定时间范围。当当前时间大于 exerciseWindowEnd 时,购买者将无法行使期权,卖方将能够取回任何剩余的抵押品。

为了防止期权卖方认为发行是欧式期权时的明显套利情况,我们强烈建议期权卖方在行使窗口打开时调用 updatePremium 以大幅提高溢价。这将确保机器人无法购买任何剩余期权并立即行使它们以获取快速利润。当然,此标准可以自定义,也许用户会发现使用可用工具自动更新溢价比手动更新更方便(尤其是当溢价基于特定动态指标(如 Black-Scholes 模型)时)。如果期权发行被认为是美式期权,则当然不需要进行此类调整。

此标准实现了 updatePremium 函数,该函数允许卖方随时更新溢价。此函数可能会给购买者带来安全问题:购买者可以购买期权,并且卖方可以通过将溢价更新为非常高的值来抢先购买者的交易。为防止这种情况,我们建议购买者仅允许合约花费约定的溢价金额,而不是更多。

该合约支持单个期权发行的多个购买者,这意味着可以购买期权发行的一部分。生态系统实际上并不支持非整数,因此分数有时会导致舍入错误。这可能会导致意外的结果,尤其是在 buy 函数中:如果设置了溢价,则购买者只需支付与他想要购买的期权数量成比例的部分。如果该分数不是整数,则会截断并因此向下舍入。这意味着卖方收到的溢价将低于预期。考虑到大多数代币都有大量的小数位数,我们认为这种风险非常小,但重要的是要意识到它。一些购买者可以通过重复购买小部分来利用这一点,因此支付的溢价低于预期。但是,考虑到 gas 成本,这可能不会盈利。

版权

版权和相关权利通过 CC0 放弃。

Citation

Please cite this document as:

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