129 lines
4.0 KiB
Solidity
129 lines
4.0 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/IAlltraTransport.sol";
|
|
|
|
/**
|
|
* @title AlltraCustomBridge
|
|
* @notice Custom transport for 138 <-> ALL Mainnet (651940). Locks tokens and emits event; no CCIP.
|
|
* @dev Deploy at same address on 138 and 651940 via CREATE2. On 138: lock + emit LockForAlltra.
|
|
* Off-chain relayer or contract on 651940 completes mint/unlock. On 651940: implement
|
|
* unlockOrMint (called by relayer or same contract) to complete the flow.
|
|
*/
|
|
contract AlltraCustomBridge is IAlltraTransport, AccessControl, ReentrancyGuard {
|
|
using SafeERC20 for IERC20;
|
|
|
|
bytes32 public constant RELAYER_ROLE = keccak256("RELAYER_ROLE");
|
|
uint256 public constant ALL_MAINNET_CHAIN_ID = 651940;
|
|
|
|
mapping(bytes32 => LockRecord) public locks;
|
|
mapping(bytes32 => bool) public releasedOnAlltra; // on 651940: prevent double release
|
|
mapping(address => uint256) public nonces;
|
|
bool private _hasRelayer;
|
|
|
|
struct LockRecord {
|
|
address sender;
|
|
address token;
|
|
uint256 amount;
|
|
address recipient;
|
|
uint256 createdAt;
|
|
bool released;
|
|
}
|
|
|
|
event LockForAlltra(
|
|
bytes32 indexed requestId,
|
|
address indexed sender,
|
|
address indexed token,
|
|
uint256 amount,
|
|
address recipient,
|
|
uint256 sourceChainId
|
|
);
|
|
|
|
event UnlockOnAlltra(
|
|
bytes32 indexed requestId,
|
|
address indexed recipient,
|
|
address indexed token,
|
|
uint256 amount
|
|
);
|
|
|
|
constructor(address admin) {
|
|
_grantRole(DEFAULT_ADMIN_ROLE, admin);
|
|
_grantRole(RELAYER_ROLE, admin);
|
|
_hasRelayer = true;
|
|
}
|
|
|
|
/**
|
|
* @notice Lock tokens and emit event for relay to ALL Mainnet. Does not use CCIP.
|
|
*/
|
|
function lockAndRelay(
|
|
address token,
|
|
uint256 amount,
|
|
address recipient
|
|
) external payable override nonReentrant 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
|
|
});
|
|
|
|
emit LockForAlltra(requestId, msg.sender, token, amount, recipient, block.chainid);
|
|
return requestId;
|
|
}
|
|
|
|
function isConfigured() external view override returns (bool) {
|
|
return _hasRelayer;
|
|
}
|
|
|
|
/**
|
|
* @notice On ALL Mainnet (651940): release tokens to recipient after relay proof.
|
|
* @dev Only RELAYER_ROLE; in production, verify merkle proof or signature from source chain.
|
|
* Uses releasedOnAlltra[requestId] to prevent double release on this chain.
|
|
*/
|
|
function releaseOnAlltra(
|
|
bytes32 requestId,
|
|
address token,
|
|
uint256 amount,
|
|
address recipient
|
|
) external onlyRole(RELAYER_ROLE) nonReentrant {
|
|
require(!releasedOnAlltra[requestId], "already released");
|
|
releasedOnAlltra[requestId] = true;
|
|
|
|
if (token == address(0)) {
|
|
(bool sent,) = payable(recipient).call{value: amount}("");
|
|
require(sent, "transfer failed");
|
|
} else {
|
|
IERC20(token).safeTransfer(recipient, amount);
|
|
}
|
|
|
|
emit UnlockOnAlltra(requestId, recipient, token, amount);
|
|
}
|
|
|
|
receive() external payable {}
|
|
}
|