Skip to content
This repository has been archived by the owner on Oct 15, 2024. It is now read-only.

Commit

Permalink
feat: Go wasm - extracting common code without generics...
Browse files Browse the repository at this point in the history
  • Loading branch information
ppedziwiatr committed Mar 9, 2022
1 parent b88b7e1 commit 5866c35
Show file tree
Hide file tree
Showing 9 changed files with 49 additions and 27 deletions.
20 changes: 15 additions & 5 deletions go/README.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
# Go smartweave example contract

Note: we're using `tinygo` compiler, as the default compiler produces huuuuge binaries
(`hello world` example - ~2MB)
![img.png](img.png)
## Install go 1.17
https://go.dev/doc/install

## How to use (default Go compiler)
- [Install easyjson](https://github.com/mailru/easyjson#install)
Expand All @@ -17,5 +16,16 @@ Note: we're using `tinygo` compiler, as the default compiler produces huuuuge bi
- Build wasm contract file: `bash build-tiny.sh` (it should create `out` folder)
- Run wasm contract simulation: `node run-tiny.js`

Size comparison for PST contract:
![img_1.png](img_1.png)
### Size comparison for PST contract (default go compiler vs `tinygo` compiler):
![img_1.png](img_1.png)

### Folder structure
- `common` - package for commons code, that will be reused between contracts (
handles the low-level WASM-JS communication for Go). Contains `SwContract` interface
that has to implemented by contract developers.
- `common_types` - package for commons structs. Have to be a separate package,
otherwise easyjson throws error during code generation
- `impl` - package that contains implementation of the given contract (i.e. implementation of the `SwContract`
interface)
- `types` - package that contains types specific to the contract that needs to be generated with easyjson
- `main.go` - main file - entry point to the wasm library.
2 changes: 1 addition & 1 deletion go/common/imports/console/imports.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import (
)

func Log(args ...interface{}) {
importConsole().Call("log", args)
importConsole().Call("log", args[0], args[1:])
}

func importConsole() js.Value {
Expand Down
11 changes: 5 additions & 6 deletions go/wasm/wasm.go → go/common/wasm.go
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
package wasm
package common

import (
"encoding/json"
"github.com/redstone-finance/redstone-contracts-wasm/go/common_types"
"github.com/redstone-finance/redstone-contracts-wasm/go/impl"
"syscall/js"
)

func Run(contract *impl.PstContract) {
func Run(contract common_types.SwContract) {
// the Go way of defining WASM exports...
// standard "exports" from the wasm module do not work here...
// that's kinda ugly TBH
Expand All @@ -22,7 +21,7 @@ func Run(contract *impl.PstContract) {
<-make(chan bool)
}

func handle(contract *impl.PstContract) js.Func {
func handle(contract common_types.SwContract) js.Func {
// note: each 'exported' function has to be wrapped into
// js.FuncOf(func(this js.Value, args []js.Value) interface{}
// - that's kinda ugly too...
Expand Down Expand Up @@ -80,14 +79,14 @@ func lang() interface{} {
})
}

func currentState(contract *impl.PstContract) interface{} {
func currentState(contract common_types.SwContract) interface{} {
return js.FuncOf(func(this js.Value, args []js.Value) interface{} {
data, _ := json.Marshal(contract.CurrentState())
return string(data)
})
}

func initState(contract *impl.PstContract) interface{} {
func initState(contract common_types.SwContract) interface{} {
return js.FuncOf(func(this js.Value, args []js.Value) interface{} {
contract.InitState(args[0].String())
return nil
Expand Down
9 changes: 9 additions & 0 deletions go/common_types/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,15 @@ type Action struct {

type ActionResult = interface{}

// SwContract We need to use "any" (interface{}) type here for the state - as this is
// a common interface for all contracts implemented in Go WASM.
// Version with generics is available on branch ppe/go-generics (but to use it real life
// we need to wait for the official release of Go 1.18 and tinygo compiler with generics
// support added - https://github.com/tinygo-org/tinygo/issues/2158)
//easyjson:skip
type SwContract interface {
Handle(action Action, actionBytes []byte) (interface{}, ActionResult, error)
InitState(stateJson string)
UpdateState(newState interface{})
CurrentState() interface{}
}
Binary file removed go/img.png
Binary file not shown.
20 changes: 12 additions & 8 deletions go/impl/pst-contract.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ type PstContract struct {
}

// Handle the function that contract developers actually need to implement
func (c *PstContract) Handle(action common_types.Action, actionBytes []byte) (*types.PstState, common_types.ActionResult, error) {
func (c *PstContract) Handle(action common_types.Action, actionBytes []byte) (interface{}, common_types.ActionResult, error) {
fn := action.Function

console.Log("Calling", fn)
Expand All @@ -22,6 +22,8 @@ func (c *PstContract) Handle(action common_types.Action, actionBytes []byte) (*t
console.Log("Block indep_hash", block.IndepHash())
console.Log("Block timestamp", block.Timestamp())

clonedState := c.CloneState().(types.PstState)

switch fn {
case "transfer":
// not sure how to "automatically" handle casting to concrete action impl in Go.
Expand All @@ -32,23 +34,23 @@ func (c *PstContract) Handle(action common_types.Action, actionBytes []byte) (*t
if err != nil {
return nil, nil, err
}
state, err := Transfer(c.CloneState(), transfer)
state, err := Transfer(clonedState, transfer)
return state, nil, err
case "balance":
var balance types.BalanceAction
err := balance.UnmarshalJSON(actionBytes)
if err != nil {
return nil, nil, err
}
result, err := Balance(c.CloneState(), balance)
result, err := Balance(clonedState, balance)
return nil, result, err
case "foreignCall":
var foreignCall types.ForeignCallAction
err := foreignCall.UnmarshalJSON(actionBytes)
if err != nil {
return nil, nil, err
}
result, err := ForeignCall(c.CloneState(), foreignCall)
result, err := ForeignCall(clonedState, foreignCall)
return nil, result, err
default:
return nil, nil, errors.New("[RE:WTF] unknown function: " + fn)
Expand All @@ -64,18 +66,20 @@ func (c *PstContract) InitState(stateJson string) {
c.UpdateState(&state)
}

func (c *PstContract) UpdateState(newState *types.PstState) {
c.state = *newState
func (c *PstContract) UpdateState(newState interface{}) {
// note: we're first type asserting here to the pointer to types.PstState
// - and the retrieving value from the pointer
c.state = *(newState.(*types.PstState))
}

func (c *PstContract) CurrentState() types.PstState {
func (c *PstContract) CurrentState() interface{} {
return c.state
}

// CloneState TODO: discuss whether it is necessary
// it allows to make the given action transactional, but
// at the cost of performance
func (c *PstContract) CloneState() types.PstState {
func (c *PstContract) CloneState() interface{} {
json, _ := c.state.MarshalJSON()
state := types.PstState{}
err := state.UnmarshalJSON(json)
Expand Down
6 changes: 3 additions & 3 deletions go/main.go
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
package main

import (
"github.com/redstone-finance/redstone-contracts-wasm/go/common"
"github.com/redstone-finance/redstone-contracts-wasm/go/impl"
"github.com/redstone-finance/redstone-contracts-wasm/go/wasm"
)

// the current state of the contract that contract developers have to define
// contract - implementation of the SwContract interface
var contract = impl.PstContract{}

// handles all the WASM-JS related trickery...
func main() {
wasm.Run(&contract)
common.Run(&contract)
}
6 changes: 3 additions & 3 deletions go/run-tiny.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ async function main() {
global.redstone.go = {
console: {
log: function (...args) {
console.log(args[0], args.slice(1));
console.log(`[WASM] ${args[0]}`, ...args.slice(1));
}
},
Transaction: {
Expand Down Expand Up @@ -102,7 +102,7 @@ async function main() {
}
}
)));
console.log('\ncurrentState()', currentState());
console.log('\ncurrentState()', JSON.parse(currentState()));

console.log("\nCalling async handle - transfer");

Expand All @@ -117,7 +117,7 @@ async function main() {
console.log('Result from transfer:', resultTransfer);
console.log('Gas used', usedGas);

console.log('\ncurrentState()', currentState());
console.log('\ncurrentState()', JSON.parse(currentState()));

console.log("\nCalling async handle - balance");
const resultBalance = await handle(JSON.stringify({
Expand Down
2 changes: 1 addition & 1 deletion go/run.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ async function main() {
global.redstone.go = {
console: {
log: function (...args) {
console.log(args[0], args.slice(1));
console.log(args[0], ...args.slice(1));
}
},
Transaction: {
Expand Down

0 comments on commit 5866c35

Please sign in to comment.