forked from Synthetixio/synthetix
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathEtherWrapper.sol
258 lines (202 loc) · 8.85 KB
/
EtherWrapper.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
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
pragma solidity ^0.5.16;
// Inheritance
import "./Owned.sol";
import "./interfaces/IAddressResolver.sol";
import "./interfaces/IEtherWrapper.sol";
import "./interfaces/ISynth.sol";
import "./interfaces/IERC20.sol";
import "./interfaces/IWETH.sol";
// Internal references
import "./Pausable.sol";
import "./interfaces/IIssuer.sol";
import "./interfaces/IExchangeRates.sol";
import "./interfaces/IFeePool.sol";
import "./MixinResolver.sol";
import "./MixinSystemSettings.sol";
// Libraries
import "openzeppelin-solidity-2.3.0/contracts/math/SafeMath.sol";
import "./SafeDecimalMath.sol";
// https://docs.synthetix.io/contracts/source/contracts/etherwrapper
contract EtherWrapper is Owned, Pausable, MixinResolver, MixinSystemSettings, IEtherWrapper {
using SafeMath for uint;
using SafeDecimalMath for uint;
/* ========== CONSTANTS ============== */
/* ========== ENCODED NAMES ========== */
bytes32 internal constant sUSD = "sUSD";
bytes32 internal constant sETH = "sETH";
bytes32 internal constant ETH = "ETH";
bytes32 internal constant SNX = "SNX";
/* ========== ADDRESS RESOLVER CONFIGURATION ========== */
bytes32 private constant CONTRACT_SYNTHSETH = "SynthsETH";
bytes32 private constant CONTRACT_SYNTHSUSD = "SynthsUSD";
bytes32 private constant CONTRACT_ISSUER = "Issuer";
bytes32 private constant CONTRACT_EXRATES = "ExchangeRates";
bytes32 private constant CONTRACT_FEEPOOL = "FeePool";
// ========== STATE VARIABLES ==========
IWETH internal _weth;
uint public sETHIssued = 0;
uint public sUSDIssued = 0;
uint public feesEscrowed = 0;
constructor(
address _owner,
address _resolver,
address payable _WETH
) public Owned(_owner) Pausable() MixinSystemSettings(_resolver) {
_weth = IWETH(_WETH);
}
/* ========== VIEWS ========== */
function resolverAddressesRequired() public view returns (bytes32[] memory addresses) {
bytes32[] memory existingAddresses = MixinSystemSettings.resolverAddressesRequired();
bytes32[] memory newAddresses = new bytes32[](5);
newAddresses[0] = CONTRACT_SYNTHSETH;
newAddresses[1] = CONTRACT_SYNTHSUSD;
newAddresses[2] = CONTRACT_EXRATES;
newAddresses[3] = CONTRACT_ISSUER;
newAddresses[4] = CONTRACT_FEEPOOL;
addresses = combineArrays(existingAddresses, newAddresses);
return addresses;
}
/* ========== INTERNAL VIEWS ========== */
function synthsUSD() internal view returns (ISynth) {
return ISynth(requireAndGetAddress(CONTRACT_SYNTHSUSD));
}
function synthsETH() internal view returns (ISynth) {
return ISynth(requireAndGetAddress(CONTRACT_SYNTHSETH));
}
function feePool() internal view returns (IFeePool) {
return IFeePool(requireAndGetAddress(CONTRACT_FEEPOOL));
}
function exchangeRates() internal view returns (IExchangeRates) {
return IExchangeRates(requireAndGetAddress(CONTRACT_EXRATES));
}
function issuer() internal view returns (IIssuer) {
return IIssuer(requireAndGetAddress(CONTRACT_ISSUER));
}
/* ========== PUBLIC FUNCTIONS ========== */
// ========== VIEWS ==========
function capacity() public view returns (uint _capacity) {
// capacity = max(maxETH - balance, 0)
uint balance = getReserves();
if (balance >= maxETH()) {
return 0;
}
return maxETH().sub(balance);
}
function getReserves() public view returns (uint) {
return _weth.balanceOf(address(this));
}
function totalIssuedSynths() public view returns (uint) {
// This contract issues two different synths:
// 1. sETH
// 2. sUSD
//
// The sETH is always backed 1:1 with WETH.
// The sUSD fees are backed by sETH that is withheld during minting and burning.
return exchangeRates().effectiveValue(sETH, sETHIssued, sUSD).add(sUSDIssued);
}
function calculateMintFee(uint amount) public view returns (uint) {
return amount.multiplyDecimalRound(mintFeeRate());
}
function calculateBurnFee(uint amount) public view returns (uint) {
return amount.multiplyDecimalRound(burnFeeRate());
}
function maxETH() public view returns (uint256) {
return getEtherWrapperMaxETH();
}
function mintFeeRate() public view returns (uint256) {
return getEtherWrapperMintFeeRate();
}
function burnFeeRate() public view returns (uint256) {
return getEtherWrapperBurnFeeRate();
}
function weth() public view returns (IWETH) {
return _weth;
}
/* ========== MUTATIVE FUNCTIONS ========== */
// Transfers `amountIn` WETH to mint `amountIn - fees` sETH.
// `amountIn` is inclusive of fees, calculable via `calculateMintFee`.
function mint(uint amountIn) external notPaused {
require(amountIn <= _weth.allowance(msg.sender, address(this)), "Allowance not high enough");
require(amountIn <= _weth.balanceOf(msg.sender), "Balance is too low");
uint currentCapacity = capacity();
require(currentCapacity > 0, "Contract has no spare capacity to mint");
if (amountIn < currentCapacity) {
_mint(amountIn);
} else {
_mint(currentCapacity);
}
}
// Burns `amountIn` sETH for `amountIn - fees` WETH.
// `amountIn` is inclusive of fees, calculable via `calculateBurnFee`.
function burn(uint amountIn) external notPaused {
uint reserves = getReserves();
require(reserves > 0, "Contract cannot burn sETH for WETH, WETH balance is zero");
// principal = [amountIn / (1 + burnFeeRate)]
uint principal = amountIn.divideDecimalRound(SafeDecimalMath.unit().add(burnFeeRate()));
if (principal < reserves) {
_burn(principal, amountIn);
} else {
_burn(reserves, reserves.add(calculateBurnFee(reserves)));
}
}
function distributeFees() external {
// Normalize fee to sUSD
require(!exchangeRates().rateIsInvalid(sETH), "Currency rate is invalid");
uint amountSUSD = exchangeRates().effectiveValue(sETH, feesEscrowed, sUSD);
// Burn sETH.
synthsETH().burn(address(this), feesEscrowed);
// Pay down as much sETH debt as we burn. Any other debt is taken on by the stakers.
sETHIssued = sETHIssued < feesEscrowed ? 0 : sETHIssued.sub(feesEscrowed);
// Issue sUSD to the fee pool
issuer().synths(sUSD).issue(feePool().FEE_ADDRESS(), amountSUSD);
sUSDIssued = sUSDIssued.add(amountSUSD);
// Tell the fee pool about this
feePool().recordFeePaid(amountSUSD);
feesEscrowed = 0;
}
// ========== RESTRICTED ==========
/**
* @notice Fallback function
*/
function() external payable {
revert("Fallback disabled, use mint()");
}
/* ========== INTERNAL FUNCTIONS ========== */
function _mint(uint amountIn) internal {
// Calculate minting fee.
uint feeAmountEth = calculateMintFee(amountIn);
uint principal = amountIn.sub(feeAmountEth);
// Transfer WETH from user.
_weth.transferFrom(msg.sender, address(this), amountIn);
// Mint `amountIn - fees` sETH to user.
synthsETH().issue(msg.sender, principal);
// Escrow fee.
synthsETH().issue(address(this), feeAmountEth);
feesEscrowed = feesEscrowed.add(feeAmountEth);
// Add sETH debt.
sETHIssued = sETHIssued.add(amountIn);
emit Minted(msg.sender, principal, feeAmountEth, amountIn);
}
function _burn(uint principal, uint amountIn) internal {
// for burn, amount is inclusive of the fee.
uint feeAmountEth = amountIn.sub(principal);
require(amountIn <= IERC20(address(synthsETH())).allowance(msg.sender, address(this)), "Allowance not high enough");
require(amountIn <= IERC20(address(synthsETH())).balanceOf(msg.sender), "Balance is too low");
// Burn `amountIn` sETH from user.
synthsETH().burn(msg.sender, amountIn);
// sETH debt is repaid by burning.
sETHIssued = sETHIssued < principal ? 0 : sETHIssued.sub(principal);
// We use burn/issue instead of burning the principal and transferring the fee.
// This saves an approval and is cheaper.
// Escrow fee.
synthsETH().issue(address(this), feeAmountEth);
// We don't update sETHIssued, as only the principal was subtracted earlier.
feesEscrowed = feesEscrowed.add(feeAmountEth);
// Transfer `amount - fees` WETH to user.
_weth.transfer(msg.sender, principal);
emit Burned(msg.sender, principal, feeAmountEth, amountIn);
}
/* ========== EVENTS ========== */
event Minted(address indexed account, uint principal, uint fee, uint amountIn);
event Burned(address indexed account, uint principal, uint fee, uint amountIn);
}