Skip to content

Commit

Permalink
fix: issue 23
Browse files Browse the repository at this point in the history
  • Loading branch information
Jean-Grimal committed Oct 23, 2023
1 parent 9c73205 commit 1b4e825
Show file tree
Hide file tree
Showing 26 changed files with 214 additions and 21 deletions.
4 changes: 2 additions & 2 deletions src/ERC4626Bundler.sol
Original file line number Diff line number Diff line change
Expand Up @@ -60,10 +60,10 @@ 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 Pass `type(uint256).max` as `assets` to withdraw max.
/// @dev Assumes the given `vault` implements EIP-4626.
function erc4626Withdraw(address vault, uint256 assets, address receiver) external payable {
_checkInitiated();
require(receiver != address(0), ErrorsLib.ZERO_ADDRESS);
/// Do not check `receiver != address(this)` to allow the bundler to receive the underlying asset.

Expand All @@ -77,10 +77,10 @@ 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 Pass `type(uint256).max` as `shares` to redeem max.
/// @dev Assumes the given `vault` implements EIP-4626.
function erc4626Redeem(address vault, uint256 shares, address receiver) external payable {
_checkInitiated();
require(receiver != address(0), ErrorsLib.ZERO_ADDRESS);
/// Do not check `receiver != address(this)` to allow the bundler to receive the underlying asset.

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

/// @notice Borrows `amount` of `asset` on behalf of the sender.
/// @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.
function morphoBorrow(MarketParams calldata marketParams, uint256 amount, uint256 shares, address receiver)
external
payable
{
_checkInitiated();

MORPHO.borrow(marketParams, amount, shares, initiator(), receiver);
}

Expand All @@ -142,22 +143,24 @@ abstract contract MorphoBundler is BaseBundler, IMorphoBundler {
}

/// @notice Withdraws `amount` of the loan asset on behalf of `onBehalf`.
/// @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.
function morphoWithdraw(MarketParams calldata marketParams, uint256 amount, uint256 shares, address receiver)
external
payable
{
_checkInitiated();

MORPHO.withdraw(marketParams, amount, shares, initiator(), receiver);
}

/// @notice Withdraws `amount` of the collateral asset on behalf of sender.
/// @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.
function morphoWithdrawCollateral(MarketParams calldata marketParams, uint256 amount, address receiver)
external
payable
{
_checkInitiated();

MORPHO.withdrawCollateral(marketParams, amount, initiator(), receiver);
}

Expand Down
3 changes: 2 additions & 1 deletion src/Permit2Bundler.sol
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,13 @@ 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.
/// @dev Pass `permit.permitted.amount = type(uint256).max` to transfer all.
function permit2TransferFrom(ISignatureTransfer.PermitTransferFrom memory permit, bytes memory signature)
external
payable
{
_checkInitiated();

address initiator = initiator();
uint256 amount = Math.min(permit.permitted.amount, ERC20(permit.permitted.token).balanceOf(initiator));

Expand Down
3 changes: 2 additions & 1 deletion src/PermitBundler.sol
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,13 @@ 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 Pass `skipRevert == true` to avoid reverting the whole bundle in case the signature expired.
function permit(address asset, uint256 amount, uint256 deadline, uint8 v, bytes32 r, bytes32 s, bool skipRevert)
external
payable
{
_checkInitiated();

try IERC20Permit(asset).permit(initiator(), address(this), amount, deadline, v, r, s) {}
catch (bytes memory returnData) {
if (!skipRevert) _revert(returnData);
Expand Down
3 changes: 2 additions & 1 deletion src/TransferBundler.sol
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,10 @@ abstract contract TransferBundler is BaseBundler {
}

/// @notice Transfers the given `amount` of `asset` from sender to this contract via ERC20 transferFrom.
/// @notice Warning: should only be called via the bundler's `multicall` function.
/// @dev Pass `amount = type(uint256).max` to transfer all.
function erc20TransferFrom(address asset, uint256 amount) external payable {
_checkInitiated();

address initiator = initiator();
amount = Math.min(amount, ERC20(asset).balanceOf(initiator));

Expand Down
6 changes: 4 additions & 2 deletions src/WNativeBundler.sol
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,10 @@ 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.
/// @dev Pass `amount = type(uint256).max` to wrap all.
function wrapNative(uint256 amount) external payable {
_checkInitiated();

amount = Math.min(amount, address(this).balance);

require(amount != 0, ErrorsLib.ZERO_AMOUNT);
Expand All @@ -49,9 +50,10 @@ 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.
/// @dev Pass `amount = type(uint256).max` to unwrap all.
function unwrapNative(uint256 amount) external payable {
_checkInitiated();

amount = Math.min(amount, ERC20(WRAPPED_NATIVE).balanceOf(address(this)));

require(amount != 0, ErrorsLib.ZERO_AMOUNT);
Expand Down
3 changes: 2 additions & 1 deletion src/ethereum/EthereumPermitBundler.sol
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,13 @@ 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.
/// @dev Pass `skipRevert = true` to avoid reverting the whole bundle in case the signature expired.
function permitDai(uint256 nonce, uint256 expiry, bool allowed, uint8 v, bytes32 r, bytes32 s, bool skipRevert)
external
payable
{
_checkInitiated();

try IDaiPermit(MainnetLib.DAI).permit(initiator(), address(this), nonce, expiry, allowed, v, r, s) {}
catch (bytes memory returnData) {
if (!skipRevert) _revert(returnData);
Expand Down
7 changes: 7 additions & 0 deletions src/interfaces/ICompoundV2MigrationBundler.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity >=0.5.0;

interface ICompoundV2MigrationBundler {
function compoundV2Repay(address cToken, uint256 amount) external payable;
function compoundV2Redeem(address cToken, uint256 amount) external payable;
}
7 changes: 7 additions & 0 deletions src/interfaces/IWNativeBundler.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity >=0.5.0;

interface IWNativeBundler {
function wrapNative(uint256 amount) external;
function unwrapNative(uint256 amount) external;
}
3 changes: 2 additions & 1 deletion src/migration/AaveV2MigrationBundler.sol
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,10 @@ 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 Pass `amount = type(uint256).max` to repay all.
function aaveV2Repay(address asset, uint256 amount, uint256 interestRateMode) external payable {
_checkInitiated();

amount = Math.min(amount, ERC20(asset).balanceOf(address(this)));

require(amount != 0, ErrorsLib.ZERO_AMOUNT);
Expand Down
3 changes: 2 additions & 1 deletion src/migration/AaveV3MigrationBundler.sol
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,10 @@ 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 Pass `amount = type(uint256).max` to repay all.
function aaveV3Repay(address asset, uint256 amount, uint256 interestRateMode) external payable {
_checkInitiated();

amount = Math.min(amount, ERC20(asset).balanceOf(address(this)));

require(amount != 0, ErrorsLib.ZERO_AMOUNT);
Expand Down
12 changes: 8 additions & 4 deletions src/migration/AaveV3OptimizerMigrationBundler.sol
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,10 @@ contract AaveV3OptimizerMigrationBundler is MigrationBundler {
/* ACTIONS */

/// @notice Repays `amount` of `underlying` on the AaveV3 Optimizer, on behalf of the initiator.
/// @notice Warning: should only be called via the bundler's `multicall` function.
/// @dev Pass `amount = type(uint256).max` to repay all.
function aaveV3OptimizerRepay(address underlying, uint256 amount) external payable {
_checkInitiated();

amount = Math.min(amount, ERC20(underlying).balanceOf(address(this)));

require(amount != 0, ErrorsLib.ZERO_AMOUNT);
Expand All @@ -41,34 +42,37 @@ contract AaveV3OptimizerMigrationBundler is MigrationBundler {

/// @notice Repays `amount` of `underlying` on the AaveV3 Optimizer, on behalf of the initiator, transferring funds
/// to `receiver`.
/// @notice Warning: should only be called via the bundler's `multicall` function.
/// @dev Initiator must have previously approved the bundler to manage their AaveV3 Optimizer position.
/// @dev Pass `amount = type(uint256).max` to withdraw all.
function aaveV3OptimizerWithdraw(address underlying, uint256 amount, address receiver, uint256 maxIterations)
external
payable
{
_checkInitiated();

AAVE_V3_OPTIMIZER.withdraw(underlying, amount, initiator(), receiver, maxIterations);
}

/// @notice Repays `amount` of `underlying` on the AaveV3 Optimizer, on behalf of the initiator, transferring funds
/// to `receiver`.
/// @notice Warning: should only be called via the bundler's `multicall` function.
/// @dev Initiator must have previously approved the bundler to manage their AaveV3 Optimizer position.
/// @dev Pass `amount = type(uint256).max` to withdraw all.
function aaveV3OptimizerWithdrawCollateral(address underlying, uint256 amount, address receiver) external payable {
_checkInitiated();

AAVE_V3_OPTIMIZER.withdrawCollateral(underlying, amount, initiator(), receiver);
}

/// @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.
function aaveV3OptimizerApproveManagerWithSig(
bool isApproved,
uint256 nonce,
uint256 deadline,
Types.Signature calldata signature
) external payable {
_checkInitiated();

AAVE_V3_OPTIMIZER.approveManagerWithSig(initiator(), address(this), isApproved, nonce, deadline, signature);
}
}
3 changes: 2 additions & 1 deletion src/migration/CompoundV2MigrationBundler.sol
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,10 @@ 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 Pass `amount = type(uint256).max` to repay all.
function compoundV2Repay(address cToken, uint256 amount) external payable {
_checkInitiated();

if (cToken == C_ETH) {
amount = Math.min(amount, address(this).balance);

Expand Down
9 changes: 6 additions & 3 deletions src/migration/CompoundV3MigrationBundler.sol
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,11 @@ contract CompoundV3MigrationBundler is MigrationBundler {
/* ACTIONS */

/// @notice Repays `amount` of `asset` on the CompoundV3 `instance`, on behalf of the initiator.
/// @notice Warning: should only be called via the bundler's `multicall` function.
/// @dev Assumes the given instance is a CompoundV3 instance.
/// @dev Pass `amount = type(uint256).max` to repay all.
function compoundV3Repay(address instance, address asset, uint256 amount) external payable {
_checkInitiated();

amount = Math.min(amount, ERC20(asset).balanceOf(address(this)));

require(amount != 0, ErrorsLib.ZERO_AMOUNT);
Expand All @@ -43,17 +44,17 @@ contract CompoundV3MigrationBundler is MigrationBundler {
}

/// @notice Withdraws `amount` of `asset` from the CompoundV3 `instance`, on behalf of the initiator.
/// @notice Warning: should only be called via the bundler's `multicall` function.
/// @dev Initiator must have previously approved the bundler to manage their CompoundV3 position.
/// @dev Assumes the given `instance` is a CompoundV3 instance.
/// @dev Pass `amount = type(uint256).max` to withdraw all.
function compoundV3WithdrawFrom(address instance, address asset, uint256 amount) external payable {
_checkInitiated();

ICompoundV3(instance).withdrawFrom(initiator(), address(this), asset, amount);
}

/// @notice Approves the bundler to act on behalf of the initiator on the CompoundV3 `instance`, given a signed
/// EIP-712 approval message.
/// @notice Warning: should only be called via the bundler's `multicall` function.
/// @dev Assumes the given `instance` is a CompoundV3 instance.
function compoundV3AllowBySig(
address instance,
Expand All @@ -64,6 +65,8 @@ contract CompoundV3MigrationBundler is MigrationBundler {
bytes32 r,
bytes32 s
) external payable {
_checkInitiated();

ICompoundV3(instance).allowBySig(initiator(), address(this), isAllowed, nonce, expiry, v, r, s);
}
}
10 changes: 10 additions & 0 deletions test/forge/ERC4626BundlerLocalTest.sol
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,11 @@ contract ERC4626BundlerLocalTest is LocalTest {
bundler.multicall(bundle);
}

function test4626DepositUninitiated(uint256 assets) public {
vm.expectRevert(bytes(ErrorsLib.UNINITIATED));
ERC4626BundlerMock(address(bundler)).erc4626Withdraw(address(vault), assets, RECEIVER);
}

function testErc4626WithdrawZeroAdressVault(uint256 assets) public {
bundle.push(_erc4626Withdraw(address(0), assets, RECEIVER));

Expand All @@ -61,6 +66,11 @@ contract ERC4626BundlerLocalTest is LocalTest {
bundler.multicall(bundle);
}

function test4626RedeemUninitiated(uint256 assets) public {
vm.expectRevert(bytes(ErrorsLib.UNINITIATED));
ERC4626BundlerMock(address(bundler)).erc4626Redeem(address(vault), assets, RECEIVER);
}

function testErc4626RedeemZeroAdressVault(uint256 assets) public {
bundle.push(_erc4626Redeem(address(0), assets, RECEIVER));

Expand Down
15 changes: 15 additions & 0 deletions test/forge/MorphoBundlerLocalTest.sol
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,11 @@ contract MorphoBundlerLocalTest is LocalTest {
_testSupplyCollateral(amount, onBehalf);
}

function testWithdrawUninitiated(uint256 withdrawnShares) public {
vm.expectRevert(bytes(ErrorsLib.UNINITIATED));
MorphoBundlerMock(address(bundler)).morphoWithdraw(marketParams, 0, withdrawnShares, RECEIVER);
}

function testWithdraw(uint256 privateKey, uint256 amount, uint256 withdrawnShares) public {
address user;
(privateKey, user) = _boundPrivateKey(privateKey);
Expand Down Expand Up @@ -247,6 +252,11 @@ contract MorphoBundlerLocalTest is LocalTest {
assertEq(morpho.borrowShares(id, user), 0, "borrowShares(user)");
}

function testBorrowUnititiated(uint256 borrowedAssets) public {
vm.expectRevert(bytes(ErrorsLib.UNINITIATED));
MorphoBundlerMock(address(bundler)).morphoBorrow(marketParams, borrowedAssets, 0, RECEIVER);
}

function _testSupplyCollateralBorrow(address user, uint256 amount, uint256 collateralAmount) internal {
assertEq(collateralToken.balanceOf(RECEIVER), 0, "collateral.balanceOf(RECEIVER)");
assertEq(loanToken.balanceOf(RECEIVER), amount, "loan.balanceOf(RECEIVER)");
Expand Down Expand Up @@ -316,6 +326,11 @@ contract MorphoBundlerLocalTest is LocalTest {
_testSupplyCollateralBorrow(user, amount, collateralAmount);
}

function testWithdrawCollateralUninitiated(uint256 collateralAmount) public {
vm.expectRevert(bytes(ErrorsLib.UNINITIATED));
MorphoBundlerMock(address(bundler)).morphoWithdrawCollateral(marketParams, collateralAmount, RECEIVER);
}

function _testRepayWithdrawCollateral(address user, uint256 collateralAmount) internal {
assertEq(collateralToken.balanceOf(RECEIVER), collateralAmount, "collateral.balanceOf(RECEIVER)");
assertEq(loanToken.balanceOf(RECEIVER), 0, "loan.balanceOf(RECEIVER)");
Expand Down
11 changes: 11 additions & 0 deletions test/forge/PermitBundlerLocalTest.sol
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,17 @@ contract PermitBundlerLocalTest is LocalTest {
assertEq(permitToken.allowance(user, address(bundler)), amount, "allowance(user, bundler)");
}

function testPermitUninitiated(uint256 amount) public {
amount = bound(amount, MIN_AMOUNT, MAX_AMOUNT);

uint8 v;
bytes32 r;
bytes32 s;

vm.expectRevert(bytes(ErrorsLib.UNINITIATED));
PermitBundlerMock(address(bundler)).permit(address(loanToken), amount, SIGNATURE_DEADLINE, v, r, s, true);
}

function testPermitRevert(uint256 amount, uint256 privateKey, uint256 deadline) public {
amount = bound(amount, MIN_AMOUNT, MAX_AMOUNT);
deadline = bound(deadline, block.timestamp, type(uint48).max);
Expand Down
7 changes: 7 additions & 0 deletions test/forge/TransferBundlerLocalTest.sol
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,13 @@ contract TransferBundlerLocalTest is LocalTest {
assertEq(loanToken.balanceOf(USER), 0, "loan.balanceOf(USER)");
}

function testTransferFromUninitiated(uint256 amount) public {
amount = bound(amount, MIN_AMOUNT, MAX_AMOUNT);

vm.expectRevert(bytes(ErrorsLib.UNINITIATED));
TransferBundlerMock(address(bundler)).erc20TransferFrom(address(loanToken), amount);
}

function testTranferFromZeroAddress(uint256 amount) public {
amount = bound(amount, MIN_AMOUNT, MAX_AMOUNT);

Expand Down
Loading

0 comments on commit 1b4e825

Please sign in to comment.