Skip to content

Commit 771eaba

Browse files
authoredMar 17, 2025··
Update Price Range (Q0) and recalculate price interval (#9)
1 parent a3ce88e commit 771eaba

File tree

9 files changed

+110
-70
lines changed

9 files changed

+110
-70
lines changed
 

‎.github/actions/setup/action.yml

+16-11
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,11 @@ runs:
77
uses: actions/setup-node@v4
88
with:
99
node-version: 18.15
10+
- name: Install Foundry
11+
uses: foundry-rs/foundry-toolchain@v1
12+
- name: Forge install
13+
shell: bash
14+
run: forge install
1015
- name: Hash submodule commit
1116
id: hash-submodule
1217
shell: bash
@@ -21,24 +26,24 @@ runs:
2126
uses: actions/cache@v4
2227
id: cache-lib-modules
2328
with:
24-
path: './lib/balancer-v3-monorepo/node_modules/*'
25-
key: lib-${{ hashFiles('./submodule-hash.txt') }}
29+
path: './lib/balancer-v3-monorepo/**/node_modules/*'
30+
key: lib-modules-${{ hashFiles('./submodule-hash.txt') }}
2631
- name: Cache library artifacts
2732
uses: actions/cache@v4
2833
id: cache-lib-artifacts
2934
with:
30-
path: './lib/balancer-v3-monorepo/pkg/*'
31-
key: lib-${{ hashFiles('./submodule-hash.txt') }}
32-
- name: Install Foundry
33-
uses: foundry-rs/foundry-toolchain@v1
35+
path: './lib/balancer-v3-monorepo/pkg/**/artifacts/*'
36+
key: lib-pkg-${{ hashFiles('./submodule-hash.txt') }}
37+
- name: Cache library typechain
38+
uses: actions/cache@v4
39+
id: cache-lib-typechain
40+
with:
41+
path: './lib/balancer-v3-monorepo/pkg/**/typechain-types/*'
42+
key: lib-typechain-${{ hashFiles('./submodule-hash.txt') }}
3443
- name: Install lcov
3544
shell: bash
3645
run: sudo apt-get install lcov
3746
- name: Install fresh
3847
shell: bash
3948
run: sh ./scripts/install-fresh.sh
40-
if: steps.cache-lib-artifacts.outputs.cache-hit != 'true' || steps.cache-lib-modules.outputs.cache-hit != 'true'
41-
- name: Yarn
42-
run: yarn --immutable
43-
shell: bash
44-
if: steps.cache-modules.outputs.cache-hit != 'true'
49+
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'

‎contracts/AclAmmPool.sol

+2-5
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,6 @@ contract AclAmmPool is
7676
balancesScaled18,
7777
_virtualBalances,
7878
_c,
79-
_calculateCurrentSqrtQ0(),
8079
_lastTimestamp,
8180
_centerednessMargin,
8281
_sqrtQ0State,
@@ -97,10 +96,9 @@ contract AclAmmPool is
9796
request.balancesScaled18,
9897
_virtualBalances,
9998
_c,
100-
_calculateCurrentSqrtQ0(),
10199
_lastTimestamp,
102-
_centerednessMargin,
103100
block.timestamp,
101+
_centerednessMargin,
104102
_sqrtQ0State
105103
);
106104
_lastTimestamp = block.timestamp;
@@ -188,10 +186,9 @@ contract AclAmmPool is
188186
balancesScaled18,
189187
_virtualBalances,
190188
_c,
191-
_calculateCurrentSqrtQ0(),
192189
_lastTimestamp,
193-
_centerednessMargin,
194190
block.timestamp,
191+
_centerednessMargin,
195192
_sqrtQ0State
196193
);
197194
}

‎contracts/lib/AclAmmMath.sol

+49-25
Original file line numberDiff line numberDiff line change
@@ -22,27 +22,33 @@ library AclAmmMath {
2222
uint256[] memory balancesScaled18,
2323
uint256[] memory lastVirtualBalances,
2424
uint256 c,
25-
uint256 sqrtQ0,
2625
uint256 lastTimestamp,
2726
uint256 centerednessMargin,
2827
SqrtQ0State memory sqrtQ0State,
2928
Rounding rounding
3029
) internal view returns (uint256) {
31-
function(uint256, uint256) pure returns (uint256) _mulUpOrDown = rounding == Rounding.ROUND_DOWN
32-
? FixedPoint.mulDown
33-
: FixedPoint.mulUp;
34-
3530
(uint256[] memory virtualBalances, ) = getVirtualBalances(
3631
balancesScaled18,
3732
lastVirtualBalances,
3833
c,
39-
sqrtQ0,
4034
lastTimestamp,
41-
centerednessMargin,
4235
block.timestamp,
36+
centerednessMargin,
4337
sqrtQ0State
4438
);
4539

40+
return computeInvariant(balancesScaled18, virtualBalances, rounding);
41+
}
42+
43+
function computeInvariant(
44+
uint256[] memory balancesScaled18,
45+
uint256[] memory virtualBalances,
46+
Rounding rounding
47+
) internal pure returns (uint256) {
48+
function(uint256, uint256) pure returns (uint256) _mulUpOrDown = rounding == Rounding.ROUND_DOWN
49+
? FixedPoint.mulDown
50+
: FixedPoint.mulUp;
51+
4652
return _mulUpOrDown((balancesScaled18[0] + virtualBalances[0]), (balancesScaled18[1] + virtualBalances[1]));
4753
}
4854

@@ -93,19 +99,27 @@ library AclAmmMath {
9399
uint256[] memory balancesScaled18,
94100
uint256[] memory lastVirtualBalances,
95101
uint256 c,
96-
uint256 sqrtQ0,
97102
uint256 lastTimestamp,
103+
uint256 currentTimestamp,
98104
uint256 centerednessMargin,
99-
uint256 currentTime,
100105
SqrtQ0State memory sqrtQ0State //TODO: optimize gas usage
101106
) internal view returns (uint256[] memory virtualBalances, bool changed) {
102107
// TODO Review rounding
103108
// TODO: try to find better way to change the virtual balances in storage
104109

105-
virtualBalances = new uint256[](balancesScaled18.length);
110+
virtualBalances = lastVirtualBalances;
111+
112+
// Calculate currentSqrtQ0
113+
uint256 currentSqrtQ0 = calculateSqrtQ0(
114+
currentTimestamp,
115+
sqrtQ0State.startSqrtQ0,
116+
sqrtQ0State.endSqrtQ0,
117+
sqrtQ0State.startTime,
118+
sqrtQ0State.endTime
119+
);
106120

107121
if (isPoolInRange(balancesScaled18, lastVirtualBalances, centerednessMargin) == false) {
108-
uint256 q0 = sqrtQ0.mulDown(sqrtQ0);
122+
uint256 q0 = currentSqrtQ0.mulDown(currentSqrtQ0);
109123

110124
if (isAboveCenter(balancesScaled18, lastVirtualBalances)) {
111125
virtualBalances[1] = lastVirtualBalances[1].mulDown(
@@ -126,26 +140,35 @@ library AclAmmMath {
126140
}
127141

128142
changed = true;
129-
} else if (sqrtQ0State.startTime != 0 && currentTime > sqrtQ0State.startTime) {
130-
uint256 rACenter = lastVirtualBalances[0].mulDown(sqrtQ0State.startSqrtQ0 - FixedPoint.ONE);
131-
uint256 rBCenter = lastVirtualBalances[1].mulDown(sqrtQ0State.startSqrtQ0 - FixedPoint.ONE);
143+
}
132144

133-
uint256 currentSqrtQ0 = calculateSqrtQ0(
134-
currentTime,
145+
if (
146+
sqrtQ0State.startTime != 0 &&
147+
currentTimestamp > sqrtQ0State.startTime &&
148+
(currentTimestamp < sqrtQ0State.endTime || lastTimestamp < sqrtQ0State.endTime)
149+
) {
150+
uint256 lastSqrtQ0 = calculateSqrtQ0(
151+
lastTimestamp,
135152
sqrtQ0State.startSqrtQ0,
136153
sqrtQ0State.endSqrtQ0,
137154
sqrtQ0State.startTime,
138155
sqrtQ0State.endTime
139156
);
140157

158+
// Ra_center = Va * (lastSqrtQ0 - 1)
159+
uint256 rACenter = lastVirtualBalances[0].mulDown(lastSqrtQ0 - FixedPoint.ONE);
160+
161+
// Va = Ra_center / (currentSqrtQ0 - 1)
141162
virtualBalances[0] = rACenter.divDown(currentSqrtQ0 - FixedPoint.ONE);
142-
virtualBalances[1] = rBCenter.divDown(currentSqrtQ0 - FixedPoint.ONE);
143163

144-
if (currentTime >= sqrtQ0State.endTime) {
145-
changed = true;
146-
}
147-
} else {
148-
virtualBalances = lastVirtualBalances;
164+
uint256 currentInvariant = computeInvariant(balancesScaled18, lastVirtualBalances, Rounding.ROUND_DOWN);
165+
166+
// Vb = currentInvariant / (currentQ0 * Va)
167+
virtualBalances[1] = currentInvariant.divDown(
168+
currentSqrtQ0.mulDown(currentSqrtQ0).mulDown(virtualBalances[0])
169+
);
170+
171+
changed = true;
149172
}
150173
}
151174

@@ -188,12 +211,13 @@ library AclAmmMath {
188211
return startSqrtQ0;
189212
} else if (currentTime >= endTime) {
190213
return endSqrtQ0;
214+
} else if (startSqrtQ0 == endSqrtQ0) {
215+
return endSqrtQ0;
191216
}
192217

193-
uint256 numerator = ((endTime - currentTime) * startSqrtQ0) + ((currentTime - startTime) * endSqrtQ0);
194-
uint256 denominator = endTime - startTime;
218+
uint256 exponent = (currentTime - startTime).divDown(endTime - startTime);
195219

196-
return numerator / denominator;
220+
return startSqrtQ0.mulDown(LogExpMath.pow(endSqrtQ0, exponent)).divDown(LogExpMath.pow(startSqrtQ0, exponent));
197221
}
198222

199223
function isAboveCenter(

‎contracts/test/AclAmmMathMock.sol

+2-6
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ contract AclAmmMathMock {
1010
uint256[] memory balancesScaled18,
1111
uint256[] memory lastVirtualBalances,
1212
uint256 c,
13-
uint256 sqrtQ0,
1413
uint256 lastTimestamp,
1514
uint256 centerednessMargin,
1615
SqrtQ0State memory sqrtQ0State,
@@ -21,7 +20,6 @@ contract AclAmmMathMock {
2120
balancesScaled18,
2221
lastVirtualBalances,
2322
c,
24-
sqrtQ0,
2523
lastTimestamp,
2624
centerednessMargin,
2725
sqrtQ0State,
@@ -74,21 +72,19 @@ contract AclAmmMathMock {
7472
uint256[] memory balancesScaled18,
7573
uint256[] memory lastVirtualBalances,
7674
uint256 c,
77-
uint256 sqrtQ0,
7875
uint256 lastTimestamp,
76+
uint256 currentTimestamp,
7977
uint256 centerednessMargin,
80-
uint256 currentTime,
8178
SqrtQ0State memory sqrtQ0State
8279
) external view returns (uint256[] memory virtualBalances, bool changed) {
8380
return
8481
AclAmmMath.getVirtualBalances(
8582
balancesScaled18,
8683
lastVirtualBalances,
8784
c,
88-
sqrtQ0,
8985
lastTimestamp,
86+
currentTimestamp,
9087
centerednessMargin,
91-
currentTime,
9288
sqrtQ0State
9389
);
9490
}

‎test/AclAmmMath.test.ts

+4-1
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,11 @@ import { deploy } from '@balancer-labs/v3-helpers/src/contract';
33
import { expect } from 'chai';
44
import { bn } from '@balancer-labs/v3-helpers/src/numbers';
55
import { calculateSqrtQ0 } from './utils/aclAmmMath';
6+
import { expectEqualWithError } from './utils/relativeError';
67

78
describe('AclAmmMath', function () {
9+
const EXPECTED_RELATIVE_ERROR = 1e-12;
10+
811
let mock: Contract;
912

1013
before(async function () {
@@ -36,7 +39,7 @@ describe('AclAmmMath', function () {
3639
const contractResult = await mock.calculateSqrtQ0(currentTime, startSqrtQ0Fp, endSqrtQ0Fp, startTime, endTime);
3740
const mathResult = calculateSqrtQ0(currentTime, startSqrtQ0Fp, endSqrtQ0Fp, startTime, endTime);
3841

39-
expect(contractResult).to.equal(mathResult);
42+
expectEqualWithError(contractResult, mathResult, EXPECTED_RELATIVE_ERROR);
4043
});
4144
});
4245
});

‎test/foundry/AclAmmMath.t.sol

+4-13
Original file line numberDiff line numberDiff line change
@@ -37,14 +37,7 @@ contract AclAmmMathTest is Test {
3737

3838
bool isInRange = AclAmmMath.isPoolInRange(balancesScaled18, virtualBalances, centerednessMargin);
3939

40-
if (balance0 == 0 || balance1 == 0) {
41-
assertEq(isInRange, false);
42-
} else {
43-
assertEq(
44-
isInRange,
45-
AclAmmMath.calculateCenteredness(balancesScaled18, virtualBalances) >= centerednessMargin
46-
);
47-
}
40+
assertEq(isInRange, AclAmmMath.calculateCenteredness(balancesScaled18, virtualBalances) >= centerednessMargin);
4841
}
4942

5043
function testCalculateCenteredness__Fuzz(
@@ -98,9 +91,7 @@ contract AclAmmMathTest is Test {
9891

9992
bool isAboveCenter = AclAmmMath.isAboveCenter(balancesScaled18, virtualBalances);
10093

101-
if (balance0 == 0) {
102-
assertEq(isAboveCenter, false);
103-
} else if (balance1 == 0) {
94+
if (balance1 == 0) {
10495
assertEq(isAboveCenter, true);
10596
} else {
10697
assertEq(isAboveCenter, balance0.divDown(balance1) > virtualBalance0.divDown(virtualBalance1));
@@ -118,8 +109,8 @@ contract AclAmmMathTest is Test {
118109
startTime = bound(startTime, 1, endTime - 1);
119110
currentTime = bound(currentTime, startTime, endTime);
120111

121-
endSqrtQ0 = bound(endSqrtQ0, 1, type(uint128).max);
122-
startSqrtQ0 = bound(endSqrtQ0, 1, type(uint128).max);
112+
endSqrtQ0 = bound(endSqrtQ0, FixedPoint.ONE, type(uint128).max);
113+
startSqrtQ0 = bound(endSqrtQ0, FixedPoint.ONE, type(uint128).max);
123114

124115
uint256 sqrtQ0 = AclAmmMath.calculateSqrtQ0(currentTime, startSqrtQ0, endSqrtQ0, startTime, endTime);
125116

‎test/foundry/AclAmmPoolVirtualBalances.t.sol

+4-4
Original file line numberDiff line numberDiff line change
@@ -152,21 +152,21 @@ contract AclAmmPoolVirtualBalancesTest is BaseAclAmmTest {
152152
poolVirtualBalancesBefore[0],
153153
"Virtual A balance after should be less than before"
154154
);
155-
assertLt(
155+
assertGt(
156156
poolVirtualBalancesAfter[1],
157157
poolVirtualBalancesBefore[1],
158-
"Virtual B balance after should be less than before"
158+
"Virtual B balance after should be greater than before"
159159
);
160160
} else {
161161
assertGe(
162162
poolVirtualBalancesAfter[0],
163163
poolVirtualBalancesBefore[0],
164164
"Virtual A balance after should be greater than before"
165165
);
166-
assertGe(
166+
assertLe(
167167
poolVirtualBalancesAfter[1],
168168
poolVirtualBalancesBefore[1],
169-
"Virtual B balance after should be greater than before"
169+
"Virtual B balance after should be less than before"
170170
);
171171
}
172172
}

‎test/utils/aclAmmMath.ts

+7-5
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { BigNumberish } from 'ethers';
2-
import { bn } from '@balancer-labs/v3-helpers/src/numbers';
2+
import { bn, fp, fpDivDown, fromFp } from '@balancer-labs/v3-helpers/src/numbers';
33

44
export function calculateSqrtQ0(
55
currentTime: number,
@@ -8,12 +8,14 @@ export function calculateSqrtQ0(
88
startTime: number,
99
endTime: number
1010
): bigint {
11-
if (currentTime > endTime) {
11+
if (currentTime < startTime) {
12+
return bn(startSqrtQ0Fp);
13+
} else if (currentTime >= endTime) {
1214
return bn(endSqrtQ0Fp);
1315
}
1416

15-
const numerator = bn(endTime - currentTime) * bn(startSqrtQ0Fp) + bn(currentTime - startTime) * bn(endSqrtQ0Fp);
16-
const denominator = bn(endTime - startTime);
17+
const exponent = fromFp(fpDivDown(fp(currentTime - startTime), fp(endTime - startTime)));
18+
const base = fromFp(fpDivDown(endSqrtQ0Fp, startSqrtQ0Fp));
1719

18-
return numerator / denominator;
20+
return fp(fromFp(startSqrtQ0Fp).mul(base.pow(exponent)));
1921
}

‎test/utils/relativeError.ts

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import { expect } from 'chai';
2+
import { BigNumberish } from 'ethers';
3+
import { bn, pct } from '../../lib/balancer-v3-monorepo/pvt/helpers/src/numbers';
4+
5+
export function expectEqualWithError(
6+
actual: BigNumberish,
7+
expected: BigNumberish,
8+
error: BigNumberish = 0.001,
9+
message?: string
10+
): void {
11+
actual = bn(actual);
12+
expected = bn(expected);
13+
const acceptedError = pct(expected, error);
14+
15+
if (actual >= 0) {
16+
expect(Number(actual)).to.be.at.least(Number(expected - acceptedError), message);
17+
expect(Number(actual)).to.be.at.most(Number(expected + acceptedError), message);
18+
} else {
19+
expect(Number(actual)).to.be.at.most(Number(expected - acceptedError), message);
20+
expect(Number(actual)).to.be.at.least(Number(expected + acceptedError), message);
21+
}
22+
}

0 commit comments

Comments
 (0)
Please sign in to comment.