Created
April 8, 2022 16:48
-
-
Save Savio-Sou/55c1ec38c967e0156a37039f3bf2be0e to your computer and use it in GitHub Desktop.
Review Horizon
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| const rlp = require('rlp'); | |
| const headerData = require('./headers.json'); | |
| const transactions = require('./transaction.json'); | |
| const { rpcWrapper, getReceiptProof } = require('../scripts/utils'); | |
| const { expect } = require('chai'); | |
| let MMRVerifier, HarmonyProver; | |
| let prover, mmrVerifier; | |
| function hexToBytes(hex) { | |
| for (var bytes = [], c = 0; c < hex.length; c += 2) | |
| bytes.push(parseInt(hex.substr(c, 2), 16)); | |
| return bytes; | |
| } | |
| describe('HarmonyProver', function () { | |
| // Deploy contracts | |
| beforeEach(async function () { | |
| MMRVerifier = await ethers.getContractFactory("MMRVerifier"); | |
| mmrVerifier = await MMRVerifier.deploy(); | |
| await mmrVerifier.deployed(); | |
| // await HarmonyProver.link('MMRVerifier', mmrVerifier); | |
| HarmonyProver = await ethers.getContractFactory( | |
| "HarmonyProver", | |
| { | |
| libraries: { | |
| MMRVerifier: mmrVerifier.address | |
| } | |
| } | |
| ); | |
| prover = await HarmonyProver.deploy(); | |
| await prover.deployed(); | |
| }); | |
| // Test parsing RLP encoded block header | |
| it('parse rlp block header', async function () { | |
| let header = await prover.toBlockHeader(hexToBytes(headerData.rlpheader)); | |
| expect(header.hash).to.equal(headerData.hash); | |
| }); | |
| // Test parsing transaction receipt proof for proving in/exclusion in the transaction Merkle-Patricia-Trie | |
| it('parse transaction receipt proof', async function () { | |
| let callback = getReceiptProof; | |
| let callbackArgs = [ | |
| process.env.LOCALNET, | |
| prover, | |
| transactions.hash | |
| ]; | |
| let isTxn = true; | |
| let txProof = await rpcWrapper( | |
| transactions.hash, | |
| isTxn, | |
| callback, | |
| callbackArgs | |
| ); | |
| console.log(txProof); | |
| expect(txProof.header.hash).to.equal(transactions.header); | |
| // let response = await prover.getBlockRlpData(txProof.header); | |
| // console.log(response); | |
| // let res = await test.bar([123, "abc", "0xD6dDd996B2d5B7DB22306654FD548bA2A58693AC"]); | |
| // // console.log(res); | |
| }); | |
| }); | |
| let TokenLockerOnEthereum, tokenLocker; | |
| let HarmonyLightClient, lightclient; | |
| describe('TokenLocker', function () { | |
| beforeEach(async function () { | |
| // Deploy and bind contracts | |
| TokenLockerOnEthereum = await ethers.getContractFactory("TokenLockerOnEthereum"); | |
| tokenLocker = await MMRVerifier.deploy(); | |
| await tokenLocker.deployed(); | |
| await tokenLocker.bind(tokenLocker.address); | |
| // // await HarmonyProver.link('MMRVerifier', mmrVerifier); | |
| // HarmonyProver = await ethers.getContractFactory( | |
| // "HarmonyProver", | |
| // { | |
| // libraries: { | |
| // MMRVerifier: mmrVerifier.address | |
| // } | |
| // } | |
| // ); | |
| // prover = await HarmonyProver.deploy(); | |
| // await prover.deployed(); | |
| }); | |
| // Test mapping token's Ethereum address to Harmony address | |
| it('issue map token test', async function () { | |
| }); | |
| // Test locking token on Ethereum as bridge deposit | |
| it('lock test', async function () { | |
| }); | |
| // Test burning token on Harmony to unlock bridge deposit on Ethereum | |
| it('unlock test', async function () { | |
| }); | |
| // Test upgrading the Harmony light client smart contract, likely via redirecting proxy | |
| it('light client upgrade test', async function () { | |
| }); | |
| }); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // SPDX-License-Identifier: UNLICENSED | |
| pragma solidity 0.7.3; | |
| pragma experimental ABIEncoderV2; | |
| import "@openzeppelin/contracts-upgradeable/math/SafeMathUpgradeable.sol"; | |
| import "@openzeppelin/contracts-upgradeable/proxy/Initializable.sol"; | |
| import "@openzeppelin/contracts-upgradeable/utils/PausableUpgradeable.sol"; | |
| import "./EthereumParser.sol"; | |
| import "./lib/EthUtils.sol"; | |
| import "./ethash/ethash.sol"; | |
| /// @title Ethereum light client | |
| /// @notice Store and verify Ethereum block headers | |
| /// @dev Deploy on Harmony | |
| contract EthereumLightClient is Ethash, Initializable, PausableUpgradeable { | |
| using SafeMathUpgradeable for uint256; | |
| // Ethereum block header | |
| struct StoredBlockHeader { | |
| uint256 parentHash; | |
| uint256 stateRoot; | |
| uint256 transactionsRoot; | |
| uint256 receiptsRoot; | |
| uint256 number; | |
| uint256 difficulty; | |
| uint256 time; | |
| uint256 hash; | |
| } | |
| struct HeaderInfo { | |
| uint256 total_difficulty; | |
| bytes32 parent_hash; | |
| uint64 number; | |
| } | |
| // The first block header hash | |
| uint256 public firstBlock; | |
| // Blocks data, in the form: blockHeaderHash => BlockHeader | |
| mapping(uint256 => StoredBlockHeader) public blocks; | |
| // Block existing map, in the form: blockHeaderHash => bool | |
| mapping(uint256 => bool) public blockExisting; | |
| // Blocks in 'Verified' state | |
| mapping(uint256 => bool) public verifiedBlocks; | |
| // Blocks in 'Finalized' state | |
| mapping(uint256 => bool) public finalizedBlocks; | |
| // Valid relayed blocks for a block height, in the form: blockNumber => blockHeaderHash[] | |
| mapping(uint256 => uint256[]) public blocksByHeight; | |
| // Block height existing map, in the form: blockNumber => bool | |
| mapping(uint256 => bool) public blocksByHeightExisting; | |
| // Max block height stored | |
| uint256 public blockHeightMax; | |
| // Block header hash that points to longest chain head | |
| // (please note that 'longest' chain is based on total difficulty) | |
| // uint public longestChainHead; | |
| // Longest branch head of each checkpoint, in the form: (checkpoint block hash) => (head block hash) | |
| // (note that 'longest branch' means the branch which has biggest cumulative difficulty from checkpoint) | |
| mapping(uint256 => uint256) public longestBranchHead; | |
| uint256 private constant DEFAULT_FINALITY_CONFIRMS = 13; | |
| uint256 public finalityConfirms; | |
| /// @dev Initialize contract | |
| /// @param _rlpHeader RLP encoded first block header to store | |
| function initialize(bytes memory _rlpHeader) external initializer { | |
| finalityConfirms = DEFAULT_FINALITY_CONFIRMS; | |
| uint256 blockHash = EthereumParser.calcBlockHeaderHash(_rlpHeader); | |
| // Parse rlp-encoded block header into structure | |
| EthereumParser.BlockHeader memory header = EthereumParser | |
| .parseBlockHeader(_rlpHeader); | |
| // Save block header info | |
| StoredBlockHeader memory storedBlock = StoredBlockHeader({ | |
| parentHash: header.parentHash, | |
| stateRoot: header.stateRoot, | |
| transactionsRoot: header.transactionsRoot, | |
| receiptsRoot: header.receiptsRoot, | |
| number: header.number, | |
| difficulty: header.difficulty, | |
| time: header.timestamp, | |
| hash: blockHash | |
| }); | |
| _setFirstBlock(storedBlock); | |
| } | |
| //uint32 constant loopAccesses = 64; // Number of accesses in hashimoto loop | |
| /// @notice Store an Ethereum block header in this contract | |
| /// @param _rlpHeader RLP encoded block header to store | |
| /// @param cache Ethash cache | |
| /// @param proofs Ethash proof | |
| function addBlockHeader( | |
| bytes memory _rlpHeader, | |
| bytes32[4][loopAccesses] memory cache, | |
| bytes32[][loopAccesses] memory proofs | |
| ) public whenNotPaused returns (bool) { | |
| // Calculate block header hash | |
| uint256 blockHash = EthereumParser.calcBlockHeaderHash(_rlpHeader); | |
| // Check block existing | |
| require( | |
| !blockExisting[blockHash], | |
| "Relay block failed: block already relayed" | |
| ); | |
| // Parse rlp-encoded block header into structure | |
| EthereumParser.BlockHeader memory header = EthereumParser | |
| .parseBlockHeader(_rlpHeader); | |
| // Check the existence of parent block | |
| require( | |
| blockExisting[header.parentHash], | |
| "Relay block failed: parent block not relayed yet" | |
| ); | |
| // Check block height | |
| require( | |
| header.number == blocks[header.parentHash].number.add(1), | |
| "Relay block failed: invalid block blockHeightMax" | |
| ); | |
| // Check timestamp | |
| require( | |
| header.timestamp > blocks[header.parentHash].time, | |
| "Relay block failed: invalid timestamp" | |
| ); | |
| // Check difficulty | |
| require( | |
| _checkDiffValidity( | |
| header.difficulty, | |
| blocks[header.parentHash].difficulty | |
| ), | |
| "Relay block failed: invalid difficulty" | |
| ); | |
| // Verify block PoW | |
| uint256 sealHash = EthereumParser.calcBlockSealHash(_rlpHeader); | |
| bool rVerified = verifyEthash( | |
| bytes32(sealHash), | |
| uint64(header.nonce), | |
| uint64(header.number), | |
| cache, | |
| proofs, | |
| header.difficulty, | |
| header.mixHash | |
| ); | |
| require(rVerified, "Relay block failed: invalid PoW"); | |
| // Save block header info | |
| StoredBlockHeader memory storedBlock = StoredBlockHeader({ | |
| parentHash: header.parentHash, | |
| stateRoot: header.stateRoot, | |
| transactionsRoot: header.transactionsRoot, | |
| receiptsRoot: header.receiptsRoot, | |
| number: header.number, | |
| difficulty: header.difficulty, | |
| time: header.timestamp, | |
| hash: blockHash | |
| }); | |
| blocks[blockHash] = storedBlock; | |
| blockExisting[blockHash] = true; | |
| // verifiedBlocks[blockHash] = true; | |
| blocksByHeight[header.number].push(blockHash); | |
| blocksByHeightExisting[header.number] = true; | |
| if (header.number > blockHeightMax) { | |
| blockHeightMax = header.number; | |
| } | |
| // Return true if success | |
| return true; | |
| } | |
| function getBlockHeightMax() public view returns (uint256) { | |
| return blockHeightMax; | |
| } | |
| function getStateRoot(bytes32 blockHash) public view returns (bytes32) { | |
| return bytes32(blocks[uint256(blockHash)].stateRoot); | |
| } | |
| function getTxRoot(bytes32 blockHash) public view returns (bytes32) { | |
| return bytes32(blocks[uint256(blockHash)].transactionsRoot); | |
| } | |
| function getReceiptRoot(bytes32 blockHash) public view returns (bytes32) { | |
| return bytes32(blocks[uint256(blockHash)].receiptsRoot); | |
| } | |
| function VerifyReceiptsHash(bytes32 blockHash, bytes32 receiptsHash) | |
| external | |
| view | |
| returns (bool) | |
| { | |
| return bytes32(blocks[uint256(blockHash)].receiptsRoot) == receiptsHash; | |
| } | |
| // Check the difficulty of block is valid or not | |
| // (the block difficulty adjustment is described here: https://github.com/ethereum/EIPs/issues/100) | |
| // Note that this is only 'minimal check' because we do not have 'block uncles' information to calculate exactly. | |
| // 'Minimal check' is enough to prevent someone from spamming relaying blocks with quite small difficulties | |
| function _checkDiffValidity(uint256 diff, uint256 parentDiff) | |
| private | |
| pure | |
| returns (bool) | |
| { | |
| return diff >= parentDiff.sub((parentDiff / 10000) * 99); | |
| } | |
| // Store first block header provided in initialize | |
| function _setFirstBlock(StoredBlockHeader memory toSetBlock) private { | |
| firstBlock = toSetBlock.hash; | |
| blocks[toSetBlock.hash] = toSetBlock; | |
| blockExisting[toSetBlock.hash] = true; | |
| verifiedBlocks[toSetBlock.hash] = true; | |
| finalizedBlocks[toSetBlock.hash] = true; | |
| blocksByHeight[toSetBlock.number].push(toSetBlock.hash); | |
| blocksByHeightExisting[toSetBlock.number] = true; | |
| blockHeightMax = toSetBlock.number; | |
| longestBranchHead[toSetBlock.hash] = toSetBlock.hash; | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // SPDX-License-Identifier: UNLICENSED | |
| pragma solidity 0.7.3; | |
| pragma experimental ABIEncoderV2; | |
| import "./HarmonyParser.sol"; | |
| import "./lib/SafeCast.sol"; | |
| import "@openzeppelin/contracts-upgradeable/math/SafeMathUpgradeable.sol"; | |
| import "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol"; | |
| // import "openzeppelin-solidity/contracts/utils/Pausable.sol"; | |
| import "@openzeppelin/contracts-upgradeable/utils/PausableUpgradeable.sol"; | |
| // import "openzeppelin-solidity/contracts/proxy/Initializable.sol"; | |
| import "@openzeppelin/contracts-upgradeable/proxy/Initializable.sol"; | |
| /// @notice Store and verify Harmony block headers | |
| /// @dev Deploy on Ethereum | |
| contract HarmonyLightClient is | |
| Initializable, | |
| PausableUpgradeable, | |
| AccessControlUpgradeable | |
| { | |
| using SafeCast for *; | |
| using SafeMathUpgradeable for uint256; | |
| // Harmony block header | |
| struct BlockHeader { | |
| bytes32 parentHash; | |
| bytes32 stateRoot; | |
| bytes32 transactionsRoot; | |
| bytes32 receiptsRoot; | |
| uint256 number; | |
| uint256 epoch; | |
| uint256 shard; | |
| uint256 time; | |
| bytes32 mmrRoot; // Merkle Mountain Range Root, for verifying block inclusion in block tree (i.e. existence in chain) | |
| bytes32 hash; | |
| } | |
| /// @notice Event of a checkpoint block header stored | |
| event CheckPoint( | |
| bytes32 stateRoot, | |
| bytes32 transactionsRoot, | |
| bytes32 receiptsRoot, | |
| uint256 number, | |
| uint256 epoch, | |
| uint256 shard, | |
| uint256 time, | |
| bytes32 mmrRoot, | |
| bytes32 hash | |
| ); | |
| BlockHeader firstBlock; | |
| BlockHeader lastCheckPointBlock; | |
| // epoch to block numbers, as there could be >=1 mmr entries per epoch | |
| mapping(uint256 => uint256[]) epochCheckPointBlockNumbers; | |
| // block number to BlockHeader | |
| mapping(uint256 => BlockHeader) checkPointBlocks; | |
| /// @dev For checking if an MMR Root is stored in this contract | |
| mapping(uint256 => mapping(bytes32 => bool)) epochMmrRoots; | |
| uint8 relayerThreshold; // max number of relayers allowed | |
| event RelayerThresholdChanged(uint256 newThreshold); | |
| event RelayerAdded(address relayer); | |
| event RelayerRemoved(address relayer); | |
| bytes32 public constant RELAYER_ROLE = keccak256("RELAYER_ROLE"); | |
| modifier onlyAdmin() { | |
| require(hasRole(DEFAULT_ADMIN_ROLE, msg.sender), "sender doesn't have admin role"); | |
| _; | |
| } | |
| modifier onlyRelayers() { | |
| require(hasRole(RELAYER_ROLE, msg.sender), "sender doesn't have relayer role"); | |
| _; | |
| } | |
| function adminPauseLightClient() external onlyAdmin { | |
| _pause(); | |
| } | |
| function adminUnpauseLightClient() external onlyAdmin { | |
| _unpause(); | |
| } | |
| function renounceAdmin(address newAdmin) external onlyAdmin { | |
| require(msg.sender != newAdmin, 'cannot renounce self'); | |
| grantRole(DEFAULT_ADMIN_ROLE, newAdmin); | |
| renounceRole(DEFAULT_ADMIN_ROLE, msg.sender); | |
| } | |
| function adminChangeRelayerThreshold(uint256 newThreshold) external onlyAdmin { | |
| relayerThreshold = newThreshold.toUint8(); | |
| emit RelayerThresholdChanged(newThreshold); | |
| } | |
| function adminAddRelayer(address relayerAddress) external onlyAdmin { | |
| require(!hasRole(RELAYER_ROLE, relayerAddress), "addr already has relayer role!"); | |
| grantRole(RELAYER_ROLE, relayerAddress); | |
| emit RelayerAdded(relayerAddress); | |
| } | |
| function adminRemoveRelayer(address relayerAddress) external onlyAdmin { | |
| require(hasRole(RELAYER_ROLE, relayerAddress), "addr doesn't have relayer role!"); | |
| revokeRole(RELAYER_ROLE, relayerAddress); | |
| emit RelayerRemoved(relayerAddress); | |
| } | |
| /// @dev Initialize contract | |
| /// @param firstRlpHeader RLP encorded first block header to store | |
| /// @param initialRelayers Addresses of relayers permissioned to feed this contract Harmony checkpoint block headers | |
| /// @param initialRelayerThreshold Maximum number of relayers allowed | |
| function initialize( | |
| bytes memory firstRlpHeader, | |
| address[] memory initialRelayers, | |
| uint8 initialRelayerThreshold | |
| ) external initializer { | |
| // Parse firstRlpHeader to firstBlock | |
| HarmonyParser.BlockHeader memory header = HarmonyParser.toBlockHeader( | |
| firstRlpHeader | |
| ); | |
| firstBlock.parentHash = header.parentHash; | |
| firstBlock.stateRoot = header.stateRoot; | |
| firstBlock.transactionsRoot = header.transactionsRoot; | |
| firstBlock.receiptsRoot = header.receiptsRoot; | |
| firstBlock.number = header.number; | |
| firstBlock.epoch = header.epoch; | |
| firstBlock.shard = header.shardID; | |
| firstBlock.time = header.timestamp; | |
| firstBlock.mmrRoot = HarmonyParser.toBytes32(header.mmrRoot); | |
| firstBlock.hash = header.hash; | |
| // Store firstBlock and update corresponding mappings | |
| epochCheckPointBlockNumbers[header.epoch].push(header.number); | |
| checkPointBlocks[header.number] = firstBlock; | |
| epochMmrRoots[header.epoch][firstBlock.mmrRoot] = true; | |
| // Permit relayers | |
| relayerThreshold = initialRelayerThreshold; | |
| _setupRole(DEFAULT_ADMIN_ROLE, msg.sender); | |
| for (uint256 i; i < initialRelayers.length; i++) { | |
| grantRole(RELAYER_ROLE, initialRelayers[i]); | |
| } | |
| } | |
| /// @notice Store a Harmony checkpoint block header in this contract, called by permissioned relayers | |
| /// @param rlpHeader RLP encoded block header to store | |
| function submitCheckpoint(bytes memory rlpHeader) external onlyRelayers whenNotPaused { | |
| // Parse rlpHeader to checkPoint Block | |
| HarmonyParser.BlockHeader memory header = HarmonyParser.toBlockHeader( | |
| rlpHeader | |
| ); | |
| BlockHeader memory checkPointBlock; | |
| checkPointBlock.parentHash = header.parentHash; | |
| checkPointBlock.stateRoot = header.stateRoot; | |
| checkPointBlock.transactionsRoot = header.transactionsRoot; | |
| checkPointBlock.receiptsRoot = header.receiptsRoot; | |
| checkPointBlock.number = header.number; | |
| checkPointBlock.epoch = header.epoch; | |
| checkPointBlock.shard = header.shardID; | |
| checkPointBlock.time = header.timestamp; | |
| checkPointBlock.mmrRoot = HarmonyParser.toBytes32(header.mmrRoot); | |
| checkPointBlock.hash = header.hash; | |
| // Store checkPointBlock and update corresponding mappings | |
| epochCheckPointBlockNumbers[header.epoch].push(header.number); | |
| checkPointBlocks[header.number] = checkPointBlock; | |
| epochMmrRoots[header.epoch][checkPointBlock.mmrRoot] = true; | |
| // Emit event of the checkpoint block header stored | |
| emit CheckPoint( | |
| checkPointBlock.stateRoot, | |
| checkPointBlock.transactionsRoot, | |
| checkPointBlock.receiptsRoot, | |
| checkPointBlock.number, | |
| checkPointBlock.epoch, | |
| checkPointBlock.shard, | |
| checkPointBlock.time, | |
| checkPointBlock.mmrRoot, | |
| checkPointBlock.hash | |
| ); | |
| } | |
| /// @notice Retrieve an epoch's latest checkpoint block header stored in this contract | |
| function getLatestCheckPoint(uint256 blockNumber, uint256 epoch) | |
| public | |
| view | |
| returns (BlockHeader memory checkPointBlock) | |
| { | |
| // Check if any checkpoint block header is stored for the specified epoch | |
| require( | |
| epochCheckPointBlockNumbers[epoch].length > 0, | |
| "no checkpoints for epoch" | |
| ); | |
| // Get the epoch's latest checkpoint block header stored | |
| uint256[] memory checkPointBlockNumbers = epochCheckPointBlockNumbers[epoch]; | |
| uint256 nearest = 0; | |
| for (uint256 i = 0; i < checkPointBlockNumbers.length; i++) { | |
| uint256 checkPointBlockNumber = checkPointBlockNumbers[i]; | |
| if ( | |
| checkPointBlockNumber > blockNumber && | |
| checkPointBlockNumber < nearest | |
| ) { | |
| nearest = checkPointBlockNumber; | |
| } | |
| } | |
| // Return result | |
| checkPointBlock = checkPointBlocks[nearest]; | |
| } | |
| /// @notice Check if an epoch has a checkpoint MMR Root stored in this contract | |
| function isValidCheckPoint(uint256 epoch, bytes32 mmrRoot) public view returns (bool status) { | |
| return epochMmrRoots[epoch][mmrRoot]; | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // SPDX-License-Identifier: UNLICENSED | |
| pragma solidity 0.7.3; | |
| pragma experimental ABIEncoderV2; | |
| import "./HarmonyLightClient.sol"; | |
| import "./lib/MMRVerifier.sol"; | |
| import "./HarmonyProver.sol"; | |
| import "./TokenLocker.sol"; | |
| import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; | |
| /// @dev Deploy on Ethereum | |
| /// Handle Harmony specific properties on top of TokenLocker.sol | |
| contract TokenLockerOnEthereum is TokenLocker, OwnableUpgradeable { | |
| HarmonyLightClient public lightclient; | |
| mapping(bytes32 => bool) public spentReceipt; | |
| function initialize() external initializer { | |
| __Ownable_init(); | |
| } | |
| function changeLightClient(HarmonyLightClient newClient) | |
| external | |
| onlyOwner | |
| { | |
| lightclient = newClient; | |
| } | |
| function bind(address otherSide) external onlyOwner { | |
| otherSideBridge = otherSide; | |
| } | |
| /// @notice Verify if a transaction is in/excluded in the transaction Merkle-Patricia-Trie (MPT) of a block. | |
| /// If yes, TokenLocker.execute accordingly. | |
| /// E.g. Verify if a bridge deposit on Harmony exists. | |
| /// If yes, withdraw corresponding token to the receipient here on Ethereum. | |
| /// @param header Block header of the block containing the transaction | |
| /// @param mmrProof Merkle Mountain Range (MMR) in/exclusion proof | |
| /// for proving block header inclusion in the block header MMR of Harmony | |
| /// @param receiptdata MPT in/exclusion proof of the transaction leaf we are proving | |
| function validateAndExecuteProof( | |
| HarmonyParser.BlockHeader memory header, | |
| MMRVerifier.MMRProof memory mmrProof, | |
| MPT.MerkleProof memory receiptdata | |
| ) external { | |
| /// Check is the block header's MMR root a valid checkping for the block's epoch | |
| require(lightclient.isValidCheckPoint(header.epoch, mmrProof.root), "checkpoint validation failed"); | |
| // Get header hash of block | |
| bytes32 blockHash = HarmonyParser.getBlockHash(header); | |
| // Get root hash of the transaction MPT in the block | |
| bytes32 rootHash = header.receiptsRoot; | |
| // Verify if the block header exists by checking its inclusion in the block header MMR of Harmony | |
| (bool status, string memory message) = HarmonyProver.verifyHeader( | |
| header, | |
| mmrProof | |
| ); | |
| require(status, "block header could not be verified"); | |
| // Check if transaction is unspent | |
| bytes32 receiptHash = keccak256( | |
| abi.encodePacked(blockHash, rootHash, receiptdata.key) | |
| ); | |
| require(spentReceipt[receiptHash] == false, "double spent!"); | |
| // Verify if the transaction is in/excluded in the transaction MPT | |
| // For successful verification, status = true | |
| // Return error if unsuccessful | |
| (status, message) = HarmonyProver.verifyReceipt(header, receiptdata); | |
| require(status, "receipt data could not be verified"); | |
| // Mark transaction as spent | |
| spentReceipt[receiptHash] = true; | |
| // Act accordingly to the transaction | |
| // e.g. If transaction was a lock deposit, mint corresponding token to receipient | |
| // If transaction was a burn deposit, transfer corresponding token to receipient | |
| uint256 executedEvents = execute(receiptdata.expectedValue); | |
| // Check if any action was performed | |
| require(executedEvents > 0, "no valid event"); | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // SPDX-License-Identifier: UNLICENSED | |
| pragma solidity 0.7.3; | |
| pragma experimental ABIEncoderV2; | |
| import "@openzeppelin/contracts-upgradeable/math/SafeMathUpgradeable.sol"; | |
| import "./EthereumLightClient.sol"; | |
| import "./EthereumProver.sol"; | |
| import "./TokenLocker.sol"; | |
| /// @dev Deploy on Harmony | |
| /// Handle Ethereum specific properties on top of TokenLocker.sol | |
| contract TokenLockerOnHarmony is TokenLocker, OwnableUpgradeable { | |
| using RLPReader for RLPReader.RLPItem; | |
| using RLPReader for bytes; | |
| using SafeMathUpgradeable for uint256; | |
| using SafeERC20Upgradeable for IERC20Upgradeable; | |
| EthereumLightClient public lightclient; | |
| mapping(bytes32 => bool) public spentReceipt; | |
| function initialize() external initializer { | |
| __Ownable_init(); | |
| } | |
| function changeLightClient(EthereumLightClient newClient) | |
| external | |
| onlyOwner | |
| { | |
| lightclient = newClient; | |
| } | |
| function bind(address otherSide) external onlyOwner { | |
| otherSideBridge = otherSide; | |
| } | |
| /// @notice Verify if a transaction is in/excluded in the transaction Merkle-Patricia-Trie (MPT) of a block. | |
| /// If yes, TokenLocker.execute accordingly. | |
| /// E.g. Verify if a bridge deposit on Ethereum exists. | |
| /// If yes, withdraw corresponding token to the receipient here on Harmony. | |
| /// @param blockNo Block number of the block containing the transaction | |
| /// @param rootHash Root hash of the transaction MPT in the block | |
| /// @param mptkey Location of the transaction leaf whose in/exclusion we are proving | |
| /// @param proof MPT in/exclusion proof | |
| function validateAndExecuteProof( | |
| uint256 blockNo, | |
| bytes32 rootHash, | |
| bytes calldata mptkey, | |
| bytes calldata proof | |
| ) external { | |
| // Get header hash of block | |
| bytes32 blockHash = bytes32(lightclient.blocksByHeight(blockNo, 0)); | |
| // Check if receiptsRoot of block blockHash in light client contract storage matches the rootHash provided | |
| require( | |
| lightclient.VerifyReceiptsHash(blockHash, rootHash), | |
| "wrong receipt hash" | |
| ); | |
| // Check if transaction is unspent | |
| bytes32 receiptHash = keccak256( | |
| abi.encodePacked(blockHash, rootHash, mptkey) | |
| ); | |
| require(spentReceipt[receiptHash] == false, "double spent!"); | |
| // Verify if the transaction is in/excluded in the transaction MPT | |
| // For successful inclusion verification, rlpdata = value of the transaction | |
| // For successful exclusion verification, rlpdata = empty byte array | |
| // Revert and fail if unsuccessful | |
| bytes memory rlpdata = EthereumProver.validateMPTProof( | |
| rootHash, | |
| mptkey, | |
| proof | |
| ); | |
| // Mark transaction as spent | |
| spentReceipt[receiptHash] = true; | |
| // Act accordingly to the transaction | |
| // e.g. If transaction was a lock deposit, mint corresponding token to receipient | |
| // If transaction was a burn deposit, transfer corresponding token to receipient | |
| uint256 executedEvents = execute(rlpdata); | |
| // Check if any action was performed | |
| require(executedEvents > 0, "no valid event"); | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment