Created
September 10, 2017 04:34
Star
You must be signed in to star a gist
This is taken from a hack-a-thon project w/ the wonderful Eric Tu (https://github.com/tueric) and myself. It's only the essential parts of the gas oracle. More coming soon (hopefully :) ).
This file contains 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
pragma solidity ^0.4.8; | |
// RLP library moved below for readability | |
contract GasOracle { | |
using RLP for RLP.RLPItem; | |
using RLP for RLP.Iterator; | |
using RLP for bytes; | |
mapping (uint => BlockHeader) blocks; | |
mapping (uint => uint) pendingPayments; | |
struct BlockHeader { | |
bytes32 prevBlockHash; // 0 | |
uint gasLimit; // 9 | |
uint gasUsed; // 1 | |
uint freeGas; // gasLimit - gasUsed | |
address servedBy; // blockPoster | |
} | |
function submitBlock(uint blockNum, bytes rlpHeader) { | |
blocks[blockNum] = parseBlockHeader(rlpHeader); | |
require(blockNum > block.number - 256); | |
if (sha3(rlpHeader) == block.blockhash(blockNum)) { | |
blocks[blockNum] = parseBlockHeader(rlpHeader); | |
blocks[blockNum].servedBy = msg.sender; | |
blocks[blockNum].freeGas = blocks[blockNum].gasLimit - blocks[blockNum].gasUsed; | |
} else { | |
revert(); | |
} | |
} | |
function requestBlock(uint blockNum) payable { | |
pendingPayments[blockNum] += msg.value; | |
} | |
function getGasLimit(uint blockNum) returns (uint) { | |
if (checkBlock(blockNum)) revert(); | |
return blocks[blockNum].gasLimit; | |
} | |
function getGasUsed(uint blockNum) returns (uint) { | |
if (!checkBlock(blockNum)) revert(); | |
return blocks[blockNum].gasUsed; | |
} | |
function getFreeGas(uint blockNum) returns (uint) { | |
if (!checkBlock(blockNum)) revert(); | |
return blocks[blockNum].freeGas; | |
} | |
/* HELPER FUNCTIONS */ | |
function checkBlock(uint blockNum) constant returns (bool) { | |
return blocks[blockNum].prevBlockHash != bytes32(0); | |
} | |
function parseBlockHeader(bytes rlpHeader) constant internal returns (BlockHeader) { | |
BlockHeader memory header; | |
var it = rlpHeader.toRLPItem().iterator(); | |
uint idx; | |
while(it.hasNext()) { | |
if (idx == 0) { | |
header.prevBlockHash = bytes32(it.next().toUint()); | |
} else if (idx == 9) { | |
header.gasLimit = it.next().toUint(); | |
} else if (idx == 10) { | |
header.gasUsed = it.next().toUint(); | |
} else { | |
it.next(); | |
} | |
idx++; | |
} | |
return header; | |
} | |
} | |
/** | |
* @title RLPReader | |
* | |
* RLPReader is used to read and parse RLP encoded data in memory. | |
* | |
* @author Andreas Olofsson (androlo1980@gmail.com) | |
* | |
* code was taken from https://github.com/androlo/standard-contracts/blob/master/contracts/src/codec/RLP.sol | |
* | |
*/ | |
library RLP { | |
uint constant DATA_SHORT_START = 0x80; | |
uint constant DATA_LONG_START = 0xB8; | |
uint constant LIST_SHORT_START = 0xC0; | |
uint constant LIST_LONG_START = 0xF8; | |
uint constant DATA_LONG_OFFSET = 0xB7; | |
uint constant LIST_LONG_OFFSET = 0xF7; | |
struct RLPItem { | |
uint _unsafe_memPtr; // Pointer to the RLP-encoded bytes. | |
uint _unsafe_length; // Number of bytes. This is the full length of the string. | |
} | |
struct Iterator { | |
RLPItem _unsafe_item; // Item that's being iterated over. | |
uint _unsafe_nextPtr; // Position of the next item in the list. | |
} | |
/* Iterator */ | |
function next(Iterator memory self) internal constant returns (RLPItem memory subItem) { | |
if(hasNext(self)) { | |
var ptr = self._unsafe_nextPtr; | |
var itemLength = _itemLength(ptr); | |
subItem._unsafe_memPtr = ptr; | |
subItem._unsafe_length = itemLength; | |
self._unsafe_nextPtr = ptr + itemLength; | |
} | |
else | |
throw; | |
} | |
function next(Iterator memory self, bool strict) internal constant returns (RLPItem memory subItem) { | |
subItem = next(self); | |
if(strict && !_validate(subItem)) | |
throw; | |
return; | |
} | |
function hasNext(Iterator memory self) internal constant returns (bool) { | |
var item = self._unsafe_item; | |
return self._unsafe_nextPtr < item._unsafe_memPtr + item._unsafe_length; | |
} | |
/* RLPItem */ | |
/// @dev Creates an RLPItem from an array of RLP encoded bytes. | |
/// @param self The RLP encoded bytes. | |
/// @return An RLPItem | |
function toRLPItem(bytes memory self) internal constant returns (RLPItem memory) { | |
uint len = self.length; | |
if (len == 0) { | |
return RLPItem(0, 0); | |
} | |
uint memPtr; | |
assembly { | |
memPtr := add(self, 0x20) | |
} | |
return RLPItem(memPtr, len); | |
} | |
/// @dev Creates an RLPItem from an array of RLP encoded bytes. | |
/// @param self The RLP encoded bytes. | |
/// @param strict Will throw if the data is not RLP encoded. | |
/// @return An RLPItem | |
function toRLPItem(bytes memory self, bool strict) internal constant returns (RLPItem memory) { | |
var item = toRLPItem(self); | |
if(strict) { | |
uint len = self.length; | |
if(_payloadOffset(item) > len) | |
throw; | |
if(_itemLength(item._unsafe_memPtr) != len) | |
throw; | |
if(!_validate(item)) | |
throw; | |
} | |
return item; | |
} | |
/// @dev Check if the RLP item is null. | |
/// @param self The RLP item. | |
/// @return 'true' if the item is null. | |
function isNull(RLPItem memory self) internal constant returns (bool ret) { | |
return self._unsafe_length == 0; | |
} | |
/// @dev Check if the RLP item is a list. | |
/// @param self The RLP item. | |
/// @return 'true' if the item is a list. | |
function isList(RLPItem memory self) internal constant returns (bool ret) { | |
if (self._unsafe_length == 0) | |
return false; | |
uint memPtr = self._unsafe_memPtr; | |
assembly { | |
ret := iszero(lt(byte(0, mload(memPtr)), 0xC0)) | |
} | |
} | |
/// @dev Check if the RLP item is data. | |
/// @param self The RLP item. | |
/// @return 'true' if the item is data. | |
function isData(RLPItem memory self) internal constant returns (bool ret) { | |
if (self._unsafe_length == 0) | |
return false; | |
uint memPtr = self._unsafe_memPtr; | |
assembly { | |
ret := lt(byte(0, mload(memPtr)), 0xC0) | |
} | |
} | |
/// @dev Check if the RLP item is empty (string or list). | |
/// @param self The RLP item. | |
/// @return 'true' if the item is null. | |
function isEmpty(RLPItem memory self) internal constant returns (bool ret) { | |
if(isNull(self)) | |
return false; | |
uint b0; | |
uint memPtr = self._unsafe_memPtr; | |
assembly { | |
b0 := byte(0, mload(memPtr)) | |
} | |
return (b0 == DATA_SHORT_START || b0 == LIST_SHORT_START); | |
} | |
/// @dev Get the number of items in an RLP encoded list. | |
/// @param self The RLP item. | |
/// @return The number of items. | |
function items(RLPItem memory self) internal constant returns (uint) { | |
if (!isList(self)) | |
return 0; | |
uint b0; | |
uint memPtr = self._unsafe_memPtr; | |
assembly { | |
b0 := byte(0, mload(memPtr)) | |
} | |
uint pos = memPtr + _payloadOffset(self); | |
uint last = memPtr + self._unsafe_length - 1; | |
uint itms; | |
while(pos <= last) { | |
pos += _itemLength(pos); | |
itms++; | |
} | |
return itms; | |
} | |
/// @dev Create an iterator. | |
/// @param self The RLP item. | |
/// @return An 'Iterator' over the item. | |
function iterator(RLPItem memory self) internal constant returns (Iterator memory it) { | |
if (!isList(self)) | |
throw; | |
uint ptr = self._unsafe_memPtr + _payloadOffset(self); | |
it._unsafe_item = self; | |
it._unsafe_nextPtr = ptr; | |
} | |
/// @dev Return the RLP encoded bytes. | |
/// @param self The RLPItem. | |
/// @return The bytes. | |
function toBytes(RLPItem memory self) internal constant returns (bytes memory bts) { | |
var len = self._unsafe_length; | |
if (len == 0) | |
return; | |
bts = new bytes(len); | |
_copyToBytes(self._unsafe_memPtr, bts, len); | |
} | |
/// @dev Decode an RLPItem into bytes. This will not work if the | |
/// RLPItem is a list. | |
/// @param self The RLPItem. | |
/// @return The decoded string. | |
function toData(RLPItem memory self) internal constant returns (bytes memory bts) { | |
if(!isData(self)) | |
throw; | |
var (rStartPos, len) = _decode(self); | |
bts = new bytes(len); | |
_copyToBytes(rStartPos, bts, len); | |
} | |
/// @dev Get the list of sub-items from an RLP encoded list. | |
/// Warning: This is inefficient, as it requires that the list is read twice. | |
/// @param self The RLP item. | |
/// @return Array of RLPItems. | |
function toList(RLPItem memory self) internal constant returns (RLPItem[] memory list) { | |
if(!isList(self)) | |
throw; | |
var numItems = items(self); | |
list = new RLPItem[](numItems); | |
var it = iterator(self); | |
uint idx; | |
while(hasNext(it)) { | |
list[idx] = next(it); | |
idx++; | |
} | |
} | |
/// @dev Decode an RLPItem into an ascii string. This will not work if the | |
/// RLPItem is a list. | |
/// @param self The RLPItem. | |
/// @return The decoded string. | |
function toAscii(RLPItem memory self) internal constant returns (string memory str) { | |
if(!isData(self)) | |
throw; | |
var (rStartPos, len) = _decode(self); | |
bytes memory bts = new bytes(len); | |
_copyToBytes(rStartPos, bts, len); | |
str = string(bts); | |
} | |
/// @dev Decode an RLPItem into a uint. This will not work if the | |
/// RLPItem is a list. | |
/// @param self The RLPItem. | |
/// @return The decoded string. | |
function toUint(RLPItem memory self) internal constant returns (uint data) { | |
if(!isData(self)) | |
throw; | |
var (rStartPos, len) = _decode(self); | |
if (len > 32 || len == 0) | |
throw; | |
assembly { | |
data := div(mload(rStartPos), exp(256, sub(32, len))) | |
} | |
} | |
/// @dev Decode an RLPItem into a boolean. This will not work if the | |
/// RLPItem is a list. | |
/// @param self The RLPItem. | |
/// @return The decoded string. | |
function toBool(RLPItem memory self) internal constant returns (bool data) { | |
if(!isData(self)) | |
throw; | |
var (rStartPos, len) = _decode(self); | |
if (len != 1) | |
throw; | |
uint temp; | |
assembly { | |
temp := byte(0, mload(rStartPos)) | |
} | |
if (temp > 1) | |
throw; | |
return temp == 1 ? true : false; | |
} | |
/// @dev Decode an RLPItem into a byte. This will not work if the | |
/// RLPItem is a list. | |
/// @param self The RLPItem. | |
/// @return The decoded string. | |
function toByte(RLPItem memory self) internal constant returns (byte data) { | |
if(!isData(self)) | |
throw; | |
var (rStartPos, len) = _decode(self); | |
if (len != 1) | |
throw; | |
uint temp; | |
assembly { | |
temp := byte(0, mload(rStartPos)) | |
} | |
return byte(temp); | |
} | |
/// @dev Decode an RLPItem into an int. This will not work if the | |
/// RLPItem is a list. | |
/// @param self The RLPItem. | |
/// @return The decoded string. | |
function toInt(RLPItem memory self) internal constant returns (int data) { | |
return int(toUint(self)); | |
} | |
/// @dev Decode an RLPItem into a bytes32. This will not work if the | |
/// RLPItem is a list. | |
/// @param self The RLPItem. | |
/// @return The decoded string. | |
function toBytes32(RLPItem memory self) internal constant returns (bytes32 data) { | |
return bytes32(toUint(self)); | |
} | |
/// @dev Decode an RLPItem into an address. This will not work if the | |
/// RLPItem is a list. | |
/// @param self The RLPItem. | |
/// @return The decoded string. | |
function toAddress(RLPItem memory self) internal constant returns (address data) { | |
if(!isData(self)) | |
throw; | |
var (rStartPos, len) = _decode(self); | |
if (len != 20) | |
throw; | |
assembly { | |
data := div(mload(rStartPos), exp(256, 12)) | |
} | |
} | |
// Get the payload offset. | |
function _payloadOffset(RLPItem memory self) private constant returns (uint) { | |
if(self._unsafe_length == 0) | |
return 0; | |
uint b0; | |
uint memPtr = self._unsafe_memPtr; | |
assembly { | |
b0 := byte(0, mload(memPtr)) | |
} | |
if(b0 < DATA_SHORT_START) | |
return 0; | |
if(b0 < DATA_LONG_START || (b0 >= LIST_SHORT_START && b0 < LIST_LONG_START)) | |
return 1; | |
if(b0 < LIST_SHORT_START) | |
return b0 - DATA_LONG_OFFSET + 1; | |
return b0 - LIST_LONG_OFFSET + 1; | |
} | |
// Get the full length of an RLP item. | |
function _itemLength(uint memPtr) private constant returns (uint len) { | |
uint b0; | |
assembly { | |
b0 := byte(0, mload(memPtr)) | |
} | |
if (b0 < DATA_SHORT_START) | |
len = 1; | |
else if (b0 < DATA_LONG_START) | |
len = b0 - DATA_SHORT_START + 1; | |
else if (b0 < LIST_SHORT_START) { | |
assembly { | |
let bLen := sub(b0, 0xB7) // bytes length (DATA_LONG_OFFSET) | |
let dLen := div(mload(add(memPtr, 1)), exp(256, sub(32, bLen))) // data length | |
len := add(1, add(bLen, dLen)) // total length | |
} | |
} | |
else if (b0 < LIST_LONG_START) | |
len = b0 - LIST_SHORT_START + 1; | |
else { | |
assembly { | |
let bLen := sub(b0, 0xF7) // bytes length (LIST_LONG_OFFSET) | |
let dLen := div(mload(add(memPtr, 1)), exp(256, sub(32, bLen))) // data length | |
len := add(1, add(bLen, dLen)) // total length | |
} | |
} | |
} | |
// Get start position and length of the data. | |
function _decode(RLPItem memory self) private constant returns (uint memPtr, uint len) { | |
if(!isData(self)) | |
throw; | |
uint b0; | |
uint start = self._unsafe_memPtr; | |
assembly { | |
b0 := byte(0, mload(start)) | |
} | |
if (b0 < DATA_SHORT_START) { | |
memPtr = start; | |
len = 1; | |
return; | |
} | |
if (b0 < DATA_LONG_START) { | |
len = self._unsafe_length - 1; | |
memPtr = start + 1; | |
} else { | |
uint bLen; | |
assembly { | |
bLen := sub(b0, 0xB7) // DATA_LONG_OFFSET | |
} | |
len = self._unsafe_length - 1 - bLen; | |
memPtr = start + bLen + 1; | |
} | |
return; | |
} | |
// Assumes that enough memory has been allocated to store in target. | |
function _copyToBytes(uint btsPtr, bytes memory tgt, uint btsLen) private constant { | |
// Exploiting the fact that 'tgt' was the last thing to be allocated, | |
// we can write entire words, and just overwrite any excess. | |
assembly { | |
{ | |
let i := 0 // Start at arr + 0x20 | |
let words := div(add(btsLen, 31), 32) | |
let rOffset := btsPtr | |
let wOffset := add(tgt, 0x20) | |
tag_loop: | |
jumpi(end, eq(i, words)) | |
{ | |
let offset := mul(i, 0x20) | |
mstore(add(wOffset, offset), mload(add(rOffset, offset))) | |
i := add(i, 1) | |
} | |
jump(tag_loop) | |
end: | |
mstore(add(tgt, add(0x20, mload(tgt))), 0) | |
} | |
} | |
} | |
// Check that an RLP item is valid. | |
function _validate(RLPItem memory self) private constant returns (bool ret) { | |
// Check that RLP is well-formed. | |
uint b0; | |
uint b1; | |
uint memPtr = self._unsafe_memPtr; | |
assembly { | |
b0 := byte(0, mload(memPtr)) | |
b1 := byte(1, mload(memPtr)) | |
} | |
if(b0 == DATA_SHORT_START + 1 && b1 < DATA_SHORT_START) | |
return false; | |
return true; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment