Skip to content

Solidity to GoLang automatic conversion

Published: at 03:22 PM

Write less code when using Garnet. Automatically convert your solidity contracts to Go code!

Table of contents

Open Table of contents

Problem with MUD

One issue that I found while using the MUD lib is that I needed to write some game logic twice, once for the solidity contract and another for the optimistic rendering of the action.

When I created Garnet, I still had the same issue, I was writing the solidity contracts using MUD but then rewriting all the logic in GoLang to optimistically predict the result of the transaction execution.

Two problems araise with this issue:

Solution

I built a transpiler from Solidity to GoLang, the solution is currently focused on the Garnet lib.

The main blocker to create the transpiler was to get the AST (Abstract Syntax Tree) for all the solidity contracts. Luckily I found out that the MUD’s CLI package stores the AST for each contract compiled in the out folder.

With that out of the way, I started to code the transpiler.

Garnet getters and setters

Garnet is a MUD indexer and in-memory db. To get the blockchain information, you need to pass the table name and key.

The first challenge was to create simple functions to get the blockchain information without passing the table name because in solidity the getters are like this: TableName.get(key).

The second challenge was to parse the mud.config.ts and identify all the enums, tables and variables. With all that information, I created all the respective structures in GoLang.

Lastly, I needed to create a way to generate predictions or MudEvents. The events are going to be used when the TableName.set(key, value) function is called in the solidity contract.

Predictions

The real challenge started after creating the getters and setters: process all the AST code and generate the equivalent code in GoLang.

The way to do it was to read all the AST nodes and create code that works in GoLang for each node. For the table setters and getters I called the functions created in the previous step instead of transpiling the code one-to-one.

After one week of fighting the parser, regexs and string manipulation, I got a usable version. The code is not pretty but it works! You can check it out here

Code generated

The tool always creates the files:

For each MUD System, it will generate a new file. Here is a small example of how it works:

// SPDX-License-Identifier: MIT
pragma solidity >=0.8.0;

// Core
import {System} from "@latticexyz/world/src/System.sol";

import {Player} from "../codegen/tables/Player.sol";
import {Position} from "../codegen/tables/Position.sol";

import {addressToEntityKey} from "../addressToEntityKey.sol";

contract MoveSystem is System {
    function Move(int32 newX, int32 newY) public {
        bytes32 senderKey = addressToEntityKey(_msgSender());

        // Make sure that the player is registered
        require(Player.get(senderKey) == true, "wallet is not registered");

        // Check that the X and Y are valid values
        require(newX >= 0 && newY >= 0, "invalid X and Y");
        Position.set(senderKey, newX, newY);
    }
}
package garnethelpers

func (p *Prediction) Move(newX int64, newY int64, senderAddress string) {
	senderKey := p.addressToEntityKey(senderAddress)
	if !(p.PlayerGet(senderKey) == true) {
		panic("wallet is not registered")
	}
	if !(newX >= int64(0) && newY >= int64(0)) {
		panic("invalid X and Y")
	}
	p.PositionSet(senderKey, newX, newY)
}

NOTE: for small functions, it doesn’t save you much time, but you can see an example of a big function solidity function converted to GoLang code.

Example

I used the tool to build Bochamon. For this project I didn’t write any predictions, everything was autogenerated by the garnetutils.

Running the tool generates the folder garnethelpers automatically:

garnetutils alpha -i ./superhack/backend/contracts-builder/contracts -o ./superhack/backend/internal/garnethelpers/

You can see the generated code here.

Using the generated code to predict the result of a Move transaction is as simple as adding these lines:

	prediction := garnethelpers.NewPrediction(b.db)
	prediction.Move(int64(moveMsg.X), int64(moveMsg.Y), ws.WalletAddress)

You can add the prediction result to the database with these lines:

	b.db.AddTxSent(data.UnconfirmedTransaction{
		Txhash: txhash.Hex(),
		Events: prediction.Events,
	})