generated from Uniswap/foundry-template
-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathSimpleAllocator.sol
221 lines (196 loc) · 9.46 KB
/
SimpleAllocator.sol
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
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.27;
import {IAllocator} from '../interfaces/IAllocator.sol';
import {ISimpleAllocator} from '../interfaces/ISimpleAllocator.sol';
import {IERC1271} from '@openzeppelin/contracts/interfaces/IERC1271.sol';
import {ERC6909} from '@solady/tokens/ERC6909.sol';
import {ITheCompact} from '@uniswap/the-compact/interfaces/ITheCompact.sol';
import {ResetPeriod} from '@uniswap/the-compact/lib/IdLib.sol';
import {Compact} from '@uniswap/the-compact/types/EIP712Types.sol';
import {ForcedWithdrawalStatus} from '@uniswap/the-compact/types/ForcedWithdrawalStatus.sol';
contract SimpleAllocator is ISimpleAllocator {
// keccak256("Compact(address arbiter,address sponsor,uint256 nonce,uint256 expires,uint256 id,uint256 amount)")
bytes32 constant COMPACT_TYPEHASH = 0xcdca950b17b5efc016b74b912d8527dfba5e404a688cbc3dab16cb943287fec2;
address public immutable COMPACT_CONTRACT;
uint256 public immutable MIN_WITHDRAWAL_DELAY;
uint256 public immutable MAX_WITHDRAWAL_DELAY;
/// @dev mapping of tokenHash to the expiration of the lock
mapping(bytes32 tokenHash => uint256 expiration) internal _claim;
/// @dev mapping of tokenHash to the amount of the lock
mapping(bytes32 tokenHash => uint256 amount) internal _amount;
/// @dev mapping of tokenHash to the nonce of the lock
mapping(bytes32 tokenHash => uint256 nonce) internal _nonce;
/// @dev mapping of the lock digest to the tokenHash of the lock
mapping(bytes32 digest => bytes32 tokenHash) internal _sponsor;
constructor(address compactContract_, uint256 minWithdrawalDelay_, uint256 maxWithdrawalDelay_) {
COMPACT_CONTRACT = compactContract_;
MIN_WITHDRAWAL_DELAY = minWithdrawalDelay_;
MAX_WITHDRAWAL_DELAY = maxWithdrawalDelay_;
ITheCompact(COMPACT_CONTRACT).__registerAllocator(address(this), '');
}
/// @inheritdoc ISimpleAllocator
function lock(Compact calldata compact_) external {
bytes32 tokenHash = _checkAllocation(compact_, true);
bytes32 digest = keccak256(
abi.encodePacked(
bytes2(0x1901),
ITheCompact(COMPACT_CONTRACT).DOMAIN_SEPARATOR(),
keccak256(
abi.encode(
COMPACT_TYPEHASH,
compact_.arbiter,
compact_.sponsor,
compact_.nonce,
compact_.expires,
compact_.id,
compact_.amount
)
)
)
);
_claim[tokenHash] = compact_.expires;
_amount[tokenHash] = compact_.amount;
_nonce[tokenHash] = compact_.nonce;
_sponsor[digest] = tokenHash;
emit Locked(compact_.sponsor, compact_.id, compact_.amount, compact_.expires);
}
/// @inheritdoc IAllocator
function attest(address, address from_, address, uint256 id_, uint256 amount_) external view returns (bytes4) {
if (msg.sender != COMPACT_CONTRACT) {
revert InvalidCaller(msg.sender, COMPACT_CONTRACT);
}
uint256 balance = ERC6909(COMPACT_CONTRACT).balanceOf(from_, id_);
// Check unlocked balance
bytes32 tokenHash = _getTokenHash(id_, from_);
uint256 fullAmount = amount_;
if (_claim[tokenHash] > block.timestamp) {
// Lock is still active, add the locked amount if the nonce has not yet been consumed
fullAmount += ITheCompact(COMPACT_CONTRACT).hasConsumedAllocatorNonce(_nonce[tokenHash], address(this))
? 0
: _amount[tokenHash];
}
if (balance < fullAmount) {
revert InsufficientBalance(from_, id_, balance, fullAmount);
}
return this.attest.selector;
}
/// @inheritdoc IERC1271
/// @dev we trust the compact contract to check the nonce is not already consumed
function isValidSignature(bytes32 hash, bytes calldata) external view returns (bytes4 magicValue) {
// The hash is the digest of the compact
bytes32 tokenHash = _sponsor[hash];
if (tokenHash == bytes32(0) || _claim[tokenHash] <= block.timestamp) {
revert InvalidLock(hash, _claim[tokenHash]);
}
return IERC1271.isValidSignature.selector;
}
/// @inheritdoc ISimpleAllocator
function checkTokensLocked(uint256 id_, address sponsor_)
external
view
returns (uint256 amount_, uint256 expires_)
{
bytes32 tokenHash = _getTokenHash(id_, sponsor_);
uint256 expires = _claim[tokenHash];
if (
expires <= block.timestamp
|| ITheCompact(COMPACT_CONTRACT).hasConsumedAllocatorNonce(_nonce[tokenHash], address(this))
) {
return (0, 0);
}
return (_amount[tokenHash], expires);
}
/// @inheritdoc ISimpleAllocator
function checkCompactLocked(Compact calldata compact_) external view returns (bool locked_, uint256 expires_) {
bytes32 tokenHash = _getTokenHash(compact_.id, compact_.sponsor);
bytes32 digest = keccak256(
abi.encodePacked(
bytes2(0x1901),
ITheCompact(COMPACT_CONTRACT).DOMAIN_SEPARATOR(),
keccak256(
abi.encode(
COMPACT_TYPEHASH,
compact_.arbiter,
compact_.sponsor,
compact_.nonce,
compact_.expires,
compact_.id,
compact_.amount
)
)
)
);
uint256 expires = _claim[tokenHash];
bool active = _sponsor[digest] == tokenHash && expires > block.timestamp
&& !ITheCompact(COMPACT_CONTRACT).hasConsumedAllocatorNonce(_nonce[tokenHash], address(this));
if (active) {
(ForcedWithdrawalStatus status, uint256 forcedWithdrawalAvailableAt) =
ITheCompact(COMPACT_CONTRACT).getForcedWithdrawalStatus(compact_.sponsor, compact_.id);
if (status == ForcedWithdrawalStatus.Enabled && forcedWithdrawalAvailableAt < expires) {
expires = forcedWithdrawalAvailableAt;
active = expires > block.timestamp;
}
}
return (active, active ? expires : 0);
}
function _getTokenHash(uint256 id_, address sponsor_) internal pure returns (bytes32) {
return keccak256(abi.encode(id_, sponsor_));
}
function _checkAllocation(Compact memory compact_, bool checkSponsor_) internal view returns (bytes32) {
// Check msg.sender is sponsor
if (checkSponsor_ && msg.sender != compact_.sponsor) {
revert InvalidCaller(msg.sender, compact_.sponsor);
}
bytes32 tokenHash = _getTokenHash(compact_.id, compact_.sponsor);
// Check no lock is already active for this sponsor
if (
_claim[tokenHash] > block.timestamp
&& !ITheCompact(COMPACT_CONTRACT).hasConsumedAllocatorNonce(_nonce[tokenHash], address(this))
) {
revert ClaimActive(compact_.sponsor);
}
// Check expiration is not too soon or too late
if (
compact_.expires < block.timestamp + MIN_WITHDRAWAL_DELAY
|| compact_.expires > block.timestamp + MAX_WITHDRAWAL_DELAY
) {
revert InvalidExpiration(compact_.expires);
}
(, address allocator, ResetPeriod resetPeriod,) = ITheCompact(COMPACT_CONTRACT).getLockDetails(compact_.id);
if (allocator != address(this)) {
revert InvalidAllocator(allocator);
}
// Check expiration is not longer then the tokens forced withdrawal time
if (compact_.expires > block.timestamp + _resetPeriodToSeconds(resetPeriod)) {
revert ForceWithdrawalAvailable(compact_.expires, block.timestamp + _resetPeriodToSeconds(resetPeriod));
}
// Check expiration is not past an active force withdrawal
(, uint256 forcedWithdrawalExpiration) =
ITheCompact(COMPACT_CONTRACT).getForcedWithdrawalStatus(compact_.sponsor, compact_.id);
if (forcedWithdrawalExpiration != 0 && forcedWithdrawalExpiration < compact_.expires) {
revert ForceWithdrawalAvailable(compact_.expires, forcedWithdrawalExpiration);
}
// Check nonce is not yet consumed
if (ITheCompact(COMPACT_CONTRACT).hasConsumedAllocatorNonce(compact_.nonce, address(this))) {
revert NonceAlreadyConsumed(compact_.nonce);
}
uint256 balance = ERC6909(COMPACT_CONTRACT).balanceOf(compact_.sponsor, compact_.id);
// Check balance is enough
if (balance < compact_.amount) {
revert InsufficientBalance(compact_.sponsor, compact_.id, balance, compact_.amount);
}
return tokenHash;
}
/// @dev copied from IdLib.sol
function _resetPeriodToSeconds(ResetPeriod resetPeriod_) internal pure returns (uint256 duration) {
assembly ("memory-safe") {
// Bitpacked durations in 24-bit segments:
// 278d00 094890 015180 000f3c 000258 00003c 00000f 000001
// 30 days 7 days 1 day 1 hour 10 min 1 min 15 sec 1 sec
let bitpacked := 0x278d00094890015180000f3c00025800003c00000f000001
// Shift right by period * 24 bits & mask the least significant 24 bits.
duration := and(shr(mul(resetPeriod_, 24), bitpacked), 0xffffff)
}
return duration;
}
}