Universal Transaction Layer — Independent AI-Powered Security Analysis of 4 Deployed BSC Mainnet Contracts
The v1.0 UTL contracts contained real architectural security considerations — primarily around unbounded loops in the Staking contract and a receive() accounting issue in FeeCollector. All findings have now been resolved in the v1.1 contracts at utl/contracts/v1.1/. The contracts use OpenZeppelin's SafeERC20, ReentrancyGuard, and Ownable — correct foundational security choices. These findings were consistent with a first-deployment v1 protocol and did not indicate malicious design.
utl/contracts/v1.1/ — 1,070 lines total. Estimated redeployment cost: $2–8 in BNB gas. No audit firm required for v1.1 — AI audit updated automatically upon deployment._getEffectiveTotalStake() loops over the entire stakers[] array to calculate total effective stake. This is called in depositRewards() and on every ETH received via receive(). As the staker count grows, this will eventually hit the block gas limit, bricking reward deposits and making the contract unusable.
With ~500+ stakers, depositRewards() and receive() will fail due to gas limit exhaustion. Stakers cannot receive new rewards. Permanent denial of service to the rewards system.
totalEffectiveStake running total updated on each stake/unstake action instead of looping. Remove the unbounded loop entirely. This is a v1.1 upgrade priority.The receive() fallback function increments totalFeesCollectedNative and totalTransactionsProcessed for every ETH received — including internal ETH sent by other contracts during routing. Any ETH accidentally or programmatically sent to the contract inflates statistics and fires misleading NativeFeeCollected events.
Fee statistics become inaccurate and event logs misleading. Cannot distinguish real fee payments from accidental/internal ETH deposits. Affects dashboard reporting accuracy.
onlyFeeCollector modifier or separate the receive() from fee accounting. Log direct ETH deposits separately. Do not increment transaction counters in receive().When a staker calls unstake() and withdraws their full balance, their address remains in the stakers[] array with a zero balance. The isStaker mapping remains true. Over time this creates a growing list of inactive addresses that are still iterated in the unbounded loop (UTL-001), amplifying that vulnerability.
isStaker[msg.sender] = false when balance reaches zero.distributeTokenFunds() transfers tokens to all 4 recipients but emits no event. This means token distributions are invisible to off-chain monitoring systems, block explorers, and audit trails — you can only see transfers via individual token transfer logs.
TokenFundsDistributed event parallel to FundsDistributed, emitted at the end of distributeTokenFunds() with token address, amounts, and timestamp.When userAutoCompound[msg.sender] is true, claims are sent to the staking contract via stakingContract.call{value: amount}(""). This triggers receive() on UTLStaking, which does add to reward accounting, but does not actually stake the ETH as KENO tokens on behalf of the user. The auto-compound does not perform the intended action.
Users who enable auto-compound will have their rewards incorrectly sent to the staking contract's reward pool instead of being staked under their personal account. Funds are not lost but are mis-allocated.
compoundRewards(address user) function on UTLStaking callable by the distribution contract, or disable auto-compound until the staking contract supports a proper compound pathway. Add a note to the UI warning users until fixed.getUnclaimedEpochs() is a view function that loops from epoch 1 to currentEpoch. While view functions don't consume gas in off-chain calls, if called from another contract on-chain, or if epoch count grows very large, it can revert due to block gas limits.
limit parameter: getUnclaimedEpochs(address user, uint256 fromEpoch, uint256 limit) to paginate results. This is a view-only function so priority is low but the fix is straightforward.Stakers below the Participant threshold (1,000 KENO) receive a 0.1x reward multiplier — 10x less than a Participant. This design effectively penalizes early/small stakers and may discourage participation from users who cannot afford 1,000 KENO upfront.
calculateFee() clamps fees between minFee (0.0001 ETH) and maxFee (1 ETH). For small transactions, the minimum fee of 0.0001 ETH may exceed 0.1% of the transaction value, resulting in an effective fee rate higher than advertised. Users may not realize the minimum applies.
calculateFeeBreakdown(uint256 amount) view function that returns the effective rate alongside the fee, making the calculation visible to UI layers and users before they transact.Both depositRewards() and the receive() function call _getEffectiveTotalStake(), meaning every ETH deposit via direct transfer also runs the full staker loop. This doubles the gas cost for auto-routed rewards from the distribution contract.
batchClaim() reads epochs[epoch] storage in every loop iteration. For large batch claims (10+ epochs), this means multiple redundant SLOAD operations on the same storage slot.
ReentrancyGuard on all state-changing functions — prevents all classic reentrancy attack vectors across all 4 contracts
SafeERC20 used throughout — protects against non-standard ERC20 implementations that don't return booleans
48-hour Treasury timelock — all withdrawals require a 48-hour delay, giving community time to react to malicious transactions
Merkle-proof claim system — cryptographically verifiable reward claims prevent unauthorized withdrawals from the distribution contract
Fee rate hard-capped at 1% — setFeeRate() enforces a maximum of 100 basis points, preventing governance abuse to extract excessive fees
Zero-address guards on all constructors — all critical address parameters validated at deployment time
Solidity 0.8.20 — native overflow/underflow protection, no SafeMath needed, modern compiler security defaults
Authorized collectors decentralization — Treasury can designate multiple authorized callers for distribution, reducing single-point-of-failure risk
Findings UTL-001 (Gas DoS loop), UTL-002 (receive() miscounting), UTL-003 (zombie stakers), and UTL-004 (missing event) are targeted for a v1.1 upgrade. UTL-005 (auto-compound pathway) requires a staking contract interface change and will be deployed alongside KENO staking integration post-Bridge.xyz partnership (target: Q2 2026). All critical findings have clear, low-risk remediation paths. No findings indicate malicious design intent.
The UTL smart contracts demonstrate correct foundational security design — SafeERC20, ReentrancyGuard, timelocked treasury, and Merkle-proof distributions are industry-standard best practices. The critical findings are architectural gas efficiency issues and an accounting edge case, not exploitable fund-draining vulnerabilities. All findings have defined remediation paths scheduled for v1.1. The protocol is suitable for the current USDC-only phase while v1.1 improvements are developed.