Liquidity Party adapter
This commit is contained in:
@@ -12,7 +12,6 @@ contract AdapterTest is Test, ISwapAdapterTypes {
|
||||
using FractionMath for Fraction;
|
||||
using EfficientERC20 for IERC20;
|
||||
|
||||
uint256 constant pricePrecision = 10e24;
|
||||
string[] public stringPctgs = ["0%", "0.1%", "50%", "100%"];
|
||||
|
||||
// @notice Test the behavior of a swap adapter for a list of pools
|
||||
@@ -44,9 +43,10 @@ contract AdapterTest is Test, ISwapAdapterTypes {
|
||||
}
|
||||
|
||||
// Prices should:
|
||||
// 1. Be monotonic decreasing
|
||||
// 1. Be monotonic decreasing (within rounding tolerance)
|
||||
// 2. Be positive
|
||||
// 3. Always be >= the executed price and >= the price after the swap
|
||||
// (within rounding tolerance)
|
||||
function testPricesForPair(
|
||||
ISwapAdapter adapter,
|
||||
bytes32 poolId,
|
||||
@@ -76,7 +76,10 @@ contract AdapterTest is Test, ISwapAdapterTypes {
|
||||
Fraction[] memory prices =
|
||||
adapter.price(poolId, tokenIn, tokenOut, amounts);
|
||||
assertGt(
|
||||
fractionToInt(prices[0]),
|
||||
fractionToInt(prices[0])
|
||||
// within rounding tolerance
|
||||
* (amounts[amounts.length - 1] + 1)
|
||||
/ amounts[amounts.length - 1],
|
||||
fractionToInt(prices[prices.length - 1]),
|
||||
"Price at limit should be smaller than price at 0"
|
||||
);
|
||||
@@ -92,7 +95,6 @@ contract AdapterTest is Test, ISwapAdapterTypes {
|
||||
uint256 priceAtZero = fractionToInt(prices[0]);
|
||||
console2.log("TEST: Price at 0: %d", priceAtZero);
|
||||
|
||||
Trade memory trade;
|
||||
deal(tokenIn, address(this), 5 * amounts[amounts.length - 1]);
|
||||
|
||||
uint256 initialState = vm.snapshot();
|
||||
@@ -104,50 +106,93 @@ contract AdapterTest is Test, ISwapAdapterTypes {
|
||||
amounts[j]
|
||||
);
|
||||
uint256 priceAtAmount = fractionToInt(prices[j]);
|
||||
// We allow the assertions to tolerate rounding errors
|
||||
// not greater than `1/amounts[j]`
|
||||
uint256 toleranceDenominator = amounts[j];
|
||||
|
||||
console2.log("TEST: Swapping %d of %s", amounts[j], tokenIn);
|
||||
trade = adapter.swap(
|
||||
try adapter.swap(
|
||||
poolId, tokenIn, tokenOut, OrderSide.Sell, amounts[j]
|
||||
);
|
||||
uint256 executedPrice =
|
||||
trade.calculatedAmount * pricePrecision / amounts[j];
|
||||
uint256 priceAfterSwap = fractionToInt(trade.price);
|
||||
console2.log("TEST: - Executed price: %d", executedPrice);
|
||||
console2.log("TEST: - Price at amount: %d", priceAtAmount);
|
||||
console2.log("TEST: - Price after swap: %d", priceAfterSwap);
|
||||
) returns (
|
||||
Trade memory trade
|
||||
) {
|
||||
uint256 executedPrice = Fraction(
|
||||
trade.calculatedAmount, amounts[j]
|
||||
).toQ128x128();
|
||||
uint256 priceAfterSwap = fractionToInt(trade.price);
|
||||
console2.log("TEST: - Executed price: %d", executedPrice);
|
||||
console2.log("TEST: - Price at amount: %d", priceAtAmount);
|
||||
console2.log("TEST: - Price after swap: %d", priceAfterSwap);
|
||||
|
||||
if (hasPriceImpact) {
|
||||
assertGe(
|
||||
executedPrice,
|
||||
priceAtAmount,
|
||||
"Price should be greated than executed price."
|
||||
);
|
||||
assertGt(
|
||||
executedPrice,
|
||||
priceAfterSwap,
|
||||
"Executed price should be greater than price after swap."
|
||||
);
|
||||
assertGt(
|
||||
priceAtZero,
|
||||
executedPrice,
|
||||
"Price should be greated than price after swap."
|
||||
);
|
||||
} else {
|
||||
assertGe(
|
||||
priceAtZero,
|
||||
priceAfterSwap,
|
||||
"Executed price should be or equal to price after swap."
|
||||
);
|
||||
assertGe(
|
||||
priceAtZero,
|
||||
priceAtAmount,
|
||||
"Executed price should be or equal to price after swap."
|
||||
);
|
||||
assertGe(
|
||||
priceAtZero,
|
||||
executedPrice,
|
||||
"Price should be or equal to price after swap."
|
||||
);
|
||||
if (hasPriceImpact) {
|
||||
assertGeTol(
|
||||
executedPrice,
|
||||
priceAtAmount,
|
||||
toleranceDenominator,
|
||||
"Price should be greater than executed price."
|
||||
);
|
||||
assertGtTol(
|
||||
executedPrice,
|
||||
priceAfterSwap,
|
||||
toleranceDenominator,
|
||||
"Executed price should be greater than price after swap."
|
||||
);
|
||||
assertGtTol(
|
||||
priceAtZero,
|
||||
executedPrice,
|
||||
toleranceDenominator,
|
||||
"Price should be greater than price after swap."
|
||||
);
|
||||
} else {
|
||||
assertGeTol(
|
||||
priceAtZero,
|
||||
priceAfterSwap,
|
||||
toleranceDenominator,
|
||||
"Executed price should be or equal to price after swap."
|
||||
);
|
||||
assertGeTol(
|
||||
priceAtZero,
|
||||
priceAtAmount,
|
||||
toleranceDenominator,
|
||||
"Executed price should be or equal to price after swap."
|
||||
);
|
||||
assertGeTol(
|
||||
priceAtZero,
|
||||
executedPrice,
|
||||
toleranceDenominator,
|
||||
"Price should be or equal to price after swap."
|
||||
);
|
||||
}
|
||||
} catch (bytes memory reason) {
|
||||
(bool isTooSmall, uint256 lowerLimit) =
|
||||
decodeTooSmallError(reason);
|
||||
(bool isLimitExceeded, uint256 limit) =
|
||||
decodeLimitExceededError(reason);
|
||||
|
||||
if (isTooSmall) {
|
||||
// We allow a TooSmall exception to occur for the smallest
|
||||
// amount only.
|
||||
if (j == 1) {
|
||||
console2.log(
|
||||
"TEST: TooSmall exception tolerated for smallest amount"
|
||||
);
|
||||
} else {
|
||||
revert(
|
||||
"TEST: TooSmall thrown for a significantly sized amount"
|
||||
);
|
||||
}
|
||||
} else if (isLimitExceeded) {
|
||||
// We never allow LimitExceeded to be thrown, since all
|
||||
// amounts should be within the stated limits.
|
||||
revert(
|
||||
"TEST: LimitExceeded thrown for an amount within limits"
|
||||
);
|
||||
} else {
|
||||
// any other revert reason bubbles up
|
||||
assembly {
|
||||
revert(add(reason, 32), mload(reason))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
vm.revertTo(initialState);
|
||||
@@ -185,24 +230,87 @@ contract AdapterTest is Test, ISwapAdapterTypes {
|
||||
);
|
||||
uint256[] memory aboveLimitArray = new uint256[](1);
|
||||
aboveLimitArray[0] = amountAboveLimit;
|
||||
bool supportsLimitExceeded = false;
|
||||
|
||||
try adapter.price(poolId, tokenIn, tokenOut, aboveLimitArray) {
|
||||
revert(
|
||||
"Pool shouldn't be able to fetch prices above the sell limit"
|
||||
);
|
||||
} catch Error(string memory s) {
|
||||
console2.log(
|
||||
"TEST: Expected error when fetching price above limit: %s", s
|
||||
);
|
||||
} catch (bytes memory reason) {
|
||||
(bool isTooSmall, uint256 lowerLimit) = decodeTooSmallError(reason);
|
||||
(bool isLimitExceeded, uint256 limit) =
|
||||
decodeLimitExceededError(reason);
|
||||
|
||||
if (isLimitExceeded) {
|
||||
supportsLimitExceeded = true;
|
||||
console2.log(
|
||||
"TEST: LimitExceeded supported! Thrown when fetching price above limit: %i",
|
||||
limit
|
||||
);
|
||||
} else if (isTooSmall) {
|
||||
console2.log(
|
||||
"TEST: UNEXPECTED TooSmall error when fetching price below limit: %i",
|
||||
lowerLimit
|
||||
);
|
||||
revert TooSmall(lowerLimit);
|
||||
} else if (
|
||||
reason.length >= 4
|
||||
&& bytes4(reason) == bytes4(keccak256("Error(string)"))
|
||||
) {
|
||||
string memory s = abi.decode(
|
||||
sliceBytes(reason, 4, reason.length - 4), (string)
|
||||
);
|
||||
console2.log(
|
||||
"TEST: Expected error when fetching price above limit: %s",
|
||||
s
|
||||
);
|
||||
} else {
|
||||
// Unexpected error type: re-raise.
|
||||
assembly {
|
||||
revert(add(reason, 32), mload(reason))
|
||||
}
|
||||
}
|
||||
}
|
||||
try adapter.swap(
|
||||
poolId, tokenIn, tokenOut, OrderSide.Sell, aboveLimitArray[0]
|
||||
) {
|
||||
revert("Pool shouldn't be able to swap above the sell limit");
|
||||
} catch Error(string memory s) {
|
||||
console2.log(
|
||||
"TEST: Expected error when swapping above limit: %s", s
|
||||
);
|
||||
} catch (bytes memory reason) {
|
||||
(bool isTooSmall, uint256 lowerLimit) = decodeTooSmallError(reason);
|
||||
(bool isLimitExceeded, uint256 limit) =
|
||||
decodeLimitExceededError(reason);
|
||||
|
||||
if (isLimitExceeded) {
|
||||
supportsLimitExceeded = true;
|
||||
console2.log(
|
||||
"TEST: LimitExceeded supported! Thrown when swapping above limit: %i",
|
||||
limit
|
||||
);
|
||||
} else if (isTooSmall) {
|
||||
console2.log(
|
||||
"TEST: UNEXPECTED TooSmall error when swapping above limit: %i",
|
||||
lowerLimit
|
||||
);
|
||||
revert TooSmall(lowerLimit);
|
||||
} else if (
|
||||
reason.length >= 4
|
||||
&& bytes4(reason) == bytes4(keccak256("Error(string)"))
|
||||
) {
|
||||
string memory s = abi.decode(
|
||||
sliceBytes(reason, 4, reason.length - 4), (string)
|
||||
);
|
||||
console2.log(
|
||||
"TEST: Expected error when swapping above limit: %s", s
|
||||
);
|
||||
} else {
|
||||
// Unexpected error type: re-raise.
|
||||
assembly {
|
||||
revert(add(reason, 32), mload(reason))
|
||||
}
|
||||
}
|
||||
}
|
||||
if (supportsLimitExceeded) {
|
||||
console.log(unicode"Adapter supports LimitExceeded ✓");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -244,7 +352,7 @@ contract AdapterTest is Test, ISwapAdapterTypes {
|
||||
pure
|
||||
returns (uint256)
|
||||
{
|
||||
return price.numerator * pricePrecision / price.denominator;
|
||||
return price.toQ128x128();
|
||||
}
|
||||
|
||||
function hasCapability(
|
||||
@@ -259,4 +367,85 @@ contract AdapterTest is Test, ISwapAdapterTypes {
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
//
|
||||
// Custom Error Helper Functions
|
||||
// TODO should we expose these in a better location / library for solvers to
|
||||
// also leverage?
|
||||
|
||||
// Helper function to check if error is TooSmall and decode it
|
||||
function decodeTooSmallError(bytes memory reason)
|
||||
internal
|
||||
pure
|
||||
returns (bool, uint256)
|
||||
{
|
||||
if (reason.length >= 4 && bytes4(reason) == TooSmall.selector) {
|
||||
if (reason.length == 36) {
|
||||
uint256 lowerLimit =
|
||||
abi.decode(sliceBytes(reason, 4, 32), (uint256));
|
||||
return (true, lowerLimit);
|
||||
}
|
||||
}
|
||||
return (false, 0);
|
||||
}
|
||||
|
||||
// Helper function to check if error is LimitExceeded and decode it
|
||||
function decodeLimitExceededError(bytes memory reason)
|
||||
internal
|
||||
pure
|
||||
returns (bool, uint256)
|
||||
{
|
||||
if (reason.length >= 4 && bytes4(reason) == LimitExceeded.selector) {
|
||||
if (reason.length == 36) {
|
||||
uint256 limit = abi.decode(sliceBytes(reason, 4, 32), (uint256));
|
||||
return (true, limit);
|
||||
}
|
||||
}
|
||||
return (false, 0);
|
||||
}
|
||||
|
||||
// Helper function to slice bytes
|
||||
function sliceBytes(bytes memory data, uint256 start, uint256 length)
|
||||
internal
|
||||
pure
|
||||
returns (bytes memory)
|
||||
{
|
||||
bytes memory result = new bytes(length);
|
||||
for (uint256 i = 0; i < length; i++) {
|
||||
result[i] = data[start + i];
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
//
|
||||
// Helper functions to assert with tolerance
|
||||
//
|
||||
|
||||
function assertGeTol(
|
||||
uint256 a,
|
||||
uint256 b,
|
||||
uint256 toleranceDenominator,
|
||||
string memory errorMessage
|
||||
) internal {
|
||||
// The tolerance is `1 / toleranceDenominator`, so we increase the value
|
||||
// of `a` by this amount. adjustedA = a * (denom+1) / denom
|
||||
uint256 adjustedA = FractionMath.mulDiv(
|
||||
a, toleranceDenominator + 1, toleranceDenominator
|
||||
);
|
||||
assertGe(adjustedA, b, errorMessage);
|
||||
}
|
||||
|
||||
function assertGtTol(
|
||||
uint256 a,
|
||||
uint256 b,
|
||||
uint256 toleranceDenominator,
|
||||
string memory errorMessage
|
||||
) internal {
|
||||
// The tolerance is `1 / toleranceDenominator`, so we increase the value
|
||||
// of `a` by this amount. adjustedA = a * (denom+1) / denom
|
||||
uint256 adjustedA = FractionMath.mulDiv(
|
||||
a, toleranceDenominator + 1, toleranceDenominator
|
||||
);
|
||||
assertGt(adjustedA, b, errorMessage);
|
||||
}
|
||||
}
|
||||
|
||||
224
evm/test/LiquidityPartySwapAdapter.t.sol
Normal file
224
evm/test/LiquidityPartySwapAdapter.t.sol
Normal file
@@ -0,0 +1,224 @@
|
||||
// SPDX-License-Identifier: UNLICENSED
|
||||
pragma solidity ^0.8.27;
|
||||
|
||||
import "forge-std/console2.sol"; // todo
|
||||
|
||||
import {
|
||||
IERC20
|
||||
} from "../lib/openzeppelin-contracts/contracts/token/ERC20/IERC20.sol";
|
||||
import {
|
||||
IERC20Metadata
|
||||
} from "../lib/openzeppelin-contracts/contracts/token/ERC20/extensions/IERC20Metadata.sol";
|
||||
import {FractionMath} from "../src/libraries/FractionMath.sol";
|
||||
import {IPartyInfo} from "../src/liquidityparty/IPartyInfo.sol";
|
||||
import {IPartyPlanner} from "../src/liquidityparty/IPartyPlanner.sol";
|
||||
import {IPartyPool} from "../src/liquidityparty/IPartyPool.sol";
|
||||
import {
|
||||
LiquidityPartySwapAdapter
|
||||
} from "../src/liquidityparty/LiquidityPartySwapAdapter.sol";
|
||||
import {AdapterTest} from "./AdapterTest.sol";
|
||||
|
||||
contract LiquidityPartyFunctionTest is AdapterTest {
|
||||
using FractionMath for Fraction;
|
||||
|
||||
IPartyPlanner internal constant PLANNER =
|
||||
IPartyPlanner(0x42977f565971F6D288a05ddEbC87A17276F71A29);
|
||||
IPartyInfo internal constant INFO =
|
||||
IPartyInfo(0x605F803cD27F5c1fa01440B2cbd5D3E4Cf7EE850);
|
||||
address internal constant MINT_IMPL =
|
||||
0xA0375403921e9B357E1BeD57bef3fA3FCE80acd0;
|
||||
address internal constant SWAP_IMPL =
|
||||
0x6aA001e87F86E83bc4D569883332882cb47E2A13;
|
||||
IPartyPool internal constant POOL =
|
||||
IPartyPool(0x2A804e94500AE379ee0CcC423a67B07cc0aF548C);
|
||||
bytes32 internal constant POOL_ID = bytes32(bytes20(address(POOL)));
|
||||
uint256 internal constant FORK_BLOCK = 23978797; // block in which the pool
|
||||
// was created
|
||||
|
||||
LiquidityPartySwapAdapter internal adapter;
|
||||
uint256 internal constant TEST_ITERATIONS = 10;
|
||||
|
||||
address[] internal tokens;
|
||||
address internal constant USDT = 0xdAC17F958D2ee523a2206206994597C13D831ec7;
|
||||
address internal constant USDC = 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48;
|
||||
address internal constant WBTC = 0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599;
|
||||
address internal constant WETH = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2;
|
||||
address internal constant UNI = 0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984;
|
||||
address internal constant WSOL = 0xD31a59c85aE9D8edEFeC411D448f90841571b89c;
|
||||
address internal constant TRX = 0x50327c6c5a14DCaDE707ABad2E27eB517df87AB5;
|
||||
address internal constant AAVE = 0x7Fc66500c84A76Ad7e9c93437bFc5Ac33E2DDaE9;
|
||||
address internal constant PEPE = 0x6982508145454Ce325dDbE47a25d4ec3d2311933;
|
||||
address internal constant SHIB = 0x95aD61b0a150d79219dCF64E1E6Cc01f0B64C4cE;
|
||||
|
||||
address private constant INPUT_TOKEN = WBTC;
|
||||
uint8 private constant INPUT_INDEX = 2;
|
||||
address private constant OUTPUT_TOKEN = SHIB;
|
||||
uint8 private constant OUTPUT_INDEX = 9;
|
||||
|
||||
function setUp() public {
|
||||
tokens = new address[](10);
|
||||
tokens[0] = USDT;
|
||||
tokens[1] = USDC;
|
||||
tokens[2] = WBTC;
|
||||
tokens[3] = WETH;
|
||||
tokens[4] = UNI;
|
||||
tokens[5] = WSOL;
|
||||
tokens[6] = TRX;
|
||||
tokens[7] = AAVE;
|
||||
tokens[8] = PEPE;
|
||||
tokens[9] = SHIB;
|
||||
|
||||
vm.createSelectFork(vm.rpcUrl("mainnet"), FORK_BLOCK);
|
||||
|
||||
adapter = new LiquidityPartySwapAdapter(PLANNER, INFO);
|
||||
|
||||
vm.label(address(PLANNER), "PartyPlanner");
|
||||
vm.label(address(INFO), "PartyInfo");
|
||||
vm.label(address(MINT_IMPL), "PartyPoolMintImpl");
|
||||
vm.label(address(SWAP_IMPL), "PartyPoolSwapImpl");
|
||||
vm.label(address(POOL), "PartyPool");
|
||||
vm.label(address(adapter), "LiquidityPartySwapAdapter");
|
||||
for (uint256 i = 0; i < tokens.length; i++) {
|
||||
vm.label(address(tokens[i]), IERC20Metadata(tokens[i]).symbol());
|
||||
}
|
||||
}
|
||||
|
||||
function testPrice() public view {
|
||||
uint256[] memory amounts = new uint256[](3);
|
||||
uint256 balance = IERC20(INPUT_TOKEN).balanceOf(address(POOL));
|
||||
amounts[0] = 2; // cannot use 1: the fee will round up and take
|
||||
// everything, resulting in a zero-output reversion
|
||||
amounts[1] = balance;
|
||||
amounts[2] = balance * 2;
|
||||
|
||||
Fraction[] memory prices =
|
||||
adapter.price(POOL_ID, INPUT_TOKEN, OUTPUT_TOKEN, amounts);
|
||||
|
||||
for (uint256 i = 0; i < prices.length; i++) {
|
||||
assertGt(prices[i].numerator, 0);
|
||||
assertGt(prices[i].denominator, 0);
|
||||
}
|
||||
}
|
||||
|
||||
function testPriceDecreasing() public view {
|
||||
uint256[] memory limits =
|
||||
adapter.getLimits(POOL_ID, INPUT_TOKEN, OUTPUT_TOKEN);
|
||||
|
||||
uint256[] memory amounts = new uint256[](TEST_ITERATIONS);
|
||||
|
||||
for (uint256 i = 0; i < TEST_ITERATIONS; i++) {
|
||||
// The first entry will be a zero amount which returns the current
|
||||
// marginal price.
|
||||
amounts[i] = limits[0] * i / (TEST_ITERATIONS - 1);
|
||||
}
|
||||
|
||||
Fraction[] memory prices =
|
||||
adapter.price(POOL_ID, INPUT_TOKEN, OUTPUT_TOKEN, amounts);
|
||||
|
||||
for (uint256 i = 0; i < TEST_ITERATIONS - 1; i++) {
|
||||
console2.log("compare price", prices[i].numerator);
|
||||
console2.log(" ", prices[i].denominator);
|
||||
console2.log(" > ", prices[i + 1].numerator);
|
||||
console2.log(" ", prices[i + 1].denominator);
|
||||
console2.log();
|
||||
assertEq(prices[i].compareFractions(prices[i + 1]), 1);
|
||||
}
|
||||
}
|
||||
|
||||
function testSwapFuzz(uint256 amount) public {
|
||||
uint256[] memory limits =
|
||||
adapter.getLimits(POOL_ID, INPUT_TOKEN, OUTPUT_TOKEN);
|
||||
vm.assume(amount > 1); // 1 will not work because we take fee-on-input
|
||||
// and round up, leaving nothing to trade
|
||||
vm.assume(amount <= limits[0]);
|
||||
|
||||
deal(INPUT_TOKEN, address(this), amount);
|
||||
IERC20(INPUT_TOKEN).approve(address(adapter), amount);
|
||||
|
||||
uint256 usdtBalance = IERC20(INPUT_TOKEN).balanceOf(address(this));
|
||||
uint256 wethBalance = IERC20(OUTPUT_TOKEN).balanceOf(address(this));
|
||||
|
||||
Trade memory trade = adapter.swap(
|
||||
POOL_ID, INPUT_TOKEN, OUTPUT_TOKEN, OrderSide.Sell, amount
|
||||
);
|
||||
|
||||
if (trade.calculatedAmount > 0) {
|
||||
assertEq(
|
||||
amount,
|
||||
usdtBalance - IERC20(INPUT_TOKEN).balanceOf(address(this))
|
||||
);
|
||||
assertEq(
|
||||
trade.calculatedAmount,
|
||||
IERC20(OUTPUT_TOKEN).balanceOf(address(this)) - wethBalance
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function testSwapSellIncreasing() public {
|
||||
uint256[] memory limits =
|
||||
adapter.getLimits(POOL_ID, INPUT_TOKEN, OUTPUT_TOKEN);
|
||||
uint256[] memory amounts = new uint256[](TEST_ITERATIONS);
|
||||
Trade[] memory trades = new Trade[](TEST_ITERATIONS);
|
||||
|
||||
for (uint256 i = 0; i < TEST_ITERATIONS; i++) {
|
||||
amounts[i] = limits[0] * (i + 1) / (TEST_ITERATIONS - 1);
|
||||
|
||||
uint256 beforeSwap = vm.snapshot();
|
||||
|
||||
deal(INPUT_TOKEN, address(this), amounts[i]);
|
||||
IERC20(INPUT_TOKEN).approve(address(adapter), amounts[i]);
|
||||
trades[i] = adapter.swap(
|
||||
POOL_ID, INPUT_TOKEN, OUTPUT_TOKEN, OrderSide.Sell, amounts[i]
|
||||
);
|
||||
|
||||
vm.revertTo(beforeSwap);
|
||||
}
|
||||
|
||||
for (uint256 i = 0; i < TEST_ITERATIONS - 1; i++) {
|
||||
assertLe(trades[i].calculatedAmount, trades[i + 1].calculatedAmount);
|
||||
assertEq(
|
||||
trades[i].price.denominator, trades[i + 1].price.denominator
|
||||
); // must share a basis
|
||||
assertGe(trades[i].price.numerator, trades[i + 1].price.numerator);
|
||||
}
|
||||
}
|
||||
|
||||
function testGetLimits() public view {
|
||||
uint256[] memory limits =
|
||||
adapter.getLimits(POOL_ID, INPUT_TOKEN, OUTPUT_TOKEN);
|
||||
|
||||
assert(limits.length == 2);
|
||||
assert(limits[0] > 0);
|
||||
assert(limits[1] > 0);
|
||||
}
|
||||
|
||||
function testGetTokens() public view {
|
||||
address[] memory adapterTokens = adapter.getTokens(POOL_ID);
|
||||
for (uint256 i = 0; i < tokens.length; i++) {
|
||||
assertEq(adapterTokens[i], tokens[i]);
|
||||
}
|
||||
}
|
||||
|
||||
function testGetPoolIds() public view {
|
||||
uint256 offset = 0;
|
||||
uint256 limit = 10;
|
||||
bytes32[] memory poolIds = adapter.getPoolIds(offset, limit);
|
||||
|
||||
assertLe(
|
||||
poolIds.length,
|
||||
limit,
|
||||
"Number of pool IDs should be less than or equal to limit"
|
||||
);
|
||||
if (poolIds.length > 0) {
|
||||
assertGt(uint256(poolIds[0]), 0, "Pool ID should be greater than 0");
|
||||
}
|
||||
}
|
||||
|
||||
// Many of the tests above seem entirely redundant with runPoolBehaviorTest
|
||||
// :shrug:
|
||||
function testLiquidityPartyPoolBehaviour() public {
|
||||
bytes32[] memory poolIds = new bytes32[](1);
|
||||
poolIds[0] = POOL_ID;
|
||||
runPoolBehaviourTest(adapter, poolIds);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user