- 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
127 lines
4.8 KiB
Solidity
127 lines
4.8 KiB
Solidity
// SPDX-License-Identifier: MIT
|
|
pragma solidity ^0.8.19;
|
|
|
|
import "@openzeppelin/contracts/access/AccessControl.sol";
|
|
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
|
|
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
|
|
import "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
|
|
import "./RouteTypesV2.sol";
|
|
import "./interfaces/IEnhancedSwapRouterV2.sol";
|
|
import "./interfaces/IBridgeIntentExecutor.sol";
|
|
|
|
contract IntentBridgeCoordinatorV2 is AccessControl, ReentrancyGuard {
|
|
using SafeERC20 for IERC20;
|
|
|
|
bytes32 public constant EXECUTOR_MANAGER_ROLE = keccak256("EXECUTOR_MANAGER_ROLE");
|
|
|
|
IEnhancedSwapRouterV2 public immutable swapRouter;
|
|
mapping(bytes32 => address) public bridgeExecutors;
|
|
|
|
event BridgeExecutorSet(bytes32 indexed bridgeType, address indexed executor);
|
|
event IntentExecuted(
|
|
bytes32 indexed intentHash,
|
|
bytes32 indexed bridgeReference,
|
|
bytes32 indexed destinationPlanHash,
|
|
address inputToken,
|
|
address bridgeToken,
|
|
uint256 amountIn,
|
|
uint256 bridgedAmount,
|
|
address recipient
|
|
);
|
|
|
|
error ZeroAddress();
|
|
error InvalidIntent();
|
|
error BridgeExecutorNotConfigured();
|
|
error BridgeValidationFailed(string reason);
|
|
|
|
constructor(address _swapRouter) {
|
|
if (_swapRouter == address(0)) revert ZeroAddress();
|
|
_grantRole(DEFAULT_ADMIN_ROLE, msg.sender);
|
|
_grantRole(EXECUTOR_MANAGER_ROLE, msg.sender);
|
|
swapRouter = IEnhancedSwapRouterV2(_swapRouter);
|
|
}
|
|
|
|
function setBridgeExecutor(
|
|
bytes32 bridgeType,
|
|
address executor
|
|
) external onlyRole(EXECUTOR_MANAGER_ROLE) {
|
|
if (executor == address(0)) revert ZeroAddress();
|
|
bridgeExecutors[bridgeType] = executor;
|
|
emit BridgeExecutorSet(bridgeType, executor);
|
|
}
|
|
|
|
function executeIntent(
|
|
RouteTypesV2.BridgeIntentPlan calldata intent
|
|
) external payable nonReentrant returns (bytes32 bridgeReference, bytes32 destinationPlanHash) {
|
|
if (intent.deadline < block.timestamp) revert InvalidIntent();
|
|
if (intent.recipient == address(0)) revert InvalidIntent();
|
|
if (intent.sourcePlan.deadline < block.timestamp) revert InvalidIntent();
|
|
if (intent.sourcePlan.recipient != address(this)) revert InvalidIntent();
|
|
|
|
address bridgeExecutor = bridgeExecutors[intent.bridgeType];
|
|
if (bridgeExecutor == address(0)) revert BridgeExecutorNotConfigured();
|
|
|
|
uint256 bridgedAmount;
|
|
address bridgeToken;
|
|
if (intent.sourcePlan.inputToken == address(0)) {
|
|
bridgedAmount = swapRouter.executeRoute{value: intent.sourcePlan.amountIn}(intent.sourcePlan);
|
|
bridgeToken = intent.sourcePlan.outputToken;
|
|
} else {
|
|
IERC20(intent.sourcePlan.inputToken).safeTransferFrom(
|
|
msg.sender,
|
|
address(this),
|
|
intent.sourcePlan.amountIn
|
|
);
|
|
IERC20(intent.sourcePlan.inputToken).forceApprove(address(swapRouter), 0);
|
|
IERC20(intent.sourcePlan.inputToken).forceApprove(address(swapRouter), intent.sourcePlan.amountIn);
|
|
bridgedAmount = swapRouter.executeRoute(intent.sourcePlan);
|
|
bridgeToken = intent.sourcePlan.outputToken;
|
|
}
|
|
|
|
(bool ok, string memory reason) = IBridgeIntentExecutor(bridgeExecutor).validateBridge(
|
|
intent.bridgeType,
|
|
intent.bridgeData,
|
|
bridgeToken,
|
|
bridgedAmount,
|
|
intent.recipient
|
|
);
|
|
if (!ok) revert BridgeValidationFailed(reason);
|
|
|
|
IERC20(bridgeToken).forceApprove(bridgeExecutor, 0);
|
|
IERC20(bridgeToken).forceApprove(bridgeExecutor, bridgedAmount);
|
|
|
|
bridgeReference = IBridgeIntentExecutor(bridgeExecutor).executeBridge(
|
|
intent.bridgeType,
|
|
intent.bridgeData,
|
|
bridgeToken,
|
|
bridgedAmount,
|
|
intent.recipient
|
|
);
|
|
destinationPlanHash = keccak256(
|
|
abi.encode(
|
|
intent.destinationPlan.chainId,
|
|
intent.destinationPlan.inputToken,
|
|
intent.destinationPlan.outputToken,
|
|
intent.destinationPlan.amountIn,
|
|
intent.destinationPlan.minAmountOut,
|
|
intent.destinationPlan.recipient,
|
|
intent.destinationPlan.deadline,
|
|
intent.destinationPlan.legs
|
|
)
|
|
);
|
|
|
|
emit IntentExecuted(
|
|
keccak256(abi.encode(intent.bridgeType, intent.recipient, intent.deadline)),
|
|
bridgeReference,
|
|
destinationPlanHash,
|
|
intent.sourcePlan.inputToken == address(0) ? address(0) : intent.sourcePlan.inputToken,
|
|
bridgeToken,
|
|
intent.sourcePlan.amountIn,
|
|
bridgedAmount,
|
|
intent.recipient
|
|
);
|
|
}
|
|
|
|
receive() external payable {}
|
|
}
|