igneum/tools/reference-apps/oracle/contracts/Mpt.sol

170 lines
7.3 KiB
Solidity

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.28;
// RLP reading and Merkle Patricia trie proofs in Ethereum's layout (keccak-keyed secure trie).
// A proof is the node list eth_getProof returns, root first, in path order. Every node is
// hashed and compared with the reference that led to it. Nodes shorter than 32 bytes may be
// embedded in their parent; those are walked in place.
library Mpt {
bytes32 internal constant EMPTY_ROOT = 0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421;
struct Item {
bool isList;
uint256 start; // position of the item's prefix
uint256 off; // position of the item's payload
uint256 len; // payload length
}
function item(bytes memory b, uint256 p) internal pure returns (Item memory it) {
require(p < b.length, "rlp: short");
uint8 x = uint8(b[p]);
it.start = p;
if (x < 0x80) {
it.off = p;
it.len = 1;
} else if (x < 0xb8) {
it.len = x - 0x80;
it.off = p + 1;
} else if (x < 0xc0) {
uint256 ll = x - 0xb7;
it.len = uintAt(b, p + 1, ll);
it.off = p + 1 + ll;
} else if (x < 0xf8) {
it.isList = true;
it.len = x - 0xc0;
it.off = p + 1;
} else {
uint256 ll = x - 0xf7;
it.isList = true;
it.len = uintAt(b, p + 1, ll);
it.off = p + 1 + ll;
}
require(it.off + it.len <= b.length, "rlp: overrun");
}
function uintAt(bytes memory b, uint256 p, uint256 n) internal pure returns (uint256 v) {
require(n <= 32 && p + n <= b.length, "rlp: bad length");
for (uint256 i = 0; i < n; i++) v = (v << 8) | uint8(b[p + i]);
}
function slice(bytes memory b, uint256 off, uint256 len) internal pure returns (bytes memory out) {
out = new bytes(len);
assembly { mcopy(add(out, 32), add(add(b, 32), off), len) }
}
function nibble(bytes32 key, uint256 i) private pure returns (uint8) {
uint8 x = uint8(key[i >> 1]);
return (i & 1) == 0 ? x >> 4 : x & 15;
}
// Walks the proof for a 32-byte (already hashed) key. Returns (found, value bytes).
// Reverts on any node that does not hash to its reference or is malformed.
function get(bytes32 root, bytes32 key, bytes[] calldata proof) internal pure returns (bool found, bytes memory value) {
if (proof.length == 0) {
require(root == EMPTY_ROOT, "mpt: no nodes for a non-empty root");
return (false, "");
}
bytes memory node = proof[0];
require(keccak256(node) == root, "mpt: the first node is not the root");
uint256 pi = 1;
uint256 pos = 0; // nibbles of the key consumed
uint256 np = 0; // position of the current node inside `node`
for (uint256 guard = 0; guard < 80; guard++) {
Item memory list = item(node, np);
require(list.isList, "mpt: node is not a list");
uint256[17] memory starts;
uint256 count = 0;
uint256 p = list.off;
while (p < list.off + list.len) {
require(count < 17, "mpt: too many items");
Item memory it = item(node, p);
starts[count++] = p;
p = it.off + it.len;
}
Item memory child;
if (count == 17) {
if (pos == 64) {
Item memory v = item(node, starts[16]);
if (v.len == 0) return (false, "");
return (true, slice(node, v.off, v.len));
}
child = item(node, starts[nibble(key, pos)]);
pos += 1;
} else if (count == 2) {
Item memory hp = item(node, starts[0]);
require(!hp.isList && hp.len > 0, "mpt: bad path");
uint8 flag = uint8(node[hp.off]) >> 4;
bool leaf = (flag & 2) != 0;
uint256 n = hp.len * 2 - ((flag & 1) != 0 ? 1 : 2);
for (uint256 i = 0; i < n; i++) {
uint256 k = i + ((flag & 1) != 0 ? 1 : 2);
uint8 x = uint8(node[hp.off + (k >> 1)]);
uint8 nib = (k & 1) == 0 ? x >> 4 : x & 15;
if (pos + i >= 64 || nib != nibble(key, pos + i)) return (false, ""); // the key diverges
}
pos += n;
child = item(node, starts[1]);
if (leaf) {
if (pos != 64) return (false, "");
return (true, slice(node, child.off, child.len));
}
} else {
revert("mpt: node with an odd item count");
}
if (child.isList) {
np = child.start; // an embedded node
continue;
}
if (child.len == 0) return (false, "");
require(child.len == 32, "mpt: bad reference");
bytes32 want;
assembly { want := mload(add(add(node, 32), mload(add(child, 64)))) }
require(pi < proof.length, "mpt: a referenced node is missing");
node = proof[pi++];
require(keccak256(node) == want, "mpt: node does not hash to its reference");
np = 0;
}
revert("mpt: path too deep");
}
function toUint(bytes memory b, uint256 off, uint256 len) internal pure returns (uint256 v) {
require(len <= 32, "rlp: integer too wide");
for (uint256 i = 0; i < len; i++) v = (v << 8) | uint8(b[off + i]);
}
function toBytes32(bytes memory b, uint256 off, uint256 len) internal pure returns (bytes32 v) {
require(len == 32, "rlp: not 32 bytes");
assembly { v := mload(add(add(b, 32), off)) }
}
// An eth_getProof account proof under `stateRoot`.
function account(bytes32 stateRoot, address a, bytes[] calldata proof)
internal
pure
returns (bool exists, uint256 nonce, uint256 balance, bytes32 storageRoot, bytes32 codeHash)
{
(bool found, bytes memory v) = get(stateRoot, keccak256(abi.encodePacked(a)), proof);
if (!found) return (false, 0, 0, bytes32(0), bytes32(0));
Item memory list = item(v, 0);
require(list.isList, "mpt: account is not a list");
Item memory f0 = item(v, list.off);
Item memory f1 = item(v, f0.off + f0.len);
Item memory f2 = item(v, f1.off + f1.len);
Item memory f3 = item(v, f2.off + f2.len);
require(f3.off + f3.len == list.off + list.len, "mpt: account has not four fields");
exists = true;
nonce = toUint(v, f0.off, f0.len);
balance = toUint(v, f1.off, f1.len);
storageRoot = toBytes32(v, f2.off, f2.len);
codeHash = toBytes32(v, f3.off, f3.len);
}
// An eth_getProof storage proof under `storageRoot`; a proven absence reads as zero.
function storageSlot(bytes32 storageRoot, bytes32 slot, bytes[] calldata proof) internal pure returns (bytes32) {
(bool found, bytes memory v) = get(storageRoot, keccak256(abi.encodePacked(slot)), proof);
if (!found) return bytes32(0);
Item memory it = item(v, 0);
require(!it.isList && it.off + it.len == v.length, "mpt: storage value is not a string");
return bytes32(toUint(v, it.off, it.len));
}
}