-
Notifications
You must be signed in to change notification settings - Fork 14
Expand file tree
/
Copy pathEtherFiAssetAdapter.sol
More file actions
182 lines (155 loc) · 7.57 KB
/
Copy pathEtherFiAssetAdapter.sol
File metadata and controls
182 lines (155 loc) · 7.57 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
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
// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.23;
import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import {IERC721Receiver} from "@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol";
import {IAssetAdapter, IERC20, IEETHWithdrawal, IEETHWithdrawalNFT, IWETH} from "../Interfaces.sol";
/**
* @title Ether.fi eETH asset adapter
* @notice Adapter for redeeming eETH through Ether.fi's withdrawal queue into WETH.
* @dev eETH shares and expected ETH assets are treated as 1:1 for accounting.
* @author Origin Protocol Inc
*/
contract EtherFiAssetAdapter is Initializable, IAssetAdapter, IERC721Receiver {
/// @notice ARM contract authorized to request and claim redemptions.
address public immutable arm;
/// @notice eETH token submitted to Ether.fi withdrawals.
IERC20 public immutable eeth;
/// @notice WETH liquidity asset returned to the ARM.
IWETH public immutable weth;
/// @notice Ether.fi withdrawal queue used to open withdrawal requests.
IEETHWithdrawal public immutable etherfiWithdrawalQueue;
/// @notice Ether.fi withdrawal NFT contract used to claim finalized requests.
IEETHWithdrawalNFT public immutable etherfiWithdrawalNFT;
/// @notice eETH share amount represented by each Ether.fi withdrawal request id.
mapping(uint256 requestId => uint256 shares) public requestShares;
uint256[] internal pendingRequestIds;
uint256 internal nextPendingIndex;
/// @dev True only while the adapter is actively claiming Ether.fi withdrawal NFTs.
bool internal claimingEtherFi;
/// @notice Thrown when Ether.fi claim proceeds arrive without an adapter-initiated claim.
error UnauthorizedEtherFiClaim(); // 0x7b2b45a4
modifier onlyARM() {
require(msg.sender == arm, "Adapter: only ARM");
_;
}
modifier nonZeroShares(uint256 shares) {
require(shares > 0, "Adapter: zero shares");
_;
}
/// @param _arm ARM contract authorized to use the adapter.
/// @param _eeth eETH token to redeem.
/// @param _weth WETH token received after claims.
/// @param _etherfiWithdrawalQueue Ether.fi withdrawal queue contract.
/// @param _etherfiWithdrawalNFT Ether.fi withdrawal NFT contract.
constructor(
address _arm,
address _eeth,
address _weth,
address _etherfiWithdrawalQueue,
address _etherfiWithdrawalNFT
) {
arm = _arm;
eeth = IERC20(_eeth);
weth = IWETH(_weth);
etherfiWithdrawalQueue = IEETHWithdrawal(_etherfiWithdrawalQueue);
etherfiWithdrawalNFT = IEETHWithdrawalNFT(_etherfiWithdrawalNFT);
}
/// @notice Re-approves eETH for Ether.fi's withdrawal queue when called through a proxy.
function initialize() external initializer {
eeth.approve(address(etherfiWithdrawalQueue), type(uint256).max);
}
/// @notice Returns WETH as the liquidity asset produced by Ether.fi claims.
function asset() external view returns (address) {
return address(weth);
}
/// @notice Converts eETH shares to expected WETH assets at a 1:1 rate.
/// @param shares Amount of eETH shares.
/// @return assets Expected WETH assets.
function convertToAssets(uint256 shares) external pure returns (uint256 assets) {
return shares;
}
/// @notice Converts WETH assets to expected eETH shares at a 1:1 rate.
/// @param assets Amount of WETH assets.
/// @return shares Expected eETH shares.
function convertToShares(uint256 assets) external pure returns (uint256 shares) {
return assets;
}
/// @notice Pulls eETH from the ARM and opens an Ether.fi withdrawal request.
/// @param shares Amount of eETH to request for redemption.
/// @return sharesRequested Amount of eETH accepted into the withdrawal request.
/// @return assetsExpected Expected WETH assets from the request.
function requestRedeem(uint256 shares)
external
onlyARM
nonZeroShares(shares)
returns (uint256 sharesRequested, uint256 assetsExpected)
{
eeth.transferFrom(arm, address(this), shares);
uint256 requestId = etherfiWithdrawalQueue.requestWithdraw(address(this), shares);
requestShares[requestId] = shares;
pendingRequestIds.push(requestId);
sharesRequested = shares;
assetsExpected = shares;
}
/// @notice Claims queued Ether.fi withdrawal requests and sweeps WETH to the ARM.
/// @dev Claims pending requests in FIFO order, wraps the full ETH balance into WETH, and transfers
/// all adapter-held WETH to the ARM. `assetsReceived` may include previously donated ETH or WETH.
/// @param shares Exact amount of eETH represented by pending requests to claim.
/// @return sharesClaimed Amount of eETH represented by claimed requests.
/// @return assetsExpected Expected WETH amount from the claimed requests.
/// @return assetsReceived Total WETH amount swept to the ARM.
function redeem(uint256 shares)
external
onlyARM
nonZeroShares(shares)
returns (uint256 sharesClaimed, uint256 assetsExpected, uint256 assetsReceived)
{
uint256 length = pendingRequestIds.length;
uint256 cursor = nextPendingIndex;
uint256 claimCount;
while (cursor + claimCount < length && sharesClaimed < shares) {
uint256 requestId = pendingRequestIds[cursor + claimCount];
uint256 requestShareAmount = requestShares[requestId];
require(requestShareAmount > 0, "Adapter: invalid request");
require(sharesClaimed + requestShareAmount <= shares, "Adapter: invalid redeem amount");
sharesClaimed += requestShareAmount;
assetsExpected += requestShareAmount;
claimCount++;
}
require(sharesClaimed == shares, "Adapter: redeem exceeds claimable");
uint256[] memory requestIds = new uint256[](claimCount);
for (uint256 i = 0; i < claimCount; ++i) {
requestIds[i] = pendingRequestIds[cursor + i];
delete requestShares[requestIds[i]];
}
nextPendingIndex = cursor + claimCount;
claimingEtherFi = true;
etherfiWithdrawalNFT.batchClaimWithdraw(requestIds);
claimingEtherFi = false;
uint256 ethBalance = address(this).balance;
if (ethBalance > 0) weth.deposit{value: ethBalance}();
assetsReceived = weth.balanceOf(address(this));
IERC20(address(weth)).transfer(arm, assetsReceived);
}
/// @notice Returns the total number of Ether.fi request ids ever stored by the adapter.
function pendingRequestIdsLength() external view returns (uint256) {
return pendingRequestIds.length;
}
/// @notice Returns a stored Ether.fi request id by array index.
/// @param index Index in the pending request id array.
function pendingRequestId(uint256 index) external view returns (uint256) {
return pendingRequestIds[index];
}
/// @notice Accepts ETH only while this adapter is claiming Ether.fi withdrawals.
/// @dev ETH arriving outside an adapter-initiated claim (e.g. a permissionless Ether.fi claim) is
/// rejected. Ether.fi reverts the entire claim, including the NFT burn, when this transfer fails.
receive() external payable {
if (!claimingEtherFi) {
revert UnauthorizedEtherFiClaim();
}
}
/// @notice Accepts Ether.fi withdrawal NFTs minted to this adapter.
function onERC721Received(address, address, uint256, bytes calldata) external pure returns (bytes4) {
return IERC721Receiver.onERC721Received.selector;
}
}