payment tests
Some checks failed
Deploy / build-deploy (push) Failing after 4m58s

This commit is contained in:
Jorijn van der Graaf 2026-08-20 01:36:42 +02:00
commit 45992c4f91
10 changed files with 670 additions and 13 deletions

View file

@ -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":")")

View file

@ -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.