From bca21b841c810a75d4e87ba00475067e8973f329 Mon Sep 17 00:00:00 2001 From: Brenzee Date: Fri, 27 Jun 2025 16:41:50 +0300 Subject: [PATCH 1/9] wip: flashloan --- src/flashloan/EnsoFlashloan.sol | 106 ++++++++++++++++++++++ src/flashloan/EnsoFlashloanInterfaces.sol | 48 ++++++++++ 2 files changed, 154 insertions(+) create mode 100644 src/flashloan/EnsoFlashloan.sol create mode 100644 src/flashloan/EnsoFlashloanInterfaces.sol diff --git a/src/flashloan/EnsoFlashloan.sol b/src/flashloan/EnsoFlashloan.sol new file mode 100644 index 0000000..5400236 --- /dev/null +++ b/src/flashloan/EnsoFlashloan.sol @@ -0,0 +1,106 @@ +// SPDX-License-Identifier: GPL-3.0-only +pragma solidity ^0.8.28; + +import {IRouter, IERC3156FlashBorrower, IEulerFlashloan, IMorpho} from "./EnsoFlashloanInterfaces.sol"; +import {IERC20, SafeERC20} from "openzeppelin-contracts/token/ERC20/utils/SafeERC20.sol"; + +enum FlashloanProtocols { + Euler, + Morpho +} + +// WARN: This is work in progress. +// Currently this approach only supports single flashloaned asset +// to be routed through router. + +contract EnsoFlashloan is IERC3156FlashBorrower { + using SafeERC20 for IERC20; + + bytes32 public constant ERC3156_SUCCESS = + keccak256("ERC3156FlashBorrower.onFlashLoan"); + + IRouter public immutable router; + IEulerFlashloan public immutable eulerFlashloan; + IMorpho public immutable morpho; + + error ProtocolNotSupported(); + + constructor(address _router, address _eulerFlashloan, address _morpho) { + router = IRouter(_router); + eulerFlashloan = IEulerFlashloan(_eulerFlashloan); + morpho = IMorpho(_morpho); + } + + function flashLoan( + FlashloanProtocols protocol, + bytes calldata data + ) external { + if (protocol == FlashloanProtocols.Euler) { + (address token, uint256 amount, bytes memory routerData) = abi + .decode(data, (address, uint256, bytes)); + + eulerFlashloan.flashLoan(address(this), token, amount, routerData); + } else if (protocol == FlashloanProtocols.Morpho) { + ( + address token, + uint256 amount, + bytes memory shortcutAndTokenData + ) = abi.decode(data, (address, uint256, bytes)); + + morpho.flashLoan(token, amount, shortcutAndTokenData); + } else { + revert ProtocolNotSupported(); + } + } + + function onMorphoFlashLoan(uint256 amount, bytes calldata data) external { + require(msg.sender == address(morpho), "not allowed"); + (address token, bytes memory shortcutData) = abi.decode( + data, + (address, bytes) + ); + + _executeRouter(token, amount, shortcutData); + + // In Morpho flashloan tokens have to be approved, + // tokens will be pulled out by the flashloan contract + IERC20(token).forceApprove(msg.sender, amount); + } + + // ERC3156 callback + function onFlashLoan( + address initiator, + address token, + uint256 amount, + uint256, + bytes calldata data + ) external returns (bytes32) { + require( + msg.sender == address(eulerFlashloan) && initiator == address(this), + "not allowed" + ); + + _executeRouter(token, amount, data); + + // In ERC3156 tokens are not required to be sent back, + // tokens will be pulled out by the flashloan contract + IERC20(token).forceApprove(msg.sender, amount); + + return ERC3156_SUCCESS; + } + + function _executeRouter( + address token, + uint256 amount, + bytes memory shortcutData + ) internal { + // Only ERC20s are possible + IRouter.Token memory tokenIn = IRouter.Token( + IRouter.TokenType.ERC20, + abi.encode(token, amount) + ); + IERC20(token).forceApprove(address(router), amount); + + router.routeSingle(tokenIn, shortcutData); + } +} diff --git a/src/flashloan/EnsoFlashloanInterfaces.sol b/src/flashloan/EnsoFlashloanInterfaces.sol new file mode 100644 index 0000000..c28e7d9 --- /dev/null +++ b/src/flashloan/EnsoFlashloanInterfaces.sol @@ -0,0 +1,48 @@ +// SPDX-License-Identifier: GPL-3.0-only +pragma solidity ^0.8.28; + +interface IRouter { + enum TokenType { + Native, + ERC20, + ERC721, + ERC1155 + } + + struct Token { + TokenType tokenType; + bytes data; + } + + function routeSingle( + Token calldata tokenIn, + bytes calldata data + ) external payable returns (bytes memory response); +} + +interface IERC3156FlashBorrower { + function onFlashLoan( + address initiator, + address token, + uint256 amount, + uint256 fee, + bytes calldata data + ) external returns (bytes32); +} + +interface IEulerFlashloan { + function flashLoan( + address receiver, + address token, + uint256 amount, + bytes memory data + ) external; +} + +interface IMorpho { + function flashLoan( + address token, + uint256 assets, + bytes calldata data + ) external; +} From 580bec869f5ea08d058cc2f3879cb6b6c96dd07c Mon Sep 17 00:00:00 2001 From: Brenzee Date: Mon, 30 Jun 2025 12:40:21 +0300 Subject: [PATCH 2/9] EnsoFlashloanShortcuts contract with Morpho support, tests --- src/flashloan/EnsoFlashloan.sol | 106 ----------------- src/flashloan/EnsoFlashloanShortcuts.sol | 84 +++++++++++++ test/EnsoFlashloanShortcuts.t.sol | 145 +++++++++++++++++++++++ 3 files changed, 229 insertions(+), 106 deletions(-) delete mode 100644 src/flashloan/EnsoFlashloan.sol create mode 100644 src/flashloan/EnsoFlashloanShortcuts.sol create mode 100644 test/EnsoFlashloanShortcuts.t.sol diff --git a/src/flashloan/EnsoFlashloan.sol b/src/flashloan/EnsoFlashloan.sol deleted file mode 100644 index 5400236..0000000 --- a/src/flashloan/EnsoFlashloan.sol +++ /dev/null @@ -1,106 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-only -pragma solidity ^0.8.28; - -import {IRouter, IERC3156FlashBorrower, IEulerFlashloan, IMorpho} from "./EnsoFlashloanInterfaces.sol"; -import {IERC20, SafeERC20} from "openzeppelin-contracts/token/ERC20/utils/SafeERC20.sol"; - -enum FlashloanProtocols { - Euler, - Morpho -} - -// WARN: This is work in progress. -// Currently this approach only supports single flashloaned asset -// to be routed through router. - -contract EnsoFlashloan is IERC3156FlashBorrower { - using SafeERC20 for IERC20; - - bytes32 public constant ERC3156_SUCCESS = - keccak256("ERC3156FlashBorrower.onFlashLoan"); - - IRouter public immutable router; - IEulerFlashloan public immutable eulerFlashloan; - IMorpho public immutable morpho; - - error ProtocolNotSupported(); - - constructor(address _router, address _eulerFlashloan, address _morpho) { - router = IRouter(_router); - eulerFlashloan = IEulerFlashloan(_eulerFlashloan); - morpho = IMorpho(_morpho); - } - - function flashLoan( - FlashloanProtocols protocol, - bytes calldata data - ) external { - if (protocol == FlashloanProtocols.Euler) { - (address token, uint256 amount, bytes memory routerData) = abi - .decode(data, (address, uint256, bytes)); - - eulerFlashloan.flashLoan(address(this), token, amount, routerData); - } else if (protocol == FlashloanProtocols.Morpho) { - ( - address token, - uint256 amount, - bytes memory shortcutAndTokenData - ) = abi.decode(data, (address, uint256, bytes)); - - morpho.flashLoan(token, amount, shortcutAndTokenData); - } else { - revert ProtocolNotSupported(); - } - } - - function onMorphoFlashLoan(uint256 amount, bytes calldata data) external { - require(msg.sender == address(morpho), "not allowed"); - (address token, bytes memory shortcutData) = abi.decode( - data, - (address, bytes) - ); - - _executeRouter(token, amount, shortcutData); - - // In Morpho flashloan tokens have to be approved, - // tokens will be pulled out by the flashloan contract - IERC20(token).forceApprove(msg.sender, amount); - } - - // ERC3156 callback - function onFlashLoan( - address initiator, - address token, - uint256 amount, - uint256, - bytes calldata data - ) external returns (bytes32) { - require( - msg.sender == address(eulerFlashloan) && initiator == address(this), - "not allowed" - ); - - _executeRouter(token, amount, data); - - // In ERC3156 tokens are not required to be sent back, - // tokens will be pulled out by the flashloan contract - IERC20(token).forceApprove(msg.sender, amount); - - return ERC3156_SUCCESS; - } - - function _executeRouter( - address token, - uint256 amount, - bytes memory shortcutData - ) internal { - // Only ERC20s are possible - IRouter.Token memory tokenIn = IRouter.Token( - IRouter.TokenType.ERC20, - abi.encode(token, amount) - ); - IERC20(token).forceApprove(address(router), amount); - - router.routeSingle(tokenIn, shortcutData); - } -} diff --git a/src/flashloan/EnsoFlashloanShortcuts.sol b/src/flashloan/EnsoFlashloanShortcuts.sol new file mode 100644 index 0000000..4f1dc6d --- /dev/null +++ b/src/flashloan/EnsoFlashloanShortcuts.sol @@ -0,0 +1,84 @@ +// SPDX-License-Identifier: GPL-3.0-only +pragma solidity ^0.8.28; + +import {IRouter, IERC3156FlashBorrower, IEulerFlashloan, IMorpho} from "./EnsoFlashloanInterfaces.sol"; + +import {VM} from "enso-weiroll/VM.sol"; + +import {ERC1155Holder} from "openzeppelin-contracts/token/ERC1155/utils/ERC1155Holder.sol"; +import {ERC721Holder} from "openzeppelin-contracts/token/ERC721/utils/ERC721Holder.sol"; +import {IERC20, SafeERC20} from "openzeppelin-contracts/token/ERC20/utils/SafeERC20.sol"; + +enum FlashloanProtocols { + Morpho +} + +contract EnsoFlashloanShortcuts is VM, ERC721Holder, ERC1155Holder { + using SafeERC20 for IERC20; + + IMorpho public immutable morpho; + + event ShortcutExecuted(bytes32 accountId, bytes32 requestId); + + error ProtocolNotSupported(); + error NotSelf(); + error OnlyProtocol(); + + constructor(address _morpho) { + morpho = IMorpho(_morpho); + } + + function flashLoan( + FlashloanProtocols protocol, + address token, + uint256 amount, + address excessFlashloanReceiver, + bytes32[] calldata commands, + bytes[] memory state + ) external { + if (protocol == FlashloanProtocols.Morpho) { + bytes memory morphoCallback = abi.encode( + token, + excessFlashloanReceiver, + commands, + state + ); + + morpho.flashLoan(token, amount, morphoCallback); + } else { + revert ProtocolNotSupported(); + } + } + + function onMorphoFlashLoan(uint256 amount, bytes calldata data) external { + require(msg.sender == address(morpho), OnlyProtocol()); + ( + IERC20 token, + address excessFlashloanReceiver, + bytes32[] memory commands, + bytes[] memory state + ) = abi.decode(data, (IERC20, address, bytes32[], bytes[])); + + this.execute(commands, state); + + // At this stage we expect loaned asset to be inside the contract. + // If at this point token.balanceOf(this) > amount, we send excess amount to receiver + uint256 flashloanedAssetBalance = token.balanceOf(address(this)); + if (flashloanedAssetBalance > amount) { + uint256 excessAmount; + unchecked { + excessAmount = flashloanedAssetBalance - amount; + } + token.safeTransfer(excessFlashloanReceiver, excessAmount); + } + + token.forceApprove(msg.sender, amount); + } + + function execute(bytes32[] calldata commands, bytes[] memory state) public { + require(msg.sender == address(this), NotSelf()); + _execute(commands, state); + } + + receive() external payable {} +} diff --git a/test/EnsoFlashloanShortcuts.t.sol b/test/EnsoFlashloanShortcuts.t.sol new file mode 100644 index 0000000..3b84e46 --- /dev/null +++ b/test/EnsoFlashloanShortcuts.t.sol @@ -0,0 +1,145 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.28; + +import "../lib/forge-std/src/Test.sol"; + +import "../src/flashloan/EnsoFlashloanShortcuts.sol"; +import "../src/router/EnsoRouter.sol"; + +import "./mocks/MockERC1155.sol"; +import "./mocks/MockERC20.sol"; +import "./mocks/MockERC721.sol"; + +import "./mocks/MockMultiVault.sol"; +import "./mocks/MockNFTVault.sol"; +import "./mocks/MockVault.sol"; + +import "./utils/WeirollPlanner.sol"; + +import {ERC1155Holder} from "openzeppelin-contracts/token/ERC1155/utils/ERC1155Holder.sol"; +import {ERC721Holder} from "openzeppelin-contracts/token/ERC721/utils/ERC721Holder.sol"; + +interface IWETH { + function deposit() external payable; + + function withdraw(uint256) external; + + function transfer(address to, uint256 value) external returns (bool); + + function balanceOf(address) external view returns (uint256); +} + +contract EnsoFlashloanShortcutsTest is Test, ERC721Holder, ERC1155Holder { + EnsoFlashloanShortcuts public shortcuts; + + address user_bob = makeAddr("bob"); + address receiver = makeAddr("receiver"); + + string _rpcURL = vm.envString("ETHEREUM_RPC_URL"); + uint256 _ethereumFork; + + function setUp() public { + _ethereumFork = vm.createFork(_rpcURL); + vm.selectFork(_ethereumFork); + shortcuts = new EnsoFlashloanShortcuts( + address(0xBBBBBbbBBb9cC5e90e3b3Af64bdAF62C37EEFFCb) // Morpho + ); + } + + // Morpho test: + // - flashlent asset: WETH + // - no other assets involved + // - WETH.withdraw -> WETH.deposit + function testMorphoFlashloan() public { + vm.selectFork(_ethereumFork); + + IWETH token = IWETH( + address(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2) + ); + uint256 amount = 10 ** 18; + + bytes32[] memory commands = new bytes32[](2); + bytes[] memory state = new bytes[](1); + + // Unwrap -> Wrap + commands[0] = WeirollPlanner.buildCommand( + token.withdraw.selector, + 0x01, // call + 0x00ffffffffff, // 1 inputs + 0xff, // no output + address(token) + ); + commands[1] = WeirollPlanner.buildCommand( + token.deposit.selector, + 0x03, // call + 0x00ffffffffff, // 1 inputs + 0xff, // no output + address(token) + ); + + state[0] = abi.encode(amount); + + shortcuts.flashLoan( + FlashloanProtocols.Morpho, + address(token), + amount, + user_bob, + commands, + state + ); + } + + // Morpho test: + // - flashlent asset: WETH + // - other assets: additional WETH + // - WETH.withdraw -> WETH.deposit + // - make sure excess is sent back to user + function testMorphoFlashloan_excess() public { + vm.selectFork(_ethereumFork); + + IWETH token = IWETH( + address(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2) + ); + uint256 amount = 10 ** 18; + + bytes32[] memory commands = new bytes32[](2); + bytes[] memory state = new bytes[](1); + commands[0] = WeirollPlanner.buildCommand( + token.withdraw.selector, + 0x01, // call + 0x00ffffffffff, // 1 inputs + 0xff, // no output + address(token) + ); + commands[1] = WeirollPlanner.buildCommand( + token.deposit.selector, + 0x03, // call + 0x00ffffffffff, // 1 inputs + 0xff, // no output + address(token) + ); + + state[0] = abi.encode(amount); + + vm.startPrank(user_bob); + vm.deal(user_bob, 1 ether); + token.deposit{value: 1 ether}(); + + // Simulate that router transfers tokens to flashloan contract + token.transfer(address(shortcuts), 1 ether); + + uint256 balanceBefore = token.balanceOf(receiver); + shortcuts.flashLoan( + FlashloanProtocols.Morpho, + address(token), + amount, + receiver, + commands, + state + ); + uint256 balanceAfter = token.balanceOf(receiver); + + // Check that receiver received the excess WETH + assertEq(balanceAfter - balanceBefore, 1 ether); + } +} From b69ba651ba41e6294bdeba9a25df119113866c6d Mon Sep 17 00:00:00 2001 From: Brenzee Date: Mon, 30 Jun 2025 16:19:11 +0300 Subject: [PATCH 3/9] EnsoFlashloanShortcuts: add Euler vault flashloan support --- src/flashloan/EnsoFlashloanInterfaces.sol | 17 ++- src/flashloan/EnsoFlashloanShortcuts.sol | 88 +++++++++---- test/EnsoFlashloanShortcuts.t.sol | 149 ++++++++++++++++++++-- 3 files changed, 211 insertions(+), 43 deletions(-) diff --git a/src/flashloan/EnsoFlashloanInterfaces.sol b/src/flashloan/EnsoFlashloanInterfaces.sol index c28e7d9..6e08e7b 100644 --- a/src/flashloan/EnsoFlashloanInterfaces.sol +++ b/src/flashloan/EnsoFlashloanInterfaces.sol @@ -30,15 +30,6 @@ interface IERC3156FlashBorrower { ) external returns (bytes32); } -interface IEulerFlashloan { - function flashLoan( - address receiver, - address token, - uint256 amount, - bytes memory data - ) external; -} - interface IMorpho { function flashLoan( address token, @@ -46,3 +37,11 @@ interface IMorpho { bytes calldata data ) external; } + +interface IEulerGenericFactory { + function isProxy(address proxy) external view returns (bool); +} + +interface IEVault { + function flashLoan(uint256 amount, bytes calldata data) external; +} diff --git a/src/flashloan/EnsoFlashloanShortcuts.sol b/src/flashloan/EnsoFlashloanShortcuts.sol index 4f1dc6d..db6d770 100644 --- a/src/flashloan/EnsoFlashloanShortcuts.sol +++ b/src/flashloan/EnsoFlashloanShortcuts.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: GPL-3.0-only pragma solidity ^0.8.28; -import {IRouter, IERC3156FlashBorrower, IEulerFlashloan, IMorpho} from "./EnsoFlashloanInterfaces.sol"; +import {IRouter, IERC3156FlashBorrower, IEulerGenericFactory, IEVault, IMorpho} from "./EnsoFlashloanInterfaces.sol"; import {VM} from "enso-weiroll/VM.sol"; @@ -10,33 +10,39 @@ import {ERC721Holder} from "openzeppelin-contracts/token/ERC721/utils/ERC721Hold import {IERC20, SafeERC20} from "openzeppelin-contracts/token/ERC20/utils/SafeERC20.sol"; enum FlashloanProtocols { - Morpho + Morpho, + Euler } contract EnsoFlashloanShortcuts is VM, ERC721Holder, ERC1155Holder { using SafeERC20 for IERC20; - IMorpho public immutable morpho; + IMorpho public immutable MORPHO; + IEulerGenericFactory public immutable EULER_FACTORY; event ShortcutExecuted(bytes32 accountId, bytes32 requestId); - error ProtocolNotSupported(); + error UnsupportedFlashloanProtocol(); error NotSelf(); - error OnlyProtocol(); + error NotAuthorized(); - constructor(address _morpho) { - morpho = IMorpho(_morpho); + constructor(IMorpho _morpho, IEulerGenericFactory _eulerFactory) { + MORPHO = _morpho; + EULER_FACTORY = _eulerFactory; } function flashLoan( FlashloanProtocols protocol, - address token, - uint256 amount, address excessFlashloanReceiver, + bytes calldata data, bytes32[] calldata commands, bytes[] memory state ) external { if (protocol == FlashloanProtocols.Morpho) { + (address token, uint256 amount) = abi.decode( + data, + (address, uint256) + ); bytes memory morphoCallback = abi.encode( token, excessFlashloanReceiver, @@ -44,14 +50,29 @@ contract EnsoFlashloanShortcuts is VM, ERC721Holder, ERC1155Holder { state ); - morpho.flashLoan(token, amount, morphoCallback); + MORPHO.flashLoan(token, amount, morphoCallback); + } else if (protocol == FlashloanProtocols.Euler) { + (address token, uint256 amount, IEVault eulerVault) = abi.decode( + data, + (address, uint256, IEVault) + ); + + bytes memory eulerCallback = abi.encode( + amount, + token, + excessFlashloanReceiver, + commands, + state + ); + + eulerVault.flashLoan(amount, eulerCallback); } else { - revert ProtocolNotSupported(); + revert UnsupportedFlashloanProtocol(); } } function onMorphoFlashLoan(uint256 amount, bytes calldata data) external { - require(msg.sender == address(morpho), OnlyProtocol()); + require(msg.sender == address(MORPHO), NotAuthorized()); ( IERC20 token, address excessFlashloanReceiver, @@ -60,25 +81,46 @@ contract EnsoFlashloanShortcuts is VM, ERC721Holder, ERC1155Holder { ) = abi.decode(data, (IERC20, address, bytes32[], bytes[])); this.execute(commands, state); - - // At this stage we expect loaned asset to be inside the contract. - // If at this point token.balanceOf(this) > amount, we send excess amount to receiver - uint256 flashloanedAssetBalance = token.balanceOf(address(this)); - if (flashloanedAssetBalance > amount) { - uint256 excessAmount; - unchecked { - excessAmount = flashloanedAssetBalance - amount; - } - token.safeTransfer(excessFlashloanReceiver, excessAmount); - } + _returnExcessAssets(token, amount, excessFlashloanReceiver); token.forceApprove(msg.sender, amount); } + function onFlashLoan(bytes calldata data) external { + require(EULER_FACTORY.isProxy(msg.sender), NotAuthorized()); + ( + uint256 amount, + IERC20 token, + address excessFlashloanReceiver, + bytes32[] memory commands, + bytes[] memory state + ) = abi.decode(data, (uint256, IERC20, address, bytes32[], bytes[])); + + this.execute(commands, state); + _returnExcessAssets(token, amount, excessFlashloanReceiver); + + token.safeTransfer(msg.sender, amount); + } + function execute(bytes32[] calldata commands, bytes[] memory state) public { require(msg.sender == address(this), NotSelf()); _execute(commands, state); } + function _returnExcessAssets( + IERC20 token, + uint256 flashloanAmount, + address receiver + ) private { + uint256 flashloanAssetBalance = token.balanceOf(address(this)); + if (flashloanAssetBalance > flashloanAmount) { + uint256 excessAmount; + unchecked { + excessAmount = flashloanAssetBalance - flashloanAmount; + } + token.safeTransfer(receiver, excessAmount); + } + } + receive() external payable {} } diff --git a/test/EnsoFlashloanShortcuts.t.sol b/test/EnsoFlashloanShortcuts.t.sol index 3b84e46..7ea52f8 100644 --- a/test/EnsoFlashloanShortcuts.t.sol +++ b/test/EnsoFlashloanShortcuts.t.sol @@ -42,7 +42,8 @@ contract EnsoFlashloanShortcutsTest is Test, ERC721Holder, ERC1155Holder { _ethereumFork = vm.createFork(_rpcURL); vm.selectFork(_ethereumFork); shortcuts = new EnsoFlashloanShortcuts( - address(0xBBBBBbbBBb9cC5e90e3b3Af64bdAF62C37EEFFCb) // Morpho + IMorpho(0xBBBBBbbBBb9cC5e90e3b3Af64bdAF62C37EEFFCb), + IEulerGenericFactory(0x29a56a1b8214D9Cf7c5561811750D5cBDb45CC8e) ); } @@ -79,11 +80,12 @@ contract EnsoFlashloanShortcutsTest is Test, ERC721Holder, ERC1155Holder { state[0] = abi.encode(amount); + bytes memory morphoData = abi.encode(token, amount); + shortcuts.flashLoan( FlashloanProtocols.Morpho, - address(token), - amount, user_bob, + morphoData, commands, state ); @@ -100,7 +102,7 @@ contract EnsoFlashloanShortcutsTest is Test, ERC721Holder, ERC1155Holder { IWETH token = IWETH( address(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2) ); - uint256 amount = 10 ** 18; + uint256 amount = 1 ether; bytes32[] memory commands = new bytes32[](2); bytes[] memory state = new bytes[](1); @@ -121,25 +123,150 @@ contract EnsoFlashloanShortcutsTest is Test, ERC721Holder, ERC1155Holder { state[0] = abi.encode(amount); + bytes memory morphoData = abi.encode(token, amount); + vm.startPrank(user_bob); - vm.deal(user_bob, 1 ether); - token.deposit{value: 1 ether}(); + vm.deal(user_bob, amount); + token.deposit{value: amount}(); // Simulate that router transfers tokens to flashloan contract - token.transfer(address(shortcuts), 1 ether); + token.transfer(address(shortcuts), amount); uint256 balanceBefore = token.balanceOf(receiver); shortcuts.flashLoan( FlashloanProtocols.Morpho, - address(token), - amount, receiver, + morphoData, + commands, + state + ); + uint256 balanceAfter = token.balanceOf(receiver); + + assertEq(balanceAfter - balanceBefore, amount); + } + + // Euler test: + // - flashlent asset: WETH + // - no other assets involved + // - WETH.withdraw -> WETH.deposit + function testEulerFlashLoan() public { + vm.selectFork(_ethereumFork); + + // eWETH-2 Euler vault + address eulerVault = address( + 0xD8b27CF359b7D15710a5BE299AF6e7Bf904984C2 + ); + + IWETH token = IWETH( + address(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2) + ); + uint256 amount = 1 ether; + + bytes32[] memory commands = new bytes32[](2); + bytes[] memory state = new bytes[](1); + + // Unwrap -> Wrap + commands[0] = WeirollPlanner.buildCommand( + token.withdraw.selector, + 0x01, // call + 0x00ffffffffff, // 1 inputs + 0xff, // no output + address(token) + ); + commands[1] = WeirollPlanner.buildCommand( + token.deposit.selector, + 0x03, // call + 0x00ffffffffff, // 1 inputs + 0xff, // no output + address(token) + ); + + state[0] = abi.encode(amount); + + bytes memory eulerData = abi.encode(token, amount, eulerVault); + + shortcuts.flashLoan( + FlashloanProtocols.Euler, + user_bob, + eulerData, + commands, + state + ); + } + + // Euler test: + // - flashlent asset: WETH + // - other assets: additional WETH + // - WETH.withdraw -> WETH.deposit + // - make sure excess is sent back to user + function testEulerFlashLoan_excess() public { + vm.selectFork(_ethereumFork); + + // eWETH-2 Euler vault + address eulerVault = address( + 0xD8b27CF359b7D15710a5BE299AF6e7Bf904984C2 + ); + + IWETH token = IWETH( + address(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2) + ); + uint256 amount = 1 ether; + + bytes32[] memory commands = new bytes32[](2); + bytes[] memory state = new bytes[](1); + + // Unwrap -> Wrap + commands[0] = WeirollPlanner.buildCommand( + token.withdraw.selector, + 0x01, // call + 0x00ffffffffff, // 1 inputs + 0xff, // no output + address(token) + ); + commands[1] = WeirollPlanner.buildCommand( + token.deposit.selector, + 0x03, // call + 0x00ffffffffff, // 1 inputs + 0xff, // no output + address(token) + ); + + state[0] = abi.encode(amount); + + bytes memory eulerData = abi.encode(token, amount, eulerVault); + + vm.startPrank(user_bob); + vm.deal(user_bob, amount); + token.deposit{value: amount}(); + + // Simulate that router transfers tokens to flashloan contract + token.transfer(address(shortcuts), amount); + + uint256 balanceBefore = token.balanceOf(receiver); + shortcuts.flashLoan( + FlashloanProtocols.Euler, + receiver, + eulerData, commands, state ); + uint256 balanceAfter = token.balanceOf(receiver); - // Check that receiver received the excess WETH - assertEq(balanceAfter - balanceBefore, 1 ether); + assertEq(balanceAfter - balanceBefore, amount); + } + + // Euler test: + // - revert if attacker calls onFlashLoan with fake Euler vault + function testEulerFlashLoan_fakeEulerVault() public { + vm.selectFork(_ethereumFork); + + address fakeEulerVault = address(0xdead); + + bytes memory data = bytes(""); + + vm.prank(fakeEulerVault); + vm.expectRevert(EnsoFlashloanShortcuts.NotAuthorized.selector); + shortcuts.onFlashLoan(data); } } From 68c94206b794122caf22369a01439e380e32ea56 Mon Sep 17 00:00:00 2001 From: Brenzee Date: Mon, 30 Jun 2025 17:34:29 +0300 Subject: [PATCH 4/9] EnsoFlashloanShortcuts: add BalancerV2 flashloan support --- src/flashloan/EnsoFlashloanInterfaces.sol | 9 ++ src/flashloan/EnsoFlashloanShortcuts.sol | 111 ++++++++----- test/EnsoFlashloanShortcuts.t.sol | 180 ++++++++++++++++++++++ 3 files changed, 265 insertions(+), 35 deletions(-) diff --git a/src/flashloan/EnsoFlashloanInterfaces.sol b/src/flashloan/EnsoFlashloanInterfaces.sol index 6e08e7b..f2da83b 100644 --- a/src/flashloan/EnsoFlashloanInterfaces.sol +++ b/src/flashloan/EnsoFlashloanInterfaces.sol @@ -45,3 +45,12 @@ interface IEulerGenericFactory { interface IEVault { function flashLoan(uint256 amount, bytes calldata data) external; } + +interface IBalancerV2Vault { + function flashLoan( + address recipient, + address[] memory tokens, + uint256[] memory amounts, + bytes memory userData + ) external; +} diff --git a/src/flashloan/EnsoFlashloanShortcuts.sol b/src/flashloan/EnsoFlashloanShortcuts.sol index db6d770..b0ef95a 100644 --- a/src/flashloan/EnsoFlashloanShortcuts.sol +++ b/src/flashloan/EnsoFlashloanShortcuts.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: GPL-3.0-only pragma solidity ^0.8.28; -import {IRouter, IERC3156FlashBorrower, IEulerGenericFactory, IEVault, IMorpho} from "./EnsoFlashloanInterfaces.sol"; +import "./EnsoFlashloanInterfaces.sol"; import {VM} from "enso-weiroll/VM.sol"; @@ -10,8 +10,9 @@ import {ERC721Holder} from "openzeppelin-contracts/token/ERC721/utils/ERC721Hold import {IERC20, SafeERC20} from "openzeppelin-contracts/token/ERC20/utils/SafeERC20.sol"; enum FlashloanProtocols { - Morpho, - Euler + Euler, + BalancerV2, + Morpho } contract EnsoFlashloanShortcuts is VM, ERC721Holder, ERC1155Holder { @@ -33,78 +34,118 @@ contract EnsoFlashloanShortcuts is VM, ERC721Holder, ERC1155Holder { function flashLoan( FlashloanProtocols protocol, - address excessFlashloanReceiver, + address excessReceiver, bytes calldata data, bytes32[] calldata commands, bytes[] memory state ) external { - if (protocol == FlashloanProtocols.Morpho) { - (address token, uint256 amount) = abi.decode( + if (protocol == FlashloanProtocols.Euler) { + (address token, uint256 amount, IEVault EulerVault) = abi.decode( data, - (address, uint256) + (address, uint256, IEVault) ); - bytes memory morphoCallback = abi.encode( + + bytes memory eulerCallback = abi.encode( + amount, token, - excessFlashloanReceiver, + excessReceiver, commands, state ); - MORPHO.flashLoan(token, amount, morphoCallback); - } else if (protocol == FlashloanProtocols.Euler) { - (address token, uint256 amount, IEVault eulerVault) = abi.decode( - data, - (address, uint256, IEVault) + EulerVault.flashLoan(amount, eulerCallback); + } else if (protocol == FlashloanProtocols.BalancerV2) { + ( + IBalancerV2Vault Vault, + address[] memory tokens, + uint256[] memory amounts + ) = abi.decode(data, (IBalancerV2Vault, address[], uint256[])); + + bytes memory balancerV2Callback = abi.encode( + excessReceiver, + commands, + state ); - bytes memory eulerCallback = abi.encode( - amount, + Vault.flashLoan(address(this), tokens, amounts, balancerV2Callback); + } else if (protocol == FlashloanProtocols.Morpho) { + (address token, uint256 amount) = abi.decode( + data, + (address, uint256) + ); + bytes memory morphoCallback = abi.encode( token, - excessFlashloanReceiver, + excessReceiver, commands, state ); - eulerVault.flashLoan(amount, eulerCallback); + MORPHO.flashLoan(token, amount, morphoCallback); } else { revert UnsupportedFlashloanProtocol(); } } - function onMorphoFlashLoan(uint256 amount, bytes calldata data) external { - require(msg.sender == address(MORPHO), NotAuthorized()); + function execute(bytes32[] calldata commands, bytes[] memory state) public { + require(msg.sender == address(this), NotSelf()); + _execute(commands, state); + } + + // --- Flashloan callbacks --- + + // Euler + function onFlashLoan(bytes calldata data) external { + require(EULER_FACTORY.isProxy(msg.sender), NotAuthorized()); ( + uint256 amount, IERC20 token, - address excessFlashloanReceiver, + address excessReceiver, bytes32[] memory commands, bytes[] memory state - ) = abi.decode(data, (IERC20, address, bytes32[], bytes[])); + ) = abi.decode(data, (uint256, IERC20, address, bytes32[], bytes[])); this.execute(commands, state); - _returnExcessAssets(token, amount, excessFlashloanReceiver); + _returnExcessAssets(token, amount, excessReceiver); - token.forceApprove(msg.sender, amount); + token.safeTransfer(msg.sender, amount); } - function onFlashLoan(bytes calldata data) external { - require(EULER_FACTORY.isProxy(msg.sender), NotAuthorized()); + // BalancerV2 + function receiveFlashLoan( + IERC20[] calldata tokens, + uint256[] calldata amounts, + uint256[] calldata feeAmounts, + bytes calldata data + ) external { ( - uint256 amount, - IERC20 token, - address excessFlashloanReceiver, + address excessReceiver, bytes32[] memory commands, bytes[] memory state - ) = abi.decode(data, (uint256, IERC20, address, bytes32[], bytes[])); + ) = abi.decode(data, (address, bytes32[], bytes[])); this.execute(commands, state); - _returnExcessAssets(token, amount, excessFlashloanReceiver); - token.safeTransfer(msg.sender, amount); + for (uint256 i = 0; i < tokens.length; i++) { + uint256 repayAmount = amounts[i] + feeAmounts[i]; + _returnExcessAssets(tokens[i], repayAmount, excessReceiver); + tokens[i].safeTransfer(msg.sender, repayAmount); + } } - function execute(bytes32[] calldata commands, bytes[] memory state) public { - require(msg.sender == address(this), NotSelf()); - _execute(commands, state); + // Morpho + function onMorphoFlashLoan(uint256 amount, bytes calldata data) external { + require(msg.sender == address(MORPHO), NotAuthorized()); + ( + IERC20 token, + address excessReceiver, + bytes32[] memory commands, + bytes[] memory state + ) = abi.decode(data, (IERC20, address, bytes32[], bytes[])); + + this.execute(commands, state); + _returnExcessAssets(token, amount, excessReceiver); + + token.forceApprove(msg.sender, amount); } function _returnExcessAssets( diff --git a/test/EnsoFlashloanShortcuts.t.sol b/test/EnsoFlashloanShortcuts.t.sol index 7ea52f8..35e23be 100644 --- a/test/EnsoFlashloanShortcuts.t.sol +++ b/test/EnsoFlashloanShortcuts.t.sol @@ -269,4 +269,184 @@ contract EnsoFlashloanShortcutsTest is Test, ERC721Holder, ERC1155Holder { vm.expectRevert(EnsoFlashloanShortcuts.NotAuthorized.selector); shortcuts.onFlashLoan(data); } + + // BalancerV2 test: + // - flashlent asset: WETH + // - no other assets involved + // - WETH.withdraw -> WETH.deposit + function testBalancerV2Flashloan() public { + vm.selectFork(_ethereumFork); + + address balancerV2Vault = address( + 0xBA12222222228d8Ba445958a75a0704d566BF2C8 + ); + + IWETH weth = IWETH(address(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2)); + uint256 wethAmount = 1 ether; + + bytes32[] memory commands = new bytes32[](2); + bytes[] memory state = new bytes[](1); + + // Unwrap -> Wrap + commands[0] = WeirollPlanner.buildCommand( + weth.withdraw.selector, + 0x01, // call + 0x00ffffffffff, // 1 inputs + 0xff, // no output + address(weth) + ); + commands[1] = WeirollPlanner.buildCommand( + weth.deposit.selector, + 0x03, // call + 0x00ffffffffff, // 1 inputs + 0xff, // no output + address(weth) + ); + + state[0] = abi.encode(wethAmount); + + address[] memory tokens = new address[](1); + uint256[] memory amounts = new uint256[](1); + tokens[0] = address(weth); + amounts[0] = wethAmount; + + bytes memory balancerV2Data = abi.encode( + balancerV2Vault, + tokens, + amounts + ); + + shortcuts.flashLoan( + FlashloanProtocols.BalancerV2, + user_bob, + balancerV2Data, + commands, + state + ); + } + + // BalancerV2 test: + // - flashlent assets: WETH and USDC + // - no other assets involved + // - WETH.withdraw -> WETH.deposit + function testBalancerV2Flashloan_multipleAssets() public { + vm.selectFork(_ethereumFork); + + address balancerV2Vault = address( + 0xBA12222222228d8Ba445958a75a0704d566BF2C8 + ); + + IWETH weth = IWETH(address(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2)); + uint256 wethAmount = 1 ether; + + address usdc = address(0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48); + uint256 usdcAmount = 100 * 10 ** 6; + + bytes32[] memory commands = new bytes32[](2); + bytes[] memory state = new bytes[](1); + + // Unwrap -> Wrap + commands[0] = WeirollPlanner.buildCommand( + weth.withdraw.selector, + 0x01, // call + 0x00ffffffffff, // 1 inputs + 0xff, // no output + address(weth) + ); + commands[1] = WeirollPlanner.buildCommand( + weth.deposit.selector, + 0x03, // call + 0x00ffffffffff, // 1 inputs + 0xff, // no output + address(weth) + ); + + state[0] = abi.encode(wethAmount); + + address[] memory tokens = new address[](2); + uint256[] memory amounts = new uint256[](2); + + // NOTE: Tokens must be in sorted order + tokens[0] = address(usdc); + amounts[0] = usdcAmount; + tokens[1] = address(weth); + amounts[1] = wethAmount; + + bytes memory balancerV2Data = abi.encode( + balancerV2Vault, + tokens, + amounts + ); + + shortcuts.flashLoan( + FlashloanProtocols.BalancerV2, + user_bob, + balancerV2Data, + commands, + state + ); + } + + function testBalancerV2Flashloan_excess() public { + vm.selectFork(_ethereumFork); + + address balancerV2Vault = address( + 0xBA12222222228d8Ba445958a75a0704d566BF2C8 + ); + + IWETH weth = IWETH(address(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2)); + uint256 wethAmount = 1 ether; + + bytes32[] memory commands = new bytes32[](2); + bytes[] memory state = new bytes[](1); + + // Unwrap -> Wrap + commands[0] = WeirollPlanner.buildCommand( + weth.withdraw.selector, + 0x01, // call + 0x00ffffffffff, // 1 inputs + 0xff, // no output + address(weth) + ); + commands[1] = WeirollPlanner.buildCommand( + weth.deposit.selector, + 0x03, // call + 0x00ffffffffff, // 1 inputs + 0xff, // no output + address(weth) + ); + + state[0] = abi.encode(wethAmount); + + address[] memory tokens = new address[](1); + uint256[] memory amounts = new uint256[](1); + + tokens[0] = address(weth); + amounts[0] = wethAmount; + + bytes memory balancerV2Data = abi.encode( + balancerV2Vault, + tokens, + amounts + ); + + vm.startPrank(user_bob); + vm.deal(user_bob, wethAmount); + weth.deposit{value: wethAmount}(); + + // Simulate that router transfers tokens to flashloan contract + weth.transfer(address(shortcuts), wethAmount); + + uint256 balanceBefore = weth.balanceOf(receiver); + shortcuts.flashLoan( + FlashloanProtocols.BalancerV2, + receiver, + balancerV2Data, + commands, + state + ); + uint256 balanceAfter = weth.balanceOf(receiver); + + assertEq(balanceAfter - balanceBefore, wethAmount); + } } From e10c29a852c1cd0d971df51ab9f05f7c46299aec Mon Sep 17 00:00:00 2001 From: Brenzee Date: Tue, 1 Jul 2025 10:52:34 +0300 Subject: [PATCH 5/9] EnsoFlashloanShortcuts: add Aave V3 support, make contract more readable, remove immutables --- src/flashloan/EnsoFlashloanInterfaces.sol | 16 ++- src/flashloan/EnsoFlashloanShortcuts.sol | 166 +++++++++++++++------- test/EnsoFlashloanShortcuts.t.sol | 78 +++++++--- 3 files changed, 184 insertions(+), 76 deletions(-) diff --git a/src/flashloan/EnsoFlashloanInterfaces.sol b/src/flashloan/EnsoFlashloanInterfaces.sol index f2da83b..5742005 100644 --- a/src/flashloan/EnsoFlashloanInterfaces.sol +++ b/src/flashloan/EnsoFlashloanInterfaces.sol @@ -38,10 +38,6 @@ interface IMorpho { ) external; } -interface IEulerGenericFactory { - function isProxy(address proxy) external view returns (bool); -} - interface IEVault { function flashLoan(uint256 amount, bytes calldata data) external; } @@ -54,3 +50,15 @@ interface IBalancerV2Vault { bytes memory userData ) external; } + +interface IAaveV3Pool { + function FLASHLOAN_PREMIUM_TOTAL() external view returns (uint128); + + function flashLoanSimple( + address receiverAddress, + address asset, + uint256 amount, + bytes calldata params, + uint16 referralCode + ) external; +} diff --git a/src/flashloan/EnsoFlashloanShortcuts.sol b/src/flashloan/EnsoFlashloanShortcuts.sol index b0ef95a..a519a4c 100644 --- a/src/flashloan/EnsoFlashloanShortcuts.sol +++ b/src/flashloan/EnsoFlashloanShortcuts.sol @@ -12,26 +12,17 @@ import {IERC20, SafeERC20} from "openzeppelin-contracts/token/ERC20/utils/SafeER enum FlashloanProtocols { Euler, BalancerV2, - Morpho + Morpho, + AaveV3 } contract EnsoFlashloanShortcuts is VM, ERC721Holder, ERC1155Holder { using SafeERC20 for IERC20; - IMorpho public immutable MORPHO; - IEulerGenericFactory public immutable EULER_FACTORY; - - event ShortcutExecuted(bytes32 accountId, bytes32 requestId); - error UnsupportedFlashloanProtocol(); error NotSelf(); error NotAuthorized(); - constructor(IMorpho _morpho, IEulerGenericFactory _eulerFactory) { - MORPHO = _morpho; - EULER_FACTORY = _eulerFactory; - } - function flashLoan( FlashloanProtocols protocol, address excessReceiver, @@ -40,47 +31,13 @@ contract EnsoFlashloanShortcuts is VM, ERC721Holder, ERC1155Holder { bytes[] memory state ) external { if (protocol == FlashloanProtocols.Euler) { - (address token, uint256 amount, IEVault EulerVault) = abi.decode( - data, - (address, uint256, IEVault) - ); - - bytes memory eulerCallback = abi.encode( - amount, - token, - excessReceiver, - commands, - state - ); - - EulerVault.flashLoan(amount, eulerCallback); + _executeEulerFlashLoan(excessReceiver, data, commands, state); } else if (protocol == FlashloanProtocols.BalancerV2) { - ( - IBalancerV2Vault Vault, - address[] memory tokens, - uint256[] memory amounts - ) = abi.decode(data, (IBalancerV2Vault, address[], uint256[])); - - bytes memory balancerV2Callback = abi.encode( - excessReceiver, - commands, - state - ); - - Vault.flashLoan(address(this), tokens, amounts, balancerV2Callback); + _executeBalancerV2FlashLoan(excessReceiver, data, commands, state); } else if (protocol == FlashloanProtocols.Morpho) { - (address token, uint256 amount) = abi.decode( - data, - (address, uint256) - ); - bytes memory morphoCallback = abi.encode( - token, - excessReceiver, - commands, - state - ); - - MORPHO.flashLoan(token, amount, morphoCallback); + _executeMorphoFlashLoan(excessReceiver, data, commands, state); + } else if (protocol == FlashloanProtocols.AaveV3) { + _executeAaveV3FlashLoan(excessReceiver, data, commands, state); } else { revert UnsupportedFlashloanProtocol(); } @@ -91,11 +48,91 @@ contract EnsoFlashloanShortcuts is VM, ERC721Holder, ERC1155Holder { _execute(commands, state); } + // --- Flashloan execution --- + + function _executeEulerFlashLoan( + address excessReceiver, + bytes calldata data, + bytes32[] calldata commands, + bytes[] memory state + ) private { + (address token, uint256 amount, IEVault EulerVault) = abi.decode( + data, + (address, uint256, IEVault) + ); + + bytes memory eulerCallback = abi.encode( + amount, + token, + excessReceiver, + commands, + state + ); + + EulerVault.flashLoan(amount, eulerCallback); + } + + function _executeBalancerV2FlashLoan( + address excessReceiver, + bytes calldata data, + bytes32[] calldata commands, + bytes[] memory state + ) private { + ( + IBalancerV2Vault Vault, + address[] memory tokens, + uint256[] memory amounts + ) = abi.decode(data, (IBalancerV2Vault, address[], uint256[])); + + bytes memory balancerV2Callback = abi.encode( + excessReceiver, + commands, + state + ); + + Vault.flashLoan(address(this), tokens, amounts, balancerV2Callback); + } + + function _executeMorphoFlashLoan( + address excessReceiver, + bytes calldata data, + bytes32[] calldata commands, + bytes[] memory state + ) private { + (IMorpho morpho, address token, uint256 amount) = abi.decode( + data, + (IMorpho, address, uint256) + ); + bytes memory morphoCallback = abi.encode( + token, + excessReceiver, + commands, + state + ); + + morpho.flashLoan(token, amount, morphoCallback); + } + + function _executeAaveV3FlashLoan( + address excessReceiver, + bytes calldata data, + bytes32[] calldata commands, + bytes[] memory state + ) private { + (IAaveV3Pool Pool, address token, uint256 amount) = abi.decode( + data, + (IAaveV3Pool, address, uint256) + ); + + bytes memory aaveCallback = abi.encode(excessReceiver, commands, state); + + Pool.flashLoanSimple(address(this), token, amount, aaveCallback, 0); + } + // --- Flashloan callbacks --- // Euler function onFlashLoan(bytes calldata data) external { - require(EULER_FACTORY.isProxy(msg.sender), NotAuthorized()); ( uint256 amount, IERC20 token, @@ -105,6 +142,7 @@ contract EnsoFlashloanShortcuts is VM, ERC721Holder, ERC1155Holder { ) = abi.decode(data, (uint256, IERC20, address, bytes32[], bytes[])); this.execute(commands, state); + _returnExcessAssets(token, amount, excessReceiver); token.safeTransfer(msg.sender, amount); @@ -134,7 +172,6 @@ contract EnsoFlashloanShortcuts is VM, ERC721Holder, ERC1155Holder { // Morpho function onMorphoFlashLoan(uint256 amount, bytes calldata data) external { - require(msg.sender == address(MORPHO), NotAuthorized()); ( IERC20 token, address excessReceiver, @@ -143,11 +180,36 @@ contract EnsoFlashloanShortcuts is VM, ERC721Holder, ERC1155Holder { ) = abi.decode(data, (IERC20, address, bytes32[], bytes[])); this.execute(commands, state); + _returnExcessAssets(token, amount, excessReceiver); token.forceApprove(msg.sender, amount); } + // Aave V3 + function executeOperation( + IERC20 asset, + uint256 amount, + uint256 premium, + address initiator, + bytes calldata data + ) external returns (bool) { + require(initiator == address(this), NotAuthorized()); + ( + address excessReceiver, + bytes32[] memory commands, + bytes[] memory state + ) = abi.decode(data, (address, bytes32[], bytes[])); + + this.execute(commands, state); + + uint256 repayAmount = amount + premium; + _returnExcessAssets(asset, repayAmount, excessReceiver); + asset.forceApprove(msg.sender, repayAmount); + + return true; + } + function _returnExcessAssets( IERC20 token, uint256 flashloanAmount, diff --git a/test/EnsoFlashloanShortcuts.t.sol b/test/EnsoFlashloanShortcuts.t.sol index 35e23be..b7aa2f2 100644 --- a/test/EnsoFlashloanShortcuts.t.sol +++ b/test/EnsoFlashloanShortcuts.t.sol @@ -35,16 +35,15 @@ contract EnsoFlashloanShortcutsTest is Test, ERC721Holder, ERC1155Holder { address user_bob = makeAddr("bob"); address receiver = makeAddr("receiver"); + address morpho = address(0xBBBBBbbBBb9cC5e90e3b3Af64bdAF62C37EEFFCb); + string _rpcURL = vm.envString("ETHEREUM_RPC_URL"); uint256 _ethereumFork; function setUp() public { _ethereumFork = vm.createFork(_rpcURL); vm.selectFork(_ethereumFork); - shortcuts = new EnsoFlashloanShortcuts( - IMorpho(0xBBBBBbbBBb9cC5e90e3b3Af64bdAF62C37EEFFCb), - IEulerGenericFactory(0x29a56a1b8214D9Cf7c5561811750D5cBDb45CC8e) - ); + shortcuts = new EnsoFlashloanShortcuts(); } // Morpho test: @@ -80,7 +79,7 @@ contract EnsoFlashloanShortcutsTest is Test, ERC721Holder, ERC1155Holder { state[0] = abi.encode(amount); - bytes memory morphoData = abi.encode(token, amount); + bytes memory morphoData = abi.encode(morpho, token, amount); shortcuts.flashLoan( FlashloanProtocols.Morpho, @@ -123,7 +122,7 @@ contract EnsoFlashloanShortcutsTest is Test, ERC721Holder, ERC1155Holder { state[0] = abi.encode(amount); - bytes memory morphoData = abi.encode(token, amount); + bytes memory morphoData = abi.encode(morpho, token, amount); vm.startPrank(user_bob); vm.deal(user_bob, amount); @@ -256,20 +255,6 @@ contract EnsoFlashloanShortcutsTest is Test, ERC721Holder, ERC1155Holder { assertEq(balanceAfter - balanceBefore, amount); } - // Euler test: - // - revert if attacker calls onFlashLoan with fake Euler vault - function testEulerFlashLoan_fakeEulerVault() public { - vm.selectFork(_ethereumFork); - - address fakeEulerVault = address(0xdead); - - bytes memory data = bytes(""); - - vm.prank(fakeEulerVault); - vm.expectRevert(EnsoFlashloanShortcuts.NotAuthorized.selector); - shortcuts.onFlashLoan(data); - } - // BalancerV2 test: // - flashlent asset: WETH // - no other assets involved @@ -449,4 +434,57 @@ contract EnsoFlashloanShortcutsTest is Test, ERC721Holder, ERC1155Holder { assertEq(balanceAfter - balanceBefore, wethAmount); } + + function testAaveV3FlashLoan() public { + vm.selectFork(_ethereumFork); + + IAaveV3Pool aaveV3Pool = IAaveV3Pool( + address(0x87870Bca3F3fD6335C3F4ce8392D69350B4fA4E2) + ); + IWETH token = IWETH( + address(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2) + ); + + uint256 amount = 1 ether; + uint256 BPS = 10_000; + uint256 totalFee = aaveV3Pool.FLASHLOAN_PREMIUM_TOTAL(); + uint256 fee = (amount * totalFee) / BPS; + + bytes32[] memory commands = new bytes32[](2); + bytes[] memory state = new bytes[](1); + + // Unwrap -> Wrap + commands[0] = WeirollPlanner.buildCommand( + token.withdraw.selector, + 0x01, // call + 0x00ffffffffff, // 1 inputs + 0xff, // no output + address(token) + ); + commands[1] = WeirollPlanner.buildCommand( + token.deposit.selector, + 0x03, // call + 0x00ffffffffff, // 1 inputs + 0xff, // no output + address(token) + ); + + state[0] = abi.encode(amount); + bytes memory aaveData = abi.encode(aaveV3Pool, token, amount); + + // Pretend that user did necessary actions to have enough to repay + // for the flashloan with fee + vm.startPrank(user_bob); + vm.deal(user_bob, amount); + token.deposit{value: fee}(); + token.transfer(address(shortcuts), fee); + + shortcuts.flashLoan( + FlashloanProtocols.AaveV3, + user_bob, + aaveData, + commands, + state + ); + } } From dab2b61b47ece064ce322a936f89031a5afcd259 Mon Sep 17 00:00:00 2001 From: Brenzee Date: Tue, 1 Jul 2025 11:03:53 +0300 Subject: [PATCH 6/9] EnsoFlashloanShortcuts: gas improvements --- src/flashloan/EnsoFlashloanShortcuts.sol | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/flashloan/EnsoFlashloanShortcuts.sol b/src/flashloan/EnsoFlashloanShortcuts.sol index a519a4c..b5a2ed6 100644 --- a/src/flashloan/EnsoFlashloanShortcuts.sol +++ b/src/flashloan/EnsoFlashloanShortcuts.sol @@ -28,7 +28,7 @@ contract EnsoFlashloanShortcuts is VM, ERC721Holder, ERC1155Holder { address excessReceiver, bytes calldata data, bytes32[] calldata commands, - bytes[] memory state + bytes[] calldata state ) external { if (protocol == FlashloanProtocols.Euler) { _executeEulerFlashLoan(excessReceiver, data, commands, state); @@ -54,7 +54,7 @@ contract EnsoFlashloanShortcuts is VM, ERC721Holder, ERC1155Holder { address excessReceiver, bytes calldata data, bytes32[] calldata commands, - bytes[] memory state + bytes[] calldata state ) private { (address token, uint256 amount, IEVault EulerVault) = abi.decode( data, @@ -76,7 +76,7 @@ contract EnsoFlashloanShortcuts is VM, ERC721Holder, ERC1155Holder { address excessReceiver, bytes calldata data, bytes32[] calldata commands, - bytes[] memory state + bytes[] calldata state ) private { ( IBalancerV2Vault Vault, @@ -97,7 +97,7 @@ contract EnsoFlashloanShortcuts is VM, ERC721Holder, ERC1155Holder { address excessReceiver, bytes calldata data, bytes32[] calldata commands, - bytes[] memory state + bytes[] calldata state ) private { (IMorpho morpho, address token, uint256 amount) = abi.decode( data, @@ -117,7 +117,7 @@ contract EnsoFlashloanShortcuts is VM, ERC721Holder, ERC1155Holder { address excessReceiver, bytes calldata data, bytes32[] calldata commands, - bytes[] memory state + bytes[] calldata state ) private { (IAaveV3Pool Pool, address token, uint256 amount) = abi.decode( data, From d4a729c2ad68e9254ac8cba5c544fdc68a7ff952 Mon Sep 17 00:00:00 2001 From: Brenzee Date: Tue, 1 Jul 2025 11:14:28 +0300 Subject: [PATCH 7/9] EnsoFlashloanShortcuts: data consistency --- src/flashloan/EnsoFlashloanShortcuts.sol | 4 ++-- test/EnsoFlashloanShortcuts.t.sol | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/flashloan/EnsoFlashloanShortcuts.sol b/src/flashloan/EnsoFlashloanShortcuts.sol index b5a2ed6..65b01f2 100644 --- a/src/flashloan/EnsoFlashloanShortcuts.sol +++ b/src/flashloan/EnsoFlashloanShortcuts.sol @@ -56,9 +56,9 @@ contract EnsoFlashloanShortcuts is VM, ERC721Holder, ERC1155Holder { bytes32[] calldata commands, bytes[] calldata state ) private { - (address token, uint256 amount, IEVault EulerVault) = abi.decode( + (IEVault EulerVault, address token, uint256 amount) = abi.decode( data, - (address, uint256, IEVault) + (IEVault, address, uint256) ); bytes memory eulerCallback = abi.encode( diff --git a/test/EnsoFlashloanShortcuts.t.sol b/test/EnsoFlashloanShortcuts.t.sol index b7aa2f2..67df643 100644 --- a/test/EnsoFlashloanShortcuts.t.sol +++ b/test/EnsoFlashloanShortcuts.t.sol @@ -182,7 +182,7 @@ contract EnsoFlashloanShortcutsTest is Test, ERC721Holder, ERC1155Holder { state[0] = abi.encode(amount); - bytes memory eulerData = abi.encode(token, amount, eulerVault); + bytes memory eulerData = abi.encode(eulerVault, token, amount); shortcuts.flashLoan( FlashloanProtocols.Euler, @@ -232,7 +232,7 @@ contract EnsoFlashloanShortcutsTest is Test, ERC721Holder, ERC1155Holder { state[0] = abi.encode(amount); - bytes memory eulerData = abi.encode(token, amount, eulerVault); + bytes memory eulerData = abi.encode(eulerVault, token, amount); vm.startPrank(user_bob); vm.deal(user_bob, amount); From 4cabf3feb47d328238598d3287c975f1a5f97d0b Mon Sep 17 00:00:00 2001 From: Brenzee Date: Tue, 1 Jul 2025 14:21:40 +0300 Subject: [PATCH 8/9] EnsoFlashloanShortcuts: make flashLoan payable --- src/flashloan/EnsoFlashloanShortcuts.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/flashloan/EnsoFlashloanShortcuts.sol b/src/flashloan/EnsoFlashloanShortcuts.sol index 65b01f2..e1720d2 100644 --- a/src/flashloan/EnsoFlashloanShortcuts.sol +++ b/src/flashloan/EnsoFlashloanShortcuts.sol @@ -29,7 +29,7 @@ contract EnsoFlashloanShortcuts is VM, ERC721Holder, ERC1155Holder { bytes calldata data, bytes32[] calldata commands, bytes[] calldata state - ) external { + ) external payable { if (protocol == FlashloanProtocols.Euler) { _executeEulerFlashLoan(excessReceiver, data, commands, state); } else if (protocol == FlashloanProtocols.BalancerV2) { From efa988667bfaaba126acd4135fe49b2242ce9124 Mon Sep 17 00:00:00 2001 From: PeterMPhillips Date: Wed, 2 Jul 2025 17:14:41 -0700 Subject: [PATCH 9/9] fmt + deploy --- .../8453/run-1751501652.json | 58 ++++++++ .../8453/run-latest.json | 58 ++++++++ script/FlashloanDeployer.s.sol | 17 +++ src/flashloan/EnsoFlashloanInterfaces.sol | 35 +---- src/flashloan/EnsoFlashloanShortcuts.sol | 127 +++++++--------- test/EnsoFlashloanShortcuts.t.sol | 138 ++++-------------- 6 files changed, 218 insertions(+), 215 deletions(-) create mode 100644 broadcast/FlashloanDeployer.s.sol/8453/run-1751501652.json create mode 100644 broadcast/FlashloanDeployer.s.sol/8453/run-latest.json create mode 100644 script/FlashloanDeployer.s.sol diff --git a/broadcast/FlashloanDeployer.s.sol/8453/run-1751501652.json b/broadcast/FlashloanDeployer.s.sol/8453/run-1751501652.json new file mode 100644 index 0000000..6fab688 --- /dev/null +++ b/broadcast/FlashloanDeployer.s.sol/8453/run-1751501652.json @@ -0,0 +1,58 @@ +{ + "transactions": [ + { + "hash": "0x49e68e7b17833be9126886aca9fae946b3f7583eed414df1765e987d47b9466a", + "transactionType": "CREATE2", + "contractName": "EnsoFlashloanShortcuts", + "contractAddress": "0x0649f744bffff139ee46765ab85af0fbb077f541", + "function": null, + "arguments": null, + "transaction": { + "from": "0x826e0bb2276271efdf2a500597f37b94f6c153ba", + "to": "0x4e59b44847b379578588920ca78fbf26c0b4956c", + "gas": "0x298681", + "value": "0x0", + "input": "0x456e736f466c6173686c6f616e53686f727463757473000000000000000000006080806040523460155761228e908161001a8239f35b5f80fdfe6080604052600436101561001a575b3615610018575f80fd5b005b5f3560e01c806301ffc9a7146100b9578063150b7a02146100b45780631b11d0ff146100af57806331f57072146100aa578063bc197c81146100a5578063c4850ea8146100a0578063de792d5f1461009b578063ee08d45214610096578063f04f2707146100915763f23a6e610361000e5761089c565b6107ff565b61076e565b6106ff565b61055b565b6104c2565b61034c565b610245565b6101bf565b3461010f57602036600319011261010f5760043563ffffffff60e01b811680910361010f57602090630271189760e51b81149081156100fe575b506040519015158152f35b6301ffc9a760e01b1490505f6100f3565b5f80fd5b6001600160a01b0381160361010f57565b634e487b7160e01b5f52604160045260245ffd5b90601f801991011681019081106001600160401b0382111761015957604052565b610124565b6001600160401b03811161015957601f01601f191660200190565b81601f8201121561010f578035906101908261015e565b9261019e6040519485610138565b8284526020838301011161010f57815f926020809301838601378301015290565b3461010f57608036600319011261010f576101db600435610113565b6101e6602435610113565b6064356001600160401b03811161010f57610205903690600401610179565b50604051630a85bd0160e11b8152602090f35b9181601f8401121561010f578235916001600160401b03831161010f576020838186019501011161010f57565b3461010f5760a036600319011261010f5760043561026281610113565b602435906044359060643561027681610113565b608435906001600160401b03821161010f576102b161029c6102b9933690600401610218565b90929091906001600160a01b031630146108f5565b810190610968565b919390303b1561010f5760405163de792d5f60e01b8152925f91849182916102e5919060048401610a45565b038183305af1908115610347576103219561031a936103099361032d575b50610ac6565b926001600160a01b03168383610d3f565b3390610dcc565b60405160018152602090f35b8061033b5f61034193610138565b806109bf565b5f610303565b610a94565b3461010f57604036600319011261010f576004356024356001600160401b03811161010f5761037f903690600401610218565b8101919060808184031261010f5780359061039982610113565b6020810135906103a882610113565b60408101356001600160401b03811161010f57856103c791830161090b565b9460608201356001600160401b03811161010f576103e59201610681565b93303b1561010f5760405163de792d5f60e01b8152945f918691829161040f919060048401610a45565b038183305af1908115610347576100189461031a9261043a575b506001600160a01b03168383610d3f565b8061033b5f61044893610138565b5f610429565b6001600160401b0381116101595760051b60200190565b9080601f8301121561010f57813561047c8161044e565b9261048a6040519485610138565b81845260208085019260051b82010192831161010f57602001905b8282106104b25750505090565b81358152602091820191016104a5565b3461010f5760a036600319011261010f576104de600435610113565b6104e9602435610113565b6044356001600160401b03811161010f57610508903690600401610465565b506064356001600160401b03811161010f57610528903690600401610465565b506084356001600160401b03811161010f57610548903690600401610179565b5060405163bc197c8160e01b8152602090f35b3461010f57602036600319011261010f576004356001600160401b03811161010f5761058b903690600401610218565b81019060a08183031261010f578035906020810135906105aa82610113565b6040810135906105b982610113565b60608101356001600160401b03811161010f57856105d891830161090b565b9460808201356001600160401b03811161010f576105f69201610681565b93303b1561010f5760405163de792d5f60e01b8152945f9186918291610620919060048401610a45565b038183305af1908115610347576100189461064a9261043a57506001600160a01b03168383610d3f565b3390610ea6565b9181601f8401121561010f578235916001600160401b03831161010f576020808501948460051b01011161010f57565b9080601f8301121561010f5781356106988161044e565b926106a66040519485610138565b81845260208085019260051b8201019183831161010f5760208201905b8382106106d257505050505090565b81356001600160401b03811161010f576020916106f487848094880101610179565b8152019101906106c3565b3461010f57604036600319011261010f576004356001600160401b03811161010f5761072f903690600401610651565b6024356001600160401b03811161010f5761074e903690600401610681565b9030330361075f5761001892610ff3565b6314e1dbf760e11b5f5260045ffd5b60a036600319011261010f57600435600481101561010f5760243561079281610113565b6044356001600160401b03811161010f576107b1903690600401610218565b6064939193356001600160401b03811161010f576107d3903690600401610651565b91608435956001600160401b03871161010f576107f7610018973690600401610651565b969095610af1565b3461010f57608036600319011261010f576004356001600160401b03811161010f5761082f903690600401610651565b6024356001600160401b03811161010f5761084e903690600401610651565b6044939193356001600160401b03811161010f57610870903690600401610651565b91606435956001600160401b03871161010f57610894610018973690600401610218565b969095610c58565b3461010f5760a036600319011261010f576108b8600435610113565b6108c3602435610113565b6084356001600160401b03811161010f576108e2903690600401610179565b5060405163f23a6e6160e01b8152602090f35b156108fc57565b63ea8e4eb560e01b5f5260045ffd5b9080601f8301121561010f5781356109228161044e565b926109306040519485610138565b81845260208085019260051b82010192831161010f57602001905b8282106109585750505090565b813581526020918201910161094b565b9160608383031261010f57823561097e81610113565b9260208101356001600160401b03811161010f578361099e91830161090b565b9260408201356001600160401b03811161010f576109bc9201610681565b90565b5f91031261010f57565b805180835260209291819084018484015e5f828201840152601f01601f1916010190565b9080602083519182815201916020808360051b8301019401925f915b838310610a1857505050505090565b9091929394602080610a36600193601f1986820301875289516109c9565b97019301930191939290610a09565b9291604084019360408152825180955260206060820193015f955b808710610a7c5750506109bc93945060208184039101526109ed565b90936020806001928751815201950196019590610a60565b6040513d5f823e3d90fd5b634e487b7160e01b5f52601160045260245ffd5b9060048201809211610ac157565b610a9f565b91908201809211610ac157565b60041115610add57565b634e487b7160e01b5f52602160045260245ffd5b96959493929190610b0188610ad3565b87610b1257610b10975061173a565b565b610b1b88610ad3565b60018803610b2d57610b1097506115d5565b610b3688610ad3565b60028803610b4857610b1097506114ad565b9190959294939680610b5b600392610ad3565b03610c1057856060918101031261010f57610ba2610bb092863594610b7f86610113565b6040602089013598610b908a610113565b013598604051978895602087016113d4565b03601f198101845283610138565b6001600160a01b0316803b1561010f576040516310ac2ddf60e21b8152935f938593849286928492610bf19290916001600160a01b03163060048601611408565b03925af1801561034757610c025750565b8061033b5f610b1093610138565b637079d06b60e01b5f5260045ffd5b60010190565b634e487b7160e01b5f52603260045260245ffd5b9190811015610c495760051b0190565b610c25565b356109bc81610113565b94919695610c6b91949396810190610968565b6001600160a01b03909216969190303b1561010f5760405163de792d5f60e01b8152915f9183918291610ca2919060048401610a45565b038183305af1801561034757610d2b575b505f5b848110610cc7575050505050505050565b80610d2586610d118a8a610d0c610d078f98610cff8d610cf8838f8f60019f610cf1918491610c39565b3593610c39565b3590610ac6565b968794610c39565b610c4e565b610d3f565b33610d20610d07858b8d610c39565b610ea6565b01610cb6565b8061033b5f610d3993610138565b5f610cb3565b6040516370a0823160e01b81523060048201529290916020846024816001600160a01b0387165afa938415610347575f94610d98575b50808411610d84575b50505050565b610d8f930391610ea6565b5f808080610d7e565b9093506020813d602011610dc4575b81610db460209383610138565b8101031261010f5751925f610d75565b3d9150610da7565b60405163095ea7b360e01b60208083019182526001600160a01b0385166024840152604480840196909652948252929390925f90610e0b606486610138565b84519082855af15f51903d81610e7a575b501590505b610e2a57505050565b60405163095ea7b360e01b60208201526001600160a01b0390931660248401525f6044840152610b1092610e7590610e6f81606481015b03601f198101835282610138565b826117cd565b6117cd565b15159050610e9a5750610e216001600160a01b0382163b15155b5f610e1c565b6001610e219114610e94565b60405163a9059cbb60e01b60208201526001600160a01b0390921660248301526044820192909252610b1091610e758260648101610ba2565b8051821015610c495760209160051b010190565b15610efa57565b60405162461bcd60e51b815260206004820152601660248201527556616c7565206d75737420626520333220627974657360501b6044820152606490fd5b602081519101519060208110610f4c575090565b5f199060200360031b1b1690565b5f19810191908211610ac157565b601f19810191908211610ac157565b3d15610fa1573d90610f888261015e565b91610f966040519384610138565b82523d5f602084013e565b606090565b60405190610fb5604083610138565b60078252662ab735b737bbb760c91b6020830152565b9081526001600160a01b0390911660208201526060604082018190526109bc929101906109c9565b905f5b8181106110035750505090565b61100e818385610c39565b359061103c6110366110306110238560201b90565b6001600160f81b03191690565b60f81c90565b60ff1690565b91604083161591826112f55760010192611057848688610c39565b356020905b60038316600181036111b057505f91829190602085166111995761108b916001600160e01b031987168c61188b565b805190602001826001600160a01b0387165af1926110a7610f77565b935b8490156110fd575050608016156110da576110d592916110cf611023610c1f9360581b90565b87611d81565b610ff6565b906110d592956110f06110236110f69460581b90565b90611cd6565b9360010190565b859250611108610fa6565b946044815111611158575b505090611145915f1461114957925b60405163ef3dcb2f60e01b81529384936001600160a01b03169060048501610fcb565b0390fd5b61115290610f5a565b92611122565b602061116f600461116884611aaa565b9301610f38565b036111135761118060248301610f38565b1461118c575b80611113565b6044019350611145611186565b50607f6111aa9160f81c168a610edf565b5161108b565b6002810361121357505f91829190602085166111fc576111db916001600160e01b031987168c61188b565b8051906020016001600160a01b0386165afa926111f6610f77565b936110a9565b50607f61120d9160f81c168a610edf565b516111db565b6003036112bd575f9181611244611231607f869560f81c168d610edf565b5161123f6020825114610ef3565b610f38565b9160208616158414611296576112666112606112789360081b90565b91610f5a565b906001600160e01b031988168d61188b565b905b815191602001906001600160a01b0387165af1926111f6610f77565b50607f6112af6110366110306110236112b69560081b90565b168b610edf565b519061127a565b60405162461bcd60e51b815260206004820152601060248201526f496e76616c69642063616c6c7479706560801b6044820152606490fd5b926006602883901b6001600160d01b031761105c565b81835290916001600160fb1b03831161010f5760209260051b809284830137010190565b908060209392818452848401375f828201840152601f01601f1916010190565b90602083828152019060208160051b85010193835f915b8383106113765750505050505090565b909192939495601f198282030186528635601e198436030181121561010f57830190602082359201916001600160401b03811161010f57803603831361010f576113c6602092839260019561132f565b980196019493019190611366565b93916109bc95936113fa9260018060a01b0316865260606020870152606086019161130b565b92604081850391015261134f565b6001600160a01b03918216815291166020820152604081019190915260a0606082018190529092915f91608091611441918601906109c9565b930152565b6001600160a01b039182168152911660208201526080604082018190526109bc9591949193919261147b92918601919061130b565b92606081850391015261134f565b6109bc939260609260018060a01b03168252602082015281604082015201906109c9565b90959186606091969495968101031261010f57610ba2611503928735946114d386610113565b604060208a0135996114e48b610113565b604051910135996001600160a01b031698909788958a60208801611446565b6001600160a01b0316803b1561010f57610bf1935f80946040519687958694859363701195a160e11b855260048501611489565b6001600160a01b03909116815260806020808301829052835191830182905260a0830196959301905f5b8181106115b65750505080850360408201526020808451968781520193015f955b80871061159e5750506109bc93945060608184039101526109c9565b90936020806001928751815201950196019590611582565b82516001600160a01b0316885260209788019790920191600101611561565b90919592939582019160608184031261010f578035936115f485610113565b60208201356001600160401b03811161010f5782019684601f8901121561010f578735976116218961044e565b9861162f6040519a8b610138565b808a526020808b019160051b8301019187831161010f57602001905b8282106116c0575050506040830135926001600160401b03841161010f5761168b95610ba29461167b9201610465565b98604051978895602087016113d4565b6001600160a01b0316803b1561010f57610bf1935f809460405196879586948593632e1c224f60e11b85523060048601611537565b6020809183356116cf81610113565b81520191019061164b565b9081526001600160a01b0391821660208201529116604082015260a0606082018190526109bc9591949193919261171592918601919061130b565b92608081850391015261134f565b6040906109bc9392815281602082015201906109c9565b9592949091826060918101031261010f576117999261178b9183359561175f87610113565b604060208601359561177087610113565b013597604051998a9660018060a01b03168a602089016116da565b03601f198101855284610138565b6001600160a01b031691823b1561010f57610bf1925f9283604051809681958294635296a43160e01b845260048401611723565b905f602091828151910182855af115610a94575f513d61181c57506001600160a01b0381163b155b6117fc5750565b635274afe760e01b5f9081526001600160a01b0391909116600452602490fd5b600114156117f5565b60405161016091906118378382610138565b600a815291601f1901366020840137565b9060206109bc9281815201906109ed565b906118638261015e565b6118706040519182610138565b8281528092611881601f199161015e565b0190602036910137565b91939290935f5f915f9261189d611825565b906060935f905b8782106119f9575b50506118ba6118bf91610ab3565b611859565b9760208901525f9060248901925f955b8787106118e157505050505050505050565b6020871015610c4957888b83891a60808116156119d75760fe810361193c5750505060208160019287526119298d61191883610ab3565b6119228b51610f68565b918b611f2f565b875101601f1901955b01960195936118cf565b60fd819794959697145f1461197157509261196592868660019b948a9998978560209c52612085565b50979195909594611932565b60fc810361199357509261196592868660019b948a9998978560209c52611f41565b936119d08882936119bf602096607f6001999c9b9a16906119b48282610edf565b515197889552610edf565b51906119ca85610ab3565b91611f1d565b0195611932565b602092506001939791506119ef607f8492168d610edf565b5101518152611932565b909395916020851015610c495786851a60ff8114611a96576080811615611a815760fe8103611a615750855115611a3f575b6020600191875101935b01969401906118a4565b945060016020604051611a5881610e618d858301611848565b96915050611a2b565b90611a759260019692602095968a8d611e4e565b95919390939294611a35565b611a906020916001938c611ddb565b93611a35565b5093965090949290506118ba6118bf6118ac565b9081516043198101818111610ac1579260445b828110611ac957505050565b8151811015610c4957818101602001516001600160f81b03191615611af057600101611abd565b92935050506043198101908111610ac15790565b15611b0b57565b60405162461bcd60e51b8152602060048201526013602482015272496e646578206f75742d6f662d626f756e647360681b6044820152606490fd5b15611b4d57565b60405162461bcd60e51b815260206004820152602860248201527f4f6e6c79206f6e652072657475726e2076616c7565207065726d697474656420604482015267287374617469632960c01b6064820152608490fd5b15611baa57565b60405162461bcd60e51b815260206004820152602a60248201527f4f6e6c79206f6e652072657475726e2076616c7565207065726d697474656420604482015269287661726961626c652960b01b6064820152608490fd5b60208183031261010f578051906001600160401b03821161010f57019080601f8301121561010f57815191611c368361044e565b92611c446040519485610138565b80845260208085019160051b8301019183831161010f5760208101915b838310611c7057505050505090565b82516001600160401b03811161010f57820185603f8201121561010f57602081015191611c9c8361015e565b611ca96040519182610138565b838152604083850101881061010f575f602085819660408397018386015e83010152815201920191611c61565b91908060f81c60ff8114611d7b576080811615611d435760fe8103611d0b5750506109bc915060208082518301019101611c02565b602091611d21610fe092607f8751911610611b04565b82840193611d3184865114611ba3565b51601f1901845260f31c168301015290565b611d77929150607f1690611d5984518310611b04565b611d666020825114611b46565b611d708285610edf565b5282610edf565b5090565b50505090565b9060f81c60ff8114611dd65782519060208201809211610ac157602092607f611dac611dbf94611859565b921691611db98383610edf565b52610edf565b51918051604084018184840160045afa5051910152565b505050565b611dec90607f602093941690610edf565b515103611df95760200190565b60405162461bcd60e51b815260206004820152602760248201527f537461746963207374617465207661726961626c6573206d75737420626520336044820152663220627974657360c81b6064820152608490fd5b90969594939260fd8103611e6e575095611e689596612212565b90919293565b60fc8103611e82575095611e68959661212b565b9196509194939291611e9791607f1690610edf565b515180151580611f12575b15611eae570160200191565b60405162461bcd60e51b815260206004820152603660248201527f44796e616d6963207374617465207661726961626c6573206d7573742062652060448201527561206d756c7469706c65206f6620333220627974657360501b6064820152608490fd5b50601f811615611ea2565b916020809185930101920160045afa50565b910160200190829060400160045afa50565b9193959692905f94611f538884610edf565b51936024600180878b019b019b0198820101915b60208910611f78575b505050505050565b80891a608081161561205c5760fb8103611f925750611f70565b60fd819c92959a9499969b93979c145f14611fe3575090611fba92918b89528a858b89612085565b9260209a93926001928c969480919d939d97929d9e01970101985b019301979291939490611f67565b60fc810361201d575090611ffe92918b89528a858b89611f41565b9260209a93926001928c969480919d939d97929d9e0197010198611fd5565b6020898b8e6001959f9e979896612050607f869716948d6119bf6120418888610edf565b51519788978897889552610edf565b019d0197010198611fd5565b602060019293979694998161207988607f999e9983961690610edf565b5101518b520198611fd5565b939291909495600101946020861015610c495760206120bf97816120ae607f868b1a1685610edf565b510151602482890101520194611f41565b929391929091602090910190565b156120d457565b60405162461bcd60e51b815260206004820152602960248201527f43616e6e6f74207573652073746174652066726f6d20696e736964652064796e604482015268616d6963207479706560b81b6064820152608490fd5b6001808501976020909601960194935f929091905b602087106121995760405162461bcd60e51b8152602060048201526024808201527f44796e616d6963207479706520776173206e6f742070726f7065726c7920636c6044820152631bdcd95960e21b6064820152608490fd5b80871a60808116156121fa5760fb81036121c1575050506121ba9083610edf565b5293929190565b6121e194956121da60fe839c949a969b959c14156120cd565b888b611e4e565b9197909692939192916001906020905b01930195612140565b61220c60209160019399969a85611ddb565b986121f1565b9291909394600101936020851015610c49576020612235607f83881a1686610edf565b515103612249576020611e6896019361212b565b60405162461bcd60e51b815260206004820152601d60248201527f4172726179206c656e677468206d7573742062652033322062797465730000006044820152606490fd", + "nonce": "0x40", + "chainId": "0x2105" + }, + "additionalContracts": [], + "isFixedGasLimit": false + } + ], + "receipts": [ + { + "status": "0x1", + "cumulativeGasUsed": "0x24fcd56", + "logs": [], + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "type": "0x2", + "transactionHash": "0x49e68e7b17833be9126886aca9fae946b3f7583eed414df1765e987d47b9466a", + "transactionIndex": "0xbe", + "blockHash": "0xcbec2e74c50bf126c83bb6ae241311125e792dd2cccb3dda9d2f09c7a4af33e5", + "blockNumber": "0x1edb72d", + "gasUsed": "0x1e104c", + "effectiveGasPrice": "0x40d1d7", + "from": "0x826e0bb2276271efdf2a500597f37b94f6c153ba", + "to": "0x4e59b44847b379578588920ca78fbf26c0b4956c", + "contractAddress": "0x0649f744bffff139ee46765ab85af0fbb077f541", + "l1BaseFeeScalar": "0x8dd", + "l1BlobBaseFee": "0x1", + "l1BlobBaseFeeScalar": "0x101c12", + "l1Fee": "0x1e37402eb8", + "l1GasPrice": "0x285593ab", + "l1GasUsed": "0x14a29" + } + ], + "libraries": [], + "pending": [], + "returns": { + "flashloanShortcuts": { + "internal_type": "contract EnsoFlashloanShortcuts", + "value": "0x0649f744bfFFF139eE46765aB85AF0FBb077F541" + } + }, + "timestamp": 1751501652, + "chain": 8453, + "commit": "4cabf3f" +} \ No newline at end of file diff --git a/broadcast/FlashloanDeployer.s.sol/8453/run-latest.json b/broadcast/FlashloanDeployer.s.sol/8453/run-latest.json new file mode 100644 index 0000000..6fab688 --- /dev/null +++ b/broadcast/FlashloanDeployer.s.sol/8453/run-latest.json @@ -0,0 +1,58 @@ +{ + "transactions": [ + { + "hash": "0x49e68e7b17833be9126886aca9fae946b3f7583eed414df1765e987d47b9466a", + "transactionType": "CREATE2", + "contractName": "EnsoFlashloanShortcuts", + "contractAddress": "0x0649f744bffff139ee46765ab85af0fbb077f541", + "function": null, + "arguments": null, + "transaction": { + "from": "0x826e0bb2276271efdf2a500597f37b94f6c153ba", + "to": "0x4e59b44847b379578588920ca78fbf26c0b4956c", + "gas": "0x298681", + "value": "0x0", + "input": "0x456e736f466c6173686c6f616e53686f727463757473000000000000000000006080806040523460155761228e908161001a8239f35b5f80fdfe6080604052600436101561001a575b3615610018575f80fd5b005b5f3560e01c806301ffc9a7146100b9578063150b7a02146100b45780631b11d0ff146100af57806331f57072146100aa578063bc197c81146100a5578063c4850ea8146100a0578063de792d5f1461009b578063ee08d45214610096578063f04f2707146100915763f23a6e610361000e5761089c565b6107ff565b61076e565b6106ff565b61055b565b6104c2565b61034c565b610245565b6101bf565b3461010f57602036600319011261010f5760043563ffffffff60e01b811680910361010f57602090630271189760e51b81149081156100fe575b506040519015158152f35b6301ffc9a760e01b1490505f6100f3565b5f80fd5b6001600160a01b0381160361010f57565b634e487b7160e01b5f52604160045260245ffd5b90601f801991011681019081106001600160401b0382111761015957604052565b610124565b6001600160401b03811161015957601f01601f191660200190565b81601f8201121561010f578035906101908261015e565b9261019e6040519485610138565b8284526020838301011161010f57815f926020809301838601378301015290565b3461010f57608036600319011261010f576101db600435610113565b6101e6602435610113565b6064356001600160401b03811161010f57610205903690600401610179565b50604051630a85bd0160e11b8152602090f35b9181601f8401121561010f578235916001600160401b03831161010f576020838186019501011161010f57565b3461010f5760a036600319011261010f5760043561026281610113565b602435906044359060643561027681610113565b608435906001600160401b03821161010f576102b161029c6102b9933690600401610218565b90929091906001600160a01b031630146108f5565b810190610968565b919390303b1561010f5760405163de792d5f60e01b8152925f91849182916102e5919060048401610a45565b038183305af1908115610347576103219561031a936103099361032d575b50610ac6565b926001600160a01b03168383610d3f565b3390610dcc565b60405160018152602090f35b8061033b5f61034193610138565b806109bf565b5f610303565b610a94565b3461010f57604036600319011261010f576004356024356001600160401b03811161010f5761037f903690600401610218565b8101919060808184031261010f5780359061039982610113565b6020810135906103a882610113565b60408101356001600160401b03811161010f57856103c791830161090b565b9460608201356001600160401b03811161010f576103e59201610681565b93303b1561010f5760405163de792d5f60e01b8152945f918691829161040f919060048401610a45565b038183305af1908115610347576100189461031a9261043a575b506001600160a01b03168383610d3f565b8061033b5f61044893610138565b5f610429565b6001600160401b0381116101595760051b60200190565b9080601f8301121561010f57813561047c8161044e565b9261048a6040519485610138565b81845260208085019260051b82010192831161010f57602001905b8282106104b25750505090565b81358152602091820191016104a5565b3461010f5760a036600319011261010f576104de600435610113565b6104e9602435610113565b6044356001600160401b03811161010f57610508903690600401610465565b506064356001600160401b03811161010f57610528903690600401610465565b506084356001600160401b03811161010f57610548903690600401610179565b5060405163bc197c8160e01b8152602090f35b3461010f57602036600319011261010f576004356001600160401b03811161010f5761058b903690600401610218565b81019060a08183031261010f578035906020810135906105aa82610113565b6040810135906105b982610113565b60608101356001600160401b03811161010f57856105d891830161090b565b9460808201356001600160401b03811161010f576105f69201610681565b93303b1561010f5760405163de792d5f60e01b8152945f9186918291610620919060048401610a45565b038183305af1908115610347576100189461064a9261043a57506001600160a01b03168383610d3f565b3390610ea6565b9181601f8401121561010f578235916001600160401b03831161010f576020808501948460051b01011161010f57565b9080601f8301121561010f5781356106988161044e565b926106a66040519485610138565b81845260208085019260051b8201019183831161010f5760208201905b8382106106d257505050505090565b81356001600160401b03811161010f576020916106f487848094880101610179565b8152019101906106c3565b3461010f57604036600319011261010f576004356001600160401b03811161010f5761072f903690600401610651565b6024356001600160401b03811161010f5761074e903690600401610681565b9030330361075f5761001892610ff3565b6314e1dbf760e11b5f5260045ffd5b60a036600319011261010f57600435600481101561010f5760243561079281610113565b6044356001600160401b03811161010f576107b1903690600401610218565b6064939193356001600160401b03811161010f576107d3903690600401610651565b91608435956001600160401b03871161010f576107f7610018973690600401610651565b969095610af1565b3461010f57608036600319011261010f576004356001600160401b03811161010f5761082f903690600401610651565b6024356001600160401b03811161010f5761084e903690600401610651565b6044939193356001600160401b03811161010f57610870903690600401610651565b91606435956001600160401b03871161010f57610894610018973690600401610218565b969095610c58565b3461010f5760a036600319011261010f576108b8600435610113565b6108c3602435610113565b6084356001600160401b03811161010f576108e2903690600401610179565b5060405163f23a6e6160e01b8152602090f35b156108fc57565b63ea8e4eb560e01b5f5260045ffd5b9080601f8301121561010f5781356109228161044e565b926109306040519485610138565b81845260208085019260051b82010192831161010f57602001905b8282106109585750505090565b813581526020918201910161094b565b9160608383031261010f57823561097e81610113565b9260208101356001600160401b03811161010f578361099e91830161090b565b9260408201356001600160401b03811161010f576109bc9201610681565b90565b5f91031261010f57565b805180835260209291819084018484015e5f828201840152601f01601f1916010190565b9080602083519182815201916020808360051b8301019401925f915b838310610a1857505050505090565b9091929394602080610a36600193601f1986820301875289516109c9565b97019301930191939290610a09565b9291604084019360408152825180955260206060820193015f955b808710610a7c5750506109bc93945060208184039101526109ed565b90936020806001928751815201950196019590610a60565b6040513d5f823e3d90fd5b634e487b7160e01b5f52601160045260245ffd5b9060048201809211610ac157565b610a9f565b91908201809211610ac157565b60041115610add57565b634e487b7160e01b5f52602160045260245ffd5b96959493929190610b0188610ad3565b87610b1257610b10975061173a565b565b610b1b88610ad3565b60018803610b2d57610b1097506115d5565b610b3688610ad3565b60028803610b4857610b1097506114ad565b9190959294939680610b5b600392610ad3565b03610c1057856060918101031261010f57610ba2610bb092863594610b7f86610113565b6040602089013598610b908a610113565b013598604051978895602087016113d4565b03601f198101845283610138565b6001600160a01b0316803b1561010f576040516310ac2ddf60e21b8152935f938593849286928492610bf19290916001600160a01b03163060048601611408565b03925af1801561034757610c025750565b8061033b5f610b1093610138565b637079d06b60e01b5f5260045ffd5b60010190565b634e487b7160e01b5f52603260045260245ffd5b9190811015610c495760051b0190565b610c25565b356109bc81610113565b94919695610c6b91949396810190610968565b6001600160a01b03909216969190303b1561010f5760405163de792d5f60e01b8152915f9183918291610ca2919060048401610a45565b038183305af1801561034757610d2b575b505f5b848110610cc7575050505050505050565b80610d2586610d118a8a610d0c610d078f98610cff8d610cf8838f8f60019f610cf1918491610c39565b3593610c39565b3590610ac6565b968794610c39565b610c4e565b610d3f565b33610d20610d07858b8d610c39565b610ea6565b01610cb6565b8061033b5f610d3993610138565b5f610cb3565b6040516370a0823160e01b81523060048201529290916020846024816001600160a01b0387165afa938415610347575f94610d98575b50808411610d84575b50505050565b610d8f930391610ea6565b5f808080610d7e565b9093506020813d602011610dc4575b81610db460209383610138565b8101031261010f5751925f610d75565b3d9150610da7565b60405163095ea7b360e01b60208083019182526001600160a01b0385166024840152604480840196909652948252929390925f90610e0b606486610138565b84519082855af15f51903d81610e7a575b501590505b610e2a57505050565b60405163095ea7b360e01b60208201526001600160a01b0390931660248401525f6044840152610b1092610e7590610e6f81606481015b03601f198101835282610138565b826117cd565b6117cd565b15159050610e9a5750610e216001600160a01b0382163b15155b5f610e1c565b6001610e219114610e94565b60405163a9059cbb60e01b60208201526001600160a01b0390921660248301526044820192909252610b1091610e758260648101610ba2565b8051821015610c495760209160051b010190565b15610efa57565b60405162461bcd60e51b815260206004820152601660248201527556616c7565206d75737420626520333220627974657360501b6044820152606490fd5b602081519101519060208110610f4c575090565b5f199060200360031b1b1690565b5f19810191908211610ac157565b601f19810191908211610ac157565b3d15610fa1573d90610f888261015e565b91610f966040519384610138565b82523d5f602084013e565b606090565b60405190610fb5604083610138565b60078252662ab735b737bbb760c91b6020830152565b9081526001600160a01b0390911660208201526060604082018190526109bc929101906109c9565b905f5b8181106110035750505090565b61100e818385610c39565b359061103c6110366110306110238560201b90565b6001600160f81b03191690565b60f81c90565b60ff1690565b91604083161591826112f55760010192611057848688610c39565b356020905b60038316600181036111b057505f91829190602085166111995761108b916001600160e01b031987168c61188b565b805190602001826001600160a01b0387165af1926110a7610f77565b935b8490156110fd575050608016156110da576110d592916110cf611023610c1f9360581b90565b87611d81565b610ff6565b906110d592956110f06110236110f69460581b90565b90611cd6565b9360010190565b859250611108610fa6565b946044815111611158575b505090611145915f1461114957925b60405163ef3dcb2f60e01b81529384936001600160a01b03169060048501610fcb565b0390fd5b61115290610f5a565b92611122565b602061116f600461116884611aaa565b9301610f38565b036111135761118060248301610f38565b1461118c575b80611113565b6044019350611145611186565b50607f6111aa9160f81c168a610edf565b5161108b565b6002810361121357505f91829190602085166111fc576111db916001600160e01b031987168c61188b565b8051906020016001600160a01b0386165afa926111f6610f77565b936110a9565b50607f61120d9160f81c168a610edf565b516111db565b6003036112bd575f9181611244611231607f869560f81c168d610edf565b5161123f6020825114610ef3565b610f38565b9160208616158414611296576112666112606112789360081b90565b91610f5a565b906001600160e01b031988168d61188b565b905b815191602001906001600160a01b0387165af1926111f6610f77565b50607f6112af6110366110306110236112b69560081b90565b168b610edf565b519061127a565b60405162461bcd60e51b815260206004820152601060248201526f496e76616c69642063616c6c7479706560801b6044820152606490fd5b926006602883901b6001600160d01b031761105c565b81835290916001600160fb1b03831161010f5760209260051b809284830137010190565b908060209392818452848401375f828201840152601f01601f1916010190565b90602083828152019060208160051b85010193835f915b8383106113765750505050505090565b909192939495601f198282030186528635601e198436030181121561010f57830190602082359201916001600160401b03811161010f57803603831361010f576113c6602092839260019561132f565b980196019493019190611366565b93916109bc95936113fa9260018060a01b0316865260606020870152606086019161130b565b92604081850391015261134f565b6001600160a01b03918216815291166020820152604081019190915260a0606082018190529092915f91608091611441918601906109c9565b930152565b6001600160a01b039182168152911660208201526080604082018190526109bc9591949193919261147b92918601919061130b565b92606081850391015261134f565b6109bc939260609260018060a01b03168252602082015281604082015201906109c9565b90959186606091969495968101031261010f57610ba2611503928735946114d386610113565b604060208a0135996114e48b610113565b604051910135996001600160a01b031698909788958a60208801611446565b6001600160a01b0316803b1561010f57610bf1935f80946040519687958694859363701195a160e11b855260048501611489565b6001600160a01b03909116815260806020808301829052835191830182905260a0830196959301905f5b8181106115b65750505080850360408201526020808451968781520193015f955b80871061159e5750506109bc93945060608184039101526109c9565b90936020806001928751815201950196019590611582565b82516001600160a01b0316885260209788019790920191600101611561565b90919592939582019160608184031261010f578035936115f485610113565b60208201356001600160401b03811161010f5782019684601f8901121561010f578735976116218961044e565b9861162f6040519a8b610138565b808a526020808b019160051b8301019187831161010f57602001905b8282106116c0575050506040830135926001600160401b03841161010f5761168b95610ba29461167b9201610465565b98604051978895602087016113d4565b6001600160a01b0316803b1561010f57610bf1935f809460405196879586948593632e1c224f60e11b85523060048601611537565b6020809183356116cf81610113565b81520191019061164b565b9081526001600160a01b0391821660208201529116604082015260a0606082018190526109bc9591949193919261171592918601919061130b565b92608081850391015261134f565b6040906109bc9392815281602082015201906109c9565b9592949091826060918101031261010f576117999261178b9183359561175f87610113565b604060208601359561177087610113565b013597604051998a9660018060a01b03168a602089016116da565b03601f198101855284610138565b6001600160a01b031691823b1561010f57610bf1925f9283604051809681958294635296a43160e01b845260048401611723565b905f602091828151910182855af115610a94575f513d61181c57506001600160a01b0381163b155b6117fc5750565b635274afe760e01b5f9081526001600160a01b0391909116600452602490fd5b600114156117f5565b60405161016091906118378382610138565b600a815291601f1901366020840137565b9060206109bc9281815201906109ed565b906118638261015e565b6118706040519182610138565b8281528092611881601f199161015e565b0190602036910137565b91939290935f5f915f9261189d611825565b906060935f905b8782106119f9575b50506118ba6118bf91610ab3565b611859565b9760208901525f9060248901925f955b8787106118e157505050505050505050565b6020871015610c4957888b83891a60808116156119d75760fe810361193c5750505060208160019287526119298d61191883610ab3565b6119228b51610f68565b918b611f2f565b875101601f1901955b01960195936118cf565b60fd819794959697145f1461197157509261196592868660019b948a9998978560209c52612085565b50979195909594611932565b60fc810361199357509261196592868660019b948a9998978560209c52611f41565b936119d08882936119bf602096607f6001999c9b9a16906119b48282610edf565b515197889552610edf565b51906119ca85610ab3565b91611f1d565b0195611932565b602092506001939791506119ef607f8492168d610edf565b5101518152611932565b909395916020851015610c495786851a60ff8114611a96576080811615611a815760fe8103611a615750855115611a3f575b6020600191875101935b01969401906118a4565b945060016020604051611a5881610e618d858301611848565b96915050611a2b565b90611a759260019692602095968a8d611e4e565b95919390939294611a35565b611a906020916001938c611ddb565b93611a35565b5093965090949290506118ba6118bf6118ac565b9081516043198101818111610ac1579260445b828110611ac957505050565b8151811015610c4957818101602001516001600160f81b03191615611af057600101611abd565b92935050506043198101908111610ac15790565b15611b0b57565b60405162461bcd60e51b8152602060048201526013602482015272496e646578206f75742d6f662d626f756e647360681b6044820152606490fd5b15611b4d57565b60405162461bcd60e51b815260206004820152602860248201527f4f6e6c79206f6e652072657475726e2076616c7565207065726d697474656420604482015267287374617469632960c01b6064820152608490fd5b15611baa57565b60405162461bcd60e51b815260206004820152602a60248201527f4f6e6c79206f6e652072657475726e2076616c7565207065726d697474656420604482015269287661726961626c652960b01b6064820152608490fd5b60208183031261010f578051906001600160401b03821161010f57019080601f8301121561010f57815191611c368361044e565b92611c446040519485610138565b80845260208085019160051b8301019183831161010f5760208101915b838310611c7057505050505090565b82516001600160401b03811161010f57820185603f8201121561010f57602081015191611c9c8361015e565b611ca96040519182610138565b838152604083850101881061010f575f602085819660408397018386015e83010152815201920191611c61565b91908060f81c60ff8114611d7b576080811615611d435760fe8103611d0b5750506109bc915060208082518301019101611c02565b602091611d21610fe092607f8751911610611b04565b82840193611d3184865114611ba3565b51601f1901845260f31c168301015290565b611d77929150607f1690611d5984518310611b04565b611d666020825114611b46565b611d708285610edf565b5282610edf565b5090565b50505090565b9060f81c60ff8114611dd65782519060208201809211610ac157602092607f611dac611dbf94611859565b921691611db98383610edf565b52610edf565b51918051604084018184840160045afa5051910152565b505050565b611dec90607f602093941690610edf565b515103611df95760200190565b60405162461bcd60e51b815260206004820152602760248201527f537461746963207374617465207661726961626c6573206d75737420626520336044820152663220627974657360c81b6064820152608490fd5b90969594939260fd8103611e6e575095611e689596612212565b90919293565b60fc8103611e82575095611e68959661212b565b9196509194939291611e9791607f1690610edf565b515180151580611f12575b15611eae570160200191565b60405162461bcd60e51b815260206004820152603660248201527f44796e616d6963207374617465207661726961626c6573206d7573742062652060448201527561206d756c7469706c65206f6620333220627974657360501b6064820152608490fd5b50601f811615611ea2565b916020809185930101920160045afa50565b910160200190829060400160045afa50565b9193959692905f94611f538884610edf565b51936024600180878b019b019b0198820101915b60208910611f78575b505050505050565b80891a608081161561205c5760fb8103611f925750611f70565b60fd819c92959a9499969b93979c145f14611fe3575090611fba92918b89528a858b89612085565b9260209a93926001928c969480919d939d97929d9e01970101985b019301979291939490611f67565b60fc810361201d575090611ffe92918b89528a858b89611f41565b9260209a93926001928c969480919d939d97929d9e0197010198611fd5565b6020898b8e6001959f9e979896612050607f869716948d6119bf6120418888610edf565b51519788978897889552610edf565b019d0197010198611fd5565b602060019293979694998161207988607f999e9983961690610edf565b5101518b520198611fd5565b939291909495600101946020861015610c495760206120bf97816120ae607f868b1a1685610edf565b510151602482890101520194611f41565b929391929091602090910190565b156120d457565b60405162461bcd60e51b815260206004820152602960248201527f43616e6e6f74207573652073746174652066726f6d20696e736964652064796e604482015268616d6963207479706560b81b6064820152608490fd5b6001808501976020909601960194935f929091905b602087106121995760405162461bcd60e51b8152602060048201526024808201527f44796e616d6963207479706520776173206e6f742070726f7065726c7920636c6044820152631bdcd95960e21b6064820152608490fd5b80871a60808116156121fa5760fb81036121c1575050506121ba9083610edf565b5293929190565b6121e194956121da60fe839c949a969b959c14156120cd565b888b611e4e565b9197909692939192916001906020905b01930195612140565b61220c60209160019399969a85611ddb565b986121f1565b9291909394600101936020851015610c49576020612235607f83881a1686610edf565b515103612249576020611e6896019361212b565b60405162461bcd60e51b815260206004820152601d60248201527f4172726179206c656e677468206d7573742062652033322062797465730000006044820152606490fd", + "nonce": "0x40", + "chainId": "0x2105" + }, + "additionalContracts": [], + "isFixedGasLimit": false + } + ], + "receipts": [ + { + "status": "0x1", + "cumulativeGasUsed": "0x24fcd56", + "logs": [], + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "type": "0x2", + "transactionHash": "0x49e68e7b17833be9126886aca9fae946b3f7583eed414df1765e987d47b9466a", + "transactionIndex": "0xbe", + "blockHash": "0xcbec2e74c50bf126c83bb6ae241311125e792dd2cccb3dda9d2f09c7a4af33e5", + "blockNumber": "0x1edb72d", + "gasUsed": "0x1e104c", + "effectiveGasPrice": "0x40d1d7", + "from": "0x826e0bb2276271efdf2a500597f37b94f6c153ba", + "to": "0x4e59b44847b379578588920ca78fbf26c0b4956c", + "contractAddress": "0x0649f744bffff139ee46765ab85af0fbb077f541", + "l1BaseFeeScalar": "0x8dd", + "l1BlobBaseFee": "0x1", + "l1BlobBaseFeeScalar": "0x101c12", + "l1Fee": "0x1e37402eb8", + "l1GasPrice": "0x285593ab", + "l1GasUsed": "0x14a29" + } + ], + "libraries": [], + "pending": [], + "returns": { + "flashloanShortcuts": { + "internal_type": "contract EnsoFlashloanShortcuts", + "value": "0x0649f744bfFFF139eE46765aB85AF0FBb077F541" + } + }, + "timestamp": 1751501652, + "chain": 8453, + "commit": "4cabf3f" +} \ No newline at end of file diff --git a/script/FlashloanDeployer.s.sol b/script/FlashloanDeployer.s.sol new file mode 100644 index 0000000..2bea2b0 --- /dev/null +++ b/script/FlashloanDeployer.s.sol @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: GPL-3.0-only +pragma solidity ^0.8.28; + +import "../src/flashloan/EnsoFlashloanShortcuts.sol"; +import "forge-std/Script.sol"; + +contract FlashloanDeployer is Script { + function run() public returns (EnsoFlashloanShortcuts flashloanShortcuts) { + uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY"); + + vm.startBroadcast(deployerPrivateKey); + + flashloanShortcuts = new EnsoFlashloanShortcuts{ salt: "EnsoFlashloanShortcuts" }(); + + vm.stopBroadcast(); + } +} diff --git a/src/flashloan/EnsoFlashloanInterfaces.sol b/src/flashloan/EnsoFlashloanInterfaces.sol index 5742005..452aef2 100644 --- a/src/flashloan/EnsoFlashloanInterfaces.sol +++ b/src/flashloan/EnsoFlashloanInterfaces.sol @@ -1,25 +1,6 @@ // SPDX-License-Identifier: GPL-3.0-only pragma solidity ^0.8.28; -interface IRouter { - enum TokenType { - Native, - ERC20, - ERC721, - ERC1155 - } - - struct Token { - TokenType tokenType; - bytes data; - } - - function routeSingle( - Token calldata tokenIn, - bytes calldata data - ) external payable returns (bytes memory response); -} - interface IERC3156FlashBorrower { function onFlashLoan( address initiator, @@ -27,15 +8,13 @@ interface IERC3156FlashBorrower { uint256 amount, uint256 fee, bytes calldata data - ) external returns (bytes32); + ) + external + returns (bytes32); } interface IMorpho { - function flashLoan( - address token, - uint256 assets, - bytes calldata data - ) external; + function flashLoan(address token, uint256 assets, bytes calldata data) external; } interface IEVault { @@ -48,7 +27,8 @@ interface IBalancerV2Vault { address[] memory tokens, uint256[] memory amounts, bytes memory userData - ) external; + ) + external; } interface IAaveV3Pool { @@ -60,5 +40,6 @@ interface IAaveV3Pool { uint256 amount, bytes calldata params, uint16 referralCode - ) external; + ) + external; } diff --git a/src/flashloan/EnsoFlashloanShortcuts.sol b/src/flashloan/EnsoFlashloanShortcuts.sol index e1720d2..ffcf4e5 100644 --- a/src/flashloan/EnsoFlashloanShortcuts.sol +++ b/src/flashloan/EnsoFlashloanShortcuts.sol @@ -1,13 +1,14 @@ // SPDX-License-Identifier: GPL-3.0-only pragma solidity ^0.8.28; -import "./EnsoFlashloanInterfaces.sol"; +import { IAaveV3Pool, IBalancerV2Vault, IERC3156FlashBorrower, IEVault, IMorpho } from "./EnsoFlashloanInterfaces.sol"; -import {VM} from "enso-weiroll/VM.sol"; +import { VM } from "enso-weiroll/VM.sol"; -import {ERC1155Holder} from "openzeppelin-contracts/token/ERC1155/utils/ERC1155Holder.sol"; -import {ERC721Holder} from "openzeppelin-contracts/token/ERC721/utils/ERC721Holder.sol"; -import {IERC20, SafeERC20} from "openzeppelin-contracts/token/ERC20/utils/SafeERC20.sol"; +import { ERC1155Holder } from "openzeppelin-contracts/token/ERC1155/utils/ERC1155Holder.sol"; + +import { IERC20, SafeERC20 } from "openzeppelin-contracts/token/ERC20/utils/SafeERC20.sol"; +import { ERC721Holder } from "openzeppelin-contracts/token/ERC721/utils/ERC721Holder.sol"; enum FlashloanProtocols { Euler, @@ -29,7 +30,10 @@ contract EnsoFlashloanShortcuts is VM, ERC721Holder, ERC1155Holder { bytes calldata data, bytes32[] calldata commands, bytes[] calldata state - ) external payable { + ) + external + payable + { if (protocol == FlashloanProtocols.Euler) { _executeEulerFlashLoan(excessReceiver, data, commands, state); } else if (protocol == FlashloanProtocols.BalancerV2) { @@ -55,19 +59,12 @@ contract EnsoFlashloanShortcuts is VM, ERC721Holder, ERC1155Holder { bytes calldata data, bytes32[] calldata commands, bytes[] calldata state - ) private { - (IEVault EulerVault, address token, uint256 amount) = abi.decode( - data, - (IEVault, address, uint256) - ); - - bytes memory eulerCallback = abi.encode( - amount, - token, - excessReceiver, - commands, - state - ); + ) + private + { + (IEVault EulerVault, address token, uint256 amount) = abi.decode(data, (IEVault, address, uint256)); + + bytes memory eulerCallback = abi.encode(amount, token, excessReceiver, commands, state); EulerVault.flashLoan(amount, eulerCallback); } @@ -77,18 +74,13 @@ contract EnsoFlashloanShortcuts is VM, ERC721Holder, ERC1155Holder { bytes calldata data, bytes32[] calldata commands, bytes[] calldata state - ) private { - ( - IBalancerV2Vault Vault, - address[] memory tokens, - uint256[] memory amounts - ) = abi.decode(data, (IBalancerV2Vault, address[], uint256[])); - - bytes memory balancerV2Callback = abi.encode( - excessReceiver, - commands, - state - ); + ) + private + { + (IBalancerV2Vault Vault, address[] memory tokens, uint256[] memory amounts) = + abi.decode(data, (IBalancerV2Vault, address[], uint256[])); + + bytes memory balancerV2Callback = abi.encode(excessReceiver, commands, state); Vault.flashLoan(address(this), tokens, amounts, balancerV2Callback); } @@ -98,17 +90,11 @@ contract EnsoFlashloanShortcuts is VM, ERC721Holder, ERC1155Holder { bytes calldata data, bytes32[] calldata commands, bytes[] calldata state - ) private { - (IMorpho morpho, address token, uint256 amount) = abi.decode( - data, - (IMorpho, address, uint256) - ); - bytes memory morphoCallback = abi.encode( - token, - excessReceiver, - commands, - state - ); + ) + private + { + (IMorpho morpho, address token, uint256 amount) = abi.decode(data, (IMorpho, address, uint256)); + bytes memory morphoCallback = abi.encode(token, excessReceiver, commands, state); morpho.flashLoan(token, amount, morphoCallback); } @@ -118,11 +104,10 @@ contract EnsoFlashloanShortcuts is VM, ERC721Holder, ERC1155Holder { bytes calldata data, bytes32[] calldata commands, bytes[] calldata state - ) private { - (IAaveV3Pool Pool, address token, uint256 amount) = abi.decode( - data, - (IAaveV3Pool, address, uint256) - ); + ) + private + { + (IAaveV3Pool Pool, address token, uint256 amount) = abi.decode(data, (IAaveV3Pool, address, uint256)); bytes memory aaveCallback = abi.encode(excessReceiver, commands, state); @@ -133,13 +118,8 @@ contract EnsoFlashloanShortcuts is VM, ERC721Holder, ERC1155Holder { // Euler function onFlashLoan(bytes calldata data) external { - ( - uint256 amount, - IERC20 token, - address excessReceiver, - bytes32[] memory commands, - bytes[] memory state - ) = abi.decode(data, (uint256, IERC20, address, bytes32[], bytes[])); + (uint256 amount, IERC20 token, address excessReceiver, bytes32[] memory commands, bytes[] memory state) = + abi.decode(data, (uint256, IERC20, address, bytes32[], bytes[])); this.execute(commands, state); @@ -154,12 +134,11 @@ contract EnsoFlashloanShortcuts is VM, ERC721Holder, ERC1155Holder { uint256[] calldata amounts, uint256[] calldata feeAmounts, bytes calldata data - ) external { - ( - address excessReceiver, - bytes32[] memory commands, - bytes[] memory state - ) = abi.decode(data, (address, bytes32[], bytes[])); + ) + external + { + (address excessReceiver, bytes32[] memory commands, bytes[] memory state) = + abi.decode(data, (address, bytes32[], bytes[])); this.execute(commands, state); @@ -172,12 +151,8 @@ contract EnsoFlashloanShortcuts is VM, ERC721Holder, ERC1155Holder { // Morpho function onMorphoFlashLoan(uint256 amount, bytes calldata data) external { - ( - IERC20 token, - address excessReceiver, - bytes32[] memory commands, - bytes[] memory state - ) = abi.decode(data, (IERC20, address, bytes32[], bytes[])); + (IERC20 token, address excessReceiver, bytes32[] memory commands, bytes[] memory state) = + abi.decode(data, (IERC20, address, bytes32[], bytes[])); this.execute(commands, state); @@ -193,13 +168,13 @@ contract EnsoFlashloanShortcuts is VM, ERC721Holder, ERC1155Holder { uint256 premium, address initiator, bytes calldata data - ) external returns (bool) { + ) + external + returns (bool) + { require(initiator == address(this), NotAuthorized()); - ( - address excessReceiver, - bytes32[] memory commands, - bytes[] memory state - ) = abi.decode(data, (address, bytes32[], bytes[])); + (address excessReceiver, bytes32[] memory commands, bytes[] memory state) = + abi.decode(data, (address, bytes32[], bytes[])); this.execute(commands, state); @@ -210,11 +185,7 @@ contract EnsoFlashloanShortcuts is VM, ERC721Holder, ERC1155Holder { return true; } - function _returnExcessAssets( - IERC20 token, - uint256 flashloanAmount, - address receiver - ) private { + function _returnExcessAssets(IERC20 token, uint256 flashloanAmount, address receiver) private { uint256 flashloanAssetBalance = token.balanceOf(address(this)); if (flashloanAssetBalance > flashloanAmount) { uint256 excessAmount; @@ -225,5 +196,5 @@ contract EnsoFlashloanShortcuts is VM, ERC721Holder, ERC1155Holder { } } - receive() external payable {} + receive() external payable { } } diff --git a/test/EnsoFlashloanShortcuts.t.sol b/test/EnsoFlashloanShortcuts.t.sol index 67df643..8c9ac81 100644 --- a/test/EnsoFlashloanShortcuts.t.sol +++ b/test/EnsoFlashloanShortcuts.t.sol @@ -16,8 +16,8 @@ import "./mocks/MockVault.sol"; import "./utils/WeirollPlanner.sol"; -import {ERC1155Holder} from "openzeppelin-contracts/token/ERC1155/utils/ERC1155Holder.sol"; -import {ERC721Holder} from "openzeppelin-contracts/token/ERC721/utils/ERC721Holder.sol"; +import { ERC1155Holder } from "openzeppelin-contracts/token/ERC1155/utils/ERC1155Holder.sol"; +import { ERC721Holder } from "openzeppelin-contracts/token/ERC721/utils/ERC721Holder.sol"; interface IWETH { function deposit() external payable; @@ -53,9 +53,7 @@ contract EnsoFlashloanShortcutsTest is Test, ERC721Holder, ERC1155Holder { function testMorphoFlashloan() public { vm.selectFork(_ethereumFork); - IWETH token = IWETH( - address(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2) - ); + IWETH token = IWETH(address(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2)); uint256 amount = 10 ** 18; bytes32[] memory commands = new bytes32[](2); @@ -81,13 +79,7 @@ contract EnsoFlashloanShortcutsTest is Test, ERC721Holder, ERC1155Holder { bytes memory morphoData = abi.encode(morpho, token, amount); - shortcuts.flashLoan( - FlashloanProtocols.Morpho, - user_bob, - morphoData, - commands, - state - ); + shortcuts.flashLoan(FlashloanProtocols.Morpho, user_bob, morphoData, commands, state); } // Morpho test: @@ -98,9 +90,7 @@ contract EnsoFlashloanShortcutsTest is Test, ERC721Holder, ERC1155Holder { function testMorphoFlashloan_excess() public { vm.selectFork(_ethereumFork); - IWETH token = IWETH( - address(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2) - ); + IWETH token = IWETH(address(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2)); uint256 amount = 1 ether; bytes32[] memory commands = new bytes32[](2); @@ -126,19 +116,13 @@ contract EnsoFlashloanShortcutsTest is Test, ERC721Holder, ERC1155Holder { vm.startPrank(user_bob); vm.deal(user_bob, amount); - token.deposit{value: amount}(); + token.deposit{ value: amount }(); // Simulate that router transfers tokens to flashloan contract token.transfer(address(shortcuts), amount); uint256 balanceBefore = token.balanceOf(receiver); - shortcuts.flashLoan( - FlashloanProtocols.Morpho, - receiver, - morphoData, - commands, - state - ); + shortcuts.flashLoan(FlashloanProtocols.Morpho, receiver, morphoData, commands, state); uint256 balanceAfter = token.balanceOf(receiver); assertEq(balanceAfter - balanceBefore, amount); @@ -152,13 +136,9 @@ contract EnsoFlashloanShortcutsTest is Test, ERC721Holder, ERC1155Holder { vm.selectFork(_ethereumFork); // eWETH-2 Euler vault - address eulerVault = address( - 0xD8b27CF359b7D15710a5BE299AF6e7Bf904984C2 - ); + address eulerVault = address(0xD8b27CF359b7D15710a5BE299AF6e7Bf904984C2); - IWETH token = IWETH( - address(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2) - ); + IWETH token = IWETH(address(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2)); uint256 amount = 1 ether; bytes32[] memory commands = new bytes32[](2); @@ -184,13 +164,7 @@ contract EnsoFlashloanShortcutsTest is Test, ERC721Holder, ERC1155Holder { bytes memory eulerData = abi.encode(eulerVault, token, amount); - shortcuts.flashLoan( - FlashloanProtocols.Euler, - user_bob, - eulerData, - commands, - state - ); + shortcuts.flashLoan(FlashloanProtocols.Euler, user_bob, eulerData, commands, state); } // Euler test: @@ -202,13 +176,9 @@ contract EnsoFlashloanShortcutsTest is Test, ERC721Holder, ERC1155Holder { vm.selectFork(_ethereumFork); // eWETH-2 Euler vault - address eulerVault = address( - 0xD8b27CF359b7D15710a5BE299AF6e7Bf904984C2 - ); + address eulerVault = address(0xD8b27CF359b7D15710a5BE299AF6e7Bf904984C2); - IWETH token = IWETH( - address(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2) - ); + IWETH token = IWETH(address(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2)); uint256 amount = 1 ether; bytes32[] memory commands = new bytes32[](2); @@ -236,19 +206,13 @@ contract EnsoFlashloanShortcutsTest is Test, ERC721Holder, ERC1155Holder { vm.startPrank(user_bob); vm.deal(user_bob, amount); - token.deposit{value: amount}(); + token.deposit{ value: amount }(); // Simulate that router transfers tokens to flashloan contract token.transfer(address(shortcuts), amount); uint256 balanceBefore = token.balanceOf(receiver); - shortcuts.flashLoan( - FlashloanProtocols.Euler, - receiver, - eulerData, - commands, - state - ); + shortcuts.flashLoan(FlashloanProtocols.Euler, receiver, eulerData, commands, state); uint256 balanceAfter = token.balanceOf(receiver); @@ -262,9 +226,7 @@ contract EnsoFlashloanShortcutsTest is Test, ERC721Holder, ERC1155Holder { function testBalancerV2Flashloan() public { vm.selectFork(_ethereumFork); - address balancerV2Vault = address( - 0xBA12222222228d8Ba445958a75a0704d566BF2C8 - ); + address balancerV2Vault = address(0xBA12222222228d8Ba445958a75a0704d566BF2C8); IWETH weth = IWETH(address(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2)); uint256 wethAmount = 1 ether; @@ -295,19 +257,9 @@ contract EnsoFlashloanShortcutsTest is Test, ERC721Holder, ERC1155Holder { tokens[0] = address(weth); amounts[0] = wethAmount; - bytes memory balancerV2Data = abi.encode( - balancerV2Vault, - tokens, - amounts - ); + bytes memory balancerV2Data = abi.encode(balancerV2Vault, tokens, amounts); - shortcuts.flashLoan( - FlashloanProtocols.BalancerV2, - user_bob, - balancerV2Data, - commands, - state - ); + shortcuts.flashLoan(FlashloanProtocols.BalancerV2, user_bob, balancerV2Data, commands, state); } // BalancerV2 test: @@ -317,9 +269,7 @@ contract EnsoFlashloanShortcutsTest is Test, ERC721Holder, ERC1155Holder { function testBalancerV2Flashloan_multipleAssets() public { vm.selectFork(_ethereumFork); - address balancerV2Vault = address( - 0xBA12222222228d8Ba445958a75a0704d566BF2C8 - ); + address balancerV2Vault = address(0xBA12222222228d8Ba445958a75a0704d566BF2C8); IWETH weth = IWETH(address(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2)); uint256 wethAmount = 1 ether; @@ -357,27 +307,15 @@ contract EnsoFlashloanShortcutsTest is Test, ERC721Holder, ERC1155Holder { tokens[1] = address(weth); amounts[1] = wethAmount; - bytes memory balancerV2Data = abi.encode( - balancerV2Vault, - tokens, - amounts - ); + bytes memory balancerV2Data = abi.encode(balancerV2Vault, tokens, amounts); - shortcuts.flashLoan( - FlashloanProtocols.BalancerV2, - user_bob, - balancerV2Data, - commands, - state - ); + shortcuts.flashLoan(FlashloanProtocols.BalancerV2, user_bob, balancerV2Data, commands, state); } function testBalancerV2Flashloan_excess() public { vm.selectFork(_ethereumFork); - address balancerV2Vault = address( - 0xBA12222222228d8Ba445958a75a0704d566BF2C8 - ); + address balancerV2Vault = address(0xBA12222222228d8Ba445958a75a0704d566BF2C8); IWETH weth = IWETH(address(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2)); uint256 wethAmount = 1 ether; @@ -409,27 +347,17 @@ contract EnsoFlashloanShortcutsTest is Test, ERC721Holder, ERC1155Holder { tokens[0] = address(weth); amounts[0] = wethAmount; - bytes memory balancerV2Data = abi.encode( - balancerV2Vault, - tokens, - amounts - ); + bytes memory balancerV2Data = abi.encode(balancerV2Vault, tokens, amounts); vm.startPrank(user_bob); vm.deal(user_bob, wethAmount); - weth.deposit{value: wethAmount}(); + weth.deposit{ value: wethAmount }(); // Simulate that router transfers tokens to flashloan contract weth.transfer(address(shortcuts), wethAmount); uint256 balanceBefore = weth.balanceOf(receiver); - shortcuts.flashLoan( - FlashloanProtocols.BalancerV2, - receiver, - balancerV2Data, - commands, - state - ); + shortcuts.flashLoan(FlashloanProtocols.BalancerV2, receiver, balancerV2Data, commands, state); uint256 balanceAfter = weth.balanceOf(receiver); assertEq(balanceAfter - balanceBefore, wethAmount); @@ -438,12 +366,8 @@ contract EnsoFlashloanShortcutsTest is Test, ERC721Holder, ERC1155Holder { function testAaveV3FlashLoan() public { vm.selectFork(_ethereumFork); - IAaveV3Pool aaveV3Pool = IAaveV3Pool( - address(0x87870Bca3F3fD6335C3F4ce8392D69350B4fA4E2) - ); - IWETH token = IWETH( - address(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2) - ); + IAaveV3Pool aaveV3Pool = IAaveV3Pool(address(0x87870Bca3F3fD6335C3F4ce8392D69350B4fA4E2)); + IWETH token = IWETH(address(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2)); uint256 amount = 1 ether; uint256 BPS = 10_000; @@ -476,15 +400,9 @@ contract EnsoFlashloanShortcutsTest is Test, ERC721Holder, ERC1155Holder { // for the flashloan with fee vm.startPrank(user_bob); vm.deal(user_bob, amount); - token.deposit{value: fee}(); + token.deposit{ value: fee }(); token.transfer(address(shortcuts), fee); - shortcuts.flashLoan( - FlashloanProtocols.AaveV3, - user_bob, - aaveData, - commands, - state - ); + shortcuts.flashLoan(FlashloanProtocols.AaveV3, user_bob, aaveData, commands, state); } }