Skip to content

Commit

Permalink
Merge branch 'main' into fee-controller-script
Browse files Browse the repository at this point in the history
  • Loading branch information
EndymionJkb committed Feb 18, 2025
2 parents c735ec2 + d516d78 commit 3b04170
Show file tree
Hide file tree
Showing 35 changed files with 224 additions and 155 deletions.
3 changes: 3 additions & 0 deletions pkg/interfaces/contracts/vault/IAggregatorRouter.sol
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { SwapKind } from "./VaultTypes.sol";

interface IAggregatorRouter {
/// @notice Thrown if native eth is received.
error CannotReceiveEth();

/// @notice Thrown when the sender does not transfer the correct amount of tokens to the Vault.
error SwapInsufficientPayment();

Expand Down
15 changes: 9 additions & 6 deletions pkg/interfaces/contracts/vault/IRouterCommon.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,11 @@
pragma solidity ^0.8.24;

import { IAllowanceTransfer } from "permit2/src/interfaces/IAllowanceTransfer.sol";
import { IPermit2 } from "permit2/src/interfaces/IPermit2.sol";
import { AddLiquidityKind, RemoveLiquidityKind } from "./VaultTypes.sol";

import { IWETH } from "../solidity-utils/misc/IWETH.sol";

/// @notice Interface for functions shared between the `Router` and `BatchRouter`.
interface IRouterCommon {
/**
Expand Down Expand Up @@ -47,16 +50,16 @@ interface IRouterCommon {
bytes userData;
}

/**
* @notice Get the first sender which initialized the call to Router.
* @return sender The address of the sender
*/
function getSender() external view returns (address sender);

/*******************************************************************************
Utils
*******************************************************************************/

/// @notice Returns WETH contract address.
function getWeth() external view returns (IWETH);

/// @notice Returns Permit2 contract address.
function getPermit2() external view returns (IPermit2);

struct PermitApproval {
address token;
address owner;
Expand Down
18 changes: 18 additions & 0 deletions pkg/interfaces/contracts/vault/IRouterCommonBase.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// SPDX-License-Identifier: GPL-3.0-or-later

pragma solidity ^0.8.24;

/// @notice Interface for functions shared across all trusted routers.
interface IRouterCommonBase {
/// @notice Incoming ETH transfer from an address that is not WETH.
error EthTransfer();

/// @notice The swap transaction was not validated before the specified deadline timestamp.
error SwapDeadline();

/**
* @notice Get the first sender which initialized the call to Router.
* @return sender The address of the sender
*/
function getSender() external view returns (address sender);
}
4 changes: 2 additions & 2 deletions pkg/pool-hooks/contracts/LotteryHookExample.sol
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.s
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol";

import { IRouterCommon } from "@balancer-labs/v3-interfaces/contracts/vault/IRouterCommon.sol";
import { IRouterCommonBase } from "@balancer-labs/v3-interfaces/contracts/vault/IRouterCommonBase.sol";
import { IHooks } from "@balancer-labs/v3-interfaces/contracts/vault/IHooks.sol";
import { IVault } from "@balancer-labs/v3-interfaces/contracts/vault/IVault.sol";
import {
Expand Down Expand Up @@ -203,7 +203,7 @@ contract LotteryHookExample is BaseHooks, VaultGuard, Ownable {
uint256 hookFee
) private returns (uint256) {
if (drawnNumber == LUCKY_NUMBER) {
address user = IRouterCommon(router).getSender();
address user = IRouterCommonBase(router).getSender();

// Iterating backwards is more efficient, since the last element is removed from the map on each iteration.
for (uint256 i = _tokensWithAccruedFees.size; i > 0; i--) {
Expand Down
4 changes: 2 additions & 2 deletions pkg/pool-hooks/contracts/MevCaptureHook.sol
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { Math } from "@openzeppelin/contracts/utils/math/Math.sol";
import {
IBalancerContractRegistry
} from "@balancer-labs/v3-interfaces/contracts/standalone-utils/IBalancerContractRegistry.sol";
import { IRouterCommon } from "@balancer-labs/v3-interfaces/contracts/vault/IRouterCommon.sol";
import { IRouterCommonBase } from "@balancer-labs/v3-interfaces/contracts/vault/IRouterCommonBase.sol";
import { IMevCaptureHook } from "@balancer-labs/v3-interfaces/contracts/pool-hooks/IMevCaptureHook.sol";
import { IHooks } from "@balancer-labs/v3-interfaces/contracts/vault/IHooks.sol";
import { IVault } from "@balancer-labs/v3-interfaces/contracts/vault/IVault.sol";
Expand Down Expand Up @@ -122,7 +122,7 @@ contract MevCaptureHook is BaseHooks, SingletonAuthentication, VaultGuard, IMevC

// We can only check senders if the router is trusted. Apply the exemption for MEV tax-exempt senders.
if (_registry.isTrustedRouter(params.router)) {
address sender = IRouterCommon(params.router).getSender();
address sender = IRouterCommonBase(params.router).getSender();
if (_isMevTaxExemptSender[sender]) {
return (true, staticSwapFeePercentage);
}
Expand Down
9 changes: 7 additions & 2 deletions pkg/pool-hooks/contracts/NftLiquidityPositionExample.sol
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { ERC721 } from "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import { IPermit2 } from "permit2/src/interfaces/IPermit2.sol";

import { IRouterCommon } from "@balancer-labs/v3-interfaces/contracts/vault/IRouterCommon.sol";
import { IRouterCommonBase } from "@balancer-labs/v3-interfaces/contracts/vault/IRouterCommonBase.sol";
import { IWETH } from "@balancer-labs/v3-interfaces/contracts/solidity-utils/misc/IWETH.sol";
import { IVault } from "@balancer-labs/v3-interfaces/contracts/vault/IVault.sol";
import {
Expand Down Expand Up @@ -256,7 +256,12 @@ contract NftLiquidityPositionExample is MinimalRouter, ERC721, BaseHooks {
hookAdjustedAmountsOutRaw = amountsOutRaw;
uint256 currentFee = getCurrentFeePercentage(tokenId);
if (currentFee > 0) {
hookAdjustedAmountsOutRaw = _takeFee(IRouterCommon(router).getSender(), pool, amountsOutRaw, currentFee);
hookAdjustedAmountsOutRaw = _takeFee(
IRouterCommonBase(router).getSender(),
pool,
amountsOutRaw,
currentFee
);
}
return (true, hookAdjustedAmountsOutRaw);
}
Expand Down
4 changes: 2 additions & 2 deletions pkg/pool-hooks/contracts/VeBALFeeDiscountHookExample.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ pragma solidity ^0.8.24;
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";

import { IBasePoolFactory } from "@balancer-labs/v3-interfaces/contracts/vault/IBasePoolFactory.sol";
import { IRouterCommon } from "@balancer-labs/v3-interfaces/contracts/vault/IRouterCommon.sol";
import { IRouterCommonBase } from "@balancer-labs/v3-interfaces/contracts/vault/IRouterCommonBase.sol";
import { IHooks } from "@balancer-labs/v3-interfaces/contracts/vault/IHooks.sol";
import { IVault } from "@balancer-labs/v3-interfaces/contracts/vault/IVault.sol";
import {
Expand Down Expand Up @@ -84,7 +84,7 @@ contract VeBALFeeDiscountHookExample is BaseHooks, VaultGuard {
return (true, staticSwapFeePercentage);
}

address user = IRouterCommon(params.router).getSender();
address user = IRouterCommonBase(params.router).getSender();

// If user has veBAL, apply a 50% discount to the current fee.
if (_veBAL.balanceOf(user) > 0) {
Expand Down
6 changes: 3 additions & 3 deletions pkg/pool-weighted/contracts/lbp/LBPool.sol
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { Ownable2Step } from "@openzeppelin/contracts/access/Ownable2Step.sol";
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol";

import { IRouterCommon } from "@balancer-labs/v3-interfaces/contracts/vault/IRouterCommon.sol";
import { IRouterCommonBase } from "@balancer-labs/v3-interfaces/contracts/vault/IRouterCommonBase.sol";
import { IVaultErrors } from "@balancer-labs/v3-interfaces/contracts/vault/IVaultErrors.sol";
import { IBasePool } from "@balancer-labs/v3-interfaces/contracts/vault/IBasePool.sol";
import { IVault } from "@balancer-labs/v3-interfaces/contracts/vault/IVault.sol";
Expand Down Expand Up @@ -358,7 +358,7 @@ contract LBPool is ILBPool, WeightedPool, Ownable2Step, BaseHooks {
uint256[] memory,
bytes memory
) public view override onlyVault onlyBeforeSale returns (bool) {
return IRouterCommon(_trustedRouter).getSender() == owner();
return IRouterCommonBase(_trustedRouter).getSender() == owner();
}

/**
Expand All @@ -375,7 +375,7 @@ contract LBPool is ILBPool, WeightedPool, Ownable2Step, BaseHooks {
uint256[] memory,
bytes memory
) public view override onlyVault onlyBeforeSale returns (bool) {
return router == _trustedRouter && IRouterCommon(router).getSender() == owner();
return router == _trustedRouter && IRouterCommonBase(router).getSender() == owner();
}

/**
Expand Down
4 changes: 2 additions & 2 deletions pkg/pool-weighted/test/foundry/LBPool.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { Create2 } from "@openzeppelin/contracts/utils/Create2.sol";

import { IAuthentication } from "@balancer-labs/v3-interfaces/contracts/solidity-utils/helpers/IAuthentication.sol";
import { IWeightedPool } from "@balancer-labs/v3-interfaces/contracts/pool-weighted/IWeightedPool.sol";
import { IRouterCommon } from "@balancer-labs/v3-interfaces/contracts/vault/IRouterCommon.sol";
import { IRouterCommonBase } from "@balancer-labs/v3-interfaces/contracts/vault/IRouterCommonBase.sol";
import { IVaultErrors } from "@balancer-labs/v3-interfaces/contracts/vault/IVaultErrors.sol";
import {
LBPoolImmutableData,
Expand Down Expand Up @@ -731,6 +731,6 @@ contract LBPoolTest is BaseLBPTest {
*******************************************************************************/

function _mockGetSender(address sender) private {
vm.mockCall(address(router), abi.encodeWithSelector(IRouterCommon.getSender.selector), abi.encode(sender));
vm.mockCall(address(router), abi.encodeWithSelector(IRouterCommonBase.getSender.selector), abi.encode(sender));
}
}
27 changes: 17 additions & 10 deletions pkg/vault/contracts/AggregatorRouter.sol
Original file line number Diff line number Diff line change
Expand Up @@ -12,21 +12,16 @@ import { IAggregatorRouter } from "@balancer-labs/v3-interfaces/contracts/vault/
import { IVault } from "@balancer-labs/v3-interfaces/contracts/vault/IVault.sol";
import "@balancer-labs/v3-interfaces/contracts/vault/VaultTypes.sol";

import { RouterCommon } from "./RouterCommon.sol";
import { RouterCommonBase } from "./RouterCommonBase.sol";

/**
* @notice Entrypoint for aggregators who want to swap without the standard permit2 payment logic.
* @dev The external API functions unlock the Vault, which calls back into the corresponding hook functions.
* These interact with the Vault and settle accounting. This is not a full-featured Router; it only implements
* `swapSingleTokenExactIn`, `swapSingleTokenExactOut`, and the associated queries.
*/
contract AggregatorRouter is IAggregatorRouter, RouterCommon {
constructor(
IVault vault,
IWETH weth,
IPermit2 permit2,
string memory routerVersion
) RouterCommon(vault, weth, permit2, routerVersion) {
contract AggregatorRouter is IAggregatorRouter, RouterCommonBase {
constructor(IVault vault, string memory routerVersion) RouterCommonBase(vault, routerVersion) {
// solhint-disable-previous-line no-empty-blocks
}

Expand Down Expand Up @@ -133,11 +128,11 @@ contract AggregatorRouter is IAggregatorRouter, RouterCommon {
if (params.kind == SwapKind.EXACT_OUT) {
// Transfer any leftovers back to the sender (amount actually paid minus amount required for the swap).
// At this point, the Vault already validated that `tokenInCredit > amountIn`.
_sendTokenOut(params.sender, params.tokenIn, tokenInCredit - amountIn, false);
_sendTokenOut(params.sender, params.tokenIn, tokenInCredit - amountIn);
}

// Finally, settle the output token by sending the credited tokens to the sender.
_sendTokenOut(params.sender, params.tokenOut, amountOut, false);
_sendTokenOut(params.sender, params.tokenOut, amountOut);

return amountCalculated;
}
Expand Down Expand Up @@ -245,4 +240,16 @@ contract AggregatorRouter is IAggregatorRouter, RouterCommon {

return amountCalculated;
}

function _sendTokenOut(address sender, IERC20 tokenOut, uint256 amountOut) internal {
if (amountOut == 0) {
return;
}

_vault.sendTo(tokenOut, sender, amountOut);
}

receive() external payable {
revert CannotReceiveEth();
}
}
86 changes: 9 additions & 77 deletions pkg/vault/contracts/RouterCommon.sol
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,11 @@ import { RevertCodec } from "@balancer-labs/v3-solidity-utils/contracts/helpers/
import {
ReentrancyGuardTransient
} from "@balancer-labs/v3-solidity-utils/contracts/openzeppelin/ReentrancyGuardTransient.sol";
import { Version } from "@balancer-labs/v3-solidity-utils/contracts/helpers/Version.sol";
import {
TransientStorageHelpers
} from "@balancer-labs/v3-solidity-utils/contracts/helpers/TransientStorageHelpers.sol";

import { VaultGuard } from "./VaultGuard.sol";
import { RouterCommonBase } from "./RouterCommonBase.sol";
import { RouterWethLib } from "./lib/RouterWethLib.sol";

/**
Expand All @@ -34,7 +33,7 @@ import { RouterWethLib } from "./lib/RouterWethLib.sol";
* Vault is the Router contract itself, not the account that invoked the Router), versioning, and the external
* invocation functions (`permitBatchAndCall` and `multicall`).
*/
abstract contract RouterCommon is IRouterCommon, VaultGuard, ReentrancyGuardTransient, Version {
abstract contract RouterCommon is IRouterCommon, RouterCommonBase {
using Address for address payable;
using StorageSlotExtension for *;
using RouterWethLib for IWETH;
Expand All @@ -44,57 +43,15 @@ abstract contract RouterCommon is IRouterCommon, VaultGuard, ReentrancyGuardTran
// after =. If you use immutable, the value is first calculated and then replaced everywhere. That means that if a
// constant has executable variables, they will be executed every time the constant is used.

// solhint-disable-next-line var-name-mixedcase
bytes32 private immutable _SENDER_SLOT = TransientStorageHelpers.calculateSlot(type(RouterCommon).name, "sender");

// solhint-disable-next-line var-name-mixedcase
bytes32 private immutable _IS_RETURN_ETH_LOCKED_SLOT =
TransientStorageHelpers.calculateSlot(type(RouterCommon).name, "isReturnEthLocked");

/// @notice Incoming ETH transfer from an address that is not WETH.
error EthTransfer();

/// @notice The swap transaction was not validated before the specified deadline timestamp.
error SwapDeadline();

// Raw token balances are stored in half a slot, so the max is uint128. Moreover, given that amounts are usually
// scaled inside the Vault, sending type(uint256).max would result in an overflow and revert.
uint256 internal constant _MAX_AMOUNT = type(uint128).max;

// solhint-disable-next-line var-name-mixedcase
IWETH internal immutable _weth;

IPermit2 internal immutable _permit2;

/**
* @notice Saves the user or contract that initiated the current operation.
* @dev It is possible to nest router calls (e.g., with reentrant hooks), but the sender returned by the Router's
* `getSender` function will always be the "outermost" caller. Some transactions require the Router to identify
* multiple senders. Consider the following example:
*
* - ContractA has a function that calls the Router, then calls ContractB with the output. ContractB in turn
* calls back into the Router.
* - Imagine further that ContractA is a pool with a "before" hook that also calls the Router.
*
* When the user calls the function on ContractA, there are three calls to the Router in the same transaction:
* - 1st call: When ContractA calls the Router directly, to initiate an operation on the pool (say, a swap).
* (Sender is contractA, initiator of the operation.)
*
* - 2nd call: When the pool operation invokes a hook (say onBeforeSwap), which calls back into the Router.
* This is a "nested" call within the original pool operation. The nested call returns, then the
* before hook returns, the Router completes the operation, and finally returns back to ContractA
* with the result (e.g., a calculated amount of tokens).
* (Nested call; sender is still ContractA through all of this.)
*
* - 3rd call: When the first operation is complete, ContractA calls ContractB, which in turn calls the Router.
* (Not nested, as the original router call from contractA has returned. Sender is now ContractB.)
*/
modifier saveSender(address sender) {
bool isExternalSender = _saveSender(sender);
_;
_discardSenderIfRequired(isExternalSender);
}

/**
* @notice Locks the return of excess ETH to the sender until the end of the function.
* @dev This also encompasses the `saveSender` functionality.
Expand All @@ -118,38 +75,22 @@ abstract contract RouterCommon is IRouterCommon, VaultGuard, ReentrancyGuardTran
_returnEth(sender);
}

function _saveSender(address sender) internal returns (bool isExternalSender) {
address savedSender = _getSenderSlot().tload();

// NOTE: Only the most external sender will be saved by the Router.
if (savedSender == address(0)) {
_getSenderSlot().tstore(sender);
isExternalSender = true;
}
}

function _discardSenderIfRequired(bool isExternalSender) internal {
// Only the external sender shall be cleaned up; if it's not an external sender it means that
// the value was not saved in this modifier.
if (isExternalSender) {
_getSenderSlot().tstore(address(0));
}
}

constructor(
IVault vault,
IWETH weth,
IPermit2 permit2,
string memory routerVersion
) VaultGuard(vault) Version(routerVersion) {
) RouterCommonBase(vault, routerVersion) {
_weth = weth;
_permit2 = permit2;
}

/// @inheritdoc IRouterCommon
function getWeth() external view returns (IWETH) {
return _weth;
}

/// @inheritdoc IRouterCommon
function getPermit2() external view returns (IPermit2) {
return _permit2;
}
Expand Down Expand Up @@ -341,6 +282,10 @@ abstract contract RouterCommon is IRouterCommon, VaultGuard, ReentrancyGuardTran
}
}

function _isReturnEthLockedSlot() internal view returns (StorageSlotExtension.BooleanSlotType) {
return _IS_RETURN_ETH_LOCKED_SLOT.asBoolean();
}

/**
* @dev Enables the Router to receive ETH. This is required for it to be able to unwrap WETH, which sends ETH to the
* caller.
Expand All @@ -356,17 +301,4 @@ abstract contract RouterCommon is IRouterCommon, VaultGuard, ReentrancyGuardTran
revert EthTransfer();
}
}

/// @inheritdoc IRouterCommon
function getSender() external view returns (address) {
return _getSenderSlot().tload();
}

function _getSenderSlot() internal view returns (StorageSlotExtension.AddressSlotType) {
return _SENDER_SLOT.asAddress();
}

function _isReturnEthLockedSlot() internal view returns (StorageSlotExtension.BooleanSlotType) {
return _IS_RETURN_ETH_LOCKED_SLOT.asBoolean();
}
}
Loading

0 comments on commit 3b04170

Please sign in to comment.