From 5ce14ab2e1bb29efe98ea85a67c9530068932d75 Mon Sep 17 00:00:00 2001 From: tim Date: Tue, 30 Sep 2025 15:42:52 -0400 Subject: [PATCH] linted --- foundry.toml | 2 +- script/DeployMock.sol | 8 +-- src/Deploy.sol | 2 +- src/IPartyPlanner.sol | 5 +- src/PartyPlanner.sol | 24 +++++---- src/PartyPool.sol | 107 ++++++++++++++++++++------------------ test/GasTest.sol | 24 +++++---- test/LMSRStabilized.t.sol | 6 +-- test/MockERC20.sol | 6 +-- test/PartyPool.t.sol | 2 + 10 files changed, 100 insertions(+), 86 deletions(-) diff --git a/foundry.toml b/foundry.toml index 33c6886..5950f5d 100644 --- a/foundry.toml +++ b/foundry.toml @@ -13,6 +13,6 @@ gas_reports = ['PartyPool', 'PartyPlanner', 'PartyPoolSwapMintImpl', 'PartyPoolM fs_permissions = [{ access = "write", path = "chain.json"}] [lint] -exclude_lints=['mixed-case-variable', 'unaliased-plain-import', ] +lint_on_build=false # more annoying than helpful # See more config options https://github.com/foundry-rs/foundry/blob/master/crates/config/README.md#all-options diff --git a/script/DeployMock.sol b/script/DeployMock.sol index 64055c5..4a8d497 100644 --- a/script/DeployMock.sol +++ b/script/DeployMock.sol @@ -12,9 +12,9 @@ import "forge-std/console2.sol"; contract DeployMock is Script { - address constant devAccount0 = 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266; + address constant public DEV_ACCOUNT_0 = 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266; // private key 0x4bbbf85ce3377467afe5d46f804f221813b2bb87f24d81f60f1fcdbf7cbf4356 - address constant devAccount7 = 0x14dC79964da2C08b23698B3D3cc7Ca32193d9955; + address constant public DEV_ACCOUNT_7 = 0x14dC79964da2C08b23698B3D3cc7Ca32193d9955; function run() public { vm.startBroadcast(); @@ -69,14 +69,14 @@ contract DeployMock is Script { _feePpm, false, msg.sender, // payer: this script - devAccount7, // receiver of initial LP + DEV_ACCOUNT_7, // receiver of initial LP initialDeposits, initialLpAmount, deadline ); // give tokens to dev7 for later use - mintAll(devAccount7, 1_000_000); + mintAll(DEV_ACCOUNT_7, 1_000_000); vm.stopBroadcast(); diff --git a/src/Deploy.sol b/src/Deploy.sol index 2474326..52e6f96 100644 --- a/src/Deploy.sol +++ b/src/Deploy.sol @@ -28,7 +28,7 @@ library Deploy { ) internal returns (PartyPool) { return new PartyPool(name_, symbol_, tokens_, bases_, _kappa, _swapFeePpm, _flashFeePpm, _stable, new PartyPoolSwapMintImpl(), - address(new PartyPoolMintImpl()) + new PartyPoolMintImpl() ); } diff --git a/src/IPartyPlanner.sol b/src/IPartyPlanner.sol index 181a1ae..1313064 100644 --- a/src/IPartyPlanner.sol +++ b/src/IPartyPlanner.sol @@ -117,6 +117,9 @@ interface IPartyPlanner { function getPoolsByToken(IERC20 token, uint256 offset, uint256 limit) external view returns (PartyPool[] memory pools); /// @notice Address of the SwapMint implementation contract used by all pools created by this factory - function swapMintImpl() external view returns (address); + function mintImpl() external view returns (PartyPoolMintImpl); + + /// @notice Address of the SwapMint implementation contract used by all pools created by this factory + function swapMintImpl() external view returns (PartyPoolSwapMintImpl); } diff --git a/src/PartyPlanner.sol b/src/PartyPlanner.sol index d873dd8..5180296 100644 --- a/src/PartyPlanner.sol +++ b/src/PartyPlanner.sol @@ -13,13 +13,15 @@ import {PartyPoolMintImpl} from "./PartyPoolMintImpl.sol"; /// @notice Factory contract for creating and tracking PartyPool instances contract PartyPlanner is IPartyPlanner { using SafeERC20 for IERC20; - int128 private constant FIXED_ONE_64x64 = int128(1) << 64; - - /// @notice Address of the SwapMint implementation contract used by all pools created by this factory - address public immutable swapMintImpl; + int128 private constant ONE = int128(1) << 64; /// @notice Address of the Mint implementation contract used by all pools created by this factory - address public immutable mintImpl; + PartyPoolMintImpl private immutable MINT_IMPL; + function mintImpl() external view returns (PartyPoolMintImpl) { return MINT_IMPL; } + + /// @notice Address of the SwapMint implementation contract used by all pools created by this factory + PartyPoolSwapMintImpl private immutable SWAP_MINT_IMPL; + function swapMintImpl() external view returns (PartyPoolSwapMintImpl) { return SWAP_MINT_IMPL; } // On-chain pool indexing PartyPool[] private _allPools; @@ -32,9 +34,9 @@ contract PartyPlanner is IPartyPlanner { /// @param _mintImpl address of the Mint implementation contract to be used by all pools constructor(PartyPoolSwapMintImpl _swapMintImpl, PartyPoolMintImpl _mintImpl) { require(address(_swapMintImpl) != address(0), "Planner: swapMintImpl address cannot be zero"); - swapMintImpl = address(_swapMintImpl); + SWAP_MINT_IMPL = _swapMintImpl; require(address(_mintImpl) != address(0), "Planner: mintImpl address cannot be zero"); - mintImpl = address(_mintImpl); + MINT_IMPL = _mintImpl; } /// Main newPool variant: accepts kappa directly (preferred). @@ -74,8 +76,8 @@ contract PartyPlanner is IPartyPlanner { _swapFeePpm, _flashFeePpm, _stable, - PartyPoolSwapMintImpl(swapMintImpl), - mintImpl + PartyPoolSwapMintImpl(SWAP_MINT_IMPL), + MINT_IMPL ); _allPools.push(pool); @@ -130,8 +132,8 @@ contract PartyPlanner is IPartyPlanner { uint256 deadline ) external returns (PartyPool pool, uint256 lpAmount) { // Validate fixed-point fractions: must be less than 1.0 in 64.64 fixed-point - require(_tradeFrac < FIXED_ONE_64x64, "Planner: tradeFrac must be < 1 (64.64)"); - require(_targetSlippage < FIXED_ONE_64x64, "Planner: targetSlippage must be < 1 (64.64)"); + require(_tradeFrac < ONE, "Planner: tradeFrac must be < 1 (64.64)"); + require(_targetSlippage < ONE, "Planner: targetSlippage must be < 1 (64.64)"); // Compute kappa from slippage params using LMSR helper (kappa depends only on n, f and s) int128 computedKappa = LMSRStabilized.computeKappaFromSlippage(_tokens.length, _tradeFrac, _targetSlippage); diff --git a/src/PartyPool.sol b/src/PartyPool.sol index 0c7d101..2c74100 100644 --- a/src/PartyPool.sol +++ b/src/PartyPool.sol @@ -12,6 +12,7 @@ import "./IPartyPool.sol"; import "./IPartyFlashCallback.sol"; import "./PartyPoolBase.sol"; import {PartyPoolSwapMintImpl} from "./PartyPoolSwapMintImpl.sol"; +import {PartyPoolMintImpl} from "./PartyPoolMintImpl.sol"; /// @title PartyPool - LMSR-backed multi-asset pool with LP ERC20 token /// @notice A multi-asset liquidity pool backed by the LMSRStabilized pricing model. @@ -34,22 +35,28 @@ contract PartyPool is PartyPoolBase, IPartyPool { /// @notice Liquidity parameter κ (Q64.64) used by the LMSR kernel: b = κ * S(q) /// @dev Pool is constructed with a fixed κ. Clients that previously passed tradeFrac/targetSlippage /// should use LMSRStabilized.computeKappaFromSlippage(...) to derive κ and pass it here. - int128 public immutable kappa; // kappa in Q64.64 + int128 private immutable KAPPA; // kappa in Q64.64 + function kappa() external view returns (int128) { return KAPPA; } /// @notice Per-swap fee in parts-per-million (ppm). Fee is taken from input amounts before LMSR computations. - uint256 public immutable swapFeePpm; + uint256 private immutable SWAP_FEE_PPM; + function swapFeePpm() external view returns (uint256) { return SWAP_FEE_PPM; } /// @notice Flash-loan fee in parts-per-million (ppm) applied to flash borrow amounts. - uint256 public immutable flashFeePpm; + uint256 private immutable FLASH_FEE_PPM; + function flashFeePpm() external view returns (uint256) { return FLASH_FEE_PPM; } /// @notice If true and there are exactly two assets, an optimized 2-asset stable-pair path is used for some computations. - bool immutable private _stablePair; // if true, the optimized LMSRStabilizedBalancedPair optimization path is enabled - - /// @notice Address of the SwapMint implementation contract for delegatecall - address public immutable swapMintImpl; + bool immutable private IS_STABLE_PAIR; // if true, the optimized LMSRStabilizedBalancedPair optimization path is enabled /// @notice Address of the Mint implementation contract for delegatecall - address public immutable mintImpl; + PartyPoolMintImpl private immutable MINT_IMPL; + function mintImpl() external view returns (PartyPoolMintImpl) { return MINT_IMPL; } + + /// @notice Address of the SwapMint implementation contract for delegatecall + PartyPoolSwapMintImpl private immutable SWAP_MINT_IMPL; + function swapMintImpl() external view returns (PartyPoolSwapMintImpl) { return SWAP_MINT_IMPL; } + /// @inheritdoc IPartyPool function getToken(uint256 i) external view returns (IERC20) { return tokens[i]; } @@ -67,38 +74,36 @@ contract PartyPool is PartyPoolBase, IPartyPool { /// @param symbol_ LP token symbol /// @param tokens_ token addresses (n) /// @param bases_ scaling bases for each token (n) - used when converting to/from internal 64.64 amounts - /// @param _kappa liquidity parameter κ (Q64.64) used to derive b = κ * S(q) - /// @param _swapFeePpm fee in parts-per-million, taken from swap input amounts before LMSR calculations - /// @param _flashFeePpm fee in parts-per-million, taken for flash loans - /// @param _stable if true and assets.length==2, then the optimization for 2-asset stablecoin pools is activated. - /// @param _swapMintImpl address of the SwapMint implementation contract - /// @param _mintImpl address of the Mint implementation contract + /// @param kappa_ liquidity parameter κ (Q64.64) used to derive b = κ * S(q) + /// @param swapFeePpm_ fee in parts-per-million, taken from swap input amounts before LMSR calculations + /// @param flashFeePpm_ fee in parts-per-million, taken for flash loans + /// @param stable_ if true and assets.length==2, then the optimization for 2-asset stablecoin pools is activated. + /// @param swapMintImpl_ address of the SwapMint implementation contract + /// @param mintImpl_ address of the Mint implementation contract constructor( string memory name_, string memory symbol_, IERC20[] memory tokens_, uint256[] memory bases_, - int128 _kappa, - uint256 _swapFeePpm, - uint256 _flashFeePpm, - bool _stable, - PartyPoolSwapMintImpl _swapMintImpl, - address _mintImpl + int128 kappa_, + uint256 swapFeePpm_, + uint256 flashFeePpm_, + bool stable_, + PartyPoolSwapMintImpl swapMintImpl_, + PartyPoolMintImpl mintImpl_ ) PartyPoolBase(name_, symbol_) { require(tokens_.length > 1, "Pool: need >1 asset"); require(tokens_.length == bases_.length, "Pool: lengths mismatch"); tokens = tokens_; bases = bases_; - kappa = _kappa; - require(_swapFeePpm < 1_000_000, "Pool: fee >= ppm"); - swapFeePpm = _swapFeePpm; - require(_flashFeePpm < 1_000_000, "Pool: flash fee >= ppm"); - flashFeePpm = _flashFeePpm; - _stablePair = _stable && tokens_.length == 2; - require(address(_swapMintImpl) != address(0), "Pool: swapMintImpl address zero"); - swapMintImpl = address(_swapMintImpl); - require(_mintImpl != address(0), "Pool: mintImpl address zero"); - mintImpl = _mintImpl; + KAPPA = kappa_; + require(swapFeePpm_ < 1_000_000, "Pool: fee >= ppm"); + SWAP_FEE_PPM = swapFeePpm_; + require(flashFeePpm_ < 1_000_000, "Pool: flash fee >= ppm"); + FLASH_FEE_PPM = flashFeePpm_; + IS_STABLE_PAIR = stable_ && tokens_.length == 2; + SWAP_MINT_IMPL = swapMintImpl_; + MINT_IMPL = mintImpl_; uint256 n = tokens_.length; @@ -175,7 +180,7 @@ contract PartyPool is PartyPoolBase, IPartyPool { } // Initialize the stabilized LMSR state with provided kappa - lmsr.init(newQInternal, kappa); + lmsr.init(newQInternal, KAPPA); // Compute actual LP tokens to mint based on size metric (scaled) if( lpTokens != 0 ) @@ -206,7 +211,7 @@ contract PartyPool is PartyPoolBase, IPartyPool { deadline ); - bytes memory result = Address.functionDelegateCall(mintImpl, data); + bytes memory result = Address.functionDelegateCall(address(MINT_IMPL), data); return abi.decode(result, (uint256)); } @@ -251,7 +256,7 @@ contract PartyPool is PartyPoolBase, IPartyPool { deadline ); - Address.functionDelegateCall(mintImpl, data); + Address.functionDelegateCall(address(MINT_IMPL), data); } /* ---------------------- @@ -411,7 +416,7 @@ contract PartyPool is PartyPoolBase, IPartyPool { require(lmsr.nAssets > 0, "swap: empty pool"); // Estimate max net input (fee on gross rounded up, then subtract) - (, uint256 netUintForSwap) = _computeFee(maxAmountIn, swapFeePpm); + (, uint256 netUintForSwap) = _computeFee(maxAmountIn, SWAP_FEE_PPM); // Convert to internal (floor) int128 deltaInternalI = _uintToInternalFloor(netUintForSwap, bases[inputTokenIndex]); @@ -419,9 +424,9 @@ contract PartyPool is PartyPoolBase, IPartyPool { // Compute internal amounts using LMSR (exact-input with price limit) // if _stablePair is true, use the optimized path - console2.log('stablepair optimization?', _stablePair); + console2.log('stablepair optimization?', IS_STABLE_PAIR); (amountInInternalUsed, amountOutInternal) = - _stablePair ? LMSRStabilizedBalancedPair.swapAmountsForExactInput(lmsr, inputTokenIndex, outputTokenIndex, deltaInternalI, limitPrice) + IS_STABLE_PAIR ? LMSRStabilizedBalancedPair.swapAmountsForExactInput(lmsr, inputTokenIndex, outputTokenIndex, deltaInternalI, limitPrice) : lmsr.swapAmountsForExactInput(inputTokenIndex, outputTokenIndex, deltaInternalI, limitPrice); // Convert actual used input internal -> uint (ceil) @@ -431,8 +436,8 @@ contract PartyPool is PartyPoolBase, IPartyPool { // Compute gross transfer including fee on the used input (ceil) feeUint = 0; grossIn = amountInUintNoFee; - if (swapFeePpm > 0) { - feeUint = _ceilFee(amountInUintNoFee, swapFeePpm); + if (SWAP_FEE_PPM > 0) { + feeUint = _ceilFee(amountInUintNoFee, SWAP_FEE_PPM); grossIn += feeUint; } @@ -479,8 +484,8 @@ contract PartyPool is PartyPoolBase, IPartyPool { feeUint = 0; grossIn = amountInUintNoFee; - if (swapFeePpm > 0) { - feeUint = _ceilFee(amountInUintNoFee, swapFeePpm); + if (SWAP_FEE_PPM > 0) { + feeUint = _ceilFee(amountInUintNoFee, SWAP_FEE_PPM); grossIn += feeUint; } @@ -491,17 +496,17 @@ contract PartyPool is PartyPoolBase, IPartyPool { /// @notice Compute fee and net amounts for a gross input (fee rounded up to favor the pool). /// @return feeUint fee taken (uint) and netUint remaining for protocol use (uint) function _computeFee(uint256 gross) internal view returns (uint256 feeUint, uint256 netUint) { - if (swapFeePpm == 0) { + if (SWAP_FEE_PPM == 0) { return (0, gross); } - feeUint = _ceilFee(gross, swapFeePpm); + feeUint = _ceilFee(gross, SWAP_FEE_PPM); netUint = gross - feeUint; } /// @notice Convenience: return gross = net + fee(net) using ceiling for fee. function _addFee(uint256 netUint) internal view returns (uint256 gross) { - if (swapFeePpm == 0) return netUint; - uint256 fee = _ceilFee(netUint, swapFeePpm); + if (SWAP_FEE_PPM == 0) return netUint; + uint256 fee = _ceilFee(netUint, SWAP_FEE_PPM); return netUint + fee; } @@ -533,10 +538,10 @@ contract PartyPool is PartyPoolBase, IPartyPool { inputTokenIndex, maxAmountIn, deadline, - swapFeePpm + SWAP_FEE_PPM ); - bytes memory result = Address.functionDelegateCall(swapMintImpl, data); + bytes memory result = Address.functionDelegateCall(address(SWAP_MINT_IMPL), data); return abi.decode(result, (uint256)); } @@ -562,10 +567,10 @@ contract PartyPool is PartyPoolBase, IPartyPool { lpAmount, inputTokenIndex, deadline, - swapFeePpm + SWAP_FEE_PPM ); - bytes memory result = Address.functionDelegateCall(swapMintImpl, data); + bytes memory result = Address.functionDelegateCall(address(SWAP_MINT_IMPL), data); return abi.decode(result, (uint256)); } @@ -577,7 +582,7 @@ contract PartyPool is PartyPoolBase, IPartyPool { for (uint256 i = 0; i < tokens.length; i++) { uint256 amount = loanAmounts[i]; if (amount > 0) { - repaymentAmounts[i] = amount + _ceilFee(amount, flashFeePpm); + repaymentAmounts[i] = amount + _ceilFee(amount, FLASH_FEE_PPM); } } } @@ -617,7 +622,7 @@ contract PartyPool is PartyPoolBase, IPartyPool { hasNonZeroAmount = true; // Calculate repayment amount with fee (ceiling) - repaymentAmounts[i] = amount + _ceilFee(amount, flashFeePpm); + repaymentAmounts[i] = amount + _ceilFee(amount, FLASH_FEE_PPM); // Record initial balance initialBalances[i] = IERC20(tokens[i]).balanceOf(address(this)); @@ -640,7 +645,7 @@ contract PartyPool is PartyPoolBase, IPartyPool { // Verify repayment: current balance must be at least (initial balance + fee) require( - currentBalance >= initialBalances[i] + _ceilFee(amounts[i], flashFeePpm), + currentBalance >= initialBalances[i] + _ceilFee(amounts[i], FLASH_FEE_PPM), "flash: repayment failed" ); diff --git a/test/GasTest.sol b/test/GasTest.sol index ec2db47..c47fcb0 100644 --- a/test/GasTest.sol +++ b/test/GasTest.sol @@ -1,5 +1,6 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.30; +/* solhint-disable erc20-unchecked-transfer */ import "forge-std/Test.sol"; import "@abdk/ABDKMath64x64.sol"; @@ -129,22 +130,23 @@ contract TestERC20 is ERC20 { /// @notice Gas testing contract for PartyPool - contains all gas measurement tests contract GasTest is Test { using ABDKMath64x64 for int128; + using SafeERC20 for TestERC20; - PartyPlanner planner; - PartyPool pool2; - PartyPool pool10; - PartyPool pool20; - PartyPool pool50; + PartyPlanner internal planner; + PartyPool internal pool2; + PartyPool internal pool10; + PartyPool internal pool20; + PartyPool internal pool50; - address alice; - address bob; + address internal alice; + address internal bob; // Common parameters - int128 tradeFrac; - int128 targetSlippage; + int128 internal tradeFrac; + int128 internal targetSlippage; - uint256 constant INIT_BAL = 1_000_000; // initial token units for each token (internal==amount when base==1) - uint256 constant BASE = 1; // use base=1 so internal amounts correspond to raw integers (Q64.64 units) + uint256 constant internal INIT_BAL = 1_000_000; // initial token units for each token (internal==amount when base==1) + uint256 constant internal BASE = 1; // use base=1 so internal amounts correspond to raw integers (Q64.64 units) /// @notice Helper function to create a pool with the specified number of tokens function createPool(uint256 numTokens) internal returns (PartyPool) { diff --git a/test/LMSRStabilized.t.sol b/test/LMSRStabilized.t.sol index 02bb6f9..03afafd 100644 --- a/test/LMSRStabilized.t.sol +++ b/test/LMSRStabilized.t.sol @@ -711,7 +711,7 @@ contract LMSRStabilizedTest is Test { } // Path 1: Direct swap from asset 0 to asset 2 - (int128 directAmountIn, int128 directAmountOut) = s.swapAmountsForExactInput(0, 2, directSwapAmount, 0); + (/*int128 directAmountIn*/, int128 directAmountOut) = s.swapAmountsForExactInput(0, 2, directSwapAmount, 0); // Restore original state for second path _updateCachedQInternal(backupQ); @@ -724,7 +724,7 @@ contract LMSRStabilizedTest is Test { s.qInternal[1] = s.qInternal[1].add(indirectAmountOut1); // Second swap: asset 1 -> asset 2 - (int128 indirectAmountIn2, int128 indirectAmountOut2) = s.swapAmountsForExactInput(1, 2, indirectAmountOut1, 0); + (/*int128 indirectAmountIn2*/, int128 indirectAmountOut2) = s.swapAmountsForExactInput(1, 2, indirectAmountOut1, 0); // The path independence property isn't perfect due to discrete swap mechanics, // but the difference should be within reasonable bounds @@ -765,7 +765,7 @@ contract LMSRStabilizedTest is Test { s.qInternal[1] = s.qInternal[1].add(amountOut1); // Step 2: Swap back asset 1 -> asset 0 - (int128 amountIn2, int128 amountOut2) = s.swapAmountsForExactInput(1, 0, amountOut1, 0); + (/*int128 amountIn2*/, int128 amountOut2) = s.swapAmountsForExactInput(1, 0, amountOut1, 0); // Calculate round-trip slippage: (initial amount - final amount) / initial amount int128 roundTripSlippage = (amountIn1.sub(amountOut2)).div(amountIn1); diff --git a/test/MockERC20.sol b/test/MockERC20.sol index 2dc7b78..fc6d981 100644 --- a/test/MockERC20.sol +++ b/test/MockERC20.sol @@ -4,11 +4,11 @@ pragma solidity ^0.8.30; import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; contract MockERC20 is ERC20 { - uint8 private immutable _decimals; + uint8 private immutable DECIMALS; - constructor(string memory name, string memory symbol, uint8 decimals_) ERC20(name, symbol) {_decimals = decimals_;} + constructor(string memory name, string memory symbol, uint8 decimals_) ERC20(name, symbol) {DECIMALS = decimals_;} - function decimals() public view virtual override returns (uint8) {return _decimals;} + function decimals() public view virtual override returns (uint8) {return DECIMALS;} function mint(address account, uint256 amount) external {_mint(account, amount);} function burn(address account, uint256 amount) external {_burn(account, amount);} } diff --git a/test/PartyPool.t.sol b/test/PartyPool.t.sol index 62fcc2f..0f9f484 100644 --- a/test/PartyPool.t.sol +++ b/test/PartyPool.t.sol @@ -1,4 +1,5 @@ // SPDX-License-Identifier: UNLICENSED +/* solhint-disable */ pragma solidity ^0.8.30; import "forge-std/Test.sol"; @@ -1389,3 +1390,4 @@ contract PartyPoolTest is Test { } } +/* solhint-enable */