- 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.
274 lines
8.3 KiB
Solidity
274 lines
8.3 KiB
Solidity
// SPDX-License-Identifier: MIT
|
|
pragma solidity ^0.8.19;
|
|
|
|
/**
|
|
* @title TransactionMirror
|
|
* @notice Mirrors Chain-138 transactions to Ethereum Mainnet for Etherscan visibility
|
|
* @dev Logs all Chain-138 transactions as events on Mainnet, making them searchable
|
|
* and viewable on Etherscan. This provides transparency and auditability.
|
|
*/
|
|
contract TransactionMirror {
|
|
address public admin;
|
|
bool public paused;
|
|
|
|
// Chain-138 chain ID
|
|
uint64 public constant CHAIN_138 = 138;
|
|
|
|
// Maximum batch size to prevent gas limit issues
|
|
uint256 public constant MAX_BATCH_SIZE = 100;
|
|
|
|
// Transaction structure
|
|
struct MirroredTransaction {
|
|
bytes32 txHash; // Chain-138 transaction hash
|
|
address from; // Sender address
|
|
address to; // Recipient address (or contract)
|
|
uint256 value; // Value transferred
|
|
uint256 blockNumber; // Chain-138 block number
|
|
uint256 blockTimestamp; // Block timestamp
|
|
uint256 gasUsed; // Gas used
|
|
bool success; // Transaction success status
|
|
bytes data; // Transaction data (if any)
|
|
bytes32 indexedHash; // Indexed hash for searchability
|
|
}
|
|
|
|
// Mapping: txHash => MirroredTransaction
|
|
mapping(bytes32 => MirroredTransaction) public transactions;
|
|
|
|
// Array of all mirrored transaction hashes
|
|
bytes32[] public mirroredTxHashes;
|
|
|
|
// Mapping: txHash => bool (replay protection)
|
|
mapping(bytes32 => bool) public processed;
|
|
|
|
// Events (indexed for Etherscan searchability)
|
|
event TransactionMirrored(
|
|
bytes32 indexed txHash,
|
|
address indexed from,
|
|
address indexed to,
|
|
uint256 value,
|
|
uint256 blockNumber,
|
|
uint256 blockTimestamp,
|
|
uint256 gasUsed,
|
|
bool success
|
|
);
|
|
|
|
event BatchTransactionsMirrored(
|
|
uint256 count,
|
|
uint256 startBlock,
|
|
uint256 endBlock
|
|
);
|
|
|
|
event AdminChanged(address indexed newAdmin);
|
|
event Paused();
|
|
event Unpaused();
|
|
|
|
modifier onlyAdmin() {
|
|
require(msg.sender == admin, "only admin");
|
|
_;
|
|
}
|
|
|
|
modifier whenNotPaused() {
|
|
require(!paused, "paused");
|
|
_;
|
|
}
|
|
|
|
constructor(address _admin) {
|
|
require(_admin != address(0), "zero admin");
|
|
admin = _admin;
|
|
}
|
|
|
|
/**
|
|
* @notice Mirror a single Chain-138 transaction to Mainnet
|
|
* @param txHash Chain-138 transaction hash
|
|
* @param from Sender address
|
|
* @param to Recipient address
|
|
* @param value Value transferred
|
|
* @param blockNumber Chain-138 block number
|
|
* @param blockTimestamp Block timestamp
|
|
* @param gasUsed Gas used
|
|
* @param success Transaction success status
|
|
* @param data Transaction data (optional)
|
|
*/
|
|
function mirrorTransaction(
|
|
bytes32 txHash,
|
|
address from,
|
|
address to,
|
|
uint256 value,
|
|
uint256 blockNumber,
|
|
uint256 blockTimestamp,
|
|
uint256 gasUsed,
|
|
bool success,
|
|
bytes calldata data
|
|
) external onlyAdmin whenNotPaused {
|
|
require(txHash != bytes32(0), "invalid hash");
|
|
require(!processed[txHash], "already mirrored");
|
|
|
|
bytes32 indexedHash = keccak256(abi.encodePacked(CHAIN_138, txHash));
|
|
|
|
transactions[txHash] = MirroredTransaction({
|
|
txHash: txHash,
|
|
from: from,
|
|
to: to,
|
|
value: value,
|
|
blockNumber: blockNumber,
|
|
blockTimestamp: blockTimestamp,
|
|
gasUsed: gasUsed,
|
|
success: success,
|
|
data: data,
|
|
indexedHash: indexedHash
|
|
});
|
|
|
|
mirroredTxHashes.push(txHash);
|
|
processed[txHash] = true;
|
|
|
|
emit TransactionMirrored(
|
|
txHash,
|
|
from,
|
|
to,
|
|
value,
|
|
blockNumber,
|
|
blockTimestamp,
|
|
gasUsed,
|
|
success
|
|
);
|
|
}
|
|
|
|
/**
|
|
* @notice Mirror multiple Chain-138 transactions in a batch
|
|
* @param txHashes Array of transaction hashes
|
|
* @param froms Array of sender addresses
|
|
* @param tos Array of recipient addresses
|
|
* @param values Array of values
|
|
* @param blockNumbers Array of block numbers
|
|
* @param blockTimestamps Array of timestamps
|
|
* @param gasUseds Array of gas used
|
|
* @param successes Array of success statuses
|
|
* @param datas Array of transaction data
|
|
*/
|
|
function mirrorBatchTransactions(
|
|
bytes32[] calldata txHashes,
|
|
address[] calldata froms,
|
|
address[] calldata tos,
|
|
uint256[] calldata values,
|
|
uint256[] calldata blockNumbers,
|
|
uint256[] calldata blockTimestamps,
|
|
uint256[] calldata gasUseds,
|
|
bool[] calldata successes,
|
|
bytes[] calldata datas
|
|
) external onlyAdmin whenNotPaused {
|
|
uint256 count = txHashes.length;
|
|
require(count > 0, "empty batch");
|
|
require(count <= MAX_BATCH_SIZE, "batch too large");
|
|
require(
|
|
count == froms.length &&
|
|
count == tos.length &&
|
|
count == values.length &&
|
|
count == blockNumbers.length &&
|
|
count == blockTimestamps.length &&
|
|
count == gasUseds.length &&
|
|
count == successes.length &&
|
|
count == datas.length,
|
|
"array length mismatch"
|
|
);
|
|
|
|
uint256 startBlock = blockNumbers[0];
|
|
uint256 endBlock = blockNumbers[count - 1];
|
|
|
|
// Process transactions in batches to avoid stack too deep
|
|
for (uint256 i = 0; i < count; i++) {
|
|
bytes32 txHash = txHashes[i];
|
|
require(txHash != bytes32(0), "invalid hash");
|
|
require(!processed[txHash], "already mirrored");
|
|
|
|
bytes32 indexedHash = keccak256(abi.encodePacked(CHAIN_138, txHash));
|
|
|
|
transactions[txHash] = MirroredTransaction({
|
|
txHash: txHash,
|
|
from: froms[i],
|
|
to: tos[i],
|
|
value: values[i],
|
|
blockNumber: blockNumbers[i],
|
|
blockTimestamp: blockTimestamps[i],
|
|
gasUsed: gasUseds[i],
|
|
success: successes[i],
|
|
data: datas[i],
|
|
indexedHash: indexedHash
|
|
});
|
|
|
|
mirroredTxHashes.push(txHash);
|
|
processed[txHash] = true;
|
|
|
|
emit TransactionMirrored(
|
|
txHash,
|
|
froms[i],
|
|
tos[i],
|
|
values[i],
|
|
blockNumbers[i],
|
|
blockTimestamps[i],
|
|
gasUseds[i],
|
|
successes[i]
|
|
);
|
|
}
|
|
|
|
emit BatchTransactionsMirrored(count, startBlock, endBlock);
|
|
}
|
|
|
|
|
|
/**
|
|
* @notice Get mirrored transaction details
|
|
* @param txHash Chain-138 transaction hash
|
|
* @return tx Mirrored transaction structure
|
|
*/
|
|
function getTransaction(bytes32 txHash) external view returns (MirroredTransaction memory tx) {
|
|
require(processed[txHash], "not mirrored");
|
|
return transactions[txHash];
|
|
}
|
|
|
|
/**
|
|
* @notice Check if a transaction is mirrored
|
|
* @param txHash Chain-138 transaction hash
|
|
* @return true if mirrored
|
|
*/
|
|
function isMirrored(bytes32 txHash) external view returns (bool) {
|
|
return processed[txHash];
|
|
}
|
|
|
|
/**
|
|
* @notice Get total number of mirrored transactions
|
|
* @return count Number of mirrored transactions
|
|
*/
|
|
function getMirroredTransactionCount() external view returns (uint256) {
|
|
return mirroredTxHashes.length;
|
|
}
|
|
|
|
/**
|
|
* @notice Get mirrored transaction hash at index
|
|
* @param index Index in mirroredTxHashes array
|
|
* @return txHash Transaction hash
|
|
*/
|
|
function getMirroredTransaction(uint256 index) external view returns (bytes32) {
|
|
require(index < mirroredTxHashes.length, "out of bounds");
|
|
return mirroredTxHashes[index];
|
|
}
|
|
|
|
/**
|
|
* @notice Admin functions
|
|
*/
|
|
function setAdmin(address newAdmin) external onlyAdmin {
|
|
require(newAdmin != address(0), "zero admin");
|
|
admin = newAdmin;
|
|
emit AdminChanged(newAdmin);
|
|
}
|
|
|
|
function pause() external onlyAdmin {
|
|
paused = true;
|
|
emit Paused();
|
|
}
|
|
|
|
function unpause() external onlyAdmin {
|
|
paused = false;
|
|
emit Unpaused();
|
|
}
|
|
}
|
|
|