03 · Tax under the cap
If there is a tax, it is fixed, or every function that can change it is bounded at or under ten percent.
The question
Can the tax be raised until selling is pointless?
A ninety-nine percent sell tax is a honeypot with extra steps. A tax that starts at three percent and has a setter with no upper bound is the same thing, waiting.
How it decides
Tax variables are state variables of unsigned integer type, not mappings, whose name contains tax or fee, excluding names that contain exempt, exclud, wallet, receiver, recipient, collector, address, denom or divisor (those are addresses and denominators, not rates).
Setters are functions reachable from an entry point, constructor excluded, that assign to a tax variable.
For each setter the check looks for a bound: the largest number literal that appears in a comparison (<=, <, >, >=) anywhere in the setter's body. require(t <= 500) gives 500. if (t > 10) revert() gives 10. No comparison with a literal gives no bound.
The denominator is the largest of 100, 1000, 10000 or 100000 that appears as a literal anywhere in the contract's functions, since that is how the tax is applied. If none is found, 100 is assumed.
- No tax variable: pass.
No tax variable in the code. - Tax variables but no setter: pass.
sellTax cannot be changed after deployment. - A setter with no bound, or
bound / denominator > 0.10: fail.sellTax can be set without a bound in setSellTax.orsellTax can be set up to 2500 of 10000 in setFees. - Every setter bounded at ten percent or less: pass, listing them.
What passes
uint256 public constant taxBps = 300;
function setTax(uint256 bps) external onlyOwner {
require(bps <= 1000, "cap");
taxBps = bps;
}
with 10000 used as the denominator somewhere in the transfer math.
What fails
function setSellTax(uint256 t) external onlyOwner { sellTax = t; }
No bound. Also fails: require(t <= 30) when the denominator is 100, because thirty percent is over the cap; a bound expressed through a state variable (require(t <= maxTax)) where maxTax itself has a setter, because the check reads literals, not variables.
Known edges
The denominator is inferred, not proven. A contract that divides by 1e4 written as 10 ** 4 is read as denominator 100 (the fallback), which makes a bound of 500 look like five hundred percent and fail. Write the denominator as a plain literal, or use a constant that is a plain literal, and the check reads it. This is the check most likely to fail an honest contract, and the fix is always a one-line change.