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 WTAO_ADDR = address(0x77E06c9eCCf2E797fd462A92B6D7642EF85b0A44);
|
||||
address BSGG_ADDR = address(0xdA16Cf041E2780618c49Dbae5d734B89a6Bac9b3);
|
||||
address GHO_ADDR = address(0x40D16FC0246aD3160Ccc09B8D0D3A2cD28aE6C2f);
|
||||
|
||||
// Maverick v2
|
||||
address MAVERICK_V2_FACTORY = 0x0A7e848Aca42d879EF06507Fca0E7b33A0a63c1e;
|
||||
address GHO_USDC_POOL = 0x14Cf6D2Fe3E1B326114b07d22A6F6bb59e346c67;
|
||||
|
||||
// Uniswap v2
|
||||
address WETH_DAI_POOL = 0xA478c2975Ab1Ea89e8196811F51A7B7Ade33eB11;
|
||||
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