-
Notifications
You must be signed in to change notification settings - Fork 8
Expand file tree
/
Copy pathXdaiExchange.sol
More file actions
189 lines (159 loc) · 6.72 KB
/
XdaiExchange.sol
File metadata and controls
189 lines (159 loc) · 6.72 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
183
184
185
186
187
188
189
// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.22;
import {Math} from '@openzeppelin/contracts/utils/math/Math.sol';
import {SafeCast} from '@openzeppelin/contracts/utils/math/SafeCast.sol';
import {Initializable} from '@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol';
import {UUPSUpgradeable} from '@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol';
import {ReentrancyGuardUpgradeable} from '@openzeppelin/contracts-upgradeable/utils/ReentrancyGuardUpgradeable.sol';
import {Ownable2StepUpgradeable} from '@openzeppelin/contracts-upgradeable/access/Ownable2StepUpgradeable.sol';
import {IXdaiExchange} from '../interfaces/IXdaiExchange.sol';
import {IBalancerVault} from '../interfaces/IBalancerVault.sol';
import {IVaultsRegistry} from '../interfaces/IVaultsRegistry.sol';
import {IChainlinkV3Aggregator} from '../interfaces/IChainlinkV3Aggregator.sol';
import {Errors} from '../libraries/Errors.sol';
/**
* @title XdaiExchange
* @author StakeWise
* @notice Defines the xDAI to GNO exchange functionality
*/
contract XdaiExchange is
Initializable,
ReentrancyGuardUpgradeable,
Ownable2StepUpgradeable,
UUPSUpgradeable,
IXdaiExchange
{
error InvalidSlippage();
error PriceFeedError();
uint256 private constant _maxPercent = 10_000; // @dev 100.00 %
/// @custom:oz-upgrades-unsafe-allow state-variable-immutable
address private immutable _gnoToken;
/// @custom:oz-upgrades-unsafe-allow state-variable-immutable
IChainlinkV3Aggregator private immutable _daiPriceFeed;
/// @custom:oz-upgrades-unsafe-allow state-variable-immutable
IChainlinkV3Aggregator private immutable _gnoPriceFeed;
/// @custom:oz-upgrades-unsafe-allow state-variable-immutable
IBalancerVault private immutable _balancerVault;
/// @custom:oz-upgrades-unsafe-allow state-variable-immutable
IVaultsRegistry private immutable _vaultsRegistry;
/// @inheritdoc IXdaiExchange
bytes32 public override balancerPoolId;
/// @inheritdoc IXdaiExchange
uint128 public override maxSlippage;
/// @inheritdoc IXdaiExchange
uint128 public override stalePriceTimeDelta;
/**
* @dev Constructor
* @dev Since the immutable variable value is stored in the bytecode,
* its value would be shared among all proxies pointing to a given contract instead of each proxy’s storage.
* @param gnoToken The address of the GNO token
* @param balancerVault The address of the Balancer Vault
* @param vaultsRegistry The address of the Vaults Registry
* @param daiPriceFeed The address of the DAI <-> USD price feed
* @param gnoPriceFeed The address of the GNO <-> USD price feed
*/
/// @custom:oz-upgrades-unsafe-allow constructor
constructor(
address gnoToken,
address balancerVault,
address vaultsRegistry,
address daiPriceFeed,
address gnoPriceFeed
) {
_gnoToken = gnoToken;
_balancerVault = IBalancerVault(balancerVault);
_vaultsRegistry = IVaultsRegistry(vaultsRegistry);
_daiPriceFeed = IChainlinkV3Aggregator(daiPriceFeed);
_gnoPriceFeed = IChainlinkV3Aggregator(gnoPriceFeed);
_disableInitializers();
}
/// @inheritdoc IXdaiExchange
function initialize(
address initialOwner,
uint128 _maxSlippage,
uint128 _stalePriceTimeDelta,
bytes32 _balancerPoolId
) external override initializer {
__ReentrancyGuard_init();
__Ownable_init(initialOwner);
_setMaxSlippage(_maxSlippage);
_setStalePriceTimeDelta(_stalePriceTimeDelta);
_setBalancerPoolId(_balancerPoolId);
}
/// @inheritdoc IXdaiExchange
function setMaxSlippage(uint128 newMaxSlippage) external override onlyOwner {
_setMaxSlippage(newMaxSlippage);
}
/// @inheritdoc IXdaiExchange
function setStalePriceTimeDelta(uint128 newStalePriceTimeDelta) external override onlyOwner {
_setStalePriceTimeDelta(newStalePriceTimeDelta);
}
/// @inheritdoc IXdaiExchange
function setBalancerPoolId(bytes32 newBalancerPoolId) external override onlyOwner {
_setBalancerPoolId(newBalancerPoolId);
}
/// @inheritdoc IXdaiExchange
function swap() external payable nonReentrant returns (uint256 assets) {
if (msg.value == 0) revert Errors.InvalidAssets();
if (!_vaultsRegistry.vaults(msg.sender)) revert Errors.AccessDenied();
// SLOAD to memory
uint256 _maxSlippage = maxSlippage;
uint256 _stalePriceTimeDelta = stalePriceTimeDelta;
// fetch prices from oracles
(, int256 answer, , uint256 updatedAt, ) = _daiPriceFeed.latestRoundData();
if (answer <= 0 || block.timestamp - updatedAt > _stalePriceTimeDelta) revert PriceFeedError();
uint256 daiUsdPrice = uint256(answer);
(, answer, , updatedAt, ) = _gnoPriceFeed.latestRoundData();
if (answer <= 0 || block.timestamp - updatedAt > _stalePriceTimeDelta) revert PriceFeedError();
uint256 gnoUsdPrice = uint256(answer);
// calculate xDAI <-> GNO exchange rate from the price feeds
uint256 limit = Math.mulDiv(msg.value, daiUsdPrice, gnoUsdPrice);
// apply slippage
limit = Math.mulDiv(limit, _maxPercent - _maxSlippage, _maxPercent);
// define balancer swap
IBalancerVault.SingleSwap memory singleSwap = IBalancerVault.SingleSwap({
poolId: balancerPoolId,
kind: IBalancerVault.SwapKind.GIVEN_IN,
assetIn: address(0),
assetOut: _gnoToken,
amount: msg.value,
userData: ''
});
// define balancer funds
IBalancerVault.FundManagement memory funds = IBalancerVault.FundManagement({
sender: address(this),
fromInternalBalance: false,
recipient: payable(msg.sender),
toInternalBalance: false
});
// swap xDAI to GNO
assets = _balancerVault.swap{value: msg.value}(singleSwap, funds, limit, block.timestamp);
}
/**
* @dev Internal function to set the maximum slippage for the exchange
* @param newMaxSlippage The new maximum slippage
*/
function _setMaxSlippage(uint128 newMaxSlippage) private {
if (newMaxSlippage >= _maxPercent) revert InvalidSlippage();
maxSlippage = newMaxSlippage;
emit MaxSlippageUpdated(newMaxSlippage);
}
/**
* @dev Internal function to set the stale price time delta for the exchange
* @param newStalePriceTimeDelta The new stale price time delta
*/
function _setStalePriceTimeDelta(uint128 newStalePriceTimeDelta) private {
stalePriceTimeDelta = newStalePriceTimeDelta;
emit StalePriceTimeDeltaUpdated(newStalePriceTimeDelta);
}
/**
* @dev Internal function to set the Balancer pool ID for the exchange
* @param newBalancerPoolId The new Balancer pool ID
*/
function _setBalancerPoolId(bytes32 newBalancerPoolId) private {
balancerPoolId = newBalancerPoolId;
emit BalancerPoolIdUpdated(newBalancerPoolId);
}
/// @inheritdoc UUPSUpgradeable
function _authorizeUpgrade(address) internal override onlyOwner {}
}