Skip to content

Commit

Permalink
merge main
Browse files Browse the repository at this point in the history
  • Loading branch information
hensha256 committed Aug 2, 2024
2 parents ad7de1c + ebf7a4d commit 36f19d5
Show file tree
Hide file tree
Showing 6 changed files with 114 additions and 3 deletions.
2 changes: 1 addition & 1 deletion .forge-snapshots/V4Router_Bytecode.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
7775
7995
7 changes: 5 additions & 2 deletions src/V4Router.sol
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {SafeCast} from "@uniswap/v4-core/src/libraries/SafeCast.sol";

import {PathKey, PathKeyLib} from "./libraries/PathKey.sol";
import {CalldataDecoder} from "./libraries/CalldataDecoder.sol";
import {BipsLibrary} from "./libraries/BipsLibrary.sol";
import {IV4Router} from "./interfaces/IV4Router.sol";
import {BaseActionsRouter} from "./base/BaseActionsRouter.sol";
import {DeltaResolver} from "./base/DeltaResolver.sol";
Expand All @@ -25,6 +26,7 @@ abstract contract V4Router is IV4Router, BaseActionsRouter, DeltaResolver {
using SafeCast for *;
using PathKeyLib for PathKey;
using CalldataDecoder for bytes;
using BipsLibrary for uint256;

constructor(IPoolManager _poolManager) BaseActionsRouter(_poolManager) {}

Expand Down Expand Up @@ -58,9 +60,10 @@ abstract contract V4Router is IV4Router, BaseActionsRouter, DeltaResolver {
} else if (action == Actions.TAKE_ALL) {
(Currency currency, address recipient) = params.decodeCurrencyAndAddress();
uint256 amount = _getFullTakeAmount(currency);

// TODO should _take have a minAmountOut added slippage check?
_take(currency, _mapRecipient(recipient), amount);
} else if (action == Actions.TAKE_PORTION) {
(Currency currency, address recipient, uint256 bips) = params.decodeCurrencyAddressAndUint256();
_take(currency, _mapRecipient(recipient), _getFullTakeAmount(currency).calculatePortion(bips));
} else {
revert UnsupportedAction(action);
}
Expand Down
17 changes: 17 additions & 0 deletions src/libraries/BipsLibrary.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity ^0.8.19;

/// @title For calculating a percentage of an amount, using bips
library BipsLibrary {
uint256 internal constant BIPS_BASE = 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 > BIPS_BASE) revert InvalidBips();
return (amount * bips) / BIPS_BASE;
}
}
13 changes: 13 additions & 0 deletions src/libraries/CalldataDecoder.sol
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,19 @@ library CalldataDecoder {
}
}

/// @dev equivalent to: abi.decode(params, (Currency, address, uint256)) in calldata
function decodeCurrencyAddressAndUint256(bytes calldata params)
internal
pure
returns (Currency currency, address _address, uint256 amount)
{
assembly ("memory-safe") {
currency := calldataload(params.offset)
_address := calldataload(add(params.offset, 0x20))
amount := calldataload(add(params.offset, 0x40))
}
}

/// @dev equivalent to: abi.decode(params, (Currency, uint256)) in calldata
function decodeCurrencyAndUint256(bytes calldata params)
internal
Expand Down
20 changes: 20 additions & 0 deletions test/libraries/BipsLibrary.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

import "forge-std/Test.sol";
import "forge-std/StdError.sol";
import {BipsLibrary} from "../../src/libraries/BipsLibrary.sol";

contract PositionConfigTest is Test {
using BipsLibrary for uint256;

function test_fuzz_calculatePortion(uint256 amount, uint256 bips) public {
amount = bound(amount, 0, uint256(type(uint128).max));
if (bips > BipsLibrary.BIPS_BASE) {
vm.expectRevert(BipsLibrary.InvalidBips.selector);
amount.calculatePortion(bips);
} else {
assertEq(amount.calculatePortion(bips), amount * bips / BipsLibrary.BIPS_BASE);
}
}
}
58 changes: 58 additions & 0 deletions test/router/Payments.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,14 @@ 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 {BipsLibrary} from "../../src/libraries/BipsLibrary.sol";

contract PaymentsTests is RoutingTestHelpers, GasSnapshot {
using CurrencyLibrary for Currency;
using Planner for Plan;

address bob = makeAddr("BOB");

function setUp() public {
setupRouterCurrenciesAndPoolsWithLiquidity();
plan = Planner.init();
Expand Down Expand Up @@ -81,4 +84,59 @@ contract PaymentsTests is RoutingTestHelpers, GasSnapshot {
assertEq(inputBalanceBefore, inputBalanceAfter);
assertEq(outputBalanceAfter - outputBalanceBefore, expectedAmountOut);
}

function test_settle_takePortion_takeAll() public {
uint256 amountIn = 1 ether;
uint256 expectedAmountOut = 992054607780215625;
IV4Router.ExactInputSingleParams memory params =
IV4Router.ExactInputSingleParams(key0, true, uint128(amountIn), 0, 0, bytes(""));

plan = plan.add(Actions.SWAP_EXACT_IN_SINGLE, abi.encode(params));
plan = plan.add(Actions.SETTLE_ALL, abi.encode(key0.currency0));
// take 15 bips to Bob
plan = plan.add(Actions.TAKE_PORTION, abi.encode(key0.currency1, bob, 15));
plan = plan.add(Actions.TAKE_ALL, abi.encode(key0.currency1, address(this)));

uint256 inputBalanceBefore = key0.currency0.balanceOfSelf();
uint256 outputBalanceBefore = key0.currency1.balanceOfSelf();
uint256 bobBalanceBefore = key0.currency1.balanceOf(bob);

// router is empty before
assertEq(currency0.balanceOf(address(router)), 0);
assertEq(currency1.balanceOf(address(router)), 0);

bytes memory data = plan.encode();
router.executeActions(data);

uint256 inputBalanceAfter = key0.currency0.balanceOfSelf();
uint256 outputBalanceAfter = key0.currency1.balanceOfSelf();
uint256 bobBalanceAfter = key0.currency1.balanceOf(bob);

uint256 expectedFee = expectedAmountOut * 15 / BipsLibrary.BIPS_BASE;

// router is empty
assertEq(currency0.balanceOf(address(router)), 0);
assertEq(currency1.balanceOf(address(router)), 0);
// Bob got expectedFee, and the caller got the rest of the output
assertEq(inputBalanceBefore - inputBalanceAfter, amountIn);
assertEq(outputBalanceAfter - outputBalanceBefore, expectedAmountOut - expectedFee);
assertEq(bobBalanceAfter - bobBalanceBefore, expectedFee);
}

function test_settle_takePortion_reverts() public {
uint256 amountIn = 1 ether;
IV4Router.ExactInputSingleParams memory params =
IV4Router.ExactInputSingleParams(key0, true, uint128(amountIn), 0, 0, bytes(""));

plan = plan.add(Actions.SWAP_EXACT_IN_SINGLE, abi.encode(params));
plan = plan.add(Actions.SETTLE_ALL, abi.encode(key0.currency0));
// bips is larger than maximum bips
plan = plan.add(Actions.TAKE_PORTION, abi.encode(key0.currency1, bob, BipsLibrary.BIPS_BASE + 1));
plan = plan.add(Actions.TAKE_ALL, abi.encode(key0.currency1, address(this)));

bytes memory data = plan.encode();

vm.expectRevert(BipsLibrary.InvalidBips.selector);
router.executeActions(data);
}
}

0 comments on commit 36f19d5

Please sign in to comment.