Skip to main content

Structured Data Types

The schema tool allows you to define your own structured data types that are composed of the predefined WasmLib value data types. The tool will generate a struct with named fields according to the definition in the schema definition file, and also will generate code to serialize and deserialize the structure to a byte array, so that it can be saved as a single unit of data bytes, for example in state storage.

You can use structs directly as a type in state storage definitions and the schema tool will automatically generate the proxy code to access and (de)serialize it properly.

For example, let's say you are creating a betting smart contract. Then you would want to store information for each bet. The Bet structure could consist of the bet amount and time, the number of the item that was bet on, and the agent ID of the one who placed the bet. And you would keep track of all bets in state storage in an array of Bet structs. To do so, you would insert the following into the schema definition file:

structs:
Bet:
amount: Int64 # bet amount
better: AgentID # who placed this bet
number: Int32 # number of item we bet on
time: Int64 # timestamp of this bet
state:
bets: Bet[] # all bets that were made in this round

The schema tool will generate the following code in structs.xx for the Bet struct:

package betting

import "github.com/iotaledger/wasp/packages/wasmvm/wasmlib/go/wasmlib/wasmtypes"

type Bet struct {
// bet amount
Amount int64
// who placed this bet
Better wasmtypes.ScAgentID
// number of item we bet on
Number int32
// timestamp of this bet
Time int64
}

func NewBetFromBytes(buf []byte) *Bet {
dec := wasmtypes.NewWasmDecoder(buf)
data := &Bet{}
data.Amount = wasmtypes.Int64Decode(dec)
data.Better = wasmtypes.AgentIDDecode(dec)
data.Number = wasmtypes.Int32Decode(dec)
data.Time = wasmtypes.Int64Decode(dec)
dec.Close()
return data
}

func (o *Bet) Bytes() []byte {
enc := wasmtypes.NewWasmEncoder()
wasmtypes.Int64Encode(enc, o.Amount)
wasmtypes.AgentIDEncode(enc, o.Better)
wasmtypes.Int32Encode(enc, o.Number)
wasmtypes.Int64Encode(enc, o.Time)
return enc.Buf()
}

type ImmutableBet struct {
proxy wasmtypes.Proxy
}

func (o ImmutableBet) Exists() bool {
return o.proxy.Exists()
}

func (o ImmutableBet) Value() *Bet {
return NewBetFromBytes(o.proxy.Get())
}

type MutableBet struct {
proxy wasmtypes.Proxy
}

func (o MutableBet) Delete() {
o.proxy.Delete()
}

func (o MutableBet) Exists() bool {
return o.proxy.Exists()
}

func (o MutableBet) SetValue(value *Bet) {
o.proxy.Set(value.Bytes())
}

func (o MutableBet) Value() *Bet {
return NewBetFromBytes(o.proxy.Get())
}

Notice how the generated ImmutableBet and MutableBet proxies use the fromBytes() and toBytes() (de)serialization code to automatically transform byte arrays into Bet structs.

The generated code in state.xx that implements the state interface is shown here:

package betting

import "github.com/iotaledger/wasp/packages/wasmvm/wasmlib/go/wasmlib/wasmtypes"

type ArrayOfImmutableBet struct {
proxy wasmtypes.Proxy
}

func (a ArrayOfImmutableBet) Length() uint32 {
return a.proxy.Length()
}

func (a ArrayOfImmutableBet) GetBet(index uint32) ImmutableBet {
return ImmutableBet{proxy: a.proxy.Index(index)}
}

type ImmutableBettingState struct {
proxy wasmtypes.Proxy
}

// all bets that were made in this round
func (s ImmutableBettingState) Bets() ArrayOfImmutableBet {
return ArrayOfImmutableBet{proxy: s.proxy.Root(StateBets)}
}

// current owner of this smart contract
func (s ImmutableBettingState) Owner() wasmtypes.ScImmutableAgentID {
return wasmtypes.NewScImmutableAgentID(s.proxy.Root(StateOwner))
}

type ArrayOfMutableBet struct {
proxy wasmtypes.Proxy
}

func (a ArrayOfMutableBet) AppendBet() MutableBet {
return MutableBet{proxy: a.proxy.Append()}
}

func (a ArrayOfMutableBet) Clear() {
a.proxy.ClearArray()
}

func (a ArrayOfMutableBet) Length() uint32 {
return a.proxy.Length()
}

func (a ArrayOfMutableBet) GetBet(index uint32) MutableBet {
return MutableBet{proxy: a.proxy.Index(index)}
}

type MutableBettingState struct {
proxy wasmtypes.Proxy
}

func (s MutableBettingState) AsImmutable() ImmutableBettingState {
return ImmutableBettingState(s)
}

// all bets that were made in this round
func (s MutableBettingState) Bets() ArrayOfMutableBet {
return ArrayOfMutableBet{proxy: s.proxy.Root(StateBets)}
}

// current owner of this smart contract
func (s MutableBettingState) Owner() wasmtypes.ScMutableAgentID {
return wasmtypes.NewScMutableAgentID(s.proxy.Root(StateOwner))
}

The end results are ImmutableBettingState and MutableBettingState structures that can directly interface to the state of the betting contract.

Note how the comments from the schema definition file have been copied to the structure definition in the code and also to the access functions for the fields. This will allow your development environment to pop up context-sensitive help for those fields and access functions.

In the next section we will look at how to use type definitions to properly define container nesting relationships.