Files
smom-dbis-138/contracts/bridge/TwoWayTokenBridgeL2.sol
defiQUG 1fb7266469 Add Oracle Aggregator and CCIP Integration
- Introduced Aggregator.sol for Chainlink-compatible oracle functionality, including round-based updates and access control.
- Added OracleWithCCIP.sol to extend Aggregator with CCIP cross-chain messaging capabilities.
- Created .gitmodules to include OpenZeppelin contracts as a submodule.
- Developed a comprehensive deployment guide in NEXT_STEPS_COMPLETE_GUIDE.md for Phase 2 and smart contract deployment.
- Implemented Vite configuration for the orchestration portal, supporting both Vue and React frameworks.
- Added server-side logic for the Multi-Cloud Orchestration Portal, including API endpoints for environment management and monitoring.
- Created scripts for resource import and usage validation across non-US regions.
- Added tests for CCIP error handling and integration to ensure robust functionality.
- Included various new files and directories for the orchestration portal and deployment scripts.
2025-12-12 14:57:48 -08:00

138 lines
5.2 KiB
Solidity

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "../ccip/IRouterClient.sol";
interface IMintableERC20 {
function mint(address to, uint256 amount) external;
function burnFrom(address from, uint256 amount) external;
function balanceOf(address account) external view returns (uint256);
}
/**
* @title TwoWayTokenBridgeL2
* @notice L2/secondary chain side: mints mirrored tokens on inbound and burns on outbound
*/
contract TwoWayTokenBridgeL2 {
IRouterClient public immutable ccipRouter;
address public immutable mirroredToken;
address public feeToken; // LINK
address public admin;
struct DestinationConfig {
uint64 chainSelector;
address l1Bridge;
bool enabled;
}
mapping(uint64 => DestinationConfig) public destinations;
uint64[] public destinationChains;
mapping(bytes32 => bool) public processed;
event Minted(address indexed recipient, uint256 amount);
event Burned(address indexed user, uint256 amount);
event CcipSend(bytes32 indexed messageId, uint64 destChain, address recipient, uint256 amount);
event DestinationAdded(uint64 chainSelector, address l1Bridge);
event DestinationUpdated(uint64 chainSelector, address l1Bridge);
event DestinationRemoved(uint64 chainSelector);
modifier onlyAdmin() {
require(msg.sender == admin, "only admin");
_;
}
modifier onlyRouter() {
require(msg.sender == address(ccipRouter), "only router");
_;
}
constructor(address _router, address _token, address _feeToken) {
require(_router != address(0) && _token != address(0) && _feeToken != address(0), "zero addr");
ccipRouter = IRouterClient(_router);
mirroredToken = _token;
feeToken = _feeToken;
admin = msg.sender;
}
function addDestination(uint64 chainSelector, address l1Bridge) external onlyAdmin {
require(l1Bridge != address(0), "zero l1");
require(!destinations[chainSelector].enabled, "exists");
destinations[chainSelector] = DestinationConfig(chainSelector, l1Bridge, true);
destinationChains.push(chainSelector);
emit DestinationAdded(chainSelector, l1Bridge);
}
function updateDestination(uint64 chainSelector, address l1Bridge) external onlyAdmin {
require(destinations[chainSelector].enabled, "missing");
require(l1Bridge != address(0), "zero l1");
destinations[chainSelector].l1Bridge = l1Bridge;
emit DestinationUpdated(chainSelector, l1Bridge);
}
function removeDestination(uint64 chainSelector) external onlyAdmin {
require(destinations[chainSelector].enabled, "missing");
destinations[chainSelector].enabled = false;
for (uint256 i = 0; i < destinationChains.length; i++) {
if (destinationChains[i] == chainSelector) {
destinationChains[i] = destinationChains[destinationChains.length - 1];
destinationChains.pop();
break;
}
}
emit DestinationRemoved(chainSelector);
}
function updateFeeToken(address newFee) external onlyAdmin {
require(newFee != address(0), "zero");
feeToken = newFee;
}
function changeAdmin(address newAdmin) external onlyAdmin {
require(newAdmin != address(0), "zero");
admin = newAdmin;
}
function getDestinationChains() external view returns (uint64[] memory) {
return destinationChains;
}
// Inbound from L1: mint mirrored tokens to recipient
function ccipReceive(IRouterClient.Any2EVMMessage calldata message) external onlyRouter {
require(!processed[message.messageId], "replayed");
processed[message.messageId] = true;
(address recipient, uint256 amount) = abi.decode(message.data, (address, uint256));
require(recipient != address(0) && amount > 0, "bad msg");
IMintableERC20(mirroredToken).mint(recipient, amount);
emit Minted(recipient, amount);
}
// Outbound to L1: burn mirrored tokens and signal release on L1
function burnAndSend(uint64 destSelector, address recipient, uint256 amount) external returns (bytes32 messageId) {
require(amount > 0 && recipient != address(0), "bad args");
DestinationConfig memory dest = destinations[destSelector];
require(dest.enabled, "dest disabled");
IMintableERC20(mirroredToken).burnFrom(msg.sender, amount);
emit Burned(msg.sender, amount);
bytes memory data = abi.encode(recipient, amount);
IRouterClient.EVM2AnyMessage memory m = IRouterClient.EVM2AnyMessage({
receiver: abi.encode(dest.l1Bridge),
data: data,
tokenAmounts: new IRouterClient.TokenAmount[](0),
feeToken: feeToken,
extraArgs: ""
});
uint256 fee = ccipRouter.getFee(destSelector, m);
if (fee > 0) {
require(IERC20(feeToken).approve(address(ccipRouter), fee), "fee approve");
}
(messageId, ) = ccipRouter.ccipSend(destSelector, m);
emit CcipSend(messageId, destSelector, recipient, amount);
return messageId;
}
}