-
Notifications
You must be signed in to change notification settings - Fork 22
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
KyberSwap Classic Adapter #34
base: main
Are you sure you want to change the base?
Changes from all commits
9d910ba
d2422bd
85ed565
bd7a544
b2f2761
e37f80f
ba38332
71f5c9d
f1449c0
ff077ab
24d7464
a1d562a
325a4c2
bdfbff4
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,378 @@ | ||
// SPDX-License-Identifier: AGPL-3.0-or-later | ||
pragma solidity ^0.8.13; | ||
|
||
import {ISwapAdapter} from "src/interfaces/ISwapAdapter.sol"; | ||
import { | ||
IERC20, | ||
SafeERC20 | ||
} from "openzeppelin-contracts/contracts/token/ERC20/utils/SafeERC20.sol"; | ||
|
||
/// @title Kyberswap Classic Adapter | ||
contract KyberSwapClassicAdapter is ISwapAdapter { | ||
/// @dev Using trade params inside a struct to avoid stack too deep errors | ||
struct TradeParams { | ||
uint112 r0; | ||
uint112 r1; | ||
uint112 vr0; | ||
uint112 vr1; | ||
uint256 feeInPrecision; | ||
} | ||
|
||
using SafeERC20 for IERC20; | ||
|
||
// Kyberswap handles arbirary amounts, but we limit the amount to 10x just | ||
// in case | ||
uint256 constant RESERVE_LIMIT_FACTOR = 10; | ||
Comment on lines
+23
to
+25
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. "just in case" of what? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Idk why this is being used below to divide the reserves |
||
|
||
uint256 constant PRECISION = (10 ** 18); | ||
|
||
IFactory immutable factory; | ||
IRouter immutable router; | ||
|
||
constructor(address _router) { | ||
router = IRouter(_router); | ||
factory = IFactory(router.factory()); | ||
} | ||
|
||
/// @inheritdoc ISwapAdapter | ||
function price( | ||
bytes32 poolId, | ||
address sellToken, | ||
address buyToken, | ||
uint256[] memory specifiedAmounts | ||
) external view override returns (Fraction[] memory prices) { | ||
prices = new Fraction[](specifiedAmounts.length); | ||
IPair pair = IPair(address(bytes20(poolId))); | ||
uint112 r0; | ||
uint112 vr0; | ||
uint112 r1; | ||
uint112 vr1; | ||
uint256 feeInPrecision; | ||
if (sellToken < buyToken) { | ||
(r0, r1, vr0, vr1, feeInPrecision) = pair.getTradeInfo(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. why not use TradeParams here ? |
||
} else { | ||
(r1, r0, vr1, vr0, feeInPrecision) = pair.getTradeInfo(); | ||
} | ||
|
||
for (uint256 i = 0; i < specifiedAmounts.length; i++) { | ||
prices[i] = getPriceAt( | ||
specifiedAmounts[i], r0, r1, vr0, vr1, feeInPrecision | ||
); | ||
} | ||
} | ||
|
||
function swap( | ||
bytes32 poolId, | ||
address sellToken, | ||
address buyToken, | ||
OrderSide side, | ||
uint256 specifiedAmount | ||
Comment on lines
+64
to
+69
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. inheritdoc? |
||
) external returns (Trade memory trade) { | ||
if (specifiedAmount == 0) { | ||
return trade; | ||
} | ||
|
||
IPair pair = IPair(address(bytes20(poolId))); | ||
TradeParams memory tradeParams; | ||
bool zero2one = sellToken < buyToken; | ||
if (zero2one) { | ||
( | ||
tradeParams.r0, | ||
tradeParams.r1, | ||
tradeParams.vr0, | ||
tradeParams.vr1, | ||
tradeParams.feeInPrecision | ||
Comment on lines
+79
to
+84
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. can you not abbreviate these attributes? will save people having to inspect the interface and the names are not so big |
||
) = pair.getTradeInfo(); | ||
} else { | ||
( | ||
tradeParams.r1, | ||
tradeParams.r0, | ||
tradeParams.vr1, | ||
tradeParams.vr0, | ||
tradeParams.feeInPrecision | ||
) = pair.getTradeInfo(); | ||
} | ||
uint256 gasBefore = gasleft(); | ||
|
||
if (side == OrderSide.Sell) { | ||
trade.calculatedAmount = | ||
sell(pair, sellToken, buyToken, zero2one, specifiedAmount); | ||
} else { | ||
trade.calculatedAmount = | ||
buy(pair, sellToken, buyToken, zero2one, specifiedAmount); | ||
} | ||
|
||
trade.gasUsed = gasBefore - gasleft(); | ||
|
||
if (zero2one) { | ||
(,, tradeParams.vr0, tradeParams.vr1,) = pair.getTradeInfo(); | ||
} else { | ||
(,, tradeParams.vr1, tradeParams.vr0,) = pair.getTradeInfo(); | ||
} | ||
|
||
/// @dev a fractional value (1/100000) of the trading liquidity, to | ||
/// impact as little as possible on the slippage | ||
trade.price = Fraction( | ||
tradeParams.vr1 * (PRECISION - tradeParams.feeInPrecision) | ||
/ PRECISION, | ||
tradeParams.vr0 | ||
); | ||
} | ||
|
||
/// @inheritdoc ISwapAdapter | ||
function getLimits(bytes32 poolId, address sellToken, address buyToken) | ||
external | ||
view | ||
override | ||
returns (uint256[] memory limits) | ||
{ | ||
IPair pair = IPair(address(bytes20(poolId))); | ||
limits = new uint256[](2); | ||
(uint256 r0, uint256 r1) = pair.getReserves(); | ||
if (sellToken < buyToken) { | ||
limits[0] = r0 / RESERVE_LIMIT_FACTOR; | ||
limits[1] = r1 / RESERVE_LIMIT_FACTOR; | ||
} else { | ||
limits[0] = r1 / RESERVE_LIMIT_FACTOR; | ||
limits[1] = r0 / RESERVE_LIMIT_FACTOR; | ||
} | ||
} | ||
|
||
/// @inheritdoc ISwapAdapter | ||
function getCapabilities(bytes32, address, address) | ||
external | ||
pure | ||
override | ||
returns (Capability[] memory capabilities) | ||
{ | ||
capabilities = new Capability[](3); | ||
capabilities[0] = Capability.SellOrder; | ||
capabilities[1] = Capability.BuyOrder; | ||
capabilities[2] = Capability.PriceFunction; | ||
} | ||
|
||
/// @inheritdoc ISwapAdapter | ||
function getTokens(bytes32 poolId) | ||
external | ||
view | ||
override | ||
returns (address[] memory tokens) | ||
{ | ||
tokens = new address[](2); | ||
IPair pair = IPair(address(bytes20(poolId))); | ||
tokens[0] = pair.token0(); | ||
tokens[1] = pair.token1(); | ||
} | ||
|
||
/// @inheritdoc ISwapAdapter | ||
function getPoolIds(uint256 offset, uint256 limit) | ||
external | ||
view | ||
override | ||
returns (bytes32[] memory ids) | ||
{ | ||
uint256 endIdx = offset + limit; | ||
if (endIdx > factory.allPoolsLength()) { | ||
endIdx = factory.allPoolsLength(); | ||
} | ||
ids = new bytes32[](endIdx - offset); | ||
for (uint256 i = 0; i < ids.length; i++) { | ||
ids[i] = bytes20(factory.allPools(offset + i)); | ||
} | ||
} | ||
|
||
/// @notice Executes a sell order on a given pool. | ||
/// @param pair The pair to trade on. | ||
/// @param sellToken The token being sold. | ||
/// @param buyToken The token being bought. | ||
/// @param amount The amount to be traded. | ||
/// @return calculatedAmount The amount of tokens received. | ||
function sell( | ||
Comment on lines
+184
to
+190
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. doenst inheritdoc work here? |
||
IPair pair, | ||
address sellToken, | ||
address buyToken, | ||
bool zero2one, | ||
uint256 amount | ||
) internal returns (uint256 calculatedAmount) { | ||
address[] memory poolsPath = new address[](1); | ||
IERC20[] memory tokensPath = new IERC20[](2); | ||
IERC20 sellTokenContract = IERC20(sellToken); | ||
|
||
poolsPath[0] = address(pair); | ||
tokensPath[0] = sellTokenContract; | ||
tokensPath[1] = IERC20(buyToken); | ||
|
||
uint256[] memory amountsOut = | ||
router.getAmountsOut(amount, poolsPath, tokensPath); | ||
|
||
sellTokenContract.safeTransferFrom(msg.sender, address(pair), amount); | ||
if (zero2one) { | ||
pair.swap(0, amountsOut[1], msg.sender, ""); | ||
} else { | ||
pair.swap(amountsOut[1], 0, msg.sender, ""); | ||
} | ||
return amountsOut[1]; | ||
} | ||
|
||
/// @notice Execute a buy order on a given pool. | ||
/// @param pair The pair to trade on. | ||
/// @param sellToken The token being sold. | ||
/// @param buyToken The token being bought. | ||
/// @param zero2one Whether the sell token is token0 or token1. | ||
/// @return calculatedAmount The amount of tokens sold. | ||
function buy( | ||
IPair pair, | ||
address sellToken, | ||
address buyToken, | ||
bool zero2one, | ||
uint256 amountOut | ||
) internal returns (uint256 calculatedAmount) { | ||
address[] memory poolsPath = new address[](1); | ||
IERC20[] memory tokensPath = new IERC20[](2); | ||
address pairAddress = address(pair); | ||
IERC20 sellTokenContract = IERC20(sellToken); | ||
|
||
poolsPath[0] = pairAddress; | ||
tokensPath[0] = sellTokenContract; | ||
tokensPath[1] = IERC20(buyToken); | ||
|
||
uint256[] memory amountsIn = | ||
router.getAmountsIn(amountOut, poolsPath, tokensPath); | ||
|
||
if (amountsIn[0] == 0) { | ||
return 0; | ||
} | ||
|
||
sellTokenContract.safeTransferFrom( | ||
msg.sender, pairAddress, amountsIn[0] | ||
); | ||
|
||
if (zero2one) { | ||
pair.swap(0, amountOut, msg.sender, ""); | ||
} else { | ||
pair.swap(amountOut, 0, msg.sender, ""); | ||
} | ||
|
||
return amountsIn[0]; | ||
} | ||
|
||
/// @notice Given an input amount of an asset and pair reserves, returns the | ||
/// maximum output amount of the other asset | ||
/// @param amountIn The amount of the token being sold. | ||
/// @param vReserveIn The virtual reserve of the token being sold. | ||
/// @param vReserveOut The virtual reserve of the token being bought. | ||
/// @param feeInPrecision Fee in PRECISION points | ||
/// @return amountOut The amount of tokens received. | ||
function getAmountOut( | ||
uint256 amountIn, | ||
uint256 vReserveIn, | ||
uint256 vReserveOut, | ||
uint256 feeInPrecision | ||
) internal pure returns (uint256 amountOut) { | ||
Comment on lines
+259
to
+271
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. what is this used for? |
||
if (amountIn == 0) { | ||
return 0; | ||
} | ||
if (vReserveIn == 0 || vReserveOut == 0) { | ||
revert Unavailable("At least one reserve is zero!"); | ||
} | ||
uint256 amountInWithFee = | ||
amountIn * (PRECISION - feeInPrecision) / PRECISION; | ||
uint256 numerator = amountInWithFee * vReserveOut; | ||
uint256 denominator = vReserveIn + amountInWithFee; | ||
amountOut = numerator / denominator; | ||
} | ||
|
||
/// @notice Calculates pool prices for specified amounts | ||
/// @param amountIn The amount of the token being sold. | ||
/// @param reserveIn The reserve of the token being sold. | ||
/// @param reserveOut The reserve of the token being bought. | ||
/// @param vReserveIn The (amplified) reserve of the token being sold. | ||
/// @param vReserveOut The (amplified) reserve of the token being bought. | ||
/// @param feeInPrecision Fee in Precision points | ||
/// @return The price as a fraction corresponding to the provided amount. | ||
function getPriceAt( | ||
uint256 amountIn, | ||
uint256 reserveIn, | ||
uint256 reserveOut, | ||
uint256 vReserveIn, | ||
uint256 vReserveOut, | ||
uint256 feeInPrecision | ||
) internal pure returns (Fraction memory) { | ||
if (reserveIn == 0 || reserveOut == 0) { | ||
revert Unavailable("At least one reserve is zero!"); | ||
} | ||
uint256 amountInWithFee = | ||
amountIn * (PRECISION - feeInPrecision) / PRECISION; | ||
uint256 numerator = amountInWithFee * vReserveOut; | ||
uint256 denominator = vReserveIn + amountInWithFee; | ||
uint256 amountOut = numerator / denominator; | ||
|
||
// get new amplified reserves | ||
uint256 newVReserveIn = vReserveIn + amountIn; | ||
uint256 newVReserveOut = vReserveOut - amountOut; | ||
|
||
return Fraction( | ||
newVReserveOut * (PRECISION - feeInPrecision) / PRECISION, | ||
newVReserveIn | ||
); | ||
} | ||
} | ||
|
||
interface IRouter { | ||
function factory() external view returns (address); | ||
function getAmountsIn( | ||
uint256 amountOut, | ||
address[] calldata poolsPath, | ||
IERC20[] calldata path | ||
) external view returns (uint256[] memory amounts); | ||
function getAmountsOut( | ||
uint256 amounntIn, | ||
address[] calldata poolsPath, | ||
IERC20[] calldata path | ||
) external view returns (uint256[] memory amounts); | ||
} | ||
|
||
interface IPair { | ||
function token0() external view returns (address); | ||
function token1() external view returns (address); | ||
function getReserves() | ||
external | ||
view | ||
returns (uint112 reserve0, uint112 reserve1); | ||
|
||
function swap( | ||
uint256 amount0Out, | ||
uint256 amount1Out, | ||
address to, | ||
bytes calldata data | ||
) external; | ||
|
||
function ampBps() external view returns (uint32); | ||
|
||
function getTradeInfo() | ||
external | ||
view | ||
returns ( | ||
uint112 _reserve0, | ||
uint112 _reserve1, | ||
uint112 _vReserve0, | ||
uint112 _vReserve1, | ||
uint256 feeInPrecision | ||
); | ||
|
||
function getVolumeTrendData() | ||
external | ||
view | ||
returns ( | ||
uint128 _shortEMA, | ||
uint128 _longEMA, | ||
uint128 _currentBlockVolume, | ||
uint128 _lastTradeBlock | ||
); | ||
} | ||
|
||
interface IFactory { | ||
function allPools(uint256) external view returns (address); | ||
|
||
function allPoolsLength() external view returns (uint256); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
maybe TradePairAttributes is a better name?