Files
smom-dbis-138/contracts/bridge/ZedxionCustomBridge.sol

218 lines
7.3 KiB
Solidity

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
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 "./interfaces/IZedxionTransport.sol";
interface ICWMintBurnToken is IERC20 {
function mint(address to, uint256 amount) external;
function burnFrom(address from, uint256 amount) external;
}
/**
* @title ZedxionCustomBridge
* @notice GRU custom transport for Chain 138 ↔ ZEDXION (83872). No CCIP.
* @dev Deploy at same address on 138 and 83872 via CREATE2 (salt keccak256("ZedxionCustomBridge")).
* On 138: lock canonical c* and emit LockForZedxion.
* On 83872: relayer calls releaseOnZedxion to mint cW* or transfer escrowed assets.
* Reverse: lock on 83872 (LockFor138), relayer completes releaseOn138 on 138.
*/
contract ZedxionCustomBridge is IZedxionTransport, AccessControl, ReentrancyGuard {
using SafeERC20 for IERC20;
bytes32 public constant RELAYER_ROLE = keccak256("RELAYER_ROLE");
uint256 public constant ZEDXION_CHAIN_ID = 83872;
uint256 public constant HUB_CHAIN_ID = 138;
mapping(bytes32 => LockRecord) public locks;
mapping(bytes32 => bool) public releasedOnDestination;
mapping(address => address) public canonicalToMirrored;
mapping(address => address) public mirroredToCanonical;
mapping(address => bool) public mintableMirroredToken;
mapping(address => uint256) public nonces;
bool private _hasRelayer;
struct LockRecord {
address sender;
address token;
uint256 amount;
address recipient;
uint256 createdAt;
bool released;
}
event LockForZedxion(
bytes32 indexed requestId,
address indexed sender,
address indexed token,
uint256 amount,
address recipient,
uint256 sourceChainId
);
event UnlockOnZedxion(
bytes32 indexed requestId,
address indexed recipient,
address indexed token,
uint256 amount
);
event LockFor138(
bytes32 indexed requestId,
address indexed sender,
address indexed token,
uint256 amount,
address recipient,
uint256 sourceChainId
);
event UnlockOn138(
bytes32 indexed requestId,
address indexed recipient,
address indexed token,
uint256 amount
);
event TokenPairConfigured(address indexed canonicalToken, address indexed mirroredToken);
constructor(address admin) {
_grantRole(DEFAULT_ADMIN_ROLE, admin);
_grantRole(RELAYER_ROLE, admin);
_hasRelayer = true;
}
function zedxionChainId() external pure override returns (uint256) {
return ZEDXION_CHAIN_ID;
}
function configureTokenPair(address canonicalToken, address mirroredToken) external onlyRole(DEFAULT_ADMIN_ROLE) {
require(canonicalToken != address(0) && mirroredToken != address(0), "zero token");
canonicalToMirrored[canonicalToken] = mirroredToken;
mirroredToCanonical[mirroredToken] = canonicalToken;
emit TokenPairConfigured(canonicalToken, mirroredToken);
}
function setMintableMirroredToken(address mirroredToken, bool enabled) external onlyRole(DEFAULT_ADMIN_ROLE) {
mintableMirroredToken[mirroredToken] = enabled;
}
function bridgeToZedxion(
address token,
uint256 amount,
address recipientOn83872
) external payable override nonReentrant returns (bytes32 requestId) {
require(block.chainid == HUB_CHAIN_ID, "ZedxionCustomBridge: hub only");
return _lock(token, amount, recipientOn83872, ZEDXION_CHAIN_ID);
}
function bridgeFromZedxion(
address tokenOn83872,
uint256 amount,
address recipientOn138
) external payable override nonReentrant returns (bytes32 requestId) {
require(block.chainid == ZEDXION_CHAIN_ID, "ZedxionCustomBridge: zedxion only");
return _lock(tokenOn83872, amount, recipientOn138, HUB_CHAIN_ID);
}
function _lock(
address token,
uint256 amount,
address recipient,
uint256 destinationChainId
) internal returns (bytes32 requestId) {
require(recipient != address(0), "zero recipient");
require(amount > 0, "zero amount");
requestId = keccak256(abi.encodePacked(
msg.sender,
token,
amount,
recipient,
nonces[msg.sender]++,
block.chainid,
block.timestamp
));
if (token == address(0)) {
require(msg.value >= amount, "insufficient value");
} else {
IERC20(token).safeTransferFrom(msg.sender, address(this), amount);
}
locks[requestId] = LockRecord({
sender: msg.sender,
token: token,
amount: amount,
recipient: recipient,
createdAt: block.timestamp,
released: false
});
if (destinationChainId == ZEDXION_CHAIN_ID) {
emit LockForZedxion(requestId, msg.sender, token, amount, recipient, block.chainid);
emit ZedxionBridgeInitiated(requestId, msg.sender, token, amount, recipient, ZEDXION_CHAIN_ID);
} else {
emit LockFor138(requestId, msg.sender, token, amount, recipient, block.chainid);
emit ZedxionBridgeInitiated(requestId, msg.sender, token, amount, recipient, HUB_CHAIN_ID);
}
}
/**
* @notice On 83872: mint cW* or release escrow after 138 lock. On 138: release canonical c* after 83872 burn/lock.
*/
function releaseOnZedxion(
bytes32 requestId,
address token,
uint256 amount,
address recipient
) external onlyRole(RELAYER_ROLE) nonReentrant {
require(block.chainid == ZEDXION_CHAIN_ID, "ZedxionCustomBridge: zedxion only");
_release(requestId, token, amount, recipient, true);
}
function releaseOn138(
bytes32 requestId,
address token,
uint256 amount,
address recipient
) external onlyRole(RELAYER_ROLE) nonReentrant {
require(block.chainid == HUB_CHAIN_ID, "ZedxionCustomBridge: hub only");
_release(requestId, token, amount, recipient, false);
}
function _release(
bytes32 requestId,
address token,
uint256 amount,
address recipient,
bool onZedxion
) internal {
require(!releasedOnDestination[requestId], "already released");
require(recipient != address(0), "zero recipient");
require(amount > 0, "zero amount");
releasedOnDestination[requestId] = true;
if (token == address(0)) {
(bool sent,) = payable(recipient).call{value: amount}("");
require(sent, "transfer failed");
} else if (mintableMirroredToken[token]) {
ICWMintBurnToken(token).mint(recipient, amount);
} else {
IERC20(token).safeTransfer(recipient, amount);
}
emit ZedxionBridgeCompleted(requestId, bytes32(0));
if (onZedxion) {
emit UnlockOnZedxion(requestId, recipient, token, amount);
} else {
emit UnlockOn138(requestId, recipient, token, amount);
}
}
receive() external payable {}
}