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

Spearbit post-audit fixes #89

Merged
merged 183 commits into from
Apr 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
183 commits
Select commit Hold shift + click to select a range
95b6928
test for LP token balance delta on allocate
kinrezC Mar 18, 2024
8cff3fc
reproduce audit finding
kinrezC Mar 18, 2024
8c675cc
fix liquidity management order of ops
kinrezC Mar 18, 2024
0557365
fix lptoken allocate test assertions
kinrezC Mar 18, 2024
27381ed
fix: optimize duplicate tokens loop check
clemlak Mar 19, 2024
8040789
fix: DFMM init now returns the totalLiquidity of the pool
clemlak Mar 19, 2024
d30b822
fix: check if token is WETH in _transferFrom
clemlak Mar 19, 2024
43b769a
test: use WETH address in _transferFrom tests
clemlak Mar 19, 2024
e1c4eea
feat: avoid stuck ETH in _transferFrom
clemlak Mar 19, 2024
849cac0
test: add test_DFMM_transferFrom_UsesWETH
clemlak Mar 19, 2024
6594c38
test: add test_DFMM_transferFrom_UsesWETHAndRefunds
clemlak Mar 19, 2024
c62ca0e
feat: add balance zero check before refunding ETH in _transferFrom
clemlak Mar 19, 2024
fe89db1
chore: add commit to OZ clone function link
clemlak Mar 19, 2024
bf1102c
feat: add InvalidReserves error
clemlak Mar 19, 2024
ed37a9b
fix: compare reserves and tokens length in init
clemlak Mar 19, 2024
1fe6809
fix: update controller fees minting in swap
clemlak Mar 19, 2024
6d3eeb9
test: update test_DFMM_swap_MintsLPTokensToFeeCollector
clemlak Mar 19, 2024
051f786
fix: typo in _computeDeallocateDeltasGivenDeltaL NatSpec
clemlak Mar 19, 2024
cfd7ddc
fix: add missing NatSpec to Pool and InitParams structs
clemlak Mar 19, 2024
163d7d3
fix: return controller in ConstantSum getPoolParams
clemlak Mar 19, 2024
21031b5
test: add test_ConstantSum_getPoolParams
clemlak Mar 19, 2024
fa84ff1
test: update ConstantSum getPoolParams test
clemlak Mar 19, 2024
88b596b
fix: add missing controller param in ConstantSum init
clemlak Mar 19, 2024
982765a
fix: ConstantSum computeTradingFunction, computeInitialPoolData
clemlak Mar 20, 2024
746cbc1
fix: compute deltaLiquidity in validateAllocate in ConstantSum
clemlak Mar 20, 2024
3f2bd90
fix: override validateDeallocate in ConstantSum
clemlak Mar 20, 2024
f3698b1
fix: remove incorrect functions in ConstantSumMath
clemlak Mar 20, 2024
5fcbba3
test: update test_ConstantSum_allocate_Works
clemlak Mar 20, 2024
94a304d
test: fix ConstantSum init test
clemlak Mar 21, 2024
bd3c680
test: add test_ConstantSum_init_TransfersTokens
clemlak Mar 21, 2024
b30f8b4
test: update test_ConstantSum_init_InitializesPool
clemlak Mar 21, 2024
f82f84c
test: move ConstantSum init test to dedicated test file
clemlak Mar 22, 2024
4f29be9
test: add test_ConstantSum_init_StoresPoolParams
clemlak Mar 22, 2024
9f6da08
test: move ConstantSum no swap fees tests
clemlak Mar 22, 2024
c63ce0c
test: move ConstantSum validateSwap tests to dedicated test file
clemlak Mar 22, 2024
8fbbc8c
test: move ConstantSum swap tests to dedicated test file
clemlak Mar 22, 2024
a946a7c
test: remove old ConstantSumTest
clemlak Mar 22, 2024
1fe5af1
test: add ConstantSum update tests
clemlak Mar 22, 2024
fb90559
test: add test_ConstantSum_getPoolParams_ReturnsPoolParams
clemlak Mar 22, 2024
8d8f8cb
test: add test_ConstantSum_constructor
clemlak Mar 22, 2024
0903141
fix: add InvalidReservesLength error to PairStrategy
clemlak Mar 22, 2024
af73745
fix: check reserves and tokens array length in ConstantSum
clemlak Mar 22, 2024
ca780dc
fix: add pool reserves length check in G3M
clemlak Mar 22, 2024
3bd1bc3
fix: add reserves and tokens array length check in LogNormal
clemlak Mar 22, 2024
6154ecd
fix: remove reserveX and reserveY in G3M init struct
clemlak Mar 22, 2024
63ed521
fix: use mulDiv to compute G3M deltaLiquidity
clemlak Mar 22, 2024
8ab9767
Merge pull request #73 from primitivefinance/fix/lp-token-tracking
clemlak Mar 25, 2024
fba3699
Merge branch 'fix/spearbit-audit' into fix/duplicate-tokens-loop
clemlak Mar 25, 2024
687a0e3
Merge pull request #74 from primitivefinance/fix/duplicate-tokens-loop
clemlak Mar 25, 2024
dbdbe65
Merge branch 'fix/spearbit-audit' into fix/returned-emitted-init-liqu…
clemlak Mar 25, 2024
923b3a7
Merge pull request #75 from primitivefinance/fix/returned-emitted-ini…
clemlak Mar 25, 2024
674f6aa
Merge branch 'fix/spearbit-audit' into fix/update-clone-commit-link
clemlak Mar 25, 2024
c9e4d9f
Merge pull request #77 from primitivefinance/fix/update-clone-commit-…
clemlak Mar 25, 2024
09fc5ec
Merge branch 'fix/spearbit-audit' into fix/compare-tokens-reserves-le…
clemlak Mar 25, 2024
436d805
Merge branch 'fix/spearbit-audit' into fix/controller-swap-fees
clemlak Mar 25, 2024
8d6fffe
Merge pull request #79 from primitivefinance/fix/compare-tokens-reser…
clemlak Mar 25, 2024
45e6364
Merge branch 'fix/spearbit-audit' into fix/natspec-typo-compute-deall…
clemlak Mar 25, 2024
55abf6c
Merge pull request #81 from primitivefinance/fix/natspec-typo-compute…
clemlak Mar 25, 2024
f8a507a
Merge branch 'fix/spearbit-audit' into fix/idfmm-natspec
clemlak Mar 25, 2024
1f70427
Merge pull request #82 from primitivefinance/fix/idfmm-natspec
clemlak Mar 25, 2024
19b012e
Merge branch 'fix/spearbit-audit' into fix/missing-constant-sum-contr…
clemlak Mar 25, 2024
7e930dc
Merge pull request #83 from primitivefinance/fix/missing-constant-sum…
clemlak Mar 25, 2024
43da6f2
Merge branch 'fix/spearbit-audit' into test/constant-sum-tests
clemlak Mar 25, 2024
22967e4
test: fix test_ConstantSum_getPoolParams_ReturnsPoolParams
clemlak Mar 25, 2024
c2fdd68
Merge pull request #84 from primitivefinance/test/constant-sum-tests
clemlak Mar 25, 2024
e3f07d2
feat: G3M init pool data now expects array of reserves
clemlak Mar 25, 2024
03bf4f4
Merge branch 'fix/spearbit-audit' into fix/reserves-length-check
clemlak Mar 25, 2024
7033738
Merge pull request #85 from primitivefinance/fix/reserves-length-check
clemlak Mar 25, 2024
df79f5a
Merge branch 'fix/spearbit-audit' into fix/g3m-init-struct
clemlak Mar 25, 2024
e6496e6
Merge pull request #86 from primitivefinance/fix/g3m-init-struct
clemlak Mar 25, 2024
5242573
Merge branch 'fix/spearbit-audit' into fix/g3m-compute-delta-liquidity
clemlak Mar 25, 2024
c6fa69b
Merge pull request #87 from primitivefinance/fix/g3m-compute-delta-li…
clemlak Mar 25, 2024
c2f0b5f
Merge branch 'fix/spearbit-audit' into fix/transfer-functions
clemlak Mar 25, 2024
6e84fcf
feat: update _transferFrom to use arrays of token and address
clemlak Mar 26, 2024
3e9497c
test: update _transferFrom tests
clemlak Mar 26, 2024
de6d27b
fix: remove unchecked scope for toUint
clemlak Mar 26, 2024
8aebaae
fix: use mulDiv to compute deltas in LogNormalMath
clemlak Mar 26, 2024
d18948c
fix: max values for width and mean in LogNormal
clemlak Mar 26, 2024
5a37380
feat: add mean and witdth max value check in init and update
clemlak Mar 26, 2024
99b0158
chore: remove mention of tau in LogNormalMath
clemlak Mar 26, 2024
ad69860
chore: rename old K to mean
clemlak Mar 26, 2024
3f94e01
fix: add _computeSwapDeltaLiquidity to PairStrategy
clemlak Mar 27, 2024
dad2ff5
feat: add computeSwapDeltaLiquidity to ConstantSumMath
clemlak Mar 27, 2024
869f482
feat: add _computeSwapDeltaLiquidity to ConstantSum
clemlak Mar 27, 2024
bda4065
feat: update _computeSwapDeltaLiquidity in PairStrategy
clemlak Mar 27, 2024
e591dea
chore: add missing NatSpec to _computeSwapDeltaLiquidity
clemlak Mar 27, 2024
28b310c
feat: add computeSwapDeltaLiquidity to G3MMath
clemlak Mar 27, 2024
c9c6f53
feat: add _computeSwapDeltaLiquidity to G3M
clemlak Mar 27, 2024
b707a67
feat: add computeSwapXForYDeltaLiquidity to LogNormalMath
clemlak Mar 27, 2024
e05bcb8
feat: add computeSwapYForXDeltaLiquidity in LogNormalMath
clemlak Mar 27, 2024
4d4b108
feat: add wip _computeSwapDeltaLiquidity to LogNormal
clemlak Mar 27, 2024
8c8ce08
feat: compute price in LogNormal _computeSwapDeltaLiquidity
clemlak Mar 27, 2024
9562053
feat: add _computeSwapDeltaLiquidity to NTokenStrategy
clemlak Mar 27, 2024
74912bc
feat: add _computeSwapDeltaLiquidity to NTokenGeometricMean
clemlak Mar 27, 2024
6a3c914
feat: add computeSwapDeltaLiquidity to NTokenG3MMath
clemlak Mar 27, 2024
a6a9ae3
feat: remove deltaLiquidity from simulateSwap in LogNormalSolver
clemlak Mar 27, 2024
2bec4ff
feat: use same functions to compute swap deltaLiquidity in LogNormal …
clemlak Mar 27, 2024
166cbaf
feat: fix computeSwapDeltaLiquidity formula in G3MMath
clemlak Mar 27, 2024
a984a6d
feat: remove deltaLiquidity parameter in simulateSwap from ConstantSu…
clemlak Mar 27, 2024
ba12fb2
feat: remove deltaLiquidity parameter in simulateSwap from G3MSolver
clemlak Mar 27, 2024
85a1695
feat: remove deltaLiquidity parameter in simulateSwap from NTokenG3MS…
clemlak Mar 27, 2024
fc63b45
feat: fix computeSwapDeltaLiquidity in ConstantSumMath
clemlak Mar 27, 2024
9bc37f2
Merge branch 'fix/spearbit-audit' into fix/swap-delta-liquidity
clemlak Mar 28, 2024
fdadbbd
fix: ConstantSum computeSwapDeltaLiquidity
clemlak Mar 28, 2024
46c1027
feat: simplify _computeSwapDeltaLiquidity in ConstantSum
clemlak Mar 29, 2024
7699e52
feat: use mulDiv in computeSwapDeltaLiquidity for ConstantSum
clemlak Mar 29, 2024
6f12c05
feat: use computeSwapDeltaLiquidity in ConstantSumSolver
clemlak Mar 29, 2024
8fc862a
Merge pull request #76 from primitivefinance/fix/transfer-functions
clemlak Mar 29, 2024
4052863
Merge branch 'fix/spearbit-audit' into fix/controller-swap-fees
clemlak Mar 29, 2024
f182bac
feat: round up pool controller swap fees in DFMM swap
clemlak Mar 29, 2024
1a2a4b4
feat: check controller fee instead of controller address in DFMM swap
clemlak Mar 29, 2024
95744d2
Merge pull request #90 from primitivefinance/fix/remove-touint-unchecked
clemlak Mar 29, 2024
e70b54e
Merge branch 'fix/spearbit-audit' into fix/log-normal-deltas-rounding
clemlak Mar 29, 2024
740d28d
Merge pull request #91 from primitivefinance/fix/log-normal-deltas-ro…
clemlak Mar 29, 2024
dce5f77
feat: add min bounds for LogNormal width and mean
clemlak Mar 29, 2024
eccb50c
Merge branch 'fix/spearbit-audit' into fix/log-normal-bounded-params
clemlak Mar 29, 2024
78ddd8c
fix: add missing closing bracket in LogNormal
clemlak Mar 29, 2024
a8f02f3
Merge branch 'fix/spearbit-audit' into fix/log-normal-fixed-vars
clemlak Mar 29, 2024
6311ddc
Merge pull request #94 from primitivefinance/fix/log-normal-fixed-vars
clemlak Mar 29, 2024
e7a7867
Merge pull request #95 from primitivefinance/fix/swap-delta-liquidity
clemlak Mar 29, 2024
1317937
Merge pull request #97 from primitivefinance/fix/log-normal-trading-f…
clemlak Mar 29, 2024
744e716
fix: remove useless return in decodeFeeUpdate in ConstantSumUtils
clemlak Mar 29, 2024
da8d520
fix: remove useless library using in ConstantSumMath
clemlak Mar 29, 2024
802491b
fix: remove useless library using in ConstantSumSolver
clemlak Mar 29, 2024
5c52003
fix: remove unused wX parameter in InitState in NTokenG3M
clemlak Mar 29, 2024
28315ce
fix: move test constants in StrategyLib into test files
clemlak Mar 29, 2024
a7cf0aa
fix: add deltas array length check in NTokenStrategy
clemlak Mar 29, 2024
26f2f8c
fix: use mulDiv for most operations in StrategyLib
clemlak Mar 29, 2024
0f5fc6f
Merge branch 'fix/spearbit-audit' into fix/strategy-lib-muldiv
clemlak Mar 29, 2024
80bc05e
fix: change rounding in G3M trading function
clemlak Mar 29, 2024
d5b485d
fix: prevent weightX to be 0
clemlak Mar 29, 2024
d4b6fd2
fix: add weightX check in G3M update
clemlak Mar 29, 2024
6d2faab
fix: add missing onlyDFMM modifier in NTokenG3M
clemlak Mar 29, 2024
9605b1f
fix: add remainder to dynamic parameter calculation
clemlak Mar 29, 2024
3c3b1ae
fix: remove totalLiquidity from init in ConstantSum
clemlak Mar 29, 2024
7b8df07
Merge branch 'fix/spearbit-audit' into fix/total-liquidity-init
clemlak Mar 29, 2024
6001c4e
feat: remove L from ConstantSum init pool data
clemlak Mar 29, 2024
276ceb3
feat: reintroduce EPSILON
clemlak Mar 29, 2024
3333eaf
feat: use EPSILON to check invariant in ConstantSum ini
clemlak Mar 29, 2024
e5db5ac
feat: use EPSILON to check invariant in G3M init
clemlak Mar 29, 2024
5a7b2bc
feat: use EPSILON to check invariant in LogNormal init
clemlak Mar 29, 2024
6ecc8e7
feat: use EPSILON to check invariant in NTokenG3M init
clemlak Mar 29, 2024
d9f23f8
Merge branch 'fix/spearbit-audit' into fix/controller-swap-fees
clemlak Mar 29, 2024
c6209b8
fix: fix `SetsValue*` tests to account for instantaneous `lastCompute…
kinrezC Mar 29, 2024
86b6b0b
Merge pull request #80 from primitivefinance/fix/controller-swap-fees
clemlak Apr 1, 2024
af6ac26
Merge branch 'fix/spearbit-audit' into fix/minor-issues
clemlak Apr 1, 2024
62eeb36
Merge pull request #98 from primitivefinance/fix/minor-issues
clemlak Apr 1, 2024
fa3af60
Merge branch 'fix/spearbit-audit' into fix/g3m-weight-check
clemlak Apr 1, 2024
c38bb61
Merge pull request #102 from primitivefinance/fix/g3m-weight-check
clemlak Apr 1, 2024
8cb531c
Merge branch 'fix/spearbit-audit' into fix/g3m-trading-function-rounding
clemlak Apr 1, 2024
274696a
Merge pull request #101 from primitivefinance/fix/g3m-trading-functio…
clemlak Apr 1, 2024
22313f9
Merge branch 'fix/spearbit-audit' into fix/missing-modifier-ntokeng3m
clemlak Apr 1, 2024
7406c0e
Merge pull request #103 from primitivefinance/fix/missing-modifier-nt…
clemlak Apr 1, 2024
49a20c7
Merge pull request #99 from primitivefinance/fix/ntoken-deltas-array-…
clemlak Apr 1, 2024
0f0ce53
fix: remove computeLGivenY in G3MMath
clemlak Apr 1, 2024
2069ef1
Merge branch 'fix/spearbit-audit' into fix/g3m-math
clemlak Apr 1, 2024
8a6788f
fix: remove computeYGivenL
clemlak Apr 1, 2024
8a7fa28
fix: use mulDivUp in computeLGivenX
clemlak Apr 1, 2024
03ea755
fix: delete unused imports in G3MSolver
clemlak Apr 1, 2024
11861e8
fix: remove computeXGivenL in G3MMath
clemlak Apr 1, 2024
1316bc2
fix: remove computeX in G3MMath
clemlak Apr 1, 2024
74719d5
Merge pull request #111 from primitivefinance/fix/ntoken-g3m-rounding
clemlak Apr 1, 2024
f332e3d
Merge branch 'fix/spearbit-audit' into fix/log-normal-bounded-params
clemlak Apr 1, 2024
a6ed4ed
Merge pull request #93 from primitivefinance/fix/log-normal-bounded-p…
clemlak Apr 1, 2024
cda904a
fix: use computeDeltaLiquidity in ConstantSum init
clemlak Apr 1, 2024
cebcdca
Merge branch 'fix/spearbit-audit' into fix/total-liquidity-init
clemlak Apr 1, 2024
8bff684
Merge pull request #107 from primitivefinance/fix/total-liquidity-init
clemlak Apr 1, 2024
1972c1c
Merge branch 'fix/spearbit-audit' into fix/strategy-lib-muldiv
clemlak Apr 1, 2024
aa9e127
test: increase default X reserve for LogNormal pools
clemlak Apr 1, 2024
32a5572
Merge pull request #100 from primitivefinance/fix/strategy-lib-muldiv
clemlak Apr 1, 2024
f597df3
Fix/unused imports ntokeng3m utils (#109)
clemlak Apr 1, 2024
79c7b22
Merge branch 'fix/spearbit-audit' into fix/g3m-math
clemlak Apr 2, 2024
4fd811a
Merge branch 'fix/spearbit-audit' into fix/dynamic-library-rounding
clemlak Apr 2, 2024
bf3c7bf
Merge pull request #106 from primitivefinance/fix/dynamic-library-rou…
clemlak Apr 2, 2024
1b9e6ed
Merge branch 'fix/spearbit-audit' into fix/g3m-math
clemlak Apr 2, 2024
a2f111e
Merge pull request #108 from primitivefinance/fix/g3m-math
clemlak Apr 2, 2024
b0c9f6f
Fix/ntoken accumulator (#105)
clemlak Apr 2, 2024
1b7a4dd
fix: optimize token decimals check in DFMM init (#112)
clemlak Apr 2, 2024
7868687
fix: computeNextLiquidity in G3MMath (#114)
clemlak Apr 2, 2024
af5c660
fix: NatSpec in computeLGivenX in LogNormalMath (#113)
clemlak Apr 2, 2024
89e1a96
fix: rounding direction in computeTradingFunction in LogNormalMath
clemlak Apr 3, 2024
32f9862
Merge pull request #117 from primitivefinance/fix/log-normal-trading-…
clemlak Apr 3, 2024
1a3d7b1
Merge branch 'main' into fix/spearbit-audit
clemlak Apr 3, 2024
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
121 changes: 114 additions & 7 deletions src/ConstantSum/ConstantSum.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,18 @@
pragma solidity 0.8.22;

import {
FixedPointMathLib, computeTradingFunction
FixedPointMathLib,
computeTradingFunction,
computeSwapDeltaLiquidity,
computeDeltaLiquidity
} from "./ConstantSumMath.sol";
import {
decodePriceUpdate,
decodeFeeUpdate,
decodeControllerUpdate
} from "./ConstantSumUtils.sol";
import { PairStrategy, IStrategy, Pool } from "src/PairStrategy.sol";
import { EPSILON } from "src/lib/StrategyLib.sol";

struct InternalParams {
uint256 price;
Expand All @@ -33,6 +37,9 @@ enum UpdateCode {
contract ConstantSum is PairStrategy {
using FixedPointMathLib for uint256;

/// @notice Thrown when the expected liquidity is not met.
error InvalidDeltaLiquidity();

/// @inheritdoc IStrategy
string public constant name = "ConstantSum";

Expand All @@ -45,7 +52,7 @@ contract ConstantSum is PairStrategy {
function init(
address,
uint256 poolId,
Pool calldata,
Pool calldata pool,
bytes calldata data
)
public
Expand All @@ -58,21 +65,106 @@ contract ConstantSum is PairStrategy {
)
{
ConstantSumParams memory params;
(reserves, totalLiquidity, params) =
abi.decode(data, (uint256[], uint256, ConstantSumParams));

(reserves, params) = abi.decode(data, (uint256[], ConstantSumParams));
totalLiquidity =
computeDeltaLiquidity(reserves[0], reserves[1], params.price);

if (pool.reserves.length != 2 || reserves.length != 2) {
revert InvalidReservesLength();
}

internalParams[poolId].price = params.price;
internalParams[poolId].swapFee = params.swapFee;
internalParams[poolId].controller = params.controller;

// Get the trading function and check this is valid
invariant =
tradingFunction(reserves, totalLiquidity, abi.encode(params));

valid = invariant >= 0;
valid = invariant >= 0 && invariant <= EPSILON;

return (valid, invariant, reserves, totalLiquidity);
}

function validateAllocate(
address,
uint256 poolId,
Pool memory pool,
bytes calldata data
)
external
view
override
returns (
bool valid,
int256 invariant,
uint256[] memory deltas,
uint256 deltaLiquidity
)
{
(uint256 deltaX, uint256 deltaY, uint256 minDeltaL) =
abi.decode(data, (uint256, uint256, uint256));

deltaLiquidity =
computeDeltaLiquidity(deltaX, deltaY, internalParams[poolId].price);
Comment on lines +109 to +110
Copy link

@MiloTruck MiloTruck Apr 6, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's worth noting that computeDeltaLiquidity() does price.mulWadUp(deltaX), which rounds up.

As such, the rounding direction here actually favors the user as it rounds away from the slippage parameter (ie. the user declares a minimum amount of deltaLiquidity acceptable, but the rounding makes deltaLiquidity larger instead of smaller).

Contrast this with validateAllocate() in other strategies, such as GeometricMean, where the user provides a maximum acceptable amount of tokenX and tokenY that needs to be transferred into the pool, and the amount of tokens calculated rounds up towards the maximum amount specified:

    function _computeAllocateDeltasGivenDeltaL(
        uint256 deltaLiquidity,
        Pool memory pool,
        bytes memory
    ) internal pure override returns (uint256[] memory deltas) {
        deltas = new uint256[](2);
        deltas[0] = computeDeltaGivenDeltaLRoundUp(
            pool.reserves[0], deltaLiquidity, pool.totalLiquidity
        );

        deltas[1] = computeDeltaGivenDeltaLRoundUp(
            pool.reserves[1], deltaLiquidity, pool.totalLiquidity
        );
    }

The effect of this is that you mint 1 wei extra of deltaLiquidity for the amount of tokenX transferred in. However, I don't think there's any serious impact since reserves have 18 decimals, and if deltaLiquidity was disproportionately larger than the amount of tokenX transferred in, the invariant check would just revert.

Recommended fix would be to calculate deltaLiquidity as price.mulWadDown(deltaX) + deltaY for only validateAllocate().

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've introduced another function to compute the liquidity and round up or down accordingly, see: 1e51776.

if (deltaLiquidity < minDeltaL) revert InvalidDeltaLiquidity();
Comment on lines +106 to +111
Copy link

@MiloTruck MiloTruck Apr 4, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there any reason why ConstantSum.validateAllocate() was changed to use deltaX and deltaY as the amount to allocate, and have minDeltaL as the slippage parameter?

The parameters for validateAllocate() in ConstantSum are now inconsistent with other pair strategies, where deltaL is the the amount to allocate and maxDeltaX and maxDeltaY are the slippage parameters. The same goes for ConstantSum.validateDeallocate() as well.

This could be confusing to users - they might call this function thinking that the parameters are the same as the other strategies. I recommend documenting this difference so that users are aware.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, so other strategies are computing the deltas given a specific amount of liquidity delta but with the ConstantSum we had to change that because we need both deltas to compute deltaLiquidity.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see, in that case I don't think this inconsistency is avoidable.


deltas = new uint256[](2);
deltas[0] = deltaX;
deltas[1] = deltaY;

pool.reserves[0] += deltaX;
pool.reserves[1] += deltaY;

invariant = tradingFunction(
pool.reserves,
pool.totalLiquidity + deltaLiquidity,
getPoolParams(poolId)
);

valid = invariant >= 0;
}

function validateDeallocate(
address,
uint256 poolId,
Pool memory pool,
bytes calldata data
)
external
view
override
returns (
bool valid,
int256 invariant,
uint256[] memory deltas,
uint256 deltaLiquidity
)
{
(uint256 deltaX, uint256 deltaY, uint256 maxDeltaL) =
abi.decode(data, (uint256, uint256, uint256));

deltaLiquidity =
computeDeltaLiquidity(deltaX, deltaY, internalParams[poolId].price);
if (maxDeltaL > deltaLiquidity) revert InvalidDeltaLiquidity();
Comment on lines +148 to +150
Copy link

@MiloTruck MiloTruck Apr 4, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This check is reversed, the function should be ensuring that deltaLiquidity is not greater than maxDeltaL:

- if (maxDeltaL > deltaLiquidity) revert InvalidDeltaLiquidity();
+ if (maxDeltaL < deltaLiquidity) revert InvalidDeltaLiquidity();

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed here 13aa509.


deltas = new uint256[](2);
deltas[0] = deltaX;
deltas[1] = deltaY;

pool.reserves[0] -= deltaX;
pool.reserves[1] -= deltaY;

invariant = tradingFunction(
pool.reserves,
pool.totalLiquidity - deltaLiquidity,
getPoolParams(poolId)
);

valid = invariant >= 0;
}

/// @inheritdoc IStrategy
function update(
address sender,
Expand Down Expand Up @@ -105,6 +197,7 @@ contract ConstantSum is PairStrategy {

params.price = internalParams[poolId].price;
params.swapFee = internalParams[poolId].swapFee;
params.controller = internalParams[poolId].controller;

return abi.encode(params);
}
Expand All @@ -128,7 +221,7 @@ contract ConstantSum is PairStrategy {
Pool memory,
bytes memory
) internal pure override returns (uint256[] memory) {
return new uint256[](0);
return new uint256[](2);
}

/// @inheritdoc PairStrategy
Expand All @@ -137,6 +230,20 @@ contract ConstantSum is PairStrategy {
Pool memory,
bytes memory
) internal pure override returns (uint256[] memory) {
return new uint256[](0);
return new uint256[](2);
}

/// @inheritdoc PairStrategy
function _computeSwapDeltaLiquidity(
Pool memory,
bytes memory params,
uint256 tokenInIndex,
uint256,
uint256 amountIn,
uint256
) internal pure override returns (uint256) {
return computeSwapDeltaLiquidity(
amountIn, abi.decode(params, (ConstantSumParams)), tokenInIndex == 0
);
}
}
82 changes: 17 additions & 65 deletions src/ConstantSum/ConstantSumMath.sol
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,16 @@ import { ConstantSumParams } from "src/ConstantSum/ConstantSum.sol";
import { ONE } from "src/lib/StrategyLib.sol";

using FixedPointMathLib for uint256;
using FixedPointMathLib for int256;

function computeTradingFunction(
uint256[] memory reserves,
uint256 totalLiquidity,
uint256 price
) pure returns (int256) {
return int256(reserves[0].divWadUp(totalLiquidity))
+ int256(reserves[1].divWadUp(totalLiquidity.mulWadUp(price))) - int256(ONE);
return int256(
price.mulWadUp(reserves[0].divWadUp(totalLiquidity))
+ reserves[1].divWadUp(totalLiquidity)
) - int256(ONE);
}

function computeInitialPoolData(
Expand All @@ -24,77 +25,28 @@ function computeInitialPoolData(
) pure returns (bytes memory) {
// The pool can be initialized with any non-negative amount of rx, and ry.
// so we have to allow a user to pass an amount of both even if one is zero.
uint256 L = rx + ry.divWadUp(params.price);
uint256[] memory reserves = new uint256[](2);
reserves[0] = rx;
reserves[1] = ry;
return abi.encode(reserves, L, params);
return abi.encode(reserves, params);
}

function computeDeallocateGivenDeltaX(
function computeDeltaLiquidity(
uint256 deltaX,
uint256 rX,
uint256 rY,
uint256 totalLiquidity
) pure returns (uint256 deltaY, uint256 deltaL) {
uint256 a = deltaX.divWadDown(rX);
if (rY > 0) {
deltaY = a.mulWadDown(rY);
}
deltaL = a.mulWadDown(totalLiquidity);
}

function computeDeallocateGivenDeltaY(
uint256 deltaY,
uint256 rX,
uint256 rY,
uint256 totalLiquidity
) pure returns (uint256 deltaX, uint256 deltaL) {
uint256 a = deltaY.divWadDown(rY);
if (rX > 0) {
deltaX = a.mulWadDown(rX);
}
deltaL = a.mulWadDown(totalLiquidity);
}

function computeAllocateGivenDeltaX(
uint256 deltaX,
uint256 rX,
uint256 rY,
uint256 totalLiquidity
) pure returns (uint256 deltaY, uint256 deltaL) {
uint256 a = deltaX.divWadUp(rX);
if (rY > 0) {
deltaY = a.mulWadUp(rY);
}
deltaL = a.mulWadUp(totalLiquidity);
}

function computeAllocateGivenDeltaY(
uint256 deltaY,
uint256 rX,
uint256 rY,
uint256 totalLiquidity
) pure returns (uint256 deltaX, uint256 deltaL) {
uint256 a = deltaY.divWadUp(rY);
if (rX > 0) {
deltaX = a.mulWadUp(rX);
}
deltaL = a.mulWadUp(totalLiquidity);
}

function computeDeltaGivenDeltaLRoundUp(
uint256 reserve,
uint256 deltaLiquidity,
uint256 totalLiquidity
uint256 price
) pure returns (uint256) {
return reserve.mulWadUp(deltaLiquidity.divWadUp(totalLiquidity));
return price.mulWadUp(deltaX) + deltaY;
}

function computeDeltaGivenDeltaLRoundDown(
uint256 reserve,
uint256 deltaLiquidity,
uint256 totalLiquidity
function computeSwapDeltaLiquidity(
uint256 delta,
ConstantSumParams memory params,
bool isSwapXForY
) pure returns (uint256) {
return reserve.mulWadDown(deltaLiquidity.divWadDown(totalLiquidity));
if (isSwapXForY) {
return (params.swapFee).mulWadUp(delta);
} else {
return (params.swapFee).mulDivUp(delta, params.price);
}
Comment on lines +47 to +51
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This logic seems inconsistent with the other functions in this file (e.g. computeDeltaLiquidity immediately above). Liquidity is in units of Y and X quantities need to be multiplied by price. If isSwapXForY == true, I expect delta to be an X amount and thus the liquidity change should be something like (params.swapFee).mulWadUp(delta.mulWadUp(params.price)), and in the false case, it should be (params.swapFee).mulWadUp(delta) as then delta is a Y quantity.

Copy link

@MiloTruck MiloTruck Apr 5, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can confirm this, when calling swap() with the correct input and output amounts, you either blow-up the invariant or it becomes negative. I ran the numbers with this:

// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.18;

import "forge-std/Test.sol";
import "src/FixedPointMathLib.sol";

contract SwapTest is Test {
    using FixedPointMathLib for uint256;
    using FixedPointMathLib for int256;

    function testSwapWithFees() public {
        // Pool state
        uint256 rX = 10e18;
        uint256 rY = 20e18;
        uint256 price = 2e18;
        uint256 swapFee = 0.05e18;

        uint256 tL = price.mulWadUp(rX) + rY;
        _invariant(rX, rY, tL, price);

        // Swap 1 tokenX for 2 tokenY
        uint256 amountIn = 1e18;
        uint256 amountOut = uint256(2e18).mulWadDown(1e18 - swapFee);
        uint256 dL = computeSwapDeltaLiquidity(amountIn, price, swapFee, true);

        _invariant(
            rX + amountIn,
            rY - amountOut,
            tL + dL,
            price
        );
    }

    function _invariant(
        uint256 rX,
        uint256 rY,
        uint256 tL,
        uint256 price
    ) internal returns (int256) {
        int256 invariant = int256(price.mulWadUp(rX.divWadUp(tL)) + rY.divWadUp(tL)) - int256(1e18);
        console2.log("invariant:", invariant);
    }

    function computeSwapDeltaLiquidity(
        uint256 delta,
        uint256 price,
        uint256 swapFee,
        bool isSwapXForY
    ) internal returns (uint256) {
        if (isSwapXForY) {
            return (swapFee).mulWadUp(delta);
        } else {
            return (swapFee).mulDivUp(delta, price);
        }
    }
}

Output:

Logs:
  invariant: 0
  invariant: 1248439450686643

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed here 6b5a3f5.

}
17 changes: 6 additions & 11 deletions src/ConstantSum/ConstantSumSolver.sol
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,14 @@ import {
import {
ONE,
computeInitialPoolData,
FixedPointMathLib
FixedPointMathLib,
computeSwapDeltaLiquidity
} from "./ConstantSumMath.sol";

contract ConstantSumSolver {
error NotEnoughLiquidity();

using FixedPointMathLib for uint256;
using FixedPointMathLib for int256;

struct Reserves {
uint256 rx;
Expand Down Expand Up @@ -59,7 +59,7 @@ contract ConstantSumSolver {
SimulateSwapState memory state;

if (swapXIn) {
state.deltaLiquidity = amountIn.mulWadUp(poolParams.swapFee);
computeSwapDeltaLiquidity(amountIn, poolParams, true);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a pure function that returns a value, but the return value isn't being used at all. Looks like state.deltaLiquidity is never set in this branch.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point, I think it's an artefact from the past, let's remove it.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The function call was deleted here 016b81a.

state.amountOut = amountIn.mulWadDown(poolParams.price).mulWadDown(
ONE - poolParams.swapFee
);
Expand All @@ -68,8 +68,7 @@ contract ConstantSumSolver {
revert NotEnoughLiquidity();
}
} else {
state.deltaLiquidity =
amountIn.mulWadUp(poolParams.swapFee).divWadUp(poolParams.price);
computeSwapDeltaLiquidity(amountIn, poolParams, false);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same comment here as on line 62.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Delete here too 016b81a.

state.amountOut = (ONE - poolParams.swapFee).mulWadDown(amountIn)
.divWadDown(poolParams.price);

Expand All @@ -81,13 +80,9 @@ contract ConstantSumSolver {
bytes memory swapData;

if (swapXIn) {
swapData = abi.encode(
0, 1, amountIn, state.amountOut, state.deltaLiquidity
);
swapData = abi.encode(0, 1, amountIn, state.amountOut);
} else {
swapData = abi.encode(
1, 0, amountIn, state.amountOut, state.deltaLiquidity
);
swapData = abi.encode(1, 0, amountIn, state.amountOut);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I notice state.deltaLiquidity is no longer being included in swapData. Either this is intentional and the function calls I commented on above can be removed, or it is unintentional and deltaLiquidity should be properly computed and included.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's intentional yes, the deltaLiquidity is now calculated by the strategies during a swap.

}

(bool valid,,,,,,) = IStrategy(strategy).validateSwap(
Expand Down
1 change: 0 additions & 1 deletion src/ConstantSum/ConstantSumUtils.sol
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ function encodeControllerUpdate(address controller)

function decodeFeeUpdate(bytes memory data) pure returns (uint256 swapFee) {
(, swapFee) = abi.decode(data, (UpdateCode, uint256));
return swapFee;
}

function decodePriceUpdate(bytes memory data)
Expand Down
Loading
Loading