generated from transmissions11/foundry-template
-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathTimelockedAsyncRedeem.sol
181 lines (141 loc) · 7.09 KB
/
TimelockedAsyncRedeem.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
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.15;
import {BaseERC7540} from "src/BaseERC7540.sol";
import {IERC7540Redeem} from "src/interfaces/IERC7540.sol";
import {SafeTransferLib} from "solmate/utils/SafeTransferLib.sol";
import {FixedPointMathLib} from "solmate/utils/FixedPointMathLib.sol";
import {ERC20} from "solmate/tokens/ERC20.sol";
// THIS VAULT IS AN UNOPTIMIZED, POTENTIALLY UNSECURE REFERENCE EXAMPLE AND IN NO WAY MEANT TO BE USED IN PRODUCTION
/**
* @notice ERC7540 Implementing Delayed Async Redeem
*
* This Vault has the following properties:
* - yield for the underlying asset is assumed to be transferred directly into the vault by some arbitrary mechanism
* - async redemptions are subject to a timelock
* - new redemptions restart the timelock even if the prior redemption is claimable.
* This can be resolved by using a more sophisticated algorithm for storing multiple requests.
* - the redemption exchange rate is locked in immediately upon request.
*/
abstract contract BaseTimelockedAsyncRedeem is BaseERC7540, IERC7540Redeem {
using FixedPointMathLib for uint256;
uint32 public timelock;
uint256 internal _totalPendingRedeemAssets;
mapping(address => RedemptionRequest) internal _pendingRedemption;
struct RedemptionRequest {
uint256 assets;
uint256 shares;
uint32 claimableTimestamp;
}
constructor(uint32 timelock_) {
timelock = timelock_;
}
function setTimelock(uint32 timelock_) public onlyOwner {
timelock = timelock_;
}
function totalAssets() public view virtual override returns (uint256) {
return ERC20(asset).balanceOf(address(this)) - _totalPendingRedeemAssets;
}
/*//////////////////////////////////////////////////////////////
ERC7540 LOGIC
//////////////////////////////////////////////////////////////*/
function requestRedeem(uint256 shares, address controller, address owner) external returns (uint256 requestId) {
require(owner == msg.sender || isOperator[owner][msg.sender], "ERC7540Vault/invalid-owner");
require(ERC20(address(this)).balanceOf(owner) >= shares, "ERC7540Vault/insufficient-balance");
require(shares != 0, "ZERO_SHARES");
uint256 assets = convertToAssets(shares);
SafeTransferLib.safeTransferFrom(this, owner, address(this), shares);
_pendingRedemption[controller] =
RedemptionRequest({assets: assets, shares: shares, claimableTimestamp: uint32(block.timestamp) + timelock});
_totalPendingRedeemAssets += assets;
emit RedeemRequest(controller, owner, REQUEST_ID, msg.sender, shares);
return REQUEST_ID;
}
function pendingRedeemRequest(uint256, address controller) public view returns (uint256 pendingShares) {
RedemptionRequest memory request = _pendingRedemption[controller];
if (request.claimableTimestamp > block.timestamp) {
return request.shares;
}
return 0;
}
function claimableRedeemRequest(uint256, address controller) public view returns (uint256 claimableShares) {
RedemptionRequest memory request = _pendingRedemption[controller];
if (request.claimableTimestamp <= block.timestamp && request.shares > 0) {
return request.shares;
}
return 0;
}
/*//////////////////////////////////////////////////////////////
ERC4626 OVERRIDDEN LOGIC
//////////////////////////////////////////////////////////////*/
function withdraw(uint256 assets, address receiver, address controller)
public
virtual
override
returns (uint256 shares)
{
require(controller == msg.sender || isOperator[controller][msg.sender], "ERC7540Vault/invalid-caller");
require(assets != 0, "Must claim nonzero amount");
RedemptionRequest storage request = _pendingRedemption[controller];
require(request.claimableTimestamp <= block.timestamp, "ERC7540Vault/not-claimable-yet");
// Claiming partially introduces precision loss. The user therefore receives a rounded down amount,
// while the claimable balance is reduced by a rounded up amount.
shares = assets.mulDivDown(request.shares, request.assets);
uint256 sharesUp = assets.mulDivUp(request.shares, request.assets);
request.assets -= assets;
request.shares = request.shares > sharesUp ? request.shares - sharesUp : 0;
_totalPendingRedeemAssets -= assets;
SafeTransferLib.safeTransfer(asset, receiver, assets);
emit Withdraw(msg.sender, receiver, controller, assets, shares);
}
function redeem(uint256 shares, address receiver, address controller)
public
virtual
override
returns (uint256 assets)
{
require(controller == msg.sender || isOperator[controller][msg.sender], "ERC7540Vault/invalid-caller");
require(shares != 0, "Must claim nonzero amount");
RedemptionRequest storage request = _pendingRedemption[controller];
require(request.claimableTimestamp <= block.timestamp, "ERC7540Vault/not-claimable-yet");
assets = shares.mulDivDown(request.assets, request.shares);
uint256 assetsUp = shares.mulDivUp(request.assets, request.shares);
request.assets = request.assets > assetsUp ? request.assets - assetsUp : 0;
request.shares -= shares;
_totalPendingRedeemAssets -= assets;
SafeTransferLib.safeTransfer(asset, receiver, assets);
emit Withdraw(msg.sender, receiver, controller, assets, shares);
}
function maxWithdraw(address controller) public view virtual override returns (uint256) {
RedemptionRequest memory request = _pendingRedemption[controller];
if (request.claimableTimestamp <= block.timestamp) {
return request.assets;
}
return 0;
}
function maxRedeem(address controller) public view virtual override returns (uint256) {
RedemptionRequest memory request = _pendingRedemption[controller];
if (request.claimableTimestamp <= block.timestamp) {
return request.shares;
}
return 0;
}
// Preview functions always revert for async flows
function previewWithdraw(uint256) public pure virtual override returns (uint256) {
revert("ERC7540Vault/async-flow");
}
function previewRedeem(uint256) public pure virtual override returns (uint256) {
revert("ERC7540Vault/async-flow");
}
/*//////////////////////////////////////////////////////////////
ERC165 LOGIC
//////////////////////////////////////////////////////////////*/
function supportsInterface(bytes4 interfaceId) public pure virtual override returns (bool) {
return interfaceId == type(IERC7540Redeem).interfaceId || super.supportsInterface(interfaceId);
}
}
contract TimelockedAsyncRedeem is BaseTimelockedAsyncRedeem {
constructor(uint32 timelock_, ERC20 _asset, string memory _name, string memory _symbol)
BaseTimelockedAsyncRedeem(timelock_)
BaseERC7540(_asset, _name, _symbol)
{}
}