187 lines
6.9 KiB
Solidity
187 lines
6.9 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/IChainAdapter.sol";
|
|
import "../../interfaces/IAlltraTransport.sol";
|
|
import "../../UniversalCCIPBridge.sol";
|
|
|
|
/**
|
|
* @title AlltraAdapter
|
|
* @notice Bridge adapter for ALL Mainnet (EVM-compatible)
|
|
* @dev ALL Mainnet (651940) is not supported by CCIP. Use setAlltraTransport() to set
|
|
* AlltraCustomBridge (or other IAlltraTransport) for 138 <-> 651940 flows.
|
|
* @dev Chain ID: 651940 (0x9f2a4) - https://chainlist.org/chain/651940
|
|
*/
|
|
contract AlltraAdapter is IChainAdapter, AccessControl, ReentrancyGuard {
|
|
using SafeERC20 for IERC20;
|
|
|
|
bytes32 public constant BRIDGE_OPERATOR_ROLE = keccak256("BRIDGE_OPERATOR_ROLE");
|
|
|
|
// ALL Mainnet Chain ID - confirmed from ChainList
|
|
uint256 public constant ALLTRA_MAINNET = 651940;
|
|
|
|
UniversalCCIPBridge public universalBridge;
|
|
IAlltraTransport public alltraTransport; // When set, used for 651940 instead of CCIP
|
|
bool public isActive;
|
|
/// @notice Configurable bridge fee (default 0.001 ALL). Set via setBridgeFee() after deployment.
|
|
uint256 public bridgeFee;
|
|
|
|
mapping(bytes32 => BridgeRequest) public bridgeRequests;
|
|
mapping(address => uint256) public nonces;
|
|
|
|
event AlltraBridgeInitiated(
|
|
bytes32 indexed requestId,
|
|
address indexed sender,
|
|
address indexed token,
|
|
uint256 amount,
|
|
address recipient
|
|
);
|
|
|
|
event AlltraBridgeConfirmed(
|
|
bytes32 indexed requestId,
|
|
bytes32 indexed alltraTxHash
|
|
);
|
|
|
|
constructor(address admin, address _bridge) {
|
|
_grantRole(DEFAULT_ADMIN_ROLE, admin);
|
|
_grantRole(BRIDGE_OPERATOR_ROLE, admin);
|
|
universalBridge = UniversalCCIPBridge(payable(_bridge));
|
|
isActive = true;
|
|
bridgeFee = 1000000000000000; // 0.001 ALL default; update via setBridgeFee() per ALL Mainnet fee structure
|
|
}
|
|
|
|
/**
|
|
* @notice Set custom transport for 138 <-> 651940 (no CCIP). When set, bridge() uses this instead of UniversalCCIPBridge.
|
|
*/
|
|
function setAlltraTransport(address _transport) external onlyRole(DEFAULT_ADMIN_ROLE) {
|
|
alltraTransport = IAlltraTransport(_transport);
|
|
}
|
|
|
|
function getChainType() external pure override returns (string memory) {
|
|
return "EVM"; // Generic chain type to distinguish from ALLTRA (orchestration layer)
|
|
}
|
|
|
|
function getChainIdentifier() external pure override returns (uint256 chainId, string memory identifier) {
|
|
return (ALLTRA_MAINNET, "ALL-Mainnet"); // Updated identifier
|
|
}
|
|
|
|
function validateDestination(bytes calldata destination) external pure override returns (bool) {
|
|
// Standard EVM address validation
|
|
if (destination.length != 20) return false;
|
|
address dest = address(bytes20(destination));
|
|
return dest != address(0);
|
|
}
|
|
|
|
function bridge(
|
|
address token,
|
|
uint256 amount,
|
|
bytes calldata destination,
|
|
bytes calldata recipient
|
|
) external payable override nonReentrant returns (bytes32 requestId) {
|
|
require(isActive, "Adapter inactive");
|
|
require(amount > 0, "Zero amount");
|
|
require(this.validateDestination(destination), "Invalid destination");
|
|
|
|
address recipientAddr = address(bytes20(destination));
|
|
|
|
// Generate request ID
|
|
requestId = keccak256(abi.encodePacked(
|
|
msg.sender,
|
|
token,
|
|
amount,
|
|
recipientAddr,
|
|
nonces[msg.sender]++,
|
|
block.timestamp
|
|
));
|
|
|
|
// Lock tokens
|
|
if (token == address(0)) {
|
|
require(msg.value >= amount, "Insufficient ETH");
|
|
} else {
|
|
IERC20(token).safeTransferFrom(msg.sender, address(this), amount);
|
|
}
|
|
|
|
// Create bridge request
|
|
bridgeRequests[requestId] = BridgeRequest({
|
|
sender: msg.sender,
|
|
token: token,
|
|
amount: amount,
|
|
destinationData: destination,
|
|
requestId: requestId,
|
|
status: BridgeStatus.Locked,
|
|
createdAt: block.timestamp,
|
|
completedAt: 0
|
|
});
|
|
|
|
// ALL Mainnet (651940) is not supported by CCIP. Use custom transport when set.
|
|
if (address(alltraTransport) != address(0) && alltraTransport.isConfigured()) {
|
|
alltraTransport.lockAndRelay{value: msg.value}(token, amount, recipientAddr);
|
|
} else {
|
|
// Fallback to UniversalCCIPBridge only if destination were CCIP-supported; 651940 is not.
|
|
revert("AlltraAdapter: set AlltraCustomBridge via setAlltraTransport for 651940");
|
|
}
|
|
|
|
emit AlltraBridgeInitiated(requestId, msg.sender, token, amount, recipientAddr);
|
|
|
|
return requestId;
|
|
}
|
|
|
|
function getBridgeStatus(bytes32 requestId)
|
|
external view override returns (BridgeRequest memory) {
|
|
return bridgeRequests[requestId];
|
|
}
|
|
|
|
function cancelBridge(bytes32 requestId) external override returns (bool) {
|
|
BridgeRequest storage request = bridgeRequests[requestId];
|
|
require(request.status == BridgeStatus.Pending || request.status == BridgeStatus.Locked, "Cannot cancel");
|
|
require(msg.sender == request.sender, "Not request sender");
|
|
|
|
// Refund tokens
|
|
if (request.token == address(0)) {
|
|
payable(request.sender).transfer(request.amount);
|
|
} else {
|
|
IERC20(request.token).safeTransfer(request.sender, request.amount);
|
|
}
|
|
|
|
request.status = BridgeStatus.Cancelled;
|
|
return true;
|
|
}
|
|
|
|
/// @notice Returns the current bridge fee (configurable via setBridgeFee).
|
|
/// @param token Unused; fee is global per adapter.
|
|
/// @param amount Unused.
|
|
/// @param destination Unused.
|
|
/// @return fee Current bridgeFee in wei.
|
|
function estimateFee(
|
|
address token,
|
|
uint256 amount,
|
|
bytes calldata destination
|
|
) external view override returns (uint256 fee) {
|
|
return bridgeFee;
|
|
}
|
|
|
|
/// @notice Update bridge fee. Call after deployment when ALL Mainnet fee structure is known.
|
|
function setBridgeFee(uint256 _fee) external onlyRole(DEFAULT_ADMIN_ROLE) {
|
|
bridgeFee = _fee;
|
|
}
|
|
|
|
function setIsActive(bool _isActive) external onlyRole(DEFAULT_ADMIN_ROLE) {
|
|
isActive = _isActive;
|
|
}
|
|
|
|
function confirmBridge(bytes32 requestId, bytes32 alltraTxHash)
|
|
external onlyRole(BRIDGE_OPERATOR_ROLE) {
|
|
BridgeRequest storage request = bridgeRequests[requestId];
|
|
require(request.status == BridgeStatus.Locked, "Invalid status");
|
|
|
|
request.status = BridgeStatus.Confirmed;
|
|
request.completedAt = block.timestamp;
|
|
|
|
emit AlltraBridgeConfirmed(requestId, alltraTxHash);
|
|
}
|
|
}
|