Radar by SecureChain AI

牛马人生 (牛马人生) security report

Radar by SecureChain AI scanned the verified 牛马人生 contract on BNB Smart Chain. Material findings include unlimited tax-processor allowance, conditional initializer takeover, dividend callback transfer blocking, and unvalidated tax rates. Slither was not run.

The contract is not free of material security and business-logic risks. The most significant issue is that the configured tax processor receives an effectively unlimited allowance over tax tokens held by the token contract. Additional risks depend on deployment configuration, including proxy initialization state, dividend-contract behavior, and whether tax rates exceed 10000 basis points.

Trust score: 48/100. Source verified: Yes. Chain: bsc. Contract: 0x35985eb91a78bf5110d32af0db8f1256d19e7777.

This report was last modified on 2026-10-01 and contains 6 material findings.

high finding: Configured tax processor can pull all tokens held by the token contract

The token grants the configured taxProcessor an effectively unlimited ERC-20 allowance whenever tax liquidation occurs. That external contract can then call transferFrom against the token contract and pull its entire token balance, not only the current liquidation amount. This is a powerful centralized dependency and can cause loss of accumulated tax tokens if the processor is malicious, compromised, or incorrectly configured.

Evidence: In _processTax(uint256 taxAmount), the contract executes _approve(address(this), taxProcessor, type(uint256).max) when the allowance is insufficient, then calls ITaxProcessor(taxProcessor).processTaxTokens(taxAmount). The taxProcessor address is supplied through initialize() and has no setter or validation beyond being nonzero. The fallback path also transfers balanceOf(address(this)) to taxProcessor after processing failure.

Recommendation: Use a narrowly scoped allowance for each liquidation, revoke or reduce the allowance after processing, and verify that the processor can only pull the intended taxAmount. Add immutable or governance-controlled processor validation and test malicious or reverting processor behavior.

high finding: Uninitialized proxy or clone can be claimed by an arbitrary caller

The initializer is externally callable by the first caller and mints the full maxSupply to msg.sender while assigning that caller as owner through __Ownable_init(). If a proxy or clone is deployed without atomically initializing it, an attacker can initialize the instance, receive the full token supply, and choose tax, pool, processor, and dividend configuration.

Evidence: initialize(InitParams memory params) is external and protected only by the one-time initializer modifier. It calls __Ownable_init(), then _mint(msg.sender, maxSupply). The source does not restrict initialization to a factory or require deployment-time initialization data. The implementation constructor disables initialization only for the implementation instance itself, not for an uninitialized proxy or clone storage context.

Recommendation: Initialize every proxy or clone atomically during deployment, restrict initialization to the intended factory or deployer where appropriate, and add deployment tests that prove an external account cannot claim an uninitialized instance.

medium finding: Dividend callback failure can block ordinary transfers

For non-pool addresses, every transfer invokes the configured dividend contract through setShare(). Any revert from that external call causes the entire token transfer to revert. A broken, paused, malicious, or incompatible dividend contract can therefore prevent ordinary transfers for affected accounts.

Evidence: _afterTokenTransfer() calls IDividend(dividendContract).setShare(from, balanceOf(from)) and setShare(to, balanceOf(to)) inside try/catch blocks. The catch handlers revert with DividendShareUpdateFailed. dividendContract is configured during initialize() and cannot be changed by a public setter.

Recommendation: Decouple balance transfers from dividend accounting, use a non-blocking update mechanism, or provide a guarded emergency path that disables callbacks. Validate the dividend implementation during initialization and test callback failures and gas exhaustion.

medium finding: Tax rates are not bounded to prevent transfer-wide reverts

Initialization accepts buyTax and sellTax as uint16 values without requiring them to be at most 10000 basis points. If either configured rate exceeds 10000, the calculated tax can equal or exceed the transfer amount, and _taxedTransfer() performs amount - tax, causing pool-related transfers to revert during tax enforcement.

Evidence: initialize() stores params.buyTax and params.sellTax directly in poolState. _getTaxWithPoolState() computes (amount * currentBuyTaxRate) / 10000 or (amount * currentSellTaxRate) / 10000. _taxedTransfer() then executes uint256 remainingAmount = amount - tax without a rate bound. uint16 permits values well above 10000.

Recommendation: Require both tax rates to be <= 10000 during initialization and enforce the same invariant in any future configuration path. Add tests for boundary values, including 10000 and values above 10000.

medium finding: Owner-controlled migration creates a configurable transfer restriction window

While the pool state is BondingCurve, all transfers involving any configured pool revert. Only the owner can call startMigration() and finalizeMigration() to advance the state. This creates a material operational and centralized-control risk: pool trading and transfer availability depend on owner-controlled state transitions and correctly configured pool addresses.

Evidence: _transfer() requires !pools[from] && !pools[to] in PoolState.BondingCurve. startMigration() and finalizeMigration() are both external onlyOwner functions, and initialize() sets mainPool and the pools mapping from caller-supplied addresses.

Recommendation: Use a verifiable factory or protocol-controlled migration mechanism, publish configured pool addresses and state-transition conditions, and add a bounded or permissionless fallback for stalled migration where appropriate.

medium finding: Failed tax processing transfers the full contract token balance to the processor

When the external tax processor call reverts, the fallback path transfers the entire current token balance of the token contract to taxProcessor. That balance can include tax accumulated across multiple periods, so a processing failure does not merely defer the current liquidation and can redirect all held tax tokens to the external processor.

Evidence: In _processTax(), the catch block obtains uint256 remainingBalance = balanceOf(address(this)) and calls _plainTransfer(address(this), taxProcessor, remainingBalance) whenever the balance is nonzero.

Recommendation: Do not transfer the full balance on failure. Preserve funds for retry, track the specific liquidation amount, and provide a separately governed recovery process with explicit limits and events.