FinanceAI FAI
97% confidenceA hard-coded operator can transfer any holder’s tokens without allowance
The deployed runtime contains a caller-specific transferFrom allowance bypass for 0x36e9a317bdfa224fde58eeb7fadc442bd8ad126c. Lutetia recovers the decisive branch; Heimdall fails to reconstruct transferFrom but does not contradict it. Direct static control-flow inspection of the supplied bytecode confirms that balances are moved and a Transfer event is emitted before the privileged-caller result determines whether ordinary allowance spending is skipped. This is an unauthorized-balance-movement capability, not proof that a particular DEX sell path is blocked.
Hidden transferFrom branch
The decisive Lutetia output, checked against Heimdall and deployed opcodes.
0x23b872dddef transferFrom(uint256 _param1, uint256 _param2, uint256 amount) payable:
require (-4 + calldata.size) >=s 96
require _param1 == address(_param1)
require _param2 == address(_param2)
require address(_param1), mem[96 len 36]
require address(_param2), mem[96 len 36]
require balanceOf[address(_param1)] >= amount, mem[96 len 100]
balanceOf[address(_param1)] -= amount
balanceOf[address(_param2)] += amount
log Transfer(
amount,
address=address(_param1),
address=address(_param2)))
if caller == 0x36e9a317bdfa224fde58eeb7fadc442bd8ad126c:
return amount
if allowance[address(_param1)][caller] == -1:
return amount
require sha3(address(_param1)) >= amount, amount
require caller, amount
allowance[address(_param1)][caller] -= amount
return amountThe sender and recipient balances change before the ordinary allowance path.
Sell path not analyzed.
Codex review
Lutetia and Heimdall are evidence inputs. Codex produces the final source-level conclusion.
completeThe dispatcher maps selector 0x23b872dd to the transferFrom entry at runtime offset 0x02b8. That entry obtains msg.sender through the helper at 0x0472, calls the internal routine at 0x048b, and conditionally skips the allowance-spending routine at 0x04f5 when the internal routine returns true. Inside the 0x048b routine, the runtime embeds the exact 20-byte value 0x36e9a317bdfa224fde58eeb7fadc442bd8ad126c, masks it as an address, compares it with the supplied caller/spender value, preserves that comparison result, and then calls the token-transfer routine at 0x0587. The transfer routine validates nonzero endpoints and invokes the balance-update routine at 0x0846. At 0x0846, the source balance is loaded and checked against the amount, reduced with SSTORE, the destination balance is increased with SSTORE, and the standard Transfer topic 0xddf252ad… is logged. Control then returns to the transferFrom entry. If the saved caller comparison is true, the conditional jump at approximately 0x02df bypasses the call to 0x04f5, so no allowance is checked or reduced; the external function then returns boolean true. For all other callers, control reaches 0x04f5, which loads allowance[from][caller], accepts the maximum-uint sentinel as infinite approval, otherwise checks that allowance is at least the amount, and invokes the allowance-writing routine at 0x0677 to subtract it. Lutetia captures this ordering and the exact hard-coded caller, although its displayed 'return amount' is a decompiler type/stack-recovery error: the outer runtime path explicitly pushes 1 before returning, consistent with the ERC-20 boolean return. Heimdall accurately recovers the ordinary transfer balance mutations but decompiles selector 0x23b872dd only as an argument-validation stub, so its output is incomplete for the decisive function rather than contrary evidence. The selector table, embedded address scan, Lutetia branch, and raw runtime control flow independently converge on the same privileged allowance bypass. The current token runtime contains no administrator setter or revocation path for this address among its nine dispatched selectors, making the privilege hard-coded in this deployed code. Because the bypass is in transferFrom and applies to arbitrary nonzero source and destination addresses subject only to source balance sufficiency, the privileged operator can move holder balances without holder approval. The evidence does not establish that routine holder transfers fail, that a router or pair is specially blacklisted, or that an exact DEX sell path is blocked.
- Treat 0x36e9a317bdfa224fde58eeb7fadc442bd8ad126c as having unilateral custody-like power over every token balance.
- Inspect verified ownership, deployment provenance, and control history for the privileged operator address.
- Determine whether the privileged address currently has runtime code or is an externally owned account using an independent chain-state query.
- If the privileged address is a contract, review its complete current runtime and every externally reachable path capable of calling this token.
- Check whether the privileged address is itself upgradeable, delegated, multisig-controlled, or governed by another privileged account.
- Confirm the token address and runtime code hash independently at the intended chain ID 4663 and block height.
- Review Transfer logs initiated through the privileged operator for holder-to-operator, holder-to-pair, burn, or arbitrary-recipient movements.
- Analyze the actual router, pair, and token call sequence separately before making any claim about DEX sellability.
- Do not rely on allowances, allowance revocation, or maximum-allowance settings to protect balances from the hard-coded operator.
- The supplied context does not identify the token contract address or the block height at which the runtime was obtained.
- The configured fetch_contractsource_and_decompile capability was not exposed as an invocable tool in this session; an attempted resource lookup returned no contract result.
- Accordingly, no verified bytecode or Lutetia output for 0x36e9a317bdfa224fde58eeb7fadc442bd8ad126c was available to characterize who controls that address.
- The fetch mechanism is specified not to follow proxies, so even a future fetch of the operator would require separate explicit inspection of any implementation or delegated target it references.
- Heimdall did not reconstruct transferFrom and is insufficient by itself for this verdict.
- Lutetia mislabels the successful transferFrom return as the transferred amount; raw outer control flow shows a boolean true return.
- Static runtime analysis establishes capability, not whether or how often the privileged operator has exercised it.
- No transaction replay, caller impersonation, forged transfer call, or dynamic sell test was requested or used.
- No exact DEX router-to-pair sell path is proven blocked by the supplied evidence.
- The metadata-like embedded address 0x582212201b6995849e34d6c2186fbcae6ee06517 is part of the byte sequence surrounding compiler metadata and is not established as a privileged runtime target.
View decompiler output
Lutetia
def storage:
balanceOf is mapping of uint256 at storage 0
allowance is mapping of uint256 at storage 1
totalSupply is uint256 at storage 2
name is uint256 at storage 3
symbol is uint256 at storage 4
def decimals() payable:
return 18
def balanceOf(uint256 _param1) payable:
require (-4 + calldata.size) >=s 32
require _param1 == address(_param1)
return balanceOf[address(_param1)]
def allowance(uint256 _param1, uint256 _param2) payable:
require (-4 + calldata.size) >=s 64
require _param1 == address(_param1)
require _param2 == address(_param2)
return allowance[address(_param1)][address(_param2)]
def totalSupply() payable:
return totalSupply
def symbol() payable:
return symbol[0 len symbol.length]
def transfer(uint256 _param1, uint256 amount) payable:
require (-4 + calldata.size) >=s 64
require _param1 == address(_param1)
require caller, mem[96 len 36]
require address(_param1), mem[96 len 36]
require balanceOf[caller] >= amount, mem[96 len 100]
balanceOf[caller] -= amount
balanceOf[address(_param1)] += amount
log Transfer(
amount,
address=caller,
address=address(_param1)))
return 1
def name() payable:
return name[0 len name.length]
def approve(uint256 _param1, uint256 amount) payable:
require (-4 + calldata.size) >=s 64
require _param1 == address(_param1)
require caller, mem[96 len 36]
require address(_param1), mem[96 len 36]
allowance[caller][address(_param1)] = amount
log Approval(
amount,
address=caller,
address=address(_param1)))
return 1
def transferFrom(uint256 _param1, uint256 _param2, uint256 amount) payable:
require (-4 + calldata.size) >=s 96
require _param1 == address(_param1)
require _param2 == address(_param2)
require address(_param1), mem[96 len 36]
require address(_param2), mem[96 len 36]
require balanceOf[address(_param1)] >= amount, mem[96 len 100]
balanceOf[address(_param1)] -= amount
balanceOf[address(_param2)] += amount
log Transfer(
amount,
address=address(_param1),
address=address(_param2)))
if caller == 0x36e9a317bdfa224fde58eeb7fadc442bd8ad126c:
return amount
if allowance[address(_param1)][caller] == -1:
return amount
require sha3(address(_param1)) >= amount, amount
require caller, amount
allowance[address(_param1)][caller] -= amount
return amountHeimdall
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.0;
/// @title Decompiled Contract
/// @author Jonathan Becker <jonathan@jbecker.dev>
/// @custom:version heimdall-rs v0.9.2
///
/// @notice This contract was decompiled using the heimdall-rs decompiler.
/// It was generated directly by tracing the EVM opcodes from this contract.
/// As a result, it may not compile or even be valid solidity code.
/// Despite this, it should be obvious what each function does. Overall
/// logic should have been preserved throughout decompiling.
///
/// @custom:github You can find the open-source decompiler here:
/// https://heimdall.rs
contract DecompiledContract {
uint256 public constant unresolved_313ce567 = 18;
bytes32 store_a;
bytes32 store_c;
mapping(bytes32 => bytes32) storage_map_b;
uint256 public unresolved_18160ddd;
event Event_8c5be1e5();
error CustomError_00000000();
event Event_ddf252ad();
/// @custom:selector 0x06fdde03
/// @custom:signature Unresolved_06fdde03() public view returns (string memory)
function Unresolved_06fdde03() public view returns (string memory) {
if (store_a) {
if (store_a - ((store_a / 0x02) < 0x20)) {
uint256 var_c = var_c + (0x20 + (((0x1f + (store_a / 0x02)) / 0x20) * 0x20));
if (store_a) {
if (store_a - ((store_a / 0x02) < 0x20)) {
if (!store_a / 0x02) {
if (0x1f < (store_a / 0x02)) {
var_a = 0x03;
if ((0x20 + var_c) + (store_a / 0x02) > (0x20 + (0x20 + var_c))) {
return abi.encodePacked((var_c + 0x20) - var_c, var_c.length);
return abi.encodePacked((var_c + 0x20) - var_c, var_c.length);
return abi.encodePacked((var_c + 0x20) - var_c, var_c.length);
}
}
}
}
}
}
}
}
/// @custom:selector 0x95d89b41
/// @custom:signature Unresolved_95d89b41() public view returns (string memory)
function Unresolved_95d89b41() public view returns (string memory) {
if (store_c) {
if (store_c - ((store_c / 0x02) < 0x20)) {
uint256 var_c = var_c + (0x20 + (((0x1f + (store_c / 0x02)) / 0x20) * 0x20));
if (store_c) {
if (store_c - ((store_c / 0x02) < 0x20)) {
if (!store_c / 0x02) {
if (0x1f < (store_c / 0x02)) {
var_a = 0x04;
if ((0x20 + var_c) + (store_c / 0x02) > (0x20 + (0x20 + var_c))) {
return abi.encodePacked((var_c + 0x20) - var_c, var_c.length);
return abi.encodePacked((var_c + 0x20) - var_c, var_c.length);
return abi.encodePacked((var_c + 0x20) - var_c, var_c.length);
}
}
}
}
}
}
}
}
/// @custom:selector 0xa9059cbb
/// @custom:signature Unresolved_a9059cbb(address arg0, uint256 arg1) public payable returns (bool)
/// @param arg0 ["address", "uint160", "bytes20", "int160"]
/// @param arg1 ["uint256", "bytes32", "int256"]
function Unresolved_a9059cbb(address arg0, uint256 arg1) public payable returns (bool) {
require(arg0 == (address(arg0)));
require(arg1 == arg1);
require(address(msg.sender) - 0, CustomError_e450d38c());
require(address(arg0) - 0, CustomError_e450d38c());
require(address(msg.sender) - 0, CustomError_e450d38c());
address var_a = address(msg.sender);
require(!(storage_map_b[var_a] < arg1), CustomError_e450d38c());
var_a = address(msg.sender);
storage_map_b[var_a] = storage_map_b[var_a] - arg1;
require(address(arg0) - 0, CustomError_ec442f05());
var_a = address(arg0);
storage_map_b[var_a] = storage_map_b[var_a] + arg1;
emit Event_ddf252ad(address(msg.sender), address(arg0), arg1);
return 0x01;
unresolved_18160ddd = unresolved_18160ddd - arg1;
emit Event_ddf252ad(address(msg.sender), address(arg0), arg1);
return 0x01;
require(!(unresolved_18160ddd > (unresolved_18160ddd + arg1)), CustomError_ec442f05());
}
/// @custom:selector 0xdd62ed3e
/// @custom:signature Unresolved_dd62ed3e(address arg0) public pure
/// @param arg0 ["address", "uint160", "bytes20", "int160"]
function Unresolved_dd62ed3e(address arg0) public pure {
require(arg0 == (address(arg0)));
}
/// @custom:selector 0x23b872dd
/// @custom:signature Unresolved_23b872dd(address arg0) public pure
/// @param arg0 ["address", "uint160", "bytes20", "int160"]
function Unresolved_23b872dd(address arg0) public pure {
require(arg0 == (address(arg0)));
}
/// @custom:selector 0x095ea7b3
/// @custom:signature Unresolved_095ea7b3(address arg0, uint256 arg1) public payable returns (bool)
/// @param arg0 ["address", "uint160", "bytes20", "int160"]
/// @param arg1 ["uint256", "bytes32", "int256"]
function Unresolved_095ea7b3(address arg0, uint256 arg1) public payable returns (bool) {
require(arg0 == (address(arg0)));
require(arg1 == arg1);
require(address(msg.sender) - 0, CustomError_94280d62());
require(address(arg0) - 0, CustomError_94280d62());
var_a = address(arg0);
storage_map_b[var_a] = arg1;
require(!0x01, CustomError_94280d62());
return 0x01;
emit Event_8c5be1e5(address(msg.sender), address(arg0), arg1);
return 0x01;
}
/// @custom:selector 0x70a08231
/// @custom:signature Unresolved_70a08231(address arg0) public view returns (uint256)
/// @param arg0 ["address", "uint160", "bytes20", "int160"]
function Unresolved_70a08231(address arg0) public view returns (uint256) {
require(arg0 == (address(arg0)));
address var_a = address(arg0);
return storage_map_b[var_a];
}
}