LLL Compiler

Note

This documentation is a work in progress, so please exercise appropriate caution. It a personal effort and has no formal connection with the Ethereum Foundation.

Installing the compiler

1. Download the latest Retesteth version https://ethereum-tests.readthedocs.io/en/latest/retesteth-tutorial.html#using-the-latest-version

  1. After building the docker image, download the raw erc20.lll file and compile the smart contract with lllc by running
cd
cd ~/retestethBuild
wget https://raw.githubusercontent.com/benjaminion/LLL_erc20/18cfbd32a08ec525668ee57533779903e2717b12/erc20.lll
sudo ./dretesteth.sh lllc --testpath /home/marcuswentz/retestethBuild/ erc20.lll

you should get the generated bytecode in the terminal

341561000b5760006000fd5b606433556103758061001f6000396000f300fe341561000b5760006000fd5b600060005260046000601c600001376306fdde036000511415610047576020600052601f80610356604039602052601f19605f60205101166000f35b6395d89b416000511415610074576020600052600380610353604039602052601f19605f60205101166000f35b63313ce567600051141561008d57600060005260206000f35b6318160ddd60005114156100a657606460005260206000f35b6370a0823160005114156100c1576004355460005260206000f35b63a9059cbb600051141561017257366044146100dd5760006000fd5b60a060020a60043504156100f15760006000fd5b606460243511156101025760006000fd5b6024351561016757335460205260205160243511156101215760006000fd5b602435602051033355602435600435540160043555602435602052600435337fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef60206020a35b600160005260206000f35b6323b872dd6000511415610272573660641461018e5760006000fd5b60a060020a60043504156101a25760006000fd5b60a060020a60243504156101b65760006000fd5b606460443511156101c75760006000fd5b60443515610267576004355460205233600052602060002060043501546040526001602051604435116101fd5750604051604435115b156102085760006000fd5b60443560205103600435556044356024355401602435556044356040510333600052602060002060043501556044356020526024356004357fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef60206020a35b600160005260206000f35b63095ea7b36000511415610324573660441461028e5760006000fd5b60a060020a60043504156102a25760006000fd5b606460243511156102b35760006000fd5b6000602435156102cd575060043560005260206000203301545b156102d85760006000fd5b6024356004356000526020600020330155602435602052600435337f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b92560206020a3600160005260206000f35b63dd62ed3e600051141561034b576024356000526020600020600435015460005260206000f35b60006000fd00fe4c4c4c4c4c4c20436f696e202d206c6f766520746f20636f646520696e204c4c4c2e
  1. Deploy bytecode as transaction data using ethers.js

Note

Add the “0x” prefix to the bytecode as shown in the deploy script below.

const ethers = require("ethers");
const rpcURL = process.env.sepoliaInfuraWSS // Your RPC URL goes here
const provider = new ethers.providers.WebSocketProvider(rpcURL)
const signer = new ethers.Wallet(Buffer.from(process.env.devTestnetPrivateKey, 'hex'), provider);

deploy_lll_contract()

async function deploy_lll_contract(){

    const contractBytecode = "0x" + "341561000b5760006000fd5b606433556103758061001f6000396000f300fe341561000b5760006000fd5b600060005260046000601c600001376306fdde036000511415610047576020600052601f80610356604039602052601f19605f60205101166000f35b6395d89b416000511415610074576020600052600380610353604039602052601f19605f60205101166000f35b63313ce567600051141561008d57600060005260206000f35b6318160ddd60005114156100a657606460005260206000f35b6370a0823160005114156100c1576004355460005260206000f35b63a9059cbb600051141561017257366044146100dd5760006000fd5b60a060020a60043504156100f15760006000fd5b606460243511156101025760006000fd5b6024351561016757335460205260205160243511156101215760006000fd5b602435602051033355602435600435540160043555602435602052600435337fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef60206020a35b600160005260206000f35b6323b872dd6000511415610272573660641461018e5760006000fd5b60a060020a60043504156101a25760006000fd5b60a060020a60243504156101b65760006000fd5b606460443511156101c75760006000fd5b60443515610267576004355460205233600052602060002060043501546040526001602051604435116101fd5750604051604435115b156102085760006000fd5b60443560205103600435556044356024355401602435556044356040510333600052602060002060043501556044356020526024356004357fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef60206020a35b600160005260206000f35b63095ea7b36000511415610324573660441461028e5760006000fd5b60a060020a60043504156102a25760006000fd5b606460243511156102b35760006000fd5b6000602435156102cd575060043560005260206000203301545b156102d85760006000fd5b6024356004356000526020600020330155602435602052600435337f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b92560206020a3600160005260206000f35b63dd62ed3e600051141561034b576024356000526020600020600435015460005260206000f35b60006000fd00fe4c4c4c4c4c4c20436f696e202d206c6f766520746f20636f646520696e204c4c4c2e";
    const txData = contractBytecode; //No constructor arguments to keep bytecode simple.

    tx = await signer.sendTransaction({
        data: txData
    });

    console.log(tx)
}

erc20.lll deployed to Sepolia testnet successfully: https://sepolia.etherscan.io/tx/0xe28b28d289dff7cc656d137d427699ca227f2283a0d0c3ce3b0285447ef443ef

  1. Import deployed contract address into Metamask and transfer tokens

erc20.lll tokens on Sepolia testnet transferred successfully: https://sepolia.etherscan.io/tx/0x6d635d65f0b046afba8cb582b12824845a77c372288a05a1433829c04a005ae8

  1. Test transferFrom after you approve tokens to bulk transfer contract

erc20.lll approve tokens on Sepolia testnet successful: https://sepolia.etherscan.io/tx/0xc0508d0c543e68783a8ba8c063f93110a62dc0a019ad8d470abc2d9975f994aa

erc20.lll bulk transfer calling transferFrom on Sepolia testnet successful: https://sepolia.etherscan.io/tx/0xb8658cceba599c4984184d9116fee96ce7fdd7cd4f76ff677ca229e2025097d5

Note

https://github.com/ethereum/solidity.git does not support LLL anymore but https://github.com/winsvega/lllc does for Retesteth EL (Execution Layer) testing.

Compiler Options

The lllc compiler options are reasonably self-explanatory and are as follows.

When multiple options are used the following rules apply.

  • -h and -V take precendence over all others, and the first one of these listed is executed.
  • -a, -b, -t, -x, -d are mutually exclusive output formats. The last of these listed defines the output format created.
  • -o can be used with any of the output formats.

-h,--help

Displays the following compiler options.

-b,--binary  Parse, compile and assemble; output byte code in binary.
-x,--hex  Parse, compile and assemble; output byte code in hex.
-a,--assembly  Only parse and compile; show assembly.
-t,--parse-tree  Only parse; show parse tree.
-o,--optimise  Turn on/off the optimiser; off by default.
-h,--help  Show this help message and exit.
-V,--version  Show the version and exit.

-x,--hex

Parse, compile and assemble; output byte code in hex.

This is the default.

> echo '(add 2 3)' | lllc
6003600201

> echo '(add 2 3)' | lllc --hex
6003600201

-a,--assembly

Only parse and compile; show assembly.

Outputs the intermediate assembly language which is shared with Solidity and compiled into the final bytecode. If the -o flag is used as well then the assembly language is displayed after optimisation.

> lllc -a erc20.lll
  jumpi(tag_42, iszero(callvalue))
   0x0
  dup1
  revert
tag_42:
  sstore(caller, 0x186a0)
  ...

-t,--parse-tree

Only parse; show parse tree.

The “parse tree” is the clean version of the source code which is fed to the LLL parser: all comments and linebreaks are removed, whitespace is normalised, numbers are all converted to decimal and quoted strings standardised.

> echo "(def 'foo (mload 0x0a)) ; define foo" | lllc -t
( def "foo" ( mload 10 ) )

-d,--disassemble

The -d option is not documented in the --help output. It decompiles hexadecimal EVM code into readable opcodes.

> echo 602a600055 | lllc -d
PUSH1 0x2A PUSH1 0x0 SSTORE

-b,--binary

Parse, compile and assemble; output byte code in binary.

I haven’t found a use for this yet.

-o,--optimise

Turn on/off the optimiser; off by default.

The optimiser passes the assembly output through Solidity’s optimiser. The main useful thing the optimiser can do is the replacement of constant expressions, but it doesn’t always manage to spot all opportunities for this.

> echo '(add 1 (mul 2 (add 3 4)))' | lllc
6004600301600202600101

> echo '(add 1 (mul 2 (add 3 4)))' | lllc -o
600f

-V,--version

Show the version and exit. Note that the short form is a capital V.

> lllc -V
LLLC, the Lovely Little Language Compiler
Version: 0.4.12-develop.2017.6.27+commit.b83f77e0.Linux.g++