Yul+

A low-level, highly efficient extension to Yul, an intermediate smart-contract language for the Ethereum Virtual Machine.
Try it Now!
Features
- All existing Yul features
- Memory structures (
mstruct
)
- Enums (
enum
)
- Constants (
const
)
- Ethereum standard ABI signature/topic generation (
sig"function ..."
, topic"event ...
)
- Booleans (
true
, false
)
- Safe math (over/under flow protection for addition, subtraction, multiplication)
- Injected methods (
mslice
and require
)
Coming Soon
- Static typing
- CLI support
Install
npm install -g yulp
Library Usage
const yulp = require('../index');
const source = yulp.compile(`
object "SimpleStore" {
code {
datacopy(0, dataoffset("Runtime"), datasize("Runtime"))
return(0, datasize("Runtime"))
}
object "Runtime" {
code {
calldatacopy(0, 0, 36) // write calldata to memory
mstruct StoreCalldata( // Custom addressable calldata structure
sig: 4,
val: 32
)
switch StoreCalldata.sig(0) // select signature from memory (at position 0)
case sig"function store(uint256 val)" { // new signature method
sstore(0, StoreCalldata.val(0)) // sstore calldata value
log2(0, 0, topic"event Store(uint256 value)", StoreCalldata.val(0))
}
case sig"function get() returns (uint256)" {
mstore(100, sload(0))
return (100, 32)
}
}
}
}
`);
console.log(yulp.print(source.results));
Enums
Here we have a fully featured enum
identifier which acts as a constant.
object "contract" {
code {
enum Colors (
Red,
Blue,
Green
)
log1(0, 0, Colors.Green)
}
}
Constants
const
will define a let
variable value that cannot be re-assigned.
object "contract" {
code {
const firstVar := 0xaa
const someOther := 1
}
}
Memory Slice
mslice(position, length)
will return a 1-32 byte value from memory.
object "contract" {
code {
mstore(30, 0xaaaa)
log1(0, 0, mslice(30, 2))
}
}
Booleans
true
and false
are added and equate to values 0x01
and 0x00
.
object "contract" {
code {
mstore(30, true)
log1(0, 0, mload(30))
}
}
Multi mstore sugar
mstore
can now be used as a proxy method
object "contract" {
code {
mstore(30, 1, 2, 3, 4)
}
}
Comparison Methods
lte
, gte
, neq
can now be used.
if and(lte(1, 10), gte(5, 2)) {
let k := neq(0, 1)
}
MAX_UINT
MAX_UINT
literal is now available (i.e. uint(-1)
)
if lt(v, MAX_UINT) {
let k := 1
}
Ethereum Standard ABI Signature and Topic Generation
sig" [ method abi ] "
will equate to a 4 byte method signature hex value
topic" [ event abi definition ] "
will equate to the 32 byte topic hash
object "contract" {
code {
const storeSig := sig"function store(uint256 val)"
const eventSig := topic"event Store (uint256 indexed val)"
log1(0, 0, storeSig)
log1(0, 0, eventSig)
}
}
Memory Structures
Memory structures enable better handling of pre-existing in-memory structures.
mstruct Identifier ( [ property, ... ] )
A structure property is defined by an identifier and length specifier (i.e. blockProducer:32
) where the identifier is blockProducer
and the length is 32 bytes
.
In-memory array like structures are defined using a name.length
property, followed by a name:[array item size]
property.
Note, mstruct
properties allow for data chunk sizes up to 32 bytes only.
Inheritance
object "Utils" {
code {
const Size := 1
function someMethod() -> {}
}
}
object "SimpleStore" is "Utils" {
code {
mstore(0, Size)
someMethod()
}
}
Imports
We now support basic file system usage, we don't support local path resolution just yet.
The fs
object is simply supplied at compile
: yulp.compile(source, fs)
.
import "./Utils.yulp"
object "SimpleStore" is "Utils" {
...
}
Error Reporting
A new experimental (post v0.0.7) feature is the error"some message"
literal.
This simply utf8 encodes the message to bytes, keccak256 hashes it and returns the first 4 bytes as an error identifier.
The compiler will return an errors
property ({ [4 byte idenfitier]: [error message], ... }
).
object "contract" {
code {
mstore(400, 0xaa)
mstore(432, 0xbb)
mstore(464, 0xcc)
mstore(496, 0x03)
mstore(554, 0xaaaabbbbcccc)
mstore(560, 0xdd)
mstore(592, 0x01)
mstore(652, 0xffffffff)
mstruct BlockHeader (
blockProducer: 32,
previousBlockHash: 32,
blockHeight: 32,
anotherArray.length: 32,
anotherArray: [2],
ethereumBlockNumber: 32,
roots.length: 32,
roots: [4]
)
BlockHeader.blockProducer(400)
BlockHeader.blockProducer.size()
BlockHeader.blockHeight(400)
BlockHeader.blockHeight.offset(400)
BlockHeader.blockHeight.position(400)
BlockHeader.blockHeight.size()
BlockHeader.blockHeight.index()
BlockHeader.blockHeight.keccak256(500)
BlockHeader.anotherArray(400, 2)
BlockHeader.size(400)
BlockHeader.offset(400)
BlockHeader.keccak256(400)
}
}
Help out
There is always a lot of work to do, and will have many rules to maintain. So please help out in any way that you can:
- Create, enhance, and debug fuel-core rules (see our guide to "Working on rules").
- Improve documentation.
- Chime in on any open issue or pull request.
- Open new issues about your ideas for making
yulp
better, and pull requests to show us how your idea works.
- Add new tests to absolutely anything.
- Create or contribute to ecosystem tools.
- Spread the word!
We communicate via issues and pull requests.
Important documents
Donate
Please consider donating if you think Yul+ is helpful to you or that my work is valuable. We are happy if you can help us buy a cup of coffee. ❤️
Or just send us some Dai, USDC or Ether:
0x3e947a271a37Ae7B59921c57be0a3246Ee0d887C Etherscan
Coming Soon
mstruct BasicRecursiveStructures (
block: BlockHeader,
root: RootHeader,
proof: MerkleProof,
leaf: TransactionLeaf,
token: 32
)
mstruct SwitchStatements (
data.switch: 1,
data: [InputUTXO,InputHTLC,TransactionDeposit]
)
mstruct FixedLengthArrays (
someArr: (7)[32],
)