The Flip Smart Contract part 6 : Bet logic

Time to code the core contract logic

Setting the base percentage

Remember in the part 4 we talked about percentages. We said hundred percentage is 10,000 due to VM limitation using integers only. For future computation we need to store this constant inside the contract code :

const HUNDRED_PERCENT: u64 = 10_000

@contract
abstract class FlipContract extends ContractBase {
// --- rest of the code ---
}

Notice the type of HUNDRED_PERCENT. It's the AssemblyScript's native u64, not the mx-sdk-as's one. Instantiate classes statically may lead to issue when deploying the contract. Avoid it and prefers use primitive types when creating globals variables.

Initialising the contract

We didn't had a constructor to our contract yet ! Time to add one inside the FlipContract class :

constructor(
    ownerPercentFees: ManagedU64,
    bountyPercentFees: ManagedU64,
    minimumBlockBounty: ManagedU64
) {
    super()
    this.ownerPercentFees().set(ownerPercentFees)
    this.bountyPercentFees().set(bountyPercentFees)

    this.require(
        minimumBlockBounty > ManageddU64.zero(),
        "minimumBlockBounty is zero"
    )

    this.minimumBlockBounty().set(minimumBlockBounty)
}

The contract's constructor is called each time the contract is deployed or updated (if allowed to be). Here we simply initialise the storage.

We do not initialise maximumBet and maximumBetPercent here as they are relative to a token.

Create the flip endpoint

Next step, create the flip endpoint. Inside the FlipContract class add the following method :

flip(): void {
    // --- Step 1 ---
    const payment = this.callValue.singlePayment

    const tokenReserve = this.tokenReserve(
        payment.tokenIdentifier,
        payment.tokenNonce
    ).get()

    this.require(
        tokenReserve > BigUint.zero(),
        "no token reserve"
    )

    this.require(
        !this.maximumBet(payment.tokenIdentifier, payment.tokenNonce).isEmpty(),
        "no maximum bet"
    )

    this.require(
        !this.maximumBetPercent(payment.tokenIdentifier, payment.tokenNonce).isEmpty(),
        "no maximum bet percent"
    )

    const maximumBet = this.maximumBet(
        payment.tokenIdentifier,
        payment.tokenNonce
    ).get()

    const maximumBetPercent = this.maximumBetPercent(
        payment.tokenIdentifier,
        payment.tokenNonce
    ).get()

    const maximumBetPercentComputed = tokenReserve * maximumBetPercent.toBigUint() / BigUint.fromU64(HUNDRED_PERCENT)

    const maximumAllowedBet = maximumBet < maximumBetPercentComputed ? maximumBet : maximumBetPercentComputed

    // --- Step 2 ---
    const ownerProfits = payment.amount * this.ownerPercentFees().get().toBigUint() / BigUint.fromU64(HUNDRED_PERCENT)
    const bounty = payment.amount * this.bountyPercentFees().get().toBigUint() / BigUint.fromU64(HUNDRED_PERCENT)
    const amount = payment.amount - bounty - ownerProfits

    // --- Step 3 ---
    this.require(
        amount <= maximumAllowedBet,
        "too much bet"
    )
    
    // --- Step 4 ---
    const lastFlipId = this.lastFlipId().isEmpty() ? ManagedU64.zero() : this.lastFlipId().get()

    const flipId = lastFlipId + ManagedU64.fromValue(1)

    const flip = Flip.new(
        flipId,
        this.blockchain.caller,
        payment.tokenIdentifier,
        payment.tokenNonce,
        amount,
        bounty,
        this.blockchain.currentBlockNonce,
        this.minimumBlockBounty().get()
    )

    this.tokenReserve(
        payment.tokenIdentifier,
        payment.tokenNonce
    ).set(tokenReserve - amount)

    this.send
        .direct(
            this.blockchain.owner,
            payment.tokenIdentifier,
            payment.tokenNonce,
            ownerProfits
        )

    this.flipForId(flipId).set(flip)
    this.lastFlipId().set(flipId)
}

What does this code do ?

Step 1 : Compute maximum bet limits

We need to ensure bet limits are not reached, hence we need to compute them.

Let's focus on the line const maximumBetPercentComputed = tokenReserve * maximumBetPercent / ManagedU64.fromValue(HUNDRED_PERCENT), math operations order is important since numbers cannot be decimals and divisions are trucated : 50 / 100 = 0.

Suppose tokenReserve = 10, maximumBetPercent = 1000 and HUNDRED_PERCENT = 10_000, tokenReserve * maximumBetPercent / ManagedU64.fromValue(HUNDRED_PERCENT) = 10 * 1000 / 10_000 = 10_000 / 10_000 = 1

Now the same computation in a different order : maximumBetPercent / ManagedU64.fromValue(HUNDRED_PERCENT) * tokenReserve = 1000 / 10_000 * 10 = 0 * 10 = 0

Always multiply before dividing.

There is no max and min functions implemented for BigUint type at the moment. Theses are planned and this tutorial will updated when done.

Step 2 : Compute amounts

We need to compute bounty and owners profits, after that can compute the real bet's amount.

Step 3 : Ensure bet limit is not reached

This is simply done with the this.require function.

Step 4 : Construct the flip object

Nothing special to say here, we get the last flip id, increment it and use all informations we have to create a new instance.

Then we update storage values : flipForId, lastFlipId and tokenReserve. For the last one we only remove computed amount (instead of full payment amount) since owner profits and bounty are not realised yet.

Next up

The contract has the ability to initialise a flip, next step is to realise it !

Last updated