-
Notifications
You must be signed in to change notification settings - Fork 12.4k
Expand file tree
/
Copy pathdraft-ERC7246.sol
More file actions
130 lines (108 loc) · 5.37 KB
/
draft-ERC7246.sol
File metadata and controls
130 lines (108 loc) · 5.37 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;
import {ERC20} from "../ERC20.sol";
import {IERC7246} from "../../../interfaces/draft-IERC7246.sol";
import {Math} from "../../../utils/math/Math.sol";
/**
* @title ERC7246
* @dev An extension of {ERC20} that adds support for encumbrances of token balances. Encumbrances are a
* stronger version of allowances: they grant the `spender` an exclusive right to transfer tokens from the
* `owner`'s balance without reducing the `owner`'s balance until the tokens are transferred or the
* encumbrance is released.
*/
abstract contract ERC7246 is ERC20, IERC7246 {
/// @dev Thrown when the result of an {_update} or {_encumber} call would result in negative {availableBalanceOf}.
error ERC7246InsufficientAvailableBalance(uint256 available, uint256 required);
/// @dev Thrown when an account tries to release more encumbered tokens than it has.
error ERC7246InsufficientEncumbrance(uint256 encumbered, uint256 required);
/// @dev Thrown when an account tries to encumber tokens to itself.
error ERC7246SelfEncumbrance();
mapping(address owner => mapping(address spender => uint256)) private _encumbrances;
mapping(address owner => uint256) private _encumberedBalances;
/// @inheritdoc IERC7246
function encumberedBalanceOf(address owner) public view virtual returns (uint256) {
return _encumberedBalances[owner];
}
/// @inheritdoc IERC7246
function availableBalanceOf(address owner) public view virtual returns (uint256) {
return balanceOf(owner) - encumberedBalanceOf(owner);
}
/// @inheritdoc IERC7246
function encumbrances(address owner, address spender) public view virtual returns (uint256) {
return _encumbrances[owner][spender];
}
/// @inheritdoc IERC7246
function encumber(address spender, uint256 amount) public virtual {
_encumber(msg.sender, spender, amount);
}
/// @inheritdoc IERC7246
function encumberFrom(address owner, address spender, uint256 amount) public virtual {
_spendAllowance(owner, msg.sender, amount);
_encumber(owner, spender, amount);
}
/// @inheritdoc IERC7246
function release(address owner, uint256 amount) public virtual {
_releaseEncumbrance(owner, msg.sender, amount);
}
/**
* @dev Encumber `amount` of tokens from `owner` to `spender`. Encumbering tokens grants an exclusive right
* to transfer the tokens without removing them from `owner`'s balance. Release the tokens by calling
* {release} or transfer them by calling {transferFrom}.
*/
function _encumber(address owner, address spender, uint256 amount) internal virtual {
require(owner != spender, ERC7246SelfEncumbrance());
uint256 availableBalance = availableBalanceOf(owner);
require(availableBalance >= amount, ERC7246InsufficientAvailableBalance(availableBalance, amount));
// Given that the `availableBalanceOf` is `balanceOf(owner) - encumberedBalanceOf(owner)`,
// we know that the new `_encumberedBalances[owner] <= balanceOf(owner)` and thus no overflow is possible.
// `_encumberedBalances[owner] >= _encumbrances[owner][spender]`, so no overflow is possible there either.
unchecked {
_encumbrances[owner][spender] += amount;
_encumberedBalances[owner] += amount;
}
emit Encumber(owner, spender, amount);
}
/**
* @dev Release `amount` of encumbered tokens from `owner` to `spender`.
*
* - Will revert if there are insufficient encumbered tokens.
* - Emits the {IERC7246-Release} event.
*/
function _releaseEncumbrance(address owner, address spender, uint256 amount) internal virtual {
uint256 encumbered = encumbrances(owner, spender);
require(encumbered >= amount, ERC7246InsufficientEncumbrance(encumbered, amount));
unchecked {
_encumbrances[owner][spender] -= amount;
_encumberedBalances[owner] -= amount;
}
emit Release(owner, spender, amount);
}
/**
* @dev See {ERC20-_spendAllowance}. Encumbrances are consumed first, then the remaining amount
* is passed to `super._spendAllowance`.
*/
function _spendAllowance(address owner, address spender, uint256 amount) internal virtual override {
uint256 amountEncumbered = encumbrances(owner, spender);
uint256 remainingAllowance = amount;
if (amountEncumbered != 0) {
uint256 encumberedToUse = Math.min(amount, amountEncumbered);
_releaseEncumbrance(owner, spender, encumberedToUse);
unchecked {
remainingAllowance -= encumberedToUse;
}
}
super._spendAllowance(owner, spender, remainingAllowance);
}
/// @dev See {ERC20-_update}. Ensures that `from` has sufficient {availableBalanceOf} to cover the `amount` being transferred.
function _update(address from, address to, uint256 amount) internal virtual override {
super._update(from, to, amount);
if (from != address(0)) {
uint256 balanceOfFrom = balanceOf(from);
uint256 encumberedBalanceOfFrom = encumberedBalanceOf(from);
require(
balanceOfFrom >= encumberedBalanceOfFrom,
ERC7246InsufficientAvailableBalance(balanceOfFrom + amount - encumberedBalanceOfFrom, amount)
);
}
}
}