Smart-Contract Development Guide

    The Plasma blockchain allows you to develop complex business logic, tokenize assets and build decentralized financial services.

    In this guide:

    Smart-contract Required

    For your work you will need

    • Set-up PlasmaDLT testnet node
    • Set-up ION Contract Development Tollkit ion.cdt
    • C++ or Typescript language
    • Basic Blockchain Knowledge

    Let's look at an example of Dice game

    •  Installing the Dice game
    •  First look
    •  Structure Smart-contract of Dice game

     Installing the Dice game

    Follow Setting up script.

      # Please go to https://github.com/plasmadlt/plasma-fairdice-contract
      $mkdir /opt/PlasmaIon/plasmadice
      $cd /opt/PlasmaIon/plasmadice
      $git clone https://github.com/plasmadlt/plasma-fairdice-contract.git
    
    
    
    

     First look

    Consider the main contract files.

    Tips:

    • yours-token.abi — abi is short for “application binary interface”, it’s basically a JSON file that knows where the hooks live once we compile our source into webassembly, more on this later.
    • yours-token.cpp — c++ file, this is where our C++ source lives.
    • yours-token.hpp — h++ file, normal header file stuff.
    • CMakeLists.txt 

    Notes:

     Structure Smart-contract of Dice game

    A special table that contains information on all bets, made by users

    TABLE allbets {
      uint64_t id;                                        //    unique identifier of the rate, calculated by the formula [previous + 1]
      ion::name player;                                 //  name of the user (account) who made the bet
      ion::asset amount;                                    //  amount, taking into account the token symbol - which the user has set
      uint8_t roll_under;                                 //    the number determining the odds for the bet made should be within [2, 96]
      ion::checksum256 seed_hash;                         //    open hash that is generated on server side
      ion::checksum256 user_seed_hash;                //    open hash that is generated on the user side
      ion::time_point created_at;                       //  additional rate ID - time it was created
      uint64_t primary_key() const { return id; }   //  overloaded service function - table integration
    };
    
    

    A special table that contains information about the results of the played bets. If the bet has not played, there will be no information on it.

    struct bets_result {
        uint64_t bet_id;                         // unique identifier of the rate, calculated by the formula [previous + 1]
        ion::name player;                       //  name of the user (account) who made the bet
        ion::asset amount;                  //  amount, taking into account the token symbol - which the user has set
        uint8_t roll_under;                 //  the number determining the odds for the bet made should be within [2, 96]
        uint8_t random_roll;                //  the number that determines the winning or losing the user's bet, a kind of indicator flag
        ion::checksum256 seed;              //  secret hash that identifies the correctness of data from the user
        ion::checksum256 seed_hash;             //  open hash that is generated on server side
        ion::checksum256 user_seed_hash;            // open hash that is generated on the user side
        ion::asset payout;                  //  amount, taking into account the token symbol - which the user receives as a payment at a rate.
    
    

    Special table in which the current value of the unique identifier of the rate is stored

    TABLE identity {
        uint64_t current_id = 1;                //  value
    };
    
    

    A special table that contains the amounts, taking into account the token symbol, reserved for potential payouts of bets that have not played so far It is necessary to maintain the balance of the system, and guarantees that the user will unambiguously receive payment in case of a win.

    TABLE dicepool {
        ion::asset locked;                          //  blocked amount
        uint64_t primary_key() const { return locked.symbol.raw(); }    //  overloaded service function - table integration
    };
    
    

    Implements the following algorithms within itself: 

    Formation of a unique hash based on the existing one. The resulting hash overwrites the existing one.

    template <class T>
    inline void hash_combine(std::size_t& seed, const T& v) {
        std::hash<T> hasher;
        seed ^= hasher(v) + 0x9e3779b9 + (seed << 6) + (seed >> 2);
    }
    
    

    Calculation of potential and real payments to the user, used to block funds to the reserve, and to generate payments

    ion::asset plasma_dice::compute_payout(uint8_t roll_under, const ion::asset& offer)
    {
        const double ODDS = 98.0 / ((double)roll_under - 1.0);
        return ion::asset(ODDS * offer.amount, offer.symbol);
    
        // return std::min(ion::asset(ODDS * offer.amount, offer.symbol), compute_available_balance(offer));
    }
    
    

    Commission per game

    ion::asset plasma_dice::compute_comission(const ion::asset& payout)
    {
        return payout / (10 * 100) * 2;
    }
    

    Checking the presence in the bank - the required amount for the potential maximum payment. If the potential maximum amount is not enough - the rate will not be accepted by the contract.

    ion::asset plasma_dice::compute_available_balance(const ion::asset& quantity)
    {
        ion::asset locked(0, quantity.symbol);
        auto it = dicepool_.find(quantity.symbol.raw());
        if (it != dicepool_.end())
        {
            locked = it->locked;
        }
    
        ion::asset balance = plasma::tokenTypes::getBalance(plasma::accountPlasmaDiceBank, quantity.symbol.code());
        ion::print("pool [", balance, "], locked [", locked, "]");
    
        ion::asset available = balance - locked;
        ion::check(available.amount >= 0, "fund pool overdraw");
    
        return available;
    }
    
    

    Business logic of forming a unique value based on two existing ones, using the hash_combine algorithm On the basis of this value, a decision is made - the winning or no user rate.

    uint8_t plasma_dice::compute_random_roll(const ion::checksum256& seed1, const ion::checksum256& seed2)
    {
        size_t hash = 0;
        plasma::hash_combine(hash, plasma::sha256_to_hex(seed1));
        plasma::hash_combine(hash, plasma::sha256_to_hex(seed2));
        return hash % 100 + 1;
    }
    

    The function for obtaining the unique identifier of the rate, uses the last saved index. The value is read from the table, a new one is formed, and re-entered into the table.

    uint64_t plasma_dice::next_id()
    {
        plasma::identity tmp = identity_.get_or_default();
        tmp.current_id++;
    
        identity_.set(tmp, _self);
        return tmp.current_id;
    }
    
    

    Block the required amount for potential maximum payouts. The guarantor of the fact that in case of winning the user will definitely receive a payment.

    void plasma_dice::dicepool_lock(const ion::asset& amount)
    {
        auto it = dicepool_.find(amount.symbol.raw());
        if (it == dicepool_.end())
        {
            dicepool_.emplace(_self, [&](auto& new_locked) {
                new_locked.locked = amount;
            });
        }
        else
        {
            dicepool_.modify(it, _self, [&](auto& locked){
                locked.locked += amount;
            });
        }
    }
    
    

    Unlock required amount for potential maximum payout. Needed to maintain the correctness of stock reserves.

    void plasma_dice::dicepool_unlock(const ion::asset& amount)
    {
        auto it = dicepool_.find(amount.symbol.raw());
        ion::check(it != dicepool_.end(), "fund unlock error - bet not found");
    
        dicepool_.modify(it, _self, [&](auto& locked){
            locked.locked -= amount;
           ion::check(locked.locked.amount >= 0, "fund unlock error - deposit overdraw");
        });
    }
    

    Implements the following methods within itself: 

    Allows the user to place a bet. Not a guarantor - that the rate will be accepted by the system.

    void bet(ion::name user, std::string quantity, uint8_t roll_under, ion::checksum256 seed_hash, ion::checksum256 user_seed_hash, bool paySysCms);
    

    1) Check the required permissions for the game. The smart contract asks for authorization for the user playing the game. The smart contract checks whether the user has the necessary permissions for the game in fair-dice.

    require_auth(user);
    ion::internal_use_do_not_use::require_auth2(user.value, plasma::accountPlasmaDicePermission.value);
    
    

    2) Check for validity of input parameters. The smart contract verifies the correctness of the entered amount, whether the token symbol is correctly specified, the amount format. The smart contract verifies the correctness - the numbers of the coefficient for the game, must be within the limits [2, 96]

    assert_quantity(quantity);
    assert_roll_under(roll_under, quantity);
    

    3) Obtaining a unique identifier of the rate with its subsequent verification A new identifier is generated for the bet, a check is made - is there a bet with the same identifier. Ensures the correctness of the algorithm for generating identifiers.

    uint64_t key = next_id();
    auto it = bets_.find(key);
    
    ion::check(it == bets_.end(), "duplicate bet ID");
    

    4) Acceptance of a bet - transfer of custom funds The "transfer" method is called for an arbitrary token contract (for example, EURP), the parameters of which go to the data for transfer. The name of the sender of the user, the account of the depository bank - the recipient of funds, the flag indicator of the option to pay the commission. The method requests the necessary permissions from the user for the transfer - the permission of the key "active".

    ion::action(
        ion::permission_level{ user, ion::name("active") },
        ion::name(issuer),
        ion::name("transfer"),
        std::make_tuple(user, plasma::accountPlasmaDiceBank, quantity.amount, paySysCms, std::string("bet placed"))
    ).send();
    

    5) Acceptance of a bet - calculation of the total amount The total amount of the user’s rate is adjusted - the transfer fee is deducted from it, clause 4. Based on the adjusted amount, the final calculation of the payment to the user will be made.

    auto commission = get_commission(paySysCms);
    quantity = quantity - (quantity * commission.first) / (commission.second * 100);
    

    6) Acceptance of a bet - saving data to a table / blockchain. At this stage, the smart contract creates a new unique entry - adding the rate created above to the table. The bet will be stored on the blockchain until the user plays it.

    plasma::allbets new_bet = { key, user, quantity, roll_under, seed_hash, user_seed_hash, ion::current_time_point() };
    bets_.emplace(user, [&](auto& r) {
        r.id = new_bet.id;
            r.player = new_bet.player;
            r.amount = new_bet.amount;
            r.roll_under = new_bet.roll_under;
            r.seed_hash = new_bet.seed_hash;
            r.user_seed_hash = new_bet.user_seed_hash;
            r.created_at = new_bet.created_at;
    });
    

    7) Acceptance of the bet - blocking the potential maximum payment to the user

    dicepool_lock(new_bet.amount);
    
    

    8) Formation of the check, the information message for the user. Called the method of "receipt" for the contract-assistant game, where the structure is sent, containing information about the newly created bet. The “receipt” method itself - in fact, is a mock-stub, but thanks to this call - we explicitly display all information about the created bid. Thanks to this, the user can subtract the generated unique identifier of his bet, then to use it for the game. The method requests the necessary permissions from the contract, the necessary permissions for the operation. These are the "active" permissions of the fair-dice contract itself.

    ion::action(
        ion::permission_level{ _self, ion::name("active") },
            ion::name(plasma::accountPlasmaDiceLogs),
            ion::name("receipt"),
            std::make_tuple(new_bet)
    ).send();
    
    

    Implements the following algorithms within itself: 

    It allows the user to play a bet that he (the user) made in advance. With a successful scenario, the user receives a payment to his account, or he receives nothing. Works in the following steps

    void plasma_dice::dice(ion::name user, uint64_t id, ion::checksum256 seed)
    

    1) Check the required permissions for the game. The smart contract asks for authorization for the user playing the game. The smart contract checks whether the user has the necessary permissions for the game in fair-dice.

    require_auth(user);
    ion::internal_use_do_not_use::require_auth2(user.value, plasma::accountPlasmaDicePermission.value);
    

    2) Validation Check - Bid ID The transferred bet ID is searched inside the blockchain / table, and if the system does not see it, then the bet cannot be played. The method completes its work, no payments are made. If the bid is found - we save the iterator "it", which stores all the necessary information (amount, hashes, user name, etc.).

    auto it = bets_.find(id);
    if (it == bets_.end())
    {
        printf("#info - bet not found");
            return ;
    }
    
    

    3) Random number generation and rate check. Using the hash_combine algorithm, we generate a random number, on the basis of which a decision will be made - a winning bid or not. Forms the amount of payment to the user - initially equal to 0.0 tokens. Then we compare the resulting number with the user number, and if the condition that the bet is lost is fulfilled, then we go to point "8" (below). No payment is made.

    ion::asset payout = ion::asset(0, it->amount.symbol);
    uint8_t random_roll = compute_random_roll(seed, it->user_seed_hash);
    if (random_roll < it->roll_under)
    
    

    4) Winning bet - payout On the basis of the bet made by the user, and the number-coefficient indicated by him, the preliminary payment is calculated. The formula for which the payment is considered is implemented in the business logic of the contract and is indicated above.

    payout = compute_payout(it->roll_under, it->amount);
    
    

    5) Winning bid - calculation of the commission to win On the payment generated above - the commission on a prize is considered. The smart contract has a constant commission of 0.2 percent of the winning amount. This commission is removed from the total payment amount to the user.

    ion::asset commission = compute_comission(payout);
    
    

    6) Winning Rate - Commission Processing Calculated above commission - sent to a special system account of the game. To do this, call the "transfer" method of the accountIonToken contract, in the parameters of which the data for transfer is transferred. We specify the account - the accepting payment (commission), we specify the sender - the stock account storage, which is used to pay rates. It is worth noting that in this case the transfer method is “free”, and we do not charge a commission for transferring commission. The method requests the necessary user permissions for the operation. We need to have "active" permission for the account accountPlasmaDiceBank to be able to transfer funds from the depository bank.

    ion::action(
        ion::permission_level{ plasma::accountPlasmaDiceBank, ion::name("active") },
        plasma::accountIonToken,
        ion::name("transfer"),
        std::make_tuple(plasma::accountPlasmaDiceBank, plasma::accountPlasmaDice, commission, commission_memo(*it))
    ).send();
    

    7) Winning bet - user payout The generated payment to the user minus the above commission is sent using the "transfer" method. To do this, use the marker smart contract method, for example, USDP It is worth noting that this method is not “free”, and the user pays a transfer fee. In the parameters of the method, we specify the sender account, this is our stock bank-storage, the recipient account is the user himself, and the payment amount. The method requests the necessary user permissions for the operation. We need to have an "active" perm for the accountPlasmaDiceBank account in order to be able to send funds from the vault bank.

    ion::action(
        ion::permission_level{ plasma::accountPlasmaDiceBank, ion::name("active") },
        ion::name(issuer),
        ion::name("transfer"),
        std::make_tuple(plasma::accountPlasmaDiceBank, user, payout.amount - commission.amount, bool(false), winner_memo(*it))
    ).send();
    

    8) Deposit unblocking - unlocking potential maximum payout to the user Since the bet is played, and all payments, in the case of a successful game, were made, we will unlock the amount previously blocked. From now on, our vault bank considers this bet played, and its reserve fund is increased by the amount of the potential maximum payout of the bet played.

    dicepool_unlock(it->amount);
    

    9) Formation of the check, the information message for the user. The method "prize" is called for the contract-assistant of the game, where the structure containing the information about the just played bet is sent. The “prize” method itself, similar to the “receipt” method, is a mock-stub, but thanks to this call, we explicitly display all information about the bet played. The user can visually see - the parameters of the bet played, a randomly generated number and his payment. In case of loss - the payment will be zero, but the format of the information message will not change.

    
    ion::action(
        ion::permission_level{ _self, ion::name("active") },
            ion::name(plasma::accountPlasmaDiceLogs),
            ion::name("receipt"),
            std::make_tuple(new_bet)
    ).send();
    

    10) Rate deletion - deletion of data from the table / blockchain. At this stage, the smart contract removes the newly played bet from the active bet table. This bet is no longer considered relevant and unique.

    bets_.erase(it);
    

    Additional Resources

    See these additional resources for more information about authorization, API resources and order actions.

    Support

    For API feature requests, bug reports and other questions related to API guides, contact [email protected].