feat: add mav executor
This commit is contained in:
93
foundry/src/executors/MaverickV2Executor.sol
Normal file
93
foundry/src/executors/MaverickV2Executor.sol
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
// SPDX-License-Identifier: BUSL-1.1
|
||||||
|
pragma solidity ^0.8.26;
|
||||||
|
|
||||||
|
import "@interfaces/IExecutor.sol";
|
||||||
|
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
|
||||||
|
|
||||||
|
error MaverickV2Executor__InvalidDataLength();
|
||||||
|
error MaverickV2Executor__InvalidTarget();
|
||||||
|
error MaverickV2Executor__InvalidFactory();
|
||||||
|
|
||||||
|
contract MaverickV2Executor is IExecutor {
|
||||||
|
using SafeERC20 for IERC20;
|
||||||
|
|
||||||
|
address public immutable factory;
|
||||||
|
address private immutable self;
|
||||||
|
|
||||||
|
constructor(address _factory) {
|
||||||
|
if (_factory == address(0)) {
|
||||||
|
revert MaverickV2Executor__InvalidFactory();
|
||||||
|
}
|
||||||
|
factory = _factory;
|
||||||
|
self = address(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
// slither-disable-next-line locked-ether
|
||||||
|
function swap(uint256 givenAmount, bytes calldata data)
|
||||||
|
external
|
||||||
|
payable
|
||||||
|
returns (uint256 calculatedAmount)
|
||||||
|
{
|
||||||
|
address target;
|
||||||
|
address receiver;
|
||||||
|
IERC20 tokenIn;
|
||||||
|
|
||||||
|
(tokenIn, target, receiver) = _decodeData(data);
|
||||||
|
|
||||||
|
_verifyPairAddress(target);
|
||||||
|
IMaverickV2Pool pool = IMaverickV2Pool(target);
|
||||||
|
|
||||||
|
bool isTokenAIn = pool.tokenA() == tokenIn;
|
||||||
|
int32 tickLimit = isTokenAIn ? type(int32).max : type(int32).min;
|
||||||
|
IMaverickV2Pool.SwapParams memory swapParams = IMaverickV2Pool
|
||||||
|
.SwapParams({
|
||||||
|
amount: givenAmount,
|
||||||
|
tokenAIn: isTokenAIn,
|
||||||
|
exactOutput: false,
|
||||||
|
tickLimit: tickLimit
|
||||||
|
});
|
||||||
|
IERC20(tokenIn).safeTransfer(target, givenAmount);
|
||||||
|
(, calculatedAmount) = pool.swap(receiver, swapParams, "");
|
||||||
|
}
|
||||||
|
|
||||||
|
function _decodeData(bytes calldata data)
|
||||||
|
internal
|
||||||
|
pure
|
||||||
|
returns (IERC20 inToken, address target, address receiver)
|
||||||
|
{
|
||||||
|
if (data.length != 60) {
|
||||||
|
revert MaverickV2Executor__InvalidDataLength();
|
||||||
|
}
|
||||||
|
inToken = IERC20(address(bytes20(data[0:20])));
|
||||||
|
target = address(bytes20(data[20:40]));
|
||||||
|
receiver = address(bytes20(data[40:60]));
|
||||||
|
}
|
||||||
|
|
||||||
|
function _verifyPairAddress(address target) internal view {
|
||||||
|
if (!IMaverickV2Factory(factory).isFactoryPool(IMaverickV2Pool(target))) {
|
||||||
|
revert MaverickV2Executor__InvalidTarget();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IMaverickV2Factory {
|
||||||
|
function isFactoryPool(IMaverickV2Pool pool) external view returns (bool);
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IMaverickV2Pool {
|
||||||
|
struct SwapParams {
|
||||||
|
uint256 amount;
|
||||||
|
bool tokenAIn;
|
||||||
|
bool exactOutput;
|
||||||
|
int32 tickLimit;
|
||||||
|
}
|
||||||
|
|
||||||
|
function swap(
|
||||||
|
address recipient,
|
||||||
|
SwapParams memory params,
|
||||||
|
bytes calldata data
|
||||||
|
) external returns (uint256 amountIn, uint256 amountOut);
|
||||||
|
|
||||||
|
function tokenA() external view returns (IERC20);
|
||||||
|
function tokenB() external view returns (IERC20);
|
||||||
|
}
|
||||||
@@ -56,6 +56,12 @@ contract Constants is Test, BaseConstants {
|
|||||||
address WSTTAO_ADDR = address(0xe9633C52f4c8B7BDeb08c4A7fE8a5c1B84AFCf67);
|
address WSTTAO_ADDR = address(0xe9633C52f4c8B7BDeb08c4A7fE8a5c1B84AFCf67);
|
||||||
address WTAO_ADDR = address(0x77E06c9eCCf2E797fd462A92B6D7642EF85b0A44);
|
address WTAO_ADDR = address(0x77E06c9eCCf2E797fd462A92B6D7642EF85b0A44);
|
||||||
address BSGG_ADDR = address(0xdA16Cf041E2780618c49Dbae5d734B89a6Bac9b3);
|
address BSGG_ADDR = address(0xdA16Cf041E2780618c49Dbae5d734B89a6Bac9b3);
|
||||||
|
address GHO_ADDR = address(0x40D16FC0246aD3160Ccc09B8D0D3A2cD28aE6C2f);
|
||||||
|
|
||||||
|
// Maverick v2
|
||||||
|
address MAVERICK_V2_FACTORY = 0x0A7e848Aca42d879EF06507Fca0E7b33A0a63c1e;
|
||||||
|
address GHO_USDC_POOL = 0x14Cf6D2Fe3E1B326114b07d22A6F6bb59e346c67;
|
||||||
|
|
||||||
// Uniswap v2
|
// Uniswap v2
|
||||||
address WETH_DAI_POOL = 0xA478c2975Ab1Ea89e8196811F51A7B7Ade33eB11;
|
address WETH_DAI_POOL = 0xA478c2975Ab1Ea89e8196811F51A7B7Ade33eB11;
|
||||||
address DAI_USDC_POOL = 0xAE461cA67B15dc8dc81CE7615e0320dA1A9aB8D5;
|
address DAI_USDC_POOL = 0xAE461cA67B15dc8dc81CE7615e0320dA1A9aB8D5;
|
||||||
|
|||||||
77
foundry/test/executors/MaverickV2Executor.t.sol
Normal file
77
foundry/test/executors/MaverickV2Executor.t.sol
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
// SPDX-License-Identifier: BUSL-1.1
|
||||||
|
pragma solidity ^0.8.26;
|
||||||
|
|
||||||
|
import "@src/executors/MaverickV2Executor.sol";
|
||||||
|
import {Test} from "../../lib/forge-std/src/Test.sol";
|
||||||
|
import {Constants} from "../Constants.sol";
|
||||||
|
|
||||||
|
contract MaverickV2ExecutorExposed is MaverickV2Executor {
|
||||||
|
constructor(address _factory) MaverickV2Executor(_factory) {}
|
||||||
|
function decodeParams(bytes calldata data)
|
||||||
|
external
|
||||||
|
pure
|
||||||
|
returns (
|
||||||
|
IERC20 tokenIn,
|
||||||
|
address target,
|
||||||
|
address receiver
|
||||||
|
)
|
||||||
|
{
|
||||||
|
return _decodeData(data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
contract MaverickV2ExecutorTest is
|
||||||
|
Test,
|
||||||
|
Constants
|
||||||
|
{
|
||||||
|
using SafeERC20 for IERC20;
|
||||||
|
|
||||||
|
MaverickV2ExecutorExposed maverickV2Exposed;
|
||||||
|
IERC20 GHO = IERC20(GHO_ADDR);
|
||||||
|
IERC20 USDC = IERC20(USDC_ADDR);
|
||||||
|
|
||||||
|
function setUp() public {
|
||||||
|
uint256 forkBlock = 20127232;
|
||||||
|
vm.createSelectFork(vm.rpcUrl("mainnet"), forkBlock);
|
||||||
|
maverickV2Exposed = new MaverickV2ExecutorExposed(MAVERICK_V2_FACTORY);
|
||||||
|
}
|
||||||
|
|
||||||
|
function testDecodeParams() public view {
|
||||||
|
bytes memory params = abi.encodePacked(
|
||||||
|
GHO_ADDR, GHO_USDC_POOL, address(2)
|
||||||
|
);
|
||||||
|
|
||||||
|
(
|
||||||
|
IERC20 tokenIn,
|
||||||
|
address target,
|
||||||
|
address receiver
|
||||||
|
) = maverickV2Exposed.decodeParams(params);
|
||||||
|
|
||||||
|
assertEq(address(tokenIn), GHO_ADDR);
|
||||||
|
assertEq(target, GHO_USDC_POOL);
|
||||||
|
assertEq(receiver, address(2));
|
||||||
|
}
|
||||||
|
|
||||||
|
function testDecodeParamsInvalidDataLength() public {
|
||||||
|
bytes memory invalidParams =
|
||||||
|
abi.encodePacked(GHO_ADDR, GHO_USDC_POOL, address(2), true);
|
||||||
|
|
||||||
|
vm.expectRevert(MaverickV2Executor__InvalidDataLength.selector);
|
||||||
|
maverickV2Exposed.decodeParams(invalidParams);
|
||||||
|
}
|
||||||
|
|
||||||
|
function testSwap() public {
|
||||||
|
uint256 amountIn = 10 ** 18;
|
||||||
|
bytes memory protocolData =
|
||||||
|
abi.encodePacked(GHO_ADDR, GHO_USDC_POOL, address(2));
|
||||||
|
|
||||||
|
deal(GHO_ADDR, address(maverickV2Exposed), amountIn);
|
||||||
|
uint256 balanceBefore = GHO.balanceOf(BOB);
|
||||||
|
|
||||||
|
uint256 amountOut = maverickV2Exposed.swap(amountIn, protocolData);
|
||||||
|
|
||||||
|
uint256 balanceAfter = GHO.balanceOf(BOB);
|
||||||
|
assertGt(balanceAfter, balanceBefore);
|
||||||
|
assertEq(balanceAfter - balanceBefore, amountOut);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user