ERC-7246: Encumber - 分割所有权和保证
一个 token 接口,允许抵押 token 而无需转移所有权。
Authors | Coburn Berry (@coburncoburn), Mykel Pereira (@mykelp), Scott Silver (@scott-silver) |
---|---|
Created | 2023-06-27 |
Discussion Link | https://ethereum-magicians.org/t/encumber-extending-the-erc-20-token-standard-to-allow-pledging-tokens-without-giving-up-ownership/14849 |
Requires | EIP-20 |
摘要
这个 ERC 提出通过添加 Encumber 来扩展 ERC-20 token 标准——Encumber 允许一个账户授予另一个账户独占的权利来移动其部分余额。Encumber 是 ERC-20 授权的一个更强的版本。虽然 ERC-20 approve 授予另一个账户转移指定 token 数量的权限,但 encumber 在确保 token 在需要时可用的前提下授予相同的权限。
动机
这个扩展增加了 ERC-20 token 标准的灵活性,并满足了需要锁定 token 的用例,但在这种情况下,保持 token 的实际所有权是更优的选择。这个接口也可以以一种直接的方式适应其他 token 标准,例如 ERC-721。
Token 持有者通常将他们的 token 转移到智能合约,这些合约将在特定条件下返回 token。在某些情况下,智能合约实际上不需要持有 token,但需要保证它们在必要时可用。由于授权不能提供足够强的保证,目前保证 token 可用性的唯一方法是将 token 转移到智能合约。锁定 token 而不移动它们可以更清楚地表明 token 的权利和所有权。这允许空投和所有权的其他辅助利益到达真正的所有者。它还增加了另一层安全性,其中耗尽 ERC-20 token 池可以在一次转移中完成,迭代账户以转移 encumber 的 token 在 gas 使用方面将具有显著的抑制作用。
规范
本文档中的关键词“必须”、“不得”、“必需”、“应该”、“不应”、“推荐”、“不推荐”、“可以”和“可选”应按照 RFC 2119 和 RFC 8174 中的描述进行解释。
一个兼容的 token 必须实现以下接口
/**
* @dev ERC-7246 标准的接口。
*/
interface IERC7246{
/**
* @dev 当从 `owner` 到 `taker` encumber 了 `amount` 个 token 时触发。
*/
event Encumber(address indexed owner, address indexed taker, uint amount);
/**
* @dev 当 `taker` 对 `owner` 的 encumber 减少了 `amount` 时触发。
*/
event Release(address indexed owner, address indexed taker, uint amount);
/**
* @dev 返回 `owner` 拥有的当前被 encumber 的 token 总量。
* 必须永远不超过 `balanceOf(owner)`
*
* 任何会将 balanceOf(owner) 减少到 encumberedBalanceOf(owner) 以下的函数都必须 revert
*/
function encumberedBalanceOf(address owner) external returns (uint);
/**
* @dev 返回 `owner` encumber 给 `taker` 的 token 数量。
*
* 当 `owner` 或另一个被允许的账户调用 {encumber} 或 {encumberFrom} 时,这个值会增加。
* 当 `taker` 调用 {release} 和 {transferFrom} 时,这个值会减少。
*/
function encumbrances(address owner, address taker) external returns (uint);
/**
* @dev 将调用者 encumber 给 `taker` 的 token 数量增加 `amount`。
* 授予 `taker` 一种保证的权利,可以通过使用 `transferFrom` 从调用者的余额中转移 `amount`。
*
* 如果调用者没有 `amount` 个可用的 token(例如,如果 `balanceOf(caller) - encumbrances(caller) < amount`),则必须 revert。
*
* 触发一个 {Encumber} 事件。
*/
function encumber(address taker, uint amount) external;
/**
* @dev 将 `owner` encumber 给 `taker` 的 token 数量增加 `amount`。
* 授予 `taker` 一种保证的权利,可以通过使用 transferFrom 从 `owner` 处转移 `amount`
*
* 除非所有者账户通过某种机制故意授权了消息的发送者,否则该函数应该 revert。
*
* 如果 `owner` 没有 `amount` 个可用的 token(例如,如果 `balanceOf(owner) - encumbrances(owner) < amount`),则必须 revert。
*
* 触发一个 {Encumber} 事件。
*/
function encumberFrom(address owner, address taker, uint amount) external;
/**
* @dev 将从 `owner` 到调用者的 encumber 的 token 数量减少 `amount`
*
* 触发一个 {Release} 事件。
*/
function release(address owner, uint amount) external;
/**
* @dev 用于读取地址的未 encumber 余额的便捷函数。
* 可以简单地实现为 `balanceOf(owner) - encumberedBalanceOf(owner)`
*/
function availableBalanceOf(address owner) public view returns (uint);
}
原理
该规范旨在补充和镜像 ERC-20 规范,以方便采用和理解。该规范有意地尽可能少地规定这种连接,其中唯一真正的要求是所有者不能转移 encumber 的 token。但是,示例实现包括一些关于在哪里连接 ERC-20 函数的值得注意的决策。它被设计为对标准 ERC-20 定义的最小侵入性。
- 该示例依赖于 approve
来促进 encumberFrom
。如果需要,这个提案允许实现者定义另一种机制,例如 approveEncumber
,它将镜像 ERC-20 授权。
- transferFrom(src, dst, amount)
被编写为首先减少 encumbrances(src, amount)
,然后随后从 allowance(src, msg.sender)
中支出。或者,可以实现 transferFrom
以同时从授权中支出 encumber。这将要求 approve
检查批准的余额不会减少到 encumber 余额所需的金额以下,并且还要使创建授权成为调用 encumber
的先决条件。
通过使用 tokenId
代替 amount
参数,可以将 Encumber 接口扩展到覆盖 ERC-721 token,因为它们都是 uint
。该接口选择了最可能用例(ERC-20)的清晰性,即使它与其他格式兼容。
向后兼容性
此 EIP 与现有的 ERC-20 标准向后兼容。实现必须添加阻止转移 encumber 给另一个账户的 token 的功能。
参考实现
这可以通过修改转移函数来阻止转移 encumber 的 token,并在通过 transferFrom 支出时释放 encumber,从而实现为任何基本 ERC-20 合同的扩展。
// 一个通过阻止转移来实现 encumber 接口的 erc-20 token。
pragma solidity ^0.8.0;
import {ERC20} from "../lib/openzeppelin-contracts/contracts/token/ERC20/ERC20.sol";
import { IERC7246 } from "./IERC7246.sol";
contract EncumberableERC20 is ERC20, IERC7246 {
// Owner -> Taker -> 可以被拿走的金额
mapping (address => mapping (address => uint)) public encumbrances;
// token 所有者的 encumber 余额。对于一个用户来说,encumberedBalance 必须不超过 balanceOf
// 请注意,这意味着重新调整 token 存在减少和违反此协议的风险
mapping (address => uint) public encumberedBalanceOf;
address public minter;
constructor(string memory name, string memory symbol) ERC20(name, symbol) {
minter = msg.sender;
}
function mint(address recipient, uint amount) public {
require(msg.sender == minter, "only minter");
_mint(recipient, amount);
}
function encumber(address taker, uint amount) external {
_encumber(msg.sender, taker, amount);
}
function encumberFrom(address owner, address taker, uint amount) external {
require(allowance(owner, msg.sender) >= amount);
_encumber(owner, taker, amount);
}
function release(address owner, uint amount) external {
_release(owner, msg.sender, amount);
}
// 如果使余额和 encumber 更接近相等,则必须检查
function availableBalanceOf(address a) public view returns (uint) {
return (balanceOf(a) - encumberedBalanceOf[a]);
}
function _encumber(address owner, address taker, uint amount) private {
require(availableBalanceOf(owner) >= amount, "insufficient balance");
encumbrances[owner][taker] += amount;
encumberedBalanceOf[owner] += amount;
emit Encumber(owner, taker, amount);
}
function _release(address owner, address taker, uint amount) private {
if (encumbrances[owner][taker] < amount) {
amount = encumbrances[owner][taker];
}
encumbrances[owner][taker] -= amount;
encumberedBalanceOf[owner] -= amount;
emit Release(owner, taker, amount);
}
function transfer(address dst, uint amount) public override returns (bool) {
// 检查但不花费 encumber
require(availableBalanceOf(msg.sender) >= amount, "insufficient balance");
_transfer(msg.sender, dst, amount);
return true;
}
function transferFrom(address src, address dst, uint amount) public override returns (bool) {
uint encumberedToTaker = encumbrances[src][msg.sender];
bool exceedsEncumbrance = amount > encumberedToTaker;
if (exceedsEncumbrance) {
uint excessAmount = amount - encumberedToTaker;
// 检查是否存在足够的 enencumber token 以从授权中支出
require(availableBalanceOf(src) >= excessAmount, "insufficient balance");
// 超过 Encumber,所以花费所有
_spendEncumbrance(src, msg.sender, encumberedToTaker);
_spendAllowance(src, dst, excessAmount);
} else {
_spendEncumbrance(src, msg.sender, amount);
}
_transfer(src, dst, amount);
return true;
}
function _spendEncumbrance(address owner, address taker, uint256 amount) internal virtual {
uint256 currentEncumbrance = encumbrances[owner][taker];
require(currentEncumbrance >= amount, "insufficient encumbrance");
uint newEncumbrance = currentEncumbrance - amount;
encumbrances[owner][taker] = newEncumbrance;
encumberedBalanceOf[owner] -= amount;
}
}
安全考虑
依赖 balanceOf
来确定可用于转移的 token 数量的各方应改为依赖 balanceOf(account) - encumberedBalance(account)
,或者,如果已实现,则依赖 availableBalanceOf(account)
。
可以通过更改 transfer
和 transferFrom
来阻止,从而以一种直接的方式实现 encumber 余额始终由 token 余额支持的属性。如果还有其他可以更改用户余额的函数,例如重新调整 token 或管理员销毁函数,则实现者必须添加额外的保护措施,以同样确保这些函数阻止将任何给定账户的 balanceOf(account)
减少到 encumberedBalanceOf(account)
以下。
版权
版权和相关权利通过 CC0 放弃。
Citation
Please cite this document as:
Coburn Berry (@coburncoburn), Mykel Pereira (@mykelp), Scott Silver (@scott-silver), "ERC-7246: Encumber - 分割所有权和保证 [DRAFT]," Ethereum Improvement Proposals, no. 7246, June 2023. [Online serial]. Available: https://eips.ethereum.org/EIPS/eip-7246.