This commit is contained in:
parent
47d302a9a4
commit
45992c4f91
10 changed files with 670 additions and 13 deletions
|
|
@ -216,6 +216,25 @@ bool JsonRpcIdIs(std::string_view json, std::int64_t want) {
|
|||
return static_cast<std::int64_t>(id->number) == want;
|
||||
}
|
||||
|
||||
std::string BalanceOfCallData(std::string_view address) {
|
||||
// Selector, then one 32-byte ABI word: 12 zero BYTES of left padding (24
|
||||
// hex digits), then the 20 address bytes (40 hex digits). The first
|
||||
// version of this appended 24 bytes of padding — bytes and hex digits
|
||||
// confused — which shifted the address past the argument word. Solidity's
|
||||
// decoder read the word's low 20 bytes (mostly padding plus the address's
|
||||
// first 8 bytes), found a valid-looking address with nothing on it, and
|
||||
// answered 0: every real payment read as "not arrived", with no error on
|
||||
// either side. Pinned byte-for-byte in ShouldParseEurcChains, and proven
|
||||
// against a real chain in ShouldSettleEurcOnTestnet — the suite that
|
||||
// caught it.
|
||||
std::string data;
|
||||
data.reserve(2 + 8 + 64);
|
||||
data += kBalanceOfSelector;
|
||||
data.append(12 * 2, '0');
|
||||
data += address.substr(2);
|
||||
return data;
|
||||
}
|
||||
|
||||
std::optional<std::int64_t> ParseEthCallUint(std::string_view json) {
|
||||
auto doc = Json::Parse(json);
|
||||
if (!doc || !doc->IsObject()) return std::nullopt;
|
||||
|
|
@ -809,13 +828,9 @@ private:
|
|||
const std::string& address) {
|
||||
// A node that is not serving this chain does not get to answer.
|
||||
if (!EndpointServesChain(chain, url)) return std::nullopt;
|
||||
// eth_call to the token contract. The address is left-padded into a
|
||||
// 32-byte ABI word: 24 zero bytes, then the 20 address bytes.
|
||||
std::string data;
|
||||
data.reserve(2 + 8 + 64);
|
||||
data += kBalanceOfSelector;
|
||||
data.append(24 * 2, '0');
|
||||
data += address.substr(2);
|
||||
// eth_call to the token contract; the calldata encoding lives in
|
||||
// BalanceOfCallData, where the self-test can pin it.
|
||||
const std::string data = BalanceOfCallData(address);
|
||||
|
||||
const std::string body =
|
||||
std::string(R"({"jsonrpc":"2.0","id":1,"method":"eth_call","params":[{"to":")")
|
||||
|
|
|
|||
|
|
@ -410,6 +410,16 @@ export namespace Catcrafts::Server {
|
|||
// the HTTP around it is thin.
|
||||
std::optional<std::int64_t> ParseEthCallUint(std::string_view json);
|
||||
|
||||
// The eth_call data for balanceOf(address): the 4-byte selector, then the
|
||||
// address left-padded into ONE 32-byte ABI word. Exported for the
|
||||
// self-test for the same reason as ParseEthCallUint, learned the hard
|
||||
// way: this encoding once padded 24 zero bytes instead of 12 (a
|
||||
// bytes-vs-hex-digits slip), which pushed the address out of the argument
|
||||
// word — every node then answered balanceOf of a zero-balance garbage
|
||||
// address, cleanly, and paid orders read as unpaid forever. No error
|
||||
// anywhere; the testnet e2e suite is what caught it.
|
||||
std::string BalanceOfCallData(std::string_view address);
|
||||
|
||||
// nullptr when the chains file or the address pool will not load. The rail
|
||||
// holds no key and no credential; it can only ever hand out an address it
|
||||
// was given.
|
||||
|
|
|
|||
Loading…
Reference in a new issue