- 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.
138 lines
5.2 KiB
Solidity
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;
|
|
}
|
|
}
|
|
|
|
|