Recipe ID: hsts-r44
We offer blockchain introduction, Hyperledger for system admin, Ethereum, Solidity, Corda R3, Hyperledger for developers, blockchain cybersecurity and more classes in self-paced video format starting at $60. Click here to learn more and register. For complete self-paced blockchain training, visit our Complete Blockchain Development Training page.
The latest version of this tutorial along with all updated source codes is available via the below link:
https://myhsts.org/blog/ethereum-dapp-with-evm-remix-golang-truffle-and-solidity-part2.html
Ethereum is a general-purpose blockchain that is more suited to describing business logic, through advanced scripts, also known as smart contracts. Ethereum was designed with a broader vision, as a decentralized or world computer that attempts to marry the power of the blockchain, as a trust machine, with a Turing-complete contract engine. Although Ethereum borrows many ideas that were initially introduced by bitcoin, there are many divergences between the two.
The Ethereum virtual machine and smart contracts are key elements of Ethereum, and constitute its main attraction. In Ethereum, smart contracts represent a piece of code written in a high-level language (Solidity, LLL, Viper) and stored as bytecode in the blockchain, in order to run reliably in a stack-based virtual machine (Ethereum Virtual Machine), in each node, once invoked. The interactions with smart contract functions happen through transactions on the blockchain network, with their payloads being executed in the Ethereum virtual machine, and the shared blockchain state being updated accordingly.
For those who are not familiar with blockchain technology reading History and Evolution of Blockchain Technology from Bitcoin article is strongly recommended. Also, if you wish to learn and practice Hyperledger blockchain development, visit Comprehensive Hyperledger Training Tutorials page to get the outline of our Hyperledger tutorial articles.
We have written two sets of tutorials to explore Ethereum and Solidity programming in depth. First set covered the following nine recipes:
In short, you learned about how to set up and configure Ethereum and develop blockchain applications using Solidity programming language. We explored its key components, including smart contracts and Web3.JS API via an Auction Decentralized Application (DApp) step-by-step.
In second set, we discuss more advance topics in Ethereum blockchain development and solidity while building a Tontine DApp game step-by-step. Specifically, we cover Truffle and Drizzle. For instance, we show you how a tool such as Truffle can be an assistant in building, testing, debugging, and deploying DApps. In summary, we are going to cover four main topics:
The 2nd set consists of 8 recipes as follows:
IMPORTANT: Understanding and completing the first set of recipes are required prior to working on second set of recipes.
In our first round of recipes, we learned a lot about the Ethereum ecosystem, but we are yet to realize the full potential of its different components. More precisely, we explored how Ethereum works, what a decentralized application (DApp) is and how to build one, and also covered the key concepts of Solidity and web3.js. We then introduced some of the most common smart contract design patterns (withdrawal from a contract, restricting access, state machines), before ending with a discussion of a contract’s cost optimization.
To brush up your knowledge and skills, on second round of recipes, we are going to build a Tontine DApp game. We will exploit this example to explore new tools that are going to change the way you build DApps, and introduce new Solidity features.
In this walkthrough, we will discover how a tool such as Truffle can aid in building, testing, debugging, and deploying our DApp.
In Ethereum, contracts communicate between each other using message calls. Practically, a smart contract is able to communicate with another one that it deploys or with an instance of an existing contract. In Solidity, for both cases, we can declare a Cplayer instance as follows: Cplayer Tplayer. Let's discuss how contracts interact between Ethereum and Solidity.
To instantiate an existing contract, we need to know its address. For example, in our contract, we will instantiate the Cplayer contract in the Ctontine constructor as follows:
constructor(address _CplayerAddress) public { Tplayer = Cplayer(_CplayerAddress);
}
The Cplayer(_CplayerAddress); expression will perform an explicit conversion of the Cplayer type, stating that "we know that the contract at the given address of _CplayerAddress is of the Cplayer type." It's worth noting that instantiating a contract using its address doesn’t imply the execution of its constructor.
The interaction with a deployed contract is a very powerful feature as it allows code reusability, since deployed contracts can be used like libraries. Furthermore, if we implement a pattern that enables us to change the used contract, instantiation can help us avoid reusing faulty contracts by changing the contract address.
The second option is to deploy a new instance of a given contract. To achieve that, Solidity provides the new keyword, which can be used as follows:
contract ContractA { uint256 x;
function ContractA (uint256 y) payable { x = y;
}
}
contract ContractB {
ContractA CAinstance = new ContractA(10);
}
As you can see, we can pass arguments when we create a contract, since the created contract has a constructor that accepts arguments. The new ContractA(arg) line will execute the created contract's constructor and return its address. Note that an instance of each smart contract is implicitly convertible to its address, and in Solidity we are not dealing with real objects as in OOP to represent contracts as we do for classes.
Moreover, you can send ether from your source contract to the newly created contract using the .value() option, as follows:
ContractA CAinstance = (new ContractA).value(amount)(arg);
Once the instance is created, the caller contract can pass messages to call remote methods or to read or edit remote states. Let’s see how to perform such calls.
In our project, we have a Ctontine contract that instantiates a Cplayer object. As we presented in the game design, the player should sign up using the Cplayer contract, and then join the game using the Ctontine contract. This implies that the Cplayer contract manages the player details and that Ctontine needs to access them.
In Ethereum, each contract has its own storage space, which can’t be accessed directly or overridden by another contract. Given the ContractA and ContractB contracts, the former can only access the storage of the latter by invoking a method (getter) that returns data from the storage of ContractB.
You might be wondering, "What if we declare states variables as public? Wouldn’t they be accessible?" It's not important whether the variable is public or not, as the public specifier provides a free getter function within the contract scope, and not external access.
Back in our contract code, if we try to read the admin variable in the Ctontine contract from the Cplayer contract directly – Address Cplayer_admin = Tplayer.admin; – we’ll get the following message error, even if admin is declared as public:
So how do we solve this?
In the target contract, we should have a getter that reads for us and returns the intended value.
As was the case for reading contract storage, you cannot modify states of another contract without defining a setter function.
Let’s have a look at the following example:
contract ContractA { uint256 public state;
function setstate(uint _value) { state = _value;
}
}
contract ContractB{
ContractA public OneInstance = new ContractA(); function getstateA() public {
OneInstance.state = 12; OneInstance.setstate(12);
}
}
The OneInstance.state = 12; line in the getstateA method will raise an error. We need instead to call the setstate() setter to update the state value.
The need for getters (read) and setters (update) in intra-contract interactions demonstrates the importance of the CRUD pattern.
Let’s get back to our game code. So far, in our Ctontine contract, we have declared the required states and our constructor is already defined. All we are missing is implementing the Ctontine contract methods.
In order to start the game, a player has to call the join() method while sending more than one ether to the Tontine contract. Therefore, in the Ctontine contract, we need to implement this function as follows:
function join() public payable returns(bool) { require(Tplayer.exist(msg.sender), "player doesn't exist"); require(msg.value >= 1 ether && Tpension[msg.sender] == 0, "send higher
pension");
Tpension[msg.sender] = msg.value; Tplayer.EditPlayer(msg.sender, active_players.length); active_players.push(Tplayer.getplayer(msg.sender)); Lindex += (active_players.length - 1); ping_time[msg.sender] = now;
emit NewActivePlayerEv(msg.sender, now); return true;
}
Let’s go over this implementation, line by line.
First of all, the join() function is marked as payable to allow the contract to accept ether via standard transactions. It is also defined as external, specifying that the method has to be called from other contracts or via transactions. We made this choice following the game design and because an external function is cheaper than a normal public function.
The join() method implements the game rules. Thus, a player should be able to join the game if the following requirements are met:
If they fulfill the conditions, we save the supplied ether in the Tpension array.
The Tplayer.EditPlayer(msg.sender, active_player.length); line edits the value of the id attribute for the given player. We use this element to keep track of the index of each player in the active player array. That will help us to know which case to delete if we want to remove this player.
Then the active_player.push() function is used to add a new player into the
active_player array.
We use a Lindex global state, which sums up the index values of the active players. Why? Because it will help us to know the last surviving player’s index in the active players array.
The ping_time[msg.sender] = now; line initially stores the time when the players join the game as their first ping time.
At the end of the function, we emit an ewActivePlayerEv(msg.sender,now) event to announce the signing of a new player.
So far, within this function, we have introduced a lot of new things related to intra-contract interaction. Let's again take a pause in writing the Ctontine contract and learn some new concepts. You can skip the next two sections if you want to concentrate on building the game.
In Solidity, you can call functions either internally or externally. Only the functions of the same contract can be called internally, whereas external functions are called by other contracts.
If you were to call a function from another contract, the EVM uses the CALL instruction, which switches the context, making its state variables inaccessible. In the following sections, we will discover two ways to call a function from another contract.
As we did in the Ctontine contract, the regular way to interact with other contracts is to call (invoke) a function on a contract object (we borrow here the OOP terminology). For example, we can call a remote function from a remote contract, ContractA , as follows: ContractA.Remotefunction(arguments).
But to be able to make the invocation this way, we need to define (in the same Solidity file) an abstract form of ContractA. For instance, if we have a contract, ContractA, then the code is as follows:
contract ContractA {
function f(uint256 a, string s) payable returns (bool) {
//your code here return true;
}
}
If this contract is deployed in the blockchain, let’s say under the 0x123456 address, and we want to call the f() function within a caller contract, ContractB, then we have to include the abstract form of ContractA in the ContractB file, then instantiate ContractA and execute f(). Here is how the ContractB contract file will look:
contract ContractA {
function f(uint256 a, string s) payable returns (bool); function h() payable returns (uint);
}
contract ContractB{
address ContractAaddress = 0x123456;
ContractA ContractAInstance = ContractA(ContractAaddress);
function g() returns (bool){
return ContractAInstance.f(10, "hello");
}
}
If we invoke a payable function, we can specify the number of wei, as well as limit the amount of gas available to the invocation using the .gas() and .value() special options, respectively: ContractAInstance.h.value(10).gas(800)();.
The parentheses at the end serve to receive the arguments needed to perform the call. If the function doesn’t accept any arguments, we keep them empty.
When the called contract doesn't adhere to the ABI, we can't just use ContractAInstance.f(); or ContractA ContractAInstance = ContractA(0x123456) to define a new instance.
In this case, we have to use the special low-level call function, using the following call structure:
contract_address.call(bytes4(sha3("function_name(arguments types)")), parameters_values)
In the previous example, we could call f() with two arguments using ContractAaddress.call(bytes4(keccak256("f(uint256,string)")), 10, ”hello”);.
This is a sort of tedious manual construction of the ABI function signature. However, things are getting better with newer Solidity. Since release 0.4.22, the abi.encode(), abi.encodePacked(), abi.encodeWithSelector(), and abi.encodeWithSignature() global functions have been defined to encode structured data, therefore helping us to build a valid call, as follows:
OneInstance.call(abi.encodeWithSignature("function_name(arguments types)")),parameters_values)
call can be used along with the .gas() and value() methods to adjust the supplied gas and value in the call.
call returns true if the called function executes without exception. Also, it will fire an exception if the call contract does not exist, or if it throws an exception or runs out of gas. If we apply call to a contract without specifying a function name, its fallback will be executed (maybe that's why the fallback function doesn’t have a name).
For security reasons, there is a capped stipend of 2,300 gas that applies to internal sends (using the transfer() or send() methods) from one smart contract to another. Therefore, the triggered fallback has a limited amount of gas to operate. However, the call function doesn’t have a similar limitation, which represents a risk for your contract. In order to define the same security measure, you have to set gas to 0 in your calls: contract_address.call.gas(0).value(xyz).
Mind you, the call method or other low-level functions should be used with care as they may cause some security issues if we deal with a malicious contract.
After this long introduction about contract remote calls, let’s get back to our Ctontine contract.
After implementing the join() function, it’s time to define the ping() function, which will allow the player to update their activity, as follows:
function ping() external returns(bool) { ping_time[msg.sender] = now;
return true;
}
There's nothing complex about this code. Each time ping() is called, we will store the current time returned by now as the new ping time. Players will not literally ping the contract as we do in a network, they will only send a transaction invoking the ping method to prove their activity.
The game logic enables a player to eliminate another unlucky opponent by triggering the
eliminate() method, which we define as follows:
function eliminate(address PlayerAddress) external returns(bool) { require(now > ping_time[PlayerAddress] + 1 days);
delete Tpension[PlayerAddress];
delete active_players[Tplayer.getplayer(PlayerAddress).id]; Lindex -= Tplayer.getplayer(PlayerAddress).id; eliminated_players.push(Tplayer.getplayer(PlayerAddress)); Tplayer.EditPlayer(msg.sender, 0); share_pension(PlayerAddress);
emit eliminatedPlayerEv(PlayerAddress); return true;
}
Take a close look, and I’m sure you’ll make sense of this code.
The require(now > ping_time[PlayerAddress] + 1 days); line ensures that we can eliminate only players who didn’t ping the contract within the last 24 hours.
This function will remove the eliminated player from the active player list (active_players) and move it to the eliminated player list (eliminated_players.push). Then we set the player's ID to zero as it has been removed from the active player list. Afterward, we call share_pension(), which will share the balance of the eliminated player between the remaining active players. We end by firing an event, declaring the elimination of the player.
As you saw, in the previous function we called share_pension() to share the eliminated player’s deposit. Here’s its implementation:
function share_pension(address user) internal returns (bool) { uint256 remainingPlayers = remaining_players(); for(uint256 i = 0; i < active_players.length; i++){
if (active_players[i].Paddress != 0x00)
Tpension[active_players[i].Paddress] = Tpension[user] / remaining_players;
}
return true;
}
function remaining_players() public view returns (uint256) { return (active_players.length-eliminated_players.length);
}
}
We declare this function internal as it’s intended to be used only within the contract scope. As we can see, share_pension() shares the eliminated players’ balances between the remaining active players. This function has to allocate the Tpension[user]/remaining_players quotient to each active player. However, we are facing a problem here! Have you spotted it?
Handling the division in the previous scenario is just the same as working with integer divisions, as Ethereum doesn’t adopt floating point numbers. The division would result in a floor of the calculation with the remainder discarded. For example, the division 17/3 equals 5, with the remainder of 2 discarded. To fix this, we create a new method that does the following:
function calcul(uint a, uint b, uint precision) public pure returns (uint)
{
require(b != 0);
return a * (10 ** (precision)) / b;
}
Note that the double asterisk, **, is a Solidity operator representing the exponentiation operation. In our example, 10 is the base whereas precision is the exponent.
If we divide 17 by 3 using the calcul() function, and we call the function with a precision of 5 (the number of digits after the decimal point), it will output 566,666, which can be displayed to the player as 5.66666. In this way, we can produce a float using integer division, though it still requires that in the frontend you divide the result by the equivalent value of (10 ** (precision)) to display the floating-point number.
Therefore, in the share_pension() function, we substitute the quotient by performing the following:
Tpension[active_players[i].Paddress] = calcul(Tpension[user], remainingPlayers, 18);
At the final stage of the game, the last active player can claim their reward by calling
claimReward(), which we define as the following:
function claimReward() external returns (bool) { require(remaining_players() == 1); active_players[Lindex].Paddress.transfer(address(this).balance); return true;
}
I’m sure the this keyword has caught your attention more than the rest of the code. So what is it?
The this keyword in Solidity is pretty similar to this in OOP, and represents a pointer to the current contract, which is explicitly convertible to an address. Moreover, all contracts inherit the members of address, thus it is possible to query the balance of the current contract using address(this).balance (you'll find this.balance in old code).
As this returns the address of the current contract, we can use it as follows: address ContractAddress = this;.
this is also used to access internally a method declared as external, otherwise the compiler won’t recognize it. In that case, the function will be called through a call message instead of being called directly via jumps.
Great, at this level, we’re nearly done with the game contracts. However, the code should be tested before we start working on the user interface side in our next recipe. I hope you didn’t forget how to use Truffle.
To conclude this recipe, we like to recommend our Learn Hands-on Blockchain Ethereum Development & Get Certified in 30 Hrs, and Become Blockchain Certified Security Architect in 30 hours courses to those interested in pursuing a blockchain development career. This recipe is written by Brian Wu who is our senior Blockchain instructor in Washington DC. His Blockchain By Example book is highly recommended for learning more about blockchain development.
Hands-on Node.JS, MongoDB and Express.js Training
Advance JavaScript, jQuery Using JSON and Ajax
Learn Hands-on Blockchain Ethereum Development & Get Certified in 30 Hrs
Learn Blockchain Hyperledger Development & Get Certified in 30 Hrs
Become Blockchain Certified Security Architect in 30 hours
Blockchain Certified Solution Architect in 30 hours
Introduction to Python Programming
Object Oriented Programming with UML
We offer private coding classes for beginners online and offline (at our Virginia site) with custom curriculum for most of our classes for $59 per hour online or $95 per hour in virginia. Give us a call or submit our Private Coding Classes for Beginners form to discuss your needs.