Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix/mpt #179

Merged
merged 10 commits into from
Dec 4, 2024
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,9 @@ jobs:
- name: Install npm dependencies
run: npm install

- name: generate interfaces
run: npm run generate:interfaces

# - name: Run Forge fmt
# run: |
# forge fmt --check
Expand Down
11 changes: 7 additions & 4 deletions contracts/lib/MerklePatriciaProof.sol
Original file line number Diff line number Diff line change
Expand Up @@ -69,16 +69,19 @@ library MerklePatriciaProof {
);
pathPtr += 1;
} else if (currentNodeList.length == 2) {
bytes memory nodeValue = RLPReader.toBytes(currentNodeList[0]);
uint256 traversed = _nibblesToTraverse(
RLPReader.toBytes(currentNodeList[0]),
nodeValue,
path,
pathPtr
);
//enforce correct nibble
bytes1 prefix = _getNthNibbleOfBytes(0, nodeValue);
if (pathPtr + traversed == path.length) {
//leaf node
if (
keccak256(RLPReader.toBytes(currentNodeList[1])) ==
keccak256(value)
keccak256(RLPReader.toBytes(currentNodeList[1])) == keccak256(value) &&
(prefix == bytes1(uint8(2)) || prefix == bytes1(uint8(3)))
) {
return true;
} else {
Expand All @@ -87,7 +90,7 @@ library MerklePatriciaProof {
}

//extension node
if (traversed == 0) {
if (traversed == 0 || (prefix != bytes1(uint8(0)) && prefix != bytes1(uint8(1)))) {
return false;
}

Expand Down
162 changes: 162 additions & 0 deletions forge/ExitPayloadReader.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
pragma solidity ^0.8.4;

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(uint src, uint dest, uint 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
uint 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];
}
}
146 changes: 146 additions & 0 deletions forge/ForkupgradeMPT.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
pragma solidity ^0.8.4;

import {RootChainManagerProxy} from "../scripts/helpers/interfaces/RootChainManagerProxy.generated.sol";
import {RootChainManager} from "../scripts/helpers/interfaces/RootChainManager.generated.sol";

import {ExitPayloadReader} from "./ExitPayloadReader.sol";
import {MerklePatriciaProof} from "./MerklePatriciaProof.sol";

import "forge-std/Test.sol";

struct TxObject {
address from;
bytes32 hash;
bytes input;
bool isError;
bytes mod_input;
bool txreceipt_status;
}

struct FileObject {
TxObject[] txObjects;
}

contract ForkupgradeMPT is Test {
using stdJson for string;

uint256 mainnetFork;
address rootChainManagerProxy = 0xA0c68C638235ee32657e8f720a23ceC1bFc77C77;
address timelock = 0xCaf0aa768A3AE1297DF20072419Db8Bb8b5C8cEf;

bytes32 notOwner721Error = keccak256(hex"08c379a0000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000294552433732313a207472616e73666572206f6620746f6b656e2074686174206973206e6f74206f776e0000000000000000000000000000000000000000000000");
bytes32 notOwnerNorApproved721Error = keccak256(hex"08c379a0000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000314552433732313a207472616e736665722063616c6c6572206973206e6f74206f776e6572206e6f7220617070726f766564000000000000000000000000000000");
bytes32 outOfBalance20Error = keccak256(hex"08c379a00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000002645524332303a205472616e7366657220616d6f756e7420657863656564732062616c616e63650000000000000000000000000000000000000000000000000000");
bytes32 exceedsBalance20Error = keccak256(hex"08c379a00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000002645524332303a207472616e7366657220616d6f756e7420657863656564732062616c616e63650000000000000000000000000000000000000000000000000000");
bytes32 safeERC20lowlevelError = keccak256(hex"08c379a0000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000205361666545524332303a206c6f772d6c6576656c2063616c6c206661696c6564");

function setUp() public {
mainnetFork = vm.createFork(vm.rpcUrl("mainnet"), 20910000);
vm.selectFork(mainnetFork);
}

function test_UpgradeMPT() public {
assertEq(vm.activeFork(), mainnetFork);

address rootChainManagerImpl = deployCode("out/RootChainManager.sol/RootChainManager.json");

vm.prank(address(timelock));
RootChainManagerProxy(payable(rootChainManagerProxy)).updateImplementation(rootChainManagerImpl);

// load tx to be replayed
string memory txsJson = vm.readFile("forge/batchExit.json");
bytes memory txs = vm.parseJson(txsJson);
FileObject memory txBatch1 = abi.decode(txs, (FileObject));
uint256 successes = 0;
uint256 successesButError = 0;
uint256 intentionalError = 0;

// loop
for (uint i = 0; i < txBatch1.txObjects.length; i++) {
//for (uint i = 13; i < 15; i++) {
console.log("tx num: ", i);
TxObject memory obj = txBatch1.txObjects[i];
console.log("from: ", obj.from);
console.log("hash:");
console.logBytes32(obj.hash);
// Get the index of the exit
bytes32 index = calcExitHash(obj.mod_input);
console.logBytes32(index);

// Calculate the storage slots we need to manipulate to replay the tx
bytes32 slotprocessedExits = keccak256(abi.encode(index, 6));

// RootChainManager
vm.store(rootChainManagerProxy, slotprocessedExits, 0);

// Pretend to be the the exitor and replay tx
vm.prank(obj.from);
(bool successSchedule, bytes memory dataSchedule) = rootChainManagerProxy.call(obj.input);
if (successSchedule == false) {
if(obj.isError) {
console.log("successful failure");
intentionalError += 1;
continue;
}
if(keccak256(dataSchedule) == notOwner721Error) {
console.log("Not owner error");
successesButError += 1;
continue;
}
if(keccak256(dataSchedule) == notOwnerNorApproved721Error) {
console.log("Not owner error");
successesButError += 1;
continue;
}
if(keccak256(dataSchedule) == outOfBalance20Error) {
console.log("Not enough balance error");
successesButError += 1;
continue;
}
if(keccak256(dataSchedule) == exceedsBalance20Error) {
console.log("Exceeds balance error");
successesButError += 1;
continue;
}
if(keccak256(dataSchedule) == safeERC20lowlevelError){
console.log("Safe ERC20 low level fail");
successesButError += 1;
continue;
}

console.log("actual error");
console.logBytes(dataSchedule);
assembly {
revert(add(dataSchedule, 32), mload(dataSchedule))
}
} else {
successes += 1;
console.log("success");
}
}
console.log("Total successful tx: ", successes);
console.log("Total successful MPT but error tx: ", successesButError);
console.log("Total intentional error tx: ", intentionalError);

}

// taken from RootChainManager
function calcExitHash(bytes memory input) internal pure returns (bytes32) {
ExitPayloadReader.ExitPayload memory payload = ExitPayloadReader.toExitPayload(input);

bytes memory branchMaskBytes = ExitPayloadReader.getBranchMaskAsBytes(payload);
// checking if exit has already been processed
// unique exit is identified using hash of (blockNumber, branchMask, receiptLogIndex)
bytes32 exitHash = keccak256(
abi.encodePacked(
ExitPayloadReader.getBlockNumber(payload),
// 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),
ExitPayloadReader.getReceiptLogIndex(payload)
)
);
return exitHash;
}
}
Loading
Loading