feat: bridges, PMM, flash workflow, token-aggregation, and deployment docs
- 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
This commit is contained in:
@@ -0,0 +1,77 @@
|
||||
// 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/IBalancerVault.sol";
|
||||
import "../RouteTypesV2.sol";
|
||||
import "../interfaces/IRouteExecutorAdapter.sol";
|
||||
|
||||
contract BalancerRouteExecutorAdapter 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.Balancer) {
|
||||
return (false, "BalancerRouteExecutorAdapter: invalid provider");
|
||||
}
|
||||
if (leg.target == address(0)) {
|
||||
return (false, "BalancerRouteExecutorAdapter: zero target");
|
||||
}
|
||||
if (leg.providerData.length != 32) {
|
||||
return (false, "BalancerRouteExecutorAdapter: invalid providerData");
|
||||
}
|
||||
return (true, "");
|
||||
}
|
||||
|
||||
function quote(
|
||||
RouteTypesV2.RouteLeg calldata leg,
|
||||
uint256 amountIn
|
||||
) external view override returns (uint256 amountOut, uint256 gasEstimate) {
|
||||
bytes32 poolId = abi.decode(leg.providerData, (bytes32));
|
||||
(address[] memory tokens, uint256[] memory balances,) = IBalancerVault(leg.target).getPoolTokens(poolId);
|
||||
uint256 inIndex = type(uint256).max;
|
||||
uint256 outIndex = type(uint256).max;
|
||||
|
||||
for (uint256 i = 0; i < tokens.length; i++) {
|
||||
if (tokens[i] == leg.tokenIn) inIndex = i;
|
||||
if (tokens[i] == leg.tokenOut) outIndex = i;
|
||||
}
|
||||
|
||||
if (inIndex != type(uint256).max && outIndex != type(uint256).max && balances[inIndex] > 0) {
|
||||
uint256 grossOut = (amountIn * balances[outIndex]) / balances[inIndex];
|
||||
amountOut = (grossOut * 9950) / 10000;
|
||||
}
|
||||
|
||||
gasEstimate = 230000;
|
||||
}
|
||||
|
||||
function execute(
|
||||
RouteTypesV2.RouteLeg calldata leg,
|
||||
uint256 amountIn
|
||||
) external override returns (uint256 amountOut) {
|
||||
bytes32 poolId = abi.decode(leg.providerData, (bytes32));
|
||||
IERC20(leg.tokenIn).forceApprove(leg.target, 0);
|
||||
IERC20(leg.tokenIn).forceApprove(leg.target, amountIn);
|
||||
|
||||
amountOut = IBalancerVault(leg.target).swap(
|
||||
IBalancerVault.SingleSwap({
|
||||
poolId: poolId,
|
||||
kind: IBalancerVault.SwapKind.GIVEN_IN,
|
||||
assetIn: leg.tokenIn,
|
||||
assetOut: leg.tokenOut,
|
||||
amount: amountIn,
|
||||
userData: ""
|
||||
}),
|
||||
IBalancerVault.FundManagement({
|
||||
sender: address(this),
|
||||
fromInternalBalance: false,
|
||||
recipient: payable(msg.sender),
|
||||
toInternalBalance: false
|
||||
}),
|
||||
leg.minAmountOut,
|
||||
block.timestamp + 300
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user