ZYPHRON Info
Zyphron Network (ZYN) is a next-generation digital finance platform focused on simplicity, security, and long-term stability. Built on advanced blockchain infrastructure, it empowers users to trade, earn, and grow with confidence in a rapidly evolving crypto ecosystem.
TrustNet Score
The TrustNet Score evaluates crypto projects based on audit results, security, KYC verification, and social media presence. This score offers a quick, transparent view of a project's credibility, helping users make informed decisions in the Web3 space.
Real-Time Threat Detection
Real-time threat detection, powered by Cyvers.io,
is currently not
activated
for this project.
This advanced feature provides continuous monitoring and instant alerts to safeguard your assets from potential security threats. Real-time detection enhances your project's security by proactively identifying and mitigating risks.
For more information, click here.
Security Assessments
Summary and Final Words
No crucial issues found
The contract does not contain issues of high or medium criticality. This means that no known vulnerabilities were found in the source code.
Contract owner cannot mint
It is not possible to mint new tokens.
Contract owner cannot blacklist addresses.
It is not possible to lock user funds by blacklisting addresses.
Contract owner cannot set high fees
The fees, if applicable, can be a maximum of 25% or lower. The contract can therefore not be locked. Please take a look in the comment section for more details.
Contract cannot be locked
Owner cannot lock any user funds.
Token cannot be burned
There is no burning within the contract without any allowances
Ownership is not renounced
The owner retains significant control, which could potentially be used to modify key contract parameters.
Contract is not upgradeable
The contract does not use proxy patterns or other mechanisms to allow future upgrades. Its behavior is locked in its current state.
Scope of Work
This audit encompasses the evaluation of the files listed below, each verified with a SHA-1 Hash. The team referenced above has provided the necessary files for assessment.
The auditing process consists of the following systematic steps:
- Specification Review: Analyze the provided specifications, source code, and instructions to fully understand the smart contract's size, scope, and functionality.
- Manual Code Examination: Conduct a thorough line-by-line review of the source code to identify potential vulnerabilities and areas for improvement.
- Specification Alignment: Ensure that the code accurately implements the provided specifications and intended functionalities.
- Test Coverage Assessment: Evaluate the extent and effectiveness of test cases in covering the codebase, identifying any gaps in testing.
- Symbolic Execution: Analyze the smart contract to determine how various inputs affect execution paths, identifying potential edge cases and vulnerabilities.
- Best Practices Evaluation: Assess the smart contracts against established industry and academic best practices to enhance efficiency, maintainability, and security.
- Actionable Recommendations: Provide detailed, specific, and actionable steps to secure and optimize the smart contracts.
A file with a different Hash has been intentionally or otherwise modified after the security review. A different Hash may indicate a changed condition or potential vulnerability that was not within the scope of this review.
Final Words
The following provides a concise summary of the audit report, accompanied by insightful comments from the auditor. This overview captures the key findings and observations, offering valuable context and clarity.
Smart Contract Analysis Statement
Contract Analysis
The Zyphron (ZYN) V2 contract is a revision of the previously audited ERC20 token. It keeps the same external behaviour - a hard-coded initial mint of 50,000,000 tokens, an automatic burn fee on every transfer, an additional sell tax when transferring to a registered DEX pair, a per-transfer cap, an emergency BNB withdrawal, and a stuck-token recovery helper - but reworks the internals to address the items raised in the prior audit. The re-audit confirmed that the majority of the prior findings have been addressed correctly, with a single regression introduced by one of the fixes:
- The supply-floor concern from the prior audit was addressed by adding a floor check inside the core ledger update. That change closes the original breach (totalSupply can no longer drop below the floor) but introduces a separate accounting regression on the user-callable burn paths. The sender's balance is debited by the full requested value before the burn amount is silently capped against the available headroom, so any voluntary burn larger than the headroom reduces the sender's balance by more than the supply decrease. The difference is destroyed without supply accounting, which violates the ERC20 invariant that the sum of all balances equals the total supply. This finding is tracked as the headline high-severity item for the re-audit.
- The timelock for the per-transfer cap was previously a 48-second delay with no binding between the announced and executed amounts. V2 raises the delay to 48 hours and stores the requested amount in the timelock-request struct so the execute path rejects any divergent amount. Both halves of the original concern are fully addressed.
- The combined burn-fee plus extra sell-tax math is now capped once after summing the components, so a near-floor sell can no longer over-burn. Because the floor cap inside the core ledger update applies belt-and-suspenders, the in-transfer cap is now the primary gate and the ledger cap is a safety net for the fee-burn path.
- The setDexPair admin function now mirrors the burn-fee exclusion to the active argument, only updates the canonical pointer on registration, and clears the previous canonical pair's flag when switching. The de-registration semantics are no longer surprising and operators no longer need a second call to fully revoke a pair.
- Zero-value transfers now succeed and emit the standard Transfer event, in line with the EIP20 specification. The token-recovery helper now rejects non-contract addresses and uses abi.encodeCall with a SafeERC20-style return-data check, closing the event-spoofing concern from the prior audit.
- The token-holder counter is now guarded so it only changes on real balance transitions, the burn(0) griefing path is closed, the per-fee-setter event mismatch is fixed, the constructor zero-address checks are present, the renamed and missing-NatSpec items are all in. A two-step renounce ceremony was introduced, but both calls can be made in the same block by the same key, so the protection is closer to a UI confirmation than a real ceremony - this is captured as a residual low-severity item.
Ownership Privileges
The ownership of the contract is held by the deployer-supplied initialOwner address using OpenZeppelin's two-step Ownable2Step pattern. The owner retains full privileges including:
- Setting the burn fee percentage up to a 5% cap.
- Setting an additional sell-tax percentage up to a 5% cap.
- Excluding or re-including arbitrary addresses from the burn fee, which in practice also bypasses the per-transfer cap for that address.
- Scheduling and executing a per-transfer cap through a 48-hour timelock; the execute path now requires the executed amount to match the requested amount stored on chain.
- Withdrawing the contract's entire native (BNB) balance in a single call without delay. The team has explicitly accepted this as a governance choice and documented the intent in the source.
- Recovering any non-ZYN ERC20 token sent to the contract in a single call without delay. The function now rejects non-contract addresses and uses a SafeERC20-style return-data check.
- Registering or de-registering DEX pairs; the burn-fee exclusion flag now mirrors the active argument and the previous canonical pair is cleared on registration of a new one.
- Renouncing ownership through a two-call ceremony. Both calls can be made in the same block by the same key, so the protection is weaker than a transferOwnership-style two-party flow. This is tracked as an open low-severity item with a recommended timelock-based fix.
Security Features
The contract implements several positive security features that have been preserved and in some cases strengthened in V2:
- Two-step ownership transfer (Ownable2Step) which protects against accidental ownership handover to an address that cannot accept it.
- A ReentrancyGuard modifier on the admin paths that move value (emergencyWithdraw, recoverBEP20) and on both halves of the per-transfer cap timelock.
- Hard caps on both the burn fee (5%) and the sell tax (5%), well below typical fee-on-transfer protocol limits.
- A 48-hour timelock on the per-transfer cap with the executed amount bound to the requested amount stored on chain, replacing the previous 48-second cosmetic delay.
- Custom errors throughout the contract in place of string-based require messages, with consistent ZYN__ naming.
- NatSpec on the constructor, on every ZYN-specific external function, and on the internal _update and _transfer hooks, documenting both intent and the team-accepted governance choices.
- Compliance with the ERC20 selectors and events as required by the standard; slither-check-erc confirms that every required function and event is present with the correct signatures. Note that the new sum-of-balances accounting regression is not visible to slither-check-erc because it is a relational invariant between two state variables rather than an ABI defect.
Re-audit Outcome
The re-audit confirmed that all open prior findings have been addressed with the following dispositions: every high and medium item from the prior audit is either fixed or has its remediation in place, with one exception. The previous high item on the burn floor has been partially addressed; the attempted fix closes the original breach but introduces a new high-severity accounting regression that is captured as a new finding. Two governance choices (drainable admin paths, on-chain holder counter, native-value receive fallback) have been formally acknowledged by the team rather than fixed. Three new informational items were surfaced during the re-audit: the burn event is only emitted on the fee-burn path, the constructor-time exclusion grants do not emit their event, and one interface member (getReserves) remains unused. A residual low-severity item tracks the weak two-step renounce. Static-analyzer hits dropped sharply from the prior baseline; the remaining detector hits are false positives or mitigated patterns.
Note - This re-audit report consists of a security analysis of the Zyphron (ZYN) V2 smart contract. The analysis did not include economic analysis of the contract's tokenomics. Only the main contract was audited; other contracts associated with the project were not in scope. We recommend the team address the new high-severity accounting regression before deployment, and we recommend investors do their own research before investing.
Files and details
Findings and Audit result
high Issues | 3 findings
Pending
#1 high Issue
remainingSupply Floor Not Enforced On Voluntary Burn (Conditional - Requires Team Confirmation Of Intent)
The contract declares `uint256 constant remainingSupply = 21000000 ether;` and uses it as a floor in calculateBurnAmount and inside the in-transfer tax math. The name and the use-site behaviour strongly imply a hard supply floor below which the token cannot fall. However, ERC20Burnable.burn and ERC20Burnable.burnFrom route through _burn into _update, which does not consult remainingSupply at all. Any holder can burn their own tokens (or approved tokens) until totalSupply is zero. A property fuzzer reproduces the breach with a two-call burn sequence. Severity depends on protocol intent. If the project team confirms that a hard floor was the design, this is a HIGH severity protocol-invariant breach. If the team confirms the constant is only a cap on the automatic burn fee, the finding collapses to a naming and documentation clarification (Informational).
Resolved
#2 high Issue
Timelock Amount Decoupled From Request And Delay Is Trivial
Two compounding problems with the per-transfer cap timelock. First, TIMELOCK_DURATION is 48 seconds, far below industry-standard delays (24-72 hours), which makes the timelock a cosmetic placeholder rather than a security control. Second, requestSetMaxTransferAmount hashes the requested amount into the action id, but executeSetMaxTransferAmount never re-derives the id from the executed amount; it accepts any amount greater than 1 ether. The owner can therefore announce one number publicly and execute any other number 48 seconds later, including a near-dust value that effectively freezes all non-excluded transfers. A symbolic check confirms the decoupling and a unit test reproduces it concretely.
Pending
#3 high Issue
Silent Burn Cap In _update Breaks The sum(balances) == totalSupply Invariant (New In V2)
The remediation for the previous burn-floor finding placed the floor cap inside _update. In the from-branch of _update the sender's balance is debited by the full requested value. The burn branch then overrides `value` to the available headroom (totalSupply minus the floor) and decrements totalSupply by the capped value. For any voluntary burn or burnFrom where the requested value exceeds the headroom, the caller's balance is reduced by the requested amount but the supply only drops by the capped amount; the difference is destroyed without any matching supply decrease, which violates the ERC-20 `sum(balances) == totalSupply` invariant. The fee-burn path inside _transfer is unaffected because it pre-caps totalTax before invoking _burn, but the direct user-callable burn paths regress. Worked example: with totalSupply at floor + 500e18 and alice holding 1000e18, alice.burn(1000e18) reduces alice's balance to zero while totalSupply only falls by 500e18, leaving sum(balances) strictly less than totalSupply by 500e18.
medium Issues | 4 findings
Resolved
#1 medium Issue
setDexPair Has Inverted Exclusion Side-Effect, Stale Pointer And Stale isDexPair Flag
setDexPair unconditionally writes `dexPair = pair`, `isDexPair[pair] = status` and `_excludedFromBurnFee[pair] = true`. Three independent logic bugs follow. First, setting the exclusion flag is a silent side effect when the owner passes status == false to de-register a pair. The exclusion is reversible by a follow-up includeInBurnFee call, but the function grants an exemption the caller never asked for and an operator may not notice. Two transactions are now needed to fully de-register a pair. Second, the canonical dexPair pointer is overwritten even on de-registration, so the public getter returns the address the owner just disabled. Third, when the owner moves to a new pair, the previous pair's isDexPair entry is never cleared, so the extra sell tax continues to fire on transfers to the old pair.
Resolved
#2 medium Issue
Zero-Value Transfers Revert (ERC-20 Compliance)
The overridden _transfer starts with `require(transferAmount > 0, "Zero amount");`. EIP-20 states explicitly that transfers of zero MUST be treated as normal transfers and fire the Transfer event. Many integrators (allowance refresh patterns, certain bridges, DEX init flows) probe with `transfer(_, 0)` and expect it to succeed. The current behaviour breaks them.
Resolved
#3 medium Issue
tokenHolder Counter Can Be Corrupted Or Reverted By burn(0)
_update decrements tokenHolder whenever the sender's balance reaches zero post-subtract, without verifying that the sender previously had a positive balance. ERC20Burnable.burn(0) routes through _burn -> _update with `value == 0` from a non-zero account; the balance check passes vacuously, `0 - 0 == 0` does not revert under unchecked, and the decrement fires. A second such call from another non-holder underflows tokenHolder and reverts. The counter is therefore wrong on the first griefing call and a footgun on the second.
Resolved
#4 medium Issue
Tax Burn Double-Cap Allows Floor Breach Per Sell (Reclassified From High To Medium)
Inside _transfer the burn fee (burnAmount) and the extra sell tax (extraTax) are each independently capped to totalSupply - remainingSupply. When the supply is close to the floor and both caps trigger, the combined burn (burnAmount + extraTax) exceeds the available headroom, and the subsequent _burn drops totalSupply below the documented floor. Magnitude per occurrence is bounded by maxBurnable, which is small when supply is at the engineered edge. The finding was reclassified from High to Medium on review because the practical breach amount is bounded and requires a specific near-floor state to materialise.
low Issues | 14 findings
Resolved
#1 low Issue
Local Variable Shadows ReentrancyGuard State
In setDexPair the second parameter is declared as `bool status`, which shadows the `uint256 status` state variable declared in ReentrancyGuard. There is no exploit path here because the inner scope cannot reach the outer storage slot, but the shadowing pattern is a frequent source of bugs and is flagged by every static analyser.
Resolved
#2 low Issue
Declared Events Are Never Emitted
The events Burn, Airdrop and SellTaxUpdated are declared but no code path emits them. Off-chain consumers subscribed to those topics will never receive notifications, which can mislead dashboards and monitoring.
Resolved
#3 low Issue
Unused Constant MAX_BATCH_SIZE
`uint256 constant MAX_BATCH_SIZE = 50;` is declared but never read by any function in the contract. The constant looks like a leftover from a batch-airdrop or batch-exclude feature that was not implemented.
Resolved
#4 low Issue
Missing Zero-Address Checks On Constructor Args
The constructor receives `factory` and `baseToken` without a zero-address check. If `factory` is zero the external `getPair` call reverts in an opaque way. If `baseToken` is zero, some DEX factories cheerfully return or create a pair whose other side is the zero address, which leaves the canonical `dexPair` pointer in an unusable state.
Pending
#5 low Issue
Single-Step Renounce Inherited Despite Ownable2Step
Ownable2Step adds a two-step pattern for `transferOwnership` but leaves the inherited `renounceOwnership` as a single-step call. A single accidental click or a phishing-prompted signature destroys all admin capability irreversibly. Several admin paths (burn floor enforcement, max-transfer, fee changes) require an owner, so renouncing freezes future safety responses too.
Resolved
#6 low Issue
Naming Convention On gettokenHolder
The external getter is named `gettokenHolder` (lower-case t in the middle), which violates the mixed-case-with-leading-lowercase convention used everywhere else in the contract. Because this is part of the public ABI, fixing it after deployment requires either keeping the old getter as an alias or breaking integrations.
Resolved
#7 low Issue
Constant Naming Convention
`uint256 constant remainingSupply = 21000000 ether;` should be `REMAINING_SUPPLY`. Constants are conventionally UPPER_CASE_WITH_UNDERSCORES; mixing camelCase here makes it look like a mutable variable and obscures the intent.
Resolved
#8 low Issue
Misleading Comment And Revert Strings
The comment on `_burnFeePercentage` says "Percentage in basis points (e.g., 1000 = 10%)", but the setter caps the value at 500 (5%), not 1000. Independently, setExtraTaxPercentage reverts with the string "Burn fee cannot exceed 5%" even though the value being set is the extra sell tax. Both are misleading.
Resolved
#9 low Issue
Double Space In Revert Message
The revert string `"Cannot recover tokens"` contains a double space between `recover` and `tokens`. This is the message users will see in their wallet if they trigger the revert.
Resolved
#10 low Issue
Missing NatSpec On Public Functions
Most of ZYN-specific public and external functions have no NatSpec at all or have only a single line of documentation. The inherited ERC-20 functions are documented but the additions are not, which makes the audit and any future review harder.
Resolved
#11 low Issue
Wrong Event Emitted By setExtraTaxPercentage (Reclassified From Medium To Low)
setExtraTaxPercentage emits BurnFeeUpdated, which is the same event emitted by setBurnFeePercentage. The contract declares a dedicated SellTaxUpdated(uint256 bp) event for this purpose but never emits it. Operations and monitoring services that subscribe to BurnFeeUpdated will see misleading data and any service subscribed to SellTaxUpdated will be silent. Reclassified from Medium to Low because it is an observability defect with no security-model impact - downstream consumers can fall back to decoding calldata.
Resolved
#12 low Issue
Low-Level Call Accepts Non-Contract Address As Success (Reclassified From Medium To Low)
recoverBEP20 performs a low-level call and accepts `success == true` plus empty return data as a successful transfer. When tokenAddress has no deployed code, EVM call returns success with empty data, and the check passes vacuously. The function then emits a TokensRecovered event for a transfer that never happened. This is the classic missing-code-check pattern that SafeERC20 addresses. Reclassified from Medium to Low because the function is onlyOwner: an honest owner sees no actual transfer, and a malicious owner has richer drain primitives elsewhere. The remaining downside is event-spoofing for off-chain monitors and a deviation from the standard safety pattern.
Acknowledged
#13 low Issue
Drainable Admin Paths Are Not Timelocked (Reclassified From Medium To Low; Centralization Concern)
emergencyWithdraw and recoverBEP20 are onlyOwner one-shot calls. There is no on-chain delay, no announcement, and no community-visible window. The contract has receive() so it can accumulate BNB, which combined with emergencyWithdraw forms a value-accumulation pattern that is unusual for a plain ERC-20. Reclassified from Medium to Low because the contract is openly an Ownable2Step token and the absence of a timelock on these paths is a governance choice, not a code defect. The finding is retained because the contract already implements a timelock for setMaxTransferAmount, which makes the inconsistency notable, and because the value-accumulation pattern warrants documentation.
Pending
#14 low Issue
Two-Step renounceOwnership Has No Time Or Actor Separation (New In V2)
The V2 renounceOwnership requires two calls (one to set `_renounceInitiated`, a second to complete) but both calls can be sent in the same block by the same key. There is no time delay between them, no required second-party signature, and no cool-down. A compromised owner key can complete the renounce in two transactions without any window for the community to react. A scripted operator or batched multisig transaction can complete it accidentally just as easily as a single call. The protection is effectively a UI confirmation hoisted into Solidity rather than a real two-party ceremony.
optimization Issues | 7 findings
Resolved
#1 optimization Issue
Remove Unused Constant
MAX_BATCH_SIZE is declared but never referenced anywhere in the contract. Removing it shrinks the deployed bytecode.
Resolved
#2 optimization Issue
Use Custom Errors Instead Of Require Strings
Throughout the contract, require statements use string-literal revert messages. Custom errors (the `error Foo();` syntax) are cheaper at every call site and at deployment, and they also allow developers to attach NatSpec to each error.
Resolved
#3 optimization Issue
Use abi.encodeCall Instead Of abi.encodeWithSelector
recoverBEP20 builds its calldata with abi.encodeWithSelector. abi.encodeCall produces the same calldata but checks argument types at compile time, preventing a silent breakage if the IBEP20.transfer signature ever changes.
Resolved
#4 optimization Issue
Split Compound Require Conditions
Several requires combine conditions with `&&`. Splitting them lowers gas slightly, makes coverage reports more precise, and produces clearer revert reasons for users.
Resolved
#5 optimization Issue
Avoid Redundant State Write In setDexPair
setDexPair unconditionally writes _excludedFromBurnFee[pair] = true on every call. After the related logic-bug fix mirrors the value to the active argument, also guard the write to avoid paying an SSTORE when the value is unchanged.
Acknowledged
#6 optimization Issue
Consider Removing tokenHolder On-Chain Counter
The tokenHolder counter is read by gettokenHolder() but is not used anywhere inside the contract logic. Maintaining it on-chain costs one or two SSTOREs per transfer and is the source of the corruption issue in the holder-counter finding. Off-chain indexing of Transfer events serves the same purpose at no on-chain cost.
Acknowledged
#7 optimization Issue
Drop receive() If Contract Should Not Hold Native Value
The receive() function allows the contract to accumulate BNB. The only path out is the owner-only emergencyWithdraw, which is itself an unprotected drain. If holding BNB was not intended, the simplest fix is to remove both.
informational Issues | 9 findings
Resolved
#1 informational Issue
Unused Interface IDexV2Router02
IDexV2Router02 is declared at the bottom of the file but no function in ZYN references it. Either remove it or integrate the router for swap-driven taxes - the current state is mid-implementation.
Resolved
#2 informational Issue
Unused Function In IDexV2Pair
The pair interface declares factory(), token0(), token1() and getReserves(); only token0 and token1 are actually called. The unused declarations are inert.
Acknowledged
#3 informational Issue
Solidity 0.8.34 Pin Versus Tooling Reach
The contract is pinned to Solidity 0.8.34, a recent release. Static analysers and symbolic tools tested in this audit handled it without issue, but downstream tooling may not.
Resolved
#4 informational Issue
Initial Fee Values Not Announced By Events
The constructor sets `_burnFeePercentage = 100` and `extraTaxPercent = 100` directly without emitting the corresponding events. Off-chain indexers that subscribe to the events will see uninitialised state until the first setter call.
Resolved
#5 informational Issue
Cumulative Burn Counter Documentation
totalBurnToken accumulates every burned amount and is exposed by getTotalBurnToken. The behaviour is correct but undocumented; users may misread it as a `recent burn` metric.
Pending
#6 informational Issue
Burn Event Only Emitted On The Fee-Burn Path (New In V2)
Burn(address indexed burner, uint256 amount) is declared and is emitted exclusively inside _transfer right after the fee-burn. The user-callable burn(value) and burnFrom(account, value) paths only emit the standard Transfer(_, address(0), value). Off-chain indexers subscribed to the Burn event see fee-burns only and miss voluntary burns. The dual-emit on the fee-burn path (Burn plus Transfer-to-zero) also means that any cumulative burn count derived from Burn events is strictly less than the count derived from Transfer-to-zero events.
Pending
#7 informational Issue
Initial Fee-Exclusion Grants In Constructor Do Not Emit ExcludedFromBurnFee (New In V2)
The constructor writes _excludedFromBurnFee[initialOwner], _excludedFromBurnFee[recipient], _excludedFromBurnFee[address(this)] and _excludedFromBurnFee[dexPair] to true directly without emitting the corresponding ExcludedFromBurnFee event for the first three. The dexPair entry is announced via PairRegistered, but consumers subscribed specifically to ExcludedFromBurnFee see post-deployment grants only and must read storage to reconstruct the initial set.
Pending
#8 informational Issue
IDexV2Pair.getReserves() Declared But Never Used (New In V2)
The previous audit's informational item on unused interface members was addressed for IDexV2Router02 and IDexV2Pair.factory(), but IDexV2Pair still declares getReserves() even though no callsite uses it. This is the same pattern as the prior unused-interface finding; mirror the fix.
Pending
#9 informational Issue
increaseAllowance / decreaseAllowance Retain The OZ v4 Race-Condition Pattern (New In V2)
The V2 contract exposes increaseAllowance and decreaseAllowance. OpenZeppelin v5 removed these helpers because they do not fix the underlying race condition in approve; they only paper over the most obvious shape of it. Retaining the v4 pattern is a documentation and expectation issue rather than a security defect, but it sets the wrong expectation for integrators.