415 lines
15 KiB
Solidity
415 lines
15 KiB
Solidity
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
pragma solidity ^0.8.13;
|
|
|
|
import "./AdapterTest.sol";
|
|
import "src/curve/CurveAdapter.sol";
|
|
|
|
contract CurveAdapterTest is Test, ISwapAdapterTypes, AdapterTest {
|
|
using FractionMath for Fraction;
|
|
|
|
CurveAdapter adapter;
|
|
|
|
// tokens
|
|
address constant USDT = 0xdAC17F958D2ee523a2206206994597C13D831ec7;
|
|
address constant USDC = 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48;
|
|
address constant WETH = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2;
|
|
address constant ETH = address(0);
|
|
address constant WBETH = 0xa2E3356610840701BDf5611a53974510Ae27E2e1;
|
|
address constant MIM = 0x99D8a9C45b2ecA8864373A26D1459e3Dff1e17F3;
|
|
address constant THREE_CRV_TOKEN =
|
|
0x6c3F90f043a72FA612cbac8115EE7e52BDe6E490;
|
|
address constant DAI = 0x6B175474E89094C44Da98b954EedeAC495271d0F;
|
|
address constant stETH = 0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84;
|
|
|
|
// pools
|
|
address constant STABLE_POOL = 0xbEbc44782C7dB0a1A60Cb6fe97d0b483032FF1C7;
|
|
address constant CRYPTO_POOL = 0x80466c64868E1ab14a1Ddf27A676C3fcBE638Fe5;
|
|
address constant STABLE_META_POOL =
|
|
0x5a6A4D54456819380173272A5E8E9B9904BdF41B;
|
|
address constant ETH_POOL = 0xBfAb6FA95E0091ed66058ad493189D2cB29385E6;
|
|
address constant STETH_POOL = 0xDC24316b9AE028F1497c275EB9192a3Ea0f67022;
|
|
address[] ADDITIONAL_POOLS_FOR_TESTING;
|
|
|
|
uint256 constant TEST_ITERATIONS = 100;
|
|
IwstETH constant wstETH =
|
|
IwstETH(0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0);
|
|
|
|
function setUp() public {
|
|
uint256 forkBlock = 20234346;
|
|
vm.createSelectFork(vm.rpcUrl("mainnet"), forkBlock);
|
|
adapter = new CurveAdapter();
|
|
|
|
// Additional pools that include custom Int128 pools
|
|
ADDITIONAL_POOLS_FOR_TESTING = [
|
|
0xEcd5e75AFb02eFa118AF914515D6521aaBd189F1,
|
|
0xEd279fDD11cA84bEef15AF5D39BB4d4bEE23F0cA,
|
|
0x43b4FdFD4Ff969587185cDB6f0BD875c5Fc83f8c,
|
|
0x9EfE1A1Cbd6Ca51Ee8319AFc4573d253C3B732af,
|
|
0x4807862AA8b2bF68830e4C8dc86D0e9A998e085a,
|
|
0xd632f22692FaC7611d2AA1C0D552930D43CAEd3B,
|
|
0xA5407eAE9Ba41422680e2e00537571bcC53efBfD,
|
|
0x5a6A4D54456819380173272A5E8E9B9904BdF41B,
|
|
0x3211C6cBeF1429da3D0d58494938299C92Ad5860,
|
|
0xDB6925eA42897ca786a045B252D95aA7370f44b4,
|
|
0xf861483fa7E511fbc37487D91B6FAa803aF5d37c,
|
|
0x1e098B32944292969fB58c85bDC85545DA397117,
|
|
0xe0e970a99bc4F53804D8145beBBc7eBc9422Ba7F,
|
|
0x6bfE880Ed1d639bF80167b93cc9c56a39C1Ba2dC,
|
|
0xBDFAe7D2cF2E69E27b75a5287ECD3808F62B5a76,
|
|
0xfB8814D005C5f32874391e888da6eB2fE7a27902,
|
|
0x0f3159811670c117c372428D4E69AC32325e4D0F,
|
|
0x4eBdF703948ddCEA3B11f675B4D1Fba9d2414A14,
|
|
0x0E9B5B092caD6F1c5E6bc7f89Ffe1abb5c95F1C2,
|
|
0x21410232B484136404911780bC32756D5d1a9Fa9,
|
|
0x322135Dd9cBAE8Afa84727d9aE1434b5B3EBA44B,
|
|
0xC26b89A667578ec7b3f11b2F98d6Fd15C07C54ba,
|
|
0x9409280DC1e6D33AB7A8C6EC03e5763FB61772B5,
|
|
0x5FAE7E604FC3e24fd43A72867ceBaC94c65b404A
|
|
];
|
|
|
|
vm.label(address(adapter), "CurveAdapter");
|
|
vm.label(USDT, "USDT");
|
|
vm.label(USDC, "USDC");
|
|
vm.label(STABLE_POOL, "STABLE_POOL");
|
|
vm.label(WETH, "WETH");
|
|
vm.label(CRYPTO_POOL, "CRYPTO_POOL");
|
|
}
|
|
|
|
receive() external payable {}
|
|
|
|
function testSwapsForAdditionalPools() public {
|
|
uint256 len = ADDITIONAL_POOLS_FOR_TESTING.length;
|
|
for (uint256 i = 0; i < len; i++) {
|
|
bytes32 pair = bytes32(bytes20(ADDITIONAL_POOLS_FOR_TESTING[i]));
|
|
address[] memory tokens = adapter.getTokens(pair);
|
|
uint256[] memory amounts = new uint256[](1);
|
|
|
|
try ICurveStableSwapPool(ADDITIONAL_POOLS_FOR_TESTING[i]).balances(
|
|
0
|
|
) returns (uint256 bal) {
|
|
amounts[0] = bal / 10;
|
|
} catch {
|
|
amounts[0] = ICurveCustomInt128Pool(
|
|
ADDITIONAL_POOLS_FOR_TESTING[i]
|
|
).balances(int128(0)) / 10;
|
|
}
|
|
|
|
deal(tokens[0], address(this), amounts[0]);
|
|
IERC20(tokens[0]).approve(address(adapter), amounts[0]);
|
|
|
|
// Test Swap
|
|
Trade memory trade = adapter.swap(
|
|
pair,
|
|
tokens[0],
|
|
tokens[1],
|
|
ISwapAdapterTypes.OrderSide.Sell,
|
|
amounts[0]
|
|
);
|
|
|
|
// Test Limits
|
|
uint256[] memory limits =
|
|
adapter.getLimits(pair, tokens[0], tokens[1]);
|
|
|
|
assertGt(trade.calculatedAmount, 0);
|
|
assertGt(trade.price.numerator, 0);
|
|
assertGt(trade.price.denominator, 0);
|
|
assertGt(limits[0], 0);
|
|
assertGt(limits[1], 0);
|
|
}
|
|
}
|
|
|
|
function testSwapFuzzCurveStEthPool(
|
|
uint256 specifiedAmount,
|
|
bool invertedSides
|
|
) public {
|
|
(address sellToken, address buyToken) =
|
|
!invertedSides ? (ETH, stETH) : (stETH, ETH);
|
|
(uint256 sellTokenBalBefore, uint256 buyTokenBalBefore) = (0, 0);
|
|
|
|
bytes32 pair = bytes32(bytes20(STETH_POOL));
|
|
uint256[] memory limits = adapter.getLimits(pair, sellToken, buyToken);
|
|
|
|
vm.assume(specifiedAmount < limits[0] && specifiedAmount > 10 ** 5);
|
|
|
|
if (sellToken == ETH) {
|
|
deal(address(adapter), specifiedAmount);
|
|
sellTokenBalBefore = address(adapter).balance;
|
|
buyTokenBalBefore = IERC20(buyToken).balanceOf(address(this));
|
|
} else {
|
|
dealStEthTokens(specifiedAmount);
|
|
IERC20(sellToken).approve(address(adapter), specifiedAmount);
|
|
sellTokenBalBefore = IERC20(sellToken).balanceOf(address(this));
|
|
buyTokenBalBefore = address(this).balance;
|
|
}
|
|
|
|
Trade memory trade = adapter.swap(
|
|
pair, sellToken, buyToken, OrderSide.Sell, specifiedAmount
|
|
);
|
|
|
|
if (sellToken == ETH) {
|
|
assertEq(
|
|
specifiedAmount, sellTokenBalBefore - address(adapter).balance
|
|
);
|
|
assertGe(
|
|
trade.calculatedAmount + 3,
|
|
IERC20(buyToken).balanceOf(address(this)) - buyTokenBalBefore
|
|
);
|
|
assertLe(
|
|
trade.calculatedAmount - 3,
|
|
IERC20(buyToken).balanceOf(address(this)) - buyTokenBalBefore
|
|
);
|
|
} else {
|
|
assertGe(
|
|
specifiedAmount + 3,
|
|
sellTokenBalBefore - IERC20(sellToken).balanceOf(address(this))
|
|
);
|
|
assertLe(
|
|
specifiedAmount - 3,
|
|
sellTokenBalBefore - IERC20(sellToken).balanceOf(address(this))
|
|
);
|
|
assertEq(
|
|
trade.calculatedAmount,
|
|
address(this).balance - buyTokenBalBefore
|
|
);
|
|
}
|
|
}
|
|
|
|
function testSwapFuzzCurveStableSwap(uint256 specifiedAmount) public {
|
|
OrderSide side = OrderSide.Sell;
|
|
|
|
bytes32 pair = bytes32(bytes20(STABLE_POOL));
|
|
uint256[] memory limits = adapter.getLimits(pair, USDC, USDT);
|
|
|
|
vm.assume(specifiedAmount < limits[0] && specifiedAmount > 10 ** 4);
|
|
|
|
deal(USDC, address(this), specifiedAmount);
|
|
IERC20(USDC).approve(address(adapter), specifiedAmount);
|
|
|
|
uint256 usdc_balance = IERC20(USDC).balanceOf(address(this));
|
|
uint256 USDT_balance = IERC20(USDT).balanceOf(address(this));
|
|
|
|
Trade memory trade =
|
|
adapter.swap(pair, USDC, USDT, side, specifiedAmount);
|
|
|
|
assertEq(
|
|
specifiedAmount,
|
|
usdc_balance - IERC20(USDC).balanceOf(address(this))
|
|
);
|
|
assertEq(
|
|
trade.calculatedAmount,
|
|
IERC20(USDT).balanceOf(address(this)) - USDT_balance
|
|
);
|
|
}
|
|
|
|
function testSwapFuzzCurveCryptoSwap(uint256 specifiedAmount) public {
|
|
OrderSide side = OrderSide.Sell;
|
|
|
|
bytes32 pair = bytes32(bytes20(CRYPTO_POOL));
|
|
uint256[] memory limits = adapter.getLimits(pair, WETH, USDT);
|
|
|
|
vm.assume(specifiedAmount < limits[0] && specifiedAmount > 10 ** 6);
|
|
|
|
deal(WETH, address(this), specifiedAmount);
|
|
IERC20(WETH).approve(address(adapter), specifiedAmount);
|
|
|
|
uint256 WETH_balance = IERC20(WETH).balanceOf(address(this));
|
|
uint256 USDT_balance = IERC20(USDT).balanceOf(address(this));
|
|
|
|
Trade memory trade =
|
|
adapter.swap(pair, WETH, USDT, side, specifiedAmount);
|
|
|
|
assertEq(
|
|
specifiedAmount,
|
|
WETH_balance - IERC20(WETH).balanceOf(address(this))
|
|
);
|
|
assertEq(
|
|
trade.calculatedAmount,
|
|
IERC20(USDT).balanceOf(address(this)) - USDT_balance
|
|
);
|
|
}
|
|
|
|
function testSwapFuzzCurveCryptoSwapUsingEth(uint256 specifiedAmount)
|
|
public
|
|
{
|
|
OrderSide side = OrderSide.Sell;
|
|
|
|
bytes32 pair = bytes32(bytes20(CRYPTO_POOL));
|
|
uint256[] memory limits = adapter.getLimits(pair, ETH, USDT);
|
|
|
|
vm.assume(specifiedAmount < limits[0] && specifiedAmount > 10 ** 6);
|
|
|
|
deal(address(adapter), specifiedAmount);
|
|
|
|
uint256 ETH_balance = address(adapter).balance;
|
|
uint256 USDT_balance = IERC20(USDT).balanceOf(address(this));
|
|
|
|
Trade memory trade =
|
|
adapter.swap(pair, ETH, USDT, side, specifiedAmount);
|
|
|
|
assertEq(specifiedAmount, ETH_balance - address(adapter).balance);
|
|
assertEq(
|
|
trade.calculatedAmount,
|
|
IERC20(USDT).balanceOf(address(this)) - USDT_balance
|
|
);
|
|
}
|
|
|
|
function testSwapFuzzCurveStablePoolEthWithEth(uint256 specifiedAmount)
|
|
public
|
|
{
|
|
OrderSide side = OrderSide.Sell;
|
|
|
|
bytes32 pair = bytes32(bytes20(ETH_POOL));
|
|
uint256[] memory limits = adapter.getLimits(pair, ETH, WBETH);
|
|
|
|
vm.assume(specifiedAmount < limits[0] && specifiedAmount > 10 ** 14);
|
|
|
|
deal(address(adapter), specifiedAmount);
|
|
|
|
uint256 eth_balance = address(adapter).balance;
|
|
uint256 WBETH_balance = IERC20(WBETH).balanceOf(address(this));
|
|
|
|
Trade memory trade =
|
|
adapter.swap(pair, ETH, WBETH, side, specifiedAmount);
|
|
|
|
assertEq(specifiedAmount, eth_balance - address(adapter).balance);
|
|
assertEq(
|
|
trade.calculatedAmount,
|
|
IERC20(WBETH).balanceOf(address(this)) - WBETH_balance
|
|
);
|
|
}
|
|
|
|
function testSwapFuzzCurveStablePoolEthWithToken(uint256 specifiedAmount)
|
|
public
|
|
{
|
|
OrderSide side = OrderSide.Sell;
|
|
|
|
bytes32 pair = bytes32(bytes20(ETH_POOL));
|
|
uint256[] memory limits = adapter.getLimits(pair, WBETH, ETH);
|
|
|
|
vm.assume(specifiedAmount < limits[0] && specifiedAmount > 10 ** 14);
|
|
|
|
deal(address(WBETH), address(this), specifiedAmount);
|
|
IERC20(WBETH).approve(address(adapter), specifiedAmount);
|
|
|
|
uint256 eth_balance = address(this).balance;
|
|
uint256 WBETH_balance = IERC20(WBETH).balanceOf(address(this));
|
|
|
|
Trade memory trade =
|
|
adapter.swap(pair, WBETH, ETH, side, specifiedAmount);
|
|
|
|
assertEq(trade.calculatedAmount, address(this).balance - eth_balance);
|
|
assertEq(
|
|
specifiedAmount,
|
|
WBETH_balance - IERC20(WBETH).balanceOf(address(this))
|
|
);
|
|
}
|
|
|
|
function testSwapSellIncreasingSwapsCurve() public {
|
|
executeIncreasingSwapsStableSwap(OrderSide.Sell);
|
|
executeIncreasingSwapsCryptoSwap(OrderSide.Sell);
|
|
}
|
|
|
|
function executeIncreasingSwapsStableSwap(OrderSide side) internal {
|
|
bytes32 pair = bytes32(bytes20(CRYPTO_POOL));
|
|
|
|
uint256[] memory amounts = new uint256[](TEST_ITERATIONS);
|
|
for (uint256 i = 0; i < TEST_ITERATIONS; i++) {
|
|
amounts[i] = 1000 * i * 10 ** 14;
|
|
}
|
|
|
|
Trade[] memory trades = new Trade[](TEST_ITERATIONS);
|
|
uint256 beforeSwap;
|
|
for (uint256 i = 0; i < TEST_ITERATIONS; i++) {
|
|
beforeSwap = vm.snapshot();
|
|
|
|
deal(WETH, address(this), amounts[i]);
|
|
IERC20(WETH).approve(address(adapter), amounts[i]);
|
|
|
|
trades[i] = adapter.swap(pair, WETH, USDT, side, amounts[i]);
|
|
vm.revertTo(beforeSwap);
|
|
}
|
|
|
|
for (uint256 i = 1; i < TEST_ITERATIONS - 1; i++) {
|
|
assertLe(trades[i].calculatedAmount, trades[i + 1].calculatedAmount);
|
|
assertEq(trades[i].price.compareFractions(trades[i + 1].price), 1);
|
|
}
|
|
}
|
|
|
|
function executeIncreasingSwapsCryptoSwap(OrderSide side) internal {
|
|
bytes32 pair = bytes32(bytes20(CRYPTO_POOL));
|
|
|
|
uint256[] memory amounts = new uint256[](TEST_ITERATIONS);
|
|
for (uint256 i = 0; i < TEST_ITERATIONS; i++) {
|
|
amounts[i] = 1000 * i * 10 ** 6;
|
|
}
|
|
|
|
Trade[] memory trades = new Trade[](TEST_ITERATIONS);
|
|
uint256 beforeSwap;
|
|
for (uint256 i = 0; i < TEST_ITERATIONS; i++) {
|
|
beforeSwap = vm.snapshot();
|
|
|
|
deal(WETH, address(this), amounts[i]);
|
|
IERC20(WETH).approve(address(adapter), amounts[i]);
|
|
|
|
trades[i] = adapter.swap(pair, WETH, USDT, side, amounts[i]);
|
|
vm.revertTo(beforeSwap);
|
|
}
|
|
|
|
for (uint256 i = 1; i < TEST_ITERATIONS - 1; i++) {
|
|
assertLe(trades[i].calculatedAmount, trades[i + 1].calculatedAmount);
|
|
assertEq(trades[i].price.compareFractions(trades[i + 1].price), 1);
|
|
}
|
|
}
|
|
|
|
function testGetCapabilitiesCurveSwap(bytes32 pair, address t0, address t1)
|
|
public
|
|
{
|
|
Capability[] memory res = adapter.getCapabilities(pair, t0, t1);
|
|
|
|
assertEq(res.length, 2);
|
|
}
|
|
|
|
function testGetTokensCurveStableSwap() public {
|
|
bytes32 pair = bytes32(bytes20(STABLE_POOL));
|
|
address[] memory tokens = adapter.getTokens(pair);
|
|
|
|
assertGe(tokens.length, 2);
|
|
}
|
|
|
|
function testGetTokensCurveCryptoSwap() public {
|
|
bytes32 pair = bytes32(bytes20(CRYPTO_POOL));
|
|
address[] memory tokens = adapter.getTokens(pair);
|
|
|
|
assertGe(tokens.length, 2);
|
|
}
|
|
|
|
function testGetLimitsCurveStableSwap() public {
|
|
bytes32 pair = bytes32(bytes20(STABLE_POOL));
|
|
uint256[] memory limits = adapter.getLimits(pair, USDC, USDT);
|
|
|
|
assertEq(limits.length, 2);
|
|
}
|
|
|
|
function testGetLimitsCurveCryptoSwap() public {
|
|
bytes32 pair = bytes32(bytes20(CRYPTO_POOL));
|
|
uint256[] memory limits = adapter.getLimits(pair, WETH, USDT);
|
|
|
|
assertEq(limits.length, 2);
|
|
}
|
|
|
|
/// @dev custom function to 'deal' stETH tokens as normal deal won't work
|
|
function dealStEthTokens(uint256 amount) internal {
|
|
uint256 wstETHAmount = wstETH.getStETHByWstETH(amount);
|
|
deal(address(wstETH), address(this), wstETHAmount);
|
|
wstETH.unwrap(wstETHAmount);
|
|
}
|
|
}
|
|
|
|
interface IwstETH is IERC20 {
|
|
function unwrap(uint256 _wstETHAmount) external returns (uint256);
|
|
function getStETHByWstETH(uint256 _wstETHAmount)
|
|
external
|
|
view
|
|
returns (uint256);
|
|
}
|