Smart Contract Audit Ethereum · UUPS Proxy Authorized CertiK Partner

Smart contract audit: fixing critical vulnerabilities before public launch

A security token on Ethereum with a UUPS proxy pattern and TimelockController. Full architecture refactoring, with all Critical/High findings closed before the report was published.

22,863 b
final contract size after refactoring
+1,713 b
margin below the EVM limit (24,576 bytes)
5
findings closed: ZHA-07, 16, 38, 40, 42
0 / 0
Critical / High at report publication
Context
The project was issuing a security token on Ethereum with a UUPS proxy pattern and TimelockController. An external security audit was required before the public listing.
Problem
The contract exceeded the EVM bytecode limit — deployment was impossible. Vulnerabilities were found in the access control and signature recovery logic:
Critical ×1 High ×2 Medium ×2
Architecture refactoring
Extracting logic into LibRecovery.sol
Cut the contract by ~1,800 bytes by extracting the inlined signature recovery logic
bytecode limit
> 24,576 bytes
Before — inlined logic
// ZSTToken.sol — inside the main contract contract ZSTToken is ERC3643, UUPSUpgradeable { // ❌ all the logic inlined in the contract function _recoverSigner( bytes32 hash, bytes memory sig ) internal pure returns (address) { bytes32 r; bytes32 s; uint8 v; assembly { r := mload(add(sig, 0x20)) s := mload(add(sig, 0x40)) v := byte(0, mload(add(sig, 0x60))) } return ecrecover(hash, v, r, s); } // +~600 bytes of bytecode }
After — LibRecovery.sol
// LibRecovery.sol — a standalone library library LibRecovery { function recover( bytes32 hash, bytes memory sig ) internal pure returns (address) { bytes32 r; bytes32 s; uint8 v; assembly { r := mload(add(sig, 0x20)) s := mload(add(sig, 0x40)) v := byte(0, mload(add(sig, 0x60))) } return ecrecover(hash, v, r, s); } } // ZSTToken.sol — uses the library import "./LibRecovery.sol"; contract ZSTToken is ERC3643, UUPSUpgradeable { using LibRecovery for bytes32; } // ✓ 22,863 bytes
Before
ZSTToken.sol
❌ > 24,576 bytes — deployment impossible
After
ZSTToken.sol
✓ 22,863 bytes · 1,713 b margin
Extracted
LibRecovery.sol
library · deploy-time linking
the library contract is linked at deploy time and doesn't count toward the main contract's size limit
Finding ZHA-07 · Critical
Bypassing the token owner check
Missing validation allowed transfer to be called on behalf of any address
severity
Critical
Vulnerable code
// ❌ ZHA-07: no msg.sender check function transferByAgent( address from, address to, uint256 amount ) external { // ❌ anyone can call this _transfer(from, to, amount); }
After the fix
// ✓ ZHA-07: role + identity check function transferByAgent( address from, address to, uint256 amount ) external { require( hasRole(AGENT_ROLE, msg.sender), "Not an agent" ); require(identityRegistry .isVerified(to), "KYC fail"); _transfer(from, to, amount); }
All closed findings: ZHA-07 ZHA-16 ZHA-38 ZHA-40 ZHA-42 ✓ Audit passed
Audit passed
Result
The contract successfully passed an external audit by an authorized firm. All Critical and High findings were closed before the report was published. The project went to public listing with no security incidents.
Solidity 0.8
UUPS Proxy · OpenZeppelin
TimelockController
Gnosis Safe · Multisig
LibRecovery.sol
ERC-3643 · security token
dimtiks.com
Blockchain Development & Smart Contract Auditing · TOKEN LAB

Have a project? Let's break it down

Tell us what's going on — we'll tell you whether we can help and what it looks like in terms of budget and timeline. No strings attached.