So i was curious if you could write a contract, that retrieves the StateRoot from the last mined block. I found a Repo from Keydonix, who used the stateRoot from previous blocks to calculate a more recent price change for dexes.
The solidity file below was written by him, unfortunately, i know very little about assembly code. I can see that the contract stores more than 16 variables, which causes the error of "Stack too deep(31)".
In line 33, he comments that the variables could be compressed into a single add function to save gas. Could anyone help me with how i would go about writing that in assembly?
Also, seeing as its a library i was thinking on perhaps making another library to lower the burden of high variable count on a single contract but wasnt sure seeing as its assembly.
Repo:
https://github.com/Keydonix/uniswap-oracle/
Best regards
Mango
pragma solidity ^0.6.8;
library BlockVerifier {
function extractStateRootAndTimestamp(bytes memory rlpBytes) internal view returns (bytes32 stateRoot, uint256 blockTimestamp, uint256 blockNumber) {
assembly {
function revertWithReason(message, length) {
mstore(0, 0x08c379a000000000000000000000000000000000000000000000000000000000)
mstore(4, 0x20)
mstore(0x24, length)
mstore(0x44, message)
revert(0, add(0x44, length))
}
function readDynamic(prefixPointer) -> dataPointer, dataLength {
let value := byte(0, mload(prefixPointer))
switch lt(value, 0x80)
case 1 {
dataPointer := prefixPointer
dataLength := 1
}
case 0 {
dataPointer := add(prefixPointer, 1)
dataLength := sub(value, 0x80)
}
}
// get the length of the data
let rlpLength := mload(rlpBytes)
// move pointer forward, ahead of length
rlpBytes := add(rlpBytes, 0x20)
// we know the length of the block will be between 483 bytes and 709 bytes, which means it will have 2 length bytes after the prefix byte, so we can skip 3 bytes in
// CONSIDER: we could save a trivial amount of gas by compressing most of this into a single add instruction
let parentHashPrefixPointer := add(rlpBytes, 3)
let parentHashPointer := add(parentHashPrefixPointer, 1)
let uncleHashPrefixPointer := add(parentHashPointer, 32)
let uncleHashPointer := add(uncleHashPrefixPointer, 1)
let minerAddressPrefixPointer := add(uncleHashPointer, 32)
let minerAddressPointer := add(minerAddressPrefixPointer, 1)
let stateRootPrefixPointer := add(minerAddressPointer, 20)
let stateRootPointer := add(stateRootPrefixPointer, 1)
let transactionRootPrefixPointer := add(stateRootPointer, 32)
let transactionRootPointer := add(transactionRootPrefixPointer, 1)
let receiptsRootPrefixPointer := add(transactionRootPointer, 32)
let receiptsRootPointer := add(receiptsRootPrefixPointer, 1)
let logsBloomPrefixPointer := add(receiptsRootPointer, 32)
let logsBloomPointer := add(logsBloomPrefixPointer, 3)
let difficultyPrefixPointer := add(logsBloomPointer, 256)
let difficultyPointer, difficultyLength := readDynamic(difficultyPrefixPointer)
let blockNumberPrefixPointer := add(difficultyPointer, difficultyLength)
let blockNumberPointer, blockNumberLength := readDynamic(blockNumberPrefixPointer)
let gasLimitPrefixPointer := add(blockNumberPointer, blockNumberLength)
let gasLimitPointer, gasLimitLength := readDynamic(gasLimitPrefixPointer)
let gasUsedPrefixPointer := add(gasLimitPointer, gasLimitLength)
let gasUsedPointer, gasUsedLength := readDynamic(gasUsedPrefixPointer)
let timestampPrefixPointer := add(gasUsedPointer, gasUsedLength)
let timestampPointer, timestampLength := readDynamic(timestampPrefixPointer)
blockNumber := shr(sub(256, mul(blockNumberLength, 8)), mload(blockNumberPointer))
let blockHash := blockhash(blockNumber)
let rlpHash := keccak256(rlpBytes, rlpLength)
if iszero(eq(blockHash, rlpHash)) { revertWithReason("blockHash != rlpHash", 20) }
stateRoot := mload(stateRootPointer)
blockTimestamp := shr(sub(256, mul(timestampLength, 8)), mload(timestampPointer))
}
}
}
Best Answer
It is not possible in Ethereum Smart Contract at the time being. Because there is no EVM opcode to retrieve that: EVM opcode list, and there is no precompiled contract to do that: Ethereum precompiled contracts. The closest information you can get to verify something is the
BLOCKHASH
opcode, but it alone is not enough for verification.The contract(library) you found has the purpose to verify what is submitted in a transaction by oracle or some actors using information from JSON-RPC interface of an Ethereum node. The unavailability of
StateRoot
on EVM is also stated by the author of theuniswap-oracle
code in Medium post about Uniswap Oracle. Nonetheless, there may have EIPs to provide suchStateRoot
opcode in the future if they get approved.