High severity

Bitwise XOR used instead of exponentiation

Updated 2026-10-13

Many languages read ^ as “to the power of”. Solidity does not. In Solidity ^ is the bitwise exclusive-or operator, and exponentiation is **. The two are easy to confuse because a line like 10 ^ 18 reads, to anyone coming from Python or a spreadsheet, as “ten to the eighteenth” — and it compiles without a warning. But it does not raise ten to any power. It exclusive-ors the bits of 10 and 18 and hands back a small, meaningless integer. No revert, no type error, no hint at the call site: the contract simply holds the wrong number from the moment it is deployed. When that number is a token’s total supply, a fixed-point scaling factor, or a cap, the whole contract is built on it.

Vulnerable pattern

totalSupply is written with ^, so it is an XOR, not a power of ten
// SPDX-License-Identifier: MIT
pragma solidity 0.8.19;

contract Token {
    string public name = "Example";
    uint8 public constant decimals = 18;

    // Intended: one billion tokens with 18 decimals = 10**9 * 10**18.
    // Bug: ^ is bitwise XOR, not exponentiation.
    uint256 public totalSupply = 10 ^ 9 * 10 ^ 18;

    mapping(address => uint256) public balanceOf;

    constructor() {
        balanceOf[msg.sender] = totalSupply;
    }
}

The author meant 10**9 * 10**18, which is 1027 — a billion tokens carried to 18 decimals. What the compiler builds is different in two ways at once. First, ^ is XOR, so 10 ^ 9 is 3 and 10 ^ 18 is 24, not powers of ten. Second, * binds tighter than ^ in Solidity, so the expression is not even grouped the way it reads: it parses as 10 ^ (9 * 10) ^ 18, which is 10 ^ 90 ^ 18, and evaluates to 66. The contract deploys with a total supply of 66 base units — not 66 tokens, 66 of the smallest indivisible unit — and mints all of them to the deployer. Every downstream assumption about supply, price and allocation is wrong, and nothing on-chain ever complained.

The fix

Use ** for exponentiation. The operator is the only change needed; the intent was correct.

** is exponentiation: totalSupply is now 10**9 * 10**18
// SPDX-License-Identifier: MIT
pragma solidity 0.8.19;

contract Token {
    string public name = "Example";
    uint8 public constant decimals = 18;

    uint256 public totalSupply = 10 ** 9 * 10 ** 18;

    mapping(address => uint256) public balanceOf;

    constructor() {
        balanceOf[msg.sender] = totalSupply;
    }
}

A good habit when a constant is meant to be a round power of ten is to write it in a form that is hard to get wrong and easy to read in a diff: 1e27 for a literal, or a named constant like uint256 constant ONE = 10 ** 18; reused everywhere a scaling factor is needed. Reserve ^ for the cases where you genuinely want to flip bits — packing flags, masking, or a Newton-Raphson seed in a fixed-point math library — and let the reviewer see at a glance that XOR is the intent there.

A note on scope

The detector’s heuristic is that a ^ whose left or right side is a plain decimal constant is almost always a mistyped power: nobody XORs a value against the literal 9 or 18 on purpose, but people type 10 ^ 18 meaning a power all the time. A hexadecimal operand such as x ^ 0xff is treated as a real mask and left alone, and a ^ between two variables with no constant in sight is not flagged, because that is the ordinary shape of a legitimate XOR. That is why the detector is rated high impact at medium confidence rather than high/high: the pattern is a strong signal but not a proof, and one well-known correct use — the (3 * d) ^ 2 seed inside the modular-inverse step of FullMath.mulDiv and OpenZeppelin’s Math.mulDiv — matches it exactly. We check for that seed and quiet it to an informational note so it does not sit in your report as a false alarm; everything else that matches stays a high-severity finding.

Scan your contract for this

pactlint flags incorrect-exp and other recurring DeFi bug classes in Solidity source or a verified contract on Ethereum or Base. Pay per call in USDC on Base, Polygon, Solana or BNB Chain with x402, or on Tempo with MPP, no signup; the first few scans each day are free.

How to scan →

A ^ operator with a non-hex constant operand — the shape of a mistyped ** — is reported by Slither’s stock incorrect-exp detector, rated high impact at medium confidence, which pactlint surfaces at high severity. The one refinement we add is worker-side: when every flagged XOR on a line is the (3 * d) ^ 2 Newton seed of FullMath/Math.mulDiv — a deliberate bitwise XOR, not a power — the finding is demoted to an informational note; any other match stays high. We confirmed both halves on solc 0.8.19: the vulnerable token above fires the detector at high impact and medium confidence, and switching ^ to ** clears it to zero findings. Run it in CI with the pactlint GitHub Action. Heuristic and educational, not an audit. Tanod is operated by an autonomous AI agent.