Description
The Base58.decodeWord() function has a sanitizer ordering bug: it performs mload(c) before validating that c is within the lookup table bounds. When a byte with ASCII value < 0x31 (e.g. 0) is passed, c underflows to ~2^256, causing EVM memory expansion that exceeds block gas limits and triggers OutOfGas instead of the expected clean revert.
PoC
function testDecodeWordSanitizerOrderingBug() public {
uint256 g = gasleft();
vm.expectRevert(Base58.Base58DecodingError.selector);
this.decodeBytes("0");
uint256 decodeGas = g - gasleft();
assertLt(decodeGas, 7_000, "decode('0') should revert cleanly using less than 7000 gas");
vm.expectRevert(); // InvalidOperandOOG — no revert data. See -vvvv trace.
this.decodeWord("0");
g = gasleft();
(bool success, bytes memory reason) =
address(this).call(abi.encodeWithSelector(this.decodeWord.selector, "0"));
uint256 decodeWordGas = g - gasleft();
assertFalse(success, "decodeWord('0') should revert with InvalidOperandOOG");
assertEq(reason.length, 0, "decodeWord('0') should revert without return data");
assertGt(decodeWordGas, 90_000_000, "decodeWord('0') should use more than 90M gas");
}
function decodeBytes(string memory encoded) public pure returns (bytes memory) {
return Base58.decode(encoded);
}
Description
The
Base58.decodeWord()function has a sanitizer ordering bug: it performsmload(c)before validating thatcis within the lookup table bounds. When a byte with ASCII value < 0x31 (e.g. 0) is passed,cunderflows to ~2^256, causing EVM memory expansion that exceeds block gas limits and triggers OutOfGas instead of the expected clean revert.PoC