diff --git a/src/V4Router.sol b/src/V4Router.sol index 0aaf7715..282ab77c 100644 --- a/src/V4Router.sol +++ b/src/V4Router.sol @@ -7,7 +7,6 @@ import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol"; import {Currency} from "@uniswap/v4-core/src/types/Currency.sol"; import {TickMath} from "@uniswap/v4-core/src/libraries/TickMath.sol"; import {SafeCast} from "@uniswap/v4-core/src/libraries/SafeCast.sol"; -import {BipsLibrary} from "@uniswap/v4-core/src/libraries/BipsLibrary.sol"; import {PathKey, PathKeyLibrary} from "./libraries/PathKey.sol"; import {CalldataDecoder} from "./libraries/CalldataDecoder.sol"; @@ -16,6 +15,7 @@ import {BaseActionsRouter} from "./base/BaseActionsRouter.sol"; import {DeltaResolver} from "./base/DeltaResolver.sol"; import {Actions} from "./libraries/Actions.sol"; import {ActionConstants} from "./libraries/ActionConstants.sol"; +import {BipsLibrary} from "./libraries/BipsLibrary.sol"; /// @title UniswapV4Router /// @notice Abstract contract that contains all internal logic needed for routing through Uniswap v4 pools diff --git a/src/base/ImmutableState.sol b/src/base/ImmutableState.sol index 8e30f1a4..9f9cf068 100644 --- a/src/base/ImmutableState.sol +++ b/src/base/ImmutableState.sol @@ -2,11 +2,12 @@ pragma solidity ^0.8.0; import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; +import {IImmutableState} from "../interfaces/IImmutableState.sol"; /// @title Immutable State /// @notice A collection of immutable state variables, commonly used across multiple contracts -contract ImmutableState { - /// @notice The Uniswap v4 PoolManager contract +contract ImmutableState is IImmutableState { + /// @inheritdoc IImmutableState IPoolManager public immutable poolManager; constructor(IPoolManager _poolManager) { diff --git a/src/interfaces/IImmutableState.sol b/src/interfaces/IImmutableState.sol new file mode 100644 index 00000000..32e20573 --- /dev/null +++ b/src/interfaces/IImmutableState.sol @@ -0,0 +1,10 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.0; + +import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; + +/// @title Interface for ImmutableState +interface IImmutableState { + /// @notice The Uniswap v4 PoolManager contract + function poolManager() external view returns (IPoolManager); +} diff --git a/src/interfaces/IPositionManager.sol b/src/interfaces/IPositionManager.sol index 616b51cc..fbdfdd5c 100644 --- a/src/interfaces/IPositionManager.sol +++ b/src/interfaces/IPositionManager.sol @@ -5,10 +5,11 @@ import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol"; import {PositionInfo} from "../libraries/PositionInfoLibrary.sol"; import {INotifier} from "./INotifier.sol"; +import {IImmutableState} from "./IImmutableState.sol"; /// @title IPositionManager /// @notice Interface for the PositionManager contract -interface IPositionManager is INotifier { +interface IPositionManager is INotifier, IImmutableState { /// @notice Thrown when the caller is not approved to modify a position error NotApproved(address caller); /// @notice Thrown when the block.timestamp exceeds the user-provided deadline diff --git a/src/interfaces/IV4Router.sol b/src/interfaces/IV4Router.sol index 13ed4775..9c081244 100644 --- a/src/interfaces/IV4Router.sol +++ b/src/interfaces/IV4Router.sol @@ -4,10 +4,11 @@ pragma solidity ^0.8.0; import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol"; import {Currency} from "@uniswap/v4-core/src/types/Currency.sol"; import {PathKey} from "../libraries/PathKey.sol"; +import {IImmutableState} from "./IImmutableState.sol"; /// @title IV4Router /// @notice Interface containing all the structs and errors for different v4 swap types -interface IV4Router { +interface IV4Router is IImmutableState { /// @notice Emitted when an exactInput swap does not receive its minAmountOut error V4TooLittleReceived(uint256 minAmountOutReceived, uint256 amountReceived); /// @notice Emitted when an exactOutput is asked for more than its maxAmountIn diff --git a/src/libraries/BipsLibrary.sol b/src/libraries/BipsLibrary.sol new file mode 100644 index 00000000..219232e2 --- /dev/null +++ b/src/libraries/BipsLibrary.sol @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity ^0.8.0; + +/// @title For calculating a percentage of an amount, using bips +library BipsLibrary { + uint256 internal constant BPS_DENOMINATOR = 10_000; + + /// @notice emitted when an invalid percentage is provided + error InvalidBips(); + + /// @param amount The total amount to calculate a percentage of + /// @param bips The percentage to calculate, in bips + function calculatePortion(uint256 amount, uint256 bips) internal pure returns (uint256) { + if (bips > BPS_DENOMINATOR) revert InvalidBips(); + return (amount * bips) / BPS_DENOMINATOR; + } +} diff --git a/test/libraries/BipsLibrary.t.sol b/test/libraries/BipsLibrary.t.sol new file mode 100644 index 00000000..02cc67d7 --- /dev/null +++ b/test/libraries/BipsLibrary.t.sol @@ -0,0 +1,51 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.24; + +import "forge-std/Test.sol"; +import {BipsLibrary} from "../../src/libraries/BipsLibrary.sol"; + +contract BipsLibraryTest is Test { + using BipsLibrary for uint256; + + // The block gas limit set in foundry config is 300_000_000 (300M) for testing purposes + uint256 BLOCK_GAS_LIMIT; + + function setUp() public { + BLOCK_GAS_LIMIT = block.gaslimit; + } + + function test_fuzz_calculatePortion(uint256 amount, uint256 bips) public { + amount = bound(amount, 0, uint256(type(uint128).max)); + if (bips > BipsLibrary.BPS_DENOMINATOR) { + vm.expectRevert(BipsLibrary.InvalidBips.selector); + amount.calculatePortion(bips); + } else { + assertEq(amount.calculatePortion(bips), amount * bips / BipsLibrary.BPS_DENOMINATOR); + } + } + + function test_fuzz_gasLimit(uint256 bips) public { + if (bips > BipsLibrary.BPS_DENOMINATOR) { + vm.expectRevert(BipsLibrary.InvalidBips.selector); + block.gaslimit.calculatePortion(bips); + } else { + assertEq(block.gaslimit.calculatePortion(bips), BLOCK_GAS_LIMIT * bips / BipsLibrary.BPS_DENOMINATOR); + } + } + + function test_gasLimit_100_percent() public view { + assertEq(block.gaslimit, block.gaslimit.calculatePortion(10_000)); + } + + function test_gasLimit_1_percent() public view { + // 100 bps = 1% + // 1% of 30M is 300K + assertEq(BLOCK_GAS_LIMIT / 100, block.gaslimit.calculatePortion(100)); + } + + function test_gasLimit_1BP() public view { + // 1bp is 0.01% + // 0.01% of 30M is 300 + assertEq(BLOCK_GAS_LIMIT / 10000, block.gaslimit.calculatePortion(1)); + } +} diff --git a/test/router/Payments.t.sol b/test/router/Payments.t.sol index 8f96a408..3b1b2c32 100644 --- a/test/router/Payments.t.sol +++ b/test/router/Payments.t.sol @@ -3,13 +3,13 @@ pragma solidity ^0.8.19; import {GasSnapshot} from "forge-gas-snapshot/GasSnapshot.sol"; import {Currency, CurrencyLibrary} from "@uniswap/v4-core/src/types/Currency.sol"; -import {BipsLibrary} from "@uniswap/v4-core/src/libraries/BipsLibrary.sol"; import {IV4Router} from "../../src/interfaces/IV4Router.sol"; import {RoutingTestHelpers} from "../shared/RoutingTestHelpers.sol"; import {Plan, Planner} from "../shared/Planner.sol"; import {Actions} from "../../src/libraries/Actions.sol"; import {ActionConstants} from "../../src/libraries/ActionConstants.sol"; +import {BipsLibrary} from "../../src/libraries/BipsLibrary.sol"; contract PaymentsTests is RoutingTestHelpers, GasSnapshot { using CurrencyLibrary for Currency;