├── .gitignore ├── src ├── dispatcher.sol ├── dispatcher.lll └── arithmetic.lll ├── README.md └── lib ├── constants.lll └── utilities.lll /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | -------------------------------------------------------------------------------- /src/dispatcher.sol: -------------------------------------------------------------------------------- 1 | contract Dispatcher { 2 | 3 | // Functions. 4 | function Dispatcher(address contractAddress); 5 | function initialize() returns (bool result); 6 | function replace(address newContract) returns (address newAddress); 7 | function double(uint256 multiplicand) returns (uint256 product); 8 | function halve(uint256 dividend) returns (uint256 quotient); 9 | 10 | // Events. 11 | event Initialized(bool result); 12 | event Replaced(address indexed oldAddress, address newAddress); 13 | event Doubled(uint256 product); 14 | event Halved(uint256 quotient); 15 | 16 | } 17 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## lll-dispatcher 2 | This repository contains an Ethereum smart contract function dispatcher and an associated example contract, both written in LLL, Ethereum's Low-level Lisp-like Language. Its purpose is to show that LLL is a viable language in which to write smart contracts and that doing so is not as difficult as one might assume. 3 | 4 | I've written a [series of articles](http://blog.syrinx.net/the-resurrection-of-lll-part-1/) on the subject where I go into great detail describing the code and my motivations for suggesting LLL as an alternative smart contract development language. 5 | 6 | The original LLL documentation was removed from the Wiki a while back, but it can still be found [here](https://github.com/ethereum/cpp-ethereum/wiki/LLL-PoC-6/04fae9e627ac84d771faddcf60098ad09230ab58). 7 | 8 | ## Release History 9 | 10 | * 0.0.1 - Initial commit. 11 | 12 | ## Contributors 13 | 14 | Daniel Ellison – [daniel@syrinx.net](mailto:daniel@syrinx.net) - [@zigguratt](https://twitter.com/zigguratt) 15 | 16 | ## License 17 | 18 | Distributed under the Apache License, Version 2.0. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0. 19 | -------------------------------------------------------------------------------- /lib/constants.lll: -------------------------------------------------------------------------------- 1 | ;;; --------------------------------------------------------------------------- 2 | ;;; Library: Constants 3 | ;;; 4 | ;;; Copyright 2016 Daniel Ellison 5 | ;;; 6 | ;;; Licensed under the Apache License, Version 2.0 (the "License"); 7 | ;;; you may not use this file except in compliance with the License. 8 | ;;; You may obtain a copy of the License at 9 | ;;; 10 | ;;; http://www.apache.org/licenses/LICENSE-2.0 11 | ;;; 12 | ;;; Unless required by applicable law or agreed to in writing, software 13 | ;;; distributed under the License is distributed on an "AS IS" BASIS, 14 | ;;; WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | ;;; See the License for the specific language governing permissions and 16 | ;;; limitations under the License. 17 | ;;; 18 | ;;; Version: 19 | ;;; 0.0.1 20 | 21 | ;; ---------------------------------------------------------------------------- 22 | ;; Start of Constants. 23 | 24 | (seq 25 | 26 | ;; -------------------------------------------------------------------------- 27 | ;; Constant definitions. 28 | 29 | ;; Booleans. 30 | (def 'true 1) 31 | (def 'false 0) 32 | 33 | ;; Memory (0x00 is used by logging). 34 | (def 'short-hash 0x20) 35 | (def 'old-address 0x40) 36 | (def 'new-address 0x60) 37 | (def 'return-code 0x80) 38 | 39 | ;; These three have a variable length. Due to memory storage gas cost, we 40 | ;; restrict the possible size to 256 bytes, which is 8 32-byte slots. After 41 | ;; 256 bytes the memory overflows with unpredictable consequences. Increase 42 | ;; these if you need more room. Likewise, reduce them if you know your data 43 | ;; requires less memory. 44 | (def 'call-data 0x0a0) ;; Usually (calldatasize). 45 | (def 'call-result 0x1a0) ;; Produced by delegatecall target. 46 | (def 'return-data 0x2a0) ;; call-result from caller's perspective. 47 | 48 | ;; Storage. 49 | (def 'contract-owner 0x00) 50 | (def 'contract-address 0x01) 51 | 52 | ;; Jumping here causes an EVM error. 53 | (def 'invalid-location 0x02) 54 | 55 | ;; Dispatcher function IDs. 56 | (def 'dispatcher 0xd8c2e7aa) ; Dispatcher(address) 57 | (def 'initialize 0x8129fc1c) ; initialize() 58 | (def 'replace 0xcabfb934) ; replace(address) 59 | 60 | ;; Arithmetic function IDs. 61 | (def 'double 0xeee97206) ; double(uint256) 62 | (def 'halve 0x20fb79e7) ; halve(uint256) 63 | 64 | ;; ---------------------------------------------------------------------------- 65 | ;; End of Constants. 66 | 67 | ) 68 | -------------------------------------------------------------------------------- /lib/utilities.lll: -------------------------------------------------------------------------------- 1 | ;;; --------------------------------------------------------------------------- 2 | ;;; Library: Utilities 3 | ;;; 4 | ;;; Copyright 2016 Daniel Ellison 5 | ;;; 6 | ;;; Licensed under the Apache License, Version 2.0 (the "License"); 7 | ;;; you may not use this file except in compliance with the License. 8 | ;;; You may obtain a copy of the License at 9 | ;;; 10 | ;;; http://www.apache.org/licenses/LICENSE-2.0 11 | ;;; 12 | ;;; Unless required by applicable law or agreed to in writing, software 13 | ;;; distributed under the License is distributed on an "AS IS" BASIS, 14 | ;;; WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | ;;; See the License for the specific language governing permissions and 16 | ;;; limitations under the License. 17 | ;;; 18 | ;;; Version: 19 | ;;; 0.0.1 20 | 21 | ;; ---------------------------------------------------------------------------- 22 | ;; Start of Utilities. 23 | 24 | (seq 25 | 26 | ;; -------------------------------------------------------------------------- 27 | ;; Modifier: only-owner 28 | ;; 29 | ;; Check that the caller is the contract owner. 30 | 31 | (def 'only-owner 32 | (when (!= (caller) @@contract-owner) 33 | (jump invalid-location))) 34 | 35 | ;; -------------------------------------------------------------------------- 36 | ;; Modifier: already-initialized 37 | ;; 38 | ;; Check whether the dispatcher has already been initialized. 39 | 40 | (def 'already-initialized 41 | (when (!= @@contract-address 0x00) 42 | (jump invalid-location))) 43 | 44 | ;; -------------------------------------------------------------------------- 45 | ;; Modifier: no-contract-address 46 | ;; 47 | ;; Check whether a contract address has been provided. 48 | 49 | (def 'no-contract-address 50 | (when (= (calldataload 4) 0x00) 51 | (jump invalid-location))) 52 | 53 | ;; -------------------------------------------------------------------------- 54 | ;; Modifier: contract-enabled 55 | ;; 56 | ;; Check whether the contract is enabled. 57 | 58 | (def 'contract-enabled 59 | (when (= (sload @@contract-address) false) 60 | (jump invalid-location))) 61 | 62 | ;; -------------------------------------------------------------------------- 63 | ;; Function: bytes4(input) 64 | ;; 65 | ;; Extracts the four leftmost bytes of the input. 66 | ;; 67 | ;; Parameters: 68 | ;; input - a long number 69 | 70 | (def 'bytes4 (input) 71 | (div input (exp 2 224))) 72 | 73 | ;; -------------------------------------------------------------------------- 74 | ;; Function: pad-right(input) 75 | ;; 76 | ;; Pads the input so it's the leftmost four bytes of the result. 77 | ;; 78 | ;; Parameters: 79 | ;; input - a short hash 80 | 81 | (def 'pad-right (input) 82 | (mul input (exp 2 224))) 83 | 84 | ;; -------------------------------------------------------------------------- 85 | ;; Function: function-id 86 | ;; 87 | ;; Retrieves the requested function ID from the first four bytes of 88 | ;; the call data. 89 | 90 | (def 'function-id 91 | (bytes4 (calldataload 0))) 92 | 93 | ;; -------------------------------------------------------------------------- 94 | ;; Function: return-size 95 | ;; 96 | ;; Retrieves data return size for a given function ID. 97 | 98 | (def 'return-size 99 | (sload (+ @@contract-address @short-hash))) 100 | 101 | ;; -------------------------------------------------------------------------- 102 | ;; Function: keccak(input) 103 | ;; 104 | ;; Determines the sha3 hash of a string. 105 | ;; 106 | ;; This function is called 'keccak' because that's actually what the EVM is 107 | ;; doing; it's not performing a sha3 hash operation. 108 | ;; 109 | ;; Parameters: 110 | ;; input - a 32 byte hex-encoded string. 111 | 112 | (def 'keccak (input) 113 | (sha3 0x00 (lit 0x00 input))) 114 | 115 | ;; ---------------------------------------------------------------------------- 116 | ;; End of Utilities. 117 | 118 | ) 119 | -------------------------------------------------------------------------------- /src/dispatcher.lll: -------------------------------------------------------------------------------- 1 | ;;; --------------------------------------------------------------------------- 2 | ;;; Contract: Dispatcher 3 | ;;; 4 | ;;; Copyright 2016 Daniel Ellison 5 | ;;; 6 | ;;; Licensed under the Apache License, Version 2.0 (the "License"); 7 | ;;; you may not use this file except in compliance with the License. 8 | ;;; You may obtain a copy of the License at 9 | ;;; 10 | ;;; http://www.apache.org/licenses/LICENSE-2.0 11 | ;;; 12 | ;;; Unless required by applicable law or agreed to in writing, software 13 | ;;; distributed under the License is distributed on an "AS IS" BASIS, 14 | ;;; WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | ;;; See the License for the specific language governing permissions and 16 | ;;; limitations under the License. 17 | ;;; 18 | ;;; Version: 19 | ;;; 0.0.1 20 | 21 | ;; ---------------------------------------------------------------------------- 22 | ;; Start of Dispatcher. 23 | 24 | (seq 25 | 26 | ;; -------------------------------------------------------------------------- 27 | ;; INIT 28 | 29 | ;; Include standard libraries. 30 | (include "../lib/constants.lll") 31 | (include "../lib/utilities.lll") 32 | 33 | ;; Set up initial state. 34 | (sstore contract-owner (caller)) 35 | (sstore contract-address 0x00) 36 | 37 | ;; -------------------------------------------------------------------------- 38 | ;; CODE 39 | 40 | (returnlll 41 | (seq 42 | 43 | ;; ---------------------------------------------------------------------- 44 | ;; Initialize the dispatcher with the contract at the address provided. 45 | ;; This function is similar to a constructor except it has to be called 46 | ;; manually with LLL. 47 | ;; 48 | ;; Signature: Dispatcher(address) 49 | ;; Returns: nothing 50 | 51 | (when (= function-id dispatcher) 52 | (seq only-owner already-initialized no-contract-address 53 | 54 | ;; Set contract address and enable it. 55 | (sstore contract-address (calldataload 4)) 56 | (sstore @@contract-address true) 57 | 58 | ;; Call the contract's initialize() function. 59 | (mstore call-data (pad-right initialize)) 60 | (delegatecall (- (gas) 1000) @@contract-address 61 | call-data 32 return-data 0) 62 | 63 | ;; Stop here; nothing to return. 64 | (stop))) 65 | 66 | ;; ---------------------------------------------------------------------- 67 | ;; No contract address has been set so we jump to an invalid location 68 | ;; which causes an exception in the EVM. This results in any ether sent 69 | ;; to the contract being returned to the caller. 70 | 71 | (when (= @@contract-address 0x00) 72 | (jump invalid-location)) 73 | 74 | ;; ---------------------------------------------------------------------- 75 | ;; First, store the short hash of the function to be called (for use by 76 | ;; the 'return-size' macro). Then copy all calldata to a known location. 77 | ;; Finally, call the contract @@contract-address providing the function 78 | ;; call return length. 79 | 80 | (mstore short-hash function-id) 81 | (calldatacopy call-data 0x00 (calldatasize)) 82 | (mstore return-code (delegatecall (- (gas) 1000) @@contract-address 83 | call-data (calldatasize) return-data return-size)) 84 | 85 | ;; ---------------------------------------------------------------------- 86 | ;; If the call was successful, relay the data returned from the call, 87 | ;; making sure we include the right length of data. If there's no return 88 | ;; data, just end contract execution. If delegatecall failed, we 89 | ;; propagate the EVM exception up. 90 | 91 | (if (= @return-code true) 92 | (if (!= return-size 0) 93 | (return return-data return-size) 94 | (stop)) 95 | (jump invalid-location)))) 96 | 97 | ;; ---------------------------------------------------------------------------- 98 | ;; End of Dispatcher. 99 | 100 | ) 101 | -------------------------------------------------------------------------------- /src/arithmetic.lll: -------------------------------------------------------------------------------- 1 | ;;; --------------------------------------------------------------------------- 2 | ;;; Contract: Arithmetic 3 | ;;; 4 | ;;; Copyright 2016 Daniel Ellison 5 | ;;; 6 | ;;; Licensed under the Apache License, Version 2.0 (the "License"); 7 | ;;; you may not use this file except in compliance with the License. 8 | ;;; You may obtain a copy of the License at 9 | ;;; 10 | ;;; http://www.apache.org/licenses/LICENSE-2.0 11 | ;;; 12 | ;;; Unless required by applicable law or agreed to in writing, software 13 | ;;; distributed under the License is distributed on an "AS IS" BASIS, 14 | ;;; WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | ;;; See the License for the specific language governing permissions and 16 | ;;; limitations under the License. 17 | ;;; 18 | ;;; Version: 19 | ;;; 0.0.1 20 | 21 | ;; ---------------------------------------------------------------------------- 22 | ;; Start of Arithmetic. 23 | 24 | (seq 25 | 26 | ;; -------------------------------------------------------------------------- 27 | ;; INIT 28 | 29 | ;; Include standard libraries. 30 | (include "../lib/constants.lll") 31 | (include "../lib/utilities.lll") 32 | 33 | ;; -------------------------------------------------------------------------- 34 | ;; CODE 35 | 36 | (returnlll 37 | (seq contract-enabled 38 | 39 | ;; ---------------------------------------------------------------------- 40 | ;; Initialize the contract function return sizes. We don't include 41 | ;; initialize() itself as it's called directly from either the dispatcher 42 | ;; or a contract that's replacing itself with another. 43 | ;; 44 | ;; Signature: initialize() 45 | ;; Returns: true 46 | 47 | (when (= function-id initialize) 48 | (seq only-owner 49 | 50 | ;; Set up function return sizes for this contract. Adding the 51 | ;; contract address and the function hash together makes each 52 | ;; function return size unique to this contract. 53 | (sstore (+ @@contract-address replace) 32) 54 | (sstore (+ @@contract-address double) 32) 55 | (sstore (+ @@contract-address halve) 32) 56 | 57 | ;; Log success and return true. 58 | (mstore call-result true) 59 | (log1 call-result 32 (sha3 0x00 (lit 0x00 "Initialized(bool)"))) 60 | (stop))) 61 | 62 | ;; ---------------------------------------------------------------------- 63 | ;; Replace this contract with the contract at the address provided. 64 | ;; 65 | ;; Signature: replace(address) 66 | ;; Returns: address of new contract 67 | 68 | (when (= function-id replace) 69 | (seq only-owner no-contract-address 70 | 71 | ;; Disable this contract. 72 | (sstore @@contract-address false) 73 | 74 | ;; Save old and new contract addresses to memory for logging. 75 | (mstore old-address @@contract-address) 76 | (mstore new-address (calldataload 4)) 77 | 78 | ;; Set new contract address and enable it. 79 | (sstore contract-address @new-address) 80 | (sstore @@contract-address true) 81 | 82 | ;; Call the new contract's initialize() function. 83 | (mstore call-data (pad-right initialize)) 84 | (delegatecall (- (gas) 1000) @@contract-address 85 | call-data 32 return-data 0) 86 | 87 | ;; Log and return result. 88 | (log2 new-address 32 89 | (sha3 0x00 (lit 0x00 "Replaced(address,address)")) 90 | @old-address) 91 | (return @@contract-address))) 92 | 93 | ;; ---------------------------------------------------------------------- 94 | ;; This multiplies the supplied number by 2. 95 | ;; 96 | ;; Signature: double(uint256) 97 | ;; Returns: product of multiplication 98 | 99 | (when (= function-id double) 100 | (seq 101 | 102 | ;; Do the doubling and save the result. 103 | (mstore call-result (mul (calldataload 4) 2)) 104 | 105 | ;; Log and return result. 106 | (log1 call-result 32 (sha3 0x00 (lit 0x00 "Doubled(uint256)"))) 107 | (return call-result 32))) 108 | 109 | ;; ---------------------------------------------------------------------- 110 | ;; This divides the supplied number by 2. 111 | ;; 112 | ;; Signature: halve(uint256) 113 | ;; Returns: quotient of division 114 | 115 | (when (= function-id halve) 116 | (seq 117 | 118 | ;; Do the halving and save the result. 119 | (mstore call-result (div (calldataload 4) 2)) 120 | 121 | ;; Log and return result. 122 | (log1 call-result 32 (sha3 0x00 (lit 0x00 "Halved(uint256)"))) 123 | (return call-result 32))) 124 | 125 | ;; ---------------------------------------------------------------------- 126 | ;; Fallback: No functions matched the function ID provided so we jump to 127 | ;; an invalid location which causes an exception in the EVM. This results 128 | ;; in any ether sent to the contract to be returned to the caller. 129 | 130 | (jump invalid-location))) 131 | 132 | ;; ---------------------------------------------------------------------------- 133 | ;; End of Arithmetic. 134 | 135 | ) 136 | --------------------------------------------------------------------------------