Skip to content

Commit

Permalink
Merge pull request #313 from morpho-org/feat/only-initiated
Browse files Browse the repository at this point in the history
feat(protect): add onlyInitiated modifier
  • Loading branch information
MerlinEgalite authored Nov 8, 2023
2 parents c4b7827 + 0a052d8 commit c57705a
Show file tree
Hide file tree
Showing 24 changed files with 175 additions and 45 deletions.
14 changes: 9 additions & 5 deletions src/BaseBundler.sol
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,15 @@ abstract contract BaseBundler is IMulticall {
/// @dev Also prevents interacting with the bundler outside of an initiated execution context.
address private _initiator = UNSET_INITIATOR;

/* MODIFIERS */

/// @dev Prevents a function to be called outside a `multicall` context.
modifier onlyInitiated() {
require(_initiator != UNSET_INITIATOR, ErrorsLib.UNINITIATED);

_;
}

/* PUBLIC */

/// @notice Returns the address of the initiator of the multicall transaction.
Expand Down Expand Up @@ -60,11 +69,6 @@ abstract contract BaseBundler is IMulticall {
}
}

/// @dev Checks that the contract is in an initiated execution context.
function _checkInitiated() internal view {
require(_initiator != UNSET_INITIATOR, ErrorsLib.UNINITIATED);
}

/// @dev Bubbles up the revert reason / custom error encoded in `returnData`.
/// @dev Assumes `returnData` is the return data of any kind of failing CALL to a contract.
function _revert(bytes memory returnData) internal pure {
Expand Down
14 changes: 10 additions & 4 deletions src/ERC4626Bundler.sol
Original file line number Diff line number Diff line change
Expand Up @@ -67,14 +67,17 @@ abstract contract ERC4626Bundler is BaseBundler {

/// @notice Withdraws the given amount of `assets` from the given ERC4626 `vault`, transferring assets to
/// `receiver`.
/// @notice Warning: should only be called via the bundler's `multicall` function.
/// @dev Warning: `vault` can re-enter the bundler flow.
/// @dev Assumes the given `vault` implements EIP-4626.
/// @param vault The address of the vault.
/// @param assets The amount of assets to withdraw. Pass `type(uint256).max` to withdraw max.
/// @param maxShares The maximum amount of shares to redeem in exchange for `assets`.
/// @param receiver The address that will receive the withdrawn assets.
function erc4626Withdraw(address vault, uint256 assets, uint256 maxShares, address receiver) external payable {
function erc4626Withdraw(address vault, uint256 assets, uint256 maxShares, address receiver)
external
payable
onlyInitiated
{
require(receiver != address(0), ErrorsLib.ZERO_ADDRESS);
/// Do not check `receiver != address(this)` to allow the bundler to receive the underlying asset.

Expand All @@ -89,14 +92,17 @@ abstract contract ERC4626Bundler is BaseBundler {
}

/// @notice Redeems the given amount of `shares` from the given ERC4626 `vault`, transferring assets to `receiver`.
/// @notice Warning: should only be called via the bundler's `multicall` function.
/// @dev Warning: `vault` can re-enter the bundler flow.
/// @dev Assumes the given `vault` implements EIP-4626.
/// @param vault The address of the vault.
/// @param shares The amount of shares to burn. Pass `type(uint256).max` to redeem max.
/// @param minAssets The minimum amount of assets to withdraw in exchange for `shares`.
/// @param receiver The address that will receive the withdrawn assets.
function erc4626Redeem(address vault, uint256 shares, uint256 minAssets, address receiver) external payable {
function erc4626Redeem(address vault, uint256 shares, uint256 minAssets, address receiver)
external
payable
onlyInitiated
{
require(receiver != address(0), ErrorsLib.ZERO_ADDRESS);
/// Do not check `receiver != address(this)` to allow the bundler to receive the underlying asset.

Expand Down
12 changes: 4 additions & 8 deletions src/MorphoBundler.sol
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,6 @@ abstract contract MorphoBundler is BaseBundler, IMorphoBundler {
}

/// @notice Borrows `assets` of the loan asset on behalf of the initiator.
/// @notice Warning: should only be called via the bundler's `multicall` function.
/// @dev Either `assets` or `shares` should be zero. Most usecases should rely on `assets` as an input so the
/// initiator is guaranteed to borrow `assets` tokens, but the possibility to mint a specific amount of shares is
/// given for full compatibility and precision.
Expand All @@ -148,7 +147,7 @@ abstract contract MorphoBundler is BaseBundler, IMorphoBundler {
uint256 shares,
uint256 slippageAmount,
address receiver
) external payable {
) external payable onlyInitiated {
(uint256 borrowedAssets, uint256 borrowedShares) =
MORPHO.borrow(marketParams, assets, shares, initiator(), receiver);

Expand Down Expand Up @@ -191,7 +190,6 @@ abstract contract MorphoBundler is BaseBundler, IMorphoBundler {
}

/// @notice Withdraws `assets` of the loan asset on behalf of the initiator.
/// @notice Warning: should only be called via the bundler's `multicall` function.
/// @dev Either `assets` or `shares` should be zero. Most usecases should rely on `assets` as an input so the
/// initiator is guaranteed to withdraw `assets` tokens, but the possibility to burn a specific amount of shares is
/// given for full compatibility and precision.
Expand All @@ -208,7 +206,7 @@ abstract contract MorphoBundler is BaseBundler, IMorphoBundler {
uint256 shares,
uint256 slippageAmount,
address receiver
) external payable {
) external payable onlyInitiated {
(uint256 withdrawnAssets, uint256 withdrawnShares) =
MORPHO.withdraw(marketParams, assets, shares, initiator(), receiver);

Expand All @@ -217,14 +215,14 @@ abstract contract MorphoBundler is BaseBundler, IMorphoBundler {
}

/// @notice Withdraws `assets` of the collateral asset on behalf of the initiator.
/// @notice Warning: should only be called via the bundler's `multicall` function.
/// @dev Initiator must have previously authorized the bundler to act on their behalf on Morpho.
/// @param marketParams The Morpho market to withdraw collateral from.
/// @param assets The amount of collateral to withdraw.
/// @param receiver The address that will receive the collateral assets.
function morphoWithdrawCollateral(MarketParams calldata marketParams, uint256 assets, address receiver)
external
payable
onlyInitiated
{
MORPHO.withdrawCollateral(marketParams, assets, initiator(), receiver);
}
Expand Down Expand Up @@ -266,9 +264,7 @@ abstract contract MorphoBundler is BaseBundler, IMorphoBundler {
/* INTERNAL */

/// @dev Triggers `_multicall` logic during a callback.
function _callback(bytes calldata data) internal {
_checkInitiated();

function _callback(bytes calldata data) internal onlyInitiated {
_multicall(abi.decode(data, (bytes[])));
}
}
4 changes: 2 additions & 2 deletions src/Permit2Bundler.sol
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,15 @@ abstract contract Permit2Bundler is BaseBundler {
/* ACTIONS */

/// @notice Permits and performs a transfer from the initiator to the recipient via Permit2.
/// @notice Warning: should only be called via the bundler's `multicall` function.
/// @notice User must have given sufficient allowance to the Permit2 contract to manage his tokens.
/// @notice User must have given sufficient allowance to the Permit2 contract to manage their tokens.
/// @dev Warning: `permit.permitted.token` can re-enter the bundler flow.
/// @dev Pass `permit.permitted.amount = type(uint256).max` to transfer all.
/// @param permit The `PermitTransferFrom` struct.
/// @param signature The signature.
function permit2TransferFrom(ISignatureTransfer.PermitTransferFrom memory permit, bytes memory signature)
external
payable
onlyInitiated
{
address initiator = initiator();
uint256 amount = Math.min(permit.permitted.amount, ERC20(permit.permitted.token).balanceOf(initiator));
Expand Down
2 changes: 1 addition & 1 deletion src/PermitBundler.sol
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ import {BaseBundler} from "./BaseBundler.sol";
abstract contract PermitBundler is BaseBundler {
/// @notice Permits the given `amount` of `asset` from sender to be spent by the bundler via EIP-2612 Permit with
/// the given `deadline` & EIP-712 signature's `v`, `r` & `s`.
/// @notice Warning: should only be called via the bundler's `multicall` function.
/// @dev Warning: `asset` can re-enter the bundler flow.
/// @param asset The address of the token to be permitted.
/// @param amount The amount of `asset` to be permitted.
Expand All @@ -24,6 +23,7 @@ abstract contract PermitBundler is BaseBundler {
function permit(address asset, uint256 amount, uint256 deadline, uint8 v, bytes32 r, bytes32 s, bool skipRevert)
external
payable
onlyInitiated
{
try IERC20Permit(asset).permit(initiator(), address(this), amount, deadline, v, r, s) {}
catch (bytes memory returnData) {
Expand Down
5 changes: 2 additions & 3 deletions src/TransferBundler.sol
Original file line number Diff line number Diff line change
Expand Up @@ -52,13 +52,12 @@ abstract contract TransferBundler is BaseBundler {
}

/// @notice Transfers the given `amount` of `asset` from sender to this contract via ERC20 transferFrom.
/// @notice User must have given sufficient allowance to the Bundler to manage his tokens.
/// @notice Warning: should only be called via the bundler's `multicall` function.
/// @notice User must have given sufficient allowance to the Bundler to manage their tokens.
/// @dev Warning: `asset` can re-enter the bundler flow.
/// @param asset The address of the ERC20 token to transfer.
/// @param amount The amount of `asset` to transfer from the initiator. Pass `type(uint256).max` to transfer the
/// initiator's balance.
function erc20TransferFrom(address asset, uint256 amount) external payable {
function erc20TransferFrom(address asset, uint256 amount) external payable onlyInitiated {
address initiator = initiator();
amount = Math.min(amount, ERC20(asset).balanceOf(initiator));

Expand Down
2 changes: 0 additions & 2 deletions src/WNativeBundler.sol
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,6 @@ abstract contract WNativeBundler is BaseBundler {
/* ACTIONS */

/// @notice Wraps the given `amount` of the native token to wNative.
/// @notice Warning: should only be called via the bundler's `multicall` function.
/// @notice Wrapped native tokens are received by the bundler and should be used afterwards.
/// @dev Initiator must have previously transferred their native tokens to the bundler.
/// @param amount The amount of native token to wrap. Pass `type(uint256).max` to wrap all.
Expand All @@ -52,7 +51,6 @@ abstract contract WNativeBundler is BaseBundler {
}

/// @notice Unwraps the given `amount` of wNative to the native token.
/// @notice Warning: should only be called via the bundler's `multicall` function.
/// @notice Unwrapped native tokens are received by the bundler and should be used afterwards.
/// @dev Initiator must have previously transferred their wrapped native tokens to the bundler.
/// @param amount The amount of wrapped native token to unwrap. Pass `type(uint256).max` to unwrap all.
Expand Down
2 changes: 1 addition & 1 deletion src/ethereum/EthereumPermitBundler.sol
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ import {PermitBundler} from "../PermitBundler.sol";
abstract contract EthereumPermitBundler is PermitBundler {
/// @notice Permits DAI from sender to be spent by the bundler with the given `nonce`, `expiry` & EIP-712
/// signature's `v`, `r` & `s`.
/// @notice Warning: should only be called via the bundler's `multicall` function.
/// @param nonce The nonce of the signed message.
/// @param expiry The expiry of the signed message.
/// @param allowed Whether the initiator gives the bundler infinite Dai approval or not.
Expand All @@ -25,6 +24,7 @@ abstract contract EthereumPermitBundler is PermitBundler {
function permitDai(uint256 nonce, uint256 expiry, bool allowed, uint8 v, bytes32 r, bytes32 s, bool skipRevert)
external
payable
onlyInitiated
{
try IDaiPermit(MainnetLib.DAI).permit(initiator(), address(this), nonce, expiry, allowed, v, r, s) {}
catch (bytes memory returnData) {
Expand Down
3 changes: 1 addition & 2 deletions src/migration/AaveV2MigrationBundler.sol
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,12 @@ contract AaveV2MigrationBundler is MigrationBundler {
/* ACTIONS */

/// @notice Repays `amount` of `asset` on AaveV2, on behalf of the initiator.
/// @notice Warning: should only be called via the bundler's `multicall` function.
/// @dev Initiator must have previously transferred their assets to the bundler.
/// @dev Warning: `asset` can re-enter the bundler flow.
/// @param asset The address of the token to repay.
/// @param amount The amount of `asset` to repay. Pass `type(uint256).max` to repay the bundler's `asset` balance.
/// @param interestRateMode The interest rate mode of the position.
function aaveV2Repay(address asset, uint256 amount, uint256 interestRateMode) external payable {
function aaveV2Repay(address asset, uint256 amount, uint256 interestRateMode) external payable onlyInitiated {
if (amount != type(uint256).max) amount = Math.min(amount, ERC20(asset).balanceOf(address(this)));

require(amount != 0, ErrorsLib.ZERO_AMOUNT);
Expand Down
3 changes: 1 addition & 2 deletions src/migration/AaveV3MigrationBundler.sol
Original file line number Diff line number Diff line change
Expand Up @@ -30,13 +30,12 @@ contract AaveV3MigrationBundler is MigrationBundler {
/* ACTIONS */

/// @notice Repays `amount` of `asset` on AaveV3, on behalf of the initiator.
/// @notice Warning: should only be called via the bundler's `multicall` function.
/// @dev Initiator must have previously transferred their assets to the bundler.
/// @dev Warning: `asset` can re-enter the bundler flow.
/// @param asset The address of the token to repay.
/// @param amount The amount of `asset` to repay. Pass `type(uint256).max` to repay the bundler's `asset` balance.
/// @param interestRateMode The interest rate mode of the position.
function aaveV3Repay(address asset, uint256 amount, uint256 interestRateMode) external payable {
function aaveV3Repay(address asset, uint256 amount, uint256 interestRateMode) external payable onlyInitiated {
if (amount != type(uint256).max) amount = Math.min(amount, ERC20(asset).balanceOf(address(this)));

require(amount != 0, ErrorsLib.ZERO_AMOUNT);
Expand Down
15 changes: 8 additions & 7 deletions src/migration/AaveV3OptimizerMigrationBundler.sol
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ contract AaveV3OptimizerMigrationBundler is MigrationBundler {
/// @param underlying The address of the underlying asset to repay.
/// @param amount The amount of `underlying` to repay. Pass `type(uint256).max` to repay the bundler's `underlying`
/// balance.
function aaveV3OptimizerRepay(address underlying, uint256 amount) external payable {
function aaveV3OptimizerRepay(address underlying, uint256 amount) external payable onlyInitiated {
if (amount != type(uint256).max) amount = Math.min(amount, ERC20(underlying).balanceOf(address(this)));

require(amount != 0, ErrorsLib.ZERO_AMOUNT);
Expand All @@ -47,31 +47,32 @@ contract AaveV3OptimizerMigrationBundler is MigrationBundler {
}

/// @notice Withdraws `amount` of `underlying` on the AaveV3 Optimizer, on behalf of the initiator`.
/// @notice Warning: should only be called via the bundler's `multicall` function.
/// @notice Withdrawn assets are received by the bundler and should be used afterwards.
/// @dev Initiator must have previously approved the bundler to manage their AaveV3 Optimizer position.
/// @param underlying The address of the underlying asset to withdraw.
/// @param amount The amount of `underlying` to withdraw. Pass `type(uint256).max` to withdraw all.
/// @param maxIterations The maximum number of iterations allowed during the matching process. If it is less than
/// `_defaultIterations.withdraw`, the latter will be used. Pass 0 to fallback to the `_defaultIterations.withdraw`.
function aaveV3OptimizerWithdraw(address underlying, uint256 amount, uint256 maxIterations) external payable {
function aaveV3OptimizerWithdraw(address underlying, uint256 amount, uint256 maxIterations)
external
payable
onlyInitiated
{
AAVE_V3_OPTIMIZER.withdraw(underlying, amount, initiator(), address(this), maxIterations);
}

/// @notice Withdraws `amount` of `underlying` used as collateral on the AaveV3 Optimizer, on behalf of the
/// initiator.
/// @notice Warning: should only be called via the bundler's `multicall` function.
/// @notice Withdrawn assets are received by the bundler and should be used afterwards.
/// @dev Initiator must have previously approved the bundler to manage their AaveV3 Optimizer position.
/// @param underlying The address of the underlying asset to withdraw.
/// @param amount The amount of `underlying` to withdraw. Pass `type(uint256).max` to withdraw all.
function aaveV3OptimizerWithdrawCollateral(address underlying, uint256 amount) external payable {
function aaveV3OptimizerWithdrawCollateral(address underlying, uint256 amount) external payable onlyInitiated {
AAVE_V3_OPTIMIZER.withdrawCollateral(underlying, amount, initiator(), address(this));
}

/// @notice Approves the bundler to act on behalf of the initiator on the AaveV3 Optimizer, given a signed EIP-712
/// approval message.
/// @notice Warning: should only be called via the bundler's `multicall` function.
/// @param isApproved Whether the bundler is allowed to manage the initiator's position or not.
/// @param nonce The nonce of the signed message.
/// @param deadline The deadline of the signed message.
Expand All @@ -83,7 +84,7 @@ contract AaveV3OptimizerMigrationBundler is MigrationBundler {
uint256 deadline,
Signature calldata signature,
bool skipRevert
) external payable {
) external payable onlyInitiated {
try AAVE_V3_OPTIMIZER.approveManagerWithSig(initiator(), address(this), isApproved, nonce, deadline, signature)
{} catch (bytes memory returnData) {
if (!skipRevert) _revert(returnData);
Expand Down
3 changes: 1 addition & 2 deletions src/migration/CompoundV2MigrationBundler.sol
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,11 @@ contract CompoundV2MigrationBundler is WNativeBundler, MigrationBundler {
/* ACTIONS */

/// @notice Repays `amount` of `cToken`'s underlying asset, on behalf of the initiator.
/// @notice Warning: should only be called via the bundler's `multicall` function.
/// @dev Initiator must have previously transferred their assets to the bundler.
/// @dev Warning: `cToken` can re-enter the bundler flow.
/// @param cToken The address of the cToken contract
/// @param amount The amount of `cToken` to repay. Pass `type(uint256).max` to repay all (except for cETH).
function compoundV2Repay(address cToken, uint256 amount) external payable {
function compoundV2Repay(address cToken, uint256 amount) external payable onlyInitiated {
if (cToken == C_ETH) {
amount = Math.min(amount, address(this).balance);

Expand Down
Loading

0 comments on commit c57705a

Please sign in to comment.