- CCIP/trustless bridge contracts, GRU tokens, DEX/PMM tests, reserve vault. - Token-aggregation service routes, planner, chain config, relay env templates. - Config snapshots and multi-chain deployment markdown updates. - gitignore services/btc-intake/dist/ (tsc output); do not track dist. Run forge build && forge test before deploy (large solc graph). Made-with: Cursor
98 lines
3.4 KiB
Solidity
98 lines
3.4 KiB
Solidity
// SPDX-License-Identifier: MIT
|
|
pragma solidity ^0.8.19;
|
|
|
|
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
|
|
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
|
|
import "../interfaces/ISwapRouter.sol";
|
|
import "../RouteTypesV2.sol";
|
|
import "../interfaces/IRouteExecutorAdapter.sol";
|
|
|
|
contract UniswapV3RouteExecutorAdapter is IRouteExecutorAdapter {
|
|
using SafeERC20 for IERC20;
|
|
|
|
function validate(
|
|
RouteTypesV2.RouteLeg calldata leg
|
|
) external pure override returns (bool ok, string memory reason) {
|
|
if (leg.provider != RouteTypesV2.Provider.UniswapV3) {
|
|
return (false, "UniswapV3RouteExecutorAdapter: invalid provider");
|
|
}
|
|
if (leg.target == address(0)) {
|
|
return (false, "UniswapV3RouteExecutorAdapter: zero target");
|
|
}
|
|
if (leg.providerData.length == 0) {
|
|
return (false, "UniswapV3RouteExecutorAdapter: missing providerData");
|
|
}
|
|
return (true, "");
|
|
}
|
|
|
|
function quote(
|
|
RouteTypesV2.RouteLeg calldata leg,
|
|
uint256 amountIn
|
|
) external view override returns (uint256 amountOut, uint256 gasEstimate) {
|
|
(bytes memory path, uint24 fee, address quoter, bool usePath) =
|
|
abi.decode(leg.providerData, (bytes, uint24, address, bool));
|
|
|
|
if (quoter != address(0)) {
|
|
bytes memory data;
|
|
bool ok;
|
|
if (usePath) {
|
|
(ok, data) = quoter.staticcall(
|
|
abi.encodeWithSignature("quoteExactInput(bytes,uint256)", path, amountIn)
|
|
);
|
|
} else {
|
|
(ok, data) = quoter.staticcall(
|
|
abi.encodeWithSignature(
|
|
"quoteExactInputSingle(address,address,uint24,uint256,uint160)",
|
|
leg.tokenIn,
|
|
leg.tokenOut,
|
|
fee,
|
|
amountIn,
|
|
uint160(0)
|
|
)
|
|
);
|
|
}
|
|
if (ok && data.length >= 32) {
|
|
amountOut = abi.decode(data, (uint256));
|
|
}
|
|
}
|
|
|
|
gasEstimate = usePath ? 220000 : 175000;
|
|
}
|
|
|
|
function execute(
|
|
RouteTypesV2.RouteLeg calldata leg,
|
|
uint256 amountIn
|
|
) external override returns (uint256 amountOut) {
|
|
(bytes memory path, uint24 fee,, bool usePath) =
|
|
abi.decode(leg.providerData, (bytes, uint24, address, bool));
|
|
|
|
IERC20(leg.tokenIn).forceApprove(leg.target, 0);
|
|
IERC20(leg.tokenIn).forceApprove(leg.target, amountIn);
|
|
|
|
if (usePath) {
|
|
amountOut = ISwapRouter(leg.target).exactInput(
|
|
ISwapRouter.ExactInputParams({
|
|
path: path,
|
|
recipient: msg.sender,
|
|
deadline: block.timestamp + 300,
|
|
amountIn: amountIn,
|
|
amountOutMinimum: leg.minAmountOut
|
|
})
|
|
);
|
|
} else {
|
|
amountOut = ISwapRouter(leg.target).exactInputSingle(
|
|
ISwapRouter.ExactInputSingleParams({
|
|
tokenIn: leg.tokenIn,
|
|
tokenOut: leg.tokenOut,
|
|
fee: fee,
|
|
recipient: msg.sender,
|
|
deadline: block.timestamp + 300,
|
|
amountIn: amountIn,
|
|
amountOutMinimum: leg.minAmountOut,
|
|
sqrtPriceLimitX96: 0
|
|
})
|
|
);
|
|
}
|
|
}
|
|
}
|