Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions foundry.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ src = "src"
out = "out"
libs = ["lib"]
evm_version = "cancun"
optimizer = true
optimizer_runs = 10_000

[fuzz]
max_test_rejects = 2_147_483_648
Expand Down
7 changes: 2 additions & 5 deletions src/ValidlyFactory.sol
Original file line number Diff line number Diff line change
Expand Up @@ -159,9 +159,7 @@ contract ValidlyFactory is IValidlyFactory {
}

/**
* @notice Claims rebase token fees accumulated in this contract.
* @dev By design of Sovereign Pools, manager fees for rebase tokens
* get transferred on every swap to its manager (this contract).
* @notice Claims accummulated fees accumulated in this contract.
* @param _token The address of the token to claim.
* @param _recipient The address of the recipient.
*/
Expand Down Expand Up @@ -189,8 +187,7 @@ contract ValidlyFactory is IValidlyFactory {
* @param _pool The address of the pool to claim the pool manager fees for.
*/
function claimFees(address _pool) external {
// It marks all fees as protocol fees to be used by gauge
ISovereignPool(_pool).claimPoolManagerFees(10_000, 10_000);
ISovereignPool(_pool).claimPoolManagerFees(0, 0);

emit FeesClaimed(_pool);
}
Expand Down
187 changes: 187 additions & 0 deletions src/ValidlyLens.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.24;

import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import {Math} from "@openzeppelin/contracts/utils/math/Math.sol";
import {ISovereignPool} from "@valantis-core/pools/interfaces/ISovereignPool.sol";

import {IValidly} from "./interfaces/IValidly.sol";

/**
* @title Validly Lens.
* @notice Helper contract with read-only functions for Validly.
*/
contract ValidlyLens {
/**
*
* CONSTANTS
*
*/
uint256 private constant MIN_LIQUIDITY = 1000;
uint256 private constant BIPS = 10_000;

/**
*
* VIEW FUNCTIONS
*
*/

/**
* @notice Simulate deposit liquidity into Validly and mint LP tokens.
* @param _validly Address of Validly deployment.
* @param _amount0Max Maximum amount of token0 to deposit.
* @param _amount1Max Maximum amount of token1 to deposit.
* @return shares Amount of shares minted.
* @return amount0 Correct amount of token0 to deposit.
* @return amount1 Correct amount of token1 to deposit.
*/
function simulateDeposit(address _validly, uint256 _amount0Max, uint256 _amount1Max)
external
view
returns (uint256 shares, uint256 amount0, uint256 amount1)
{
uint256 totalSupplyCache = ERC20(_validly).totalSupply();
if (totalSupplyCache == 0) {
amount0 = _amount0Max;
amount1 = _amount1Max;

shares = Math.sqrt(amount0 * amount1) - MIN_LIQUIDITY;
} else {
ISovereignPool pool = IValidly(_validly).pool();
(uint256 reserve0, uint256 reserve1) = pool.getReserves();

uint256 shares0 = Math.mulDiv(_amount0Max, totalSupplyCache, reserve0);
uint256 shares1 = Math.mulDiv(_amount1Max, totalSupplyCache, reserve1);

if (shares0 < shares1) {
shares = shares0;
amount1 = Math.mulDiv(reserve1, shares, totalSupplyCache, Math.Rounding.Ceil);
amount0 = _amount0Max;
} else {
shares = shares1;
amount0 = Math.mulDiv(reserve0, shares, totalSupplyCache, Math.Rounding.Ceil);
amount1 = _amount1Max;
}
}
}

/**
* @notice Simulate withdraw liquidity from Validly and burn LP tokens.
* @param _validly Address of Validly deployment.
* @param _shares Amount of LP tokens to burn.
* @return amount0 Amount of token0 withdrawn. WARNING: Potentially innacurate in case token0 is rebase.
* @return amount1 Amount of token1 withdrawn. WARNING: Potentially innacurate in case token1 is rebase.
*/
function simulateWithdraw(address _validly, uint256 _shares)
external
view
returns (uint256 amount0, uint256 amount1)
{
if (_shares == 0) return (0, 0);

ISovereignPool pool = IValidly(_validly).pool();
(uint256 reserve0, uint256 reserve1) = pool.getReserves();

uint256 totalSupplyCache = ERC20(_validly).totalSupply();
amount0 = Math.mulDiv(reserve0, _shares, totalSupplyCache);
amount1 = Math.mulDiv(reserve1, _shares, totalSupplyCache);

if (amount0 == 0 || amount1 == 0) revert("zero_amount_withdrawn");
}

/**
* @notice Simulate swap quote from Validly.
* @param _validly Address of Validly deployment.
* @param _isZeroToOne Direction of the swap.
* @param _amountIn Amount of input token to swap.
* @return amountOut Amount of output token received after swap.
*/
function simulateSwap(address _validly, bool _isZeroToOne, uint256 _amountIn)
external
view
returns (uint256 amountOut)
{
if (_amountIn == 0) return 0;

IValidly validly = IValidly(_validly);

ISovereignPool pool = validly.pool();
bool isStable = validly.isStable();

(uint256 reserve0, uint256 reserve1) = pool.getReserves();

uint256 amountInWithoutFee = Math.mulDiv(_amountIn, BIPS, BIPS + pool.defaultSwapFeeBips());

(uint256 reserveIn, uint256 reserveOut) = _isZeroToOne ? (reserve0, reserve1) : (reserve1, reserve0);

uint256 invariant;
if (isStable) {
uint256 decimals0 = validly.decimals0();
uint256 decimals1 = validly.decimals1();

invariant = _stableInvariant(reserve0, reserve1, decimals0, decimals1);
// Scale reserves and amounts to 18 decimals
reserveIn = _isZeroToOne ? (reserveIn * 1e18) / decimals0 : (reserveIn * 1e18) / decimals1;
reserveOut = _isZeroToOne ? (reserveOut * 1e18) / decimals1 : (reserveOut * 1e18) / decimals0;
uint256 amountIn =
_isZeroToOne ? (amountInWithoutFee * 1e18) / decimals0 : (amountInWithoutFee * 1e18) / decimals1;
amountOut = reserveOut - _get_y_stableInvariant(amountIn + reserveIn, invariant, reserveOut);

amountOut = (amountOut * (_isZeroToOne ? decimals1 : decimals0)) / 1e18;
} else {
invariant = reserve0 * reserve1;

amountOut = (reserveOut * amountInWithoutFee) / (reserveIn + amountInWithoutFee);
}
}

/**
*
* PRIVATE FUNCTIONS
*
*/
function _stableInvariant(uint256 x, uint256 y, uint256 decimals0, uint256 decimals1)
private
pure
returns (uint256)
{
uint256 _x = (x * 1e18) / decimals0;
uint256 _y = (y * 1e18) / decimals1;
uint256 _a = (_x * _y) / 1e18;
uint256 _b = ((_x * _x) / 1e18 + (_y * _y) / 1e18);
return (_a * _b) / 1e18; // x3y+y3x >= k
}

function _f(uint256 x0, uint256 y) private pure returns (uint256) {
return (x0 * ((((y * y) / 1e18) * y) / 1e18)) / 1e18 + (((((x0 * x0) / 1e18) * x0) / 1e18) * y) / 1e18;
}

function _d(uint256 x0, uint256 y) private pure returns (uint256) {
return (3 * x0 * ((y * y) / 1e18)) / 1e18 + ((((x0 * x0) / 1e18) * x0) / 1e18);
}

function _get_y_stableInvariant(uint256 x0, uint256 invariant, uint256 y) private pure returns (uint256) {
for (uint256 i = 0; i < 255; i++) {
uint256 y_prev = y;
uint256 k = _f(x0, y);
if (k < invariant) {
uint256 dy = ((invariant - k) * 1e18) / _d(x0, y);
y = y + dy;
} else {
uint256 dy = ((k - invariant) * 1e18) / _d(x0, y);
y = y - dy;
}
if (y > y_prev) {
if (y - y_prev <= 1) {
return y;
}
} else {
if (y_prev - y <= 1) {
return y;
}
}
}
// Did not converge in 255 fixed point iterations
revert("stable_invariant_not_converged");
}
}
39 changes: 36 additions & 3 deletions test/Validly.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,13 @@ import {SovereignPoolFactory} from "@valantis-core/pools/factories/SovereignPool
import {ALMLiquidityQuoteInput} from "@valantis-core/ALM/structs/SovereignALMStructs.sol";

import {Validly} from "../src/Validly.sol";
import {ValidlyLens} from "../src/ValidlyLens.sol";
import {ValidlyFactory} from "../src/ValidlyFactory.sol";

contract ValidlyTest is Test {
ValidlyFactory public factory;
ValidlyLens public lens;

ERC20Mock public token0;
ERC20Mock public token1;

Expand All @@ -28,6 +31,8 @@ contract ValidlyTest is Test {
token0 = new ERC20Mock();
token1 = new ERC20Mock();

lens = new ValidlyLens();

ProtocolFactory protocolFactory = new ProtocolFactory(address(this));

SovereignPoolFactory poolFactory = new SovereignPoolFactory();
Expand Down Expand Up @@ -79,9 +84,29 @@ contract ValidlyTest is Test {
token0.approve(address(volatilePair), 1000 ether);
token1.approve(address(volatilePair), 1000 ether);

volatilePair.deposit(1 ether, 10 ether, 0, block.timestamp + 1, address(this), "");

volatilePair.deposit(1 ether, 20 ether, 0, block.timestamp + 1, address(this), "");
(uint256 sharesSimulation, uint256 amount0Simulation, uint256 amount1Simulation) =
lens.simulateDeposit(address(volatilePair), 1 ether, 10 ether);
(uint256 shares, uint256 amount0, uint256 amount1) =
volatilePair.deposit(1 ether, 10 ether, 0, block.timestamp + 1, address(this), "");
assertEq(sharesSimulation, shares);
assertEq(amount0Simulation, amount0);
assertEq(amount1Simulation, amount1);

(sharesSimulation, amount0Simulation, amount1Simulation) =
lens.simulateDeposit(address(volatilePair), 40 ether, 0.1 ether);
(shares, amount0, amount1) =
volatilePair.deposit(40 ether, 0.1 ether, 0, block.timestamp + 1, address(this), "");
assertEq(sharesSimulation, shares);
assertEq(amount0Simulation, amount0);
assertEq(amount1Simulation, amount1);

(sharesSimulation, amount0Simulation, amount1Simulation) =
lens.simulateDeposit(address(volatilePair), 0.1 ether, 60 ether);
(shares, amount0, amount1) =
volatilePair.deposit(0.1 ether, 60 ether, 0, block.timestamp + 1, address(this), "");
assertEq(sharesSimulation, shares);
assertEq(amount0Simulation, amount0);
assertEq(amount1Simulation, amount1);

vm.expectRevert(Validly.Validly__deposit_zeroShares.selector);
volatilePair.deposit(1 ether, 0, 0, block.timestamp + 1, address(this), "");
Expand Down Expand Up @@ -116,8 +141,12 @@ contract ValidlyTest is Test {
vm.expectRevert(Validly.Validly__withdraw_insufficientToken1Withdrawn.selector);
volatilePair.withdraw(sharesToWithdraw, 0, 100 ether, block.timestamp + 1, address(this), "");

(uint256 amount0Simulation, uint256 amount1Simulation) =
lens.simulateWithdraw(address(volatilePair), sharesToWithdraw);
(uint256 amount0, uint256 amount1) =
volatilePair.withdraw(sharesToWithdraw, 0, 0, block.timestamp + 1, address(this), "");
assertEq(amount0Simulation, amount0);
assertEq(amount1Simulation, amount1);

assertEq(amount0, expectedAmount0);
assertEq(amount1, expectedAmount1);
Expand All @@ -144,7 +173,9 @@ contract ValidlyTest is Test {

token1.approve(address(stablePool), 1 ether);

uint256 amountOutSimulation = lens.simulateSwap(address(stablePair), params.isZeroToOne, params.amountIn);
(uint256 amountInUsed, uint256 amountOut) = stablePool.swap(params);
assertEq(amountOutSimulation, amountOut);

assertApproxEqAbs(amountOut, amountInUsed, Math.mulDiv(amountInUsed, 1, 1000));
}
Expand Down Expand Up @@ -178,7 +209,9 @@ contract ValidlyTest is Test {
token0.approve(address(volatilePool), 1 ether);
token1.approve(address(volatilePool), 10 ether);

uint256 amountOutSimulation = lens.simulateSwap(address(volatilePair), params.isZeroToOne, params.amountIn);
(uint256 amountInUsed, uint256 amountOut) = volatilePool.swap(params);
assertEq(amountOutSimulation, amountOut);

uint256 expectedAmountOut = Math.mulDiv(
reserve1, Math.mulDiv(amountInUsed, 10000, 10001), reserve0 + Math.mulDiv(amountInUsed, 10000, 10001)
Expand Down
15 changes: 10 additions & 5 deletions test/ValidlyFactory.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -100,18 +100,19 @@ contract ValidlyFactoryTest is Test {

address pool = factory.pools(key);

token0.mint(address(pool), 1e18);
token1.mint(address(pool), 10e18);

vm.store(address(pool), bytes32(uint256(5)), bytes32(uint256(1e18)));
vm.store(address(pool), bytes32(uint256(6)), bytes32(uint256(10e18)));

factory.claimFees(pool);

assertEq(SovereignPool(pool).feeProtocol0(), 1e18);
assertEq(SovereignPool(pool).feeProtocol1(), 10e18);
assertEq(token0.balanceOf(address(factory)), 1e18);
assertEq(token1.balanceOf(address(factory)), 10e18);
}

function test_claimTokens() public {
token0.mint(address(factory), 1e18);

address ALICE = makeAddr("ALICE");

vm.expectRevert(ValidlyFactory.ValidlyFactory__onlyProtocolManager.selector);
Expand All @@ -124,8 +125,12 @@ contract ValidlyFactoryTest is Test {
vm.expectRevert(ValidlyFactory.ValidlyFactory__claimTokens_invalidRecipient.selector);
factory.claimTokens(address(token0), address(0));

factory.claimTokens(address(token0), ALICE);
test_claimFees();

factory.claimTokens(address(token0), ALICE);
assertEq(token0.balanceOf(ALICE), 1e18);

factory.claimTokens(address(token1), ALICE);
assertEq(token1.balanceOf(ALICE), 10e18);
}
}
Loading