diff --git a/contracts/_abstract/BuddleBridge.sol b/contracts/_abstract/BuddleBridge.sol index e3cc3d2..b325889 100644 --- a/contracts/_abstract/BuddleBridge.sol +++ b/contracts/_abstract/BuddleBridge.sol @@ -115,6 +115,8 @@ abstract contract BuddleBridge is IBuddleBridge, Ownable { /** * @inheritdoc IBuddleBridge */ + // TODO: Move this to a common state contract because otherwise we'd need to call the + // same function on every bridge contract every time BuddleSrcX is added on any X chain. function addBuddleBridge( uint256 _chain, address _contract diff --git a/contracts/polygon/BuddleBridgePolygon.sol b/contracts/polygon/BuddleBridgePolygon.sol new file mode 100644 index 0000000..4aaa89e --- /dev/null +++ b/contracts/polygon/BuddleBridgePolygon.sol @@ -0,0 +1,164 @@ +// SPDX-License-Identifier: LGPL-3.0-or-later +pragma solidity ^0.8.11; + +import "../_abstract/BuddleBridge.sol"; + +import "@eth-optimism/contracts/L1/messaging/IL1StandardBridge.sol"; +import "@eth-optimism/contracts/libraries/bridge/ICrossDomainMessenger.sol"; + + +/* +Goeril: "RootChainManager": "0x0d2ae21bf7e72f646ef8e1f2aa92edbd13588691", +Mumbai: "ChildChainManager": "0xb5505a6d998549090530911180f38aC5130101c6" +: + +*/ + +/** + * + * + */ +contract BuddleBridgePolygon is BuddleBridge { + using SafeERC20 for IERC20; + + uint256 constant public CHAIN = 80001; // Polygon mumbai + + address public messenger; + address public stdBridge; + + /********************** + * onlyOwner functions * + ***********************/ + + /** + * Initialize the contract with state variables + * + * @param _version Contract version + * @param _messenger The address of the L1 Cross Domain Messenger Contract + * @param _stdBridge The address of the L1 Standard Token Bridge + */ + function initialize( + bytes32 _version, + address _messenger, + address _stdBridge + ) external onlyOwner { + require(bytes32(VERSION) == bytes32(""), "Contract already initialized!"); + + VERSION = _version; + messenger = _messenger; + stdBridge = _stdBridge; + + buddleBridge[CHAIN] = address(this); + knownBridges[address(this)] = true; + } + + function updateXDomainMessenger( + address _newMessengerAddress + ) external onlyOwner checkInitialization { + messenger = _newMessengerAddress; + } + + function updateStandardBridge( + address _newBridgeAddress + ) external onlyOwner checkInitialization { + stdBridge = _newBridgeAddress; + } + + /********************** + * public functions * + ***********************/ + + /** + * @inheritdoc IBuddleBridge + */ + function claimBounty( + bytes32 _ticket, + uint _chain, + address[] memory _tokens, + uint256[] memory _amounts, + uint256[] memory _bounty, + uint256 _firstIdForTicket, + uint256 _lastIdForTicket, + bytes32 stateRoot + ) external payable + checkInitialization { + + ICrossDomainMessenger(messenger).sendMessage( + buddle.source, + abi.encodeWithSignature( + "confirmTicket(bytes32,uint256,address[],uint256[],uint256[],uint256,uint256,bytes32,address)", + _ticket, _chain, _tokens, _amounts, _bounty, _firstIdForTicket, _lastIdForTicket, stateRoot, msg.sender + ), + 1000000 + ); + + IBuddleBridge _bridge = IBuddleBridge(buddleBridge[_chain]); + _bridge.transferFunds{value: msg.value}(_tokens, _amounts, msg.sender, _ticket); + _bridge.approveRoot(stateRoot); + + } + + /** + * @inheritdoc IBuddleBridge + */ + function transferFunds( + address[] memory _tokens, + uint256[] memory _amounts, + address bountySeeker, + bytes32 _ticket + ) external payable + checkInitialization + onlyKnownBridge { + + IL1StandardBridge _bridge = IL1StandardBridge(stdBridge); + + for(uint i=0; i < _tokens.length; i++) { + if(_tokens[i] == BASE_TOKEN_ADDRESS) { + require(msg.value >= _amounts[i], "Insufficient funds sent"); + _bridge.depositETHTo{value: msg.value}( + buddle.destination, + 1000000, + bytes("") + ); + } else { + IERC20 token = IERC20(_tokens[i]); + require(token.balanceOf(bountySeeker) >= _amounts[i], "Insufficient funds sent"); + + token.safeTransferFrom(bountySeeker, address(this), _amounts[i]); + token.approve(messenger, _amounts[i]); + + _bridge.depositERC20To( + tokenMap[_tokens[i]], // L1 token address + _tokens[i], // L2 token address + buddle.destination, // to address + _amounts[i], // amount to be transferred + 1000000, // Gas limit + bytes("") // Data empty + ); + } + } + + emit FundsBridged(CHAIN, _tokens, _amounts, block.timestamp, _ticket); + + } + + /** + * @inheritdoc IBuddleBridge + */ + function approveRoot( + bytes32 _root + ) external + checkInitialization + onlyKnownBridge { + + ICrossDomainMessenger(messenger).sendMessage( + buddle.destination, + abi.encodeWithSignature( + "approveStateRoot(uint256,bytes32)", + CHAIN, _root + ), + 1000000 + ); + + } +} \ No newline at end of file diff --git a/contracts/polygon/BuddleDestPolygon.sol b/contracts/polygon/BuddleDestPolygon.sol new file mode 100644 index 0000000..669bc85 --- /dev/null +++ b/contracts/polygon/BuddleDestPolygon.sol @@ -0,0 +1,49 @@ +// SPDX-License-Identifier: LGPL-3.0-or-later +pragma solidity ^0.8.11; + +import "../_abstract/BuddleDestination.sol"; + +import "@eth-optimism/contracts/libraries/bridge/ICrossDomainMessenger.sol"; + +contract BuddleDestPolygon is BuddleDestination { + + address public messenger; + + /********************** + * onlyOwner functions * + ***********************/ + + /** + * Change the layer-2 cross domain messenger + * + * @param _messenger Layer-2 Cross Domain messenger contract + */ + function setXDomainMessenger( + address _messenger + ) external onlyOwner checkInitialization { + messenger = _messenger; + } + + /** + * Change the layer-2 cross domain messenger + * + * @param _newMessenger Layer-2 Cross Domain messenger contract + */ + function updateXDomainMessenger( + address _newMessenger + ) external onlyOwner checkInitialization { + messenger = _newMessenger; + } + + /********************** + * internal functions * + ***********************/ + + /** + * @inheritdoc BuddleDestination + */ + function isBridgeContract() internal view override returns (bool) { + return (msg.sender == messenger && + ICrossDomainMessenger(messenger).xDomainMessageSender() == buddleBridge); + } +} diff --git a/contracts/polygon/BuddleSrcPolygon.sol b/contracts/polygon/BuddleSrcPolygon.sol new file mode 100644 index 0000000..e8f319e --- /dev/null +++ b/contracts/polygon/BuddleSrcPolygon.sol @@ -0,0 +1,86 @@ +// SPDX-License-Identifier: LGPL-3.0-or-later +pragma solidity ^0.8.11; + +import "../_abstract/BuddleSource.sol"; + +import { FxStateChildTunnel } from "./FxStateChildTunnel.sol"; +import { IFxMessageProcessor } from "./ext/FXBaseChildTunnel.sol"; + + +/** + * + * + */ +contract BuddleSrcPolygon is BuddleSource { + using SafeERC20 for IERC20; + + uint256 constant public CHAIN = 80001; // Polygon mumbai + + IFxMessageProcessor public fxChildTunnel; + + bool public isInitiated = false; + + /********************** + * onlyOwner functions * + ***********************/ + + /** + * Deploy new instance of FxStateChildTunnel and initialize contract + * + * @param _fxChild Polygon FxBaseChildTunnel address + */ + function setBuddleSrcPolygon(address _fxChild) external checkInitialization onlyOwner { + require(isInitiated == false, "Already initiated"); + fxChildTunnel = new FxStateChildTunnel(_fxChild); + isInitiated = true; + } + + function modifyFxStateChildTunnel(address _fxChild) external onlyOwner { + fxChildTunnel = _fxChild; + } + + + /********************** + * internal functions * + ***********************/ + + /** + * @inheritdoc BuddleSource + */ + function isBridgeContract() internal view override returns (bool) { + return (msg.sender == address(fxChildTunnel)); + } + + /** + * @inheritdoc BuddleSource + */ + function _emitTransfer( + TransferData memory _data, + uint256 _id, + bytes32 _node + ) internal override { + emit TransferStarted(_data, _id, _node, CHAIN); + } + + /** + * @inheritdoc BuddleSource + */ + function _bridgeFunds( + uint256 _destChain, + address[] memory _tokens, + uint256[] memory _tokenAmounts, + uint256[] memory _bountyAmounts, + address _provider + ) internal override { + + for (uint n = 0; n < _tokens.length; n++) { + if(_tokens[n] == BASE_TOKEN_ADDRESS) { + address(0).transfer(_tokenAmounts[0]); + } else { + IERC20(_token[n]).transfer(address(0), _tokenAmounts[n]); + } + tokenAmounts[_destChain][_tokens[n]] -= _tokenAmounts[n]; + bountyAmounts[_destChain][_tokens[n]] -= _bountyAmounts[n]; + } + } +} \ No newline at end of file diff --git a/contracts/polygon/FxStateChildTunnel.sol b/contracts/polygon/FxStateChildTunnel.sol new file mode 100644 index 0000000..b2849c7 --- /dev/null +++ b/contracts/polygon/FxStateChildTunnel.sol @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import { FxBaseChildTunnel } from "./ext/FxBaseChildTunnel.sol"; + +/** + * @title FxStateChildTunnel + */ +contract FxStateChildTunnel is FxBaseChildTunnel { + uint256 public latestStateId; + address public latestRootMessageSender; +( bytes public latestData; +) + constructor(address _fxChild) FxBaseChildTunnel(_fxChild) {} + + function _processMessageFromRoot( + uint256 stateId, + address sender, + bytes memory data + ) internal override validateSender(sender) { + latestStateId = stateId; + latestRootMessageSender = sender; + latestData = data; + } + + function sendMessageToRoot(bytes memory message) public { + _sendMessageToRoot(message); + } +} diff --git a/contracts/polygon/FxStateRootTunnel.sol b/contracts/polygon/FxStateRootTunnel.sol new file mode 100644 index 0000000..a590d73 --- /dev/null +++ b/contracts/polygon/FxStateRootTunnel.sol @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import {FxBaseRootTunnel} from "./ext/FxBaseRootTunnel.sol"; + +/** + * @title FxStateRootTunnel + */ +contract FxStateRootTunnel is FxBaseRootTunnel { + bytes public latestData; + + constructor(address _checkpointManager, address _fxRoot) FxBaseRootTunnel(_checkpointManager, _fxRoot) {} + + function _processMessageFromChild(bytes memory data) internal override { + latestData = data; + } + + function sendMessageToChild(bytes memory message) public { + _sendMessageToChild(message); + } +} diff --git a/contracts/polygon/README.md b/contracts/polygon/README.md new file mode 100644 index 0000000..7550f73 --- /dev/null +++ b/contracts/polygon/README.md @@ -0,0 +1,50 @@ +## Deployed Contracts + +### Optimism Kovan +> Chain ID: 69 + +- Source : [`0x923266F0F6D3a66Ad23a6cD8CEb3512f24258B85`](https://kovan-optimistic.etherscan.io/address/0x923266F0F6D3a66Ad23a6cD8CEb3512f24258B85#code) +- Bridge : [`0xe396721BF9FD7c320c3c528077428847c4940C65`](https://kovan.etherscan.io/address/0xe396721BF9FD7c320c3c528077428847c4940C65#code) +- Destination : [`0x556591FABb4cCc4a417093d2a991713E1ba58372`](https://kovan-optimistic.etherscan.io/address/0x556591FABb4cCc4a417093d2a991713E1ba58372#code) + +### Optimism Mainnet +> Chain ID: 10 + +COMING SOON + +## Initiation Values + +[BuddleSrcOptimism.sol](BuddleSrcOptimism.sol) + +| func | var | Kovan | +| --- | --- | --- | +| initialize | _version | 0x76302e312e3000000000000000000000000000000000000000000000000000 | +| | _feeBasisPoints | 5 | +| | _feeRampUp | 60 | +| | _buddleBridge | 0xe396721BF9FD7c320c3c528077428847c4940C65 | +| setXDomainMessenger | _messenger | 0x4200000000000000000000000000000000000007 | +| | _stdBridge | 0x4200000000000000000000000000000000000010 | +| addDestination | _chain | 69 | +| | _contract | 0x556591FABb4cCc4a417093d2a991713E1ba58372 | + + +[BuddleBridgeOptimism.sol](BuddleBridgeOptimism.sol) + +| func | var | Kovan | +| --- | --- | --- | +| initialize | _version | 0x76302e312e3000000000000000000000000000000000000000000000000000 | +| | _messenger | 0x4361d0f75a0186c05f971c566dc6bea5957483fd | +| | _stdBridge | 0x22f24361d548e5faafb36d1437839f080363982b | +| setSource | _src | 0x923266F0F6D3a66Ad23a6cD8CEb3512f24258B85 | +| setDestination | _dest | 0x556591FABb4cCc4a417093d2a991713E1ba58372 | +| addTokenMap | _l2TokenAddress | 0x0000000000000000000000000000000000000000 | +| | _l1TokenAddress | 0x0000000000000000000000000000000000000000 | + + +[BuddleDestOptimism.sol](BuddleDestOptimism.sol) + +| func | var | Kovan | +| --- | --- | --- | +| initialize | _version | 0x76302e312e3000000000000000000000000000000000000000000000000000 | +| | _buddleBridge | 0xe396721BF9FD7c320c3c528077428847c4940C65 | +| setXDomainMessenger | _messenger | 0x4200000000000000000000000000000000000007 | diff --git a/contracts/polygon/ext/FxBaseChildTunnel.sol b/contracts/polygon/ext/FxBaseChildTunnel.sol new file mode 100644 index 0000000..63e45cf --- /dev/null +++ b/contracts/polygon/ext/FxBaseChildTunnel.sol @@ -0,0 +1,78 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +// IFxMessageProcessor represents interface to process message +interface IFxMessageProcessor { + function processMessageFromRoot( + uint256 stateId, + address rootMessageSender, + bytes calldata data + ) external; +} + +/** + * @notice Mock child tunnel contract to receive and send message from L2 + */ +abstract contract FxBaseChildTunnel is IFxMessageProcessor { + // MessageTunnel on L1 will get data from this event + event MessageSent(bytes message); + + // fx child + address public fxChild; + + // fx root tunnel + address public fxRootTunnel; + + constructor(address _fxChild) { + fxChild = _fxChild; + } + + // Sender must be fxRootTunnel in case of ERC20 tunnel + modifier validateSender(address sender) { + require(sender == fxRootTunnel, "FxBaseChildTunnel: INVALID_SENDER_FROM_ROOT"); + _; + } + + // set fxRootTunnel if not set already + function setFxRootTunnel(address _fxRootTunnel) external virtual { + require(fxRootTunnel == address(0x0), "FxBaseChildTunnel: ROOT_TUNNEL_ALREADY_SET"); + fxRootTunnel = _fxRootTunnel; + } + + function processMessageFromRoot( + uint256 stateId, + address rootMessageSender, + bytes calldata data + ) external override { + require(msg.sender == fxChild, "FxBaseChildTunnel: INVALID_SENDER"); + _processMessageFromRoot(stateId, rootMessageSender, data); + } + + /** + * @notice Emit message that can be received on Root Tunnel + * @dev Call the internal function when need to emit message + * @param message bytes message that will be sent to Root Tunnel + * some message examples - + * abi.encode(tokenId); + * abi.encode(tokenId, tokenMetadata); + * abi.encode(messageType, messageData); + */ + function _sendMessageToRoot(bytes memory message) internal { + emit MessageSent(message); + } + + /** + * @notice Process message received from Root Tunnel + * @dev function needs to be implemented to handle message as per requirement + * This is called by onStateReceive function. + * Since it is called via a system call, any event will not be emitted during its execution. + * @param stateId unique state id + * @param sender root message sender + * @param message bytes message that was sent from Root Tunnel + */ + function _processMessageFromRoot( + uint256 stateId, + address sender, + bytes memory message + ) internal virtual; +} \ No newline at end of file diff --git a/contracts/polygon/ext/FxBaseRootTunnel.sol b/contracts/polygon/ext/FxBaseRootTunnel.sol new file mode 100644 index 0000000..6344620 --- /dev/null +++ b/contracts/polygon/ext/FxBaseRootTunnel.sol @@ -0,0 +1,179 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import { RLPReader } from "./lib/RLPReader.sol"; +import { MerklePatriciaProof } from "./lib/MerklePatriciaProof.sol"; +import { Merkle } from "./lib/Merkle.sol"; +import "./lib/ExitPayloadReader.sol"; + +interface IFxStateSender { + function sendMessageToChild(address _receiver, bytes calldata _data) external; +} + +contract ICheckpointManager { + struct HeaderBlock { + bytes32 root; + uint256 start; + uint256 end; + uint256 createdAt; + address proposer; + } + + /** + * @notice mapping of checkpoint header numbers to block details + * @dev These checkpoints are submited by plasma contracts + */ + mapping(uint256 => HeaderBlock) public headerBlocks; +} + +abstract contract FxBaseRootTunnel { + using RLPReader for RLPReader.RLPItem; + using Merkle for bytes32; + using ExitPayloadReader for bytes; + using ExitPayloadReader for ExitPayloadReader.ExitPayload; + using ExitPayloadReader for ExitPayloadReader.Log; + using ExitPayloadReader for ExitPayloadReader.LogTopics; + using ExitPayloadReader for ExitPayloadReader.Receipt; + + // keccak256(MessageSent(bytes)) + bytes32 public constant SEND_MESSAGE_EVENT_SIG = 0x8c5261668696ce22758910d05bab8f186d6eb247ceac2af2e82c7dc17669b036; + + // state sender contract + IFxStateSender public fxRoot; + // root chain manager + ICheckpointManager public checkpointManager; + // child tunnel contract which receives and sends messages + address public fxChildTunnel; + + // storage to avoid duplicate exits + mapping(bytes32 => bool) public processedExits; + + constructor(address _checkpointManager, address _fxRoot) { + checkpointManager = ICheckpointManager(_checkpointManager); + fxRoot = IFxStateSender(_fxRoot); + } + + // set fxChildTunnel if not set already + function setFxChildTunnel(address _fxChildTunnel) public virtual { + require(fxChildTunnel == address(0x0), "FxBaseRootTunnel: CHILD_TUNNEL_ALREADY_SET"); + fxChildTunnel = _fxChildTunnel; + } + + /** + * @notice Send bytes message to Child Tunnel + * @param message bytes message that will be sent to Child Tunnel + * some message examples - + * abi.encode(tokenId); + * abi.encode(tokenId, tokenMetadata); + * abi.encode(messageType, messageData); + */ + function _sendMessageToChild(bytes memory message) internal { + fxRoot.sendMessageToChild(fxChildTunnel, message); + } + + function _validateAndExtractMessage(bytes memory inputData) internal returns (bytes memory) { + ExitPayloadReader.ExitPayload memory payload = inputData.toExitPayload(); + + bytes memory branchMaskBytes = payload.getBranchMaskAsBytes(); + uint256 blockNumber = payload.getBlockNumber(); + // checking if exit has already been processed + // unique exit is identified using hash of (blockNumber, branchMask, receiptLogIndex) + bytes32 exitHash = keccak256( + abi.encodePacked( + blockNumber, + // first 2 nibbles are dropped while generating nibble array + // this allows branch masks that are valid but bypass exitHash check (changing first 2 nibbles only) + // so converting to nibble array and then hashing it + MerklePatriciaProof._getNibbleArray(branchMaskBytes), + payload.getReceiptLogIndex() + ) + ); + require(processedExits[exitHash] == false, "FxRootTunnel: EXIT_ALREADY_PROCESSED"); + processedExits[exitHash] = true; + + ExitPayloadReader.Receipt memory receipt = payload.getReceipt(); + ExitPayloadReader.Log memory log = receipt.getLog(); + + // check child tunnel + require(fxChildTunnel == log.getEmitter(), "FxRootTunnel: INVALID_FX_CHILD_TUNNEL"); + + bytes32 receiptRoot = payload.getReceiptRoot(); + // verify receipt inclusion + require( + MerklePatriciaProof.verify(receipt.toBytes(), branchMaskBytes, payload.getReceiptProof(), receiptRoot), + "FxRootTunnel: INVALID_RECEIPT_PROOF" + ); + + // verify checkpoint inclusion + _checkBlockMembershipInCheckpoint( + blockNumber, + payload.getBlockTime(), + payload.getTxRoot(), + receiptRoot, + payload.getHeaderNumber(), + payload.getBlockProof() + ); + + ExitPayloadReader.LogTopics memory topics = log.getTopics(); + + require( + bytes32(topics.getField(0).toUint()) == SEND_MESSAGE_EVENT_SIG, // topic0 is event sig + "FxRootTunnel: INVALID_SIGNATURE" + ); + + // received message data + bytes memory message = abi.decode(log.getData(), (bytes)); // event decodes params again, so decoding bytes to get message + return message; + } + + function _checkBlockMembershipInCheckpoint( + uint256 blockNumber, + uint256 blockTime, + bytes32 txRoot, + bytes32 receiptRoot, + uint256 headerNumber, + bytes memory blockProof + ) private view returns (uint256) { + (bytes32 headerRoot, uint256 startBlock, , uint256 createdAt, ) = checkpointManager.headerBlocks(headerNumber); + + require( + keccak256(abi.encodePacked(blockNumber, blockTime, txRoot, receiptRoot)).checkMembership( + blockNumber - startBlock, + headerRoot, + blockProof + ), + "FxRootTunnel: INVALID_HEADER" + ); + return createdAt; + } + + /** + * @notice receive message from L2 to L1, validated by proof + * @dev This function verifies if the transaction actually happened on child chain + * + * @param inputData RLP encoded data of the reference tx containing following list of fields + * 0 - headerNumber - Checkpoint header block number containing the reference tx + * 1 - blockProof - Proof that the block header (in the child chain) is a leaf in the submitted merkle root + * 2 - blockNumber - Block number containing the reference tx on child chain + * 3 - blockTime - Reference tx block time + * 4 - txRoot - Transactions root of block + * 5 - receiptRoot - Receipts root of block + * 6 - receipt - Receipt of the reference transaction + * 7 - receiptProof - Merkle proof of the reference receipt + * 8 - branchMask - 32 bits denoting the path of receipt in merkle tree + * 9 - receiptLogIndex - Log Index to read from the receipt + */ + function receiveMessage(bytes memory inputData) public virtual { + bytes memory message = _validateAndExtractMessage(inputData); + _processMessageFromChild(message); + } + + /** + * @notice Process message received from Child Tunnel + * @dev function needs to be implemented to handle message as per requirement + * This is called by onStateReceive function. + * Since it is called via a system call, any event will not be emitted during its execution. + * @param message bytes message that was sent from Child Tunnel + */ + function _processMessageFromChild(bytes memory message) internal virtual; +} \ No newline at end of file diff --git a/contracts/polygon/ext/interface/IRootChainManager.sol b/contracts/polygon/ext/interface/IRootChainManager.sol new file mode 100644 index 0000000..11e175d --- /dev/null +++ b/contracts/polygon/ext/interface/IRootChainManager.sol @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +interface IRootChainManager { + event TokenMapped( + address indexed rootToken, + address indexed childToken, + bytes32 indexed tokenType + ); + + event PredicateRegistered( + bytes32 indexed tokenType, + address indexed predicateAddress + ); + + function registerPredicate(bytes32 tokenType, address predicateAddress) + external; + + function mapToken( + address rootToken, + address childToken, + bytes32 tokenType + ) external; + + function depositEtherFor(address user) external payable; + + function depositFor( + address user, + address rootToken, + bytes calldata depositData + ) external; + + function exit(bytes calldata inputData) external; +} \ No newline at end of file diff --git a/contracts/polygon/ext/lib/Create2.sol b/contracts/polygon/ext/lib/Create2.sol new file mode 100644 index 0000000..ac808fb --- /dev/null +++ b/contracts/polygon/ext/lib/Create2.sol @@ -0,0 +1,39 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +// Create2 adds common methods for minimal proxy with create2 +abstract contract Create2 { + // creates clone using minimal proxy + function createClone(bytes32 _salt, address _target) internal returns (address _result) { + bytes20 _targetBytes = bytes20(_target); + + assembly { + let clone := mload(0x40) + mstore(clone, 0x3d602d80600a3d3981f3363d3d373d3d3d363d73000000000000000000000000) + mstore(add(clone, 0x14), _targetBytes) + mstore(add(clone, 0x28), 0x5af43d82803e903d91602b57fd5bf30000000000000000000000000000000000) + _result := create2(0, clone, 0x37, _salt) + } + + require(_result != address(0), "Create2: Failed on minimal deploy"); + } + + // get minimal proxy creation code + function minimalProxyCreationCode(address logic) internal pure returns (bytes memory) { + bytes10 creation = 0x3d602d80600a3d3981f3; + bytes10 prefix = 0x363d3d373d3d3d363d73; + bytes20 targetBytes = bytes20(logic); + bytes15 suffix = 0x5af43d82803e903d91602b57fd5bf3; + return abi.encodePacked(creation, prefix, targetBytes, suffix); + } + + // get computed create2 address + function computedCreate2Address( + bytes32 salt, + bytes32 bytecodeHash, + address deployer + ) public pure returns (address) { + bytes32 _data = keccak256(abi.encodePacked(bytes1(0xff), deployer, salt, bytecodeHash)); + return address(uint160(uint256(_data))); + } +} diff --git a/contracts/polygon/ext/lib/ExitPayloadReader.sol b/contracts/polygon/ext/lib/ExitPayloadReader.sol new file mode 100644 index 0000000..1136c2b --- /dev/null +++ b/contracts/polygon/ext/lib/ExitPayloadReader.sol @@ -0,0 +1,160 @@ +pragma solidity ^0.8.0; + +import {RLPReader} from "./RLPReader.sol"; + +library ExitPayloadReader { + using RLPReader for bytes; + using RLPReader for RLPReader.RLPItem; + + uint8 constant WORD_SIZE = 32; + + struct ExitPayload { + RLPReader.RLPItem[] data; + } + + struct Receipt { + RLPReader.RLPItem[] data; + bytes raw; + uint256 logIndex; + } + + struct Log { + RLPReader.RLPItem data; + RLPReader.RLPItem[] list; + } + + struct LogTopics { + RLPReader.RLPItem[] data; + } + + // copy paste of private copy() from RLPReader to avoid changing of existing contracts + function copy( + uint256 src, + uint256 dest, + uint256 len + ) private pure { + if (len == 0) return; + + // copy as many word sizes as possible + for (; len >= WORD_SIZE; len -= WORD_SIZE) { + assembly { + mstore(dest, mload(src)) + } + + src += WORD_SIZE; + dest += WORD_SIZE; + } + + // left over bytes. Mask is used to remove unwanted bytes from the word + uint256 mask = 256**(WORD_SIZE - len) - 1; + assembly { + let srcpart := and(mload(src), not(mask)) // zero out src + let destpart := and(mload(dest), mask) // retrieve the bytes + mstore(dest, or(destpart, srcpart)) + } + } + + function toExitPayload(bytes memory data) internal pure returns (ExitPayload memory) { + RLPReader.RLPItem[] memory payloadData = data.toRlpItem().toList(); + + return ExitPayload(payloadData); + } + + function getHeaderNumber(ExitPayload memory payload) internal pure returns (uint256) { + return payload.data[0].toUint(); + } + + function getBlockProof(ExitPayload memory payload) internal pure returns (bytes memory) { + return payload.data[1].toBytes(); + } + + function getBlockNumber(ExitPayload memory payload) internal pure returns (uint256) { + return payload.data[2].toUint(); + } + + function getBlockTime(ExitPayload memory payload) internal pure returns (uint256) { + return payload.data[3].toUint(); + } + + function getTxRoot(ExitPayload memory payload) internal pure returns (bytes32) { + return bytes32(payload.data[4].toUint()); + } + + function getReceiptRoot(ExitPayload memory payload) internal pure returns (bytes32) { + return bytes32(payload.data[5].toUint()); + } + + function getReceipt(ExitPayload memory payload) internal pure returns (Receipt memory receipt) { + receipt.raw = payload.data[6].toBytes(); + RLPReader.RLPItem memory receiptItem = receipt.raw.toRlpItem(); + + if (receiptItem.isList()) { + // legacy tx + receipt.data = receiptItem.toList(); + } else { + // pop first byte before parsting receipt + bytes memory typedBytes = receipt.raw; + bytes memory result = new bytes(typedBytes.length - 1); + uint256 srcPtr; + uint256 destPtr; + assembly { + srcPtr := add(33, typedBytes) + destPtr := add(0x20, result) + } + + copy(srcPtr, destPtr, result.length); + receipt.data = result.toRlpItem().toList(); + } + + receipt.logIndex = getReceiptLogIndex(payload); + return receipt; + } + + function getReceiptProof(ExitPayload memory payload) internal pure returns (bytes memory) { + return payload.data[7].toBytes(); + } + + function getBranchMaskAsBytes(ExitPayload memory payload) internal pure returns (bytes memory) { + return payload.data[8].toBytes(); + } + + function getBranchMaskAsUint(ExitPayload memory payload) internal pure returns (uint256) { + return payload.data[8].toUint(); + } + + function getReceiptLogIndex(ExitPayload memory payload) internal pure returns (uint256) { + return payload.data[9].toUint(); + } + + // Receipt methods + function toBytes(Receipt memory receipt) internal pure returns (bytes memory) { + return receipt.raw; + } + + function getLog(Receipt memory receipt) internal pure returns (Log memory) { + RLPReader.RLPItem memory logData = receipt.data[3].toList()[receipt.logIndex]; + return Log(logData, logData.toList()); + } + + // Log methods + function getEmitter(Log memory log) internal pure returns (address) { + return RLPReader.toAddress(log.list[0]); + } + + function getTopics(Log memory log) internal pure returns (LogTopics memory) { + return LogTopics(log.list[1].toList()); + } + + function getData(Log memory log) internal pure returns (bytes memory) { + return log.list[2].toBytes(); + } + + function toRlpBytes(Log memory log) internal pure returns (bytes memory) { + return log.data.toRlpBytes(); + } + + // LogTopics methods + function getField(LogTopics memory topics, uint256 index) internal pure returns (RLPReader.RLPItem memory) { + return topics.data[index]; + } +} diff --git a/contracts/polygon/ext/lib/Merkle.sol b/contracts/polygon/ext/lib/Merkle.sol new file mode 100755 index 0000000..2dd3a86 --- /dev/null +++ b/contracts/polygon/ext/lib/Merkle.sol @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +library Merkle { + function checkMembership( + bytes32 leaf, + uint256 index, + bytes32 rootHash, + bytes memory proof + ) internal pure returns (bool) { + require(proof.length % 32 == 0, "Invalid proof length"); + uint256 proofHeight = proof.length / 32; + // Proof of size n means, height of the tree is n+1. + // In a tree of height n+1, max #leafs possible is 2 ^ n + require(index < 2**proofHeight, "Leaf index is too big"); + + bytes32 proofElement; + bytes32 computedHash = leaf; + for (uint256 i = 32; i <= proof.length; i += 32) { + assembly { + proofElement := mload(add(proof, i)) + } + + if (index % 2 == 0) { + computedHash = keccak256(abi.encodePacked(computedHash, proofElement)); + } else { + computedHash = keccak256(abi.encodePacked(proofElement, computedHash)); + } + + index = index / 2; + } + return computedHash == rootHash; + } +} diff --git a/contracts/polygon/ext/lib/MerklePatriciaProof.sol b/contracts/polygon/ext/lib/MerklePatriciaProof.sol new file mode 100644 index 0000000..430d7dc --- /dev/null +++ b/contracts/polygon/ext/lib/MerklePatriciaProof.sol @@ -0,0 +1,137 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import {RLPReader} from "./RLPReader.sol"; + +library MerklePatriciaProof { + /* + * @dev Verifies a merkle patricia proof. + * @param value The terminating value in the trie. + * @param encodedPath The path in the trie leading to value. + * @param rlpParentNodes The rlp encoded stack of nodes. + * @param root The root hash of the trie. + * @return The boolean validity of the proof. + */ + function verify( + bytes memory value, + bytes memory encodedPath, + bytes memory rlpParentNodes, + bytes32 root + ) internal pure returns (bool) { + RLPReader.RLPItem memory item = RLPReader.toRlpItem(rlpParentNodes); + RLPReader.RLPItem[] memory parentNodes = RLPReader.toList(item); + + bytes memory currentNode; + RLPReader.RLPItem[] memory currentNodeList; + + bytes32 nodeKey = root; + uint256 pathPtr = 0; + + bytes memory path = _getNibbleArray(encodedPath); + if (path.length == 0) { + return false; + } + + for (uint256 i = 0; i < parentNodes.length; i++) { + if (pathPtr > path.length) { + return false; + } + + currentNode = RLPReader.toRlpBytes(parentNodes[i]); + if (nodeKey != keccak256(currentNode)) { + return false; + } + currentNodeList = RLPReader.toList(parentNodes[i]); + + if (currentNodeList.length == 17) { + if (pathPtr == path.length) { + if (keccak256(RLPReader.toBytes(currentNodeList[16])) == keccak256(value)) { + return true; + } else { + return false; + } + } + + uint8 nextPathNibble = uint8(path[pathPtr]); + if (nextPathNibble > 16) { + return false; + } + nodeKey = bytes32(RLPReader.toUintStrict(currentNodeList[nextPathNibble])); + pathPtr += 1; + } else if (currentNodeList.length == 2) { + uint256 traversed = _nibblesToTraverse(RLPReader.toBytes(currentNodeList[0]), path, pathPtr); + if (pathPtr + traversed == path.length) { + //leaf node + if (keccak256(RLPReader.toBytes(currentNodeList[1])) == keccak256(value)) { + return true; + } else { + return false; + } + } + + //extension node + if (traversed == 0) { + return false; + } + + pathPtr += traversed; + nodeKey = bytes32(RLPReader.toUintStrict(currentNodeList[1])); + } else { + return false; + } + } + } + + function _nibblesToTraverse( + bytes memory encodedPartialPath, + bytes memory path, + uint256 pathPtr + ) private pure returns (uint256) { + uint256 len = 0; + // encodedPartialPath has elements that are each two hex characters (1 byte), but partialPath + // and slicedPath have elements that are each one hex character (1 nibble) + bytes memory partialPath = _getNibbleArray(encodedPartialPath); + bytes memory slicedPath = new bytes(partialPath.length); + + // pathPtr counts nibbles in path + // partialPath.length is a number of nibbles + for (uint256 i = pathPtr; i < pathPtr + partialPath.length; i++) { + bytes1 pathNibble = path[i]; + slicedPath[i - pathPtr] = pathNibble; + } + + if (keccak256(partialPath) == keccak256(slicedPath)) { + len = partialPath.length; + } else { + len = 0; + } + return len; + } + + // bytes b must be hp encoded + function _getNibbleArray(bytes memory b) internal pure returns (bytes memory) { + bytes memory nibbles = ""; + if (b.length > 0) { + uint8 offset; + uint8 hpNibble = uint8(_getNthNibbleOfBytes(0, b)); + if (hpNibble == 1 || hpNibble == 3) { + nibbles = new bytes(b.length * 2 - 1); + bytes1 oddNibble = _getNthNibbleOfBytes(1, b); + nibbles[0] = oddNibble; + offset = 1; + } else { + nibbles = new bytes(b.length * 2 - 2); + offset = 0; + } + + for (uint256 i = offset; i < nibbles.length; i++) { + nibbles[i] = _getNthNibbleOfBytes(i - offset + 2, b); + } + } + return nibbles; + } + + function _getNthNibbleOfBytes(uint256 n, bytes memory str) private pure returns (bytes1) { + return bytes1(n % 2 == 0 ? uint8(str[n / 2]) / 0x10 : uint8(str[n / 2]) % 0x10); + } +} diff --git a/contracts/polygon/ext/lib/RLPReader.sol b/contracts/polygon/ext/lib/RLPReader.sol new file mode 100644 index 0000000..e190e00 --- /dev/null +++ b/contracts/polygon/ext/lib/RLPReader.sol @@ -0,0 +1,340 @@ +/* + * @author Hamdi Allam hamdi.allam97@gmail.com + * Please reach out with any questions or concerns + */ +pragma solidity ^0.8.0; + +library RLPReader { + uint8 constant STRING_SHORT_START = 0x80; + uint8 constant STRING_LONG_START = 0xb8; + uint8 constant LIST_SHORT_START = 0xc0; + uint8 constant LIST_LONG_START = 0xf8; + uint8 constant WORD_SIZE = 32; + + struct RLPItem { + uint256 len; + uint256 memPtr; + } + + struct Iterator { + RLPItem item; // Item that's being iterated over. + uint256 nextPtr; // Position of the next item in the list. + } + + /* + * @dev Returns the next element in the iteration. Reverts if it has not next element. + * @param self The iterator. + * @return The next element in the iteration. + */ + function next(Iterator memory self) internal pure returns (RLPItem memory) { + require(hasNext(self)); + + uint256 ptr = self.nextPtr; + uint256 itemLength = _itemLength(ptr); + self.nextPtr = ptr + itemLength; + + return RLPItem(itemLength, ptr); + } + + /* + * @dev Returns true if the iteration has more elements. + * @param self The iterator. + * @return true if the iteration has more elements. + */ + function hasNext(Iterator memory self) internal pure returns (bool) { + RLPItem memory item = self.item; + return self.nextPtr < item.memPtr + item.len; + } + + /* + * @param item RLP encoded bytes + */ + function toRlpItem(bytes memory item) internal pure returns (RLPItem memory) { + uint256 memPtr; + assembly { + memPtr := add(item, 0x20) + } + + return RLPItem(item.length, memPtr); + } + + /* + * @dev Create an iterator. Reverts if item is not a list. + * @param self The RLP item. + * @return An 'Iterator' over the item. + */ + function iterator(RLPItem memory self) internal pure returns (Iterator memory) { + require(isList(self)); + + uint256 ptr = self.memPtr + _payloadOffset(self.memPtr); + return Iterator(self, ptr); + } + + /* + * @param item RLP encoded bytes + */ + function rlpLen(RLPItem memory item) internal pure returns (uint256) { + return item.len; + } + + /* + * @param item RLP encoded bytes + */ + function payloadLen(RLPItem memory item) internal pure returns (uint256) { + return item.len - _payloadOffset(item.memPtr); + } + + /* + * @param item RLP encoded list in bytes + */ + function toList(RLPItem memory item) internal pure returns (RLPItem[] memory) { + require(isList(item)); + + uint256 items = numItems(item); + RLPItem[] memory result = new RLPItem[](items); + + uint256 memPtr = item.memPtr + _payloadOffset(item.memPtr); + uint256 dataLen; + for (uint256 i = 0; i < items; i++) { + dataLen = _itemLength(memPtr); + result[i] = RLPItem(dataLen, memPtr); + memPtr = memPtr + dataLen; + } + + return result; + } + + // @return indicator whether encoded payload is a list. negate this function call for isData. + function isList(RLPItem memory item) internal pure returns (bool) { + if (item.len == 0) return false; + + uint8 byte0; + uint256 memPtr = item.memPtr; + assembly { + byte0 := byte(0, mload(memPtr)) + } + + if (byte0 < LIST_SHORT_START) return false; + return true; + } + + /* + * @dev A cheaper version of keccak256(toRlpBytes(item)) that avoids copying memory. + * @return keccak256 hash of RLP encoded bytes. + */ + function rlpBytesKeccak256(RLPItem memory item) internal pure returns (bytes32) { + uint256 ptr = item.memPtr; + uint256 len = item.len; + bytes32 result; + assembly { + result := keccak256(ptr, len) + } + return result; + } + + function payloadLocation(RLPItem memory item) internal pure returns (uint256, uint256) { + uint256 offset = _payloadOffset(item.memPtr); + uint256 memPtr = item.memPtr + offset; + uint256 len = item.len - offset; // data length + return (memPtr, len); + } + + /* + * @dev A cheaper version of keccak256(toBytes(item)) that avoids copying memory. + * @return keccak256 hash of the item payload. + */ + function payloadKeccak256(RLPItem memory item) internal pure returns (bytes32) { + (uint256 memPtr, uint256 len) = payloadLocation(item); + bytes32 result; + assembly { + result := keccak256(memPtr, len) + } + return result; + } + + /** RLPItem conversions into data types **/ + + // @returns raw rlp encoding in bytes + function toRlpBytes(RLPItem memory item) internal pure returns (bytes memory) { + bytes memory result = new bytes(item.len); + if (result.length == 0) return result; + + uint256 ptr; + assembly { + ptr := add(0x20, result) + } + + copy(item.memPtr, ptr, item.len); + return result; + } + + // any non-zero byte is considered true + function toBoolean(RLPItem memory item) internal pure returns (bool) { + require(item.len == 1); + uint256 result; + uint256 memPtr = item.memPtr; + assembly { + result := byte(0, mload(memPtr)) + } + + return result == 0 ? false : true; + } + + function toAddress(RLPItem memory item) internal pure returns (address) { + // 1 byte for the length prefix + require(item.len == 21); + + return address(uint160(toUint(item))); + } + + function toUint(RLPItem memory item) internal pure returns (uint256) { + require(item.len > 0 && item.len <= 33); + + uint256 offset = _payloadOffset(item.memPtr); + uint256 len = item.len - offset; + + uint256 result; + uint256 memPtr = item.memPtr + offset; + assembly { + result := mload(memPtr) + + // shfit to the correct location if neccesary + if lt(len, 32) { + result := div(result, exp(256, sub(32, len))) + } + } + + return result; + } + + // enforces 32 byte length + function toUintStrict(RLPItem memory item) internal pure returns (uint256) { + // one byte prefix + require(item.len == 33); + + uint256 result; + uint256 memPtr = item.memPtr + 1; + assembly { + result := mload(memPtr) + } + + return result; + } + + function toBytes(RLPItem memory item) internal pure returns (bytes memory) { + require(item.len > 0); + + uint256 offset = _payloadOffset(item.memPtr); + uint256 len = item.len - offset; // data length + bytes memory result = new bytes(len); + + uint256 destPtr; + assembly { + destPtr := add(0x20, result) + } + + copy(item.memPtr + offset, destPtr, len); + return result; + } + + /* + * Private Helpers + */ + + // @return number of payload items inside an encoded list. + function numItems(RLPItem memory item) private pure returns (uint256) { + if (item.len == 0) return 0; + + uint256 count = 0; + uint256 currPtr = item.memPtr + _payloadOffset(item.memPtr); + uint256 endPtr = item.memPtr + item.len; + while (currPtr < endPtr) { + currPtr = currPtr + _itemLength(currPtr); // skip over an item + count++; + } + + return count; + } + + // @return entire rlp item byte length + function _itemLength(uint256 memPtr) private pure returns (uint256) { + uint256 itemLen; + uint256 byte0; + assembly { + byte0 := byte(0, mload(memPtr)) + } + + if (byte0 < STRING_SHORT_START) itemLen = 1; + else if (byte0 < STRING_LONG_START) itemLen = byte0 - STRING_SHORT_START + 1; + else if (byte0 < LIST_SHORT_START) { + assembly { + let byteLen := sub(byte0, 0xb7) // # of bytes the actual length is + memPtr := add(memPtr, 1) // skip over the first byte + /* 32 byte word size */ + let dataLen := div(mload(memPtr), exp(256, sub(32, byteLen))) // right shifting to get the len + itemLen := add(dataLen, add(byteLen, 1)) + } + } else if (byte0 < LIST_LONG_START) { + itemLen = byte0 - LIST_SHORT_START + 1; + } else { + assembly { + let byteLen := sub(byte0, 0xf7) + memPtr := add(memPtr, 1) + + let dataLen := div(mload(memPtr), exp(256, sub(32, byteLen))) // right shifting to the correct length + itemLen := add(dataLen, add(byteLen, 1)) + } + } + + return itemLen; + } + + // @return number of bytes until the data + function _payloadOffset(uint256 memPtr) private pure returns (uint256) { + uint256 byte0; + assembly { + byte0 := byte(0, mload(memPtr)) + } + + if (byte0 < STRING_SHORT_START) return 0; + else if (byte0 < STRING_LONG_START || (byte0 >= LIST_SHORT_START && byte0 < LIST_LONG_START)) return 1; + else if (byte0 < LIST_SHORT_START) + // being explicit + return byte0 - (STRING_LONG_START - 1) + 1; + else return byte0 - (LIST_LONG_START - 1) + 1; + } + + /* + * @param src Pointer to source + * @param dest Pointer to destination + * @param len Amount of memory to copy from the source + */ + function copy( + uint256 src, + uint256 dest, + uint256 len + ) private pure { + if (len == 0) return; + + // copy as many word sizes as possible + for (; len >= WORD_SIZE; len -= WORD_SIZE) { + assembly { + mstore(dest, mload(src)) + } + + src += WORD_SIZE; + dest += WORD_SIZE; + } + + if (len == 0) return; + + // left over bytes. Mask is used to remove unwanted bytes from the word + uint256 mask = 256**(WORD_SIZE - len) - 1; + + assembly { + let srcpart := and(mload(src), not(mask)) // zero out src + let destpart := and(mload(dest), mask) // retrieve the bytes + mstore(dest, or(destpart, srcpart)) + } + } +}