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.
This commit is contained in:
137
contracts/bridge/TwoWayTokenBridgeL2.sol
Normal file
137
contracts/bridge/TwoWayTokenBridgeL2.sol
Normal file
@@ -0,0 +1,137 @@
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user