Published on

# Embracing Optimism

0 views

L2s are the future of Ethereum - and it's time to embrace them.

I have been working on a Conviction Voting dApp that requires users to stake tokens on Ethereum mainnet. I was giving a demo to Kevin Owocki, founder of Gitcoin, when the question came up: how much gas does this cost?

It was surprisingly expensive.

Take a look at this struct:

struct Vote {
bool released;
uint152 amount;
uint48 grantId;
uint56 voteId;
uint48 lockedSince;
uint48 lockedUntil;
}


It contains the minimum amount of information needed to track votes on-chain over time. Keeping in mind that staking on multiple proposals (grants in this case) requires a single transaction, since the call data is passed as an array, the gas cost required to stake a few tokens on 4 proposals was around 65 €.

Too much for this use case.

There are possible optimisations that would make the transaction cheaper on mainnet, e.g. 2 fields can be removed and events can be used to track the votes. The struct can be reduced to 5 fields:

struct Vote {
bool released;
uint152 amount;
uint48 grantId;
uint56 voteId;
}


This structure moves vote tracking off-chain: the contract would fire an event when a vote is casted or tokens are released. I can then run an indexer and create a subgraph, which would allow me to fill in the missing data.

Spoiler alert: that is what we decided to go for.

Before making any decision we wanted to see what the cost of bridging tokens to an L2 was. We weren't sure how to bridge tokens to Optimism. We asked around. On Twitter. Web3 style.

Austin Griffith, who was on the call, figured out a manual process to send tokens over to Optimism using the gateway contract. Hopes were high, but it didn't last long.

Austin successfully bridged 1 GTC (4.42 USD), and he had to pay 41.23 USD for the transaction. And this is what he had to do.

Not what we were hoping. But why is it that expensive?

I decided to try to understand what bridging entails by writing my own simple bridge on a testnet. For that I used 🏗 scaffold-eth, which has become my tool of choice for quick dApp development and prototyping. More on that in a future post.

I won't go into the details of how to build a bridge here, this post would become incredibly long if I did, but I will go through the steps needed to set up a local environment to develop on Optimism.

## Hello Optimism 🔴

Step one was diving into the documentation.

You should familiarise yourself with the differences between Ethereum and Optimism.

Optimism has pretty detailed developer docs on how to send data between L1 and L2. Before you go ahead you should at least skim it to get familiar with the concepts.

### Environment set-up

Prerequisites for the next steps:

#### 🏗 scaffold-eth

Clone/fork scaffold-eth. I updated the Optimism submodule in the repo to point to the master branch, since it was pretty outdated. Until I merge my changes (I will update this post once I do), if you want to follow along you'll have to clone/fork my repo:

git clone https://github.com/DanieleSalatti/gitcoin-grants-conviction.git
git checkout embracing-optimism


ℹ️ The branch contains a few contracts that I was tinkering with - they are not functional, at least at the time of this writing.

Normally at this point you should start the Hardhat chain. Since we are going to run the whole L1 + L2 caboodle you can skip this part.

#### Local Optimism

It's time to set up a local Optimism development node! 🔴

Open a second terminal, and initiate the Optimism submodule:

cd packages/services/optimism
git submodule init
git submodule update


This might take a while, it's a good time go grab a coffee or a tea ☕️

Once that process completes, we need to install the dependencies for the submodule. From packages/services/optimism:

yarn install
yarn build


The build command is required to transpile Typescript to JavaScript.

It is now time to bring up the Optimism stack 🚀

cd ops
docker compose up --scale relayer=1 --scale verifier=1


You will be faced with a barrage of error messages like these:

ops-dtl-1              | curl: (7) Failed to connect to deployer port 8081 after 3 ms: Connection refused
ops-relayer-1          | curl: (7) Failed to connect to l2geth port 8545 after 0 ms: Connection refused
ops-verifier-1         | curl: (7) Failed to connect to deployer port 8081 after 0 ms: Connection refused
ops-replica-1          | curl: (7) Failed to connect to deployer port 8081 after 0 ms: Connection refused
ops-relayer-1          | curl: (7) Failed to connect to l2geth port 8545 after 0 ms: Connection refused
ops-relayer-1          | curl: (7) Failed to connect to l2geth port 8545 after 0 ms: Connection refused
ops-relayer-1          | curl: (7) Failed to connect to l2geth port 8545 after 1 ms: Connection refused
ops-relayer-1          | curl: (7) Failed to connect to l2geth port 8545 after 0 ms: Connection refused
ops-batch_submitter-1  | curl: (7) Failed to connect to deployer port 8081 after 0 ms: Connection refused


It is all normal. Services depend on each other, and require time to start. It can take several minutes but eventually you will see a constant stream of text with no errors in it.

This part is done. Congratulations! 🎉

#### 🏗 scaffold-eth - Part 2

We need to tell scaffold-eth how to connect to the infrastructure we spun up with Docker.

Because Optimism is EVM compatible, we can connect to the RPC URLs exposed by the containers:

l1Local: { rpc: "http://localhost:9545", chainId: 31337 }
l2Local: { rpc: "http://localhost:8545", chainId: 420 }


This is a good moment to configure these networks in your browser wallet - MetaMask or anything else you use.

If you look at packages/react-app/src/constants.js you will see that these endpoints are configured:

packages/react-app/src/constants.js
localOptimismL1: {
name: "localOptimismL1",
color: "#f01a37",
chainId: 31337,
blockExplorer: "",
rpcUrl: "http://" + (global.window ? window.location.hostname : "localhost") + ":9545",
},
localOptimism: {
name: "localOptimism",
color: "#f01a37",
chainId: 17,
blockExplorer: "",
rpcUrl: "http://" + (global.window ? window.location.hostname : "localhost") + ":8545",
gasPrice: 0,
},


Luckily scaffold-eth supports multiple networks. Go ahead and set the initialNetwork and networkOptions variables in App.jsx:

packages/react-app/src/App.jsx
// Line 56
const initialNetwork = NETWORKS.localOptimismL1;

// Line 76
const networkOptions = [initialNetwork.name, "localOptimism"];


Start your frontend in a new terminal:

cd scaffold-eth
yarn start


Open http://localhost:3000 and you should see the app.

ℹ️ To get test ETH on our L1 and L2 we can use a set of special accounts that is funded with 5000 ETH on both L1 and L2. The mnemonic for the accounts is can be found here and is test test test test test test test test test test test junk.

Optimism relies on a set of pre-deployed contracts for cross-chain communication and bridging. You can get the local contracts addresses by running this command in a terminal:

% curl http://localhost:8080/addresses.json

{
"BondManager": "0x5FC8d32690cc91D4c39d9d3abcBD16989F875707",
"L1StandardBridge_for_verification_only": "0x3Aa5ebB10DC797CAC828524e59A333d0A371443c",
"ChainStorageContainer-CTC-batches": "0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512",
"StateCommitmentChain": "0xDc64a140Aa3E981100a9becA4E685f962f0cF6C9",
"CanonicalTransactionChain": "0xCf7Ed3AccA5a467e9e704C703E8D87F634fB0Fc9",