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
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: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.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.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.message get_leaderboard_arguments {}
andmessage get_leaderboard_result {}
- Theget_leaderboard_arguments
entry point is used to retrieve information to generate a leaderboard. Instead of expecting anempty_message
as a response, we createdget_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.message get_games_stats_arguments{}
andmessage 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 ofempty_message
, the response isget_games_stats_results
. This entry point is useful for the front end of our dApp.metadata_object
- stores basic information relating to the dApp such as the initialization state (true/false), the address of the reward token from theinitialize
entry point, and the id of the previous game.game_stats_key
- stores the current game id.game_stats_object
- Stores the game_id, the time of the game, how many tokens are to be rewarded, and who the winner is.leaderboard_key
- Stores the winning player's wallet address and how many times they've won.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.