Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix invariant error #12

Merged
merged 7 commits into from
Mar 17, 2025
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
23 changes: 7 additions & 16 deletions .github/actions/setup/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,28 +22,19 @@ runs:
with:
path: './node_modules/*'
key: yarn-pool-${{ hashFiles('./yarn.lock') }}
- name: Cache library node modules
restore-keys: |
yarn-pool-
- name: Cache library pkg
uses: actions/cache@v4
id: cache-lib-modules
id: cache-lib-pkg
with:
path: './lib/balancer-v3-monorepo/**/node_modules/*'
key: lib-modules-${{ hashFiles('./submodule-hash.txt') }}
- name: Cache library artifacts
uses: actions/cache@v4
id: cache-lib-artifacts
with:
path: './lib/balancer-v3-monorepo/pkg/**/artifacts/*'
path: './lib/balancer-v3-monorepo/pkg'
key: lib-pkg-${{ hashFiles('./submodule-hash.txt') }}
- name: Cache library typechain
uses: actions/cache@v4
id: cache-lib-typechain
with:
path: './lib/balancer-v3-monorepo/pkg/**/typechain-types/*'
key: lib-typechain-${{ hashFiles('./submodule-hash.txt') }}
restore-keys: |
lib-pkg-
- name: Install lcov
shell: bash
run: sudo apt-get install lcov
- name: Install fresh
shell: bash
run: sh ./scripts/install-fresh.sh
if: steps.cache-lib-artifacts.outputs.cache-hit != 'true' || steps.cache-lib-modules.outputs.cache-hit != 'true' || steps.cache-lib-typechain.outputs.cache-hit != 'true'
36 changes: 0 additions & 36 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,36 +24,12 @@ jobs:
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
FORGE_SNAPSHOT_CHECK: true
- uses: actions/upload-artifact@v4
with:
name: built-artifacts
path: artifacts/
- uses: actions/upload-artifact@v4
with:
name: built-lib
path: lib/balancer-v3-monorepo/pkg/
- uses: actions/upload-artifact@v4
with:
name: built-lib-pvt
path: lib/balancer-v3-monorepo/pvt/

test-forge:
runs-on: ubuntu-latest
needs: lint-and-build
steps:
- uses: actions/checkout@v4
- uses: actions/download-artifact@v4
with:
name: built-artifacts
path: artifacts/
- uses: actions/download-artifact@v4
with:
name: built-lib
path: lib/balancer-v3-monorepo/pkg/
- uses: actions/download-artifact@v4
with:
name: built-lib-pvt
path: lib/balancer-v3-monorepo/pvt/
- name: Set up environment
uses: ./.github/actions/setup
- name: Test
Expand All @@ -66,17 +42,5 @@ jobs:
- uses: actions/checkout@v4
- name: Set up environment
uses: ./.github/actions/setup
- uses: actions/download-artifact@v4
with:
name: built-artifacts
path: artifacts/
- uses: actions/download-artifact@v4
with:
name: built-lib
path: lib/balancer-v3-monorepo/pkg/
- uses: actions/download-artifact@v4
with:
name: built-lib-pvt
path: lib/balancer-v3-monorepo/pvt/
- name: Test
run: yarn test:hardhat
2 changes: 0 additions & 2 deletions contracts/interfaces/IAclAmmPool.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@

pragma solidity ^0.8.24;

import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";

import { IBasePool } from "@balancer-labs/v3-interfaces/contracts/vault/IBasePool.sol";

/// @dev Struct with data for deploying a new AclAmmPool.
Expand Down
58 changes: 32 additions & 26 deletions contracts/lib/AclAmmMath.sol
Original file line number Diff line number Diff line change
Expand Up @@ -64,9 +64,9 @@ library AclAmmMath {
finalBalances[0] = balancesScaled18[0] + virtualBalances[0];
finalBalances[1] = balancesScaled18[1] + virtualBalances[1];

uint256 invariant = finalBalances[0].mulDown(finalBalances[1]);
uint256 invariant = finalBalances[0].mulUp(finalBalances[1]);

return finalBalances[tokenOutIndex] - invariant.divDown(finalBalances[tokenInIndex] + amountGivenScaled18);
return finalBalances[tokenOutIndex] - invariant.divUp(finalBalances[tokenInIndex] + amountGivenScaled18);
}

function calculateInGivenOut(
Expand Down Expand Up @@ -109,6 +109,12 @@ library AclAmmMath {

virtualBalances = lastVirtualBalances;

// If the last timestamp is the same as the current timestamp, virtual balances were already reviewed in the
// current block.
if (lastTimestamp == block.timestamp) {
return (virtualBalances, false);
}

// Calculate currentSqrtQ0
uint256 currentSqrtQ0 = calculateSqrtQ0(
currentTimestamp,
Expand All @@ -118,30 +124,6 @@ library AclAmmMath {
sqrtQ0State.endTime
);

if (isPoolInRange(balancesScaled18, lastVirtualBalances, centerednessMargin) == false) {
uint256 q0 = currentSqrtQ0.mulDown(currentSqrtQ0);

if (isAboveCenter(balancesScaled18, lastVirtualBalances)) {
virtualBalances[1] = lastVirtualBalances[1].mulDown(
LogExpMath.pow(FixedPoint.ONE - c, (block.timestamp - lastTimestamp) * FixedPoint.ONE)
);
// Va = (Ra * (Vb + Rb)) / (((Q0 - 1) * Vb) - Rb)
virtualBalances[0] = (balancesScaled18[0].mulDown(virtualBalances[1] + balancesScaled18[1])).divDown(
(q0 - FixedPoint.ONE).mulDown(virtualBalances[1]) - balancesScaled18[1]
);
} else {
virtualBalances[0] = lastVirtualBalances[0].mulDown(
LogExpMath.pow(FixedPoint.ONE - c, (block.timestamp - lastTimestamp) * FixedPoint.ONE)
);
// Vb = (Rb * (Va + Ra)) / (((Q0 - 1) * Va) - Ra)
virtualBalances[1] = (balancesScaled18[1].mulDown(virtualBalances[0] + balancesScaled18[0])).divDown(
(q0 - FixedPoint.ONE).mulDown(virtualBalances[0]) - balancesScaled18[0]
);
}

changed = true;
}

if (
sqrtQ0State.startTime != 0 &&
currentTimestamp > sqrtQ0State.startTime &&
Expand Down Expand Up @@ -170,6 +152,30 @@ library AclAmmMath {

changed = true;
}

if (isPoolInRange(balancesScaled18, lastVirtualBalances, centerednessMargin) == false) {
uint256 q0 = currentSqrtQ0.mulDown(currentSqrtQ0);

if (isAboveCenter(balancesScaled18, lastVirtualBalances)) {
virtualBalances[1] = lastVirtualBalances[1].mulDown(
LogExpMath.pow(FixedPoint.ONE - c, (block.timestamp - lastTimestamp) * FixedPoint.ONE)
);
// Va = (Ra * (Vb + Rb)) / (((Q0 - 1) * Vb) - Rb)
virtualBalances[0] = (balancesScaled18[0].mulDown(virtualBalances[1] + balancesScaled18[1])).divDown(
(q0 - FixedPoint.ONE).mulDown(virtualBalances[1]) - balancesScaled18[1]
);
} else {
virtualBalances[0] = lastVirtualBalances[0].mulDown(
LogExpMath.pow(FixedPoint.ONE - c, (block.timestamp - lastTimestamp) * FixedPoint.ONE)
);
// Vb = (Rb * (Va + Ra)) / (((Q0 - 1) * Va) - Ra)
virtualBalances[1] = (balancesScaled18[1].mulDown(virtualBalances[0] + balancesScaled18[0])).divDown(
(q0 - FixedPoint.ONE).mulDown(virtualBalances[0]) - balancesScaled18[0]
);
}

changed = true;
}
}

function isPoolInRange(
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
"prettier": "npx prettier --write --plugin=prettier-plugin-solidity 'contracts/**/*.sol' 'test/**/*.sol'",
"test": "yarn test:hardhat && yarn test:forge",
"test:hardhat": "hardhat test",
"test:forge": "forge test --ffi -vvv"
"test:forge": "yarn build && REUSING_HARDHAT_ARTIFACTS=true forge test --ffi -vvv"
},
"packageManager": "[email protected]",
"dependencies": {
Expand Down
66 changes: 40 additions & 26 deletions test/foundry/AclAmmPoolVirtualBalances.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@

pragma solidity ^0.8.24;

import { console } from "forge-std/Test.sol";

import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";

import { GyroPoolMath } from "@balancer-labs/v3-pool-gyro/contracts/lib/GyroPoolMath.sol";
Expand All @@ -20,14 +18,13 @@ contract AclAmmPoolVirtualBalancesTest is BaseAclAmmTest {
using FixedPoint for uint256;
using ArrayHelpers for *;

uint256 internal constant maxPrice = 4000;
uint256 internal constant minPrice = 2000;
uint256 internal constant initialABalance = 1_000_000e18;
uint256 internal constant initialBBalance = 100_000e18;
uint256 private constant _PRICE_RANGE = 2e18; // Max price is 2x min price.
uint256 private constant _INITIAL_BALANCE_A = 1_000_000e18;
uint256 private constant _INITIAL_BALANCE_B = 100_000e18;

function setUp() public virtual override {
setSqrtQ0(minPrice, maxPrice);
setInitialBalances(initialABalance, initialBBalance);
setPriceRange(_PRICE_RANGE);
setInitialBalances(_INITIAL_BALANCE_A, _INITIAL_BALANCE_B);
setIncreaseDayRate(0);
super.setUp();
}
Expand All @@ -53,11 +50,11 @@ contract AclAmmPoolVirtualBalancesTest is BaseAclAmmTest {

uint256[] memory newInitialBalances = new uint256[](2);
if (diffCoefficient > 0) {
newInitialBalances[0] = initialABalance * uint256(diffCoefficient);
newInitialBalances[1] = initialBBalance * uint256(diffCoefficient);
newInitialBalances[0] = _INITIAL_BALANCE_A * uint256(diffCoefficient);
newInitialBalances[1] = _INITIAL_BALANCE_B * uint256(diffCoefficient);
} else {
newInitialBalances[0] = initialABalance / uint256(-diffCoefficient);
newInitialBalances[1] = initialBBalance / uint256(-diffCoefficient);
newInitialBalances[0] = _INITIAL_BALANCE_A / uint256(-diffCoefficient);
newInitialBalances[1] = _INITIAL_BALANCE_B / uint256(-diffCoefficient);
}

setInitialBalances(newInitialBalances[0], newInitialBalances[1]);
Expand Down Expand Up @@ -94,17 +91,17 @@ contract AclAmmPoolVirtualBalancesTest is BaseAclAmmTest {
}
}

function testWithDifferentPriceRange_Fuzz(uint256 newSqrtQ) public {
newSqrtQ = bound(newSqrtQ, 1.4e18, 1_000_000e18);
function testWithDifferentPriceRange_Fuzz(uint256 newSqrtQ0) public {
newSqrtQ0 = bound(newSqrtQ0, 1.001e18, 1_000_000e18); // Price range cannot be lower than 1.

uint256 initialSqrtQ = sqrtQ0();
setSqrtQ0(newSqrtQ);
uint256 initialSqrtQ0 = sqrtQ0();
setSqrtQ0(newSqrtQ0);
(address firstPool, address secondPool) = _createNewPool();

uint256[] memory curentFirstPoolVirtualBalances = AclAmmPool(firstPool).getLastVirtualBalances();
uint256[] memory curentNewPoolVirtualBalances = AclAmmPool(secondPool).getLastVirtualBalances();

if (newSqrtQ > initialSqrtQ) {
if (newSqrtQ0 > initialSqrtQ0) {
assertLt(
curentNewPoolVirtualBalances[0],
curentFirstPoolVirtualBalances[0],
Expand Down Expand Up @@ -171,21 +168,38 @@ contract AclAmmPoolVirtualBalancesTest is BaseAclAmmTest {
}
}

function testSwap_Fuzz(uint256 exactAmountIn) public {
exactAmountIn = bound(exactAmountIn, 1e18, 10_000e18);
function testSwapExactIn_Fuzz(uint256 exactAmountIn) public {
exactAmountIn = bound(exactAmountIn, 1e6, _INITIAL_BALANCE_A);

uint256[] memory virtualBalances = _calculateVirtualBalances();
uint256[] memory oldVirtualBalances = AclAmmPool(pool).getLastVirtualBalances();
uint256 invariantBefore = _getCurrentInvariant();

vm.prank(alice);
router.swapSingleTokenExactIn(pool, dai, usdc, exactAmountIn, 1, UINT256_MAX, false, new bytes(0));

uint256 invariantAfter = _getCurrentInvariant();
assertEq(invariantBefore, invariantAfter, "Invariant should not change");
assertLe(invariantBefore, invariantAfter, "Invariant should not decrease");

uint256[] memory curentVirtualBalances = AclAmmPool(pool).getLastVirtualBalances();
assertEq(curentVirtualBalances[0], virtualBalances[0], "Virtual A balances don't equal");
assertEq(curentVirtualBalances[1], virtualBalances[1], "Virtual B balances don't equal");
uint256[] memory newVirtualBalances = AclAmmPool(pool).getLastVirtualBalances();
assertEq(newVirtualBalances[0], oldVirtualBalances[0], "Virtual A balances do not match");
assertEq(newVirtualBalances[1], oldVirtualBalances[1], "Virtual B balances do not match");
}

function testSwapExactOut_Fuzz(uint256 exactAmountOut) public {
exactAmountOut = bound(exactAmountOut, 1e6, _INITIAL_BALANCE_B);

uint256[] memory virtualBalances = _calculateVirtualBalances();
uint256 invariantBefore = _getCurrentInvariant();

vm.prank(alice);
router.swapSingleTokenExactOut(pool, dai, usdc, exactAmountOut, UINT256_MAX, UINT256_MAX, false, new bytes(0));

uint256 invariantAfter = _getCurrentInvariant();
assertLe(invariantBefore, invariantAfter, "Invariant should not decrease");

uint256[] memory currentVirtualBalances = AclAmmPool(pool).getLastVirtualBalances();
assertEq(currentVirtualBalances[0], virtualBalances[0], "Virtual A balances don't equal");
assertEq(currentVirtualBalances[1], virtualBalances[1], "Virtual B balances don't equal");
}

function testAddLiquidity_Fuzz(uint256 exactBptAmountOut) public {
Expand Down Expand Up @@ -238,8 +252,8 @@ contract AclAmmPoolVirtualBalancesTest is BaseAclAmmTest {
virtualBalances = new uint256[](2);

uint256 sqrtQMinusOne = sqrtQ0() - FixedPoint.ONE;
virtualBalances[0] = initialABalance.divDown(sqrtQMinusOne);
virtualBalances[1] = initialBBalance.divDown(sqrtQMinusOne);
virtualBalances[0] = _INITIAL_BALANCE_A.divDown(sqrtQMinusOne);
virtualBalances[1] = _INITIAL_BALANCE_B.divDown(sqrtQMinusOne);
}

function _createNewPool() internal returns (address initalPool, address newPool) {
Expand Down
9 changes: 4 additions & 5 deletions test/foundry/utils/BaseAclAmmTest.sol
Original file line number Diff line number Diff line change
Expand Up @@ -57,14 +57,13 @@ contract BaseAclAmmTest is AclAmmPoolContractsDeployer, BaseVaultTest {
(daiIdx, usdcIdx) = getSortedIndexes(address(dai), address(usdc));
}

function setSqrtQ0(uint256 minPrice, uint256 maxPrice) internal {
uint256 doubleQ0 = maxPrice.divDown(minPrice);
uint256 Q0 = GyroPoolMath.sqrt(doubleQ0, 5);
function setPriceRange(uint256 priceRange) internal {
uint256 Q0 = GyroPoolMath.sqrt(priceRange, 5);
_sqrtQ0 = GyroPoolMath.sqrt(Q0, 5);
}

function setSqrtQ0(uint256 sqrtQ0_) internal {
_sqrtQ0 = sqrtQ0_;
function setSqrtQ0(uint256 newSqrtQ0) internal {
_sqrtQ0 = newSqrtQ0;
}

function sqrtQ0() internal view returns (uint256) {
Expand Down
Loading