Skip to content
On this page

What is an entry point?

Smart contracts perform actions given specific user inputs. These inputs are known as an entry point. In other words, entry points are how all interactions with the blockchain begin and end.

How does data flow once received through an entry point?

When a user interacts with the specific entry point, the information is processed by the smart contract's logic and a response is provided. Our smart contract may consist of several different layers of code during development and may even be written in several languages. To manage the flow of information, we serialize the data using protobufs in a .proto file.

Because every interaction with a smart contract involves entry points, it is convenient to begin developing our entry points first.

Determining our contract entry points

Here is the portion of the code we will review with our entry points:

syntax = "proto3";

package gamestats;

import "koinos/options.proto"; 

message empty_message {}

// @description Initialize the contract
// @read-only false
// @result empty_message
message initialize_arguments {
   bytes rewards_token_address = 1 [(koinos.btype) = CONTRACT_ID];
}

// @description Submit game stats
// @read-only false
// @result empty_message
message submit_game_stats_arguments {
   game_stats_object game_stats = 1;
}

// @description Return a player's info
// @read-only true
// @result player_object
message get_player_info_arguments {
   bytes player = 1 [(koinos.btype) = ADDRESS];
}

// @description Return the leaderboard
// @read-only true
message get_leaderboard_arguments {
   leaderboard_key offset_key = 1;
   uint64 limit = 2 [jstype = JS_STRING];
   bool least_to_most_wins = 3;
}

message get_leaderboard_result {
   repeated leaderboard_key leaderboard = 1;
}

// @description Return the past games stats
// @read-only true
message get_games_stats_arguments {
   game_stats_key offset_key = 1;
   uint64 limit = 2 [jstype = JS_STRING];
   bool oldest_to_newest = 3;
}

message get_games_stats_result {
   repeated game_stats_object games_stats = 1;
}

// objects
message metadata_object {
   bool initialized = 1;
   bytes rewards_token_address = 2 [(koinos.btype) = ADDRESS];
   uint64 last_game_id = 3;
}

message game_stats_key {
   uint64 game_id = 1;
}

message game_stats_object {
   uint64 game_id = 1 [jstype = JS_STRING];
   uint64 timestamp = 2 [jstype = JS_STRING];
   uint64 rewards = 3 [jstype = JS_STRING];
   bytes winner = 4 [(koinos.btype) = ADDRESS];
}

message leaderboard_key {
   uint32 wins = 1;
   bytes player = 2;
}

message player_object {
   uint32 wins = 1;
}

// events
  1. message empty_message {} - This is a special type of entry point that you will see often. Protobufs allow us to use an entry point as a response or argument to another entry point therefore, if there is no error during the interaction, providing an empty message is sufficient. The usage is as follows:

  2. message initialize_arguments {}- This is a common entry point for contracts that require some initial data to begin working. In our dApp, when the gamestat contract is initialized we must provide the smart contract with the wallet address of our reward token, which will be uploaded as an independent smart contract. This is done because we don't know to which address the reward token contract will be uploaded to.

  3. message submit_game_stats_arguments {} - This entry point is used to store data. The required argument is the game_stats_object which contains whatever gamestats we want to include. We can make as many required arguments as we wish but only need 1 for our dApp.

  4. message get_player_info_arguments {} - Since we are creating a leaderboard, we need to store the player addresses over many games. The required argument is the player's address.

  5. message get_leaderboard_arguments {} and message get_leaderboard_result {}- The get_leaderboard_arguments entry point is used to retrieve information to generate a leaderboard. Instead of expecting an empty_message as a response, we created get_leaderboard_result that provides the leaderboard_key. Note, that the values 1, 2, and 3 are the serial numbers relating to google protobufs and not a value.

  6. message get_games_stats_arguments{} and message get_games_stats_result {} - This entry point is used to pull records of previous game statistics so they can be updated during the next game. Instead of empty_message, the response is get_games_stats_results. This entry point is useful for the front end of our dApp.

  7. metadata_object- stores basic information relating to the dApp such as the initialization state (true/false), the address of the reward token from the initialize entry point, and the id of the previous game.

  8. game_stats_key - stores the current game id.

  9. game_stats_object - Stores the game_id, the time of the game, how many tokens are to be rewarded, and who the winner is.

  10. leaderboard_key - Stores the winning player's wallet address and how many times they've won.

  11. player_object - Stores the winning player.

Note: Objects should be defined in the .proto file so they can be used by the business logic.

GENERATE THE BOILERPLATE

Once our game-stat entry points are defined in our .porto file, we'll run yarn build:release and generate the Gamestats.boilerplate.ts file from the /assembly directory which incorporates all of our entry points. We'll copy the boilerplate and rename it Gamestats.ts and develop our code on this new file.

Our boilerplate will look as follows:

import { System, Protobuf, authority } from "@koinos/sdk-as";
import { gamestats } from "./proto/gamestats";

export class Gamestats {
  initialize(args: gamestats.initialize_arguments): gamestats.empty_message {
    // const rewards_token_address = args.rewards_token_address;

    // YOUR CODE HERE

    const res = new gamestats.empty_message();

    return res;
  }

  submit_game_stats(
    args: gamestats.submit_game_stats_arguments
  ): gamestats.empty_message {
    // const game_stats = args.game_stats;

    // YOUR CODE HERE

    const res = new gamestats.empty_message();

    return res;
  }

  get_player_info(
    args: gamestats.get_player_info_arguments
  ): gamestats.player_object {
    // const player = args.player;

    // YOUR CODE HERE

    const res = new gamestats.player_object();
    // res.wins = ;

    return res;
  }

  get_leaderboard(
    args: gamestats.get_leaderboard_arguments
  ): gamestats.get_leaderboard_result {
    // const offset_key = args.offset_key;
    // const limit = args.limit;
    // const least_to_most_wins = args.least_to_most_wins;

    // YOUR CODE HERE

    const res = new gamestats.get_leaderboard_result();
    // res.leaderboard = ;

    return res;
  }

  get_games_stats(
    args: gamestats.get_games_stats_arguments
  ): gamestats.get_games_stats_result {
    // const offset_key = args.offset_key;
    // const limit = args.limit;
    // const oldest_to_newest = args.oldest_to_newest;

    // YOUR CODE HERE

    const res = new gamestats.get_games_stats_result();
    // res.games_stats = ;

    return res;
  }
}

CLONE THE BOILERPLATE FILE

To prepare for the second part of this guide, copy the Gamestats.boilerplate.ts and rename it Gamestats.ts. This file is where the bulk of our contract code will reside.

PRO TIP! During your development process, you may find that you must add new entry points to your .proto file. If you do this, regenerate the boilerplate by running yarn build:release and copy the new entry points back into your production file.