The Flip Smart Contract part 4 : Writing the contract storage

The storage is the fondation of our contract

The contract storage is the way our contract store data between two transactions. We can store almost anything into the storage : numbers, strings, objects, arrays, ...

Be careful when designing the storage, the more you store the more gas users will pay to interact with your contract.

Avoid using arrays if possible as they are very gas consuming.

Storing flips informations

The first thing we need to store are informations about each flip. A flip will be represented by the following properties :

  • An identifier which is a number, useful for differentiate two flips

  • The player's address associated to the flip

  • The token identifier the flip is about

  • The token nonce the flip is about

  • The amount bet by the player

  • The bounty amount gained by users who do it

  • The block number (a.k.a block nonce) the flip was created

  • The minimum blocks to wait before the bounty can be made

We will use a class, create a new file called flip.ts inside the assembly folder with the following code :

import { ManagedU64, ManagedAddress, TokenIdentifier, BigUint } from "@gfusee/mx-sdk-as"

@struct
export class Flip {
    id: ManagedU64
    playerAddress: ManagedAddress
    tokenIdentifier: TokenIdentifier
    tokenNonce: ManagedU64
    amount: BigUint
    bounty: BigUint
    blockNonce: ManagedU64
    minimumBlockBounty: ManagedU64
}

The @struct annotation tells the framework to implement stuffs to make this class works with the MultiversX VM.

At the moment, class annotated with @struct should have an empty constructor (or no constructor at all). In order to instantiate we need to create a static method and add ! to all properties :

import { ManagedU64, ManagedAddress, TokenIdentifier, BigUint } from "@gfusee/mx-sdk-as"

@struct
export class Flip {
    id!: ManagedU64
    playerAddress!: ManagedAddress
    tokenIdentifier!: TokenIdentifier
    tokenNonce!: ManagedU64
    amount!: BigUint
    bounty!: BigUint
    blockNonce!: ManagedU64
    minimumBlockBounty!: ManagedU64

    static new(
        id: ManagedU64,
        playerAddress: ManagedAddress,
        tokenIdentifier: TokenIdentifier,
        tokenNonce: ManagedU64,
        amount: BigUint,
        bounty: BigUint,
        blockNonce: ManagedU64,
        minimumBlockBounty: ManagedU64
    ): Flip {
        const result = new Flip()

        result.id = id
        result.playerAddress = playerAddress
        result.tokenIdentifier = tokenIdentifier
        result.tokenNonce = tokenNonce
        result.amount = amount
        result.bounty = bounty
        result.blockNonce = blockNonce
        result.minimumBlockBounty = minimumBlockBounty

        return result
    }
}

We know that the empty constructor is a big limitation and we hope to be able to remove it as soon as possible.

We are now able to model a flip, we need to store it. Inside the FlipContract contract class in the index.ts file we will now add the flip storage code :

@contract
abstract class FlipContract extends ContractBase {

    abstract flipForId(id: ManagedU64): Mapping<Flip>

}

This new line of code declare that we want to store Flip models under ManagedU64 keys. In some way this is similar to a map. A Mapping object have the two following methods :

  • get() : get the value from the blockchain storage

  • set() : set the value in the blockchain storage

You can see the Mapping type as a bridge between your contract and datas stored in the blockchain.

This method is marked abstract, the framework will take care to create an implementation of it during the compilation.

You can have a mapping with how many keys you want (even 0 key !). All VM compatibles types can be used as keys : ManagedBuffer, ManagedUXX, TokenIdentifier, ... As well as any user declared types with @struct or @enumtype annotation

Implementing other values

Let's check what we need to store in the contract :

  • Fees percentage that go to us

  • Bounty percentage for peoples who make one

  • The contract's token reserve, for each token, we will not query the contract balance directly because we need to include waiting bet bounty fees

  • The absolute maximum bet allowed, for a specific token identifier

  • The maximum bet allowed as a percentage, relative to the contract token reserve

  • The minimum blocks between a bet and the bounty

  • The last flip id

  • The last flip id where the bounty has been made, we want our bounty endpoint to generate random numbers ALL waiting flips. This value let us know which flips are waiting random generation

Let's talk a bit about percentages. The MultiversX VM only handle integer types, in order to compute percentage we will assume that 10,000 - 100%. Hence, 50% will be represented in storage by 5,000, 1% by 100, 0.1% by 10, etc...

The full contract storage looks like this :

@contract
abstract class FlipContract extends ContractBase {

    abstract ownerPercentFees(): Mapping<ManagedU64>

    abstract bountyPercentFees(): Mapping<ManagedU64>

    abstract maximumBet(
        tokenIdentifier: TokenIdentifier,
        tokenNonce: ManagedU64
    ): Mapping<BigUint>

    abstract maximumBetPercent(
        tokenIdentifier: TokenIdentifier,
        tokenNonce: ManagedU64
    ): Mapping<ManagedU64>

    abstract minimumBlockBounty(): Mapping<ManagedU64>

    abstract tokenReserve(
        tokenIdentifier: TokenIdentifier,
        tokenNonce: ManagedU64
    ): Mapping<BigUint>

    abstract flipForId(id: ManagedU64): Mapping<Flip>

    abstract lastFlipId(): Mapping<ManagedU64>

    abstract lastBountyFlipId(): Mapping<ManagedU64>

}

Next up

The storage is ready, the next step is to create the administration endpoints that will allow us to change settings like fees 🫡

Last updated