Liquidity Party adapter
This commit is contained in:
@@ -40,7 +40,6 @@ interface ISwapAdapterTypes {
|
||||
}
|
||||
|
||||
/// @dev Representation used for rational numbers such as prices.
|
||||
// TODO: Use only uint128 for numerator and denominator.
|
||||
struct Fraction {
|
||||
uint256 numerator;
|
||||
uint256 denominator;
|
||||
@@ -61,6 +60,20 @@ interface ISwapAdapterTypes {
|
||||
/// available for unexpected reason. E.g. it was paused due to a bug.
|
||||
error Unavailable(string reason);
|
||||
|
||||
/// @dev The InvalidOrder error is thrown when the input to a swap is
|
||||
/// not valid: e.g. if the limit price is negative, or below the
|
||||
/// current price; the request amount is 0; the requested swap tokens
|
||||
/// are not part of the pool; etc.
|
||||
error InvalidOrder(string reason);
|
||||
|
||||
/// @dev The TooSmall error is thrown when the requested trade amount
|
||||
/// is too small, causing either zero output or a numerical imprecision
|
||||
/// problem. If lowerLimit is not zero, then it specifies the minimum
|
||||
/// trade size required. If lowerLimit is zero, then the lower bound
|
||||
/// cannot be easily computed, in which case solvers can binary search
|
||||
/// for a precise lower bound.
|
||||
error TooSmall(uint256 lowerLimit);
|
||||
|
||||
/// @dev The LimitExceeded error is thrown when a limit has been exceeded.
|
||||
/// E.g. the specified amount can't be traded safely.
|
||||
error LimitExceeded(uint256 limit);
|
||||
|
||||
@@ -13,14 +13,267 @@ library FractionMath {
|
||||
ISwapAdapterTypes.Fraction memory frac1,
|
||||
ISwapAdapterTypes.Fraction memory frac2
|
||||
) internal pure returns (int8) {
|
||||
uint256 crossProduct1 = frac1.numerator * frac2.denominator;
|
||||
uint256 crossProduct2 = frac2.numerator * frac1.denominator;
|
||||
uint256 fixed1 = toQ128x128(frac1.numerator, frac1.denominator);
|
||||
uint256 fixed2 = toQ128x128(frac2.numerator, frac2.denominator);
|
||||
|
||||
// fractions are equal
|
||||
if (crossProduct1 == crossProduct2) return 0;
|
||||
if (fixed1 == fixed2) return 0;
|
||||
// frac1 is greater than frac2
|
||||
else if (crossProduct1 > crossProduct2) return 1;
|
||||
else if (fixed1 > fixed2) return 1;
|
||||
// frac1 is less than frac2
|
||||
else return -1;
|
||||
}
|
||||
|
||||
/// @notice Converts a Fraction into unsigned Q128.128 fixed point
|
||||
function toQ128x128(ISwapAdapterTypes.Fraction memory rational)
|
||||
internal
|
||||
pure
|
||||
returns (uint256 result)
|
||||
{
|
||||
return toQ128x128(rational.numerator, rational.denominator);
|
||||
}
|
||||
|
||||
/// @notice Converts an unsigned rational `numerator / denominator`
|
||||
/// into Q128.128 (unsigned 128.128 fixed point),
|
||||
/// rounding toward zero (floor for positive inputs).
|
||||
///
|
||||
/// see https://github.com/Liquidity-Party/toQ128x128
|
||||
///
|
||||
/// @dev Reverts if:
|
||||
/// - `denominator == 0`, or
|
||||
/// - the exact result >= 2^256 (i.e. overflow of uint256).
|
||||
///
|
||||
/// This computes floor(numerator * 2^128 / denominator)
|
||||
/// using a full 512-bit intermediate to avoid precision loss.
|
||||
///
|
||||
function toQ128x128(uint256 numerator, uint256 denominator)
|
||||
internal
|
||||
pure
|
||||
returns (uint256 result)
|
||||
{
|
||||
require(denominator != 0, "toQ128x128: div by zero");
|
||||
|
||||
// We want (numerator * 2^128) / denominator using full precision,
|
||||
// so we implement a 512-bit muldiv.
|
||||
//
|
||||
// Let:
|
||||
// prod = numerator * 2^128
|
||||
//
|
||||
// Since 2^128 is a power of two, the 512-bit product is easy:
|
||||
// prod0 = (numerator << 128) mod 2^256 (low 256 bits)
|
||||
// prod1 = (numerator >> 128) (high 256 bits)
|
||||
//
|
||||
// So prod = (prod1 * 2^256 + prod0).
|
||||
uint256 prod0;
|
||||
uint256 prod1;
|
||||
unchecked {
|
||||
prod0 = numerator << 128;
|
||||
prod1 = numerator >> 128;
|
||||
}
|
||||
|
||||
// If the high 256 bits are zero, the product fits in 256 bits.
|
||||
// This is the cheap path: just do a normal 256-bit division.
|
||||
if (prod1 == 0) {
|
||||
unchecked {
|
||||
// denominator was already checked for 0.
|
||||
return prod0 / denominator;
|
||||
}
|
||||
}
|
||||
|
||||
// At this point prod1 > 0, so the 512-bit product does not fit in a
|
||||
// uint256. We need a full-precision 512/256 division:
|
||||
//
|
||||
// result = floor((prod1 * 2^256 + prod0) / denominator)
|
||||
//
|
||||
// and we must ensure the final result fits in uint256.
|
||||
|
||||
// Ensure result < 2^256. This is equivalent to requiring:
|
||||
// denominator > prod1
|
||||
// because if denominator <= prod1, then:
|
||||
// (prod1 * 2^256) / denominator >= 2^256.
|
||||
require(denominator > prod1, "Q128x128: overflow");
|
||||
|
||||
// Make division exact by subtracting the remainder from [prod1 prod0].
|
||||
uint256 remainder;
|
||||
assembly {
|
||||
// remainder = (prod1 * 2^256 + prod0) % denominator
|
||||
// Since we can only directly mod 256-bit values, we first mod
|
||||
// `prod0`, then adjust using the high word.
|
||||
remainder := mulmod(numerator, shl(128, 1), denominator)
|
||||
}
|
||||
|
||||
// Now subtract `remainder` from the 512-bit product [prod1 prod0].
|
||||
assembly {
|
||||
// Subtract remainder from the low part; if it underflows, borrow
|
||||
// 1 from the high part.
|
||||
let borrow := lt(prod0, remainder)
|
||||
prod0 := sub(prod0, remainder)
|
||||
prod1 := sub(prod1, borrow)
|
||||
}
|
||||
|
||||
// Factor powers of two out of denominator to simplify the division.
|
||||
//
|
||||
// Let denominator = d * 2^shift, with d odd.
|
||||
// We can divide prod0 by 2^shift cheaply (bit shift),
|
||||
// then do an exact division by the odd d using modular inverse.
|
||||
uint256 twos;
|
||||
unchecked {
|
||||
// largest power of two divisor of denominator
|
||||
twos = denominator & (~denominator + 1);
|
||||
}
|
||||
|
||||
assembly {
|
||||
// Divide denominator by twos.
|
||||
denominator := div(denominator, twos)
|
||||
|
||||
// Divide the low word by twos.
|
||||
prod0 := div(prod0, twos)
|
||||
|
||||
// Adjust the high word so that the full 512-bit number is shifted
|
||||
// by `twos`.
|
||||
// twos = 2^k, so:
|
||||
// combined = prod1 * 2^256 + prod0
|
||||
// combined / twos =
|
||||
// prod1 * 2^256 / twos + prod0 / twos
|
||||
// and 2^256 / twos = 2^(256-k).
|
||||
//
|
||||
// Here we compute:
|
||||
// twos = 2^256 / twos
|
||||
twos := add(div(sub(0, twos), twos), 1)
|
||||
|
||||
// Now add the shifted high bits into prod0:
|
||||
prod0 := or(prod0, mul(prod1, twos))
|
||||
}
|
||||
|
||||
// At this point, denominator is odd and the 512-bit value
|
||||
// has been squeezed into prod0 (prod1 is effectively 0).
|
||||
|
||||
// Compute the modular inverse of denominator modulo 2^256.
|
||||
// This uses Newton-Raphson iteration:
|
||||
//
|
||||
// inv ≡ denominator^{-1} (mod 2^256)
|
||||
//
|
||||
// Starting from a seed for odd denominator:
|
||||
// All operations must be unchecked as they rely on modular arithmetic.
|
||||
unchecked {
|
||||
uint256 inv = (3 * denominator) ^ 2;
|
||||
|
||||
// Perform Newton-Raphson iterations to refine the inverse.
|
||||
// Starting from inv which is correct modulo 2^4, then each
|
||||
// Newton-Raphson step doubles the number of correct bits:
|
||||
// 2⁴ → 2⁸ → 2¹⁶ → 2³² → 2⁶⁴ →
|
||||
// 2¹²⁸ → 2²⁵⁶
|
||||
// Requiring six iterations for 256-bit precision:
|
||||
inv *= 2 - denominator * inv;
|
||||
inv *= 2 - denominator * inv;
|
||||
inv *= 2 - denominator * inv;
|
||||
inv *= 2 - denominator * inv;
|
||||
inv *= 2 - denominator * inv;
|
||||
inv *= 2 - denominator * inv;
|
||||
|
||||
// Now inv is the modular inverse of denominator mod 2^256.
|
||||
// The exact division result is then:
|
||||
//
|
||||
// result = (prod0 * inv) mod 2^256
|
||||
//
|
||||
// which is just ordinary 256-bit multiplication.
|
||||
result = prod0 * inv;
|
||||
}
|
||||
}
|
||||
|
||||
/// @notice Multiply a Fraction and a uint256 using full precision
|
||||
function mul(ISwapAdapterTypes.Fraction memory rational, uint256 y)
|
||||
internal
|
||||
pure
|
||||
returns (uint256 result)
|
||||
{
|
||||
return mulDiv(rational.numerator, y, rational.denominator);
|
||||
}
|
||||
|
||||
/// @notice Full-precision mulDiv: computes floor(x * y / denominator)
|
||||
/// with 512-bit intermediate precision to avoid overflow.
|
||||
///
|
||||
/// @dev Reverts if `denominator == 0` or the exact result >= 2^256.
|
||||
/// The implementation mirrors the 512/256 division flow used by
|
||||
/// `toQ128x128(uint256,uint256)`, but with a general multiplicand `y`
|
||||
/// instead of the fixed 2^128 shift.
|
||||
function mulDiv(uint256 x, uint256 y, uint256 denominator)
|
||||
internal
|
||||
pure
|
||||
returns (uint256 result)
|
||||
{
|
||||
require(denominator != 0, "mulDiv: div by zero");
|
||||
|
||||
// Compute the 512-bit product [prod1 prod0] = x * y.
|
||||
// mm = (x * y) mod (2^256 - 1)
|
||||
// prod0 = (x * y) mod 2^256
|
||||
// prod1 = (x * y - prod0 - (mm < prod0 ? 1 : 0)) / 2^256
|
||||
uint256 prod0;
|
||||
uint256 prod1;
|
||||
assembly {
|
||||
let mm := mulmod(x, y, not(0))
|
||||
prod0 := mul(x, y)
|
||||
prod1 := sub(sub(mm, prod0), lt(mm, prod0))
|
||||
}
|
||||
|
||||
// If the high 256 bits are zero, we can do a simple 256-bit division.
|
||||
if (prod1 == 0) {
|
||||
unchecked {
|
||||
return prod0 / denominator;
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure result < 2^256. This is equivalent to requiring
|
||||
// denominator > prod1.
|
||||
require(denominator > prod1, "mulDiv: overflow");
|
||||
|
||||
// Make division exact by subtracting the remainder from [prod1 prod0].
|
||||
uint256 remainder;
|
||||
assembly {
|
||||
remainder := mulmod(x, y, denominator)
|
||||
// Subtract remainder from the low part; if it underflows, borrow 1
|
||||
// from the high part.
|
||||
let borrow := lt(prod0, remainder)
|
||||
prod0 := sub(prod0, remainder)
|
||||
prod1 := sub(prod1, borrow)
|
||||
}
|
||||
|
||||
// Factor powers of two out of denominator to simplify the division.
|
||||
uint256 twos;
|
||||
unchecked {
|
||||
// largest power of two divisor of denominator
|
||||
twos = denominator & (~denominator + 1);
|
||||
}
|
||||
|
||||
assembly {
|
||||
// Divide denominator by twos.
|
||||
denominator := div(denominator, twos)
|
||||
|
||||
// Divide the low word by twos.
|
||||
prod0 := div(prod0, twos)
|
||||
|
||||
// Compute twos = 2^256 / twos.
|
||||
twos := add(div(sub(0, twos), twos), 1)
|
||||
|
||||
// Shift bits from the high word into the low word.
|
||||
prod0 := or(prod0, mul(prod1, twos))
|
||||
}
|
||||
|
||||
// Compute modular inverse of the (now odd) denominator modulo 2^256
|
||||
// via Newton-Raphson iterations.
|
||||
// `inv` is correct to four bits, so we require six iterations
|
||||
// to achieve 256-bit precision.
|
||||
unchecked {
|
||||
uint256 inv = (3 * denominator) ^ 2;
|
||||
inv *= 2 - denominator * inv; // 2^8
|
||||
inv *= 2 - denominator * inv; // 2^16
|
||||
inv *= 2 - denominator * inv; // 2^32
|
||||
inv *= 2 - denominator * inv; // 2^64
|
||||
inv *= 2 - denominator * inv; // 2^128
|
||||
inv *= 2 - denominator * inv; // 2^256
|
||||
|
||||
// Exact division: result = prod0 * inv mod 2^256
|
||||
result = prod0 * inv;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
18
evm/src/liquidityparty/Funding.sol
Normal file
18
evm/src/liquidityparty/Funding.sol
Normal file
@@ -0,0 +1,18 @@
|
||||
// SPDX-License-Identifier: UNLICENSED
|
||||
pragma solidity ^0.8.27;
|
||||
|
||||
library Funding {
|
||||
/// @notice a constant passed to swap as the fundingSelector to indicate
|
||||
/// that the payer has used regular ERC20 approvals to allow the pool to
|
||||
/// move the necessary input tokens.
|
||||
// Slither analysis of this line is literally wrong and broken. The extra zero digits are REQUIRED by Solidity since it is a bytes4 literal.
|
||||
// slither-disable-next-line too-many-digits
|
||||
bytes4 internal constant APPROVALS = 0x00000000;
|
||||
|
||||
/// @notice a constant passed to swap as the fundingSelector to indicate
|
||||
/// that the payer has already sent sufficient input tokens to the pool
|
||||
/// before calling swap, so no movement of input tokens is required.
|
||||
// Slither analysis of this line is literally wrong and broken. The extra zero digits are REQUIRED by Solidity since it is a bytes4 literal.
|
||||
// slither-disable-next-line too-many-digits
|
||||
bytes4 internal constant PREFUNDING = 0x00000001;
|
||||
}
|
||||
24
evm/src/liquidityparty/IPartyInfo.sol
Normal file
24
evm/src/liquidityparty/IPartyInfo.sol
Normal file
@@ -0,0 +1,24 @@
|
||||
// SPDX-License-Identifier: UNLICENSED
|
||||
pragma solidity ^0.8.27;
|
||||
|
||||
import {IPartyPool} from "./IPartyPool.sol";
|
||||
|
||||
interface IPartyInfo {
|
||||
/// @notice returns true iff the pool is not killed and has been initialized
|
||||
/// with liquidity.
|
||||
function working(IPartyPool pool) external view returns (bool);
|
||||
|
||||
/// @notice Infinitesimal out-per-in marginal price for swap base->quote as
|
||||
/// Q128.128, not adjusted for token decimals.
|
||||
/// @dev Returns p_base / p_quote in Q128.128 format, scaled to external
|
||||
/// units by (denom_quote / denom_base). This aligns with the swap kernel so
|
||||
/// that, fee-free, avg(out/in) ≤ price(base, quote) for exact-in trades.
|
||||
/// @param baseTokenIndex index of the input (base) asset
|
||||
/// @param quoteTokenIndex index of the output (quote) asset
|
||||
/// @return price Q128.128 value equal to out-per-in (j per i)
|
||||
function price(
|
||||
IPartyPool pool,
|
||||
uint256 baseTokenIndex,
|
||||
uint256 quoteTokenIndex
|
||||
) external view returns (uint256);
|
||||
}
|
||||
18
evm/src/liquidityparty/IPartyPlanner.sol
Normal file
18
evm/src/liquidityparty/IPartyPlanner.sol
Normal file
@@ -0,0 +1,18 @@
|
||||
// SPDX-License-Identifier: UNLICENSED
|
||||
pragma solidity ^0.8.27;
|
||||
|
||||
import {IPartyPool} from "./IPartyPool.sol";
|
||||
|
||||
/// @title IPartyPlanner
|
||||
/// @notice Interface for factory contract for creating and tracking PartyPool
|
||||
/// instances
|
||||
interface IPartyPlanner {
|
||||
/// @notice Retrieves a page of pool addresses
|
||||
/// @param offset Starting index for pagination
|
||||
/// @param limit Maximum number of items to return
|
||||
/// @return pools Array of pool addresses for the requested page
|
||||
function getAllPools(uint256 offset, uint256 limit)
|
||||
external
|
||||
view
|
||||
returns (IPartyPool[] memory pools);
|
||||
}
|
||||
86
evm/src/liquidityparty/IPartyPool.sol
Normal file
86
evm/src/liquidityparty/IPartyPool.sol
Normal file
@@ -0,0 +1,86 @@
|
||||
pragma solidity ^0.8.27;
|
||||
|
||||
/// @title PartyPool - LMSR-backed multi-asset pool with LP ERC20 token
|
||||
/// @notice A multi-asset liquidity pool backed by the LMSRStabilized pricing
|
||||
/// model. The pool issues an ERC20 LP token representing proportional
|
||||
/// ownership.
|
||||
/// It supports:
|
||||
/// - Proportional minting and burning of LP _tokens,
|
||||
/// - Single-token mint (swapMint) and single-asset withdrawal (burnSwap),
|
||||
/// - Exact-input swaps and swaps-to-price-limits,
|
||||
/// - Flash loans via a callback interface.
|
||||
interface IPartyPool {
|
||||
/// @notice If a security problem is found, the vault owner may call this
|
||||
/// function to permanently disable swap and mint functionality, leaving
|
||||
/// only burns (withdrawals) working.
|
||||
function killed() external view returns (bool);
|
||||
|
||||
/// @notice Returns the number of tokens (n) in the pool.
|
||||
function numTokens() external view returns (uint256);
|
||||
|
||||
/// @notice Returns the list of all token addresses in the pool (copy).
|
||||
function allTokens() external view returns (address[] memory);
|
||||
|
||||
/// @notice External view to quote exact-in swap amounts (gross input incl.
|
||||
/// fee and output), matching swap() computations @param inputTokenIndex
|
||||
/// index of input token
|
||||
/// @param outputTokenIndex index of output token
|
||||
/// @param maxAmountIn maximum gross input allowed (inclusive of fee)
|
||||
/// @param limitPrice maximum acceptable marginal price (pass 0 to ignore)
|
||||
/// @return amountIn gross input amount to transfer (includes fee),
|
||||
/// amountOut output amount user would receive, inFee fee taken from input
|
||||
/// amount
|
||||
function swapAmounts(
|
||||
uint256 inputTokenIndex,
|
||||
uint256 outputTokenIndex,
|
||||
uint256 maxAmountIn,
|
||||
int128 limitPrice
|
||||
) external view returns (uint256 amountIn, uint256 amountOut, uint256 inFee);
|
||||
|
||||
/// @notice Swap input token inputTokenIndex -> token outputTokenIndex.
|
||||
/// Payer must approve token inputTokenIndex. @param payer address of the
|
||||
/// account that pays for the swap
|
||||
/// @param fundingSelector If set to USE_APPROVALS, then the payer must use
|
||||
/// regular ERC20 approvals to authorize the pool to move the required input
|
||||
/// amount. If this fundingSelector is USE_PREFUNDING, then all of the input
|
||||
/// amount is expected to have already been sent to the pool and no
|
||||
/// additional transfers are needed. Refunds of excess input amount are NOT
|
||||
/// provided and it is illegal to use this funding method with a limit
|
||||
/// price. Otherwise, for any other fundingSelector value, a callback style
|
||||
/// funding mechanism is used where the given selector is invoked on the
|
||||
/// payer, passing the arguments of (address inputToken, uint256
|
||||
/// inputAmount). The callback function must send the given amount of input
|
||||
/// coin to the pool in order to continue the swap transaction, otherwise
|
||||
/// "Insufficient funds" is thrown. @param receiver address that will
|
||||
/// receive the output tokens
|
||||
/// @param inputTokenIndex index of input asset
|
||||
/// @param outputTokenIndex index of output asset
|
||||
/// @param maxAmountIn maximum amount of token inputTokenIndex (uint256) to
|
||||
/// transfer in (inclusive of fees) @param limitPrice maximum acceptable
|
||||
/// marginal price (64.64 fixed point). Pass 0 to ignore.
|
||||
/// @param deadline timestamp after which the transaction will revert. Pass
|
||||
/// 0 to ignore. @param unwrap If true, then any output of wrapper token
|
||||
/// will be unwrapped and native ETH sent to the receiver.
|
||||
/// @param cbData callback data if fundingSelector is of the callback type.
|
||||
/// @return amountIn actual input used (uint256), amountOut actual output
|
||||
/// sent (uint256), inFee fee taken from the input (uint256)
|
||||
function swap(
|
||||
address payer,
|
||||
bytes4 fundingSelector,
|
||||
address receiver,
|
||||
uint256 inputTokenIndex,
|
||||
uint256 outputTokenIndex,
|
||||
uint256 maxAmountIn,
|
||||
int128 limitPrice,
|
||||
uint256 deadline,
|
||||
bool unwrap,
|
||||
bytes memory cbData
|
||||
)
|
||||
external
|
||||
payable
|
||||
returns (uint256 amountIn, uint256 amountOut, uint256 inFee);
|
||||
|
||||
/// @notice Effective combined fee in ppm for the given asset pair (i as
|
||||
/// input, j as output).
|
||||
function fee(uint256 i, uint256 j) external view returns (uint256);
|
||||
}
|
||||
258
evm/src/liquidityparty/LiquidityPartySwapAdapter.sol
Normal file
258
evm/src/liquidityparty/LiquidityPartySwapAdapter.sol
Normal file
@@ -0,0 +1,258 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
pragma solidity ^0.8.27;
|
||||
|
||||
import {
|
||||
IERC20
|
||||
} from "../../lib/openzeppelin-contracts/contracts/token/ERC20/IERC20.sol";
|
||||
import {
|
||||
SafeERC20
|
||||
} from "../../lib/openzeppelin-contracts/contracts/token/ERC20/utils/SafeERC20.sol";
|
||||
import {ISwapAdapter} from "../interfaces/ISwapAdapter.sol";
|
||||
import {Funding} from "./Funding.sol";
|
||||
import {IPartyInfo} from "./IPartyInfo.sol";
|
||||
import {IPartyPlanner} from "./IPartyPlanner.sol";
|
||||
import {IPartyPool} from "./IPartyPool.sol";
|
||||
import {console2} from "../../lib/forge-std/src/console2.sol";
|
||||
|
||||
contract LiquidityPartySwapAdapter is ISwapAdapter {
|
||||
using SafeERC20 for IERC20;
|
||||
|
||||
// Forge lint wants immutables to be all caps. Slither wants them to be
|
||||
// mixed case. Why do we care about pedantic linters? The Solidity style
|
||||
// guide mentions "constants" but never "immutables." Faced with an
|
||||
// irresolvable linter conflict, I chose to disable the slither linter,
|
||||
// since its detection of immutables as constants seems to be broken.
|
||||
// slither-disable-next-line naming-convention
|
||||
IPartyPlanner public immutable PLANNER;
|
||||
// slither-disable-next-line naming-convention
|
||||
IPartyInfo public immutable INFO;
|
||||
|
||||
constructor(IPartyPlanner planner, IPartyInfo info) {
|
||||
PLANNER = planner;
|
||||
INFO = info;
|
||||
}
|
||||
|
||||
function price(
|
||||
bytes32 poolId,
|
||||
address sellToken,
|
||||
address buyToken,
|
||||
uint256[] memory specifiedAmounts
|
||||
) external view override returns (Fraction[] memory prices) {
|
||||
IPartyPool pool = _poolFromId(poolId);
|
||||
(uint256 indexIn, uint256 indexOut) =
|
||||
_tokenIndexes(pool, sellToken, buyToken);
|
||||
prices = new Fraction[](specifiedAmounts.length);
|
||||
for (uint256 i = 0; i < specifiedAmounts.length; i++) {
|
||||
uint256 amount = specifiedAmounts[i];
|
||||
if (amount == 0) {
|
||||
// Marginal price support
|
||||
prices[i] = _marginalPrice(pool, indexIn, indexOut);
|
||||
} else {
|
||||
// Regular slippage calculation
|
||||
// slither-disable-next-line unused-return calls-loop
|
||||
(
|
||||
uint256 amountIn,
|
||||
uint256 amountOut, /*uint256 inFee*/
|
||||
) = pool.swapAmounts(indexIn, indexOut, amount, 0);
|
||||
prices[i].numerator = amountOut;
|
||||
prices[i].denominator = amountIn;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function swap(
|
||||
bytes32 poolId,
|
||||
address sellToken,
|
||||
address buyToken,
|
||||
OrderSide,
|
||||
/*side*/
|
||||
uint256 specifiedAmount
|
||||
) external returns (Trade memory trade) {
|
||||
// Setup
|
||||
address swapper = msg.sender;
|
||||
IPartyPool pool = _poolFromId(poolId);
|
||||
// This require should never trigger if the substreams module correctly
|
||||
// removes components that were killed.
|
||||
if (!INFO.working(pool)) {
|
||||
revert Unavailable("LiqP pool not working");
|
||||
}
|
||||
(uint256 indexIn, uint256 indexOut) =
|
||||
_tokenIndexes(pool, sellToken, buyToken);
|
||||
|
||||
// Transfer and Swap
|
||||
uint256 startingGas = gasleft();
|
||||
IERC20(sellToken)
|
||||
.safeTransferFrom(swapper, address(pool), specifiedAmount);
|
||||
// slither-disable-next-line unused-return
|
||||
try pool.swap(
|
||||
address(0),
|
||||
Funding.PREFUNDING,
|
||||
swapper,
|
||||
indexIn,
|
||||
indexOut,
|
||||
specifiedAmount,
|
||||
0,
|
||||
0,
|
||||
false,
|
||||
""
|
||||
) returns (
|
||||
uint256 amountIn, uint256 amountOut, uint256 inFee
|
||||
) {
|
||||
uint256 endingGas = gasleft();
|
||||
uint256 gasUsed = startingGas - endingGas;
|
||||
Fraction memory poolPrice = _marginalPrice(pool, indexIn, indexOut);
|
||||
console2.log("Successfully swapped", amountOut);
|
||||
// forge-lint: disable-next-line(named-struct-fields)
|
||||
return Trade(amountOut, gasUsed, poolPrice);
|
||||
} catch (bytes memory reason) {
|
||||
bytes32 hash = keccak256(reason);
|
||||
if (hash == keccak256("swap: input too small after fee")) {
|
||||
revert TooSmall(0);
|
||||
} else if (
|
||||
hash == keccak256("swap: transfer exceeds max")
|
||||
|| hash
|
||||
== keccak256("LMSR: a/b too large (would overflow exp)")
|
||||
|| hash == keccak256("swap: transfer exceeds max")
|
||||
) {
|
||||
revert LimitExceeded(0); // todo size
|
||||
} else if (hash == keccak256("killed")) {
|
||||
revert Unavailable("pool has been permanently killed");
|
||||
} else if (hash == keccak256("LMSR: size metric zero")) {
|
||||
revert Unavailable("pool currently has no LP assets");
|
||||
} else if (hash == keccak256("LMSR: limitPrice <= current price")) {
|
||||
revert InvalidOrder("limit price is below current price");
|
||||
} else if (
|
||||
hash == keccak256("LMSR: ratio<=0") // invalid limit price
|
||||
) {
|
||||
revert InvalidOrder("limit price cannot be negative");
|
||||
} else {
|
||||
console2.log("Unhandled error", string(reason));
|
||||
// re-raise
|
||||
assembly {
|
||||
revert(add(reason, 0x20), mload(reason))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function getLimits(bytes32 poolId, address sellToken, address buyToken)
|
||||
external
|
||||
view
|
||||
returns (uint256[] memory limits)
|
||||
{
|
||||
// We arbitrarily limit the amounts like Uniswap V2 does, to make the
|
||||
// test cases work. There is no theoretical limit on the input amount.
|
||||
// forge-lint: disable-next-line(unsafe-typecast)
|
||||
address pool = address(bytes20(poolId));
|
||||
limits = new uint256[](2);
|
||||
|
||||
// input token limit: Theoretically unlimited, but artificially limited
|
||||
// here to appease Tycho's test cases. Instead of estimating actual
|
||||
// input limits based on a maximum target slippage, we merely return the
|
||||
// current
|
||||
// inventory of input token. Even for large stablecoin pools with a
|
||||
// kappa near 1, this input amount should result in an "unreasonably"
|
||||
// high slippage:
|
||||
// Pool Size => Slippage for inputAmount=reserveBalance and kappa=1
|
||||
// 2 => 33.7%
|
||||
// 10 => 9.2%
|
||||
// 25 => 3.9%
|
||||
// 50 => 2.1%
|
||||
// See the commented-out method below for an exact computation of the
|
||||
// maximum input amount for a given pool configuration and target
|
||||
// slippage.
|
||||
limits[0] = IERC20(sellToken).balanceOf(pool);
|
||||
|
||||
// output token limit: the pool's current balance (an overestimate)
|
||||
limits[1] = IERC20(buyToken).balanceOf(pool);
|
||||
}
|
||||
|
||||
function getCapabilities(
|
||||
bytes32,
|
||||
/*poolId*/
|
||||
address,
|
||||
/*sellToken*/
|
||||
address /*buyToken*/
|
||||
)
|
||||
external
|
||||
pure
|
||||
returns (Capability[] memory capabilities)
|
||||
{
|
||||
capabilities = new Capability[](3);
|
||||
capabilities[0] = Capability.SellOrder;
|
||||
capabilities[1] = Capability.PriceFunction;
|
||||
capabilities[2] = Capability.MarginalPrice;
|
||||
return capabilities;
|
||||
}
|
||||
|
||||
function getTokens(bytes32 poolId)
|
||||
external
|
||||
view
|
||||
returns (address[] memory tokens)
|
||||
{
|
||||
IPartyPool pool = _poolFromId(poolId);
|
||||
return pool.allTokens();
|
||||
}
|
||||
|
||||
function getPoolIds(uint256 offset, uint256 limit)
|
||||
external
|
||||
view
|
||||
returns (bytes32[] memory ids)
|
||||
{
|
||||
IPartyPool[] memory pools = PLANNER.getAllPools(offset, limit);
|
||||
ids = new bytes32[](pools.length);
|
||||
for (uint256 i = 0; i < pools.length; i++) {
|
||||
ids[i] = bytes32(uint256(uint160(address(pools[i]))));
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Internal Helpers
|
||||
//
|
||||
|
||||
uint256 private constant NONE = type(uint256).max;
|
||||
|
||||
/// @dev Liquidity Party pools identify tokens by index rather than address,
|
||||
/// saving 5200 gas per swap.
|
||||
function _tokenIndexes(IPartyPool pool, address sellToken, address buyToken)
|
||||
internal
|
||||
view
|
||||
returns (uint256 indexIn, uint256 indexOut)
|
||||
{
|
||||
indexIn = NONE;
|
||||
indexOut = NONE;
|
||||
address[] memory tokens = pool.allTokens();
|
||||
uint256 numTokens = pool.numTokens();
|
||||
for (uint256 i = 0; i < numTokens; i++) {
|
||||
if (tokens[i] == sellToken) {
|
||||
indexIn = i;
|
||||
} else if (tokens[i] == buyToken) {
|
||||
indexOut = i;
|
||||
}
|
||||
}
|
||||
// This should never happen if the token metadata was correctly loaded
|
||||
// by substreams
|
||||
require(indexIn != NONE && indexOut != NONE, "tokens not in pool");
|
||||
}
|
||||
|
||||
function _marginalPrice(IPartyPool pool, uint256 indexIn, uint256 indexOut)
|
||||
internal
|
||||
view
|
||||
returns (Fraction memory poolPrice)
|
||||
{
|
||||
// Liquidity Party prices are Q128.128 fixed point format
|
||||
// slither-disable-next-line calls-loop
|
||||
uint256 price128x128 = INFO.price(pool, indexIn, indexOut);
|
||||
uint256 feePpm = pool.fee(indexIn, indexOut);
|
||||
// price128x128 *= 1_000_000 - feePpm;
|
||||
// price128x128 /= 1_000_000;
|
||||
// forge-lint: disable-next-line(unsafe-typecast,named-struct-fields)
|
||||
return Fraction(price128x128, 1 << 128);
|
||||
}
|
||||
|
||||
function _poolFromId(bytes32 poolId) internal pure returns (IPartyPool) {
|
||||
// forge-lint: disable-next-line(unsafe-typecast)
|
||||
return IPartyPool(address(bytes20(poolId)));
|
||||
}
|
||||
}
|
||||
|
||||
25
evm/src/liquidityparty/manifest.yaml
Normal file
25
evm/src/liquidityparty/manifest.yaml
Normal file
@@ -0,0 +1,25 @@
|
||||
author:
|
||||
name: Tim Olson
|
||||
email: tim@dexorder.com
|
||||
|
||||
constants:
|
||||
# This is our median gas cost for a 20-asset pool. Gas varies by pool size from 120k to 200k.
|
||||
protocol_gas: 147238
|
||||
capabilities:
|
||||
- SellSide
|
||||
- PriceFunction
|
||||
- MarginalPrice
|
||||
|
||||
contract: LiquidityPartySwapAdapter.sol
|
||||
|
||||
# Deployment instances used to generate chain specific bytecode.
|
||||
instances:
|
||||
- chain:
|
||||
name: sepolia
|
||||
id: 11155111
|
||||
arguments:
|
||||
- "0x77C29B1790D18A3AD269BcE09b7dB1074911Dcb6" # PartyPlanner
|
||||
- "0x784BA6cD19B484bEE9Cee880B18b57fC6e8b2D5c" # PartyInfo
|
||||
|
||||
# We do implement getPoolIds() and getTokens(), so explicit swap tests are not needed.
|
||||
tests: {}
|
||||
@@ -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,51 +106,94 @@ 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];
|
||||
) 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(
|
||||
assertGeTol(
|
||||
executedPrice,
|
||||
priceAtAmount,
|
||||
"Price should be greated than executed price."
|
||||
toleranceDenominator,
|
||||
"Price should be greater than executed price."
|
||||
);
|
||||
assertGt(
|
||||
assertGtTol(
|
||||
executedPrice,
|
||||
priceAfterSwap,
|
||||
toleranceDenominator,
|
||||
"Executed price should be greater than price after swap."
|
||||
);
|
||||
assertGt(
|
||||
assertGtTol(
|
||||
priceAtZero,
|
||||
executedPrice,
|
||||
"Price should be greated than price after swap."
|
||||
toleranceDenominator,
|
||||
"Price should be greater than price after swap."
|
||||
);
|
||||
} else {
|
||||
assertGe(
|
||||
assertGeTol(
|
||||
priceAtZero,
|
||||
priceAfterSwap,
|
||||
toleranceDenominator,
|
||||
"Executed price should be or equal to price after swap."
|
||||
);
|
||||
assertGe(
|
||||
assertGeTol(
|
||||
priceAtZero,
|
||||
priceAtAmount,
|
||||
toleranceDenominator,
|
||||
"Executed price should be or equal to price after swap."
|
||||
);
|
||||
assertGe(
|
||||
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) {
|
||||
} catch (bytes memory reason) {
|
||||
(bool isTooSmall, uint256 lowerLimit) = decodeTooSmallError(reason);
|
||||
(bool isLimitExceeded, uint256 limit) =
|
||||
decodeLimitExceededError(reason);
|
||||
|
||||
if (isLimitExceeded) {
|
||||
supportsLimitExceeded = true;
|
||||
console2.log(
|
||||
"TEST: Expected error when fetching price above limit: %s", s
|
||||
"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) {
|
||||
} 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);
|
||||
}
|
||||
}
|
||||
@@ -1,2 +1,11 @@
|
||||
export PATH := /home/linuxbrew/.linuxbrew/bin:$(PATH)
|
||||
|
||||
all: ethereum
|
||||
|
||||
build:
|
||||
cargo build --target wasm32-unknown-unknown --release
|
||||
|
||||
ethereum: build
|
||||
substreams pack ethereum-liquidityparty.yaml -o ethereum-liquidityparty.spkg
|
||||
|
||||
.PHONY: build ethereum
|
||||
|
||||
@@ -2,15 +2,15 @@
|
||||
{
|
||||
"type": "constructor",
|
||||
"inputs": [
|
||||
{
|
||||
"name": "swapImpl_",
|
||||
"type": "address",
|
||||
"internalType": "contract PartyPoolSwapImpl"
|
||||
},
|
||||
{
|
||||
"name": "mintImpl",
|
||||
"type": "address",
|
||||
"internalType": "contract PartyPoolMintImpl"
|
||||
},
|
||||
{
|
||||
"name": "swapImpl_",
|
||||
"type": "address",
|
||||
"internalType": "contract PartyPoolSwapImpl"
|
||||
}
|
||||
],
|
||||
"stateMutability": "nonpayable"
|
||||
@@ -54,7 +54,7 @@
|
||||
"internalType": "uint256"
|
||||
},
|
||||
{
|
||||
"name": "inputTokenIndex",
|
||||
"name": "outputTokenIndex",
|
||||
"type": "uint256",
|
||||
"internalType": "uint256"
|
||||
}
|
||||
@@ -64,6 +64,11 @@
|
||||
"name": "amountOut",
|
||||
"type": "uint256",
|
||||
"internalType": "uint256"
|
||||
},
|
||||
{
|
||||
"name": "outFee",
|
||||
"type": "uint256",
|
||||
"internalType": "uint256"
|
||||
}
|
||||
],
|
||||
"stateMutability": "view"
|
||||
@@ -97,30 +102,6 @@
|
||||
],
|
||||
"stateMutability": "view"
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
"name": "flashRepaymentAmounts",
|
||||
"inputs": [
|
||||
{
|
||||
"name": "pool",
|
||||
"type": "address",
|
||||
"internalType": "contract IPartyPool"
|
||||
},
|
||||
{
|
||||
"name": "loanAmounts",
|
||||
"type": "uint256[]",
|
||||
"internalType": "uint256[]"
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "repaymentAmounts",
|
||||
"type": "uint256[]",
|
||||
"internalType": "uint256[]"
|
||||
}
|
||||
],
|
||||
"stateMutability": "view"
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
"name": "maxFlashLoan",
|
||||
@@ -216,8 +197,8 @@
|
||||
"outputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "int128",
|
||||
"internalType": "int128"
|
||||
"type": "uint256",
|
||||
"internalType": "uint256"
|
||||
}
|
||||
],
|
||||
"stateMutability": "view"
|
||||
@@ -249,12 +230,12 @@
|
||||
"internalType": "uint256"
|
||||
},
|
||||
{
|
||||
"name": "fee",
|
||||
"name": "lpMinted",
|
||||
"type": "uint256",
|
||||
"internalType": "uint256"
|
||||
},
|
||||
{
|
||||
"name": "lpMinted",
|
||||
"name": "inFee",
|
||||
"type": "uint256",
|
||||
"internalType": "uint256"
|
||||
}
|
||||
@@ -298,11 +279,30 @@
|
||||
"internalType": "uint256"
|
||||
},
|
||||
{
|
||||
"name": "fee",
|
||||
"name": "inFee",
|
||||
"type": "uint256",
|
||||
"internalType": "uint256"
|
||||
}
|
||||
],
|
||||
"stateMutability": "view"
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
"name": "working",
|
||||
"inputs": [
|
||||
{
|
||||
"name": "pool",
|
||||
"type": "address",
|
||||
"internalType": "contract IPartyPool"
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "bool",
|
||||
"internalType": "bool"
|
||||
}
|
||||
],
|
||||
"stateMutability": "view"
|
||||
}
|
||||
]
|
||||
@@ -23,14 +23,14 @@
|
||||
"internalType": "contract PartyPoolMintImpl"
|
||||
},
|
||||
{
|
||||
"name": "deployer_",
|
||||
"name": "poolInitCodeStorage_",
|
||||
"type": "address",
|
||||
"internalType": "contract IPartyPoolDeployer"
|
||||
"internalType": "contract PartyPoolInitCode"
|
||||
},
|
||||
{
|
||||
"name": "balancedPairDeployer_",
|
||||
"name": "balancedPairInitCodeStorage_",
|
||||
"type": "address",
|
||||
"internalType": "contract IPartyPoolDeployer"
|
||||
"internalType": "contract PartyPoolBalancedPairInitCode"
|
||||
},
|
||||
{
|
||||
"name": "protocolFeePpm_",
|
||||
@@ -174,12 +174,91 @@
|
||||
"internalType": "contract IERC20[]"
|
||||
},
|
||||
{
|
||||
"name": "bases_",
|
||||
"name": "kappa_",
|
||||
"type": "int128",
|
||||
"internalType": "int128"
|
||||
},
|
||||
{
|
||||
"name": "swapFeesPpm_",
|
||||
"type": "uint256[]",
|
||||
"internalType": "uint256[]"
|
||||
},
|
||||
{
|
||||
"name": "kappa_",
|
||||
"name": "flashFeePpm_",
|
||||
"type": "uint256",
|
||||
"internalType": "uint256"
|
||||
},
|
||||
{
|
||||
"name": "stable_",
|
||||
"type": "bool",
|
||||
"internalType": "bool"
|
||||
},
|
||||
{
|
||||
"name": "payer",
|
||||
"type": "address",
|
||||
"internalType": "address"
|
||||
},
|
||||
{
|
||||
"name": "receiver",
|
||||
"type": "address",
|
||||
"internalType": "address"
|
||||
},
|
||||
{
|
||||
"name": "initialDeposits",
|
||||
"type": "uint256[]",
|
||||
"internalType": "uint256[]"
|
||||
},
|
||||
{
|
||||
"name": "initialLpAmount",
|
||||
"type": "uint256",
|
||||
"internalType": "uint256"
|
||||
},
|
||||
{
|
||||
"name": "deadline",
|
||||
"type": "uint256",
|
||||
"internalType": "uint256"
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "pool",
|
||||
"type": "address",
|
||||
"internalType": "contract IPartyPool"
|
||||
},
|
||||
{
|
||||
"name": "lpAmount",
|
||||
"type": "uint256",
|
||||
"internalType": "uint256"
|
||||
}
|
||||
],
|
||||
"stateMutability": "nonpayable"
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
"name": "newPool",
|
||||
"inputs": [
|
||||
{
|
||||
"name": "name_",
|
||||
"type": "string",
|
||||
"internalType": "string"
|
||||
},
|
||||
{
|
||||
"name": "symbol_",
|
||||
"type": "string",
|
||||
"internalType": "string"
|
||||
},
|
||||
{
|
||||
"name": "tokens_",
|
||||
"type": "address[]",
|
||||
"internalType": "contract IERC20[]"
|
||||
},
|
||||
{
|
||||
"name": "tradeFrac_",
|
||||
"type": "int128",
|
||||
"internalType": "int128"
|
||||
},
|
||||
{
|
||||
"name": "targetSlippage_",
|
||||
"type": "int128",
|
||||
"internalType": "int128"
|
||||
},
|
||||
@@ -258,17 +337,7 @@
|
||||
"internalType": "contract IERC20[]"
|
||||
},
|
||||
{
|
||||
"name": "bases_",
|
||||
"type": "uint256[]",
|
||||
"internalType": "uint256[]"
|
||||
},
|
||||
{
|
||||
"name": "tradeFrac_",
|
||||
"type": "int128",
|
||||
"internalType": "int128"
|
||||
},
|
||||
{
|
||||
"name": "targetSlippage_",
|
||||
"name": "kappa_",
|
||||
"type": "int128",
|
||||
"internalType": "int128"
|
||||
},
|
||||
@@ -340,6 +409,86 @@
|
||||
],
|
||||
"stateMutability": "view"
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
"name": "params",
|
||||
"inputs": [],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "tuple",
|
||||
"internalType": "struct IPartyPoolDeployer.DeployParams",
|
||||
"components": [
|
||||
{
|
||||
"name": "nonce",
|
||||
"type": "bytes32",
|
||||
"internalType": "bytes32"
|
||||
},
|
||||
{
|
||||
"name": "owner",
|
||||
"type": "address",
|
||||
"internalType": "address"
|
||||
},
|
||||
{
|
||||
"name": "name",
|
||||
"type": "string",
|
||||
"internalType": "string"
|
||||
},
|
||||
{
|
||||
"name": "symbol",
|
||||
"type": "string",
|
||||
"internalType": "string"
|
||||
},
|
||||
{
|
||||
"name": "tokens",
|
||||
"type": "address[]",
|
||||
"internalType": "contract IERC20[]"
|
||||
},
|
||||
{
|
||||
"name": "kappa",
|
||||
"type": "int128",
|
||||
"internalType": "int128"
|
||||
},
|
||||
{
|
||||
"name": "fees",
|
||||
"type": "uint256[]",
|
||||
"internalType": "uint256[]"
|
||||
},
|
||||
{
|
||||
"name": "flashFeePpm",
|
||||
"type": "uint256",
|
||||
"internalType": "uint256"
|
||||
},
|
||||
{
|
||||
"name": "protocolFeePpm",
|
||||
"type": "uint256",
|
||||
"internalType": "uint256"
|
||||
},
|
||||
{
|
||||
"name": "protocolFeeAddress",
|
||||
"type": "address",
|
||||
"internalType": "address"
|
||||
},
|
||||
{
|
||||
"name": "wrapper",
|
||||
"type": "address",
|
||||
"internalType": "contract NativeWrapper"
|
||||
},
|
||||
{
|
||||
"name": "swapImpl",
|
||||
"type": "address",
|
||||
"internalType": "contract PartyPoolSwapImpl"
|
||||
},
|
||||
{
|
||||
"name": "mintImpl",
|
||||
"type": "address",
|
||||
"internalType": "contract PartyPoolMintImpl"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"stateMutability": "view"
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
"name": "poolCount",
|
||||
@@ -405,6 +554,19 @@
|
||||
"outputs": [],
|
||||
"stateMutability": "nonpayable"
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
"name": "setProtocolFeeAddress",
|
||||
"inputs": [
|
||||
{
|
||||
"name": "feeAddress",
|
||||
"type": "address",
|
||||
"internalType": "address"
|
||||
}
|
||||
],
|
||||
"outputs": [],
|
||||
"stateMutability": "nonpayable"
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
"name": "swapImpl",
|
||||
|
||||
@@ -1,73 +1,7 @@
|
||||
[
|
||||
{
|
||||
"type": "constructor",
|
||||
"inputs": [
|
||||
{
|
||||
"name": "owner_",
|
||||
"type": "address",
|
||||
"internalType": "address"
|
||||
},
|
||||
{
|
||||
"name": "name_",
|
||||
"type": "string",
|
||||
"internalType": "string"
|
||||
},
|
||||
{
|
||||
"name": "symbol_",
|
||||
"type": "string",
|
||||
"internalType": "string"
|
||||
},
|
||||
{
|
||||
"name": "tokens_",
|
||||
"type": "address[]",
|
||||
"internalType": "contract IERC20[]"
|
||||
},
|
||||
{
|
||||
"name": "bases_",
|
||||
"type": "uint256[]",
|
||||
"internalType": "uint256[]"
|
||||
},
|
||||
{
|
||||
"name": "kappa_",
|
||||
"type": "int128",
|
||||
"internalType": "int128"
|
||||
},
|
||||
{
|
||||
"name": "swapFeePpm_",
|
||||
"type": "uint256",
|
||||
"internalType": "uint256"
|
||||
},
|
||||
{
|
||||
"name": "flashFeePpm_",
|
||||
"type": "uint256",
|
||||
"internalType": "uint256"
|
||||
},
|
||||
{
|
||||
"name": "protocolFeePpm_",
|
||||
"type": "uint256",
|
||||
"internalType": "uint256"
|
||||
},
|
||||
{
|
||||
"name": "protocolFeeAddress_",
|
||||
"type": "address",
|
||||
"internalType": "address"
|
||||
},
|
||||
{
|
||||
"name": "wrapperToken_",
|
||||
"type": "address",
|
||||
"internalType": "contract NativeWrapper"
|
||||
},
|
||||
{
|
||||
"name": "swapImpl_",
|
||||
"type": "address",
|
||||
"internalType": "contract PartyPoolSwapImpl"
|
||||
},
|
||||
{
|
||||
"name": "mintImpl_",
|
||||
"type": "address",
|
||||
"internalType": "contract PartyPoolMintImpl"
|
||||
}
|
||||
],
|
||||
"inputs": [],
|
||||
"stateMutability": "nonpayable"
|
||||
},
|
||||
{
|
||||
@@ -84,11 +18,6 @@
|
||||
"type": "tuple",
|
||||
"internalType": "struct LMSRStabilized.State",
|
||||
"components": [
|
||||
{
|
||||
"name": "nAssets",
|
||||
"type": "uint256",
|
||||
"internalType": "uint256"
|
||||
},
|
||||
{
|
||||
"name": "kappa",
|
||||
"type": "int128",
|
||||
@@ -256,7 +185,7 @@
|
||||
"internalType": "uint256"
|
||||
},
|
||||
{
|
||||
"name": "inputTokenIndex",
|
||||
"name": "outputTokenIndex",
|
||||
"type": "uint256",
|
||||
"internalType": "uint256"
|
||||
},
|
||||
@@ -273,7 +202,12 @@
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "amountOutUint",
|
||||
"name": "amountOut",
|
||||
"type": "uint256",
|
||||
"internalType": "uint256"
|
||||
},
|
||||
{
|
||||
"name": "outFee",
|
||||
"type": "uint256",
|
||||
"internalType": "uint256"
|
||||
}
|
||||
@@ -313,6 +247,43 @@
|
||||
],
|
||||
"stateMutability": "view"
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
"name": "fee",
|
||||
"inputs": [
|
||||
{
|
||||
"name": "i",
|
||||
"type": "uint256",
|
||||
"internalType": "uint256"
|
||||
},
|
||||
{
|
||||
"name": "j",
|
||||
"type": "uint256",
|
||||
"internalType": "uint256"
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "uint256",
|
||||
"internalType": "uint256"
|
||||
}
|
||||
],
|
||||
"stateMutability": "view"
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
"name": "fees",
|
||||
"inputs": [],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "uint256[]",
|
||||
"internalType": "uint256[]"
|
||||
}
|
||||
],
|
||||
"stateMutability": "view"
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
"name": "flashFeePpm",
|
||||
@@ -360,25 +331,6 @@
|
||||
],
|
||||
"stateMutability": "nonpayable"
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
"name": "getToken",
|
||||
"inputs": [
|
||||
{
|
||||
"name": "i",
|
||||
"type": "uint256",
|
||||
"internalType": "uint256"
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "address",
|
||||
"internalType": "contract IERC20"
|
||||
}
|
||||
],
|
||||
"stateMutability": "view"
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
"name": "initialMint",
|
||||
@@ -478,7 +430,7 @@
|
||||
{
|
||||
"name": "",
|
||||
"type": "address",
|
||||
"internalType": "contract PartyPoolMintImpl"
|
||||
"internalType": "address"
|
||||
}
|
||||
],
|
||||
"stateMutability": "view"
|
||||
@@ -555,6 +507,19 @@
|
||||
"outputs": [],
|
||||
"stateMutability": "nonpayable"
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
"name": "setProtocolFeeAddress",
|
||||
"inputs": [
|
||||
{
|
||||
"name": "feeAddress",
|
||||
"type": "address",
|
||||
"internalType": "address"
|
||||
}
|
||||
],
|
||||
"outputs": [],
|
||||
"stateMutability": "nonpayable"
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
"name": "swap",
|
||||
@@ -564,6 +529,11 @@
|
||||
"type": "address",
|
||||
"internalType": "address"
|
||||
},
|
||||
{
|
||||
"name": "fundingSelector",
|
||||
"type": "bytes4",
|
||||
"internalType": "bytes4"
|
||||
},
|
||||
{
|
||||
"name": "receiver",
|
||||
"type": "address",
|
||||
@@ -598,6 +568,11 @@
|
||||
"name": "unwrap",
|
||||
"type": "bool",
|
||||
"internalType": "bool"
|
||||
},
|
||||
{
|
||||
"name": "cbData",
|
||||
"type": "bytes",
|
||||
"internalType": "bytes"
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
@@ -612,7 +587,7 @@
|
||||
"internalType": "uint256"
|
||||
},
|
||||
{
|
||||
"name": "fee",
|
||||
"name": "inFee",
|
||||
"type": "uint256",
|
||||
"internalType": "uint256"
|
||||
}
|
||||
@@ -656,7 +631,7 @@
|
||||
"internalType": "uint256"
|
||||
},
|
||||
{
|
||||
"name": "fee",
|
||||
"name": "inFee",
|
||||
"type": "uint256",
|
||||
"internalType": "uint256"
|
||||
}
|
||||
@@ -665,13 +640,13 @@
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
"name": "swapFeePpm",
|
||||
"name": "swapImpl",
|
||||
"inputs": [],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "uint256",
|
||||
"internalType": "uint256"
|
||||
"type": "address",
|
||||
"internalType": "address"
|
||||
}
|
||||
],
|
||||
"stateMutability": "view"
|
||||
@@ -707,27 +682,24 @@
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "amountInUsed",
|
||||
"type": "uint256",
|
||||
"internalType": "uint256"
|
||||
},
|
||||
{
|
||||
"name": "lpMinted",
|
||||
"type": "uint256",
|
||||
"internalType": "uint256"
|
||||
},
|
||||
{
|
||||
"name": "inFee",
|
||||
"type": "uint256",
|
||||
"internalType": "uint256"
|
||||
}
|
||||
],
|
||||
"stateMutability": "payable"
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
"name": "swapMintImpl",
|
||||
"inputs": [],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "address",
|
||||
"internalType": "contract PartyPoolSwapImpl"
|
||||
}
|
||||
],
|
||||
"stateMutability": "view"
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
"name": "swapToLimit",
|
||||
@@ -737,6 +709,11 @@
|
||||
"type": "address",
|
||||
"internalType": "address"
|
||||
},
|
||||
{
|
||||
"name": "fundingSelector",
|
||||
"type": "bytes4",
|
||||
"internalType": "bytes4"
|
||||
},
|
||||
{
|
||||
"name": "receiver",
|
||||
"type": "address",
|
||||
@@ -766,6 +743,11 @@
|
||||
"name": "unwrap",
|
||||
"type": "bool",
|
||||
"internalType": "bool"
|
||||
},
|
||||
{
|
||||
"name": "cbData",
|
||||
"type": "bytes",
|
||||
"internalType": "bytes"
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
@@ -780,7 +762,7 @@
|
||||
"internalType": "uint256"
|
||||
},
|
||||
{
|
||||
"name": "fee",
|
||||
"name": "inFee",
|
||||
"type": "uint256",
|
||||
"internalType": "uint256"
|
||||
}
|
||||
@@ -800,6 +782,25 @@
|
||||
],
|
||||
"stateMutability": "view"
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
"name": "token",
|
||||
"inputs": [
|
||||
{
|
||||
"name": "i",
|
||||
"type": "uint256",
|
||||
"internalType": "uint256"
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "address",
|
||||
"internalType": "contract IERC20"
|
||||
}
|
||||
],
|
||||
"stateMutability": "view"
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
"name": "totalSupply",
|
||||
@@ -1333,6 +1334,22 @@
|
||||
"name": "FailedCall",
|
||||
"inputs": []
|
||||
},
|
||||
{
|
||||
"type": "error",
|
||||
"name": "InsufficientBalance",
|
||||
"inputs": [
|
||||
{
|
||||
"name": "balance",
|
||||
"type": "uint256",
|
||||
"internalType": "uint256"
|
||||
},
|
||||
{
|
||||
"name": "needed",
|
||||
"type": "uint256",
|
||||
"internalType": "uint256"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "error",
|
||||
"name": "OwnableInvalidOwner",
|
||||
|
||||
11
substreams/ethereum-liquidityparty/bin/generate_abi
Executable file
11
substreams/ethereum-liquidityparty/bin/generate_abi
Executable file
@@ -0,0 +1,11 @@
|
||||
#!/bin/bash
|
||||
LMSR_HOME=../../../lmsr-amm
|
||||
CHAIN_ID=11155111
|
||||
abi() {
|
||||
jq '.abi' $LMSR_HOME/deployment/$CHAIN_ID/v1/out/$1.sol/$1.json > abi/$2.abi.json
|
||||
echo abi/$2.abi.json
|
||||
}
|
||||
|
||||
abi PartyPlanner party_planner
|
||||
abi PartyPool party_pool
|
||||
abi PartyInfo party_info
|
||||
@@ -16,18 +16,17 @@ binaries:
|
||||
type: wasm/rust-v1
|
||||
file: ../target/wasm32-unknown-unknown/release/ethereum_liquidityparty.wasm
|
||||
|
||||
network: sepolia
|
||||
network: mainnet
|
||||
networks:
|
||||
sepolia:
|
||||
mainnet:
|
||||
initialBlock:
|
||||
map_protocol_components: 9460804
|
||||
map_protocol_components: 23978797
|
||||
params:
|
||||
map_protocol_components: planner=0x0ad06C08ab5049e6Fd4d7f5AF457115A1475326b&viewer=0x750d63a39a4ccfCfB69D2f5aFDa065909C717cAB&mint_impl=0x25bb10BA84944F8aAEf1fD247C3B7Fe7271C23F9&swap_impl=0x69b4F102e0747f61F8529b3bbFf2FC4b27438d0F&deployer=0x0939F93BAa3c96226853F9F39A95beF48eA8fF04&bp_deployer=0xfda454fF7876aad9408517Ed2F0d11AA229Ad0a4
|
||||
map_protocol_components: planner=0x42977f565971F6D288a05ddEbC87A17276F71A29&info=0x605F803cD27F5c1fa01440B2cbd5D3E4Cf7EE850&mint_impl=0xA0375403921e9B357E1BeD57bef3fA3FCE80acd0&swap_impl=0x6aA001e87F86E83bc4D569883332882cb47E2A13
|
||||
|
||||
modules:
|
||||
- name: map_protocol_components
|
||||
kind: map
|
||||
initialBlock: 1
|
||||
inputs:
|
||||
- params: string
|
||||
- source: sf.ethereum.type.v2.Block
|
||||
@@ -36,7 +35,6 @@ modules:
|
||||
|
||||
- name: store_protocol_components
|
||||
kind: store
|
||||
initialBlock: 1
|
||||
updatePolicy: set
|
||||
valueType: string
|
||||
inputs:
|
||||
@@ -44,7 +42,6 @@ modules:
|
||||
|
||||
- name: map_relative_component_balance
|
||||
kind: map
|
||||
initialBlock: 1
|
||||
inputs:
|
||||
- source: sf.ethereum.type.v2.Block
|
||||
- store: store_protocol_components
|
||||
@@ -53,7 +50,6 @@ modules:
|
||||
|
||||
- name: store_balances
|
||||
kind: store
|
||||
initialBlock: 1
|
||||
updatePolicy: add
|
||||
valueType: bigint
|
||||
inputs:
|
||||
@@ -61,7 +57,6 @@ modules:
|
||||
|
||||
- name: map_protocol_changes
|
||||
kind: map
|
||||
initialBlock: 1
|
||||
inputs:
|
||||
- source: sf.ethereum.type.v2.Block
|
||||
- map: map_protocol_components
|
||||
@@ -1,69 +1,40 @@
|
||||
# Name of the substreams config file in your substreams module. Usually "./substreams.yaml"
|
||||
substreams_yaml_path: ./substreams.yaml
|
||||
# Name of the adapter contract, usually: ProtocolSwapAdapter"
|
||||
adapter_contract: "SwapAdapter"
|
||||
# Constructor signature of the Adapter contract"
|
||||
adapter_build_signature: "constructor(address)"
|
||||
# A comma separated list of args to be passed to the contructor of the Adapter contract"
|
||||
adapter_build_args: "0x0000000000000000000000000000000000000000"
|
||||
# Whether the testing script should skip checking balances of the protocol components.
|
||||
# If set to `true` please always add a reason why it's skipped.
|
||||
skip_balance_check: false
|
||||
# Accounts that will be automatically initialized at test start
|
||||
# IMPORTANT: These are TEST FIXTURES ONLY. Your actual code must still properly
|
||||
# initialize these accounts. This configuration only eliminates the need to include
|
||||
# historical blocks containing the initialization events in your test data.
|
||||
#
|
||||
# Example use case:
|
||||
# - Your substream would normally initialize account XYZ at block 10000
|
||||
# - Your test only includes blocks 20000-21000 for efficiency
|
||||
# - You list XYZ here so the test environment will automatically initialize the account XYZ with the state it had at block 20000
|
||||
# - Your actual substream code MUST STILL contain the initialization and state tracking logic for this contract
|
||||
#
|
||||
# Without this, you would need to include block 10000 in your test data or your
|
||||
# test would fail because the account appears uninitialized to your code.
|
||||
initialized_accounts:
|
||||
- "0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84" # Needed for ....
|
||||
# A list of protocol types names created by your Substreams module.
|
||||
substreams_yaml_path: ./sepolia-liquidityparty.yaml
|
||||
adapter_contract: "LiquidityPartySwapAdapter"
|
||||
adapter_build_signature: "constructor(address,address)"
|
||||
# PartyPlanner, PartyInfo
|
||||
adapter_build_args: "0x42977f565971F6D288a05ddEbC87A17276F71A29,0x605F803cD27F5c1fa01440B2cbd5D3E4Cf7EE850"
|
||||
# Liquidity Party protocol fees are retained in the pool until collection, so the pool balance does not match the TVL.
|
||||
skip_balance_check: true
|
||||
protocol_type_names:
|
||||
- "type_name_1"
|
||||
- "type_name_2"
|
||||
# A list of tests.
|
||||
# The name of the protocol system
|
||||
protocol_system: "protocol_name"
|
||||
- "liquidityparty_pool"
|
||||
protocol_system: "vm:liquidityparty"
|
||||
initialized_accounts: [
|
||||
"0x42977f565971F6D288a05ddEbC87A17276F71A29", # PartyPlanner
|
||||
"0x605F803cD27F5c1fa01440B2cbd5D3E4Cf7EE850", # PartyInfo
|
||||
"0xA0375403921e9B357E1BeD57bef3fA3FCE80acd0", # PartyPoolMintImpl
|
||||
"0x6aA001e87F86E83bc4D569883332882cb47E2A13", # PartyPoolSwapImpl
|
||||
]
|
||||
|
||||
tests:
|
||||
# Name of the test
|
||||
- name: test_pool_creation
|
||||
# Indexed block range
|
||||
start_block: 123
|
||||
stop_block: 456
|
||||
# Same as global `initialized_accounts` but only scoped to this test.
|
||||
initialized_accounts:
|
||||
- "0x0c0e5f2fF0ff18a3be9b835635039256dC4B4963" # Needed for ....
|
||||
# A list of expected component indexed in the block range. Each component must match perfectly the `ProtocolComponent` indexed by your subtreams module.
|
||||
start_block: 23978797
|
||||
stop_block: 23978798
|
||||
initialized_accounts: []
|
||||
expected_components:
|
||||
- id: "0xbebc44782c7db0a1a60cb6fe97d0b483032ff1c7"
|
||||
- id: "0x2A804e94500AE379ee0CcC423a67B07cc0aF548C"
|
||||
tokens:
|
||||
- "0xdac17f958d2ee523a2206206994597c13d831ec7"
|
||||
- "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48"
|
||||
- "0x6b175474e89094c44da98b954eedeac495271d0f"
|
||||
# Does this test whether the tokens are in the correct order?
|
||||
- "0xdAC17F958D2ee523a2206206994597C13D831ec7" # USDT
|
||||
- "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48" # USDC
|
||||
- "0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599" # WBTC
|
||||
- "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2" # WETH
|
||||
- "0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984" # UNI
|
||||
- "0xD31a59c85aE9D8edEFeC411D448f90841571b89c" # SOL
|
||||
- "0x50327c6c5a14DCaDE707ABad2E27eB517df87AB5" # TRX
|
||||
- "0x7Fc66500c84A76Ad7e9c93437bFc5Ac33E2DDaE9" # AAVE
|
||||
- "0x6982508145454Ce325dDbE47a25d4ec3d2311933" # PEPE
|
||||
- "0x95aD61b0a150d79219dCF64E1E6Cc01f0B64C4cE" # SHIB
|
||||
static_attributes: {}
|
||||
creation_tx: "0x20793bbf260912aae189d5d261ff003c9b9166da8191d8f9d63ff1c7722f3ac6"
|
||||
# Whether the script should skip trying to simulate a swap on this component.
|
||||
# If set to `true` please always add a reason why it's skipped.
|
||||
creation_tx: "0x5c8b1e1e6ec10143a1252799d14df09c7e84f6a99ccde95fc11295a61c20060e"
|
||||
skip_simulation: false
|
||||
# Whether the script should skip trying to simulate execution of a swap on this component.
|
||||
# If set to `true` please always add a reason why it's skipped.
|
||||
skip_execution: false
|
||||
- name: test_something_else
|
||||
start_block: 123
|
||||
stop_block: 456
|
||||
expected_components:
|
||||
- id: "0xdc24316b9ae028f1497c275eb9192a3ea0f67022"
|
||||
tokens:
|
||||
- "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE"
|
||||
- "0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84"
|
||||
static_attributes: { }
|
||||
creation_tx: "0xfac67ecbd423a5b915deff06045ec9343568edaec34ae95c43d35f2c018afdaa"
|
||||
skip_simulation: true # If true, always add a reason
|
||||
skip_execution: true # If true, always add a reason
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
mod.rs
|
||||
party_planner.rs
|
||||
party_pool.rs
|
||||
party_pool_viewer.rs
|
||||
party_info.rs
|
||||
|
||||
@@ -53,6 +53,7 @@ fn map_protocol_components(
|
||||
param_string: String,
|
||||
block: eth::v2::Block
|
||||
) -> Result<BlockTransactionProtocolComponents> {
|
||||
substreams::log::debug!("Processing block {} for protocol components", block.number);
|
||||
let params = Params::parse(¶m_string)?;
|
||||
Ok(BlockTransactionProtocolComponents {
|
||||
tx_components: block
|
||||
@@ -94,6 +95,7 @@ fn store_protocol_components(
|
||||
map_protocol_components: BlockTransactionProtocolComponents,
|
||||
store: StoreSetRaw,
|
||||
) {
|
||||
substreams::log::debug!("Storing {} protocol components", map_protocol_components.tx_components.len());
|
||||
map_protocol_components
|
||||
.tx_components
|
||||
.into_iter()
|
||||
@@ -129,6 +131,7 @@ fn map_relative_component_balance(
|
||||
block: eth::v2::Block,
|
||||
store: StoreGetRaw,
|
||||
) -> Result<BlockBalanceDeltas, anyhow::Error> {
|
||||
substreams::log::debug!("Processing block {} for balance changes", block.number);
|
||||
let mut res = Vec::new();
|
||||
|
||||
for log in block.logs() {
|
||||
@@ -239,6 +242,12 @@ fn map_protocol_changes(
|
||||
balance_store: StoreDeltas,
|
||||
deltas: BlockBalanceDeltas,
|
||||
) -> Result<BlockChanges, substreams::errors::Error> {
|
||||
substreams::log::debug!(
|
||||
"Processing block {} changes: {} new components, {} balance deltas",
|
||||
block.number,
|
||||
new_components.tx_components.len(),
|
||||
deltas.balance_deltas.len()
|
||||
);
|
||||
// We merge contract changes by transaction (identified by transaction index)
|
||||
// making it easy to sort them at the very end.
|
||||
let mut transaction_changes: HashMap<_, TransactionChangesBuilder> = HashMap::new();
|
||||
|
||||
@@ -4,20 +4,17 @@ use serde::Deserialize;
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct StringParams {
|
||||
planner: String,
|
||||
viewer: String,
|
||||
info: String,
|
||||
mint_impl: String,
|
||||
swap_impl: String,
|
||||
deployer: String,
|
||||
bp_deployer: String,
|
||||
}
|
||||
|
||||
pub(crate) struct Params {
|
||||
pub planner: Vec<u8>,
|
||||
pub viewer: Vec<u8>,
|
||||
#[allow(dead_code)] // We keep the unused info field for future pricing/view operations
|
||||
pub info: Vec<u8>,
|
||||
pub mint_impl: Vec<u8>,
|
||||
pub swap_impl: Vec<u8>,
|
||||
pub deployer: Vec<u8>,
|
||||
pub bp_deployer: Vec<u8>,
|
||||
}
|
||||
|
||||
impl StringParams {
|
||||
@@ -44,11 +41,9 @@ impl Params {
|
||||
|
||||
Ok(Self {
|
||||
planner: decode_addr(¶ms.planner)?,
|
||||
viewer: decode_addr(¶ms.viewer)?,
|
||||
info: decode_addr(¶ms.info)?,
|
||||
mint_impl: decode_addr(¶ms.mint_impl)?,
|
||||
swap_impl: decode_addr(¶ms.swap_impl)?,
|
||||
deployer: decode_addr(¶ms.deployer)?,
|
||||
bp_deployer: decode_addr(¶ms.bp_deployer)?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
use crate::abi;
|
||||
use crate::params::Params;
|
||||
use substreams_ethereum::pb::eth::v2::{Call, Log, TransactionTrace};
|
||||
use substreams_ethereum::Event;
|
||||
use tycho_substreams::models::{
|
||||
ChangeType, FinancialType, ImplementationType, ProtocolComponent, ProtocolType,
|
||||
ImplementationType, ProtocolComponent,
|
||||
};
|
||||
use crate::abi;
|
||||
use crate::params::Params;
|
||||
|
||||
/// Potentially constructs a new ProtocolComponent given a call
|
||||
///
|
||||
@@ -21,19 +21,10 @@ pub fn maybe_create_component(
|
||||
) -> Option<ProtocolComponent> {
|
||||
if call.address.as_slice() == params.planner {
|
||||
if let Some(event) = abi::party_planner::events::PartyStarted::match_and_decode(_log) {
|
||||
return Some(ProtocolComponent {
|
||||
id: hex::encode(&event.pool),
|
||||
tokens: event.tokens,
|
||||
contracts: vec![event.pool.clone(), params.mint_impl.clone(), params.swap_impl.clone()],
|
||||
static_att: vec![],
|
||||
change: ChangeType::Creation.into(),
|
||||
protocol_type: Some(ProtocolType {
|
||||
name: "liquidity_party".to_string(),
|
||||
financial_type: FinancialType::Swap.into(),
|
||||
attribute_schema: vec![],
|
||||
implementation_type: ImplementationType::Vm.into(),
|
||||
}),
|
||||
});
|
||||
return Some(ProtocolComponent::new(&format!("0x{}", hex::encode(&event.pool)))
|
||||
.with_tokens(&event.tokens.clone())
|
||||
.with_contracts(&vec![event.pool.clone(), params.mint_impl.clone(), params.swap_impl.clone()])
|
||||
.as_swap_type("liquidityparty_pool", ImplementationType::Vm));
|
||||
}
|
||||
}
|
||||
None
|
||||
|
||||
Reference in New Issue
Block a user