Security Skill Level: Beginner

Introduction
Leaderboards are a core part of many blockchain games, but poorly designed leaderboard contracts can introduce vulnerabilities. The most critical concept when creating smart contracts for any chain is
- What should the smart contract do?
- How should the smart contract do it?
- Write in a programming language.
This tutorial will show how to secure a leaderboard smart contract on the EVM using the Solidity programming language. We have addressed that DoS attacks are prevalent in web2 and web3 games. We’ll start with a vulnerable example as intuition and progress to more secure implementation.
The Design Phase
Many bugs can be found in the design phase of development — however, often. Developers want to rush into the coding phase without reviewing the program's needs and dissecting the purpose of the smart contract.
What Should the Smart Contract Do?
This question defines the purpose and functionality of the smart contract. It focuses on identifying the business logic and requirements the contract must fulfil. For example:
- In a Leaderboard Contract: The smart contract should be in this context:
- Allow users to compete by staking tokens.
- Track and display the highest staker as the leader.
- Handle refunds for previous leaders.
- Enable reward distribution for the top leader.
Key Considerations:
- Clarity: Clearly outline the objectives and functionality.
- Stakeholders: Consider the needs of users, game developers, and administrators.
- Use Cases: Cover all user interactions and edge cases (e.g., refunds, tie scores, malicious actors).
How Should the Smart Contract Do It?
This question dives into implementation details and constraints, including:
- Data Structures: How to efficiently store and manage data (e.g., mapping for refunds, leader’s address, etc.).
- Security Measures: Implementing best practices to prevent vulnerabilities like reentrancy or DoS attacks.
- Gas Efficiency: Optimizing for low transaction costs to ensure usability.
- Standards: Adhering to existing standards (e.g., ERC20, ERC721) when applicable.
- Upgradeability: Ensuring the contract can evolve if the game requirements change.
Example for Leaderboard:
- Refund Handling: Use pull payments instead of direct transfers to avoid reliance on recipient behaviour.
- Token Transfers: Use the ERC20 transferFrom function to manage token deposits securely.
- Event Logging: Emit events for transparency and integration with off-chain systems.
Write in a Programming Language
This step involves implementing the smart contract in a blockchain programming language like Solidity / Vyper or Reach, which is the case for the EVM. However, these concepts directly apply to smart contracts deployed on other chains. Whether Algorand / Solana and Stacks, etc., the implementation must follow the design principles outlined above while adhering to the constraints of the target blockchain platform.
Key Practices for EVM:
- Readability: Write clean, modular, and well-documented code. Use audited code when implementing an ERC/EIP standard unless in custom use cases.
- Error Handling: Use require, revert, and assert effectively to validate inputs and enforce rules.
- Testing: Thoroughly test the contract with tools like Hardhat, Foundry, or Truffle to identify and fix bugs.
- Auditing: Have the code reviewed by a professional auditing firm for security.
- Use Battle Tested Code: OpenZeppelin has many code examples and libraries. There is no shame in standing on the shoulders of giants. These contracts are open source and fully audited.
High-Level Overview of DoS (Denial of Service) Attacks in Leaderboard Smart ContractsWhat is a Denial of Service (DoS) Attack?
A Denial of Service (DoS) attack occurs when a malicious actor disrupts the normal functioning of a system, making it inaccessible or unusable for legitimate users. In the context of blockchain and smart contracts, DoS attacks target vulnerabilities in contract logic to block users from executing certain operations, such as claiming refunds or updating the leaderboard.
How Do DoS Attacks Impact Smart Contracts?
Players compete to become a blockchain leaderboard system leader by staking tokens or Ether. Malicious actors can exploit a poorly designed contract to:
- Block New Players: Prevent new participants from staking or becoming the leader.
- Freeze Funds: Trap refunds or rewards, making them inaccessible.
- Disrupt Game Mechanics: Prevent smooth transitions of leadership or reward payouts.
Real-World Scenario
Imagine a game where players must outbid each other to secure the top spot on the leaderboard. Each time a new leader emerges:
- The previous leader is refunded their stake.
- The new leader’s stake becomes the highest bid.
If a malicious player can disrupt the refund process, the contract could fail, blocking others from becoming the leader. One of the most famous examples of this attack happened decades ago. When Dark Dante, aka Kevin Poulson, executed a DoS on a radio station to win a Porsche.
Vulnerable Example: Direct Payments
This contract uses direct payments (send) to refund the previous leader. However, it assumes all participants are cooperative, making it vulnerable to denial of service (DoS) attacks.
// SPDX-License-Identifier: MIT
pragma solidity 0.8.26;
contract VulnerableLeaderboard {
address payable public currentLeader;
uint public highestScore;
// Function to compete for the leaderboard
function compete() public payable {
require(msg.value > highestScore, "Stake must exceed the current highest score");
// Refund the previous leader
if (currentLeader != address(0)) {
require(currentLeader.send(highestScore), "Refund failed");
}
// Update leaderboard
currentLeader = payable(msg.sender);
highestScore = msg.value;
}
}Vulnerability
- DoS Attack with Malicious Fallback:
- An attacker can deploy a contract with a revert() in its receive() function. This will block refunds, preventing new players from becoming the highest staker.
Attack Example// SPDX-License-Identifier: MIT
import "contracts/VulnerableLeaderboard.sol";
contract Attacker {
function attack(address leaderboardAddress) public payable {
VulnerableLeaderboard(leaderboardAddress).compete{value: msg.value}();
}
// Malicious fallback to block refunds
receive() external payable {
revert();
}
}First Secure Version: Pull Payments
The pull payment pattern solves the issue by allowing participants to withdraw refunds rather than forcing the contract to send funds. Now, let us think about this at a higher level.
The Pull Payment Pattern in Smart Contracts
The pull payment pattern is a widely recommended design approach in Solidity and blockchain development. It solves several common issues, particularly with direct payments (or “push payments”) within smart contracts.
What is the Pull Payment Pattern?
Instead of the smart contract actively sending funds to recipients (push payment), the pull payment pattern enables recipients to withdraw their funds from the contract at their convenience.
This approach shifts the responsibility for receiving funds from the contract to the recipient, improving security and reliability.
How Does the Pull Payment Pattern Solve Issues?Prevents Denial of Service (DoS) Attacks
In push payment designs, a malicious recipient can block payments by reverting transactions in their fallback function (receive() or fallback()). For example:
- A previous leaderboard leader might deploy a contract with a revert() in its fallback function, preventing the send() or transfer() call in the refund process.
- With pull payments, the recipient must explicitly withdraw their funds, and their behaviour does not block the contract’s functionality for others.
Mitigates Reentrancy Risks
Reentrancy occurs when an external contract calls back into the vulnerable contract during execution, manipulating its state. By using pull payments:
- The contract updates the state (e.g., clearing the owed balance) before transferring funds.
- This ensures reentrancy cannot exploit incomplete state changes.
Increases Gas Efficiency and Reliability
Directly sending Ether or tokens may fail if the recipient’s account is a contract with insufficient gas to process the transfer. Using pull payments:
- Recipients decide when to withdraw their funds.
- Gas-related failures no longer affect the contract’s operation.
Provides Transparency and Flexibility
With pull payments:
- Recipients can check their pending balances before withdrawing.
- The process is more transparent, as funds remain in the contract until explicitly claimed.
// SPDX-License-Identifier: MIT
pragma solidity 0.8.26;
contract SecureLeaderboard {
address public currentLeader;
uint public highestScore;
// Mapping to track refunds for previous leaders
mapping(address => uint) public refunds;
// Function to compete for the leaderboard
function compete() public payable {
require(msg.value > highestScore, "Stake must exceed the current highest score");
// Track refund for the previous leader
if (currentLeader != address(0)) {
refunds[currentLeader] += highestScore;
}
// Update leaderboard
currentLeader = msg.sender;
highestScore = msg.value;
}
// Function to withdraw refunds
function withdrawRefund() public {
uint refundAmount = refunds[msg.sender];
require(refundAmount > 0, "No funds to withdraw");
// Reset refund amount before transferring
refunds[msg.sender] = 0;
(bool success, ) = payable(msg.sender).call{value: refundAmount}("");
require(success, "Refund transfer failed");
}
}Improvements
- Pull Payments:
- Refunds are stored in a mapping and withdrawn by users via withdrawRefund().
- Reentrancy Protection:
- Refunds are reset before the transfer to prevent reentrancy attacks.
Although, we have protected this contract again rentrancy. It is a better option to use OpenZeppelin’s Security Suite contract.
Enhanced Secure Version: ERC20 Integration
For blockchain games that use tokens, the leaderboard can be adapted to support ERC20 tokens. This version also includes a reward pool to incentivise participation.
// SPDX-License-Identifier: MIT
pragma solidity 0.8.26;
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
contract EnhancedLeaderboard {
IERC20 public gameToken;
address public currentLeader;
uint public highestScore;
uint public rewardPool;
mapping(address => uint) public refunds;
event NewLeader(address indexed player, uint score);
event RewardClaimed(address indexed leader, uint reward);
event RefundWithdrawn(address indexed player, uint amount);
constructor(IERC20 _gameToken) {
gameToken = _gameToken;
}
// Function to compete for the leaderboard
function compete(uint amount) public {
require(amount > highestScore, "Stake must exceed the current highest score");
// Refund the previous leader
if (currentLeader != address(0)) {
refunds[currentLeader] += highestScore;
}
// Transfer tokens from the player to the contract
require(gameToken.transferFrom(msg.sender, address(this), amount), "Token transfer failed");
// Update leaderboard
currentLeader = msg.sender;
highestScore = amount;
// Add a portion to the reward pool
rewardPool += (amount * 10) / 100;
emit NewLeader(msg.sender, amount);
}
// Function to withdraw refunds
function withdrawRefund() public {
uint refundAmount = refunds[msg.sender];
require(refundAmount > 0, "No funds to withdraw");
refunds[msg.sender] = 0;
require(gameToken.transfer(msg.sender, refundAmount), "Refund transfer failed");
emit RefundWithdrawn(msg.sender, refundAmount);
}
// Function for the leader to claim rewards
function claimReward() public {
require(msg.sender == currentLeader, "Only the current leader can claim rewards");
require(rewardPool > 0, "No rewards available");
uint rewardAmount = rewardPool;
rewardPool = 0;
require(gameToken.transfer(msg.sender, rewardAmount), "Reward transfer failed");
emit RewardClaimed(msg.sender, rewardAmount);
}
}Features
ERC20 Token Support:
- Players stake tokens instead of Ether.
- Refunds and rewards are also handled in tokens.
Reward Pool:
- A portion of each stake (e.g., 10%) is added to a reward pool.
- The current leader can claim this reward.
Game-Specific Events:
- Events (NewLeader, RewardClaimed, and RefundWithdrawn) provide transparency for game tracking and analytics.
Conclusion
By understanding and dissecting a vulnerable leaderboard contract and progressing to a more secure implementation, we’ve shown and gained intuition in addressing common issues like DoS attacks and improving functionality for blockchain games. In the enhanced version, we secure the leaderboard and introduce features like ERC20 token support and a reward pool for greater engagement. You can do more, and your security can continually be enhanced. Check out our socials, show some love, and give us a clap.
Join our discord: https://discord.gg/nN5TDwSN
Follow us on Twitter: https://x.com/NebulaGamingHub
Follow Skale on Twitter: https://x.com/SkaleNetwork
View online content on YouTube: https://www.youtube.com/@blockchaingamedev

Securing Leaderboards in Blockchain Games === Pull Payment Pattern was originally published in Coinmonks on Medium, where people are continuing the conversation by highlighting and responding to this story.