├── .gitignore ├── .gitmodules ├── migrations ├── 1_initial_migration.js ├── 2_contracts.js └── 3_safe_module.js ├── contracts ├── example │ ├── Emitter.sol │ └── Subscriber.sol ├── Migrations.sol ├── Receipt.sol ├── Block.sol ├── Verifier.sol ├── RelayNetwork.sol ├── Ethbase.sol ├── SafeModule.sol └── lib │ ├── PatriciaTrie.sol │ └── RLP.sol ├── test ├── relay-network.js ├── example │ └── subscriber.js ├── ethbase.js └── verifier.js ├── truffle-config.js ├── truffle.js ├── package.json ├── README.md └── LICENSE /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | build/ 3 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "contracts/safe"] 2 | path = contracts/safe 3 | url = git@github.com:gnosis/safe-contracts.git 4 | branch = master 5 | -------------------------------------------------------------------------------- /migrations/1_initial_migration.js: -------------------------------------------------------------------------------- 1 | var Migrations = artifacts.require("./Migrations.sol"); 2 | 3 | module.exports = function(deployer) { 4 | deployer.deploy(Migrations); 5 | }; 6 | -------------------------------------------------------------------------------- /contracts/example/Emitter.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.24; 2 | 3 | contract Emitter { 4 | event Transfer(uint amount); 5 | 6 | function transfer(uint value_) public { 7 | emit Transfer(value_); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /test/relay-network.js: -------------------------------------------------------------------------------- 1 | const RelayNetwork = artifacts.require('RelayNetwork') 2 | 3 | contract('RelayNetwork', async (accounts) => { 4 | let instance 5 | 6 | before(async () => { 7 | instance = await RelayNetwork.new() 8 | }) 9 | 10 | it('should have no relayers initially', async () => { 11 | let relayersCount = await instance.relayersCount.call() 12 | assert.equal(relayersCount, 0) 13 | }) 14 | }) 15 | -------------------------------------------------------------------------------- /contracts/Migrations.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.23; 2 | 3 | contract Migrations { 4 | address public owner; 5 | uint public last_completed_migration; 6 | 7 | constructor() public { 8 | owner = msg.sender; 9 | } 10 | 11 | modifier restricted() { 12 | if (msg.sender == owner) _; 13 | } 14 | 15 | function setCompleted(uint completed) public restricted { 16 | last_completed_migration = completed; 17 | } 18 | 19 | function upgrade(address new_address) public restricted { 20 | Migrations upgraded = Migrations(new_address); 21 | upgraded.setCompleted(last_completed_migration); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /truffle-config.js: -------------------------------------------------------------------------------- 1 | /* 2 | * NB: since truffle-hdwallet-provider 0.0.5 you must wrap HDWallet providers in a 3 | * function when declaring them. Failure to do so will cause commands to hang. ex: 4 | * ``` 5 | * mainnet: { 6 | * provider: function() { 7 | * return new HDWalletProvider(mnemonic, 'https://mainnet.infura.io/') 8 | * }, 9 | * network_id: '1', 10 | * gas: 4500000, 11 | * gasPrice: 10000000000, 12 | * }, 13 | */ 14 | 15 | module.exports = { 16 | // See 17 | // to customize your Truffle configuration! 18 | }; 19 | -------------------------------------------------------------------------------- /contracts/Receipt.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.24; 2 | 3 | import "./lib/RLP.sol"; 4 | 5 | library Receipt { 6 | using RLP for RLP.RLPItem; 7 | 8 | function extractLog(bytes _value, uint _logIndex) public returns (bytes) { 9 | RLP.RLPItem[] memory receiptFields = RLP.toRLPItem(_value).toList(); 10 | require(receiptFields.length == 4, "receipt rlp has wrong len"); 11 | 12 | RLP.RLPItem[] memory logs = receiptFields[3].toList(); 13 | require(_logIndex < logs.length, "log index too big"); 14 | 15 | RLP.RLPItem[] memory logFields = logs[_logIndex].toList(); 16 | require(logFields.length == 3, "log rlp has wrong number of fields"); 17 | 18 | return logFields[2].toData(); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /migrations/2_contracts.js: -------------------------------------------------------------------------------- 1 | const Subscriber = artifacts.require('./example/Subscriber.sol') 2 | const Emitter = artifacts.require('./example/Emitter.sol') 3 | const Ethbase = artifacts.require('./Ethbase.sol') 4 | const PatriciaTrie = artifacts.require('./lib/PatriciaTrie.sol') 5 | const Receipt = artifacts.require('./Receipt') 6 | const Block = artifacts.require('./Block') 7 | 8 | module.exports = function (deployer) { 9 | deployer.deploy(Receipt) 10 | deployer.deploy(Block) 11 | deployer.deploy(PatriciaTrie) 12 | deployer.deploy(Emitter) 13 | deployer.link(Receipt, Ethbase) 14 | deployer.link(Block, Ethbase) 15 | deployer.link(PatriciaTrie, Ethbase) 16 | deployer.deploy(Ethbase).then((instance) => { 17 | return deployer.deploy(Subscriber, instance.address) 18 | }) 19 | } 20 | -------------------------------------------------------------------------------- /contracts/Block.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.24; 2 | 3 | import "./lib/RLP.sol"; 4 | 5 | library Block { 6 | 7 | struct BlockHeader { 8 | bytes32 hash; 9 | bytes32 receiptHash; 10 | uint number; 11 | } 12 | 13 | function decodeBlockHeader(bytes _blockHeader) internal pure returns (BlockHeader memory h) { 14 | RLP.RLPItem memory headerItem = RLP.toRLPItem(_blockHeader); 15 | RLP.RLPItem[] memory headerList = RLP.toList(headerItem); 16 | 17 | bytes32 hash = keccak256(_blockHeader); 18 | bytes32 receiptHash = RLP.toBytes32(headerList[5]); 19 | uint number = RLP.toUint(headerList[8]); 20 | 21 | h = BlockHeader(hash, receiptHash, number); 22 | } 23 | 24 | function validateHeader(BlockHeader memory h) internal view returns (bool) { 25 | bytes32 validBlockHash = blockhash(h.number); 26 | return validBlockHash == h.hash; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /truffle.js: -------------------------------------------------------------------------------- 1 | const HDWalletProvider = require('truffle-hdwallet-provider') 2 | 3 | const mnemonic = process.env.MNEMONIC 4 | 5 | module.exports = { 6 | networks: { 7 | ganache: { 8 | host: '127.0.0.1', 9 | port: 7545, 10 | network_id: '*' // Match any network id, 11 | }, 12 | development: { 13 | host: '127.0.0.1', 14 | port: 8545, 15 | network_id: '*', // Match any network id, 16 | gas: 6000000 17 | }, 18 | ropsten: { 19 | provider: function () { 20 | return new HDWalletProvider(mnemonic, 'https://ropsten.infura.io/') 21 | }, 22 | network_id: '3', 23 | gas: 6700000, 24 | gasPrice: 2000000000 // 2 Gwei 25 | }, 26 | rinkeby: { 27 | provider: function () { 28 | return new HDWalletProvider(mnemonic, 'https://rinkeby.infura.io/') 29 | }, 30 | network_id: '4', 31 | gas: 6700000, 32 | gasPrice: 2000000000 // 2 Gwei 33 | } 34 | }, 35 | compilers: { 36 | solc: { 37 | version: '0.4.24' 38 | } 39 | }, 40 | solc: { 41 | optimizer: { 42 | enabled: true, 43 | runs: 200 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ethbase", 3 | "version": "0.2.0", 4 | "description": "Ethereum on-chain event subscription", 5 | "main": "truffle-config.js", 6 | "directories": { 7 | "test": "test" 8 | }, 9 | "scripts": { 10 | "compile": "npx truffle compile", 11 | "test": "npx truffle test" 12 | }, 13 | "repository": { 14 | "type": "git", 15 | "url": "git+https://github.com/planet-ethereum/ethbase.git" 16 | }, 17 | "author": "Sina Mahmoodi", 18 | "license": "MIT", 19 | "bugs": { 20 | "url": "https://github.com/planet-ethereum/ethbase/issues" 21 | }, 22 | "homepage": "https://github.com/planet-ethereum/ethbase#readme", 23 | "standard": { 24 | "globals": [ 25 | "artifacts", 26 | "contract", 27 | "assert" 28 | ], 29 | "envs": [ 30 | "mocha" 31 | ] 32 | }, 33 | "devDependencies": { 34 | "ethereumjs-abi": "^0.6.5", 35 | "standard": "^12.0.0", 36 | "truffle": "^5.0.0-beta.1", 37 | "truffle-hdwallet-provider": "^1.0.0-web3one.0" 38 | }, 39 | "dependencies": { 40 | "bignumber.js": "^7.2.1", 41 | "openzeppelin-solidity": "^1.12.0", 42 | "truffle-contract": "^4.0.0-beta.0", 43 | "web3": "^1.0.0-beta.35" 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /contracts/example/Subscriber.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.24; 2 | 3 | import "../Ethbase.sol"; 4 | 5 | 6 | contract Subscriber { 7 | address owner; 8 | Ethbase ethbase; 9 | 10 | uint public value = 0; 11 | bytes32 public text; 12 | 13 | constructor(address _ethbase) public { 14 | owner = msg.sender; 15 | ethbase = Ethbase(_ethbase); 16 | } 17 | 18 | /** 19 | * @dev Subscribe to ethbase, and provide callback, for when a log is emitted. 20 | * @param _emitter Address of event emitter contract. 21 | * @param _eventTopic E.g. keccak256(ExampleEvent(type1,type2)). 22 | * @param _method Callback method: bytes4(keccak256(exampleMethod(type1,type2))). 23 | */ 24 | function subscribe(address _emitter, bytes32 _eventTopic, bytes4 _method) public { 25 | require(msg.sender == owner); 26 | ethbase.subscribe(_emitter, _eventTopic, this, _method); 27 | } 28 | 29 | function unsubscribe(bytes32 _eventId) public { 30 | require(msg.sender == owner); 31 | ethbase.unsubscribe(_eventId, this); 32 | } 33 | 34 | // Example callback function. 35 | function setValue(uint _value) public { 36 | value = _value; 37 | } 38 | 39 | function setValues(uint _value, bytes32 _text) public { 40 | value = _value; 41 | text = _text; 42 | } 43 | 44 | function setRandomValue() public { 45 | // Totally random! 46 | value = 21; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /contracts/Verifier.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.24; 2 | 3 | import "./lib/PatriciaTrie.sol"; 4 | import "./lib/RLP.sol"; 5 | 6 | // Only for testing purposes 7 | contract Verifier { 8 | 9 | struct BlockHeader { 10 | bytes32 receiptHash; 11 | uint number; 12 | } 13 | 14 | /** 15 | * @dev Verify the proof of an event. 16 | */ 17 | function verify(bytes _value, bytes _parentNodes, bytes _path, bytes _blockHeader) public view returns (bool) { 18 | BlockHeader memory header = decodeBlockHeader(_blockHeader); 19 | 20 | bytes32 validBlockHash = blockhash(header.number); 21 | bytes32 givenBlockHash = keccak256(_blockHeader); 22 | 23 | require(validBlockHash == givenBlockHash, "invalid block hash"); 24 | 25 | return verifyProof(_value, _parentNodes, _path, header.receiptHash); 26 | } 27 | 28 | function verifyProof(bytes _value, bytes _parentNodes, bytes _path, bytes32 rootHash) public view returns (bool) { 29 | return PatriciaTrie.verifyProof(_value, _parentNodes, _path, rootHash); 30 | } 31 | 32 | function decodeBlockHeader(bytes _blockHeader) internal pure returns (BlockHeader memory h) { 33 | RLP.RLPItem memory headerItem = RLP.toRLPItem(_blockHeader); 34 | RLP.RLPItem[] memory headerList = RLP.toList(headerItem); 35 | 36 | bytes32 receiptHash = RLP.toBytes32(headerList[5]); 37 | uint number = RLP.toUint(headerList[8]); 38 | 39 | h = BlockHeader(receiptHash, number); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /test/example/subscriber.js: -------------------------------------------------------------------------------- 1 | /* global web3 */ 2 | const ABI = require('ethereumjs-abi') 3 | 4 | const Subscriber = artifacts.require('Subscriber.sol') 5 | const Emitter = artifacts.require('Emitter.sol') 6 | const Ethbase = artifacts.require('Ethbase.sol') 7 | 8 | contract('Subscriber', async () => { 9 | let instance 10 | let emitter 11 | let ethbase 12 | let eventId 13 | 14 | before(async () => { 15 | instance = await Subscriber.deployed() 16 | emitter = await Emitter.deployed() 17 | ethbase = await Ethbase.deployed() 18 | }) 19 | 20 | it('should have default value', async () => { 21 | let val = await instance.value() 22 | assert.equal(val, '0') 23 | }) 24 | 25 | it('should set new value', async () => { 26 | await instance.setValue(5) 27 | let val = await instance.value() 28 | 29 | assert.equal(val, '5') 30 | }) 31 | 32 | it('should set new values', async () => { 33 | let hex = web3.utils.sha3('test') 34 | 35 | await instance.setValues(6, hex) 36 | let val = await instance.value() 37 | let text = await instance.text() 38 | 39 | assert.equal(val, '6') 40 | assert.equal(text, hex) 41 | }) 42 | 43 | it('should subscribe', async () => { 44 | let topic = web3.utils.sha3('Transfer(uint256)') 45 | let method = web3.utils.sha3('setValue(uint256)') 46 | await instance.subscribe(emitter.address, topic, method) 47 | eventId = ABI.soliditySHA3(['address', 'bytes32'], [emitter.address, topic]) 48 | eventId = '0x' + eventId.toString('hex') 49 | }) 50 | }) 51 | -------------------------------------------------------------------------------- /migrations/3_safe_module.js: -------------------------------------------------------------------------------- 1 | const PatriciaTrie = artifacts.require('./lib/PatriciaTrie.sol') 2 | const SafeModule = artifacts.require('./SafeModule') 3 | const ProxyFactory = artifacts.require('./ProxyFactory') 4 | const GnosisSafePersonalEdition = artifacts.require('./safe/contracts/GnosisSafePersonalEdition') 5 | const CreateAndAddModules = artifacts.require('./safe/contracts/libraries/CreateAndAddModules') 6 | 7 | module.exports = async function (deployer, network, accounts) { 8 | await deployer.link(PatriciaTrie, SafeModule) 9 | 10 | let proxyFactory = await deployer.deploy(ProxyFactory) 11 | let createAndAddModules = await deployer.deploy(CreateAndAddModules) 12 | let safe = await deployer.deploy(GnosisSafePersonalEdition) 13 | let safeModule = await deployer.deploy(SafeModule) 14 | 15 | let safeModuleSetupData = await safeModule.contract.methods.setup().encodeABI() 16 | let safeModuleCreationData = await proxyFactory.contract.methods 17 | .createProxy(safeModule.address, safeModuleSetupData).encodeABI() 18 | let modulesCreationData = processModulesData([safeModuleCreationData]) 19 | let createAndAddModulesData = await createAndAddModules.contract.methods 20 | .createAndAddModules(proxyFactory.address, modulesCreationData).encodeABI() 21 | 22 | let gnosisSafeData = await safe.contract.methods 23 | .setup([accounts[0]], 1, createAndAddModules.address, createAndAddModulesData).encodeABI() 24 | let tx = await proxyFactory.createProxy(safe.address, gnosisSafeData) 25 | console.log('SafeModuleProxy: ', tx.logs[0].args.proxy, 'SafeProxy: ', tx.logs[1].args.proxy) 26 | } 27 | 28 | // eslint-disable-next-line 29 | const mw = new web3.eth.Contract([{"constant":false,"inputs":[{"name":"data","type":"bytes"}],"name":"setup","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"}]) 30 | 31 | function processModulesData (dataArray) { 32 | return dataArray.reduce((acc, data) => acc + mw.methods.setup(data).encodeABI().substr(74), '0x') 33 | } 34 | -------------------------------------------------------------------------------- /contracts/RelayNetwork.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.24; 2 | 3 | contract RelayNetwork { 4 | event Registered(address addr); 5 | 6 | struct Relayer { 7 | uint256 registered; 8 | uint256 deposit; 9 | } 10 | 11 | mapping (address => Relayer) public relayers; 12 | address[] public relayerArray; 13 | 14 | uint256 constant STAKE_AMOUNT = 1 ether; 15 | 16 | /** 17 | * @dev Registers a relayer, if enough stake is deposited. 18 | */ 19 | function register() public payable { 20 | require(relayers[msg.sender].registered == 0, "Relayer already registered"); 21 | require(msg.value == STAKE_AMOUNT, "Incorrect deposit value"); 22 | 23 | Relayer memory r = Relayer(now, msg.value); 24 | relayers[msg.sender] = r; 25 | relayerArray.push(msg.sender); 26 | 27 | emit Registered(msg.sender); 28 | } 29 | 30 | /** 31 | * @dev Returns true if relayer can submit txes this block. 32 | * @param _addr Address of relayer 33 | */ 34 | function canSubmit(address _addr) public view returns (bool) { 35 | require(relayers[msg.sender].registered != 0, "Relayer not registered"); 36 | 37 | uint256 seed = uint256(blockhash(block.number-1)); 38 | uint i = seed % relayerArray.length; 39 | 40 | return _addr == relayerArray[i]; 41 | } 42 | 43 | /** 44 | * @dev Delists relayer and returns their deposit. 45 | */ 46 | function withdraw() public { 47 | require(relayers[msg.sender].registered != 0, "Relayer not registered"); 48 | 49 | msg.sender.transfer(relayers[msg.sender].deposit); 50 | 51 | uint i = relayerIndex(msg.sender); 52 | delete relayers[msg.sender]; 53 | delete relayerArray[i]; 54 | } 55 | 56 | /** 57 | * @dev Returns number of relayers. 58 | */ 59 | function relayersCount() public returns (uint) { 60 | return relayerArray.length; 61 | } 62 | 63 | function relayerIndex(address _addr) internal view returns (uint) { 64 | uint i = 0; 65 | while (relayerArray[i] != _addr) { 66 | i++; 67 | } 68 | return i; 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /test/ethbase.js: -------------------------------------------------------------------------------- 1 | /* global web3 */ 2 | const Ethbase = artifacts.require('Ethbase.sol') 3 | const Subscriber = artifacts.require('Subscriber.sol') 4 | const Emitter = artifacts.require('Emitter.sol') 5 | 6 | let emitter 7 | const zeroHex = web3.utils.asciiToHex('0x0') 8 | 9 | before(async () => { 10 | emitter = await Emitter.deployed() 11 | }) 12 | 13 | contract('Ethbase: sub/unsub', async () => { 14 | let instance 15 | let subscriber 16 | let eventId 17 | 18 | before(async () => { 19 | instance = await Ethbase.deployed() 20 | subscriber = await Subscriber.deployed() 21 | }) 22 | 23 | it('should revert if not subscribed', async () => { 24 | let topic = web3.utils.sha3('Transfer(uint256)') 25 | let id = web3.utils.sha3(emitter.address, topic) 26 | try { 27 | await instance.submitLog(zeroHex, zeroHex, zeroHex, 0, zeroHex, subscriber.address, id) 28 | assert.fail('Expected revert not received') 29 | } catch (e) { 30 | const revertFound = e.message.search('revert') >= 0 31 | assert(revertFound, `Expected "revert", got ${e} instead`) 32 | } 33 | }) 34 | 35 | it('should subscribe to an event', async () => { 36 | let topic = web3.utils.sha3('Transfer(uint256)') 37 | let method = web3.utils.sha3('setRandomValue()') 38 | const tx = await instance.subscribe(emitter.address, topic, subscriber.address, method) 39 | eventId = tx.logs[0].args.eventId 40 | }) 41 | 42 | it('should unsubscribe', async () => { 43 | await instance.unsubscribe(eventId, subscriber.address) 44 | }) 45 | 46 | it('should revert for unsubscribed event', async () => { 47 | try { 48 | await instance.submitLog(zeroHex, zeroHex, zeroHex, 0, zeroHex, subscriber.address, eventId) 49 | assert.fail('Expected revert not received') 50 | } catch (e) { 51 | const revertFound = e.message.search('revert') >= 0 52 | assert(revertFound, `Expected "revert", got ${e} instead`) 53 | } 54 | }) 55 | }) 56 | 57 | contract('Ethbase: multiple subscribers', async () => { 58 | let instance 59 | let sub1 60 | let sub2 61 | let eventId 62 | 63 | before(async () => { 64 | instance = await Ethbase.deployed() 65 | sub1 = await Subscriber.deployed() 66 | sub2 = await Subscriber.new(instance.address) 67 | }) 68 | 69 | it('should subscribe both contracts', async () => { 70 | let topic = web3.utils.sha3('Transfer(uint256)') 71 | let method = web3.utils.sha3('setValue(uint256)') 72 | 73 | let tx = await instance.subscribe(emitter.address, topic, sub1.address, method) 74 | eventId = tx.logs[0].args.eventId 75 | 76 | tx = await instance.subscribe(emitter.address, topic, sub2.address, method) 77 | assert.equal(tx.logs[0].args.eventId, eventId) 78 | }) 79 | }) 80 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Ethbase 2 | [![Gitter chat](https://badges.gitter.im/gitterHQ/gitter.png)](https://gitter.im/planet-ethereum/Lobby) 3 | 4 | The aim of this project is to provide a mechanism for enabling smart contracts to subscribe to events emitted in other contracts. For a short description of the why and how of this project, you can refer to the [about](https://github.com/planet-ethereum/ethbase/wiki/About) wiki page. 5 | 6 | Events are submitted to the network by [relayers](https://github.com/planet-ethereum/relay-network). To make the process trustless, relayers provide [MPT](https://github.com/ethereum/wiki/wiki/Patricia-Tree#receipts-trie) proofs that the event log they're submitting has been included in the receipts trie of a recent block (last 256 blocks). 7 | 8 | *Note:* Ethbase is currently in a very early development and experimental stage, and is not suitable for production use. 9 | 10 | Rinkeby: [0x2ccf778b371e24010b6733a377e999844bdc114a](https://rinkeby.etherscan.io/address/0x2ccf778b371e24010b6733a377e999844bdc114a) 11 | 12 | ## Usage 13 | In the given [example](contracts/example), we have an `Emitter` contract, which emits `Transfer(uint256)` when its method is called. We want our `Subscriber` contract to update its state, whenever `Transfer` is emitted. This can be done, by subscribing to the registry, and specifying the target event and the callback method that should be executed when the event is emitted. Afterwards, any [relayer](https://github.com/planet-ethereum/relay-network), upon seeing a `Transfer` event, can call `submitLog` on `Ethbase`, which would in turn invoke `Subscriber`'s specified method. 14 | 15 | To subscribe to an event, you can create an instance of `Ethbase` and call `subscribe` in your smart contract: 16 | 17 | ``` 18 | ethbase = Ethbase(ethbase); 19 | ethbase.subscribe(emitter, eventTopic, subscriber, method); 20 | ``` 21 | 22 | - **emitter** is the address of the contract that emits the event. 23 | - **eventTopic** is the topic you're interested in, e.g. `keccak256("Transfer(uint256)")`. 24 | - **subscriber** the contract that should be called when event is emitted, e.g. `this`. 25 | - **method** the method that should be called. Method must have the same signature as the event, and generated by `bytes4(keccak256("METHOD_NAME(TYPE1,TYPE2)"))`. 26 | 27 | ## Contribute 28 | Most of the discussions are happening in the [issues](https://github.com/planet-ethereum/ethbase/issues) and [pull requests](https://github.com/planet-ethereum/ethbase/pulls), Suggestions, contributions and critisisms are more than welcome, join in. 29 | 30 | If you want to get involved: first of all, thanks for considering to do so! Here's how you can setup your environment: 31 | 32 | ### Requirements 33 | Node is the only thing you need. After cloning the repository, install the dependencies, and build the contracts. 34 | 35 | ```bash 36 | $ npm i 37 | $ npx truffle compile 38 | ``` 39 | 40 | ### Tests 41 | To run the contracts test suite: 42 | 43 | ```bash 44 | $ npx truffle test 45 | ``` 46 | -------------------------------------------------------------------------------- /test/verifier.js: -------------------------------------------------------------------------------- 1 | const Verifier = artifacts.require('Verifier.sol') 2 | const PatriciaTrie = artifacts.require('PatriciaTrie.sol') 3 | 4 | contract('Verifier', async () => { 5 | let instance 6 | 7 | before(async () => { 8 | Verifier.link(PatriciaTrie) 9 | instance = await Verifier.new() 10 | }) 11 | 12 | it('should verify a proof', async () => { 13 | let rootHash = '0xda2e968e25198a0a41e4dcdc6fcb03b9d49274b3d44cb35d921e4ebe3fb5c54c' 14 | 15 | let path = '0x61' 16 | let value = '0x857465737431' 17 | let nodes = '0xf83bf839808080808080c8318685746573743180a0207947cf85c03bd3d9f9ff5119267616318dcef0e12de2f8ca02ff2cdc720a978080808080808080' 18 | 19 | let ok = await instance.verifyProof.call(value, nodes, path, rootHash) 20 | assert.isTrue(ok, 'verification failed') 21 | }) 22 | 23 | it('should verify actual receipt data', async () => { 24 | let rootHash = '0xa4ae699afee1fdf34e04bb1d1343f545eb307507d51bab278455a2fdd59d9291' 25 | 26 | let path = '0x05' 27 | let value = '0xf90164018326ffacb9010000000000000000000000000000000000000000000000000000000000000000000000000400000000000000000000400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000080000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000040000000000000000000000000f85af85894fc8e2b378a37ea51b90049df56fad5f44cd35dafe1a0248dd4076d0a389d795107efafd558ce7f31ae37b441ccb9a599c60868f480d5a00000000000000000000000000000000000000000000000000000000000000003' 28 | let nodes = '0xf902f5f851a0255ecc037ff6811f9b0baa6c5758918418f00ed387046f64de740eee331b49f180808080808080a030a6010cdd0fa8d28273e1f23df5e6d2ef6e8ddf70358bf290254b7bf8abcb078080808080808080f9013180a02b116ec481e7112485436513c89b26b859f72ef8ca7868a579e4cdbca5bc9740a040ba87af7b11b0a719207dc7486a04288eef64930f723ed0ae7c5a6755803c39a05a19bbd0c0b07a3f9670f6a9b8fd81a961a75765c6979b64b14b0dd4f7f5094fa0f290f63439068283b129e7464a293b9649a391a28b50e480e80ecbb8e9a73507a06dea5f80bbcccc6abb799e614da28c6abaeaea435239b868cdee0b0adbedc276a0ccc807ffe815b6dc0d9cdfa65c3134e651e5623bca3f973941b60e88d8f28084a0044bbd26f864ec2ab65c9b4753112d1d0bfc23e45975611deb2ea972c3d9d676a0fc6d4532e18442b7cbb09507f73c08e520b9947bc7ca188d1aa10e34d76ea49ba04fa16566afa4f4a08a06009a2d0994e8990a9339b2870131827aa86cc5ac055980808080808080f9016b20b90167f90164018326ffacb9010000000000000000000000000000000000000000000000000000000000000000000000000400000000000000000000400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000080000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000040000000000000000000000000f85af85894fc8e2b378a37ea51b90049df56fad5f44cd35dafe1a0248dd4076d0a389d795107efafd558ce7f31ae37b441ccb9a599c60868f480d5a00000000000000000000000000000000000000000000000000000000000000003' 29 | 30 | let ok = await instance.verifyProof.call(value, nodes, path, rootHash) 31 | assert.isTrue(ok, 'verification failed') 32 | }) 33 | }) 34 | -------------------------------------------------------------------------------- /contracts/Ethbase.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.24; 2 | 3 | import "./Receipt.sol"; 4 | import "./Block.sol"; 5 | import "./lib/PatriciaTrie.sol"; 6 | 7 | contract Ethbase { 8 | using Block for Block.BlockHeader; 9 | 10 | event Subscribed(bytes32 eventId, address emitter, bytes32 eventTopic, address account, bytes4 method); 11 | 12 | struct Subscriber { 13 | bytes4 method; 14 | uint timestamp; 15 | } 16 | 17 | // Multiple contracts can subscribe to the same event 18 | // Key is keccak256(emitterAddr, eventName) 19 | mapping(bytes32 => mapping(address => Subscriber)) subscribers; 20 | mapping(bytes32 => address[]) subscriberList; 21 | 22 | // Keep track of submitted event logs to prevent re-submitting. 23 | // Key: keccak256(blockHash, txId, logId) 24 | mapping(bytes32 => bool) logs; 25 | 26 | modifier isSubscribed(bytes32 _eventId, address _account) { 27 | require(subscribers[_eventId][_account].timestamp != 0, "not subscribed"); 28 | _; 29 | } 30 | 31 | /** 32 | * @dev Subscribes to an event. 33 | * @param _emitter Address of contract emitting event. 34 | * @param _eventTopic E.g. keccak256(ExampleEvent(type1,type2)). 35 | * @param _account Address of subscribing contract, which should be invoked. 36 | * @param _method bytes4(keccak256(signature)) where signature ~= method(param1,param2). 37 | */ 38 | function subscribe(address _emitter, bytes32 _eventTopic, address _account, bytes4 _method) public { 39 | bytes32 eventId = keccak256(abi.encodePacked(_emitter, _eventTopic)); 40 | 41 | Subscriber storage s = subscribers[eventId][_account]; 42 | s.method = _method; 43 | s.timestamp = now; 44 | 45 | subscriberList[eventId].push(_account); 46 | 47 | emit Subscribed(eventId, _emitter, _eventTopic, _account, _method); 48 | } 49 | 50 | /** 51 | * @dev Unsubscribers from an event. 52 | * @param _eventId Name of the event. 53 | * @param _subscriber Address of contract wanting to unsubscribe. 54 | */ 55 | function unsubscribe(bytes32 _eventId, address _subscriber) public isSubscribed(_eventId, _subscriber) { 56 | delete subscribers[_eventId][_subscriber]; 57 | uint i = accountIndex(_eventId, _subscriber); 58 | delete subscriberList[_eventId][i]; 59 | } 60 | 61 | /** 62 | * @dev Submits proof of log, and invokes subscriber. 63 | * @param _receipt RLP-encoded receipt which contains log. 64 | * @param _parentNodes RLP-encoded list of proof nodes from root to leaf. 65 | * @param _key Index of TX in block. 66 | * @param _logIndex Index of log in receipt. 67 | * @param _blockHeader RLP-encoded block header. 68 | * @param _subscriber Address of subscriber. 69 | * @param _eventId EventId emitted after subscribing. 70 | */ 71 | function submitLog( 72 | bytes _receipt, 73 | bytes _parentNodes, 74 | bytes _key, 75 | uint _logIndex, 76 | bytes _blockHeader, 77 | address _subscriber, 78 | bytes32 _eventId 79 | ) public isSubscribed(_eventId, _subscriber) { 80 | Block.BlockHeader memory header = Block.decodeBlockHeader(_blockHeader); 81 | require(header.validateHeader(), "invalid block header"); 82 | 83 | bytes32 logId = keccak256(abi.encodePacked(header.hash, _key, _logIndex)); 84 | require(logs[logId] == false, "log already submitted"); 85 | 86 | // Verify proof 87 | require(PatriciaTrie.verifyProof(_receipt, _parentNodes, _key, header.receiptHash), "proof verification failed"); 88 | 89 | // Mark log as submitted 90 | logs[logId] = true; 91 | 92 | // Call subscriber 93 | Subscriber storage s = subscribers[_eventId][_subscriber]; 94 | bytes memory data = Receipt.extractLog(_receipt, _logIndex); 95 | require(_subscriber.call(s.method, data), "call to subscriber failed"); 96 | } 97 | 98 | function accountIndex(bytes32 _eventId, address _account) internal view returns(uint) { 99 | uint i = 0; 100 | while (subscriberList[_eventId][i] != _account) { 101 | i++; 102 | } 103 | return i; 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /contracts/SafeModule.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.24; 2 | 3 | import "./Receipt.sol"; 4 | import "./Block.sol"; 5 | import "./lib/PatriciaTrie.sol"; 6 | import "./safe/contracts/Module.sol"; 7 | import "./safe/contracts/OwnerManager.sol"; 8 | import "./safe/contracts/SignatureValidator.sol"; 9 | 10 | contract SafeModule is Module, SignatureValidator { 11 | using Block for Block.BlockHeader; 12 | 13 | uint256 public nonce; 14 | mapping (bytes32 => uint256) public isExecuted; 15 | mapping (bytes32 => uint256) public logs; 16 | 17 | /// @dev Setup function 18 | function setup() public { 19 | setManager(); 20 | } 21 | 22 | /** 23 | * @dev Executes tx, if event specified by user has occured. 24 | * @param _receipt RLP-encoded receipt which contains log. 25 | * @param _parentNodes RLP-encoded list of proof nodes from root to leaf. 26 | * @param _key Index of TX in block. 27 | * @param _logIndex Index of log in receipt. 28 | * @param _blockHeader RLP-encoded block header. 29 | * @param _emitter Address of event emitter. 30 | * @param _eventTopic E.g. keccak256(ExampleEvent(type1,type2)). 31 | * @param _to Destination address of Safe transaction. 32 | * @param _value Ether value of Safe transaction. 33 | * @param _data Data payload of Safe transaction. 34 | * @param _operation Operation type of Safe transaction. 35 | * @param _nonce Nonce used for this Safe transaction. 36 | * @param _signatures Packed signature data ({bytes32 r}{bytes32 s}{uint8 v}) 37 | */ 38 | function execConditional( 39 | bytes _receipt, 40 | bytes _parentNodes, 41 | bytes _key, 42 | uint _logIndex, 43 | bytes _blockHeader, 44 | address _emitter, 45 | bytes32 _eventTopic, 46 | address _to, 47 | uint256 _value, 48 | bytes _data, 49 | Enum.Operation _operation, 50 | uint256 _nonce, 51 | bytes _signatures 52 | ) public { 53 | // Checks tx signature, and that it hasn't been executed before. 54 | checkTransaction(_to, _value, _data, _operation, _nonce, _signatures); 55 | nonce++; 56 | 57 | // Verifies log proof, checks that this log hasn't been submitted, 58 | // and if not, marks the log as submitted. 59 | processLog(_receipt, _parentNodes, _key, _logIndex, _blockHeader, _emitter, _eventTopic); 60 | 61 | // TODO: Append log data and pass to callback 62 | // bytes memory data = Receipt.extractLog(_receipt, _logIndex); 63 | require(manager.execTransactionFromModule(_to, _value, _data, _operation), "Could not execute transaction"); 64 | } 65 | 66 | function processLog( 67 | bytes _receipt, 68 | bytes _parentNodes, 69 | bytes _key, 70 | uint _logIndex, 71 | bytes _blockHeader, 72 | address _emitter, 73 | bytes32 _eventTopic 74 | ) internal view { 75 | Block.BlockHeader memory header = Block.decodeBlockHeader(_blockHeader); 76 | require(header.validateHeader(), "invalid block header"); 77 | 78 | bytes32 logId = keccak256(abi.encodePacked(header.hash, _key, _logIndex)); 79 | require(logs[logId] == 0, "log already submitted"); 80 | 81 | // Verify proof 82 | require(PatriciaTrie.verifyProof(_receipt, _parentNodes, _key, header.receiptHash), "proof verification failed"); 83 | 84 | // Mark log as submitted 85 | logs[logId] = 1; 86 | } 87 | 88 | function checkTransaction( 89 | address _to, 90 | uint256 _value, 91 | bytes _data, 92 | Enum.Operation _operation, 93 | uint256 _nonce, 94 | bytes _signatures 95 | ) internal view { 96 | bytes32 transactionHash = getTransactionHash(_to, _value, _data, _operation, _nonce); 97 | require(isExecuted[transactionHash] == 0, "Transaction already executed"); 98 | 99 | checkHash(transactionHash, _signatures); 100 | } 101 | 102 | function checkHash(bytes32 transactionHash, bytes signatures) internal view { 103 | // There cannot be an owner with address 0. 104 | address lastOwner = address(0); 105 | address currentOwner; 106 | uint256 i; 107 | uint256 threshold = OwnerManager(manager).getThreshold(); 108 | // Validate threshold is reached. 109 | for (i = 0; i < threshold; i++) { 110 | currentOwner = recoverKey(transactionHash, signatures, i); 111 | require(OwnerManager(manager).isOwner(currentOwner), "Signature not provided by owner"); 112 | require(currentOwner > lastOwner, "Signatures are not ordered by owner address"); 113 | lastOwner = currentOwner; 114 | } 115 | } 116 | 117 | /// @dev Returns hash to be signed by owners. 118 | /// @param to Destination address. 119 | /// @param value Ether value. 120 | /// @param data Data payload. 121 | /// @param operation Operation type. 122 | /// @param _nonce Transaction nonce. 123 | /// @return Transaction hash. 124 | function getTransactionHash( 125 | address to, 126 | uint256 value, 127 | bytes data, 128 | Enum.Operation operation, 129 | uint256 _nonce 130 | ) public view returns (bytes32) { 131 | return keccak256(abi.encodePacked(byte(0x19), byte(0), this, to, value, data, operation, _nonce)); 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /contracts/lib/PatriciaTrie.sol: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016-2018 Clearmatics Technologies Ltd 2 | // SPDX-License-Identifier: LGPL-3.0+ 3 | pragma solidity ^0.4.23; 4 | 5 | import "./RLP.sol"; 6 | 7 | library PatriciaTrie { 8 | 9 | function verifyProof(bytes _value, bytes _parentNodes, bytes _path, bytes32 _root) public returns (bool) { 10 | RLP.RLPItem memory nodes = RLP.toRLPItem(_parentNodes); 11 | RLP.RLPItem[] memory parentNodes = RLP.toList(nodes); 12 | 13 | bytes32 currentNodeKey = _root; 14 | 15 | uint traversedNibbles = 0; 16 | bytes memory path = toNibbleArray(_path, false); 17 | 18 | for (uint i = 0; i < parentNodes.length; i++) { 19 | if (currentNodeKey != keccak256(RLP.toBytes(parentNodes[i]))) { 20 | return false; 21 | } 22 | 23 | RLP.RLPItem[] memory currentNode = RLP.toList(parentNodes[i]); 24 | 25 | if (currentNode.length == 17) { 26 | // Branch Node 27 | (currentNodeKey, traversedNibbles) = processBranchNode(currentNode, traversedNibbles, path, _value); 28 | } else if (currentNode.length == 2) { 29 | // Extension/Leaf Node 30 | (currentNodeKey, traversedNibbles) = processExtensionLeafNode(currentNode, traversedNibbles, path, _value); 31 | } else { 32 | return false; 33 | } 34 | 35 | // Read comment block below for explanation of this 36 | if (currentNodeKey == 0x0) { 37 | return traversedNibbles == 1; 38 | } 39 | } 40 | } 41 | 42 | /** 43 | Node Processing 44 | 45 | processBranchNodes returns (bytes32 currentNodeKey, uint traversedNibbles) 46 | processExtensionLeafNode returns (bytes32 currentNodeKey, uint traversedNibbles) 47 | 48 | Due to the dual nature of how a branch node may be processed where the next node in the path could be either 49 | referenced by hash or nested in the branch node is the total RLP-encoded node is less than 32 bytes (nested node), 50 | we required separation of logic due to "stack-too-deep" issues and opted for a messy returning of reused variables. 51 | These returned variables now hold two purposes: 52 | 53 | * currentNodeKey (bytes32): Holds value of the hash of the next node to be processed. If processing is finished this 54 | value is 0x0. 55 | * traversedNibbles (uint): Tracks how many nibbles have been traversed. If processing is finished this value will 56 | be 0 if verification failed, and 1 if verification succeeded. 57 | 58 | The dual-functionality of these variables is the crux of how I avoided stack issues which makes the code somewhat 59 | unreadable. If there is an improvement to this algorithm that can make it more readable please share. 60 | 61 | */ 62 | 63 | function processBranchNode(RLP.RLPItem[] memory _currentNode, uint _traversedNibbles, bytes memory _path, bytes _value) private returns (bytes32, uint) { 64 | if (_traversedNibbles == _path.length) { 65 | return (0x0, checkNodeValue(_value, RLP.toBytes(_currentNode[16])) ? 1 : 0); 66 | } 67 | 68 | uint16 nextPathNibble = uint16(_path[_traversedNibbles]); 69 | RLP.RLPItem memory nextNode = _currentNode[nextPathNibble]; 70 | _traversedNibbles += 1; 71 | 72 | bytes32 currentNodeKey; 73 | if (RLP.toBytes(nextNode).length < 32) { 74 | //Nested 'Node' 75 | (currentNodeKey, _traversedNibbles) = processNestedNode(nextNode, _traversedNibbles, _path, _value); 76 | } else { 77 | currentNodeKey = RLP.toBytes32(_currentNode[nextPathNibble]); 78 | } 79 | return (currentNodeKey, _traversedNibbles); 80 | } 81 | 82 | function processExtensionLeafNode(RLP.RLPItem[] memory _currentNode, uint _traversedNibbles, bytes memory _path, bytes _value) private returns (bytes32, uint) { 83 | bytes memory nextPathNibbles = RLP.toData(_currentNode[0]); 84 | _traversedNibbles += toNibbleArray(nextPathNibbles, true).length; 85 | 86 | if (_traversedNibbles == _path.length) { 87 | return (0x0, checkNodeValue(_value, RLP.toData(_currentNode[1])) ? 1 : 0); 88 | } 89 | 90 | // Reached a leaf before end of the path. Proof false. 91 | if (toNibbleArray(nextPathNibbles, true).length == 0) { 92 | return (0x0, 0); 93 | } 94 | 95 | bytes memory nextNodeKey = RLP.toData(_currentNode[1]); 96 | bytes32 currentNodeKey = bytesToBytes32(nextNodeKey, 0); 97 | 98 | return (currentNodeKey, _traversedNibbles); 99 | } 100 | 101 | function processNestedNode(RLP.RLPItem memory _nextNode, uint _traversedNibbles, bytes memory _path, bytes _value) private returns (bytes32, uint) { 102 | RLP.RLPItem[] memory currentNode = RLP.toList(_nextNode); 103 | if (currentNode.length == 17) { 104 | // Extension Node 105 | return processBranchNode(currentNode, _traversedNibbles, _path, _value); 106 | } else if (currentNode.length == 2) { 107 | // Leaf Node 108 | return processExtensionLeafNode(currentNode, _traversedNibbles, _path, _value); 109 | } else { 110 | return (0x0, 0); 111 | } 112 | } 113 | 114 | function checkNodeValue(bytes _expected, bytes _nodeValue) private returns (bool) { 115 | return keccak256(_expected) == keccak256(_nodeValue); 116 | } 117 | 118 | function toNibbleArray(bytes b, bool hexPrefixed) private pure returns (bytes) { 119 | bytes memory nibbleArray = new bytes(255); 120 | 121 | uint8 nibblesFound = 0; 122 | for (uint i = 0; i < b.length; i++) { 123 | byte[2] memory nibbles = byteToNibbles(b[i]); 124 | 125 | if (hexPrefixed && i == 0) { 126 | if (nibbles[0] == 1 || nibbles[0] == 3) { 127 | nibbleArray[nibblesFound] = nibbles[1]; 128 | nibblesFound += 1; 129 | } 130 | } else { 131 | nibbleArray[nibblesFound] = nibbles[0]; 132 | nibbleArray[nibblesFound + 1] = nibbles[1]; 133 | nibblesFound += 2; 134 | } 135 | } 136 | 137 | bytes memory finiteNibbleArray = new bytes(nibblesFound); 138 | for (uint j = 0; j < nibblesFound; j++) { 139 | finiteNibbleArray[j] = nibbleArray[j]; 140 | } 141 | return finiteNibbleArray; 142 | } 143 | 144 | function byteToNibbles(byte b) private pure returns (byte[2]) { 145 | byte firstNibble = rightShift(b, 4); 146 | byte secondNibble = b & 0xf; 147 | 148 | return [firstNibble, secondNibble]; 149 | } 150 | 151 | function leftShift(byte i, uint8 bits) private pure returns (byte) { 152 | return byte(uint8(i) * uint8(2) ** uint8(bits)); 153 | } 154 | 155 | function rightShift(byte i, uint8 bits) private pure returns (byte) { 156 | return byte(uint8(i) / uint8(2) ** uint8(bits)); 157 | } 158 | 159 | function bytesToBytes32(bytes b, uint offset) private pure returns (bytes32) { 160 | bytes32 out; 161 | 162 | for (uint i = 0; i < 32; i++) { 163 | out |= bytes32(b[offset + i] & 0xFF) >> (i * 8); 164 | } 165 | return out; 166 | } 167 | } 168 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU LESSER GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | 9 | This version of the GNU Lesser General Public License incorporates 10 | the terms and conditions of version 3 of the GNU General Public 11 | License, supplemented by the additional permissions listed below. 12 | 13 | 0. Additional Definitions. 14 | 15 | As used herein, "this License" refers to version 3 of the GNU Lesser 16 | General Public License, and the "GNU GPL" refers to version 3 of the GNU 17 | General Public License. 18 | 19 | "The Library" refers to a covered work governed by this License, 20 | other than an Application or a Combined Work as defined below. 21 | 22 | An "Application" is any work that makes use of an interface provided 23 | by the Library, but which is not otherwise based on the Library. 24 | Defining a subclass of a class defined by the Library is deemed a mode 25 | of using an interface provided by the Library. 26 | 27 | A "Combined Work" is a work produced by combining or linking an 28 | Application with the Library. The particular version of the Library 29 | with which the Combined Work was made is also called the "Linked 30 | Version". 31 | 32 | The "Minimal Corresponding Source" for a Combined Work means the 33 | Corresponding Source for the Combined Work, excluding any source code 34 | for portions of the Combined Work that, considered in isolation, are 35 | based on the Application, and not on the Linked Version. 36 | 37 | The "Corresponding Application Code" for a Combined Work means the 38 | object code and/or source code for the Application, including any data 39 | and utility programs needed for reproducing the Combined Work from the 40 | Application, but excluding the System Libraries of the Combined Work. 41 | 42 | 1. Exception to Section 3 of the GNU GPL. 43 | 44 | You may convey a covered work under sections 3 and 4 of this License 45 | without being bound by section 3 of the GNU GPL. 46 | 47 | 2. Conveying Modified Versions. 48 | 49 | If you modify a copy of the Library, and, in your modifications, a 50 | facility refers to a function or data to be supplied by an Application 51 | that uses the facility (other than as an argument passed when the 52 | facility is invoked), then you may convey a copy of the modified 53 | version: 54 | 55 | a) under this License, provided that you make a good faith effort to 56 | ensure that, in the event an Application does not supply the 57 | function or data, the facility still operates, and performs 58 | whatever part of its purpose remains meaningful, or 59 | 60 | b) under the GNU GPL, with none of the additional permissions of 61 | this License applicable to that copy. 62 | 63 | 3. Object Code Incorporating Material from Library Header Files. 64 | 65 | The object code form of an Application may incorporate material from 66 | a header file that is part of the Library. You may convey such object 67 | code under terms of your choice, provided that, if the incorporated 68 | material is not limited to numerical parameters, data structure 69 | layouts and accessors, or small macros, inline functions and templates 70 | (ten or fewer lines in length), you do both of the following: 71 | 72 | a) Give prominent notice with each copy of the object code that the 73 | Library is used in it and that the Library and its use are 74 | covered by this License. 75 | 76 | b) Accompany the object code with a copy of the GNU GPL and this license 77 | document. 78 | 79 | 4. Combined Works. 80 | 81 | You may convey a Combined Work under terms of your choice that, 82 | taken together, effectively do not restrict modification of the 83 | portions of the Library contained in the Combined Work and reverse 84 | engineering for debugging such modifications, if you also do each of 85 | the following: 86 | 87 | a) Give prominent notice with each copy of the Combined Work that 88 | the Library is used in it and that the Library and its use are 89 | covered by this License. 90 | 91 | b) Accompany the Combined Work with a copy of the GNU GPL and this license 92 | document. 93 | 94 | c) For a Combined Work that displays copyright notices during 95 | execution, include the copyright notice for the Library among 96 | these notices, as well as a reference directing the user to the 97 | copies of the GNU GPL and this license document. 98 | 99 | d) Do one of the following: 100 | 101 | 0) Convey the Minimal Corresponding Source under the terms of this 102 | License, and the Corresponding Application Code in a form 103 | suitable for, and under terms that permit, the user to 104 | recombine or relink the Application with a modified version of 105 | the Linked Version to produce a modified Combined Work, in the 106 | manner specified by section 6 of the GNU GPL for conveying 107 | Corresponding Source. 108 | 109 | 1) Use a suitable shared library mechanism for linking with the 110 | Library. A suitable mechanism is one that (a) uses at run time 111 | a copy of the Library already present on the user's computer 112 | system, and (b) will operate properly with a modified version 113 | of the Library that is interface-compatible with the Linked 114 | Version. 115 | 116 | e) Provide Installation Information, but only if you would otherwise 117 | be required to provide such information under section 6 of the 118 | GNU GPL, and only to the extent that such information is 119 | necessary to install and execute a modified version of the 120 | Combined Work produced by recombining or relinking the 121 | Application with a modified version of the Linked Version. (If 122 | you use option 4d0, the Installation Information must accompany 123 | the Minimal Corresponding Source and Corresponding Application 124 | Code. If you use option 4d1, you must provide the Installation 125 | Information in the manner specified by section 6 of the GNU GPL 126 | for conveying Corresponding Source.) 127 | 128 | 5. Combined Libraries. 129 | 130 | You may place library facilities that are a work based on the 131 | Library side by side in a single library together with other library 132 | facilities that are not Applications and are not covered by this 133 | License, and convey such a combined library under terms of your 134 | choice, if you do both of the following: 135 | 136 | a) Accompany the combined library with a copy of the same work based 137 | on the Library, uncombined with any other library facilities, 138 | conveyed under the terms of this License. 139 | 140 | b) Give prominent notice with the combined library that part of it 141 | is a work based on the Library, and explaining where to find the 142 | accompanying uncombined form of the same work. 143 | 144 | 6. Revised Versions of the GNU Lesser General Public License. 145 | 146 | The Free Software Foundation may publish revised and/or new versions 147 | of the GNU Lesser General Public License from time to time. Such new 148 | versions will be similar in spirit to the present version, but may 149 | differ in detail to address new problems or concerns. 150 | 151 | Each version is given a distinguishing version number. If the 152 | Library as you received it specifies that a certain numbered version 153 | of the GNU Lesser General Public License "or any later version" 154 | applies to it, you have the option of following the terms and 155 | conditions either of that published version or of any later version 156 | published by the Free Software Foundation. If the Library as you 157 | received it does not specify a version number of the GNU Lesser 158 | General Public License, you may choose any version of the GNU Lesser 159 | General Public License ever published by the Free Software Foundation. 160 | 161 | If the Library as you received it specifies that a proxy can decide 162 | whether future versions of the GNU Lesser General Public License shall 163 | apply, that proxy's public statement of acceptance of any version is 164 | permanent authorization for you to choose that version for the 165 | Library. 166 | -------------------------------------------------------------------------------- /contracts/lib/RLP.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.23; 2 | /** 3 | * @title RLPReader 4 | * 5 | * RLPReader is used to read and parse RLP encoded data in memory. 6 | * 7 | * @author Andreas Olofsson (androlo1980@gmail.com) from https://github.com/androlo/standard-contracts/blob/master/contracts/src/codec/RLP.sol 8 | */ 9 | library RLP { 10 | 11 | uint constant DATA_SHORT_START = 0x80; 12 | uint constant DATA_LONG_START = 0xB8; 13 | uint constant LIST_SHORT_START = 0xC0; 14 | uint constant LIST_LONG_START = 0xF8; 15 | 16 | uint constant DATA_LONG_OFFSET = 0xB7; 17 | uint constant LIST_LONG_OFFSET = 0xF7; 18 | 19 | 20 | struct RLPItem { 21 | uint _unsafe_memPtr; // Pointer to the RLP-encoded bytes. 22 | uint _unsafe_length; // Number of bytes. This is the full length of the string. 23 | } 24 | 25 | struct Iterator { 26 | RLPItem _unsafe_item; // Item that's being iterated over. 27 | uint _unsafe_nextPtr; // Position of the next item in the list. 28 | } 29 | 30 | /* Iterator */ 31 | 32 | function next(Iterator memory self) internal pure returns (RLPItem memory subItem) { 33 | if(hasNext(self)) { 34 | uint ptr = self._unsafe_nextPtr; 35 | uint itemLength = _itemLength(ptr); 36 | subItem._unsafe_memPtr = ptr; 37 | subItem._unsafe_length = itemLength; 38 | self._unsafe_nextPtr = ptr + itemLength; 39 | } 40 | else 41 | revert(); 42 | } 43 | 44 | function next(Iterator memory self, bool strict) internal pure returns (RLPItem memory subItem) { 45 | subItem = next(self); 46 | if(strict && !_validate(subItem)) 47 | revert(); 48 | return; 49 | } 50 | 51 | function hasNext(Iterator memory self) internal pure returns (bool) { 52 | RLPItem memory item = self._unsafe_item; 53 | return self._unsafe_nextPtr < item._unsafe_memPtr + item._unsafe_length; 54 | } 55 | 56 | /* RLPItem */ 57 | 58 | /// @dev Creates an RLPItem from an array of RLP encoded bytes. 59 | /// @param self The RLP encoded bytes. 60 | /// @return An RLPItem 61 | function toRLPItem(bytes memory self) internal pure returns (RLPItem memory) { 62 | uint len = self.length; 63 | if (len == 0) { 64 | return RLPItem(0, 0); 65 | } 66 | uint memPtr; 67 | assembly { 68 | memPtr := add(self, 0x20) 69 | } 70 | return RLPItem(memPtr, len); 71 | } 72 | 73 | /// @dev Creates an RLPItem from an array of RLP encoded bytes. 74 | /// @param self The RLP encoded bytes. 75 | /// @param strict Will revert() if the data is not RLP encoded. 76 | /// @return An RLPItem 77 | function toRLPItem(bytes memory self, bool strict) internal pure returns (RLPItem memory) { 78 | RLPItem memory item = toRLPItem(self); 79 | if(strict) { 80 | uint len = self.length; 81 | if(_payloadOffset(item) > len) 82 | revert(); 83 | if(_itemLength(item._unsafe_memPtr) != len) 84 | revert(); 85 | if(!_validate(item)) 86 | revert(); 87 | } 88 | return item; 89 | } 90 | 91 | /// @dev Check if the RLP item is null. 92 | /// @param self The RLP item. 93 | /// @return 'true' if the item is null. 94 | function isNull(RLPItem memory self) internal pure returns (bool ret) { 95 | return self._unsafe_length == 0; 96 | } 97 | 98 | /// @dev Check if the RLP item is a list. 99 | /// @param self The RLP item. 100 | /// @return 'true' if the item is a list. 101 | function isList(RLPItem memory self) internal pure returns (bool ret) { 102 | if (self._unsafe_length == 0) 103 | return false; 104 | uint memPtr = self._unsafe_memPtr; 105 | assembly { 106 | ret := iszero(lt(byte(0, mload(memPtr)), 0xC0)) 107 | } 108 | } 109 | 110 | /// @dev Check if the RLP item is data. 111 | /// @param self The RLP item. 112 | /// @return 'true' if the item is data. 113 | function isData(RLPItem memory self) internal pure returns (bool ret) { 114 | if (self._unsafe_length == 0) 115 | return false; 116 | uint memPtr = self._unsafe_memPtr; 117 | assembly { 118 | ret := lt(byte(0, mload(memPtr)), 0xC0) 119 | } 120 | } 121 | 122 | /// @dev Check if the RLP item is empty (string or list). 123 | /// @param self The RLP item. 124 | /// @return 'true' if the item is null. 125 | function isEmpty(RLPItem memory self) internal pure returns (bool ret) { 126 | if(isNull(self)) 127 | return false; 128 | uint b0; 129 | uint memPtr = self._unsafe_memPtr; 130 | assembly { 131 | b0 := byte(0, mload(memPtr)) 132 | } 133 | return (b0 == DATA_SHORT_START || b0 == LIST_SHORT_START); 134 | } 135 | 136 | /// @dev Get the number of items in an RLP encoded list. 137 | /// @param self The RLP item. 138 | /// @return The number of items. 139 | function items(RLPItem memory self) internal pure returns (uint) { 140 | if (!isList(self)) 141 | return 0; 142 | uint b0; 143 | uint memPtr = self._unsafe_memPtr; 144 | assembly { 145 | b0 := byte(0, mload(memPtr)) 146 | } 147 | uint pos = memPtr + _payloadOffset(self); 148 | uint last = memPtr + self._unsafe_length - 1; 149 | uint itms; 150 | while(pos <= last) { 151 | pos += _itemLength(pos); 152 | itms++; 153 | } 154 | return itms; 155 | } 156 | 157 | /// @dev Create an iterator. 158 | /// @param self The RLP item. 159 | /// @return An 'Iterator' over the item. 160 | function iterator(RLPItem memory self) internal pure returns (Iterator memory it) { 161 | if (!isList(self)) 162 | revert(); 163 | uint ptr = self._unsafe_memPtr + _payloadOffset(self); 164 | it._unsafe_item = self; 165 | it._unsafe_nextPtr = ptr; 166 | } 167 | 168 | /// @dev Return the RLP encoded bytes. 169 | /// @param self The RLPItem. 170 | /// @return The bytes. 171 | function toBytes(RLPItem memory self) internal returns (bytes memory bts) { 172 | uint len = self._unsafe_length; 173 | if (len == 0) 174 | return; 175 | bts = new bytes(len); 176 | _copyToBytes(self._unsafe_memPtr, bts, len); 177 | } 178 | 179 | /// @dev Decode an RLPItem into bytes. This will not work if the 180 | /// RLPItem is a list. 181 | /// @param self The RLPItem. 182 | /// @return The decoded string. 183 | function toData(RLPItem memory self) internal returns (bytes memory bts) { 184 | if(!isData(self)) 185 | revert(); 186 | uint rStartPos; 187 | uint len; 188 | (rStartPos, len) = _decode(self); 189 | bts = new bytes(len); 190 | _copyToBytes(rStartPos, bts, len); 191 | } 192 | 193 | /// @dev Get the list of sub-items from an RLP encoded list. 194 | /// Warning: This is inefficient, as it requires that the list is read twice. 195 | /// @param self The RLP item. 196 | /// @return Array of RLPItems. 197 | function toList(RLPItem memory self) internal pure returns (RLPItem[] memory list) { 198 | if(!isList(self)) 199 | revert(); 200 | uint numItems = items(self); 201 | list = new RLPItem[](numItems); 202 | Iterator memory it = iterator(self); 203 | uint idx; 204 | while(hasNext(it)) { 205 | list[idx] = next(it); 206 | idx++; 207 | } 208 | } 209 | 210 | /// @dev Decode an RLPItem into an ascii string. This will not work if the 211 | /// RLPItem is a list. 212 | /// @param self The RLPItem. 213 | /// @return The decoded string. 214 | function toAscii(RLPItem memory self) internal returns (string memory str) { 215 | if(!isData(self)) 216 | revert(); 217 | uint rStartPos; 218 | uint len; 219 | (rStartPos, len) = _decode(self); 220 | bytes memory bts = new bytes(len); 221 | _copyToBytes(rStartPos, bts, len); 222 | str = string(bts); 223 | } 224 | 225 | /// @dev Decode an RLPItem into a uint. This will not work if the 226 | /// RLPItem is a list. 227 | /// @param self The RLPItem. 228 | /// @return The decoded string. 229 | function toUint(RLPItem memory self) internal pure returns (uint data) { 230 | if(!isData(self)) 231 | revert(); 232 | uint rStartPos; 233 | uint len; 234 | (rStartPos, len) = _decode(self); 235 | if (len > 32 || len == 0) 236 | revert(); 237 | assembly { 238 | data := div(mload(rStartPos), exp(256, sub(32, len))) 239 | } 240 | } 241 | 242 | /// @dev Decode an RLPItem into a boolean. This will not work if the 243 | /// RLPItem is a list. 244 | /// @param self The RLPItem. 245 | /// @return The decoded string. 246 | function toBool(RLPItem memory self) internal pure returns (bool data) { 247 | if(!isData(self)) 248 | revert(); 249 | uint rStartPos; 250 | uint len; 251 | (rStartPos, len) = _decode(self); 252 | if (len != 1) 253 | revert(); 254 | uint temp; 255 | assembly { 256 | temp := byte(0, mload(rStartPos)) 257 | } 258 | if (temp > 1) 259 | revert(); 260 | return temp == 1 ? true : false; 261 | } 262 | 263 | /// @dev Decode an RLPItem into a byte. This will not work if the 264 | /// RLPItem is a list. 265 | /// @param self The RLPItem. 266 | /// @return The decoded string. 267 | function toByte(RLPItem memory self) internal pure returns (byte data) { 268 | if(!isData(self)) 269 | revert(); 270 | uint rStartPos; 271 | uint len; 272 | (rStartPos, len) = _decode(self); 273 | if (len != 1) 274 | revert(); 275 | uint temp; 276 | assembly { 277 | temp := byte(0, mload(rStartPos)) 278 | } 279 | return byte(temp); 280 | } 281 | 282 | /// @dev Decode an RLPItem into an int. This will not work if the 283 | /// RLPItem is a list. 284 | /// @param self The RLPItem. 285 | /// @return The decoded string. 286 | function toInt(RLPItem memory self) internal pure returns (int data) { 287 | return int(toUint(self)); 288 | } 289 | 290 | /// @dev Decode an RLPItem into a bytes32. This will not work if the 291 | /// RLPItem is a list. 292 | /// @param self The RLPItem. 293 | /// @return The decoded string. 294 | function toBytes32(RLPItem memory self) internal pure returns (bytes32 data) { 295 | return bytes32(toUint(self)); 296 | } 297 | 298 | /// @dev Decode an RLPItem into an address. This will not work if the 299 | /// RLPItem is a list. 300 | /// @param self The RLPItem. 301 | /// @return The decoded string. 302 | function toAddress(RLPItem memory self) internal pure returns (address data) { 303 | if(!isData(self)) 304 | revert(); 305 | uint rStartPos; 306 | uint len; 307 | (rStartPos, len) = _decode(self); 308 | if (len != 20) 309 | revert(); 310 | assembly { 311 | data := div(mload(rStartPos), exp(256, 12)) 312 | } 313 | } 314 | 315 | // Get the payload offset. 316 | function _payloadOffset(RLPItem memory self) private pure returns (uint) { 317 | if(self._unsafe_length == 0) 318 | return 0; 319 | uint b0; 320 | uint memPtr = self._unsafe_memPtr; 321 | assembly { 322 | b0 := byte(0, mload(memPtr)) 323 | } 324 | if(b0 < DATA_SHORT_START) 325 | return 0; 326 | if(b0 < DATA_LONG_START || (b0 >= LIST_SHORT_START && b0 < LIST_LONG_START)) 327 | return 1; 328 | if(b0 < LIST_SHORT_START) 329 | return b0 - DATA_LONG_OFFSET + 1; 330 | return b0 - LIST_LONG_OFFSET + 1; 331 | } 332 | 333 | // Get the full length of an RLP item. 334 | function _itemLength(uint memPtr) private pure returns (uint len) { 335 | uint b0; 336 | assembly { 337 | b0 := byte(0, mload(memPtr)) 338 | } 339 | if (b0 < DATA_SHORT_START) 340 | len = 1; 341 | else if (b0 < DATA_LONG_START) 342 | len = b0 - DATA_SHORT_START + 1; 343 | else if (b0 < LIST_SHORT_START) { 344 | assembly { 345 | let bLen := sub(b0, 0xB7) // bytes length (DATA_LONG_OFFSET) 346 | let dLen := div(mload(add(memPtr, 1)), exp(256, sub(32, bLen))) // data length 347 | len := add(1, add(bLen, dLen)) // total length 348 | } 349 | } 350 | else if (b0 < LIST_LONG_START) 351 | len = b0 - LIST_SHORT_START + 1; 352 | else { 353 | assembly { 354 | let bLen := sub(b0, 0xF7) // bytes length (LIST_LONG_OFFSET) 355 | let dLen := div(mload(add(memPtr, 1)), exp(256, sub(32, bLen))) // data length 356 | len := add(1, add(bLen, dLen)) // total length 357 | } 358 | } 359 | } 360 | 361 | // Get start position and length of the data. 362 | function _decode(RLPItem memory self) private pure returns (uint memPtr, uint len) { 363 | if(!isData(self)) 364 | revert(); 365 | uint b0; 366 | uint start = self._unsafe_memPtr; 367 | assembly { 368 | b0 := byte(0, mload(start)) 369 | } 370 | if (b0 < DATA_SHORT_START) { 371 | memPtr = start; 372 | len = 1; 373 | return; 374 | } 375 | if (b0 < DATA_LONG_START) { 376 | len = self._unsafe_length - 1; 377 | memPtr = start + 1; 378 | } else { 379 | uint bLen; 380 | assembly { 381 | bLen := sub(b0, 0xB7) // DATA_LONG_OFFSET 382 | } 383 | len = self._unsafe_length - 1 - bLen; 384 | memPtr = start + bLen + 1; 385 | } 386 | return; 387 | } 388 | 389 | // Assumes that enough memory has been allocated to store in target. 390 | function _copyToBytes(uint btsPtr, bytes memory tgt, uint btsLen) private view { 391 | // Exploiting the fact that 'tgt' was the last thing to be allocated, 392 | // we can write entire words, and just overwrite any excess. 393 | assembly { 394 | { 395 | let i := 0 // Start at arr + 0x20 396 | let words := div(add(btsLen, 31), 32) 397 | let rOffset := btsPtr 398 | let wOffset := add(tgt, 0x20) 399 | tag_loop: 400 | jumpi(end, eq(i, words)) 401 | { 402 | let offset := mul(i, 0x20) 403 | mstore(add(wOffset, offset), mload(add(rOffset, offset))) 404 | i := add(i, 1) 405 | } 406 | jump(tag_loop) 407 | end: 408 | mstore(add(tgt, add(0x20, mload(tgt))), 0) 409 | } 410 | } 411 | } 412 | 413 | // Check that an RLP item is valid. 414 | function _validate(RLPItem memory self) private pure returns (bool ret) { 415 | // Check that RLP is well-formed. 416 | uint b0; 417 | uint b1; 418 | uint memPtr = self._unsafe_memPtr; 419 | assembly { 420 | b0 := byte(0, mload(memPtr)) 421 | b1 := byte(1, mload(memPtr)) 422 | } 423 | if(b0 == DATA_SHORT_START + 1 && b1 < DATA_SHORT_START) 424 | return false; 425 | return true; 426 | } 427 | } 428 | --------------------------------------------------------------------------------