By: Sean Sing and Robbie Kruszynski
Edited by: Tom Hay, Chris Anatalio and Clarissa Watson
Building Ethereum Enabled Snake
Proof-of-Snake is based on the classic snake game with a twist; the high scorer earns Ether (ETH) from every attempt to beat the high score. In addition, a NFT is rewarded whenever a player beats the current high score.
This rewards system is enabled by tracking the high score and token ownership with smart contracts that run on Ethereum. The Proof-of-Snake project currently lives on the Rinkeby testnet. You will need to have ETH on the Rinkeby network to play. You can request test ETH from this Rinkeby faucet for free.
Proof-of-Snake was the final project submission for Sean Sing from the 2020 ConsenSys Developer Bootcamp. As Sean shares:
“The idea came to me when my internet connection was disrupted and I spent some time playing the Chrome browser’s Dinosaur Game. I had made it quite far into the game by my fifth attempt; and when my T-Rex avatar crashed into a pterodactyl, I started to wonder how someone could be officially recognized as the top scorer.
If the Dinosaur Game was hooked up to a smart contract, then the high scores could be official, accessible globally and its legacy would live on virtually, forever. This goal became the inspiration for the Proof-of-Snake smart contract. I also added token and game mechanics to incentivize and motivate players to become the top player of Proof-of-Snake!”
Understanding the Game Dynamics: How Does Proof-of-Snake Work?
Every time a player pays the fee to play the game, the fee is split between the game creator and the current high scorer.
The smart contract has a mapping that tracks the balance of the current high scorer. This is represented as the “Proof-of-Snake Pot”.
Players who beat the high score are also awarded a “Proof-of-Snake” NFT which can be displayed, sold or redeemed for rewards.
Smart Contract Walkthrough
Check out the smart contract repository to follow along.
Note: Please keep in mind that this repository was intended as a proof of concept and does not represent a secure, audited smart contract ready for mainnet. This article is part of a series which will identify the security vulnerabilities present in this smart contract and how to remediate those vulnerabilities in future articles.
To elevate this smart contract to production–level code, we would have to update the code to the latest version of Solidity and standards, perform a peer review, add automated testing, perform static analysis using an automated service such as Diligence and also have the contract formally audited by a respected industry auditor.
This article is meant to highlight a developer’s journey as a student from Consensys Academy, through the steps required to be able to release secure, mainnet-ready code.
Solidity Compiler Version Declaration and Library Import Statements
The contact is initialized with the name “ProofOfSnake” and inherits the imported Open Zeppelin ERC721 contract.
The contract then declares a few key variables:.
- Two uint256 variables:
- The highScore
- The gameFee.
- The addresses for the current high scorer: currentLeader
- The address of the owner: owner
- A mapping with an address key and uint value pair called potBalance to track the earnings of the current high scorer and past winners
- A boolean called stopped initially set to false
Next, let’s define a few modifiers which are reusable functions:
- The onlyOwner modifier incorporates the access restriction design pattern where only the contract’s owner is allowed access to certain functions
- The stopInEmergency and onlyInEmergency modifiers check the “stopped” variable. If true, the function’s downstream logic will fail to execute.
This approach is part of the circuit breaker design pattern for smart contract safety. The emergency function swaps the value of the “stopped” boolean. Only the owner can call this function. If a bug was found, the contract owner could stop players from continuing to pay the game fee until an updated, fixed version of the contract is deployed.
The constructor function is executed during the deployment of the smart contract:
- “owner” and “currentLeader” are set to the contract owner’s address,
- The mapping to “potBalance” is set to 0
- Initial “highScore” is set to 2
The contract inherits from Open Zeppelin’s ERC721 contract, so the arguments for the ERC721’s contract’s construction functions are required (the token’s name and symbol).
The smart contract has three main functions.
- public and payable
- Receives and transfers the game fee in Ether
- Adopts the “stopInEmergency” modifier
- Declares a require statement which checks if the “msg.value” (the amount of ETH sent) meets the gameFee that was set. If the amount sent was less than gameFee, the transaction reverts
- Player’s potBalance mapping to 0
- The contract owner and currentLeader’s balances in the “potBalance” is increased by half the amount of the “msg.value” received
- Accepts a uint256 value, the verified score(validated by the front-end)
- A require statement checks that the potBalance > 0
- Ensures that the player has at least paid the fee to play the game once, otherwise it reverts the transaction with an error.
- If the checks pass:
- Player has played Proof-of-Snake before and has achieved a new high score
- The “currentLeader” address is updated to “msg.sender” (the dApp initiates a transaction that the player signs)
- The “highScore” is set to the _score argument received from the front-end
- Then increases the “totalSupply” of this token by 1 and mints a Proof-of-Snake High Scorer NFT with the player’s address. The high score could also be added as an input.
- Previous high scorers of Proof-of-Snake can call this function to withdraw their earnings
- High scorers can only withdraw their earnings after they have been dethroned
- A require statement checks that the “msg.sender” is not the “currentLeader” and has a “potBalance” of greater than 0 Eth.
- If the checks fails, the transaction is reverted with an error message
- If the checks pass, the function sets msg.sender’s potBalance to 0 and transfers the balance to “msg.sender”
Notice anything wrong?
The code first wipes out the sender's balance and then transfers them 0. The code should use a temporary variable to store the value, instead of operating directly on the source maps.
The code should store the value in a temporary variable before resetting the pot to 0 and transferring the balance. You can look more into this common pattern in the Solidity documentation.
Now, let’s deploy the smart contract using Infura!
Note: We won’t be covering automated testing of the smart contract in this article. Check out the Truffle Suite to learn more about testing your smart contracts prior to deployment. We will also cover implementing testing in later articles in this series - follow our blog to stay updated!
Deploying our Smart Contract
Since we are using public nodes, we will need to sign our transactions locally. The declaration of the “HDWalletProvider” variable at the top of our “truffle-config.js” file allows us to do this.
Note: This declaration is possible post-install with the following command
$: npm install @truffle/hdwallet-provider
Check out the HDWalletProvider NPM repo for more information.
We now have the ability to sign transactions.
Generating your own Mnemonic
Note: HDwalletProvider will automatically use the address of the first address that's generated from the mnemonic. If you pass in a specific index, it'll use that address instead; otherwise, you could supply your own mnemonic. An example of this below:
In your terminal, enter:
Note: If your network is not correctly configured between Ganache and your truffle-config.js file, this command will fail.
Next, update the following lines to create your wallet:
Enter your generated mnemonic:
Define your wallet provider:
Now, when you execute the terminal command:
You’ll see a list of accounts/addresses:
By default, the first address in the array will be the account you need to fund with test ETH, in order to successfully deploy.
Using your Generated Mnemonic
We will now take the same mnemonic used and create a variable adding our mnemonic within the string.
Note: For the sake of this project example, we are placing our mnemonic and projectID inside of the truffle.config file. In practice, you want to make sure you abstract this information away and use an .env file or, if preferred, a json file that is added to the .gitignore file.
You do not want to leak this information in your public Github repository.
Infura Project Set-Up
Next, let’s configure the provider to connect to the test network, by using your Infura project ID. Please review the Infura Getting Started docs for details on how to get this.
Now, in our “truffle-config.js” file, we will declare which testnet we are using.
We are using the Rinkeby testnet. Ensure that we are pulling in our declared HDWalletProvider. (The HDWalletProvider is a wrapped instance in a functional closure to ensure that only one network is connected.)
Deploy to Rinkeby
Now, let’s deploy our project to the testnet.
Upon successful deployment to Rinkeby, we can now integrate our smart contract with our front-end to build out a full-stack dApp.
This contract is currently deployed here:
The full dApp is available at: https://proof-of-snake.vercel.app/
You may notice that the high score has been set to 8,888,888, which indicates that the deployed contract on testnet has been exploited. We will walk through how this contract was exploited and how to prevent this, in future articles in this series. Thanks to Larry Yang for identifying this exploit.
Reference this transaction to see how the exploit occurred: https://rinkeby.etherscan.io/tx/0x6bea63a806ccdb4cf9a689e841da19bfdee902b26f2741fceee120bea4e0352a
Inspired to join the revolutionary world of blockchain? Check out https://consensys.net/careers/ today!
Want to get started on your journey as a Web3 developer? https://infura.io/register
Read more stories like this at: https://blog.infura.io/
Stay up to date with Infura with: The Infura Monthly Newsletter
About the Author
The decentralized application (dApp) Cryptokitties got me wondering how smart contracts were built, which then led to my discovery of Solidity. I also found out that ConsenSys, the company behind the widely used tools for smart contract development, also had a developer bootcamp!
I signed up for the ConsenSys Developer Bootcamp in 2020 and to this day I am still blown away by the amount of value that I gained from it. The course materials were rich and dove deep into many technical aspects of Ethereum. Explanations for key concepts and complex jargon were easy to digest. There were endless links to the articles, research papers, Medium posts, YouTube talks and sample dApps that helped solidify my understanding.
The hidden gem in the program was the heartfelt support from the mentors and leaders of the bootcamp who dedicated their time to office hours and Zoom calls for discussions that ranged from Uniswap walkthroughs, advanced topics like major DeFi hacks, technical guidance, brainstorming and pair programming. It was definitely an exciting, memorable and rewarding three months!
After bootcamp, I joined three hackathons back to back, the Finastra’s Hack to the Future 2020, EthDenver 2021 and the Solana x Serum DeFi Hackathon.
The nerve-wrecking process of job applications resulted in three out of the five applications leading to interview requests. These lead to an offer within a month or two. It was surreal and many friends and family asked how I was able to make a career transition from a traditional engineering role to a blockchain developer.
The blockchain community has been very welcoming and everyone shares a common goal to help this technology evolve and advance. Organic passion and grit is highly valued. Not once was my lack of formal computer science education questioned and everyone was genuinely curious and excited to hear about my journey of how I stumbled upon blockchain and why I dedicated the time and effort to learn the technical skill sets required. It was evident that to them, the technical skills required could be taught and polished so long as the candidate possessed genuine curiosity and the tenacity to learn and grow.
I hope my journey serves as an inspiration for others. I encourage anyone who is interested about blockchain to take that first step while it is still in its infancy. I cannot wait for the day that blockchain changes the world and look forward to working with many other like-minded and talented folks. Connect with Sean Sing.
Special note: Since my participation in the 2020 Bootcamp, ConsenSys has taken the initiative to launch Basic Training based on feedback from past graduates. It’s a vital resource for students to become better prepared for the Bootcamp and also is a key reference to help them complete their assignments and projects.