Meplang - An EVM low-level language
Meplang is a low-level programming language that produces EVM bytecode. It is designed for developers who need full control over the control flow in their smart contracts.
Meplang is a low-level language and is not meant for complex smart contract development. It is recommended to use more high-level languages like Solidity or Yul for that.
Please note that the work on Meplang is still in progress, and users should always verify that the output bytecode is as expected before deployment.
Installation
-
Install Rust on your machine.
-
Run the following to build the Meplang compiler from source:
To update from source, run the same command again.
Hello World
Here is an example of a simple Meplang contract, that returns "Hello World!" as bytes:
contract HelloWorld
To compile this contract saved as hello_world.mep, run the following command:
Or the shortened version:
This will print the runtime bytecode in the terminal. To export the compilation artifacts (including the runtime bytecode), use the argument -o or -output:
Deployment bytecode
The compilation gives the runtime bytecode of the smart contract. To get the deployment contract, use an auxiliary contract, and compile it:
contract Constructor
// the contract that will be deployed
contract Deployed
Compile the contract Constructor to get the deployment bytecode of the contract Deployed.
Basic syntax
- A contract is declared with the keyword
contract. Many contracts can be defined in a single file. A contract can copy the runtime bytecode of another contract using&Contract.codeinside a block. - A block is declared inside a contract using the keyword
block. A block can be defined abstract (see later) using the keywordabstractbeforeblock. The first opcodes of the contract are from the necessary block namedmain(or a block surrounded by the attribute#[main]). - A constant is declared inside a contract using the keyword
const. Constants can only be used inside a functionpushinside a block.
contract BalanceGetter
- Inside a block, any opcode can be used except PUSH1 to PUSH32 opcodes (PUSH0 is allowed). Raw bytecode can also be used as is. A value can be pushed using the function
push, which can take an hexadecimal literal, a constant, a non-abstract block PC or size as an argument. Only values inside apushfunction will be optimized by the compiler.
contract Contract
- A non-abstract block can be copied at most once inside another block using the operator
*. An abstract block can be copied as many times as desired inside other blocks using the operator&. Therefore, we cannot refer to thepcor to thesizeof an abstract block, because it may appear multiple times in the bytecode, and not be compiled the same every time.
contract Contract
- Many attributes exist to guide the compiler. They are declared over a contract, a block, or a line inside a block using the syntax
#[ATTRIBUTE]. The current list of existing attributes is:assumeto tell the compiler that from this point, an opcode will push on the stack a defined value. The compiler can then replace somepushopcodes with these assumptions.clear_assumeto clear an assumption made previously.mainthe main block can be marked with this attribute if it is not namedmain.lastto tell the compiler that the block must be placed at the end of the bytecode.keepto tell the compiler that this block must be kept somewhere in the bytecode even if it is unused.
More examples of contracts can be found in the folder examples.
Future features
assertattribute to impose conditions on a block pc or a contract size.- Heuristics to improve compilation optimizations.
- Inheritance of contracts.