-
Notifications
You must be signed in to change notification settings - Fork 46
/
SwapsImplSovrynSwap.sol
301 lines (271 loc) · 12.6 KB
/
SwapsImplSovrynSwap.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
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
pragma solidity 0.5.17;
import "../../core/State.sol";
import "../../feeds/IPriceFeeds.sol";
import "../../openzeppelin/SafeERC20.sol";
import "./interfaces/ISovrynSwapNetwork.sol";
import "./interfaces/IContractRegistry.sol";
/**
* @dev WARNING: This contract is deprecated, all public functions are moved to the protocol modules.
* @title Swaps Implementation Sovryn contract.
*
* @notice This contract code comes from bZx. bZx is a protocol for tokenized
* margin trading and lending https://bzx.network similar to the dYdX protocol.
*
* This contract contains the implementation of swap process and rate
* calculations for Sovryn network.
* */
contract SwapsImplSovrynSwap is State {
using SafeERC20 for IERC20;
/// bytes32 contractName = hex"42616e636f724e6574776f726b"; /// "SovrynSwapNetwork"
constructor() internal {
// abstract
}
/**
* Get the hex name of a contract.
* @param source The name of the contract.
* */
function getContractHexName(string memory source) public pure returns (bytes32 result) {
assembly {
result := mload(add(source, 32))
}
}
/**
* Look up the Sovryn swap network contract registered at the given address.
* @param sovrynSwapRegistryAddress The address of the registry.
* */
function getSovrynSwapNetworkContract(
address sovrynSwapRegistryAddress
) public view returns (ISovrynSwapNetwork) {
/// State variable sovrynSwapContractRegistryAddress is part of
/// State.sol and set in ProtocolSettings.sol and this function
/// needs to work without delegate call as well -> therefore pass it.
IContractRegistry contractRegistry = IContractRegistry(sovrynSwapRegistryAddress);
return
ISovrynSwapNetwork(
contractRegistry.addressOf(getContractHexName("SovrynSwapNetwork"))
);
}
/**
* Swap the source token for the destination token on the oracle based AMM.
* On loan opening: minSourceTokenAmount = maxSourceTokenAmount and requiredDestTokenAmount = 0
* -> swap the minSourceTokenAmount
* On loan rollover: (swap interest) minSourceTokenAmount = 0, maxSourceTokenAmount = complete collateral and requiredDestTokenAmount > 0
* -> amount of required source tokens to swap is estimated (want to fill requiredDestTokenAmount, not more). maxSourceTokenAMount is not exceeded.
* On loan closure: minSourceTokenAmount <= maxSourceTokenAmount and requiredDestTokenAmount >= 0
* -> same as on rollover. minimum amount is not considered at all.
*
* @param sourceTokenAddress The address of the source tokens.
* @param destTokenAddress The address of the destination tokens.
* @param receiverAddress The address who will received the swap token results
* @param returnToSenderAddress The address to return unspent tokens to (when called by the protocol, it's always the protocol contract).
* @param minSourceTokenAmount The minimum amount of source tokens to swapped (only considered if requiredDestTokens == 0).
* @param maxSourceTokenAmount The maximum amount of source tokens to swapped.
* @param requiredDestTokenAmount The required amount of destination tokens.
* */
function internalSwap(
address sourceTokenAddress,
address destTokenAddress,
address receiverAddress,
address returnToSenderAddress,
uint256 minSourceTokenAmount,
uint256 maxSourceTokenAmount,
uint256 requiredDestTokenAmount
) public payable returns (uint256 destTokenAmountReceived, uint256 sourceTokenAmountUsed) {
require(sourceTokenAddress != destTokenAddress, "source == dest");
require(
supportedTokens[sourceTokenAddress] && supportedTokens[destTokenAddress],
"invalid tokens"
);
ISovrynSwapNetwork sovrynSwapNetwork = getSovrynSwapNetworkContract(
sovrynSwapContractRegistryAddress
);
IERC20[] memory path = getConversionPath(
sourceTokenAddress,
destTokenAddress,
sovrynSwapNetwork
);
uint256 minReturn = 1;
sourceTokenAmountUsed = minSourceTokenAmount;
/// If the required amount of destination tokens is passed, we need to
/// calculate the estimated amount of source tokens regardless of the
/// minimum source token amount (name is misleading).
if (requiredDestTokenAmount > 0) {
sourceTokenAmountUsed = estimateSourceTokenAmount(
sourceTokenAddress,
destTokenAddress,
requiredDestTokenAmount,
maxSourceTokenAmount
);
/// sovrynSwapNetwork.rateByPath does not return a rate, but instead the amount of destination tokens returned.
require(
sovrynSwapNetwork.rateByPath(path, sourceTokenAmountUsed) >=
requiredDestTokenAmount,
"insufficient source tokens provided."
);
minReturn = requiredDestTokenAmount;
}
require(sourceTokenAmountUsed > 0, "cannot swap 0 tokens");
allowTransfer(sourceTokenAmountUsed, sourceTokenAddress, address(sovrynSwapNetwork));
/// @dev Note: the kyber connector uses .call() to interact with kyber
/// to avoid bubbling up. here we allow bubbling up.
destTokenAmountReceived = sovrynSwapNetwork.convertByPath(
path,
sourceTokenAmountUsed,
minReturn,
receiverAddress,
address(0),
0
);
/// If the sender is not the protocol (calling with delegatecall),
/// return the remainder to the specified address.
/// @dev Note: for the case that the swap is used without the
/// protocol. Not sure if it should, though. needs to be discussed.
if (returnToSenderAddress != address(this)) {
if (sourceTokenAmountUsed < maxSourceTokenAmount) {
/// Send unused source token back.
IERC20(sourceTokenAddress).safeTransfer(
returnToSenderAddress,
maxSourceTokenAmount - sourceTokenAmountUsed
);
}
}
}
/**
* @notice Check whether the existing allowance suffices to transfer
* the needed amount of tokens.
* If not, allows the transfer of an arbitrary amount of tokens.
*
* @param tokenAmount The amount to transfer.
* @param tokenAddress The address of the token to transfer.
* @param sovrynSwapNetwork The address of the sovrynSwap network contract.
* */
function allowTransfer(
uint256 tokenAmount,
address tokenAddress,
address sovrynSwapNetwork
) internal {
uint256 tempAllowance = IERC20(tokenAddress).allowance(address(this), sovrynSwapNetwork);
if (tempAllowance < tokenAmount) {
IERC20(tokenAddress).safeApprove(sovrynSwapNetwork, uint256(-1));
}
}
/**
* @notice Calculate the number of source tokens to provide in order to
* obtain the required destination amount.
*
* @param sourceTokenAddress The address of the source token address.
* @param destTokenAddress The address of the destination token address.
* @param requiredDestTokenAmount The number of destination tokens needed.
* @param maxSourceTokenAmount The maximum number of source tokens to spend.
*
* @return The estimated amount of source tokens needed.
* Minimum: minSourceTokenAmount, maximum: maxSourceTokenAmount
* */
function estimateSourceTokenAmount(
address sourceTokenAddress,
address destTokenAddress,
uint256 requiredDestTokenAmount,
uint256 maxSourceTokenAmount
) internal view returns (uint256 estimatedSourceAmount) {
uint256 sourceToDestPrecision = IPriceFeeds(priceFeeds).queryPrecision(
sourceTokenAddress,
destTokenAddress
);
if (sourceToDestPrecision == 0) return maxSourceTokenAmount;
/// Compute the expected rate for the maxSourceTokenAmount -> if spending less, we can't get a worse rate.
uint256 expectedRate = internalExpectedRate(
sourceTokenAddress,
destTokenAddress,
maxSourceTokenAmount,
sovrynSwapContractRegistryAddress
);
/// Compute the source tokens needed to get the required amount with the worst case rate.
estimatedSourceAmount = requiredDestTokenAmount.mul(sourceToDestPrecision).div(
expectedRate
);
/// If the actual rate is exactly the same as the worst case rate, we get rounding issues. So, add a small buffer.
/// buffer = min(estimatedSourceAmount/1000 , sourceBuffer) with sourceBuffer = 10000
uint256 buffer = estimatedSourceAmount.div(1000);
if (buffer > sourceBuffer) buffer = sourceBuffer;
estimatedSourceAmount = estimatedSourceAmount.add(buffer);
/// Never spend more than the maximum.
if (estimatedSourceAmount == 0 || estimatedSourceAmount > maxSourceTokenAmount)
return maxSourceTokenAmount;
}
/**
* @notice Get the expected rate for 1 source token when exchanging the
* given amount of source tokens.
*
* @param sourceTokenAddress The address of the source token contract.
* @param destTokenAddress The address of the destination token contract.
* @param sourceTokenAmount The amount of source tokens to get the rate for.
* */
function internalExpectedRate(
address sourceTokenAddress,
address destTokenAddress,
uint256 sourceTokenAmount,
address sovrynSwapContractRegistryAddress
) public view returns (uint256) {
ISovrynSwapNetwork sovrynSwapNetwork = getSovrynSwapNetworkContract(
sovrynSwapContractRegistryAddress
);
IERC20[] memory path = getConversionPath(
sourceTokenAddress,
destTokenAddress,
sovrynSwapNetwork
);
/// Is returning the total amount of destination tokens.
uint256 expectedReturn = sovrynSwapNetwork.rateByPath(path, sourceTokenAmount);
/// Return the rate for 1 token with 18 decimals.
return expectedReturn.mul(10 ** 18).div(sourceTokenAmount);
}
/**
* @notice Get the expected return amount when exchanging the given
* amount of source tokens.
*
* @notice Right now, this function is being called directly by _swapsExpectedReturn from the protocol
* So, this function is not using getConversionPath function since it will try to read the defaultPath storage which is stored in the protocol's slot, and it will cause an issue for direct call.
* Instead, this function is accepting additional parameters called defaultPath which value can be declared by the caller (protocol in this case).
*
* @param sourceTokenAddress The address of the source token contract.
* @param destTokenAddress The address of the destination token contract.
* @param sourceTokenAmount The amount of source tokens to get the return for.
* @param sovrynSwapContractRegistry The sovryn swap contract reigstry address.
* @param defaultPath The default path for specific pairs.
* */
function internalExpectedReturn(
address sourceTokenAddress,
address destTokenAddress,
uint256 sourceTokenAmount,
address sovrynSwapContractRegistry,
IERC20[] memory defaultPath
) public view returns (uint256 expectedReturn) {
ISovrynSwapNetwork sovrynSwapNetwork = getSovrynSwapNetworkContract(
sovrynSwapContractRegistry
);
IERC20[] memory path = defaultPath.length >= 3
? defaultPath
: sovrynSwapNetwork.conversionPath(
IERC20(sourceTokenAddress),
IERC20(destTokenAddress)
);
/// Is returning the total amount of destination tokens.
expectedReturn = sovrynSwapNetwork.rateByPath(path, sourceTokenAmount);
}
function getConversionPath(
address sourceTokenAddress,
address destTokenAddress,
ISovrynSwapNetwork sovrynSwapNetwork
) private view returns (IERC20[] memory path) {
IERC20[] memory _defaultPathConversion = defaultPathConversion[sourceTokenAddress][
destTokenAddress
];
/// will use the defaultPath if it's set, otherwise query from the SovrynSwapNetwork.
path = _defaultPathConversion.length >= 3
? _defaultPathConversion
: sovrynSwapNetwork.conversionPath(
IERC20(sourceTokenAddress),
IERC20(destTokenAddress)
);
}
}