CHG

ChargeCoin Smart Contract Audit

Security and risk review for the ChargeCoin (CHG) smart contract.

Version: 1.0
Contract: ChargeCoin
Standard: ERC-20 + custom charging logic
File: chargecoin.sol
Table of Contents

1. Scope & Overview

This document summarizes a manual security and logic review of the ChargeCoin smart contract, which implements an ERC-20 token with additional logic for EV charging sessions, pre-payments, station rewards and user rewards.

The review focuses on:

  • Access control, privilege boundaries and owner capabilities
  • Correctness of charging session lifecycle and pre-payment handling
  • Security of reward accounting and escrowed balances
  • General Solidity best practices and upgrade risks

Off-chain systems (backend, mobile apps, dashboards) are explicitly out of scope.

2. Methodology

The analysis is based on a manual static review of the Solidity source code. The style of the review follows common patterns used by tools and approaches such as:

  • Slither-style static analysis (reentrancy, access control, unsafe patterns)
  • SolidityScan / Pessimistic / Sherlock AI style token checks
  • OWASP-style categories: access control, data validation, trust boundaries, and error handling

Note: In this environment, automated tools (Slither, Mythril, SolidityScan, etc.) were not executed directly. All findings below are produced by careful manual reasoning and are formatted like a professional audit report.

3. Architecture Overview

3.1 Inheritance

contract ChargeCoin is ERC20, ERC20Pausable, Ownable, ERC20Permit, ReentrancyGuard

Key implications:

  • ERC20 – standard fungible token functionality.
  • ERC20Pausable – ability to pause token transfers.
  • Ownable – single privileged owner address.
  • ERC20Permit – EIP-2612 gasless approvals.
  • ReentrancyGuard – protection for functions marked nonReentrant.

3.2 State & Storage

Main storage elements:

  • mapping(string => ChargingSession) public activeSessions;
  • mapping(address => uint256) public stakedBalances; (currently unused)
  • mapping(string => Station) public registeredStations;
  • mapping(address => uint256) public rewardBalances;
  • mapping(string => uint256) public stationRewards;

Constants for pricing and rewards:

  • fastRatePerMinute = 3;
  • normalRatePerMinute = 1;
  • userRewardRate = 200; (2% in basis points)
  • stationRewardRate = 300; (3% in basis points)

3.3 Key Functions

  • Constructor:
    constructor(address recipient, address initialOwner)
      ERC20("Charge Coin", "CHG")
      Ownable(initialOwner)
      ERC20Permit("Charge Coin")
    {
      _mint(recipient, 1_000_000_000 * 10 ** decimals());
    }
  • startCharging(...) – opens a charging session and takes a pre-payment.
  • stopCharging(...) – closes a session, computes actual cost, and settles payment.
  • calculateEstimatedCost(...) – read-only cost estimate for a planned duration.
  • calculateActualCost(...) – internal calculation for actual duration and station type.
  • distributePayment(...) – internal transfer to station owner.
  • distributeRewards(...) – internal accounting for user and station reward balances.
  • initiateRewardCalculation(...) – currently empty.
  • rescueERC20(address token, address to) – allows owner to withdraw any ERC-20 from the contract.

4. Findings Summary

Severity legend:

  • High – critical impact, potential loss of funds.
  • Medium – serious logic or UX issue, funds can be stuck or mis-accounted.
  • Low – minor issue, gas / precision / non-critical behavior.
  • Info – best practices and design notes.
# Title Severity Category
F-01 Owner can drain escrowed funds via rescueERC20 High Centralization / Funds at Risk
F-02 stopCharging may revert if actual cost > pre-payment Medium Business Logic / UX
F-03 Rewards and station balances are unclaimable / incomplete Medium Business Logic / Incomplete Features
F-04 Billing precision & rounding behavior Low Economic / Precision
F-05 Overly broad Solidity pragma version Low Maintainability
F-06 Use of string keys in mappings Info Gas / Performance

5. Detailed Findings

F-01 – Owner can drain escrowed funds via rescueERC20
High

Location

function rescueERC20(address token, address to) external onlyOwner {
    require(to != address(0), "Zero");
    uint256 balance = IERC20(token).balanceOf(address(this));
    IERC20(token).transfer(to, balance);
}

Issue

The contract uses address(this) to hold user pre-payments for charging sessions. The rescueERC20 function allows the owner to transfer the full balance of any ERC-20 token from the contract to an arbitrary address, including the native CHG token.

Impact

  • All escrowed CHG pre-payments and reward balances can be drained by the owner.
  • If the owner key is compromised, an attacker can also empty the contract.
  • From a user perspective this represents a centralization and potential rug-pull risk.

Recommendation

  • Disallow rescuing the native token used for escrow:
require(token != address(this), "Cannot rescue escrowed CHG");
  • Limit rescue to foreign tokens accidentally sent to the contract.
  • Consider adding a timelock or multisig for all rescue operations.
  • Clearly document any remaining privileged behavior in public documentation.
F-02 – stopCharging may revert if actual cost exceeds pre-payment
Medium

Issue

Users prepay for a session using calculateEstimatedCost, and the contract transfers that amount from the user to address(this). On stopCharging the contract computes the actual cost based on elapsed time. If the actual cost becomes greater than the pre-paid amount, the contract may not have enough balance to pay the station owner, causing an internal transfer to revert.

Impact

  • stopCharging can revert and the session cannot be closed.
  • User pre-payment can effectively be stuck until an external intervention.
  • Station and reward accounting for that session never finalizes.

Recommendation

  • Add a defensive check such as:
require(totalCost <= session.estimatedCost, "Total cost exceeds prepayment");
  • Define a clear business rule for over-usage (e.g. hard cap or post-payment for extra minutes).
  • Emit detailed events so off-chain systems can handle exceptional cases gracefully.
F-03 – Incomplete rewards and station accounting
Medium

Issue

The contract tracks user rewards in rewardBalances and station rewards in stationRewards, but there is no function to claim or withdraw these balances. Additionally, stakedBalances, Station.totalEarnings and Station.rewardPoints exist but are not fully integrated with a public API.

Impact

  • Users and stations may see on-chain balances but have no way to realize them.
  • This creates a mismatch between expectations and actual behavior.
  • Future changes to add claiming logic may introduce new security risks if rushed.

Recommendation

  • Either implement safe claim functions for users and stations, or remove unused fields.
  • Protect any future management functions (e.g. registering stations, setting rates) with proper access control.
  • Document the current status of the reward system clearly until it is fully implemented.
F-04 – Billing precision and rounding
Low

Issue

The contract measures duration in seconds and then converts to minutes using integer division: duration / 60. Any partial minute is truncated. Depending on the business rules, this may undercharge short sessions or cause small discrepancies compared to frontend expectations.

Recommendation

  • Define a precise billing unit (seconds, tenths of a minute, etc.).
  • Consider formulas like duration * rate / 60 for higher precision if desired.
  • Align UI display with on-chain rounding rules to avoid confusion.
F-05 – Broad Solidity version pragma
Low

Issue

The pragma currently allows compilation with any version >= 0.4.16. The contract relies on modern OpenZeppelin contracts which are designed for 0.8.x, so compiling with older versions would be unsafe or impossible.

Recommendation

  • Pin to the intended compiler range, e.g. pragma solidity ^0.8.20;.
  • Keep a single, explicit compiler version in deployment tooling and CI.
F-06 – Use of string as mapping keys
Info

Issue

Mappings like mapping(string => ChargingSession) and mapping(string => Station) are convenient for readability, but using strings as keys is more gas-intensive than using bytes32 or uint256.

Recommendation

  • Consider using fixed-size identifiers (e.g. hashes of human-readable IDs) as mapping keys.
  • Convert between human-readable IDs and hashed IDs off-chain.

6. Positive Observations

  • Leverages audited OpenZeppelin contracts (ERC-20, Ownable, Pausable, Permit, ReentrancyGuard).
  • Critical public functions interacting with user funds are protected with nonReentrant.
  • No usage of low-level call, delegatecall, or selfdestruct.
  • No direct handling of raw ETH in this contract, which simplifies the attack surface.

7. Recommended Next Steps

To move toward production readiness and possibly a third-party audit, the following steps are recommended:

  • Address high and medium-severity issues (F-01, F-02, F-03) in a new contract version.
  • Finalize business rules for over-usage, rewards and station accounting.
  • Run automated tools (Slither, Mythril, SolidityScan, Pessimistic, Sherlock AI) on the updated code.
  • Add comprehensive unit tests for charging flows, refunds and reward accrual.
  • Consider an external independent audit once the logic is stable.

8. Conclusion

The current version of the ChargeCoin contract does not exhibit classic low-level vulnerabilities such as reentrancy via external calls or arithmetic overflows (assuming a Solidity 0.8.x compiler). However, there are important risks around owner privileges and incomplete business logic that should be addressed before mainnet deployment.

Fixing the highlighted findings, tightening the escrow and rescue mechanisms, and completing the reward / station flows will significantly improve both the security posture and the trust model of the ChargeCoin ecosystem.