Hello again! Welcome back to another Stylus Saturdays post!
I’m pleased to announce we’ve been graciously funded by the Arbitrum grants committee! I’m super thankful to everyone advocated for this little newsletter, we love building on Stylus, and with this support we’re able to do the following:
Launch three major dApps
With this support, we will develop three major dApps: A fully on-chain orderbook with hooks, a gas golfing competition for an on-chain optimisation problem, and a fully on-chain NFT platform (again, with hooks)!
These three projects will be fully audited and MIT licensed, ready for any would-be developers to jump in and fork! The emphasis will be on extensibility to bootstrap projects developing with Stylus. We intend to actively support the Stylus developer experience with these projects, and having the audit component sorted will mean it’s possible for others to safely enjoy the benefits!
The gas competition will be run (mostly) on-chain, with a cash prize for finding the most efficient Stylus solution for an optimisation problem! Stay tuned for more.
Kickstart the Stylus Developers DAO
I have created an unofficial Stylus developers community hosted on Discord, with an eye for hands-on advice specific to individual projects based on mine and my team’s experience. Ask questions about anything, and hopefully get some personalised advice to your codebase! This will also be a vehicle to organise community events about alternative programming on Arbitrum.
Release a ton of posts
Thanks to Arbitrum’s generous support, I will be releasing posts on a regular cadence and of a broader range than was possible previously!
CALL FOR STYLUS BUILDERS
Anyone, anywhere, if you’re building on Stylus, I want to know! Please drop me a line at @doggish on Telegram. I’d love to hear more about what you’re up to, and maybe feature you here! I’ll be reaching out to projects I’m aware of this week to get you included.
Using Go with Arbitrum Stylus
Stylus = WASM. This means you can use most programming languages that generate WASM (though there’s no support for WASI so be mindful)! This includes Go with Tinygo as the backend.
In a simple Go contract, we’ll define a simple counter app that lets you add and subtract from a storage variable:
package main
//go:generate stylus-go
import (
"math/big"
"github.com/af-afk/stylus"
)
//stylus entrypoint
type Storage struct {
Counter stylus.StorageUint256
}
//stylus uint256
func (s Storage) Add(x *big.Int) ([]byte, error) {
y := s.Counter.Get()
y.Add(y, x)
s.Counter.Set(y)
var b [32]byte
x.FillBytes(b[:])
return b[:], nil
}
func (s Storage) Sub(x *big.Int) ([]byte, error) {
y := s.Counter.Get()
y.Sub(y, x)
s.Counter.Set(y)
var b [32]byte
x.FillBytes(b[:])
return b[:], nil
}
Reflection is difficult in the Tinygo environment, so we’ll be using code generation to generate the entrypoint/code for this to make it actually work. This is similar to Rust functionally speaking, which makes things comparatively similar and simple.
The repo where this lives is at https://github.com/af-afk/stylus. The devex needs some work, though I expect with time, this should become easier!
How to implement this yourself: writing some primitives
First, to reproduce the SDK and code generator I’ve mentioned here, we need to implement some basic primitives to do things like convert byte arrays to *big.Int and storage accesses. For all of the code below, we have some functions for hostio access using Tinygo stubs.
We implement an internal uint256 number and conversion methods:
Which we later use to implement some storage:
A simple approach to storage in this case is to have an internal counter and to simply bump it every time someone asks for a storage pointer at runtime. This is the approach implemented above.
Lastly, we implement a pair of conversion functions:
These will come in handy as we implement our code generator.
Implementing the code generator
Second, we need to implement some code that’s able to parse a pattern similar to Rust. *big.Int is a good experience (much to my researcher’s despair when he reads Go) in my opinion, so for a simple counter app, we should allow users to optionally declare the type of the arguments for their functions using a comment. For this example, we’re simply supporting uint256 and int256, as addresses could be implemented trivially at a later time.
We’re forcing the user of this Go code to return a byte slice and error, with the former being sent directly as returndata, and the latter tested to be non-nil to know whether to return an erroneous contract state (triggering a revert by the runtime). This is obviously not great devex, but it should be trivial to implement another feature later that also converts the return type in the code generator the same way we do for arguments.
Writing a code generator a la gqlgen (which we use extensively at Fluidity Labs for our backend code) is as simple as reading a number of Go files in a directory, then parsing the Go. We can do so beginning with the following:
The fully implemented form of all of the code here lives at https://github.com/af-afk/stylus/blob/trunk/cmd/stylus-go/main.go#L45-L69.
This code gives us a map with the AST for the various Go files in the current directory. Our goal is to find a struct that’s been marked as the entrypoint for the contract, then to start to read its associated functions, to check what the preferred types are, then to spit out a symbol exported entrypoint function.
The above code does some work to find a comment that’s like:
//stylus entrypoint
This clues the code in for its first pass as to which functions we’re searching for. Some more work is done to confirm that the type is, in fact, a struct, but we’re going to omit this for now for brevity reasons.
After some boring work involving confirming the struct, we’re going to emit our first bit of generated code:
This uses this function to generate code like this:
In practice resulting with the following:
For each storage unit in the struct, we bump an internal counter, and we generate code that knows how to read from the storage slot at that position.
After confirming that we’ve found the struct, we’re going to search for each function in each file with a second pass:
We’re going to confirm the following:
This is titled (so as to be exported under normal circumstances by Go).
This might have a hint as to what the types should be. Hinting the types with a comment like
//stylus uint256
empowers the developer to specify specifically what the type should be for the functions.That this function is associated with the struct from before that’s been marked as the entrypoint. If this isn’t the case, we need to ignore this function.
After confirming these things about the function, we’re going to begin to spit out templated code with the meat of our entrypoint.
The code explainArgs does some unpacking to determine the internal and external-facing types for users:
The type guessing takes place with the //stylus
comment. There are some functions below to convert the AST human readable name to the external type, which we’ll skip for now.
The templating code spits out functions to follow the first print that takes place in the structure checking code:
This results in code like this:
Much like the generated code for the Rust SDK I imagine. This code can then be compiled with Tinygo (see here) for a good Tinygo build profile and stripping with wasm-opt
. For our simple example this all results in 13 kilobytes.
I will be developing this SDK to improve its user experience in the coming few months, hopefully eventually being possible to be used at scale by individual users alongside my regular (dApp-focused) programming!
The repo lives here: https://github.com/af-afk/stylus, including the TODO list. The flexibility of WASM means we can develop smart contracts in most programming languages!
Interview with Howard from Lit Protocol
I had the opportunity to interview Howard from Lit Protocol. We’ve previously covered them here, and I’m super excited to discuss their product again! Lit Protocol is building a decentralised signing network.
Could you tell us some more about your background before web3?
I got into web3 pretty early on in my career, pretty much right after college. After obtaining my Master of Engineering at University of Oxford, I joined a startup foundry in Vancouver called Axiom Zen as a Frontend Engineer Intern. Around six months after joining, CryptoKitties, the project that was created by the same folks who were sitting by my side in the office, was really taking off, and I seized the opportunity to work on that project. That project changed my life.
I transitioned into backend engineering on the job, learning Golang, NodeJS, Solidity, which culminated to my first major achievement on that team - I wrote the set of contracts for the Offers feature and at one point in time it had held more than $3.5MM USD in escrow on chain. It was truly some wild stuff. Then, I went on to lead a team of 7 engineers to develop the Dapper Wallet and Payment Rails for the NBA TopShot project with Dapper Labs.
Have you worked in other ecosystems aside from the EVM?
Yes, I worked in the Flow blockchain ecosystem back when I was at Dapper Labs.
How did you learn about Stylus?
At Lit Protocol, we need a base chain that is cheap and fast for processing transactions, as well as one that would help us move fast as a team. We use the chain as a public consensus and data layer allows us to keep the Lit nodes stateless except for key material, which is a significantly simpler architecture than otherwise. Alternatives are not impossible but rather long winded.
A long time ago, we were on Celo. Then, we moved to the OP stack because, at the time, they had decent devX for building in the necessary precompiles we needed for our PKPs. At some point, we had reached the limits of the OP rollup and needed an even better solution, so we started to look into Arbitrum.
While OP has 2s block times, Arbitrum has almost instantaneous confirmations - this was great first signs. We also noticed that Arbitrum was even cheaper than on OP stack, another great sign. When we were using OP stack, every time we needed to add or change the precompile code, we needed to coordinate with Caldera - our OP rollup provider - for shipping the build and upgrading the chain, which was a very lengthy process especially since it involves communicating across teams and timezones.
This was also a rather involved process when developing in non-production environments, too. Every precompile code change resulted in needing to update a fork of Anvil (Foundry) before we can run our CI tests. With Stylus, we can simply deploy the contract after changing the Rust code, and we no longer need to maintain a fork of Foundry for this particular purpose. Lit dev team work in Rust, and OP stack is built upon geth and the larger Golang ecosystem - at the time, we had to spend costly cycles switching contexts from Rust to Golang to write our precompiles
With Stylus, we can just write in Rust like we normally do, and deploy that as a smart contract using the security benefits of Rust, as opposed to Golang.
What were the opportunities for Lit Protocol with Stylus?
Lit uses smart contracts to manage programmable keys (PKPs) and their permissions. Each Lit PKP has a unique public key in order to use them, and this public key is derived using Rust code that is deployed as a Stylus contract - this allows us to model the necessary logic as simply contract-contract interactions.
What were the joys of working with Stylus?
These were the benefits that I had experienced working with Stylus:
1. Interoperability between Solidity and Stylus smart contracts meant we can write simpler code.
2. Cheaper and faster execution of the WASM VM compared to the EVM (geth).
3. Good documentation and hands-on support resulting in good devX! Easy to run a dev node locally for POC and testing.
It was also easy to get started with a basic POC, there was a good amount of examples to reference, and I got as far as setting up a local Nitro testnode where I could deploy and interact with my Stylus contracts, which was sufficient to complete the development cycle.
Being able to write in Rust opens up the opportunity for us to write all kinds of precompiles in the future, and the guides for most basic operations are pretty accessible.
How do you see the Stylus ecosystem evolving?
Generally speaking, Stylus as a product will be great for helping convert more developers to develop on rollups, and for the people who prefer to work with the safety of the Rust language itself, this tool offers that option for them to deploy a smart contract without needing to learn the intricacies of Solidity.
I expect to see all verticals to grow within the Stylus ecosystem because it just makes it that much easier to work with smart contracts across the board now.
How can we get in touch with you?
LinkedIn: https://www.linkedin.com/in/itshowardtam/
X: https://x.com/itshwrdtm
Lit Protocol Discord: https://discord.com/invite/zBw5hDZve8
This post is powered by… Arbitrum! With a grant from the Fund the Stylus Sprint program. You can learn more about Arbitrum grants here: https://arbitrum.foundation/grants
Join the Stylus Devs DAO: https://discord.gg/eTRt3r3F
Follow me on X: @baygeeth
Side note: I develop Superposition, a defi-first chain that pays you to use it. You can check more out at https://superposition.so