GhaSShee


LLL


  # LLL PoC6 LLL is the Ethereum PoC Low-level Lisp-like Language. It is Lisp-like in syntax rather than anything deeper and is designed to make easier the task of writing a program in EVM-code 1, aka ES-1. It is automatically compiled in Ethereum PoC series including PoC-3 upwards. ## Basic Syntax A contract written in LLL takes the form of a single expression. An expression may be one of: - A quoted string e.g. "Hello world!". Such strings have maximum 32 characters and evaluate to the 32 byte value with the ASCII encoding of the string aligned to the left-side (i.e. in the high-order bytes should it be interpreted as a big endian integer). - A space-less symbol-less string with a single quote at the beginning, e.g. 'HelloWorld. Again such strings have a maximum character limit &c. as above. - An integer, optionally prefixed with 0x for hex base and suffixed with any of the standard Ethereum denominations. e.g. 69, 0x42, 10 ether or 3finney. - An executed expression which takes the form of a parenthesised list of expressions e.g. (add 1 2), with the first expression of the list being the operation and the others being operands. - All instructions of the EVM-code 1 spec are valid, though you generally shouldn't need to use the stack manipulation and jump operations. The operands should be given in order from top of the stack downwards. Any comments should begin with a ;, after which all text will be ignored until the end of the line. ## Control Flow In addition, several control flow operations are provided: - (if PRED Y N) equivalent to the evaluation of Y if PRED evaluates to a non-zero value and otherwise to the evaluation of N. - (when PRED BODY) evaluates BODY (ignoring any result) if and only if PRED evaluates to a non-zero value. - (unless PRED BODY) evaluates BODY (ignoring any result) if and only if PRED evaluates to zero. - (for INIT PRED POST BODY) evaluates INIT once (ignoring any result), then evaluates BODY and POST (discarding the result of both) as long as PRED is true. - (seq EXPR1 EXPR2 ...) evaluates all following expressions in order. Evaluates to the result of the final expression given. - (raw EXPR1 EXPR2 ...) evaluates all following expressions in order. Evaluates to the result of the first non-void expression (i.e. the first expression that leaves anything atop the stack), or void if there is none. This can be used with POP in order to customise exactly which expression's result you wish to have the block evaluate to. - There's a short form for (seq EXPR1 EXPR2 ...): { EXPR1 EXPR2 ... }. You'll generally want to enclose your programs with { ... } so you can execute more than a single expression. ## Memory & Storage Access There's a short form for accessing memory: - @ EXPR expands to (mload EXPR), used for dereferencing a location in memory. The C-language equivalent would be *EXPR. - [ EXPR1 ] EXPR2 expands to (mstore EXPR1 EXPR2), used for placing a value into a location of memory. The C-language equivalent would be *EXPR1 = EXPR2. - @@ EXPR expands to (sload EXPR), used for dereferencing a location in storage. - [[ EXPR1 ]] EXPR2 expands to (sstore EXPR1 EXPR2), used for placing a value, EXPR2, into a location in storage, EXPR1. - In the cases of both [...] and [[...]], the last closing square-bracket may optionally have a colon inserted for readability. Any otherwise undefined text strings are assumed to be variable definitions, thus a for loop to count between 0 to 9 looks something like: ~~~ (for [i]:0 (< @i 10) [i](+ @i 1) ;; do something ) ~~~ ## Arithmetic & Logical Operators A number of operators are also valid. These include the bitwise operators: ~~~ & (bitwise and) | (bitwise or) ^ (bitwise xor) ~ (bitwise not) ~~~ There are also logical operators, which have the same behaviour as in C in that they skip evaluation of later arguments if unneeded and, with the exception of logical not, may be used for any non-zero number of arguments: ~~~ && (logical and) || (logical or) ! (logical not) ~~~ The multi-ary operators +, -, *, / and % may be used, along side the binary operators <, <=, >, >=, = and !=. e.g. (&& 0 (= (+ 2 2 4) 8)) would evaluate to (i.e. leave atop the stack) 0 without evaluating the equality. ## Literals & Code When literals must be included that can be placed into memory, there is the lit operation: - (lit POS STRING) Places the string STRING in memory at POS and evaluates to its length. - (lit POS INT1 INT2 ...) Places each of the integers INT1, INT2 &c. in to memory beginning at POS and each 32-bytes apart. Evaluates to total memory used. - (lit POS BIGINT) Places BIGINT into memory at POS and evaluates to the number of bytes it takes. Unlike for the previous case, BIGINT may be arbitrarily large, and thus if specified as hex, can facilitate storing arbitrary binary data. - For handling cases where code needs to be compiled and passed around, there is the lll operation: ~~~ (lll EXPR POS MAXSIZE) (lll EXPR POS) ~~~ This places the EVM-code as compiled from EXPR into memory at position POS if and only if said EVM-code is at most MAXSIZE bytes, or if MAXSIZE is not provided. It evaluates to the number of bytes of memory written, i.e. either 0 or the number of bytes of EVM-code; if provided, this is always at most MAXSIZE. Contract creation code will typically look something like: ~~~lisp { ;; Initialisation code goes here ;; This just records who the original creator is [[0]] (caller) ;; Return the contract code (return 0 (lll { ;; Contract code goes here ;; This just suicides if called by the original creator (when (= (caller) @@0) (suicide (caller))) } 0)) } ~~~ ## Macro Expansion Macros are supported with the def operation. This works for a single non-parameterised expression as well as one with parameters. - (def NAME EXPR) From this point onwards in the code, any uses of the unquoted NAME as an expression will instead result in the code EXPR. NAME will typically be a quoted string, but if it is a symbol it must resolve through previous definitions into a string. - (def NAME (ARG1 ARG2 ...) EXPR) Similar to the above, but EXPR here may involve the symbols ARG1, ARG2, &c. which will each be substituted at the time of macro expansion. The behaviour is similar to #define in C, for example: ~~~ { (def 'sixtynine 69) (def 'sqr (z) (* z z)) (when (> (sqr sixtynine) sixtynine) (stop)) } ~~~ This results in the program stopping. Unlike in C, macros here are scoped; environmental definitions at the time of the macro's definition are recorded alongside the macro. When a definition must be resolved, definitions made within the present macro are treated preferentially, then arguments to the macro, then definitions & arguments made in the scope of which the macro was called (treated in the same order), then finally definitions & arguments stemming from the scope in which the macro was created (again, treated in the same order). This can be used to great effect for implementing counters and some functional-programming-esque idioms. ## Including Inline Assembler Low-level assembler may be included in line with one caveat; it must have transparent stack usage. This basically means that JUMP or JUMPI are best avoided; if used then ignoring their jump effects (and thus assuming the jump doesn't happen and the PC just gets incremented) must have a valid final result in terms of items deposited on the stack. Usage is: ~~~ (asm ATOM1 ATOM2 ...) ~~~ Where the ATOMs may be either valid, non-PUSH VM instructions (see the formal definition of the machine in the Yellow Paper) or literals (in which case they will result in an appropriate PUSH instruction). For example: ~~~ (asm 69 42 ADD) ~~~ Evaluates into the value 111. Note any assembler fragment that results in fewer than zero items being deposited on the stack or greater than 1 will almost certainly not interoperate with the rest of the language and thus cause compile errors. Here be (even more than usual) dragons. ## Additional helpers There are several addition helper operations to aid in program construction. These are implemented as macros (for what little it matters). ~~~ gav: My address. config: The address of the configuration contract. allgas: All gas available, when used as a parameter to send, call or msg. (sendgavcoin ): Sends GAV to account . (regname ): Registers the name name with the caller. (regcoins ): Registers the three-letter upper-case currency code with the caller. (send ): Sends Wei to the account . (send ): Sends Wei to the account with a limit on the allowed gas . (msg ): Sends a message to the account with word-sized data item , evaluates to the word-sized return value. (msg ): Sends a message to the account with word-sized data item and Wei, evaluates to the word-sized return value. (msg ): Sends a message to the account with word-sized data item and Wei and a limit of gas , evaluates to the word-sized return value. (msg ): Sends a message to the account with data of length , stored in memory at position and Wei and a limit of gas , evaluates to the word-sized return value. (create ): Creates a new contract with initialisation code . (create ): Creates a new contract with endowment Wei and initialisation code . (sha3 ): Evaluates to the sha3 of the given . (sha3pair ): Evaluates to the sha3 of the 64-bytes given by the concatenation of and . (sha3trip ): Evaluates to the sha3 of the 64-bytes given by the concatenation of , and . (return ): Returns from the call with the word-sized data . (returnlll ): Returns from the call with the code representing the LLL expression . ~~~ ## Examples See LLL Examples for PoC 5 for examples. # LLL PoC3 See LLL PoC 4 or LLL PoC 5 for the latest version of LLL. LLL is the Ethereum PoC Low-level Lisp-like Language. It is Lisp-like in syntax rather than anything deeper and is designed to make easier the task of writing a program in EVM-code 1, aka ES-1. It is automatically compiled in Ethereum PoC series including PoC-3 upwards. A contract written in LLL takes the form of a single expression. An expression may be one of: * A quoted string e.g. "Hello world!" or "This is \"Great\"". Such strings have maximum 32 characters are evaluate to the 32 byte value with the ASCII encoding of the string aligned to the left-side (i.e. in the high-order bytes should it be interpreted as a big endian integer). * An integer, optionally prefixed with 0x for hex base and suffixed with any of the standard Ethereum denominations. e.g. 69, 0x42 or 3finney. * An executed expression which takes the form of a parenthesised list of expressions e.g. (add 1 2), with the first expression of the list being the operation and the others being operands. All instructions of the EVM-code 1 spec are valid, though you generally shouldn't need to use the stack manipulation and jump operations. The operands should be given in order from top of the stack downwards. In addition, several control flow operations are provided: * (if PRED Y N) executes PRED, pops the stack and executes Y if the popped value is non-zero otherwise N. * (when PRED BODY) executes PRED, pops the stack and executes BODY if the popped value is non-zero. * (unless PRED BODY) executes PRED, pops the stack and executes BODY if the popped value is zero. * (for PRED BODY) executes PRED, pops the stack and executes BODY if the popped value is non-zero before repeating all again indefinitely. * (seq A B ...) executes all following expressions in order. And two shorthand forms for storing and loading to the permanent store: * (INT), (STRING) treats the value as an address and fetches to the top of the stack the value from storage (i.e. executes a PUSH & SLOAD) * (INT EXPR), (STRING EXPR) treats the first value as an address as above and stores the EXPR in storage at that address. It's inefficient, but you can generally use the last two as a sort of replacement for global variables. Set i to 0 with ("i" 0) and read it back again with ("i"). There's also and and or for building boolean conditions (they skip evaluation of later, unneeded arguments like in C) and may be used for any non-zero number of arguments. The multi-ary operators +, -, *, / and % may be used, along side the binary operators <, <=, >, >=, = and != and the unary operator !. e.g. (and 0 (= (+ 2 2 4) 8)) would evaluate to (i.e. leave atop the stack) 0 without evaluating the equality. You'll generally want to enclose your programs with (seq ...) so you can execute more than a single expression. Finally, any comments should begin with a ;, after which all text will be ignored until the end of the line. See LLL Examples for PoC 3 for examples and the LLL Tutorial PoC 3 for a tutorial.   # Writing a Contract in LLL We're going to use PoC-3 build of AlethZero to write a simple contract in LLL, the Low-level Lisp-like Language. We'll assume you have a decent supply of ether to pay for this contract to be created. If you don't have so much, then you should mine for a little while to build up your supply. First thing to creating a contract is to make sure the To field of the Transact pane is empty, in order that you get a grayed-out (Create Contract) in the box. We'll endow no cash into the contract, so we'll put a zero into the Value box. The Data box (i.e. of the two, the top box) contains our contract's source code; the lower box contains the assembly-language corresponding to that source code - when you alter the source code, you can immediately see the assembly changing. With the transaction otherwise set up we're ready to actually author it. For this example, we'll make a simple bank. We want people to be able to deposit ether with the contract and be able to withdraw that ether at some time later. We're going to need to execute several 'steps' in a sequence (much like in a normal programming language). To do this in LLL, we use the seq operation. In LLL, all operations take the form of an open-parenthesis (i.e. (), the operator (seq in our case), any expressions (here we've yet to decide what these are), and ended by a close-parenthesis ()). So we begin with: ~~~ (seq ) ~~~ This does nothing, of course, since we haven't described yet which steps we must execute. When an Ethereum contract executes in the Ethereum virtual machine (EVM), you get a grace period of 16 execution cycles free from fees. This is so you can check whether the transaction sender has sent you enough ether for you to bother running the contract into the fee-paying portion or not. So the first step will be to check that the transaction value, (txvalue), is greater than or equal to the minimum cost of running this contract, twenty times the base fee,(basefee). As such our program becomes: ~~~ (seq (unless (>= (txvalue) (* 20 (basefee))) (stop)) ) ~~~ Once you've typed this in you'll notice assembly code filling up the lower box. We're on our way! To make the decision over whether to stop early, we use the unless operator. This takes two operands; firstly an expression for the 'condition', and secondly an expression for the 'body', the latter to be evaluated only if the condition evaluates to zero. In this case, our body is the stop operation, which unsurprisingly, immediately halts all execution of the contract. Our condition is a >= operation, and as such only evaluates to one if the first argument to that operation ((txvalue), the value of all the ether bundled with the transaction) is greater than or equal to the second argument ((* 20 (basefee)), twenty times the basefee for transaction execution). We might read the line as "unless txvalue is greater than or equal to 20 times the basefeethen stop". Right, so we've checked that they've given us at least twenty times the basefee, so the next thing to do is to handle the deposit. Because our memory is (theoretically) 2^256 entries large (each entry able to store a 256 bits worth of data), we can just use it as a massive associative array for our addresses, storing each address's account balance at that place in memory. So, to handle deposits, we'll increment the value in our memory corresponding to the transaction sender's address by the amount that they've given us less the cost of transacting with this contract, 20 times the basefee: ~~~ (seq (unless (>= (txvalue) (* 20 (basefee))) (stop)) (sstore (txsender) (+ (sload (txsender)) (- (txvalue) (* 20 (basefee))) ) ) ) ~~~ So here we subtract our price from the transaction's value, add it to whatever was in the transaction sender's slot before ((sload (txsender)); "load the value from storage at the location given by the transaction sender's hash") and stores the resultant amount back at the same position ((sstore ...). So that works fine for depositing ether, but it's a pretty rubbish bank that doesn't let you withdraw from your account! So how can we do that? The trick is to use the transaction data (txdatan and txdata operators) to determine if there's any additional data and, if so, withdraw the amount given from the account. Of course, we'll have to check that they have at least that amount in their account first! The first thing to do is to check whether it's purely a depositing transaction or whether they want to issue a withdrawal. For this we use the txdatan to see how many data items were bundled with the transaction; if it's zero then it's just a deposit, however if there's (at least) one, then we've been issued a withdrawal, too: ~~~ (seq (unless (>= (txvalue) (* 20 (basefee))) (stop)) (if (txdatan) (seq ;; TODO: Withdraw ) ;; Else... Deposit (sstore (txsender) (+ (sload (txsender)) (- (txvalue) (* 20 (basefee))) ) ) ) ) ~~~ You'll notice we added a few comments. There is also the if operation that evaluates the second operand if the first is non-zero otherwise the third. You might put an imaginary "else" between the second and third operands. Now we have to implement the withdraw portion. To conduct a withdrawal, two things need to happen: subtract the withdrawal amount from the account balance and make a transaction to bestow the funds back to the sender. The first is easy enough: we use the txdata operator to get the first data item of the transaction ((txdata 0)) and then just sstore, sload and txsender as for depositing. The second is quite easy, too. We use the mktx operator to make a new transaction that delivers the required amount of ether ((txdata 0); the first data item to the transaction) to the sender's account ((txsender)). The final 0 of the operation is the number of data items for the transaction; none. So we end up with: ~~~ (seq (unless (>= (txvalue) (* 20 (basefee))) (stop)) (if (txdatan) ;; At least one data item... Withdraw (seq (sstore (txsender) (- (sload (txsender)) (txdata 0))) (mktx (txsender) (txdata 0) 0) ) ;; Else... Deposit (sstore (txsender) (+ (sload (txsender)) (- (txvalue) (* 20 (basefee))) ) ) ) ) ~~~ # Checks and Balances So this is a minimal working implementation, however there are several things missing. First and foremost is that the mktx operation we execute for withdrawal costs us 100 times the basefee, yes when a withdrawal takes place, they can pay only the minimal 20 times; we should really bump up our price accordingly. To do this, we'll add an additional condition for withdrawal - that you must pay at least 135 times the basefee (using the boolean and operator). So (if (txdatan) ... becomes (if (and (txdatan) (>= (txvalue) (* 135 (basefee))) ) ... So now they must pay the extra amount for withdrawing funds, however, nothing (aside from the contract running out of funds) is stopping them from withdrawing more than they have. We'll make a third check that ensures they have at least the amount they wish to withdraw in their account with us. We just use the txdata and sload operators to determine this, so our final condition for withdrawal becomes: ~~~ (if (and (txdatan) (>= (txvalue) (* 135 (basefee))) (>= (sload (txsender)) (txdata 0)) ) ... ~~~ # Low Sender The final issue is that we're also storing the program in the lower portion of the memory (approximately the first 256 entries) and we don't want to stomp on our program in the unlikely event of someone with an address evaluating to a number less than 256 depositing cash with us. We'll take the quickest way out and deny "low-addresses", by putting in an extra condition at the very start, so the first unless now becomes: ~~~ (unless (and (> (txsender) 0xff) (>= (txvalue) (* 20 (basefee)))) (stop)) ~~~ Leaving us with the final contract of: ~~~ (seq ;; Stop unless there is at least the fee and the sender has a valid account.    (unless (and (> (txsender) 0xff)(>= (txvalue)(* 20 (basefee))))(stop) ) ;; Check to see if there's at least one argument(i.e. is a withdrawal) and ;; if the appropriate fees have been paid for withdrawal. (if (and (txdatan) (>= (txvalue) (* 135 (basefee))) (>= (sload (txsender)) (txdata 0)) ) ;; At least one data item... Withdraw (seq ;; Subtract the value from the balance of the account (sstore (txsender) (- (sload (txsender)) (txdata 0))) (mktx (txsender) (txdata 0) 0) ) ;; Else... Deposit (sstore (txsender) (+ (sload (txsender)) (- (txvalue) (* 20 (basefee)))) ) ) ) ~~~ # Creating and Using Now, once this LLL code is in the Data of the transaction, it's time to actually create the contract on the block chain. To do this we simply "send" the transaction. You'll see your contract-creation transaction become pending (see the Pending Transactions pane), and after a little time, assuming there are miners on the network, you'll see it become written into the block chain (see the Block Chain pane). Once in the block chain, you can interact with it! We'll make two deposit transactions and a withdrawal. You'll need to know the account address of your new contract. You'll see in initially the pending and then the block chain pane an item corresponding to your transaction; it will have one of your addresses (see Owned Addresses pane; and remember it for later) followed by a "+>", denoting a contract creation transaction and then the new contract's address. Once your contract has been created, look down the list in All Contracts until you find that same address of your new contract and double-click the item, which copies the full address into the clipboard. For the first deposit, we'll deposit one ether (minus banking charges) with our newly created bank. Paste the freshly copied contract address into the To box and make sure the Amount reads "1 ether". Clear the Data box as we're not making a withdrawal, then click Send and wait for it to move onto the block chain from the Pending Transactions (or just press the Preview button on the toolbar if you can't wait). Let's check that it went through alright; find your contract address in the All Contractspane and this time select it to inspect its storage. In here you'll see a big dump of EVM code which corresponds to the program we just authored and then one additional entry; this should be your address and it should denote that some considerable sum (a hex number next to your address, denominated in wei, and so it'll be quite big) is deposited for you. Let's check that it goes up when we make a second deposit. We'll make the deposit by clicking Send again and wait for the transaction to be mined onto the block chain (or ensure Preview is depressed). Selecting the contract again from the All Contracts pane to inspect it should show you a higher amount next to your address in its state than before. If it doesn't something has gone wrong with your contract and you should check its code. Finally, we'll make a withdrawal of fund back to our account. Let's assume we want to take one ether back. All we must do is send a transaction to the contract making sure to pay the charge of 135 times the basefee (100 szabo), and specify the amount to withdraw as a single item in the Data. So change the Amount so it reads "13500 szabo" and write "1ether" (note that there's no space between the "1" and the "ether" - that's important) and click Send. Once that transaction goes through on to the block chain (or if you havePreview down, then immediately) then you should see your account go up by an ether. If you then check your account with the bank (looking at the contract's state in All Contracts, you'll see that the amount associated with your address is reduced to just shy of an ether's worth of wei. And so you've just learnt to write, create and interact with a contract in Ethereum PoC. While pointless, the code we've made can be used a base for all sorts of contracts - currencies, lending banks, interest-giving banks, even reputation systems. Your imagination is the limit.   # Bank A very simple bank. You can send ether in a transaction to load your account with funds along with 20 * basefee. Withdraw your ether by specifying a data argument with the amount to withdraw (and attach an extra 125 * basefee). Withdraw to a different account by specifying a second data argument with the account. ~~~ (seq ;; Stop unless there is at least the fee and the sender has a valid account. (unless (and (> (txsender) 0xff) (>= (txvalue) (* 20 (basefee)))) (stop)) ;; Check to see if there's at least one argument(i.e. is a withdrawal) and ;; if the appropriate fees have been paid for withdrawal. (if (and (txdatan) (>= (txvalue) (* 125 (basefee))) (>= (sload (txsender)) (txdata 0)) ) ;; Withdrawal: (seq ;; Subtract the value from the balance of the account (sstore (txsender) (- (sload (txsender)) (txdata 0))) ;; Transfer the funds either to... (if (= (txdatan) 1) (mktx (txsender) (txdata 0) 0) ; ...the sender... (mktx (txdata 1) (txdata 0) 0) ; ...or the supplied account. ) ) ;; Deposit; just increase the account balance by that amount. (sstore (txsender) (+ (sload (txsender)) (- (txvalue) (* 20 (basefee)))) ) ) ) ) ~~~ Name Registrar A simple name registeration service. Names (represented as 32-byte values whose left-most bytes store an ASCII string) are stored in the address space of the contract leading to the (160-bit ethereum) address of the name's owner. The owner addresses are similarly stored in the contract's address space with a value leading back to the name. ~~~ (seq ;; Stop if the value is less than 150 * basefee. (unless(and (> (txvalue) (* 150 (basefee))) (> (txsender) 0xff) )(stop)) ;; If there's at least one argument (if (txdatan) (seq ;; Stop if the first arg (name) is in program space ;; or if it's already been registered. (unless (and (> (txdata 0) 0xff) (! (sload (txdata 0))) ) (stop)) ;; If there's only one arg (if (= (txdatan) 1) (seq ;; Store sender at name, and name at sender. (sstore (txdata 0) (txsender)) (sstore (txsender) (txdata 0)) ) ;; No - the second arg (address) exists. (seq ;; Stop if the address is in program space ;; or if it's already registered. (unless (and (> (txdata 1) 0xff) (! (sload (txdata 1))) ) (stop)) ;; Store the address at the name and the name at the address. (sstore (txdata 0) (txdata 1)) (sstore (txdata 1) (txdata 0)) ) ) ) ;; No arguments - either deregister or suicide ;; (if it's from owner's address). (seq ;; Suicide if it's from owner's address. (when (= (txsender) 0x8a40bfaa73256b60764c1bf40675a99083efb075) (suicide 0x8a40bfaa73256b60764c1bf40675a99083efb075) ) ;; Otherwise, just deregister any name sender has, if they are registered. (when (sload (txsender)) (seq (sstore (sload (txsender)) 0) (sstore (txsender) 0) )) ) ) ) ~~~ # Splitter Simple cash splitter - removes 150 * baseFee for each item plus one, then splits the rest amongst each of the addresses given as data items. ~~~ (seq ;; Stop if the transaction is worth less than the usage fee ;; (150 * (txdatan + 1) * basefee). (unless (> (txvalue) (* 150 (basefee) (+ 1 (txdatan)))) (stop)) ;; Set 'value' as the value to be paid out to each address. ("value"(/ (- (txvalue) (* 150 (basefee) (+ 1 (txdatan)))) (txdatan) )) ;; Cycle through each address with 'i' being the index. ("i" 0) ; Initialise 'i'. (for (< ("i") (txdatan)) ; Exit once 'i' hits the argument count. (seq ;; Send to 'i'th argument (assuming it's an address). (mktx (txdata ("i")) ("value") 0) ("i" (+ ("i") 1)) ; Increment 'i'. ) ) ) ~~~