GrainFi (GrainFi) security report
GrainFi was AI scanned from verified source and reviewed with GoPlus evidence. No confirmed critical issue was found. Medium risks include owner-controlled migration, uncapped initialization tax rates, external dividend callbacks that may revert transfers, and 41.5% owner concentration. Slither was not run.
The contract was AI scanned using the verified source and reviewed against the supplied GoPlus evidence. No confirmed critical vulnerability was established. Medium-risk issues include owner-controlled trading-state transitions, tax-rate parameters that are not capped at 100%, reliance on external dividend callbacks that can revert transfers, and significant holder concentration. Slither was skipped.
Trust score: 68/100. Source verified: Yes. Chain: bsc. Contract: 0x1d4189f1bf285759d5033cff2438bb4175247777.
This report was last modified on 2026-09-19 and contains 5 material findings.
medium finding: Owner controls migration and can keep pool transfers restricted
The owner exclusively controls startMigration() and finalizeMigration(). While the token remains in BondingCurve state, every transfer involving any configured pool reverts. The contract has no permissionless or time-based transition that guarantees migration completion, allowing the owner to delay or control when pool trading becomes available.
Evidence: startMigration() and finalizeMigration() are both marked onlyOwner. In _transfer(), PoolState.BondingCurve executes require(!pools[from] && !pools[to], "Transfers to/from pools are restricted in BondingCurve state").
Recommendation: Use a timelock, immutable launch schedule, or independently verifiable migration conditions. Publish the configured owner and migration process, and consider restricting owner control after launch.
medium finding: Initialization permits tax rates above 100%, which can lock pool transfers
initialize() accepts uint16 buyTax and sellTax values without enforcing a maximum of 10000 basis points. If either configured rate exceeds 10000, _taxedTransfer() computes tax greater than amount and amount - tax reverts. This can make buys or sells involving configured pools fail for the duration of tax enforcement. The supplied ABI does not disclose the initialization arguments, so the deployed instance's actual rates were not independently confirmed.
Evidence: initialize() stores params.buyTax and params.sellTax directly in poolState. _getTaxWithPoolState() calculates (amount * rate) / 10000, while _taxedTransfer() executes uint256 remainingAmount = amount - tax.
Recommendation: Require buyTax and sellTax to be <= 10000 during initialization, and add deployment-time assertions and tests for boundary values.
medium finding: External dividend callback can block ordinary token transfers
When dividendContract is nonzero, _afterTokenTransfer() synchronously calls IDividend.setShare() for non-excluded sender and recipient addresses. Any revert from that external contract is propagated as DividendShareUpdateFailed, reverting the token transfer. A broken, unavailable, or incompatible dividend contract can therefore materially impair transfers for regular holders.
Evidence: _afterTokenTransfer() calls IDividend(dividendContract).setShare(...) in try/catch and explicitly reverts with DividendShareUpdateFailed when the callback fails. dividendContract is set during initialize() and there is no setter or fallback mode.
Recommendation: Avoid making core transfers depend on an external callback, or use a non-blocking update path with retryable accounting. Validate the dividend contract during initialization and monitor its availability.
medium finding: Significant holder concentration creates market and governance risk
GoPlus reports that the owner address holds approximately 41.5331% of total supply. This concentration can materially affect price, liquidity, and the token's market outcome if transferred or sold, even though the contract does not expose an owner-only mint or confiscation function.
Evidence: Supplied GoPlus evidence: owner_address 0xe2ce6ab80874fa9fa2aae65d277dd6b8e65c9de0; owner_balance 415330755.35627455; owner_percent 0.415331. Reported liquidity is approximately $24,251.26.
Recommendation: Disclose allocation and vesting, use transparent lock or multisignature controls where appropriate, and monitor holder movements relative to available liquidity.
medium finding: Unprotected initialization is deployment-sensitive
The public initialize() function is protected only by the one-time initializer modifier and mints the full maxSupply to msg.sender. If a proxy or clone is deployed without atomically invoking initialize(), the first external caller could set arbitrary token, pool, tax, processor, and dividend parameters and receive the full supply. Current GoPlus ownership and supply data indicate the supplied address is initialized, but deployment sequencing was not provided.
Evidence: initialize() is external and uses initializer; it calls _mint(msg.sender, maxSupply). The source itself warns that uninitialized upgradeable contracts can be taken over, while the reviewed deployment context does not include proxy-constructor calldata or deployment transactions.
Recommendation: Atomically initialize proxies or clones during deployment, verify initialization events and owner configuration, and reject or monitor uninitialized instances before liquidity is added.