From f374f390bb9e153f9dd0ae335a30a2d2f9224a64 Mon Sep 17 00:00:00 2001 From: krandder Date: Sun, 12 Jul 2026 22:58:15 -0300 Subject: [PATCH 1/4] Add agent work publication lane --- README.md | 3 + docs/agent-work-v1.md | 87 +++++ metadata/agent-work-index.json | 20 + src/AgentWorkIndex.sol | 25 ++ test/AgentDocumentGolden.t.sol | 52 +++ test/AgentWorkIndex.t.sol | 89 +++++ tools/agent_documents.py | 438 ++++++++++++++++++++++ tools/agent_work_index_code_hashes.py | 152 ++++++++ tools/fixtures/agent-document-golden.json | 85 +++++ tools/test_agent_documents.py | 164 ++++++++ 10 files changed, 1115 insertions(+) create mode 100644 docs/agent-work-v1.md create mode 100644 metadata/agent-work-index.json create mode 100644 src/AgentWorkIndex.sol create mode 100644 test/AgentDocumentGolden.t.sol create mode 100644 test/AgentWorkIndex.t.sol create mode 100644 tools/agent_documents.py create mode 100644 tools/agent_work_index_code_hashes.py create mode 100644 tools/fixtures/agent-document-golden.json create mode 100644 tools/test_agent_documents.py diff --git a/README.md b/README.md index d472b84..65195bb 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,8 @@ # Futarchy Autonomous Optimizer (FAO) +The [agent-work v1 specification](docs/agent-work-v1.md) defines the ownerless publication index, +canonical task/receipt/payment documents, and their exact binding to typed treasury transfers. + This repository contains the smart contracts for the Futarchy Autonomous Optimizer token (FAO) and its sale mechanics. The codebase is implemented with [Foundry](https://book.getfoundry.sh/) and relies on OpenZeppelin libraries for security-reviewed primitives. ## Contracts diff --git a/docs/agent-work-v1.md b/docs/agent-work-v1.md new file mode 100644 index 0000000..e51b504 --- /dev/null +++ b/docs/agent-work-v1.md @@ -0,0 +1,87 @@ +# FAO agent-work documents v1 + +Lane 5 records work and proposed payment without adding a second governance or custody system. +The reviewed `TransferAction` remains the only payment primitive. The optional `AgentWorkIndex` +is one ownerless, storage-free event log shared by every FAO on a chain; economics never read it. + +## Publication + +```solidity +publish(bytes32 kind, bytes32 parentDigest, bytes document) returns (bytes32 documentDigest) +``` + +`documentDigest` is `keccak256(document)`. `Published` indexes the kind, parent, and digest and +carries the publisher and exact document bytes. The contract accepts every kind and every nonempty +byte string. It has no owner, allowlist, size cap, deduplication, custody, withdrawal, endorsement, +or payment authority. Gas is the spam bound; clients filter and deduplicate. Earliest finalized log +ordering is only evidence for humans, challengers, and markets. + +The v1 kind values are: + +- `keccak256("FAO_AGENT_TASK_V1")` +- `keccak256("FAO_AGENT_RECEIPT_V1")` +- `keccak256("FAO_AGENT_PAYMENT_V1")` + +## Canonical JSON bytes + +Documents are UTF-8 without a BOM and contain one top-level object. Arrays keep their given order. +Object keys are sorted by Unicode codepoint. There is no insignificant whitespace. Every scalar +leaf is a JSON string: numbers, booleans, and null are forbidden, and an absent optional value means +the key is omitted. + +Strings escape only quote, backslash, and U+0000 through U+001F. Every control is the lowercase +six-byte form `\u00xx`, including newline and tab; non-ASCII text is raw UTF-8. Unpaired surrogates +are invalid. Addresses are lowercase 20-byte hex, digests are lowercase 32-byte hex, and integers +are unsigned decimal strings without signs or leading zeroes. The tools normalize semantic builder +input, but validators reject exact event bytes whose schema values are not already canonical. + +Raw bytes are authoritative: their digest is always Keccak-256. Canonicalization is required for +the three known schemas, not for arbitrary future index kinds. + +## Schemas and lineage + +A task uses `parentDigest = bytes32(0)`: + +```json +{"v":"1","kind":"fao.task","chainId":"11155111","vault":"0x…","title":"…","spec":"…","salt":"0x…"} +``` + +It must contain either `spec`, or both `specDigest` and advisory `specUri`. `deadline` is optional. +`reward:{"asset":"0x…","amount":"…"}` is optional and advisory; publication creates no bounty +or obligation. + +A receipt uses `parentDigest = taskDigest`: + +```json +{"v":"1","kind":"fao.receipt","chainId":"…","vault":"0x…","task":"0x…","worker":"0x…","artifacts":[{"digest":"0x…","uri":"…"}],"summary":"…","salt":"0x…"} +``` + +Each artifact commits to the exact artifact bytes with Keccak-256. Its URI is advisory and limited +to 256 UTF-8 bytes by the v1 tools; `note` is optional. No CID or IPFS dependency exists. + +A payment envelope uses `parentDigest = receiptDigest`: + +```json +{"v":"1","kind":"fao.payment","chainId":"…","vault":"0x…","asset":"0x…","recipient":"0x…","amount":"…","task":"0x…","receipt":"0x…","salt":"0x…"} +``` + +`note` is optional. The transfer is exactly +`{asset, recipient, amount, salt: keccak256(envelopeBytes)}`. Envelope chain, vault, asset, +recipient, and amount must match the proposed action. The envelope's own salt permits a fresh +identity when rejected or expired economics are proposed again. The resulting transfer hash is the +proposal ID and remains chain- and vault-domain-separated by `FAOTreasuryActions`. + +## Payment meaning + +Acceptance means authorized, executable while funded, and never partially paid. Funds are neither +reserved nor escrowed: ragequit, buyback, or another action may consume them before execution. A +shortfall reverts atomically and can be retried before expiry. There are no v1 bids, guaranteed +bounties, slashing, exclusive winners, copying rules, or self-payment exceptions. Every proposer, +worker, and recipient goes through the same immutable transfer limits and futarchy path. + +The dependency-free implementations and shared golden vectors are +[`tools/agent_documents.py`](../tools/agent_documents.py) and +[`tools/fixtures/agent-document-golden.json`](../tools/fixtures/agent-document-golden.json). +The separate singleton build, runtime hash, CREATE2 salt, and predicted address are pinned in +[`metadata/agent-work-index.json`](../metadata/agent-work-index.json); verify them with +`python3 tools/agent_work_index_code_hashes.py --check`. diff --git a/metadata/agent-work-index.json b/metadata/agent-work-index.json new file mode 100644 index 0000000..ed141d8 --- /dev/null +++ b/metadata/agent-work-index.json @@ -0,0 +1,20 @@ +{ + "schemaVersion": 1, + "source": "src/AgentWorkIndex.sol", + "contract": "AgentWorkIndex", + "compiler": { + "solcVersion": "0.8.20+commit.a1b79de6", + "solcSettingsKeccak256": "0xb68de617889310001775008072534de8400024510d5e09eaad212e2b3f942376" + }, + "creationCodeBytes": 401, + "creationCodeKeccak256": "0xd8e698ee533ee2e293601190990c3762df48b66e8f8de0d7a7f4ef7ac797a781", + "runtimeCodeBytes": 374, + "runtimeCodeKeccak256": "0x2e8a42e082d6ee00eb3edfe933feef37e2eb9a81745c440c51e2af6c9b4ad5e1", + "create2": { + "deployer": "0x4e59b44847b379578588920ca78fbf26c0b4956c", + "saltPreimage": "FAO_AGENT_WORK_INDEX_V1", + "salt": "0x42e43b332008715cd34a0232b58cd6b4efa707de7fd9c87bac4fab22f11285d4", + "predictedAddress": "0x07db8c5bd5a2ea12de1d5a322b73cea536afff49" + }, + "economicBytecodeIncluded": false +} diff --git a/src/AgentWorkIndex.sol b/src/AgentWorkIndex.sol new file mode 100644 index 0000000..25a1ad0 --- /dev/null +++ b/src/AgentWorkIndex.sol @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +/// @notice Ownerless publication log for agent-work documents. +/// @dev It has no storage, authority, or custody; FAO economics never read it. +contract AgentWorkIndex { + error EmptyDocument(); + + event Published( + bytes32 indexed kind, + bytes32 indexed parentDigest, + bytes32 indexed documentDigest, + address publisher, + bytes document + ); + + function publish(bytes32 kind, bytes32 parentDigest, bytes calldata document) + external + returns (bytes32 documentDigest) + { + if (document.length == 0) revert EmptyDocument(); + documentDigest = keccak256(document); + emit Published(kind, parentDigest, documentDigest, msg.sender, document); + } +} diff --git a/test/AgentDocumentGolden.t.sol b/test/AgentDocumentGolden.t.sol new file mode 100644 index 0000000..405fd87 --- /dev/null +++ b/test/AgentDocumentGolden.t.sol @@ -0,0 +1,52 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import {Test} from "forge-std/Test.sol"; + +import {FAOTreasuryActions} from "../src/FAOTreasuryActions.sol"; + +contract AgentDocumentGoldenTest is Test { + function testPaymentEnvelopeMatchesPythonAndJavascriptGolden() public pure { + bytes memory document = + hex"7b22616d6f756e74223a22313135373932303839323337333136313935343233353730393835303038363837393037383533323639393834363635363430353634303339343537353834303037393133313239363339393335222c226173736574223a22307863636363636363636363636363636363636363636363636363636363636363636363636363636363222c22636861696e4964223a223131313535313131222c226b696e64223a2266616f2e7061796d656e74222c2272656365697074223a22307862633135636630316130393134303863313661623834333431613261656264613533343032623861333162343534333139396334643262313736643437336435222c22726563697069656e74223a22307864646464646464646464646464646464646464646464646464646464646464646464646464646464222c2273616c74223a22307865666566656665666566656665666566656665666566656665666566656665666566656665666566656665666566656665666566656665666566656665666566222c227461736b223a22307832336166653436316238643336653963306631633035303365316563316139386164373933373734383332346363343037333137343037386133306532333335222c2276223a2231222c227661756c74223a22307861616161616161616161616161616161616161616161616161616161616161616161616161616161227d"; + bytes32 envelopeDigest = 0x60fccea2c2617a6e1f35bb536e2a48b7384a8b6d91d3486075c1f77981eadc18; + assertEq(keccak256(document), envelopeDigest); + + FAOTreasuryActions.TransferAction memory action = FAOTreasuryActions.TransferAction({ + asset: address(uint160(0x00cccccccccccccccccccccccccccccccccccccccc)), + recipient: address(uint160(0x00dddddddddddddddddddddddddddddddddddddddd)), + amount: type(uint256).max, + salt: envelopeDigest + }); + bytes memory payload = FAOTreasuryActions.transferEvaluationPayload( + 11_155_111, address(uint160(0x00aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa)), action + ); + assertEq( + payload, + hex"27e49851e3b79673e847d7c12acc52a3936006b8517243a42df902b3df4e902e0000000000000000000000000000000000000000000000000000000000aa36a7000000000000000000000000aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa000000000000000000000000cccccccccccccccccccccccccccccccccccccccc000000000000000000000000ddddddddddddddddddddddddddddddddddddddddffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff60fccea2c2617a6e1f35bb536e2a48b7384a8b6d91d3486075c1f77981eadc18" + ); + bytes32 actionHash = FAOTreasuryActions.transferHash( + 11_155_111, address(uint160(0x00aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa)), action + ); + assertEq(actionHash, 0x24a4125ae332e790e5fa9cbdc5d243b63a026e7b96fc9e796fd6d87706f2a87e); + assertEq( + uint256(actionHash), + 16_573_152_149_377_518_413_156_023_629_436_668_511_050_935_944_683_357_744_569_870_274_751_223_736_446 + ); + } + + function testAgentKindPreimagesMatchGolden() public pure { + assertEq( + keccak256("FAO_AGENT_TASK_V1"), + 0xa87c1f2bd1ee275d3f44c021b929709db51ad8a945c2c34a0857974e28595821 + ); + assertEq( + keccak256("FAO_AGENT_RECEIPT_V1"), + 0xe2c91b1ce0f47a0ac033aec054b0ce8dd8a0ca22e4812f61776ed60835734da1 + ); + assertEq( + keccak256("FAO_AGENT_PAYMENT_V1"), + 0x8161a6637134aa32b72dafb5032097de770b8555713c2415d800ea5af322c7bd + ); + } +} diff --git a/test/AgentWorkIndex.t.sol b/test/AgentWorkIndex.t.sol new file mode 100644 index 0000000..8c1e2a2 --- /dev/null +++ b/test/AgentWorkIndex.t.sol @@ -0,0 +1,89 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import {Test} from "forge-std/Test.sol"; + +import {AgentWorkIndex} from "../src/AgentWorkIndex.sol"; + +contract AgentWorkToken { + mapping(address => uint256) public balanceOf; + + function mint(address recipient, uint256 amount) external { + balanceOf[recipient] += amount; + } + + function transfer(address recipient, uint256 amount) external returns (bool) { + balanceOf[msg.sender] -= amount; + balanceOf[recipient] += amount; + return true; + } +} + +contract ForceAgentWorkEther { + constructor() payable {} + + function destroy(address payable recipient) external { + selfdestruct(recipient); + } +} + +contract AgentWorkIndexTest is Test { + event Published( + bytes32 indexed kind, + bytes32 indexed parentDigest, + bytes32 indexed documentDigest, + address publisher, + bytes document + ); + + AgentWorkIndex internal index; + + function setUp() public { + index = new AgentWorkIndex(); + } + + function testRejectsEmptyDocument() public { + vm.expectRevert(AgentWorkIndex.EmptyDocument.selector); + index.publish(bytes32(0), bytes32(0), ""); + } + + function testFuzzPublishesExactDigestAndEvent( + bytes32 kind, + bytes32 parentDigest, + address publisher, + bytes calldata rawDocument + ) public { + bytes memory document = rawDocument; + vm.assume(publisher != address(0)); + vm.assume(document.length != 0 && document.length <= 4096); + bytes32 digest = keccak256(document); + + vm.expectEmit(true, true, true, true, address(index)); + emit Published(kind, parentDigest, digest, publisher, document); + vm.prank(publisher); + assertEq(index.publish(kind, parentDigest, document), digest); + } + + function testPublicationDoesNotWriteStorage() public { + bytes32 slot = keccak256("arbitrary slot"); + assertEq(vm.load(address(index), slot), bytes32(0)); + index.publish(bytes32(0), bytes32(0), "document"); + assertEq(vm.load(address(index), slot), bytes32(0)); + } + + function testForcedAssetsCreateNoWithdrawalSeam() public { + AgentWorkToken token = new AgentWorkToken(); + token.mint(address(index), 12 ether); + ForceAgentWorkEther force = new ForceAgentWorkEther{value: 1 ether}(); + force.destroy(payable(address(index))); + + assertEq(token.balanceOf(address(index)), 12 ether); + assertEq(address(index).balance, 1 ether); + + (bool success,) = address(index) + .call(abi.encodeCall(token.transfer, (address(this), token.balanceOf(address(index))))); + assertFalse(success); + assertEq(token.balanceOf(address(index)), 12 ether); + assertEq(address(index).balance, 1 ether); + } +} diff --git a/tools/agent_documents.py b/tools/agent_documents.py new file mode 100644 index 0000000..175a40e --- /dev/null +++ b/tools/agent_documents.py @@ -0,0 +1,438 @@ +#!/usr/bin/env python3 +"""Build and verify canonical FAO agent-work documents and index messages.""" + +from __future__ import annotations + +import argparse +import json +import re +import sys +from pathlib import Path +from typing import Any, Callable + +try: + from tools.flm_code_hashes import keccak256 +except ModuleNotFoundError: # Direct script execution. + from flm_code_hashes import keccak256 # type: ignore + + +ZERO_DIGEST = "0x" + "00" * 32 +TASK_KIND = "0xa87c1f2bd1ee275d3f44c021b929709db51ad8a945c2c34a0857974e28595821" +RECEIPT_KIND = "0xe2c91b1ce0f47a0ac033aec054b0ce8dd8a0ca22e4812f61776ed60835734da1" +PAYMENT_KIND = "0x8161a6637134aa32b72dafb5032097de770b8555713c2415d800ea5af322c7bd" +TRANSFER_KIND = "0x27e49851e3b79673e847d7c12acc52a3936006b8517243a42df902b3df4e902e" +PUBLISH_SELECTOR = "52bf8ff2" +PUBLISHED_TOPIC = "0x9b8065b31fd378509bae92224c8f432ce836e42765fe48ed19a4c94713cc24a4" +UINT256_MAX = (1 << 256) - 1 + + +class DocumentError(ValueError): + pass + + +def _raw_bytes(value: Any, label: str = "document") -> bytes: + if isinstance(value, bytes): + return value + if isinstance(value, bytearray): + return bytes(value) + if isinstance(value, memoryview): + return value.tobytes() + if isinstance(value, str): + try: + return value.encode("utf-8") + except UnicodeEncodeError as exc: + raise DocumentError(f"{label} must be valid UTF-8") from exc + raise DocumentError(f"{label} must be bytes or text") + + +def _escape(value: str) -> str: + output = ['"'] + for character in value: + codepoint = ord(character) + if character == '"': + output.append('\\"') + elif character == "\\": + output.append("\\\\") + elif codepoint < 0x20: + output.append(f"\\u{codepoint:04x}") + else: + output.append(character) + output.append('"') + return "".join(output) + + +def _canonical_text(value: Any) -> str: + if isinstance(value, str): + return _escape(value) + if isinstance(value, list): + return "[" + ",".join(_canonical_text(item) for item in value) + "]" + if isinstance(value, dict): + if any(not isinstance(key, str) for key in value): + raise DocumentError("document keys must be strings") + return "{" + ",".join( + _escape(key) + ":" + _canonical_text(value[key]) for key in sorted(value) + ) + "}" + raise DocumentError("every scalar leaf must be a JSON string") + + +def canonicalize(value: Any) -> bytes: + if not isinstance(value, dict): + raise DocumentError("document must be a top-level object") + try: + return _canonical_text(value).encode("utf-8") + except UnicodeEncodeError as exc: + raise DocumentError("document must not contain unpaired Unicode surrogates") from exc + + +def parse_canonical(value: Any) -> dict[str, Any]: + raw = _raw_bytes(value) + try: + text = raw.decode("utf-8") + document = json.loads(text) + except (UnicodeDecodeError, json.JSONDecodeError) as exc: + raise DocumentError("document is not valid UTF-8 JSON") from exc + if not isinstance(document, dict) or canonicalize(document) != raw: + raise DocumentError("document bytes are not canonical") + return document + + +def document_digest(value: Any, hash_: Callable[[bytes], str] = keccak256) -> str: + return hash_(_raw_bytes(value)) + + +def _record(value: Any, required: set[str], optional: set[str], label: str) -> dict[str, Any]: + if not isinstance(value, dict): + raise DocumentError(f"{label} must be an object") + keys = set(value) + if not required <= keys or keys - required - optional: + raise DocumentError(f"{label} has invalid fields") + return value + + +def _text(value: Any, label: str, *, nonempty: bool = False, max_bytes: int | None = None) -> str: + if not isinstance(value, str): + raise DocumentError(f"{label} must be a string") + try: + size = len(value.encode("utf-8")) + except UnicodeEncodeError as exc: + raise DocumentError(f"{label} must be valid UTF-8") from exc + if nonempty and size == 0: + raise DocumentError(f"{label} cannot be empty") + if max_bytes is not None and size > max_bytes: + raise DocumentError(f"{label} exceeds {max_bytes} UTF-8 bytes") + return value + + +def _decimal(value: Any, label: str, *, positive: bool = False) -> str: + if not isinstance(value, str) or not re.fullmatch(r"0|[1-9][0-9]*", value): + raise DocumentError(f"{label} must be an unsigned canonical decimal string") + number = int(value) + if number > UINT256_MAX or (positive and number == 0): + raise DocumentError(f"{label} must fit uint256" + (" and be positive" if positive else "")) + return value + + +def _hex(value: Any, size: int, label: str) -> str: + if not isinstance(value, str) or not re.fullmatch(rf"0x[0-9a-fA-F]{{{size * 2}}}", value): + raise DocumentError(f"{label} must be {size}-byte 0x hex") + return value.lower() + + +def _address(value: Any, label: str, *, allow_zero: bool = False) -> str: + address = _hex(value, 20, label) + if not allow_zero and address == "0x" + "00" * 20: + raise DocumentError(f"{label} cannot be zero") + return address + + +def _digest(value: Any, label: str) -> str: + return _hex(value, 32, label) + + +def _input(value: Any) -> dict[str, Any]: + return parse_canonical(value) if isinstance(value, (bytes, bytearray, memoryview, str)) else value + + +def _validated(value: Any, normalized: dict[str, Any]) -> dict[str, Any]: + if isinstance(value, (bytes, bytearray, memoryview, str)) and canonicalize(normalized) != _raw_bytes(value): + raise DocumentError("document values are not in canonical schema form") + return normalized + + +def validate_task(value: Any) -> dict[str, Any]: + raw = _record( + _input(value), + {"v", "kind", "chainId", "vault", "title", "salt"}, + {"spec", "specDigest", "specUri", "deadline", "reward"}, + "task", + ) + inline = "spec" in raw + external = "specDigest" in raw or "specUri" in raw + if inline == external or (external and not {"specDigest", "specUri"} <= set(raw)): + raise DocumentError("task requires exactly spec or specDigest plus specUri") + if raw["v"] != "1" or raw["kind"] != "fao.task": + raise DocumentError("task version or kind is invalid") + task: dict[str, Any] = { + "v": "1", + "kind": "fao.task", + "chainId": _decimal(raw["chainId"], "task.chainId", positive=True), + "vault": _address(raw["vault"], "task.vault"), + "title": _text(raw["title"], "task.title", nonempty=True), + "salt": _digest(raw["salt"], "task.salt"), + } + if inline: + task["spec"] = _text(raw["spec"], "task.spec", nonempty=True) + else: + task["specDigest"] = _digest(raw["specDigest"], "task.specDigest") + task["specUri"] = _text(raw["specUri"], "task.specUri", nonempty=True, max_bytes=256) + if "deadline" in raw: + task["deadline"] = _decimal(raw["deadline"], "task.deadline") + if "reward" in raw: + reward = _record(raw["reward"], {"asset", "amount"}, set(), "task.reward") + task["reward"] = { + "asset": _address(reward["asset"], "task.reward.asset", allow_zero=True), + "amount": _decimal(reward["amount"], "task.reward.amount", positive=True), + } + return _validated(value, task) + + +def validate_receipt(value: Any) -> dict[str, Any]: + raw = _record( + _input(value), + {"v", "kind", "chainId", "vault", "task", "worker", "artifacts", "summary", "salt"}, + set(), + "receipt", + ) + if raw["v"] != "1" or raw["kind"] != "fao.receipt": + raise DocumentError("receipt version or kind is invalid") + if not isinstance(raw["artifacts"], list) or not raw["artifacts"]: + raise DocumentError("receipt.artifacts must be a nonempty array") + artifacts = [] + for index, value_ in enumerate(raw["artifacts"]): + artifact = _record(value_, {"digest", "uri"}, {"note"}, f"receipt.artifacts[{index}]") + normalized = { + "digest": _digest(artifact["digest"], f"receipt.artifacts[{index}].digest"), + "uri": _text( + artifact["uri"], f"receipt.artifacts[{index}].uri", nonempty=True, max_bytes=256 + ), + } + if "note" in artifact: + normalized["note"] = _text(artifact["note"], f"receipt.artifacts[{index}].note") + artifacts.append(normalized) + return _validated(value, { + "v": "1", + "kind": "fao.receipt", + "chainId": _decimal(raw["chainId"], "receipt.chainId", positive=True), + "vault": _address(raw["vault"], "receipt.vault"), + "task": _digest(raw["task"], "receipt.task"), + "worker": _address(raw["worker"], "receipt.worker"), + "artifacts": artifacts, + "summary": _text(raw["summary"], "receipt.summary", nonempty=True), + "salt": _digest(raw["salt"], "receipt.salt"), + }) + + +def validate_payment(value: Any) -> dict[str, Any]: + raw = _record( + _input(value), + {"v", "kind", "chainId", "vault", "asset", "recipient", "amount", "task", "receipt", "salt"}, + {"note"}, + "payment", + ) + if raw["v"] != "1" or raw["kind"] != "fao.payment": + raise DocumentError("payment version or kind is invalid") + payment = { + "v": "1", + "kind": "fao.payment", + "chainId": _decimal(raw["chainId"], "payment.chainId", positive=True), + "vault": _address(raw["vault"], "payment.vault"), + "asset": _address(raw["asset"], "payment.asset", allow_zero=True), + "recipient": _address(raw["recipient"], "payment.recipient"), + "amount": _decimal(raw["amount"], "payment.amount", positive=True), + "task": _digest(raw["task"], "payment.task"), + "receipt": _digest(raw["receipt"], "payment.receipt"), + "salt": _digest(raw["salt"], "payment.salt"), + } + if "note" in raw: + payment["note"] = _text(raw["note"], "payment.note") + return _validated(value, payment) + + +def build_task(value: Any) -> bytes: + return canonicalize(validate_task(value)) + + +def build_receipt(value: Any) -> bytes: + return canonicalize(validate_receipt(value)) + + +def build_payment(value: Any) -> bytes: + return canonicalize(validate_payment(value)) + + +def _word(value: int) -> bytes: + if value < 0 or value > UINT256_MAX: + raise DocumentError("ABI integer does not fit uint256") + return value.to_bytes(32, "big") + + +def _hex_bytes(value: Any, size: int, label: str) -> bytes: + return bytes.fromhex(_hex(value, size, label)[2:]) + + +def _address_word(value: Any, label: str, *, allow_zero: bool = False) -> bytes: + return b"\x00" * 12 + bytes.fromhex(_address(value, label, allow_zero=allow_zero)[2:]) + + +def payment_transfer_action(value: Any, hash_: Callable[[bytes], str] = keccak256) -> dict[str, str]: + payment = validate_payment(value) + document = build_payment(payment) + return { + "asset": payment["asset"], + "recipient": payment["recipient"], + "amount": payment["amount"], + "salt": document_digest(document, hash_), + } + + +def transfer_evaluation_payload(chain_id: Any, vault: Any, action: Any) -> bytes: + action = _record(action, {"asset", "recipient", "amount", "salt"}, set(), "TransferAction") + return b"".join( + ( + _hex_bytes(TRANSFER_KIND, 32, "transfer kind"), + _word(int(_decimal(str(chain_id), "chainId", positive=True))), + _address_word(vault, "vault"), + _address_word(action["asset"], "TransferAction.asset", allow_zero=True), + _address_word(action["recipient"], "TransferAction.recipient"), + _word(int(_decimal(str(action["amount"]), "TransferAction.amount", positive=True))), + _hex_bytes(action["salt"], 32, "TransferAction.salt"), + ) + ) + + +def transfer_hash( + chain_id: Any, + vault: Any, + action: Any, + hash_: Callable[[bytes], str] = keccak256, +) -> str: + return hash_(transfer_evaluation_payload(chain_id, vault, action)) + + +def validate_payment_binding( + value: Any, + chain_id: Any, + vault: Any, + action: Any, + hash_: Callable[[bytes], str] = keccak256, +) -> str: + payment = validate_payment(value) + expected = payment_transfer_action(payment, hash_) + normalized_action = { + "asset": _address(action.get("asset"), "TransferAction.asset", allow_zero=True), + "recipient": _address(action.get("recipient"), "TransferAction.recipient"), + "amount": _decimal(str(action.get("amount")), "TransferAction.amount", positive=True), + "salt": _digest(action.get("salt"), "TransferAction.salt"), + } + if payment["chainId"] != _decimal(str(chain_id), "chainId", positive=True): + raise DocumentError("payment chainId does not match") + if payment["vault"] != _address(vault, "vault"): + raise DocumentError("payment vault does not match") + if normalized_action != expected: + raise DocumentError("payment does not bind the exact TransferAction") + return transfer_hash(chain_id, vault, normalized_action, hash_) + + +def publish_calldata(kind: Any, parent_digest: Any, document: Any) -> str: + raw = _raw_bytes(document) + if not raw: + raise DocumentError("document cannot be empty") + padding = (-len(raw)) % 32 + encoded = b"".join( + ( + _hex_bytes(kind, 32, "kind"), + _hex_bytes(parent_digest, 32, "parentDigest"), + _word(96), + _word(len(raw)), + raw, + b"\x00" * padding, + ) + ) + return "0x" + PUBLISH_SELECTOR + encoded.hex() + + +def prepare_publication(schema: str, value: Any) -> dict[str, Any]: + try: + validate, build, kind = { + "task": (validate_task, build_task, TASK_KIND), + "receipt": (validate_receipt, build_receipt, RECEIPT_KIND), + "payment": (validate_payment, build_payment, PAYMENT_KIND), + }[schema] + except KeyError as exc: + raise DocumentError("schema must be task, receipt, or payment") from exc + normalized = validate(value) + document = build(normalized) + parent = ZERO_DIGEST if schema == "task" else normalized["task" if schema == "receipt" else "receipt"] + return { + "kind": kind, + "parentDigest": parent, + "documentDigest": document_digest(document), + "document": document, + "calldata": publish_calldata(kind, parent, document), + } + + +def decode_published_log(log: Any) -> dict[str, Any]: + log = _record(log, {"topics", "data"}, {"address"}, "Published log") + topics = log["topics"] + if not isinstance(topics, list) or len(topics) != 4 or _digest(topics[0], "event topic") != PUBLISHED_TOPIC: + raise DocumentError("Published log topics are invalid") + data = _hex_bytes_variable(log["data"], "Published log data") + if len(data) < 96 or len(data) % 32 or data[:12] != b"\x00" * 12 or int.from_bytes(data[32:64], "big") != 64: + raise DocumentError("Published log data are invalid") + size = int.from_bytes(data[64:96], "big") + padded = (size + 31) // 32 * 32 + if len(data) != 96 + padded or any(data[96 + size :]): + raise DocumentError("Published log document encoding is invalid") + document = data[96 : 96 + size] + digest = _digest(topics[3], "documentDigest") + if not document or document_digest(document) != digest: + raise DocumentError("Published log document digest is invalid") + return { + "kind": _digest(topics[1], "kind"), + "parentDigest": _digest(topics[2], "parentDigest"), + "documentDigest": digest, + "publisher": "0x" + data[12:32].hex(), + "document": document, + } + + +def _hex_bytes_variable(value: Any, label: str) -> bytes: + if not isinstance(value, str) or not re.fullmatch(r"0x(?:[0-9a-fA-F]{2})*", value): + raise DocumentError(f"{label} must be even-length 0x hex") + return bytes.fromhex(value[2:]) + + +VALIDATORS = {"task": validate_task, "receipt": validate_receipt, "payment": validate_payment} + + +def main(argv: list[str] | None = None) -> int: + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("schema", choices=tuple(VALIDATORS)) + parser.add_argument("path", nargs="?", default="-", help="JSON file or - for stdin") + parser.add_argument("--digest", action="store_true", help="print Keccak-256 instead of JSON") + args = parser.parse_args(argv) + try: + raw = sys.stdin.buffer.read() if args.path == "-" else Path(args.path).read_bytes() + value = json.loads(raw) + document = canonicalize(VALIDATORS[args.schema](value)) + output = document_digest(document) + "\n" if args.digest else document.decode() + "\n" + except (OSError, json.JSONDecodeError, DocumentError) as exc: + print(f"error: {exc}", file=sys.stderr) + return 1 + sys.stdout.write(output) + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/tools/agent_work_index_code_hashes.py b/tools/agent_work_index_code_hashes.py new file mode 100644 index 0000000..4dd6868 --- /dev/null +++ b/tools/agent_work_index_code_hashes.py @@ -0,0 +1,152 @@ +#!/usr/bin/env python3 +"""Generate the standalone AgentWorkIndex CREATE2 and bytecode evidence.""" + +from __future__ import annotations + +import argparse +import json +import re +import shutil +import subprocess +import sys +from pathlib import Path +from typing import Any + +try: + from tools.flm_code_hashes import keccak256 +except ModuleNotFoundError: # Direct script execution. + from flm_code_hashes import keccak256 # type: ignore + + +ROOT = Path(__file__).resolve().parents[1] +SOURCE = "src/AgentWorkIndex.sol" +CONTRACT = "AgentWorkIndex" +ARTIFACT = Path("out/AgentWorkIndex.sol/AgentWorkIndex.json") +MANIFEST = Path("metadata/agent-work-index.json") +CREATE2_PROXY = "0x4e59b44847b379578588920ca78fbf26c0b4956c" +SALT_PREIMAGE = "FAO_AGENT_WORK_INDEX_V1" + + +class EvidenceError(ValueError): + pass + + +def _run(command: list[str], root: Path) -> None: + try: + subprocess.run(command, cwd=root, check=True) + except (OSError, subprocess.CalledProcessError) as exc: + raise EvidenceError(f"command failed: {' '.join(command)}") from exc + + +def _code(artifact: dict[str, Any], key: str, label: str) -> bytes: + value = artifact.get(key) + if not isinstance(value, dict) or value.get("linkReferences") != {}: + raise EvidenceError(f"{label} has unresolved links") + raw = value.get("object") + if not isinstance(raw, str) or not re.fullmatch(r"0x[0-9a-fA-F]+", raw) or len(raw) % 2: + raise EvidenceError(f"{label} bytecode is missing") + return bytes.fromhex(raw[2:]) + + +def _compiled(root: Path) -> tuple[bytes, bytes, str, dict[str, Any]]: + path = root / ARTIFACT + try: + artifact = json.loads(path.read_text(encoding="utf-8")) + except (OSError, UnicodeError, json.JSONDecodeError) as exc: + raise EvidenceError(f"cannot read {path}") from exc + metadata = artifact.get("metadata") + if isinstance(metadata, str): + metadata = json.loads(metadata) + if not isinstance(metadata, dict): + raise EvidenceError("compiler metadata is missing") + compiler = metadata.get("compiler") + settings = metadata.get("settings") + if not isinstance(compiler, dict) or not isinstance(settings, dict): + raise EvidenceError("compiler identity is incomplete") + if settings.get("compilationTarget") != {SOURCE: CONTRACT}: + raise EvidenceError("artifact compilation target is stale") + normalized = dict(settings) + normalized.pop("compilationTarget") + version = compiler.get("version") + if not isinstance(version, str) or not version: + raise EvidenceError("solc version is missing") + return ( + _code(artifact, "bytecode", "creation"), + _code(artifact, "deployedBytecode", "runtime"), + version, + normalized, + ) + + +def _canonical(value: Any) -> bytes: + return json.dumps(value, sort_keys=True, separators=(",", ":")).encode() + + +def _manifest( + creation: bytes, + runtime: bytes, + solc_version: str, + settings: dict[str, Any], +) -> bytes: + salt = bytes.fromhex(keccak256(SALT_PREIMAGE.encode())[2:]) + creation_hash = bytes.fromhex(keccak256(creation)[2:]) + deployer = bytes.fromhex(CREATE2_PROXY[2:]) + predicted = "0x" + keccak256(b"\xff" + deployer + salt + creation_hash)[-40:] + value = { + "schemaVersion": 1, + "source": SOURCE, + "contract": CONTRACT, + "compiler": { + "solcVersion": solc_version, + "solcSettingsKeccak256": keccak256(_canonical(settings)), + }, + "creationCodeBytes": len(creation), + "creationCodeKeccak256": "0x" + creation_hash.hex(), + "runtimeCodeBytes": len(runtime), + "runtimeCodeKeccak256": keccak256(runtime), + "create2": { + "deployer": CREATE2_PROXY, + "saltPreimage": SALT_PREIMAGE, + "salt": "0x" + salt.hex(), + "predictedAddress": predicted, + }, + "economicBytecodeIncluded": False, + } + return json.dumps(value, indent=2).encode() + b"\n" + + +def generate(root: Path = ROOT, *, check: bool = False) -> None: + forge = shutil.which("forge") + if not forge: + raise EvidenceError("Foundry forge is required") + _run([forge, "build", "--no-cache", "--build-info", SOURCE], root) + creation, runtime, version, settings = _compiled(root) + content = _manifest(creation, runtime, version, settings) + path = root / MANIFEST + if check: + try: + current = path.read_bytes() + except OSError as exc: + raise EvidenceError(f"generated file is missing: {path}") from exc + if current != content: + raise EvidenceError(f"generated file is stale: {path}") + else: + path.parent.mkdir(parents=True, exist_ok=True) + path.write_bytes(content) + + +def main(argv: list[str] | None = None) -> int: + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--check", action="store_true") + args = parser.parse_args(argv) + try: + generate(check=args.check) + except EvidenceError as exc: + print(f"error: {exc}", file=sys.stderr) + return 1 + print("AgentWorkIndex evidence is current" if args.check else "generated AgentWorkIndex evidence") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/tools/fixtures/agent-document-golden.json b/tools/fixtures/agent-document-golden.json new file mode 100644 index 0000000..b387a72 --- /dev/null +++ b/tools/fixtures/agent-document-golden.json @@ -0,0 +1,85 @@ +{ + "schemaVersion": 1, + "kinds": { + "task": "0xa87c1f2bd1ee275d3f44c021b929709db51ad8a945c2c34a0857974e28595821", + "receipt": "0xe2c91b1ce0f47a0ac033aec054b0ce8dd8a0ca22e4812f61776ed60835734da1", + "payment": "0x8161a6637134aa32b72dafb5032097de770b8555713c2415d800ea5af322c7bd" + }, + "task": { + "parentDigest": "0x0000000000000000000000000000000000000000000000000000000000000000", + "input": { + "v": "1", + "kind": "fao.task", + "chainId": "11155111", + "vault": "0xAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", + "title": "Prova ☕\ncontrole\u0001", + "spec": "Ship \"Lane 5\" \\ safely", + "salt": "0xABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABAB" + }, + "canonicalHex": "0x7b22636861696e4964223a223131313535313131222c226b696e64223a2266616f2e7461736b222c2273616c74223a22307861626162616261626162616261626162616261626162616261626162616261626162616261626162616261626162616261626162616261626162616261626162222c2273706563223a2253686970205c224c616e6520355c22205c5c20736166656c79222c227469746c65223a2250726f766120e298955c7530303061636f6e74726f6c655c7530303031222c2276223a2231222c227661756c74223a22307861616161616161616161616161616161616161616161616161616161616161616161616161616161227d", + "digest": "0x23afe461b8d36e9c0f1c0503e1ec1a98ad7937748324cc4073174078a30e2335" + }, + "receipt": { + "parentDigest": "0x23afe461b8d36e9c0f1c0503e1ec1a98ad7937748324cc4073174078a30e2335", + "input": { + "v": "1", + "kind": "fao.receipt", + "chainId": "11155111", + "vault": "0xAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", + "task": "0x23AFE461B8D36E9C0F1C0503E1EC1A98AD7937748324CC4073174078A30E2335", + "worker": "0xBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB", + "artifacts": [ + { + "digest": "0xCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCD", + "uri": "https://example.test/ação" + }, + { + "digest": "0x1212121212121212121212121212121212121212121212121212121212121212", + "uri": "ipfs://bafy-agent-evidence", + "note": "tests: 42/42" + } + ], + "summary": "Concluído ✓", + "salt": "0x3434343434343434343434343434343434343434343434343434343434343434" + }, + "canonicalHex": "0x7b22617274696661637473223a5b7b22646967657374223a22307863646364636463646364636463646364636463646364636463646364636463646364636463646364636463646364636463646364636463646364636463646364222c22757269223a2268747470733a2f2f6578616d706c652e746573742f61c3a7c3a36f227d2c7b22646967657374223a22307831323132313231323132313231323132313231323132313231323132313231323132313231323132313231323132313231323132313231323132313231323132222c226e6f7465223a2274657374733a2034322f3432222c22757269223a22697066733a2f2f626166792d6167656e742d65766964656e6365227d5d2c22636861696e4964223a223131313535313131222c226b696e64223a2266616f2e72656365697074222c2273616c74223a22307833343334333433343334333433343334333433343334333433343334333433343334333433343334333433343334333433343334333433343334333433343334222c2273756d6d617279223a22436f6e636c75c3ad646f20e29c93222c227461736b223a22307832336166653436316238643336653963306631633035303365316563316139386164373933373734383332346363343037333137343037386133306532333335222c2276223a2231222c227661756c74223a22307861616161616161616161616161616161616161616161616161616161616161616161616161616161222c22776f726b6572223a22307862626262626262626262626262626262626262626262626262626262626262626262626262626262227d", + "digest": "0xbc15cf01a091408c16ab84341a2aebda53402b8a31b4543199c4d2b176d473d5" + }, + "payment": { + "parentDigest": "0xbc15cf01a091408c16ab84341a2aebda53402b8a31b4543199c4d2b176d473d5", + "input": { + "v": "1", + "kind": "fao.payment", + "chainId": "11155111", + "vault": "0xAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", + "asset": "0xCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC", + "recipient": "0xDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD", + "amount": "115792089237316195423570985008687907853269984665640564039457584007913129639935", + "task": "0x23AFE461B8D36E9C0F1C0503E1EC1A98AD7937748324CC4073174078A30E2335", + "receipt": "0xBC15CF01A091408C16AB84341A2AEBDA53402B8A31B4543199C4D2B176D473D5", + "salt": "0xEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEF" + }, + "canonicalHex": "0x7b22616d6f756e74223a22313135373932303839323337333136313935343233353730393835303038363837393037383533323639393834363635363430353634303339343537353834303037393133313239363339393335222c226173736574223a22307863636363636363636363636363636363636363636363636363636363636363636363636363636363222c22636861696e4964223a223131313535313131222c226b696e64223a2266616f2e7061796d656e74222c2272656365697074223a22307862633135636630316130393134303863313661623834333431613261656264613533343032623861333162343534333139396334643262313736643437336435222c22726563697069656e74223a22307864646464646464646464646464646464646464646464646464646464646464646464646464646464222c2273616c74223a22307865666566656665666566656665666566656665666566656665666566656665666566656665666566656665666566656665666566656665666566656665666566222c227461736b223a22307832336166653436316238643336653963306631633035303365316563316139386164373933373734383332346363343037333137343037386133306532333335222c2276223a2231222c227661756c74223a22307861616161616161616161616161616161616161616161616161616161616161616161616161616161227d", + "digest": "0x60fccea2c2617a6e1f35bb536e2a48b7384a8b6d91d3486075c1f77981eadc18", + "transferAction": { + "asset": "0xcccccccccccccccccccccccccccccccccccccccc", + "recipient": "0xdddddddddddddddddddddddddddddddddddddddd", + "amount": "115792089237316195423570985008687907853269984665640564039457584007913129639935", + "salt": "0x60fccea2c2617a6e1f35bb536e2a48b7384a8b6d91d3486075c1f77981eadc18" + }, + "transferEvaluationPayload": "0x27e49851e3b79673e847d7c12acc52a3936006b8517243a42df902b3df4e902e0000000000000000000000000000000000000000000000000000000000aa36a7000000000000000000000000aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa000000000000000000000000cccccccccccccccccccccccccccccccccccccccc000000000000000000000000ddddddddddddddddddddddddddddddddddddddddffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff60fccea2c2617a6e1f35bb536e2a48b7384a8b6d91d3486075c1f77981eadc18", + "actionHash": "0x24a4125ae332e790e5fa9cbdc5d243b63a026e7b96fc9e796fd6d87706f2a87e", + "proposalId": "16573152149377518413156023629436668511050935944683357744569870274751223736446" + }, + "unicodeKeyOrder": { + "value": { + "😀": "astral", + "": "bmp" + }, + "canonicalHex": "0x7b22ee8080223a22626d70222c22f09f9880223a2261737472616c227d" + }, + "nonCanonical": { + "hex": "0x7b22636861696e4964223a223131313535313131222c226b696e64223a2266616f2e7461736b222c2273616c74223a22307861626162616261626162616261626162616261626162616261626162616261626162616261626162616261626162616261626162616261626162616261626162222c2273706563223a2253686970205c224c616e6520355c22205c5c20736166656c79222c227469746c65223a2250726f766120e298955c6e636f6e74726f6c655c7530303031222c2276223a2231222c227661756c74223a22307861616161616161616161616161616161616161616161616161616161616161616161616161616161227d", + "rawDigest": "0x51f165e13e2210409a59d008e332568561e2cc2d16c2a9030289fba3ed528e5e" + } +} diff --git a/tools/test_agent_documents.py b/tools/test_agent_documents.py new file mode 100644 index 0000000..a7ef4d3 --- /dev/null +++ b/tools/test_agent_documents.py @@ -0,0 +1,164 @@ +from __future__ import annotations + +import copy +import json +import unittest +from pathlib import Path + +from tools import agent_documents as agent + + +FIXTURE = json.loads( + (Path(__file__).parent / "fixtures/agent-document-golden.json").read_text(encoding="utf-8") +) + + +def raw(value: str) -> bytes: + return bytes.fromhex(value[2:]) + + +def published_data(publisher: str, document: bytes) -> str: + padding = (-len(document)) % 32 + return "0x" + ( + b"\x00" * 12 + + bytes.fromhex(publisher[2:]) + + (64).to_bytes(32, "big") + + len(document).to_bytes(32, "big") + + document + + b"\x00" * padding + ).hex() + + +class AgentDocumentsTest(unittest.TestCase): + def test_kind_preimages_and_cross_language_golden_documents(self) -> None: + self.assertEqual(agent.keccak256(b"FAO_AGENT_TASK_V1"), FIXTURE["kinds"]["task"]) + self.assertEqual(agent.keccak256(b"FAO_AGENT_RECEIPT_V1"), FIXTURE["kinds"]["receipt"]) + self.assertEqual(agent.keccak256(b"FAO_AGENT_PAYMENT_V1"), FIXTURE["kinds"]["payment"]) + for name, builder in ( + ("task", agent.build_task), + ("receipt", agent.build_receipt), + ("payment", agent.build_payment), + ): + document = builder(FIXTURE[name]["input"]) + self.assertEqual(document, raw(FIXTURE[name]["canonicalHex"])) + self.assertEqual(agent.document_digest(document), FIXTURE[name]["digest"]) + + def test_exact_canonical_rules_cover_unicode_controls_and_omissions(self) -> None: + self.assertEqual( + agent.canonicalize(FIXTURE["unicodeKeyOrder"]["value"]), + raw(FIXTURE["unicodeKeyOrder"]["canonicalHex"]), + ) + task = agent.validate_task(FIXTURE["task"]["input"]) + self.assertEqual(task["vault"], "0x" + "aa" * 20) + self.assertNotIn("deadline", task) + self.assertNotIn("reward", task) + self.assertIn(b"\\u000a", agent.build_task(task)) + with self.assertRaisesRegex(agent.DocumentError, "surrogates|UTF-8"): + agent.canonicalize({"value": "\ud800"}) + with self.assertRaisesRegex(agent.DocumentError, "scalar leaf"): + agent.canonicalize({"amount": 1}) + + def test_noncanonical_raw_bytes_still_have_one_raw_digest(self) -> None: + document = raw(FIXTURE["nonCanonical"]["hex"]) + self.assertEqual(agent.document_digest(document), FIXTURE["nonCanonical"]["rawDigest"]) + with self.assertRaisesRegex(agent.DocumentError, "canonical"): + agent.validate_task(document) + + def test_payment_binds_exact_transfer_domain_and_max_uint256(self) -> None: + payment = FIXTURE["payment"] + action = agent.payment_transfer_action(raw(payment["canonicalHex"])) + self.assertEqual(action, payment["transferAction"]) + self.assertEqual( + "0x" + agent.transfer_evaluation_payload( + payment["input"]["chainId"], payment["input"]["vault"], action + ).hex(), + payment["transferEvaluationPayload"], + ) + self.assertEqual( + agent.validate_payment_binding( + raw(payment["canonicalHex"]), + payment["input"]["chainId"], + payment["input"]["vault"], + action, + ), + payment["actionHash"], + ) + self.assertEqual(str(int(payment["actionHash"], 16)), payment["proposalId"]) + + for field, replacement in ( + ("asset", "0x" + "11" * 20), + ("recipient", "0x" + "22" * 20), + ("amount", "1"), + ("salt", "0x" + "33" * 32), + ): + changed = dict(action) + changed[field] = replacement + with self.assertRaisesRegex(agent.DocumentError, "exact TransferAction"): + agent.validate_payment_binding( + raw(payment["canonicalHex"]), "11155111", payment["input"]["vault"], changed + ) + with self.assertRaisesRegex(agent.DocumentError, "chainId"): + agent.validate_payment_binding( + raw(payment["canonicalHex"]), "1", payment["input"]["vault"], action + ) + with self.assertRaisesRegex(agent.DocumentError, "vault"): + agent.validate_payment_binding( + raw(payment["canonicalHex"]), "11155111", "0x" + "11" * 20, action + ) + + def test_schema_validation_rejects_non_strings_unknowns_and_overflow(self) -> None: + external = copy.deepcopy(FIXTURE["task"]["input"]) + external.pop("spec") + external.update( + { + "specDigest": "0x" + "56" * 32, + "specUri": "https://example.test/spec", + "deadline": "1760000000", + "reward": {"asset": "0x" + "00" * 20, "amount": "1"}, + } + ) + self.assertEqual(agent.validate_task(external)["reward"]["amount"], "1") + for field, value in (("chainId", "01"), ("amount", str(1 << 256))): + changed = copy.deepcopy(FIXTURE["payment"]["input"]) + changed[field] = value + with self.assertRaises(agent.DocumentError): + agent.validate_payment(changed) + changed = copy.deepcopy(FIXTURE["task"]["input"]) + changed["unknown"] = "value" + with self.assertRaisesRegex(agent.DocumentError, "invalid fields"): + agent.validate_task(changed) + + def test_publish_calldata_and_log_decoder_are_exact(self) -> None: + document = raw(FIXTURE["receipt"]["canonicalHex"]) + publication = agent.prepare_publication("receipt", FIXTURE["receipt"]["input"]) + self.assertEqual(publication["kind"], agent.RECEIPT_KIND) + self.assertEqual(publication["parentDigest"], FIXTURE["receipt"]["parentDigest"]) + self.assertEqual(publication["documentDigest"], FIXTURE["receipt"]["digest"]) + calldata = agent.publish_calldata( + agent.RECEIPT_KIND, FIXTURE["receipt"]["parentDigest"], document + ) + self.assertTrue(calldata.startswith("0x52bf8ff2")) + self.assertEqual(int(calldata[2 + 8 + 128 : 2 + 8 + 192], 16), 96) + + publisher = "0x" + "69" * 20 + log = { + "topics": [ + agent.PUBLISHED_TOPIC, + agent.RECEIPT_KIND, + FIXTURE["receipt"]["parentDigest"], + FIXTURE["receipt"]["digest"], + ], + "data": published_data(publisher, document), + } + decoded = agent.decode_published_log(log) + self.assertEqual(decoded["publisher"], publisher) + self.assertEqual(decoded["document"], document) + self.assertEqual(decoded["documentDigest"], FIXTURE["receipt"]["digest"]) + + log["topics"][-1] = "0x" + "00" * 32 + with self.assertRaisesRegex(agent.DocumentError, "digest"): + agent.decode_published_log(log) + + +if __name__ == "__main__": + unittest.main() From 39251707f49e38423ff164a9a5804c5058a3f16b Mon Sep 17 00:00:00 2001 From: krandder Date: Mon, 13 Jul 2026 00:43:08 -0300 Subject: [PATCH 2/4] Add stateless agent work runner --- README.md | 6 + docs/agent-work-v1.md | 25 + tools/agent_anvil_drill.py | 763 +++++++++++++++++++++++++++ tools/agent_runner.py | 843 ++++++++++++++++++++++++++++++ tools/economic_deployment.py | 50 +- tools/test_agent_runner.py | 405 ++++++++++++++ tools/test_economic_deployment.py | 64 ++- 7 files changed, 2113 insertions(+), 43 deletions(-) create mode 100644 tools/agent_anvil_drill.py create mode 100644 tools/agent_runner.py create mode 100644 tools/test_agent_runner.py diff --git a/README.md b/README.md index 65195bb..e76e3f1 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,8 @@ The [agent-work v1 specification](docs/agent-work-v1.md) defines the ownerless publication index, canonical task/receipt/payment documents, and their exact binding to typed treasury transfers. +Its P1 reference agent recomputes finalized state on every tick and keeps signing behind an +injected boundary; `python3 tools/agent_anvil_drill.py` regenerates the 16-drill evidence matrix. This repository contains the smart contracts for the Futarchy Autonomous Optimizer token (FAO) and its sale mechanics. The codebase is implemented with [Foundry](https://book.getfoundry.sh/) and relies on OpenZeppelin libraries for security-reviewed primitives. @@ -139,6 +141,10 @@ Override `ECONOMIC_METADATA_BUNDLE`, `EXPECTED_DEPLOYER`, and `EXPECTED_DEPLOYER for a different deployment. The bundle must describe the release strategy derived from that deployer and nonce. +Economic deployment manifests use schema v4. Both broadcast and finalized-chain reconstruction +pin the exact live runtime hashes of the vault, proposal gateway, arbitration, and treasury +executor; RPC verification rejects any substituted authority contract before trusting its views. + ## Gnosis Liquidity Stack Deploy Deploy `FutarchyOfficialProposalSource`, two Swapr adapters (spot/conditional), and `FutarchyLiquidityManager`: diff --git a/docs/agent-work-v1.md b/docs/agent-work-v1.md index e51b504..bedc064 100644 --- a/docs/agent-work-v1.md +++ b/docs/agent-work-v1.md @@ -58,6 +58,8 @@ A receipt uses `parentDigest = taskDigest`: Each artifact commits to the exact artifact bytes with Keccak-256. Its URI is advisory and limited to 256 UTF-8 bytes by the v1 tools; `note` is optional. No CID or IPFS dependency exists. +The on-chain kind is permanently `FAO_AGENT_RECEIPT_V1`. “Submission” is only explanatory prose +for this work receipt; it is not a fourth document kind. A payment envelope uses `parentDigest = receiptDigest`: @@ -85,3 +87,26 @@ The dependency-free implementations and shared golden vectors are The separate singleton build, runtime hash, CREATE2 salt, and predicted address are pinned in [`metadata/agent-work-index.json`](../metadata/agent-work-index.json); verify them with `python3 tools/agent_work_index_code_hashes.py --check`. +The address is a CREATE2 prediction, not deployment evidence. A pinned runtime hash or predicted +address must never be presented as deployed until a chain receipt and matching live code prove it. + +## Stateless reference agent + +[`tools/agent_runner.py`](../tools/agent_runner.py) is the dependency-free P1 reference agent. Each +tick starts from finalized logs and views, chooses at most one action, simulates and cap-checks it, +then passes the unsigned transaction to an injected sender. It holds no key or authoritative local +state. Accepted, executable, and paid are deliberately separate: acceptance needs a matching view +and finalized arbitration log; executability needs a live queue window and successful exact +`eth_call`; payment needs the exact execution log and conserved balance deltas. + +The executable unit, fake-RPC, Anvil, and Sepolia-fork matrix is: + +```sh +python3 -m unittest tools.test_agent_documents tools.test_agent_runner +python3 tools/agent_anvil_drill.py +``` + +The latter regenerates +[`metadata/agent-work-p1-evidence.json`](../metadata/agent-work-p1-evidence.json) and its SHA-256 +sidecar. These are house-wallet engineering fixtures. They do not prove a live index deployment, +live payment, useful work, external demand, or guaranteed payment. diff --git a/tools/agent_anvil_drill.py b/tools/agent_anvil_drill.py new file mode 100644 index 0000000..0b3d0eb --- /dev/null +++ b/tools/agent_anvil_drill.py @@ -0,0 +1,763 @@ +#!/usr/bin/env python3 +"""Deploy the Lane 4/5 stack on Anvil and emit executed P1 drill evidence.""" + +from __future__ import annotations + +import argparse +import hashlib +import json +import re +import shutil +import subprocess +import sys +import time +from pathlib import Path +from typing import Any, Sequence + +try: + from tools import agent_documents as documents + from tools import agent_runner as runner +except ModuleNotFoundError: # Direct script execution. + import agent_documents as documents # type: ignore + import agent_runner as runner # type: ignore + + +ROOT = Path(__file__).resolve().parents[1] +INDEX_MANIFEST = ROOT / "metadata/agent-work-index.json" +SEPOLIA_WETH = "0xfff9976782d46cc05630d1f6ebab18b2324d6b14" +ATTEMPT_LEDGER: list[dict[str, Any]] = [] + + +class DrillError(ValueError): + pass + + +class PinnedRpc(runner.JsonRpc): + """Anvil has no finalized head; a mined latest block is the explicit drill pin.""" + + def finalized_block(self) -> dict[str, Any]: + return self.block("latest") + + +def _run(command: Sequence[str]) -> str: + result = subprocess.run(command, cwd=ROOT, text=True, capture_output=True) + if result.returncode: + raise DrillError("command failed: %s\n%s" % (" ".join(command), result.stderr[-2000:])) + return result.stdout.strip() + + +def _deploy(contract: str, rpc_url: str, sender: str, arguments: Sequence[str] = ()) -> str: + command = [ + "forge", "create", contract, "--rpc-url", rpc_url, "--unlocked", "--from", sender, + "--broadcast", "--gas-limit", "30000000", + ] + if arguments: + command.extend(("--constructor-args", *arguments)) + output = _run(command) + match = re.search(r"Deployed to:\s*(0x[0-9a-fA-F]{40})", output) + if match is None: + raise DrillError("forge create did not report a deployment") + return match.group(1).lower() + + +def _receipt(rpc: runner.JsonRpc, tx_hash: str) -> dict[str, Any]: + for _ in range(200): + value = rpc.request("eth_getTransactionReceipt", [tx_hash]) + if isinstance(value, dict): + return value + time.sleep(0.02) + raise DrillError("transaction receipt did not arrive") + + +def _send( + rpc: runner.JsonRpc, + sender: str, + target: str, + data: str, + *, + value: int = 0, + simulate: bool = True, +) -> tuple[str, dict[str, Any]]: + transaction = {"from": sender, "to": target, "data": data, "value": hex(value)} + if simulate: + rpc.call(transaction, "latest") + tx_hash = rpc.request("eth_sendTransaction", [transaction]) + if not isinstance(tx_hash, str): + raise DrillError("eth_sendTransaction returned no hash") + receipt = _receipt(rpc, tx_hash) + ATTEMPT_LEDGER.append( + { + "from": sender, + "to": target, + "value": str(value), + "dataKeccak256": documents.keccak256(bytes.fromhex(data[2:])), + "transactionHash": tx_hash.lower(), + "blockHash": receipt["blockHash"].lower(), + "blockNumber": str(int(receipt["blockNumber"], 16)), + "status": str(int(receipt["status"], 16)), + } + ) + return tx_hash.lower(), receipt + + +def _try_call(rpc: runner.JsonRpc, sender: str, target: str, data: str) -> bool: + try: + rpc.call({"from": sender, "to": target, "data": data, "value": "0x0"}, "latest") + return True + except runner.RpcCallError: + return False + + +def _mine(rpc: runner.JsonRpc, seconds: int = 0) -> None: + if seconds: + rpc.request("evm_increaseTime", [seconds]) + rpc.request("evm_mine", []) + + +def _uint(rpc: runner.JsonRpc, target: str, signature: str, *args: str) -> int: + data = "0x" + runner._selector(signature) + for arg in args: + data += int(arg, 0).to_bytes(32, "big").hex() + return int(rpc.call({"to": target, "data": data}, "latest"), 16) + + +def _address_view(rpc: runner.JsonRpc, target: str, signature: str) -> str: + raw = bytes.fromhex(rpc.call({"to": target, "data": "0x" + runner._selector(signature)}, "latest")[2:]) + if len(raw) != 32 or any(raw[:12]): + raise DrillError("address view is malformed") + return "0x" + raw[12:].hex() + + +def _token_balance(rpc: runner.JsonRpc, token: str, account: str) -> int: + return _uint(rpc, token, "balanceOf(address)", account) + + +def _token_call(signature: str, account: str, amount: int) -> str: + return "0x" + runner._selector(signature) + bytes(12).hex() + account[2:] + amount.to_bytes(32, "big").hex() + + +def _stack(rpc_url: str, rpc: runner.JsonRpc, accounts: list[str], fork: bool) -> dict[str, Any]: + deployer, automation, challenger, resolver = accounts[:4] + start_block = int(rpc.block("latest")["number"], 16) + if fork: + asset = SEPOLIA_WETH + for account in (deployer, automation, challenger): + _send(rpc, account, asset, "0xd0e30db0", value=10**18) + else: + asset = _deploy("src/FAOSiteToken.sol:FAOSiteToken", rpc_url, deployer, (deployer, "1000000")) + for account in (automation, challenger): + _send(rpc, deployer, asset, _token_call("transfer(address,uint256)", account, 10_000)) + + arbitration = _deploy( + "src/FutarchyArbitration.sol:FutarchyArbitration", + rpc_url, + deployer, + (asset, "100", "10"), + ) + index = _deploy("src/AgentWorkIndex.sol:AgentWorkIndex", rpc_url, deployer) + head = rpc.block("latest") + now = int(head["timestamp"], 16) + c1, c2, tap, tap_max = ( + (10**17, 10**18, 10**18, 2 * 10**18) if fork else (100, 1000, 1000, 2000) + ) + config_arg = ( + '("Agent Work Drill","AWD",%s,%s,%s,%s,%d,%d,100000000000000000000,10,' + "1000000000000000000000,1000000000000000000,0,1000,[(%s,%d,%d,%d,%d)])" + % ( + asset, deployer, arbitration, index, now + 10_000, now + 20_000, asset, + c1, c2, tap, tap_max, + ) + ) + vault = _deploy("src/GenesisVault.sol:GenesisVault", rpc_url, deployer, (config_arg, "[]")) + executor = _address_view(rpc, vault, "TREASURY_EXECUTOR()") + gateway = _deploy( + "src/EconGateway.sol:EconGateway", + rpc_url, + deployer, + (index, index, arbitration, vault, "1", "2"), + ) + _send( + rpc, + deployer, + arbitration, + "0x" + runner._selector("setProposalGateway(address)") + bytes(12).hex() + gateway[2:], + ) + rpc.request( + "anvil_setStorageAt", + [vault, "0x1", "0x" + (2).to_bytes(32, "big").hex()], + ) + rpc.request( + "anvil_setStorageAt", + [arbitration, "0x6", "0x" + (bytes(12) + bytes.fromhex(resolver[2:])).hex()], + ) + _mine(rpc) + for account in (automation, challenger): + _send(rpc, account, asset, _token_call("approve(address,uint256)", arbitration, 10_000)) + manifest = json.loads(INDEX_MANIFEST.read_text(encoding="utf-8")) + runtime = rpc.request("eth_getCode", [index, "latest"]) + if documents.keccak256(bytes.fromhex(runtime[2:])) != manifest["runtimeCodeKeccak256"]: + raise DrillError("deployed AgentWorkIndex runtime does not match its pin") + contracts = { + "index": index, + "gateway": gateway, + "arbitration": arbitration, + "vault": vault, + "executor": executor, + } + return { + "chainId": rpc.chain_id(), + "startBlock": start_block, + "asset": asset, + "arbitration": arbitration, + "index": index, + "vault": vault, + "executor": executor, + "gateway": gateway, + "actors": { + "deployer": deployer, + "automation": automation, + "challenger": challenger, + "resolver": resolver, + "worker": accounts[4], + "recipientA": accounts[5], + "recipientB": accounts[6], + "keeperA": accounts[7], + "keeperB": accounts[8], + }, + "indexRuntimeKeccak256": manifest["runtimeCodeKeccak256"], + "runtimeCodeKeccak256": { + name: documents.keccak256(bytes.fromhex(rpc.request("eth_getCode", [address, "latest"])[2:])) + for name, address in contracts.items() + }, + } + + +def _config(stack: dict[str, Any], ordinal: int, *, recipient: str | None = None, amount: int = 10) -> dict[str, Any]: + actors = stack["actors"] + task = { + "v": "1", "kind": "fao.task", "chainId": str(stack["chainId"]), "vault": stack["vault"], + "title": "Anvil task %d" % ordinal, "spec": "Return exact deterministic evidence.", + "salt": "0x" + (1000 + ordinal).to_bytes(32, "big").hex(), + } + task_digest = documents.document_digest(documents.build_task(task)) + receipt = { + "v": "1", "kind": "fao.receipt", "chainId": str(stack["chainId"]), "vault": stack["vault"], + "task": task_digest, "worker": actors["worker"], + "artifacts": [{"digest": "0x" + (2000 + ordinal).to_bytes(32, "big").hex(), "uri": "https://example.test/%d" % ordinal}], + "summary": "Anvil evidence %d" % ordinal, + "salt": "0x" + (3000 + ordinal).to_bytes(32, "big").hex(), + } + receipt_digest = documents.document_digest(documents.build_receipt(receipt)) + payment = { + "v": "1", "kind": "fao.payment", "chainId": str(stack["chainId"]), "vault": stack["vault"], + "asset": stack["asset"], "recipient": recipient or actors["recipientA"], + "amount": str(amount), "task": task_digest, "receipt": receipt_digest, + "salt": "0x" + (4000 + ordinal).to_bytes(32, "big").hex(), + } + return { + "chainId": stack["chainId"], + "fromBlock": stack["startBlock"], + "index": stack["index"], + "gateway": stack["gateway"], + "arbitration": stack["arbitration"], + "vault": stack["vault"], + "executor": stack["executor"], + "automation": actors["automation"], + "documents": {"task": task, "receipt": receipt, "payment": payment}, + "caps": {"paymentAmount": str(amount), "bondAmount": "100", "transactionValue": "0"}, + } + + +def _clean(config: dict[str, Any]) -> dict[str, Any]: + return {key: value for key, value in config.items() if key != "_actors"} + + +def _runner_state(rpc_url: str, config: dict[str, Any]) -> dict[str, Any]: + # A fresh adapter on every boundary is the restart proof; no local state is carried. + rpc = PinnedRpc(rpc_url) + clean = _clean(config) + return runner.derive_state(clean, runner.collect_snapshot(rpc, clean)) + + +def _state_evidence(state: dict[str, Any]) -> dict[str, Any]: + return { + "lifecycle": state["lifecycle"], + "finalized": state["finalized"], + "actionHash": state["actionHash"], + "proposalId": state["proposalId"], + "accepted": state["accepted"], + "acceptanceRoute": state["acceptanceRoute"], + "executable": state["executable"], + "paid": state["paid"], + "proposal": state["proposal"], + "queued": state["queued"], + "publicationOccurrences": { + name: state["publications"][name]["occurrences"] + for name in ("task", "receipt", "payment") + }, + } + + +def _published_documents(rpc: runner.JsonRpc, stack: dict[str, Any]) -> list[dict[str, Any]]: + end = int(rpc.block("latest")["number"], 16) + result = [] + for raw in rpc.logs(stack["startBlock"], end, [stack["index"]]): + log = runner._canonical_log(raw) + if log["topics"][0] != documents.PUBLISHED_TOPIC: + continue + decoded = documents.decode_published_log({"topics": log["topics"], "data": log["data"]}) + result.append( + { + "kind": decoded["kind"], + "parentDigest": decoded["parentDigest"], + "documentDigest": decoded["documentDigest"], + "document": "0x" + decoded["document"].hex(), + "publisher": decoded["publisher"], + "transactionHash": log["transactionHash"], + "blockHash": log["blockHash"], + "blockNumber": str(log["blockNumber"]), + } + ) + return result + + +def _publish_all(rpc: runner.JsonRpc, config: dict[str, Any]) -> list[dict[str, Any]]: + receipts = [] + for name in ("task", "receipt", "payment"): + publication = documents.prepare_publication(name, config["documents"][name]) + _, receipt = _send( + rpc, config["automation"], config["index"], publication["calldata"] + ) + receipts.append(receipt) + return receipts + + +def _action(config: dict[str, Any]) -> tuple[dict[str, str], str, int]: + raw = documents.build_payment(config["documents"]["payment"]) + action = documents.payment_transfer_action(raw) + action_hash = documents.validate_payment_binding(raw, config["chainId"], config["vault"], action) + return action, action_hash, int(action_hash, 16) + + +def _propose(rpc: runner.JsonRpc, config: dict[str, Any]) -> dict[str, Any]: + action, _, _ = _action(config) + _, receipt = _send( + rpc, + config["automation"], + config["gateway"], + runner._call(runner.SELECTORS["propose"], runner._action_words(action)), + ) + return receipt + + +def _bond_yes(rpc: runner.JsonRpc, config: dict[str, Any], amount: int = 2, sender: str | None = None) -> None: + _, _, proposal_id = _action(config) + _send( + rpc, + sender or config["automation"], + config["arbitration"], + runner._call(runner.SELECTORS["placeYes"], runner._word(proposal_id), runner._word(amount)), + ) + + +def _timeout(rpc: runner.JsonRpc, config: dict[str, Any], accepted: bool) -> None: + actors = config["_actors"] + _, _, proposal_id = _action(config) + _bond_yes(rpc, config) + if not accepted: + _send( + rpc, + actors["challenger"], + config["arbitration"], + runner._call(runner._selector("placeNoBond(uint256)"), runner._word(proposal_id)), + ) + _mine(rpc, 11) + _send( + rpc, + actors["keeperA"], + config["arbitration"], + runner._call(runner.SELECTORS["finalize"], runner._word(proposal_id)), + ) + + +def _evaluated(rpc: runner.JsonRpc, config: dict[str, Any], accepted: bool) -> None: + actors = config["_actors"] + _, _, proposal_id = _action(config) + _bond_yes(rpc, config) + _send( + rpc, + actors["challenger"], + config["arbitration"], + runner._call(runner._selector("placeNoBond(uint256)"), runner._word(proposal_id)), + ) + _bond_yes(rpc, config, 100) + _send(rpc, actors["keeperA"], config["arbitration"], "0x" + runner._selector("startNextEvaluation()")) + _send( + rpc, + actors["resolver"], + config["arbitration"], + runner._call(runner._selector("resolveActiveEvaluation(bool)"), runner._word(1 if accepted else 0)), + ) + + +def _queue_data(config: dict[str, Any]) -> str: + action, _, _ = _action(config) + return runner._call(runner.SELECTORS["queue"], runner._action_words(action)) + + +def _execute_data(config: dict[str, Any]) -> str: + action, _, _ = _action(config) + return runner._call(runner.SELECTORS["execute"], runner._action_words(action)) + + +def _fund(rpc: runner.JsonRpc, stack: dict[str, Any], amount: int) -> None: + _send( + rpc, + stack["actors"]["deployer"], + stack["asset"], + _token_call("transfer(address,uint256)", stack["executor"], amount), + ) + + +def _local_drills(rpc_url: str, rpc: runner.JsonRpc, stack: dict[str, Any]) -> tuple[list[dict[str, Any]], dict[str, Any]]: + actors = stack["actors"] + attempts = [] + states = [] + + # Conflicting receipts/envelopes share no proposal or settlement state. + first = _config(stack, 1, recipient=actors["recipientA"], amount=10) + second = _config(stack, 2, recipient=actors["recipientB"], amount=11) + for cfg in (first, second): + cfg["_actors"] = actors + _publish_all(rpc, cfg) + _propose(rpc, cfg) + first_id = _action(first)[2] + second_id = _action(second)[2] + if first_id == second_id: + raise DrillError("conflicting envelopes produced one proposal id") + _timeout(rpc, first, True) + _timeout(rpc, second, False) + if not _uint(rpc, stack["arbitration"], "isAccepted(uint256)", hex(first_id)) or _uint( + rpc, stack["arbitration"], "isAccepted(uint256)", hex(second_id) + ): + raise DrillError("conflicting settlements cross-contaminated") + + # Same-envelope propose is deterministic and the duplicate reverts. + duplicate_ok = not _try_call(rpc, actors["automation"], stack["gateway"], runner._call( + runner.SELECTORS["propose"], runner._action_words(_action(first)[0]) + )) + if not duplicate_ok: + raise DrillError("duplicate proposal unexpectedly simulated") + + # Self-payment takes the ordinary timeout, queue, grace and treasury path. + self_pay = _config(stack, 3, recipient=actors["automation"], amount=5) + self_pay["_actors"] = actors + _publish_all(rpc, self_pay) + _propose(rpc, self_pay) + _timeout(rpc, self_pay, True) + _fund(rpc, stack, 5) + _send(rpc, actors["keeperA"], stack["vault"], _queue_data(self_pay)) + _mine(rpc, 86_400) + before_self = _token_balance(rpc, stack["asset"], actors["automation"]) + _send(rpc, actors["keeperA"], stack["vault"], _execute_data(self_pay)) + after_self = _token_balance(rpc, stack["asset"], actors["automation"]) + if after_self - before_self != 5: + raise DrillError("self-payment balance did not reconcile") + + # Underfunded accepted transfer stays atomic, then succeeds after funding. + underfunded = _config(stack, 4, amount=40) + underfunded["_actors"] = actors + _publish_all(rpc, underfunded) + _propose(rpc, underfunded) + _timeout(rpc, underfunded, True) + _send(rpc, actors["keeperA"], stack["vault"], _queue_data(underfunded)) + _mine(rpc, 86_400) + if _try_call(rpc, actors["keeperA"], stack["vault"], _execute_data(underfunded)): + raise DrillError("underfunded execution unexpectedly simulated") + before_executor = _token_balance(rpc, stack["asset"], stack["executor"]) + before_recipient = _token_balance(rpc, stack["asset"], actors["recipientA"]) + _fund(rpc, stack, 40) + _send(rpc, actors["keeperA"], stack["vault"], _execute_data(underfunded)) + after_executor = _token_balance(rpc, stack["asset"], stack["executor"]) + after_recipient = _token_balance(rpc, stack["asset"], actors["recipientA"]) + if before_executor != after_executor or after_recipient - before_recipient != 40: + raise DrillError("refunded execution did not conserve exact balances") + + # Expiry is terminal; a fresh envelope salt creates a fresh proposal identity. + expired = _config(stack, 5, amount=7) + expired["_actors"] = actors + _publish_all(rpc, expired) + _propose(rpc, expired) + _timeout(rpc, expired, True) + _send(rpc, actors["keeperA"], stack["vault"], _queue_data(expired)) + _mine(rpc, 86_400 + 7 * 86_400 + 1) + if _try_call(rpc, actors["keeperA"], stack["vault"], _execute_data(expired)): + raise DrillError("expired transfer unexpectedly simulated") + recovery = _config(stack, 6, amount=7) + recovery["_actors"] = actors + _publish_all(rpc, recovery) + _propose(rpc, recovery) + if _action(expired)[2] == _action(recovery)[2]: + raise DrillError("recovery reused the expired proposal id") + + # Evaluated acceptance and rejection are external resolver observations. + evaluated_yes = _config(stack, 7, amount=200) + evaluated_no = _config(stack, 8, amount=201) + for cfg, accepted in ((evaluated_yes, True), (evaluated_no, False)): + cfg["_actors"] = actors + _publish_all(rpc, cfg) + _propose(rpc, cfg) + _evaluated(rpc, cfg, accepted) + state = _runner_state(rpc_url, cfg) + if state["accepted"] != accepted or state["acceptanceRoute"] != "evaluated": + raise DrillError("evaluated outcome was not observed exactly") + if _try_call(rpc, actors["keeperA"], stack["vault"], _queue_data(evaluated_no)): + raise DrillError("rejected evaluated payment unexpectedly queued") + + # Two permissionless keepers race one queue call: one landed, one benign loser. + race = _config(stack, 9, amount=9) + race["_actors"] = actors + _publish_all(rpc, race) + _propose(rpc, race) + _timeout(rpc, race, True) + data = _queue_data(race) + for keeper in (actors["keeperA"], actors["keeperB"]): + rpc.call({"from": keeper, "to": stack["vault"], "data": data}, "latest") + rpc.request("anvil_setAutomine", [False]) + hashes = [ + rpc.request("eth_sendTransaction", [{"from": keeper, "to": stack["vault"], "data": data}]) + for keeper in (actors["keeperA"], actors["keeperB"]) + ] + rpc.request("evm_mine", []) + rpc.request("anvil_setAutomine", [True]) + receipts = [_receipt(rpc, value) for value in hashes] + statuses = sorted(int(value["status"], 16) for value in receipts) + if statuses != [0, 1]: + raise DrillError("queue race did not have one winner and one loser") + attempts.extend( + { + "kind": "queue-race", + "actor": keeper, + "transactionHash": tx_hash.lower(), + "status": receipt["status"], + "classification": "landed" if int(receipt["status"], 16) else "benign-race", + } + for keeper, tx_hash, receipt in zip((actors["keeperA"], actors["keeperB"]), hashes, receipts) + ) + + # Response-dropped recovery: the effect lands; a fresh runner observes PAID and sends nothing. + _fund(rpc, stack, 9) + _mine(rpc, 86_400) + tx_hash, receipt = _send(rpc, actors["keeperA"], stack["vault"], _execute_data(race)) + if int(receipt["status"], 16) != 1: + raise DrillError("dropped-response fixture did not land") + dropped_state = _runner_state(rpc_url, race) + if dropped_state["lifecycle"] != "PAID" or runner.next_action(_clean(race), dropped_state) is not None: + raise DrillError("restart after dropped response would duplicate payment") + attempts.append({"kind": "response-dropped", "transactionHash": tx_hash, "classification": "landed-on-restart"}) + + # Fresh adapters at task, receipt, payment, proposed, bonded, accepted, queued and paid boundaries. + restart = _config(stack, 10, amount=8) + restart["_actors"] = actors + for name in ("task", "receipt", "payment"): + publication = documents.prepare_publication(name, restart["documents"][name]) + _send(rpc, actors["automation"], stack["index"], publication["calldata"]) + state = _runner_state(rpc_url, restart) + states.append(_state_evidence(state)) + _propose(rpc, restart) + states.append(_state_evidence(_runner_state(rpc_url, restart))) + _bond_yes(rpc, restart) + states.append(_state_evidence(_runner_state(rpc_url, restart))) + _mine(rpc, 11) + _send(rpc, actors["keeperA"], stack["arbitration"], runner._call(runner.SELECTORS["finalize"], runner._word(_action(restart)[2]))) + states.append(_state_evidence(_runner_state(rpc_url, restart))) + _send(rpc, actors["keeperA"], stack["vault"], _queue_data(restart)) + states.append(_state_evidence(_runner_state(rpc_url, restart))) + _fund(rpc, stack, 8) + _mine(rpc, 86_400) + _send(rpc, actors["keeperA"], stack["vault"], _execute_data(restart)) + states.append(_state_evidence(_runner_state(rpc_url, restart))) + expected = ["TASK_PUBLISHED", "RECEIPT_PUBLISHED", "PAYMENT_PUBLISHED", "PROPOSED", "BONDED", "ACCEPTED", "QUEUED", "PAID"] + if [item["lifecycle"] for item in states] != expected: + raise DrillError("restart boundary states differ: %r" % [item["lifecycle"] for item in states]) + + drills = [ + {"id": 3, "status": "pass", "tier": "A", "detail": "conflicting receipts/envelopes settled independently"}, + {"id": 4, "status": "pass", "tier": "A", "detail": "self-payment used ordinary timeout and treasury path"}, + {"id": 6, "status": "pass", "tier": "A", "detail": "duplicate proposal id reverted without new state"}, + {"id": 7, "status": "pass", "tier": "A", "detail": "underfunded execution reverted then reconciled after funding"}, + {"id": 8, "status": "pass", "tier": "A", "detail": "expired action remained dead; fresh envelope produced a new id"}, + {"id": 12, "status": "pass", "tier": "A", "detail": "dropped-response restart observed exactly one landed effect"}, + {"id": 13, "status": "pass", "tier": "A", "detail": "fresh runner restart matched every lifecycle boundary"}, + {"id": 14, "status": "pass", "tier": "A", "detail": "two keepers produced one queue winner and one benign loser"}, + ] + return drills, {"raceAttempts": attempts, "restartStates": states, "acceptanceRoutes": ["timeout", "evaluated-yes", "evaluated-no"]} + + +def _fork_golden(rpc_url: str, rpc: runner.JsonRpc, stack: dict[str, Any]) -> tuple[list[dict[str, Any]], dict[str, Any]]: + cfg = _config(stack, 16, amount=10**16) + cfg["_actors"] = stack["actors"] + cfg["caps"]["paymentAmount"] = str(10**16) + _publish_all(rpc, cfg) + _propose(rpc, cfg) + _timeout(rpc, cfg, True) + _send(rpc, stack["actors"]["keeperA"], stack["vault"], _queue_data(cfg)) + _mine(rpc, 86_400) + if _try_call(rpc, stack["actors"]["keeperA"], stack["vault"], _execute_data(cfg)): + raise DrillError("fork underfunded execution unexpectedly simulated") + _fund(rpc, stack, 10**16) + _send(rpc, stack["actors"]["keeperA"], stack["vault"], _execute_data(cfg)) + state = _runner_state(rpc_url, cfg) + if state["lifecycle"] != "PAID" or state["actionHash"] != _action(cfg)[1]: + raise DrillError("fork golden path did not reconcile exact binding and balances") + replay = _runner_state(rpc_url, cfg) + state_bytes = runner.canonical_json(_state_evidence(state)) + if state_bytes != runner.canonical_json(_state_evidence(replay)): + raise DrillError("clean RPC replay was not byte-identical") + proof = state["views"]["balanceProof"] + return ( + [ + {"id": 7, "status": "pass", "tier": "K", "detail": "Sepolia-fork underfunded retry reconciled"}, + {"id": 16, "status": "pass", "tier": "K", "detail": "Sepolia-fork task-to-paid path exactly reconciled"}, + ], + { + "actionHash": state["actionHash"], + "proposalId": state["proposalId"], + "queueWindow": state["queued"], + "balanceProof": proof, + "cleanRpcReplaySha256": "0x" + hashlib.sha256(state_bytes).hexdigest(), + }, + ) + + +def _start_anvil(port: int, fork_url: str | None) -> subprocess.Popen[bytes]: + command = [ + "anvil", "--silent", "--order", "fifo", "--port", str(port), "--accounts", "10", + "--balance", "1000", "--timestamp", "1", + ] + if fork_url: + command.extend(("--fork-url", fork_url)) + else: + command.extend(("--chain-id", "31337")) + return subprocess.Popen(command, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) + + +def _session(port: int, fork_url: str | None, fork: bool) -> tuple[dict[str, Any], dict[str, Any]]: + rpc_url = "http://127.0.0.1:%d" % port + process = _start_anvil(port, fork_url) + try: + rpc = runner.JsonRpc(rpc_url) + for _ in range(200): + try: + accounts = [value.lower() for value in rpc.request("eth_accounts", [])] + break + except Exception: + time.sleep(0.05) + else: + raise DrillError("Anvil did not start") + ATTEMPT_LEDGER.clear() + stack = _stack(rpc_url, rpc, accounts, fork) + if fork: + drills, detail = _fork_golden(rpc_url, rpc, stack) + else: + drills, detail = _local_drills(rpc_url, rpc, stack) + detail["attemptLedger"] = list(ATTEMPT_LEDGER) + detail["publishedDocuments"] = _published_documents(rpc, stack) + return {"stack": stack, "drills": drills}, detail + finally: + process.terminate() + process.wait(timeout=5) + + +def run(port: int, fork_url: str) -> dict[str, Any]: + for command in ("anvil", "cast", "forge"): + if shutil.which(command) is None: + raise DrillError("%s is required" % command) + tests = _run([sys.executable, "-m", "unittest", "tools.test_agent_documents", "tools.test_agent_runner"]) + local, local_detail = _session(port, None, False) + fork, fork_detail = _session(port + 1, fork_url, True) + unit_fake = [ + {"id": 1, "status": "pass", "tier": "U+F", "detail": "digest-only view dedupe; append-only logs"}, + {"id": 2, "status": "pass", "tier": "F", "detail": "copied recipient changes envelope, salt and proposal id"}, + {"id": 5, "status": "pass", "tier": "U", "detail": "field substitution and domain replay rejected"}, + {"id": 9, "status": "pass", "tier": "F", "detail": "clean replay identical; bad lineage rejected"}, + {"id": 10, "status": "pass", "tier": "F", "detail": "partial RPC failure sent nothing; retry resumed"}, + {"id": 11, "status": "pass", "tier": "F", "detail": "signer failure recorded and retried exactly"}, + {"id": 15, "status": "pass", "tier": "U+F", "detail": "hostile document remained inert"}, + ] + drills = unit_fake + local["drills"] + fork["drills"] + covered = {item["id"] for item in drills} + if covered != set(range(1, 17)): + raise DrillError("drill coverage is incomplete: %r" % sorted(covered)) + manifest = json.loads(INDEX_MANIFEST.read_text(encoding="utf-8")) + return { + "kind": "fao.agentwork.p1-evidence", + "v": "1", + "repository": {"commit": _run(["git", "rev-parse", "HEAD"]), "dirty": True}, + "pins": { + "agentWorkIndexRuntimeKeccak256": manifest["runtimeCodeKeccak256"], + "agentWorkIndexPredictedAddress": manifest["create2"]["predictedAddress"], + "predictionDeployed": False, + }, + "actors": [ + { + "role": role, + "address": address, + "provenance": "house-wallet", + "manifestCap": { + "nativeWei": "1000000000000000000000", + "bondAsset": "10000" if role in ("automation", "challenger") else "0", + "treasuryAuthority": "0", + }, + } + for role, address in sorted(local["stack"]["actors"].items()) + ], + "chains": { + "anvil": { + **{key: local["stack"][key] for key in ("chainId", "startBlock", "index", "gateway", "arbitration", "vault", "executor")}, + "runtimeCodeKeccak256": local["stack"]["runtimeCodeKeccak256"], + }, + "sepoliaFork": { + **{key: fork["stack"][key] for key in ("chainId", "startBlock", "index", "gateway", "arbitration", "vault", "executor")}, + "canonicalWeth": fork["stack"]["asset"], + "runtimeCodeKeccak256": fork["stack"]["runtimeCodeKeccak256"], + }, + }, + "documents": {"canonicalBuilder": "tools/agent_documents.py", "submissionTerm": "work receipt"}, + "drills": sorted(drills, key=lambda item: (item["id"], item["tier"])), + "attemptLedger": { + "anvil": local_detail["attemptLedger"], + "sepoliaFork": fork_detail["attemptLedger"], + "raceClassifications": local_detail["raceAttempts"], + }, + "publishedDocuments": { + "anvil": local_detail["publishedDocuments"], + "sepoliaFork": fork_detail["publishedDocuments"], + }, + "lifecycle": local_detail["restartStates"], + "acceptanceRoutes": local_detail["acceptanceRoutes"], + "forkGolden": fork_detail, + "testCommand": "%s -m unittest tools.test_agent_documents tools.test_agent_runner" % sys.executable, + "testSummary": tests.splitlines()[-1] if tests else "ok", + "claims": {"liveDeployment": False, "livePayment": False, "demand": False, "guaranteedPayment": False}, + } + + +def main(argv: Sequence[str] | None = None) -> int: + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--port", type=int, default=18645) + parser.add_argument("--fork-url", default="https://ethereum-sepolia-rpc.publicnode.com") + parser.add_argument("--output", type=Path, default=ROOT / "metadata/agent-work-p1-evidence.json") + args = parser.parse_args(argv) + evidence = run(args.port, args.fork_url) + digest = runner.write_evidence(args.output, evidence) + print("wrote %s (%s)" % (args.output, digest)) + return 0 + + +if __name__ == "__main__": + try: + raise SystemExit(main()) + except (DrillError, runner.RunnerError, documents.DocumentError, OSError, ValueError) as exc: + print("error: %s" % exc, file=sys.stderr) + raise SystemExit(1) diff --git a/tools/agent_runner.py b/tools/agent_runner.py new file mode 100644 index 0000000..fb6fb74 --- /dev/null +++ b/tools/agent_runner.py @@ -0,0 +1,843 @@ +#!/usr/bin/env python3 +"""Stateless finalized-state reference runner for FAO agent-work payments.""" + +from __future__ import annotations + +import hashlib +import json +import re +import urllib.error +import urllib.request +from dataclasses import dataclass +from pathlib import Path +from typing import Any, Protocol, Sequence + +try: + from tools import agent_documents as documents +except ModuleNotFoundError: # Direct script execution. + import agent_documents as documents # type: ignore + + +ZERO = "0x" + "00" * 32 +UINT256_MAX = (1 << 256) - 1 +STATE_NAMES = ("INACTIVE", "YES", "NO", "QUEUED", "EVALUATING", "SETTLED") + + +class RunnerError(ValueError): + pass + + +class RpcCallError(RunnerError): + def __init__(self, message: str, data: Any = None) -> None: + super().__init__(message) + self.data = data + + +class StaticCaller(Protocol): + def call(self, transaction: dict[str, Any], block: str) -> str: + ... + + +class TransactionSender(Protocol): + """The only signing/broadcast exit; implementations and keys stay outside this package.""" + + def send(self, unsigned_transaction: dict[str, Any]) -> str: + ... + + +@dataclass(frozen=True) +class Action: + kind: str + to: str + data: str + cap_kind: str = "transactionValue" + cap_amount: int = 0 + + def transaction(self, sender: str, chain_id: int) -> dict[str, Any]: + return { + "from": _address(sender, "sender"), + "to": _address(self.to, "action.to"), + "data": _bytes_hex(self.data, "action.data"), + "value": "0x0", + "chainId": hex(chain_id), + } + + +class JsonRpc: + """Small read-only stdlib JSON-RPC adapter; it cannot sign transactions.""" + + def __init__(self, url: str) -> None: + self.url = url + self._id = 0 + + def request(self, method: str, params: Sequence[Any]) -> Any: + self._id += 1 + raw = json.dumps( + {"jsonrpc": "2.0", "id": self._id, "method": method, "params": list(params)}, + separators=(",", ":"), + ).encode() + request = urllib.request.Request( + self.url, raw, {"Content-Type": "application/json"}, method="POST" + ) + try: + with urllib.request.urlopen(request, timeout=30) as response: + value = json.loads(response.read().decode()) + except (OSError, UnicodeError, json.JSONDecodeError, urllib.error.URLError) as exc: + raise RunnerError("JSON-RPC request failed") from exc + if not isinstance(value, dict) or value.get("id") != self._id: + raise RunnerError("JSON-RPC response envelope is invalid") + if value.get("error") is not None: + error = value["error"] + data = error.get("data") if isinstance(error, dict) else None + raise RpcCallError(f"JSON-RPC {method} failed: {error}", data) + return value.get("result") + + def chain_id(self) -> int: + return _quantity(self.request("eth_chainId", []), "eth_chainId") + + def block(self, number: int | str) -> dict[str, Any]: + tag = hex(number) if isinstance(number, int) else number + value = self.request("eth_getBlockByNumber", [tag, False]) + if not isinstance(value, dict): + raise RunnerError(f"block is unavailable: {tag}") + return value + + def finalized_block(self) -> dict[str, Any]: + return self.block("finalized") + + def logs(self, start: int, end: int, addresses: list[str]) -> list[dict[str, Any]]: + value = self.request( + "eth_getLogs", + [{"fromBlock": hex(start), "toBlock": hex(end), "address": addresses}], + ) + if not isinstance(value, list): + raise RunnerError("eth_getLogs returned a non-array") + return value + + def call(self, transaction: dict[str, Any], block: str) -> str: + value = self.request("eth_call", [transaction, block]) + return _bytes_hex(value, "eth_call result") + + def balance(self, address: str, block: int) -> int: + return _quantity( + self.request("eth_getBalance", [_address(address, "balance address"), hex(block)]), + "eth_getBalance", + ) + + +def _selector(signature: str) -> str: + return documents.keccak256(signature.encode())[2:10] + + +SELECTORS = { + "approve": _selector("approve(address,uint256)"), + "allowance": _selector("allowance(address,address)"), + "balanceOf": _selector("balanceOf(address)"), + "bondToken": _selector("bondToken()"), + "execute": _selector("executeTreasuryTransfer((address,address,uint256,bytes32))"), + "finalize": _selector("finalizeByTimeout(uint256)"), + "getProposal": _selector("getProposal(uint256)"), + "minBond": _selector("treasuryMinActivationBond()"), + "placeYes": _selector("placeYesBond(uint256,uint256)"), + "propose": _selector("proposeTransfer((address,address,uint256,bytes32))"), + "queue": _selector("queueTreasuryTransfer((address,address,uint256,bytes32))"), + "queuedActions": _selector("queuedActions(bytes32)"), + "timeout": _selector("timeout()"), +} +PROPOSAL_NOT_FOUND = "0x" + _selector("ProposalNotFound()") + +TOPICS = { + name: documents.keccak256(signature.encode()) + for name, signature in { + "proposalCreated": "ProposalCreated(uint256,address,uint256)", + "transferProposed": "TransferProposed(uint256,address,address,address,uint256,bytes32)", + "finalized": "FinalizedByTimeout(uint256,bool,address,uint256)", + "resolved": "EvaluationResolved(uint256,bool,address,uint256)", + "queued": "TreasuryTransferQueued(bytes32,uint256,uint256,uint256)", + "executed": "TreasuryTransferExecuted(bytes32,address,address,uint256)", + }.items() +} + + +def _hex(value: Any, size: int, label: str) -> str: + if not isinstance(value, str) or not re.fullmatch(f"0x[0-9a-fA-F]{{{size * 2}}}", value): + raise RunnerError(f"{label} must be {size}-byte hex") + return value.lower() + + +def _address(value: Any, label: str, *, allow_zero: bool = False) -> str: + result = _hex(value, 20, label) + if not allow_zero and result == "0x" + "00" * 20: + raise RunnerError(f"{label} cannot be zero") + return result + + +def _bytes_hex(value: Any, label: str) -> str: + if not isinstance(value, str) or not re.fullmatch(r"0x(?:[0-9a-fA-F]{2})*", value): + raise RunnerError(f"{label} must be byte hex") + return value.lower() + + +def _quantity(value: Any, label: str) -> int: + if isinstance(value, int) and 0 <= value <= UINT256_MAX: + return value + if not isinstance(value, str) or not re.fullmatch(r"0x(?:0|[1-9a-fA-F][0-9a-fA-F]*)", value): + raise RunnerError(f"{label} must be a canonical nonnegative quantity") + return int(value, 16) + + +def _decimal(value: Any, label: str) -> int: + if not isinstance(value, str) or not re.fullmatch(r"0|[1-9][0-9]*", value): + raise RunnerError(f"{label} must be a canonical decimal string") + result = int(value) + if result > UINT256_MAX: + raise RunnerError(f"{label} exceeds uint256") + return result + + +def _word(number: int) -> bytes: + if not isinstance(number, int) or number < 0 or number > UINT256_MAX: + raise RunnerError("ABI word is out of range") + return number.to_bytes(32, "big") + + +def _address_word(address: str, *, allow_zero: bool = False) -> bytes: + return bytes(12) + bytes.fromhex(_address(address, "ABI address", allow_zero=allow_zero)[2:]) + + +def _action_words(action: dict[str, str]) -> bytes: + return b"".join( + ( + _address_word(action["asset"], allow_zero=True), + _address_word(action["recipient"]), + _word(_decimal(action["amount"], "action.amount")), + bytes.fromhex(_hex(action["salt"], 32, "action.salt")[2:]), + ) + ) + + +def _call(selector: str, *words: bytes) -> str: + return "0x" + selector + b"".join(words).hex() + + +def _words(value: str, count: int | None = None) -> list[bytes]: + raw = bytes.fromhex(_bytes_hex(value, "ABI output")[2:]) + if len(raw) % 32 or (count is not None and len(raw) != count * 32): + raise RunnerError("ABI output has the wrong length") + return [raw[index : index + 32] for index in range(0, len(raw), 32)] + + +def _uint_call(caller: StaticCaller, to: str, data: str, block: str) -> int: + return int.from_bytes(_words(caller.call({"to": to, "data": data}, block), 1)[0], "big") + + +def _address_call(caller: StaticCaller, to: str, data: str, block: str) -> str: + word = _words(caller.call({"to": to, "data": data}, block), 1)[0] + if any(word[:12]): + raise RunnerError("ABI address has nonzero padding") + return _address("0x" + word[12:].hex(), "ABI address") + + +def _canonical_log(value: Any) -> dict[str, Any]: + if not isinstance(value, dict): + raise RunnerError("log must be an object") + topics = value.get("topics") + if not isinstance(topics, list) or not topics: + raise RunnerError("log topics are missing") + removed = value.get("removed", False) + if not isinstance(removed, bool): + raise RunnerError("log.removed must be boolean") + return { + "address": _address(value.get("address"), "log.address"), + "blockHash": _hex(value.get("blockHash"), 32, "log.blockHash"), + "blockNumber": _quantity(value.get("blockNumber"), "log.blockNumber"), + "transactionHash": _hex(value.get("transactionHash"), 32, "log.transactionHash"), + "logIndex": _quantity(value.get("logIndex"), "log.logIndex"), + "topics": [_hex(topic, 32, "log.topic") for topic in topics], + "data": _bytes_hex(value.get("data"), "log.data"), + "removed": removed, + } + + +def _config(value: Any) -> dict[str, Any]: + if not isinstance(value, dict): + raise RunnerError("config must be an object") + required = { + "chainId", "fromBlock", "index", "gateway", "arbitration", "vault", "executor", + "automation", "documents", "caps", + } + if set(value) != required: + raise RunnerError("config fields are invalid") + docs = value["documents"] + caps = value["caps"] + if not isinstance(docs, dict) or set(docs) != {"task", "receipt", "payment"}: + raise RunnerError("config.documents fields are invalid") + if not isinstance(caps, dict) or set(caps) != {"paymentAmount", "bondAmount", "transactionValue"}: + raise RunnerError("config.caps fields are invalid") + if ( + not isinstance(value["chainId"], int) or isinstance(value["chainId"], bool) + or not isinstance(value["fromBlock"], int) or isinstance(value["fromBlock"], bool) + ): + raise RunnerError("config chain and start block must be integers") + normalized = { + "chainId": value["chainId"], + "fromBlock": value["fromBlock"], + **{ + key: _address(value[key], f"config.{key}") + for key in ("index", "gateway", "arbitration", "vault", "executor", "automation") + }, + "documents": { + "task": documents.build_task(docs["task"]), + "receipt": documents.build_receipt(docs["receipt"]), + "payment": documents.build_payment(docs["payment"]), + }, + "caps": {key: _decimal(str(caps[key]), f"config.caps.{key}") for key in caps}, + } + if normalized["chainId"] <= 0 or normalized["fromBlock"] < 0: + raise RunnerError("config chain or start block is invalid") + payment = documents.validate_payment(normalized["documents"]["payment"]) + if payment["chainId"] != str(normalized["chainId"]) or payment["vault"] != normalized["vault"]: + raise RunnerError("payment domain does not match config") + return normalized + + +def _prepared(config: dict[str, Any]) -> dict[str, dict[str, Any]]: + return { + name: documents.prepare_publication(name, config["documents"][name]) + for name in ("task", "receipt", "payment") + } + + +def _proposal_view(raw: str) -> dict[str, Any]: + words = _words(raw, 11) + state = int.from_bytes(words[5], "big") + booleans = [int.from_bytes(words[index], "big") for index in (7, 8, 10)] + if ( + state >= len(STATE_NAMES) or any(value not in (0, 1) for value in booleans) + or any(words[index][:12] != bytes(12) for index in (1, 3)) + ): + raise RunnerError("proposal view is invalid") + return { + "minActivationBond": int.from_bytes(words[0], "big"), + "yesBidder": "0x" + words[1][12:].hex(), + "yesBondAmount": int.from_bytes(words[2], "big"), + "noBidder": "0x" + words[3][12:].hex(), + "noBondAmount": int.from_bytes(words[4], "big"), + "state": STATE_NAMES[state], + "lastStateChangeAt": int.from_bytes(words[6], "big"), + "settled": bool(booleans[0]), + "accepted": bool(booleans[1]), + "queuePosition": int.from_bytes(words[9], "big"), + "exists": bool(booleans[2]), + } + + +def _balance(caller: JsonRpc, asset: str, account: str, block: int) -> int: + if asset == "0x" + "00" * 20: + return caller.balance(account, block) + return _uint_call( + caller, + asset, + _call(SELECTORS["balanceOf"], _address_word(account)), + hex(block), + ) + + +def _revert_data(value: Any) -> str | None: + while isinstance(value, dict): + value = value.get("data") + return value.lower() if isinstance(value, str) and re.fullmatch(r"0x[0-9a-fA-F]*", value) else None + + +def collect_snapshot(rpc: JsonRpc, config_value: Any) -> dict[str, Any]: + """Read one complete finalized snapshot or fail before any signer call.""" + + config = _config(config_value) + if rpc.chain_id() != config["chainId"]: + raise RunnerError("RPC chainId does not match config") + final_raw = rpc.finalized_block() + final = { + "number": _quantity(final_raw.get("number"), "finalized.number"), + "hash": _hex(final_raw.get("hash"), 32, "finalized.hash"), + "timestamp": _quantity(final_raw.get("timestamp"), "finalized.timestamp"), + } + if final["number"] < config["fromBlock"]: + raise RunnerError("finalized head precedes config.fromBlock") + + # ponytail: a single short lineage walk; add chunking only when a real deployment needs it. + lineage: dict[int, str] = {} + previous: str | None = None + for number in range(config["fromBlock"], final["number"] + 1): + raw = final_raw if number == final["number"] else rpc.block(number) + block_hash = _hex(raw.get("hash"), 32, "block.hash") + parent = _hex(raw.get("parentHash"), 32, "block.parentHash") + if previous is not None and parent != previous: + raise RunnerError("finalized block lineage is discontinuous") + lineage[number] = block_hash + previous = block_hash + + watched = [config[key] for key in ("index", "gateway", "arbitration", "vault")] + logs = [] + for raw in rpc.logs(config["fromBlock"], final["number"], watched): + log = _canonical_log(raw) + if log["removed"]: + continue + if lineage.get(log["blockNumber"]) != log["blockHash"]: + raise RunnerError("log is not on the finalized lineage") + logs.append(log) + logs.sort(key=lambda item: (item["blockNumber"], item["logIndex"])) + if len({(item["blockHash"], item["logIndex"]) for item in logs}) != len(logs): + raise RunnerError("duplicate finalized log position") + + block = hex(final["number"]) + prepared = _prepared(config) + payment = documents.validate_payment(config["documents"]["payment"]) + action = documents.payment_transfer_action(config["documents"]["payment"]) + proposal_id = int(documents.validate_payment_binding( + config["documents"]["payment"], config["chainId"], config["vault"], action + ), 16) + proposal_data = _call(SELECTORS["getProposal"], _word(proposal_id)) + try: + proposal = _proposal_view(rpc.call({"to": config["arbitration"], "data": proposal_data}, block)) + except RpcCallError as exc: + if _revert_data(exc.data) != PROPOSAL_NOT_FOUND: + raise + proposal = None + queued_words = _words( + rpc.call( + {"to": config["vault"], "data": _call(SELECTORS["queuedActions"], _word(proposal_id))}, + block, + ), + 4, + ) + queued = { + "executeAfter": int.from_bytes(queued_words[0], "big"), + "expiresAt": int.from_bytes(queued_words[1], "big"), + "executed": bool(int.from_bytes(queued_words[2], "big")), + "expired": bool(int.from_bytes(queued_words[3], "big")), + } + if any(int.from_bytes(word, "big") not in (0, 1) for word in queued_words[2:]): + raise RunnerError("queued action booleans are invalid") + min_bond = _uint_call(rpc, config["gateway"], "0x" + SELECTORS["minBond"], block) + timeout = _uint_call(rpc, config["arbitration"], "0x" + SELECTORS["timeout"], block) + bond_token = _address_call(rpc, config["arbitration"], "0x" + SELECTORS["bondToken"], block) + allowance = _uint_call( + rpc, + bond_token, + _call( + SELECTORS["allowance"], + _address_word(config["automation"]), + _address_word(config["arbitration"]), + ), + block, + ) + execute_tx = { + "from": config["automation"], + "to": config["vault"], + "data": _call(SELECTORS["execute"], _action_words(action)), + "value": "0x0", + } + try: + rpc.call(execute_tx, block) + execution_ok = True + except RpcCallError: + execution_ok = False + + executed_logs = [ + log + for log in logs + if log["address"] == config["vault"] + and log["topics"][0] == TOPICS["executed"] + and len(log["topics"]) == 4 + and log["topics"][1] == "0x" + proposal_id.to_bytes(32, "big").hex() + ] + balance_proof = None + if executed_logs: + if len(executed_logs) != 1 or executed_logs[0]["blockNumber"] == 0: + raise RunnerError("payment execution log cardinality is invalid") + paid_block = executed_logs[0]["blockNumber"] + balance_proof = { + "beforeBlock": paid_block - 1, + "afterBlock": paid_block, + "executorBefore": _balance(rpc, payment["asset"], config["executor"], paid_block - 1), + "executorAfter": _balance(rpc, payment["asset"], config["executor"], paid_block), + "recipientBefore": _balance(rpc, payment["asset"], payment["recipient"], paid_block - 1), + "recipientAfter": _balance(rpc, payment["asset"], payment["recipient"], paid_block), + } + return { + "finalized": final, + "logs": logs, + "views": { + "proposal": proposal, + "queued": queued, + "minimumBond": min_bond, + "timeout": timeout, + "bondToken": bond_token, + "allowance": allowance, + "executionSimulationOk": execution_ok, + "balanceProof": balance_proof, + }, + "prepared": {name: prepared[name]["documentDigest"] for name in prepared}, + } + + +def _event_words(log: dict[str, Any], count: int) -> list[bytes]: + return _words(log["data"], count) + + +def derive_state(config_value: Any, snapshot: Any) -> dict[str, Any]: + """Purely derive one lifecycle view from exact finalized logs and pinned views.""" + + config = _config(config_value) + if not isinstance(snapshot, dict) or set(snapshot) != {"finalized", "logs", "views", "prepared"}: + raise RunnerError("snapshot fields are invalid") + final = snapshot["finalized"] + if not isinstance(final, dict) or set(final) != {"number", "hash", "timestamp"}: + raise RunnerError("finalized block fields are invalid") + final = { + "number": _quantity(final["number"], "finalized.number"), + "hash": _hex(final["hash"], 32, "finalized.hash"), + "timestamp": _quantity(final["timestamp"], "finalized.timestamp"), + } + now = final["timestamp"] + raw_logs = snapshot["logs"] + views = snapshot["views"] + if not isinstance(raw_logs, list) or not isinstance(views, dict): + raise RunnerError("snapshot logs or views are invalid") + logs = [_canonical_log(value) for value in raw_logs] + if logs != sorted(logs, key=lambda item: (item["blockNumber"], item["logIndex"])): + raise RunnerError("snapshot logs are not canonically ordered") + if len({(item["blockHash"], item["logIndex"]) for item in logs}) != len(logs): + raise RunnerError("snapshot has a duplicate log position") + + prepared = _prepared(config) + payment = documents.validate_payment(config["documents"]["payment"]) + receipt = documents.validate_receipt(config["documents"]["receipt"]) + if receipt["task"] != prepared["task"]["documentDigest"]: + raise RunnerError("receipt is not parented to the configured task") + if payment["task"] != receipt["task"] or payment["receipt"] != prepared["receipt"]["documentDigest"]: + raise RunnerError("payment lineage does not match the configured receipt") + action = documents.payment_transfer_action(config["documents"]["payment"]) + action_hash = documents.validate_payment_binding( + config["documents"]["payment"], config["chainId"], config["vault"], action + ) + proposal_id = int(action_hash, 16) + action_topic = "0x" + proposal_id.to_bytes(32, "big").hex() + + publications: dict[str, dict[str, Any]] = {} + hostile = [] + decoded_logs = [] + schemas = { + documents.TASK_KIND: "task", + documents.RECEIPT_KIND: "receipt", + documents.PAYMENT_KIND: "payment", + } + for log in logs: + if log["address"] != config["index"] or log["topics"][0] != documents.PUBLISHED_TOPIC: + continue + try: + decoded = documents.decode_published_log({"topics": log["topics"], "data": log["data"]}) + validator = documents.VALIDATORS.get(schemas.get(decoded["kind"], "")) + if validator is not None: + validator(decoded["document"]) + decoded_logs.append((log, decoded)) + except documents.DocumentError as exc: + hostile.append({"transactionHash": log["transactionHash"], "reason": str(exc)}) + for name in ("task", "receipt", "payment"): + expected = prepared[name] + matches = [] + for log, decoded in decoded_logs: + if ( + decoded["kind"] == expected["kind"] + and decoded["parentDigest"] == expected["parentDigest"] + and decoded["documentDigest"] == expected["documentDigest"] + and decoded["document"] == expected["document"] + ): + matches.append(log) + publications[name] = { + "published": bool(matches), + "occurrences": len(matches), + "digest": expected["documentDigest"], + "firstLog": None if not matches else matches[0], + } + + proposal = views.get("proposal") + proposal_created = [ + log for log in logs + if log["address"] == config["arbitration"] and log["topics"][0] == TOPICS["proposalCreated"] + and len(log["topics"]) == 3 and log["topics"][1] == action_topic + ] + transfer_proposed = [ + log for log in logs + if log["address"] == config["gateway"] and log["topics"][0] == TOPICS["transferProposed"] + and len(log["topics"]) == 4 and log["topics"][1] == action_topic + ] + proposed = proposal is not None + if proposed and (len(proposal_created) != 1 or len(transfer_proposed) != 1): + raise RunnerError("proposal view lacks exact creation and transfer logs") + if not proposed and (proposal_created or transfer_proposed): + raise RunnerError("proposal logs disagree with proposal view") + if transfer_proposed: + words = _event_words(transfer_proposed[0], 3) + logged_recipient = "0x" + words[0][12:].hex() + if ( + transfer_proposed[0]["topics"][3] != "0x" + bytes(12).hex() + action["asset"][2:] + or logged_recipient != action["recipient"] + or int.from_bytes(words[1], "big") != int(action["amount"]) + or "0x" + words[2].hex() != action["salt"] + ): + raise RunnerError("TransferProposed does not match payment binding") + if proposal_created: + created = proposal_created[0] + if ( + created["topics"][2] != "0x" + bytes(12).hex() + config["gateway"][2:] + or int.from_bytes(_event_words(created, 1)[0], "big") + != int(views["minimumBond"]) + ): + raise RunnerError("ProposalCreated does not match the gateway bond binding") + + settlement = [ + log for log in logs + if log["address"] == config["arbitration"] + and log["topics"][0] in (TOPICS["finalized"], TOPICS["resolved"]) + and len(log["topics"]) == 3 and log["topics"][1] == action_topic + ] + accepted = rejected = False + acceptance_route = None + if proposal is not None and proposal["settled"]: + if len(settlement) != 1: + raise RunnerError("settled proposal lacks one matching finalized arbitration log") + accepted_word = int.from_bytes(_event_words(settlement[0], 2)[0], "big") + if accepted_word not in (0, 1) or bool(accepted_word) != bool(proposal["accepted"]): + raise RunnerError("accepted view and finalized arbitration log disagree") + accepted = bool(accepted_word) + rejected = not accepted + acceptance_route = "timeout" if settlement[0]["topics"][0] == TOPICS["finalized"] else "evaluated" + elif settlement: + raise RunnerError("finalized arbitration log disagrees with proposal view") + + queued = views.get("queued") + if not isinstance(queued, dict): + raise RunnerError("queued view is missing") + queue_logs = [ + log for log in logs + if log["address"] == config["vault"] and log["topics"][0] == TOPICS["queued"] + and len(log["topics"]) == 3 and log["topics"][1] == action_topic + and log["topics"][2] == action_topic + ] + is_queued = int(queued["executeAfter"]) != 0 + if is_queued: + if len(queue_logs) != 1: + raise RunnerError("queued view lacks one matching queue log") + queue_words = _event_words(queue_logs[0], 2) + if [int.from_bytes(word, "big") for word in queue_words] != [ + int(queued["executeAfter"]), int(queued["expiresAt"]) + ]: + raise RunnerError("queue event and view disagree") + elif queue_logs: + raise RunnerError("queue log disagrees with queued view") + + execution_logs = [ + log for log in logs + if log["address"] == config["vault"] and log["topics"][0] == TOPICS["executed"] + and len(log["topics"]) == 4 and log["topics"][1] == action_topic + ] + paid = bool(queued.get("executed", False)) + if paid: + if len(execution_logs) != 1: + raise RunnerError("executed view lacks one matching treasury log") + event = execution_logs[0] + if ( + event["topics"][2] != "0x" + bytes(12).hex() + action["asset"][2:] + or event["topics"][3] != "0x" + bytes(12).hex() + action["recipient"][2:] + or int.from_bytes(_event_words(event, 1)[0], "big") != int(action["amount"]) + ): + raise RunnerError("treasury execution log does not match exact action") + proof = views.get("balanceProof") + amount = int(action["amount"]) + if not isinstance(proof, dict) or ( + int(proof["executorBefore"]) - int(proof["executorAfter"]) != amount + or int(proof["recipientAfter"]) - int(proof["recipientBefore"]) != amount + or int(proof["executorBefore"]) + int(proof["recipientBefore"]) + != int(proof["executorAfter"]) + int(proof["recipientAfter"]) + ): + raise RunnerError("paid balance deltas do not conserve the exact transfer") + elif execution_logs: + raise RunnerError("treasury execution log disagrees with executed view") + + expired = bool(queued.get("expired", False)) or ( + is_queued and now > int(queued["expiresAt"]) and not paid + ) + executable = ( + accepted and is_queued and not paid and not expired + and now >= int(queued["executeAfter"]) and bool(views.get("executionSimulationOk")) + ) + shortfall = ( + accepted and is_queued and not paid and not expired + and now >= int(queued["executeAfter"]) and not bool(views.get("executionSimulationOk")) + ) + + if paid: + lifecycle = "PAID" + elif expired: + lifecycle = "EXPIRED" + elif executable: + lifecycle = "EXECUTABLE" + elif shortfall: + lifecycle = "SHORTFALL" + elif accepted and is_queued: + lifecycle = "QUEUED" + elif accepted: + lifecycle = "ACCEPTED" + elif rejected: + lifecycle = "REJECTED" + elif proposal and proposal["state"] in ("YES", "NO", "QUEUED", "EVALUATING", "SETTLED"): + lifecycle = "BONDED" if proposal["yesBondAmount"] else "PROPOSED" + elif proposed: + lifecycle = "PROPOSED" + elif publications["payment"]["published"]: + lifecycle = "PAYMENT_PUBLISHED" + elif publications["receipt"]["published"]: + lifecycle = "RECEIPT_PUBLISHED" + elif publications["task"]["published"]: + lifecycle = "TASK_PUBLISHED" + else: + lifecycle = "IDLE" + + return { + "lifecycle": lifecycle, + "finalized": final, + "publications": publications, + "hostileDocuments": hostile, + "action": action, + "actionHash": action_hash, + "proposalId": str(proposal_id), + "proposal": proposal, + "accepted": accepted, + "rejected": rejected, + "acceptanceRoute": acceptance_route, + "queued": queued, + "executable": executable, + "shortfall": shortfall, + "paid": paid, + "views": views, + } + + +def next_action(config_value: Any, state: Any) -> Action | None: + """Choose at most one deterministic transaction from a fully derived state.""" + + config = _config(config_value) + if not isinstance(state, dict): + raise RunnerError("state must be an object") + prepared = _prepared(config) + for name in ("task", "receipt", "payment"): + if not state["publications"][name]["published"]: + item = prepared[name] + return Action(f"publish-{name}", config["index"], item["calldata"]) + + action = state["action"] + proposal_id = int(state["proposalId"]) + if state["proposal"] is None: + return Action( + "propose", + config["gateway"], + _call(SELECTORS["propose"], _action_words(action)), + ) + proposal = state["proposal"] + if proposal["settled"]: + if not proposal["accepted"]: + return None + if not state["queued"]["executeAfter"]: + return Action("queue", config["vault"], _call(SELECTORS["queue"], _action_words(action))) + if state["executable"]: + return Action( + "execute", + config["vault"], + _call(SELECTORS["execute"], _action_words(action)), + "paymentAmount", + int(action["amount"]), + ) + return None + + min_bond = int(state["views"]["minimumBond"]) + if proposal["state"] == "INACTIVE": + if int(state["views"]["allowance"]) < min_bond: + return Action( + "approve-bond", + state["views"]["bondToken"], + _call(SELECTORS["approve"], _address_word(config["arbitration"]), _word(min_bond)), + "bondAmount", + min_bond, + ) + return Action( + "place-yes-bond", + config["arbitration"], + _call(SELECTORS["placeYes"], _word(proposal_id), _word(min_bond)), + "bondAmount", + min_bond, + ) + if proposal["state"] == "YES" and int(state["finalized"]["timestamp"]) >= ( + int(proposal["lastStateChangeAt"]) + int(state["views"]["timeout"]) + ): + return Action("finalize-timeout", config["arbitration"], _call(SELECTORS["finalize"], _word(proposal_id))) + # QUEUED/EVALUATING outcomes belong to the resolver; this runner only observes them. + return None + + +def _check_cap(config: dict[str, Any], action: Action) -> None: + if action.cap_kind not in config["caps"] or action.cap_amount > config["caps"][action.cap_kind]: + raise RunnerError(f"{action.cap_kind} cap exhausted") + + +def tick( + config_value: Any, + rpc: JsonRpc, + caller: StaticCaller, + sender: TransactionSender, +) -> dict[str, Any]: + """Re-derive, simulate, cap-check and send no more than one unsigned transaction.""" + + config = _config(config_value) + snapshot = collect_snapshot(rpc, config_value) + state = derive_state(config_value, snapshot) + action = next_action(config_value, state) + if action is None: + return {"state": state, "action": None, "attempts": []} + transaction = action.transaction(config["automation"], config["chainId"]) + attempt = {"kind": action.kind, "transaction": transaction, "simulation": "pending", "outcome": "not-sent"} + try: + result = caller.call(transaction, hex(int(state["finalized"]["number"]))) + _bytes_hex(result, "simulation result") + attempt["simulation"] = "ok" + except Exception as exc: + attempt.update(simulation="failed", error=str(exc)) + return {"state": state, "action": action.kind, "attempts": [attempt]} + try: + _check_cap(config, action) + except RunnerError as exc: + attempt.update(outcome="cap-refused", error=str(exc)) + return {"state": state, "action": action.kind, "attempts": [attempt]} + try: + attempt["transactionHash"] = _hex(sender.send(transaction), 32, "transaction hash") + attempt["outcome"] = "submitted" + except Exception as exc: + attempt.update(outcome="signer-failed", error=str(exc)) + return {"state": state, "action": action.kind, "attempts": [attempt]} + + +def canonical_json(value: Any) -> bytes: + return json.dumps(value, sort_keys=True, separators=(",", ":")).encode() + + +def write_evidence(path: Path, evidence: Any) -> str: + if not isinstance(evidence, dict) or evidence.get("kind") != "fao.agentwork.p1-evidence" or evidence.get("v") != "1": + raise RunnerError("P1 evidence kind or version is invalid") + drills = evidence.get("drills") + if not isinstance(drills, list) or not drills or any(item.get("status") != "pass" for item in drills): + raise RunnerError("evidence may contain only drills that actually passed") + raw = canonical_json(evidence) + b"\n" + digest = "0x" + hashlib.sha256(raw).hexdigest() + path.write_bytes(raw) + path.with_suffix(path.suffix + ".sha256").write_text(digest + "\n", encoding="ascii") + return digest diff --git a/tools/economic_deployment.py b/tools/economic_deployment.py index 9a08a94..088499a 100644 --- a/tools/economic_deployment.py +++ b/tools/economic_deployment.py @@ -178,7 +178,7 @@ "runtimeCodeHashes", "finalization", } -RUNTIME_CODE_HASH_KEYS = ("treasuryExecutor",) +RUNTIME_CODE_HASH_KEYS = ("vault", "proposalGateway", "arbitration", "treasuryExecutor") SELF_SERVE_KEYS = {"schemaVersion", "network", "chainId", "registrar", "prerequisites"} ManifestError = site_deployment.ManifestError @@ -774,14 +774,14 @@ def validate_manifest( schema_version = _json_integer(manifest.get("schemaVersion"), "schemaVersion") _expect_keys(manifest, MANIFEST_KEYS, "economic deployment manifest") if ( - schema_version != 3 + schema_version != 4 or manifest["network"] != "sepolia" or _json_integer(manifest["chainId"], "chainId") != CHAIN_ID or manifest["status"] not in {"sealed", "live"} ): - raise ManifestError("manifest must be sealed/live Sepolia schema version 3") + raise ManifestError("manifest must be sealed/live Sepolia schema version 4") if manifest["creationRoute"] not in {"create", "registrar"}: - raise ManifestError("schema version 3 requires a create or registrar route") + raise ManifestError("schema version 4 requires a create or registrar route") registrar_route = manifest["creationRoute"] == "registrar" expected_core, expected_flm, canonical_creations = _canonical_blob_hashes() @@ -941,10 +941,8 @@ def validate_manifest( runtime_hashes = _require_dict(manifest["runtimeCodeHashes"], "runtimeCodeHashes") if tuple(runtime_hashes) != RUNTIME_CODE_HASH_KEYS: raise ManifestError("runtimeCodeHashes is not in canonical order") - _canonical_hash( - runtime_hashes["treasuryExecutor"], - "runtimeCodeHashes.treasuryExecutor", - ) + for key in RUNTIME_CODE_HASH_KEYS: + _canonical_hash(runtime_hashes[key], f"runtimeCodeHashes.{key}") finalization = manifest["finalization"] if manifest["status"] == "sealed": @@ -963,6 +961,18 @@ def _digest(value: bytes, hash_: Callable[[bytes], str]) -> str: return _canonical_hash(hash_(value), "Keccak-256 digest") +def _runtime_code_hashes( + contracts: dict[str, Any], client: Any, hash_: Callable[[bytes], str] +) -> dict[str, str]: + hashes = {} + for key in RUNTIME_CODE_HASH_KEYS: + code = client.code(contracts[key]) + if not code: + raise ManifestError(f"contracts.{key} has no deployed code") + hashes[key] = _digest(code, hash_) + return hashes + + def _create_address(sender: str, nonce: int, hash_: Callable[[bytes], str]) -> str: if nonce < 0 or nonce >= 1 << 64: raise ManifestError("CREATE nonce is outside the Ethereum account nonce range") @@ -1593,14 +1603,14 @@ def manifest_from_broadcast( core_config, grants, core_codes = _decode_deploy_core(deploy_core["input"]) flm_config, flm_codes = _decode_deploy_flm(deploy_flm["input"]) contracts = _deployed_contracts(receipt_address, len(grants), client, hash_) - executor_runtime_hash = _digest(client.code(contracts["treasuryExecutor"]), hash_) + runtime_code_hashes = _runtime_code_hashes(contracts, client, hash_) core_hashes = { key: _digest(code, hash_) for key, code in zip(CORE_BLOBS, core_codes) } flm_hashes = {key: _digest(code, hash_) for key, code in zip(FLM_BLOBS, flm_codes)} manifest = { - "schemaVersion": 3, + "schemaVersion": 4, "creationRoute": "create", "status": "sealed", "network": "sepolia", @@ -1629,7 +1639,7 @@ def manifest_from_broadcast( "observationCardinality": OBSERVATION_CARDINALITY, "contracts": contracts, "codeBlobs": {"core": core_hashes, "flm": flm_hashes}, - "runtimeCodeHashes": {"treasuryExecutor": executor_runtime_hash}, + "runtimeCodeHashes": runtime_code_hashes, "finalization": None, } verify_rpc( @@ -2016,7 +2026,7 @@ def manifest_from_chain( flm_config, flm_codes = flm["config"], flm["codes"] receipt_address = discovered["receipt"] contracts = _deployed_contracts(receipt_address, len(grants), client, hash_) - executor_runtime_hash = _digest(client.code(contracts["treasuryExecutor"]), hash_) + runtime_code_hashes = _runtime_code_hashes(contracts, client, hash_) _validate_seal_log( core["log"], CORE_SEALED_EVENT, @@ -2051,7 +2061,7 @@ def manifest_from_chain( ) registrar = discovered["registrar"] manifest = { - "schemaVersion": 3, + "schemaVersion": 4, "creationRoute": "registrar", "status": "live" if discovered["status"] == "live" else "sealed", "network": "sepolia", @@ -2087,7 +2097,7 @@ def manifest_from_chain( "core": {key: _digest(code, hash_) for key, code in zip(CORE_BLOBS, core_codes)}, "flm": {key: _digest(code, hash_) for key, code in zip(FLM_BLOBS, flm_codes)}, }, - "runtimeCodeHashes": {"treasuryExecutor": executor_runtime_hash}, + "runtimeCodeHashes": runtime_code_hashes, "finalization": None if discovered["finalization"] is None else discovered["finalization"]["evidence"], @@ -2426,13 +2436,13 @@ def _verify_code_and_wiring(manifest: dict[str, Any], client: Any, hash_: Callab for index, wallet in enumerate(c["vestingWallets"]): if not client.code(wallet): raise ManifestError(f"vesting wallet {index} has no deployed code") - executor_code = client.code(c["treasuryExecutor"]) - executor_hash = _digest(executor_code, hash_) + live_runtime_hashes = _runtime_code_hashes(c, client, hash_) + for key in RUNTIME_CODE_HASH_KEYS: + if live_runtime_hashes[key] != manifest["runtimeCodeHashes"][key]: + raise ManifestError(f"{key} runtime code hash mismatch") + executor_hash = live_runtime_hashes["treasuryExecutor"] expected_executor_hash = _digest(_executor_runtime_code(c["vault"]), hash_) - if ( - executor_hash != expected_executor_hash - or executor_hash != manifest["runtimeCodeHashes"]["treasuryExecutor"] - ): + if executor_hash != expected_executor_hash: raise ManifestError("treasury executor runtime code hash mismatch") if not _call_bool(client, receipt, "coreSealed()(bool)") or not _call_bool( diff --git a/tools/test_agent_runner.py b/tools/test_agent_runner.py new file mode 100644 index 0000000..f934587 --- /dev/null +++ b/tools/test_agent_runner.py @@ -0,0 +1,405 @@ +from __future__ import annotations + +import copy +import hashlib +import json +import tempfile +import unittest +from pathlib import Path + +from tools import agent_documents as documents +from tools import agent_runner as runner + + +def address(byte: str) -> str: + return "0x" + byte * 40 + + +def digest(number: int) -> str: + return "0x" + number.to_bytes(32, "big").hex() + + +INDEX = address("1") +GATEWAY = address("2") +ARBITRATION = address("3") +VAULT = address("4") +EXECUTOR = address("5") +BOND = address("6") +WORKER = address("7") +RECIPIENT = address("8") +ASSET = address("9") +AUTOMATION = "0x693e3fb46bb36ee43c702fe94f9463df0691b43d" + + +def config() -> dict: + task = { + "v": "1", + "kind": "fao.task", + "chainId": "31337", + "vault": VAULT, + "title": "Deterministic task", + "spec": "Return the exact artifact digest.", + "salt": digest(101), + } + task_digest = documents.document_digest(documents.build_task(task)) + receipt = { + "v": "1", + "kind": "fao.receipt", + "chainId": "31337", + "vault": VAULT, + "task": task_digest, + "worker": WORKER, + "artifacts": [{"digest": digest(102), "uri": "https://example.test/artifact"}], + "summary": "Exact result", + "salt": digest(103), + } + receipt_digest = documents.document_digest(documents.build_receipt(receipt)) + payment = { + "v": "1", + "kind": "fao.payment", + "chainId": "31337", + "vault": VAULT, + "asset": ASSET, + "recipient": RECIPIENT, + "amount": "10", + "task": task_digest, + "receipt": receipt_digest, + "salt": digest(104), + } + return { + "chainId": 31337, + "fromBlock": 1, + "index": INDEX, + "gateway": GATEWAY, + "arbitration": ARBITRATION, + "vault": VAULT, + "executor": EXECUTOR, + "automation": AUTOMATION, + "documents": {"task": task, "receipt": receipt, "payment": payment}, + "caps": {"paymentAmount": "10", "bondAmount": "2", "transactionValue": "0"}, + } + + +def event_data(*values: object) -> str: + words = [] + for value in values: + if isinstance(value, bool): + words.append(int(value).to_bytes(32, "big")) + elif isinstance(value, int): + words.append(value.to_bytes(32, "big")) + elif isinstance(value, str) and len(value) == 42: + words.append(bytes(12) + bytes.fromhex(value[2:])) + elif isinstance(value, str) and len(value) == 66: + words.append(bytes.fromhex(value[2:])) + else: + raise AssertionError(value) + return "0x" + b"".join(words).hex() + + +def log(address_: str, topic0: str, topics: list[str], data: str, ordinal: int) -> dict: + return { + "address": address_, + "blockHash": digest(1000 + ordinal), + "blockNumber": ordinal, + "transactionHash": digest(2000 + ordinal), + "logIndex": 0, + "topics": [topic0, *topics], + "data": data, + "removed": False, + } + + +def published(name: str, cfg: dict, ordinal: int, document: bytes | None = None) -> dict: + item = documents.prepare_publication(name, cfg["documents"][name]) + raw = item["document"] if document is None else document + size = len(raw) + data = ( + bytes(12) + + bytes.fromhex(AUTOMATION[2:]) + + (64).to_bytes(32, "big") + + size.to_bytes(32, "big") + + raw + + bytes((-size) % 32) + ) + return log( + INDEX, + documents.PUBLISHED_TOPIC, + [item["kind"], item["parentDigest"], documents.document_digest(raw)], + "0x" + data.hex(), + ordinal, + ) + + +def action_facts(cfg: dict) -> tuple[dict[str, str], str, str]: + raw = documents.build_payment(cfg["documents"]["payment"]) + action = documents.payment_transfer_action(raw) + action_hash = documents.validate_payment_binding(raw, cfg["chainId"], cfg["vault"], action) + return action, action_hash, action_hash + + +def proposal_logs(cfg: dict, start: int = 4) -> list[dict]: + action, action_hash, topic = action_facts(cfg) + return [ + log( + ARBITRATION, + runner.TOPICS["proposalCreated"], + [topic, "0x" + bytes(12).hex() + GATEWAY[2:]], + event_data(2), + start, + ), + log( + GATEWAY, + runner.TOPICS["transferProposed"], + [topic, "0x" + bytes(12).hex() + AUTOMATION[2:], "0x" + bytes(12).hex() + action["asset"][2:]], + event_data(action["recipient"], int(action["amount"]), action["salt"]), + start + 1, + ), + ] + + +def proposal(state: str = "INACTIVE", **changes: object) -> dict: + value = { + "minActivationBond": 2, + "yesBidder": address("0"), + "yesBondAmount": 0, + "noBidder": address("0"), + "noBondAmount": 0, + "state": state, + "lastStateChangeAt": 90, + "settled": False, + "accepted": False, + "queuePosition": 0, + "exists": True, + } + value.update(changes) + return value + + +def snapshot(cfg: dict, logs: list[dict] | None = None, proposal_: dict | None = None, now: int = 100) -> dict: + prepared = { + name: documents.prepare_publication(name, cfg["documents"][name])["documentDigest"] + for name in ("task", "receipt", "payment") + } + return { + "finalized": {"number": 100, "hash": digest(100), "timestamp": now}, + "logs": [] if logs is None else logs, + "views": { + "proposal": proposal_, + "queued": {"executeAfter": 0, "expiresAt": 0, "executed": False, "expired": False}, + "minimumBond": 2, + "timeout": 10, + "bondToken": BOND, + "allowance": 0, + "executionSimulationOk": False, + "balanceProof": None, + }, + "prepared": prepared, + } + + +class AgentRunnerUnitTest(unittest.TestCase): + def test_one_action_sequence_keeps_accepted_executable_and_paid_distinct(self) -> None: + cfg = config() + logs: list[dict] = [] + state = runner.derive_state(cfg, snapshot(cfg, logs)) + self.assertEqual((state["lifecycle"], runner.next_action(cfg, state).kind), ("IDLE", "publish-task")) + for index, name in enumerate(("task", "receipt", "payment"), 1): + logs.append(published(name, cfg, index)) + state = runner.derive_state(cfg, snapshot(cfg, logs)) + expected = {"task": "publish-receipt", "receipt": "publish-payment", "payment": "propose"}[name] + self.assertEqual(runner.next_action(cfg, state).kind, expected) + + logs += proposal_logs(cfg) + state = runner.derive_state(cfg, snapshot(cfg, logs, proposal())) + self.assertEqual((state["lifecycle"], runner.next_action(cfg, state).kind), ("PROPOSED", "approve-bond")) + ready = snapshot(cfg, logs, proposal()) + ready["views"]["allowance"] = 2 + state = runner.derive_state(cfg, ready) + self.assertEqual(runner.next_action(cfg, state).kind, "place-yes-bond") + + bonded = proposal("YES", yesBidder=AUTOMATION, yesBondAmount=2) + state = runner.derive_state(cfg, snapshot(cfg, logs, bonded, 99)) + self.assertEqual((state["lifecycle"], runner.next_action(cfg, state)), ("BONDED", None)) + state = runner.derive_state(cfg, snapshot(cfg, logs, bonded, 100)) + self.assertEqual(runner.next_action(cfg, state).kind, "finalize-timeout") + + _, action_hash, topic = action_facts(cfg) + logs.append(log(ARBITRATION, runner.TOPICS["finalized"], [topic, "0x" + bytes(12).hex() + AUTOMATION[2:]], event_data(True, 2), 10)) + accepted = proposal("SETTLED", yesBidder=AUTOMATION, yesBondAmount=2, settled=True, accepted=True) + accepted_state = runner.derive_state(cfg, snapshot(cfg, logs, accepted, 110)) + self.assertEqual((accepted_state["lifecycle"], accepted_state["accepted"], accepted_state["paid"]), ("ACCEPTED", True, False)) + self.assertEqual(runner.next_action(cfg, accepted_state).kind, "queue") + + logs.append(log(VAULT, runner.TOPICS["queued"], [topic, topic], event_data(120, 200), 11)) + queued = snapshot(cfg, logs, accepted, 119) + queued["views"]["queued"].update(executeAfter=120, expiresAt=200) + state = runner.derive_state(cfg, queued) + self.assertEqual((state["lifecycle"], state["executable"]), ("QUEUED", False)) + queued["finalized"]["timestamp"] = 120 + state = runner.derive_state(cfg, queued) + self.assertEqual((state["lifecycle"], state["shortfall"], runner.next_action(cfg, state)), ("SHORTFALL", True, None)) + queued["views"]["executionSimulationOk"] = True + state = runner.derive_state(cfg, queued) + self.assertEqual((state["lifecycle"], runner.next_action(cfg, state).kind), ("EXECUTABLE", "execute")) + + action = state["action"] + logs.append(log(VAULT, runner.TOPICS["executed"], [topic, "0x" + bytes(12).hex() + action["asset"][2:], "0x" + bytes(12).hex() + action["recipient"][2:]], event_data(10), 12)) + queued["logs"] = logs + queued["views"]["queued"]["executed"] = True + queued["views"]["balanceProof"] = { + "beforeBlock": 11, "afterBlock": 12, + "executorBefore": 100, "executorAfter": 90, + "recipientBefore": 5, "recipientAfter": 15, + } + state = runner.derive_state(cfg, queued) + self.assertEqual((state["lifecycle"], state["paid"], runner.next_action(cfg, state)), ("PAID", True, None)) + + def test_duplicate_receipt_is_append_only_but_deduplicated_only_in_view(self) -> None: + cfg = config() + logs = [published("task", cfg, 1), published("receipt", cfg, 2), published("receipt", cfg, 3)] + state = runner.derive_state(cfg, snapshot(cfg, logs)) + self.assertEqual(state["publications"]["receipt"]["occurrences"], 2) + self.assertTrue(state["publications"]["receipt"]["published"]) + self.assertEqual(len(logs), 3) + + def test_copied_work_and_every_binding_substitution_are_independent(self) -> None: + original = config() + copied = copy.deepcopy(original) + copied["documents"]["payment"]["recipient"] = WORKER + copied["documents"]["payment"]["salt"] = digest(999) + original_action, original_hash, _ = action_facts(original) + copied_action, copied_hash, _ = action_facts(copied) + self.assertNotEqual((original_action["salt"], original_hash), (copied_action["salt"], copied_hash)) + self.assertEqual(action_facts(original)[1], original_hash) + + raw = documents.build_payment(original["documents"]["payment"]) + for field, replacement in ( + ("asset", address("a")), ("recipient", address("b")), ("amount", "11"), ("salt", digest(12)) + ): + changed = dict(original_action) + changed[field] = replacement + with self.assertRaises(documents.DocumentError): + documents.validate_payment_binding(raw, 31337, VAULT, changed) + for chain, vault in ((1, VAULT), (31337, address("c"))): + with self.assertRaises(documents.DocumentError): + documents.validate_payment_binding(raw, chain, vault, original_action) + + def test_view_log_disagreement_and_nonconserving_payment_fail_closed(self) -> None: + cfg = config() + logs = [published(name, cfg, i) for i, name in enumerate(("task", "receipt", "payment"), 1)] + proposal_logs(cfg) + _, _, topic = action_facts(cfg) + logs.append(log(ARBITRATION, runner.TOPICS["finalized"], [topic, "0x" + bytes(12).hex() + AUTOMATION[2:]], event_data(False, 2), 10)) + with self.assertRaisesRegex(runner.RunnerError, "disagree"): + runner.derive_state(cfg, snapshot(cfg, logs, proposal("SETTLED", settled=True, accepted=True))) + + def test_malformed_hostile_index_document_is_inert(self) -> None: + cfg = config() + hostile = published("task", cfg, 1, b'{"not":"canonical", "v":"1"}') + state = runner.derive_state(cfg, snapshot(cfg, [hostile])) + self.assertEqual(state["lifecycle"], "IDLE") + self.assertEqual(len(state["hostileDocuments"]), 1) + self.assertEqual(runner.next_action(cfg, state).kind, "publish-task") + + def test_evidence_is_compact_sorted_and_bound_by_sha256(self) -> None: + evidence = {"v": "1", "kind": "fao.agentwork.p1-evidence", "drills": [{"id": 1, "status": "pass"}]} + with tempfile.TemporaryDirectory() as directory: + path = Path(directory) / "evidence.json" + digest_ = runner.write_evidence(path, evidence) + raw = path.read_bytes() + self.assertEqual(raw, runner.canonical_json(evidence) + b"\n") + self.assertEqual(digest_, "0x" + hashlib.sha256(raw).hexdigest()) + self.assertEqual(path.with_suffix(".json.sha256").read_text().strip(), digest_) + + +class FakeRpc: + def __init__(self, *, broken_lineage: bool = False, fail_call: int | None = None) -> None: + self.broken_lineage = broken_lineage + self.fail_call = fail_call + self.calls = 0 + + def chain_id(self) -> int: + return 31337 + + def finalized_block(self) -> dict: + return self.block(3) + + def block(self, number: int | str) -> dict: + number = int(number) + parent = digest(700 + number - 1) + if self.broken_lineage and number == 3: + parent = digest(999) + return {"number": hex(number), "hash": digest(700 + number), "parentHash": parent, "timestamp": hex(100 + number)} + + def logs(self, start: int, end: int, addresses: list[str]) -> list[dict]: + return [] + + def call(self, transaction: dict, block: str) -> str: + self.calls += 1 + if self.fail_call == self.calls: + raise runner.RunnerError("partial RPC failure") + selector = transaction["data"][2:10] + if selector == runner.SELECTORS["getProposal"]: + raise runner.RpcCallError("expected revert", runner.PROPOSAL_NOT_FOUND) + if selector == runner.SELECTORS["execute"]: + raise runner.RpcCallError("expected revert") + if selector == runner.SELECTORS["queuedActions"]: + return "0x" + bytes(128).hex() + if selector in (runner.SELECTORS["minBond"], runner.SELECTORS["timeout"]): + return "0x" + (2 if selector == runner.SELECTORS["minBond"] else 10).to_bytes(32, "big").hex() + if selector == runner.SELECTORS["bondToken"]: + return "0x" + (bytes(12) + bytes.fromhex(BOND[2:])).hex() + if selector == runner.SELECTORS["allowance"]: + return "0x" + bytes(32).hex() + return "0x" + + def balance(self, address_: str, block: int) -> int: + return 0 + + +class Caller: + def call(self, transaction: dict, block: str) -> str: + return "0x" + + +class Sender: + def __init__(self, fail: bool = False) -> None: + self.fail = fail + self.sent: list[dict] = [] + + def send(self, transaction: dict) -> str: + self.sent.append(transaction) + if self.fail: + raise RuntimeError("signer unavailable") + return digest(len(self.sent)) + + +class AgentRunnerFakeRpcTest(unittest.TestCase): + def test_finalized_lineage_replay_is_identical_and_bad_lineage_fails_closed(self) -> None: + cfg = config() + first = runner.collect_snapshot(FakeRpc(), cfg) + second = runner.collect_snapshot(FakeRpc(), cfg) + self.assertEqual(runner.canonical_json(first), runner.canonical_json(second)) + with self.assertRaisesRegex(runner.RunnerError, "lineage"): + runner.collect_snapshot(FakeRpc(broken_lineage=True), cfg) + + def test_partial_rpc_failure_aborts_before_send_then_resumes(self) -> None: + cfg = config() + sender = Sender() + with self.assertRaisesRegex(runner.RunnerError, "partial RPC"): + runner.tick(cfg, FakeRpc(fail_call=3), Caller(), sender) + self.assertEqual(sender.sent, []) + result = runner.tick(cfg, FakeRpc(), Caller(), sender) + self.assertEqual((result["action"], len(sender.sent)), ("publish-task", 1)) + + def test_signer_failure_records_attempt_and_stateless_retry_is_exact(self) -> None: + cfg = config() + failed = runner.tick(cfg, FakeRpc(), Caller(), Sender(fail=True)) + self.assertEqual(failed["attempts"][0]["outcome"], "signer-failed") + sender = Sender() + retried = runner.tick(cfg, FakeRpc(), Caller(), sender) + self.assertEqual(retried["attempts"][0]["outcome"], "submitted") + self.assertEqual(failed["attempts"][0]["transaction"], retried["attempts"][0]["transaction"]) + + +if __name__ == "__main__": + unittest.main() diff --git a/tools/test_economic_deployment.py b/tools/test_economic_deployment.py index 96074f5..d0fce8d 100644 --- a/tools/test_economic_deployment.py +++ b/tools/test_economic_deployment.py @@ -318,7 +318,7 @@ def _manifest(self) -> dict: }, } return { - "schemaVersion": 3, + "schemaVersion": 4, "creationRoute": "create", "status": "sealed", "network": "sepolia", @@ -363,7 +363,13 @@ def _manifest(self) -> dict: "contracts": self.contracts, "codeBlobs": {"core": self.core_hashes, "flm": self.flm_hashes}, "runtimeCodeHashes": { - "treasuryExecutor": self.executor_runtime_hash, + key: economic_deployment._digest( + self.executor_runtime + if key == "treasuryExecutor" + else f"runtime-{key}".encode(), + self.hash, + ) + for key in economic_deployment.RUNTIME_CODE_HASH_KEYS }, "finalization": None, } @@ -939,25 +945,28 @@ def test_executor_address_immutable_and_runtime_hash_are_all_verified(self) -> N ): economic_deployment._executor_runtime_code(self.contracts["vault"]) - broken = copy.deepcopy(self.manifest) - broken["runtimeCodeHashes"]["treasuryExecutor"] = "0x" + "11" * 32 - with self.assertRaisesRegex( - economic_deployment.ManifestError, "executor runtime code hash mismatch" - ): - economic_deployment.verify_rpc( - broken, - self.creation, - self.prerequisite_creation, - self.client, - hash_=self.hash, - ) + for key in economic_deployment.RUNTIME_CODE_HASH_KEYS: + with self.subTest(runtime_hash=key): + broken = copy.deepcopy(self.manifest) + broken["runtimeCodeHashes"][key] = "0x" + "11" * 32 + with self.assertRaisesRegex( + economic_deployment.ManifestError, f"{key} runtime code hash mismatch" + ): + economic_deployment.verify_rpc( + broken, + self.creation, + self.prerequisite_creation, + self.client, + hash_=self.hash, + ) - self.client.codes[self.contracts["treasuryExecutor"]] = b"wrong-runtime" - with self.assertRaisesRegex( - economic_deployment.ManifestError, "executor runtime code hash mismatch" - ): - self._verify() - self.client.codes[self.contracts["treasuryExecutor"]] = self.executor_runtime + original = self.client.codes[self.contracts[key]] + self.client.codes[self.contracts[key]] = b"wrong-runtime" + with self.assertRaisesRegex( + economic_deployment.ManifestError, f"{key} runtime code hash mismatch" + ): + self._verify() + self.client.codes[self.contracts[key]] = original self._put( self.contracts["vault"], @@ -1231,7 +1240,7 @@ def test_discovery_rejects_unfinalized_or_reorgable_log_provenance(self) -> None hash_=self.hash, ) - def test_schema_v3_names_both_creation_routes_and_rejects_legacy_manifests(self) -> None: + def test_schema_v4_names_both_creation_routes_and_rejects_legacy_manifests(self) -> None: economic_deployment.validate_manifest(self.manifest, hash_=self.hash) shared, registrar_manifest, _ = self._registrar_evidence() del shared @@ -1244,10 +1253,19 @@ def test_schema_v3_names_both_creation_routes_and_rejects_legacy_manifests(self) del broken["creationRoute"] with self.assertRaises(economic_deployment.ManifestError): economic_deployment.validate_manifest(broken, hash_=self.hash) - for legacy_version in (1, 2): + broken = copy.deepcopy(self.manifest) + broken["runtimeCodeHashes"] = dict(reversed(tuple(broken["runtimeCodeHashes"].items()))) + with self.assertRaisesRegex(economic_deployment.ManifestError, "canonical order"): + economic_deployment.validate_manifest(broken, hash_=self.hash) + for key in economic_deployment.RUNTIME_CODE_HASH_KEYS: + broken = copy.deepcopy(self.manifest) + del broken["runtimeCodeHashes"][key] + with self.assertRaisesRegex(economic_deployment.ManifestError, "canonical order"): + economic_deployment.validate_manifest(broken, hash_=self.hash) + for legacy_version in (1, 2, 3): broken = copy.deepcopy(self.manifest) broken["schemaVersion"] = legacy_version - with self.assertRaisesRegex(economic_deployment.ManifestError, "schema version 3"): + with self.assertRaisesRegex(economic_deployment.ManifestError, "schema version 4"): economic_deployment.validate_manifest(broken, hash_=self.hash) def test_discovery_cli_has_a_separate_default_output(self) -> None: From b12a6424ed377562065e8527021ee1f70e58bdb7 Mon Sep 17 00:00:00 2001 From: krandder Date: Mon, 13 Jul 2026 00:43:55 -0300 Subject: [PATCH 3/4] Pin Lane 5 reference-agent evidence --- metadata/agent-work-p1-evidence.json | 1 + metadata/agent-work-p1-evidence.json.sha256 | 1 + 2 files changed, 2 insertions(+) create mode 100644 metadata/agent-work-p1-evidence.json create mode 100644 metadata/agent-work-p1-evidence.json.sha256 diff --git a/metadata/agent-work-p1-evidence.json b/metadata/agent-work-p1-evidence.json new file mode 100644 index 0000000..296bf37 --- /dev/null +++ b/metadata/agent-work-p1-evidence.json @@ -0,0 +1 @@ +{"acceptanceRoutes":["timeout","evaluated-yes","evaluated-no"],"actors":[{"address":"0x70997970c51812dc3a010c7d01b50e0d17dc79c8","manifestCap":{"bondAsset":"10000","nativeWei":"1000000000000000000000","treasuryAuthority":"0"},"provenance":"house-wallet","role":"automation"},{"address":"0x3c44cdddb6a900fa2b585dd299e03d12fa4293bc","manifestCap":{"bondAsset":"10000","nativeWei":"1000000000000000000000","treasuryAuthority":"0"},"provenance":"house-wallet","role":"challenger"},{"address":"0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266","manifestCap":{"bondAsset":"0","nativeWei":"1000000000000000000000","treasuryAuthority":"0"},"provenance":"house-wallet","role":"deployer"},{"address":"0x14dc79964da2c08b23698b3d3cc7ca32193d9955","manifestCap":{"bondAsset":"0","nativeWei":"1000000000000000000000","treasuryAuthority":"0"},"provenance":"house-wallet","role":"keeperA"},{"address":"0x23618e81e3f5cdf7f54c3d65f7fbc0abf5b21e8f","manifestCap":{"bondAsset":"0","nativeWei":"1000000000000000000000","treasuryAuthority":"0"},"provenance":"house-wallet","role":"keeperB"},{"address":"0x9965507d1a55bcc2695c58ba16fb37d819b0a4dc","manifestCap":{"bondAsset":"0","nativeWei":"1000000000000000000000","treasuryAuthority":"0"},"provenance":"house-wallet","role":"recipientA"},{"address":"0x976ea74026e726554db657fa54763abd0c3a0aa9","manifestCap":{"bondAsset":"0","nativeWei":"1000000000000000000000","treasuryAuthority":"0"},"provenance":"house-wallet","role":"recipientB"},{"address":"0x90f79bf6eb2c4f870365e785982e1f101e93b906","manifestCap":{"bondAsset":"0","nativeWei":"1000000000000000000000","treasuryAuthority":"0"},"provenance":"house-wallet","role":"resolver"},{"address":"0x15d34aaf54267db7d7c367839aaf71a00a2c6a65","manifestCap":{"bondAsset":"0","nativeWei":"1000000000000000000000","treasuryAuthority":"0"},"provenance":"house-wallet","role":"worker"}],"attemptLedger":{"anvil":[{"blockHash":"0xad3e88fb1f117f9ba150069c4547f413314bdb725c35319ca4264f516102d445","blockNumber":"2","dataKeccak256":"0x1d32731f4b92cd9d1be8a00556570ed2da21a4ff78fa1bbcd2cac764de7e7579","from":"0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266","status":"1","to":"0x5fbdb2315678afecb367f032d93f642f64180aa3","transactionHash":"0x30d3fb2f2b08a458409f59a5cc1e8559be9076994891d819b835b0d65ab003c2","value":"0"},{"blockHash":"0x7a22e249c57e2249b668c8aeb53e8f98486884fc037f4a702021319bc3fbd870","blockNumber":"3","dataKeccak256":"0x4d5ff3ccd2f7eab9099c86d4d3477c959880192c9102c78ea31f11747426e7f9","from":"0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266","status":"1","to":"0x5fbdb2315678afecb367f032d93f642f64180aa3","transactionHash":"0x65ef244277337e565cc0129add2794d1c10518105ffcf530f7de5dca01590dee","value":"0"},{"blockHash":"0x946696e266ec9bc16519c348e19854e4f2acd4a1432539349fa90ae14637463b","blockNumber":"8","dataKeccak256":"0x0bb0bffb82185fe62844687f4678e2fe565748a651b1d13e6bbbaa74ec2b9d61","from":"0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266","status":"1","to":"0xcf7ed3acca5a467e9e704c703e8d87f634fb0fc9","transactionHash":"0x7102392ab5f671ad173e48383fddc332a80a7edfabafd23152b4e2d08931ee55","value":"0"},{"blockHash":"0x5262bd0a852b2b48f17502e16c60ec20a6d2d5898b0ac8678dfb47fb56529d93","blockNumber":"10","dataKeccak256":"0xfed87eab8dba926ce944d41c1e1fa6f10dae8628ab9a3f196e549fc72ac76020","from":"0x70997970c51812dc3a010c7d01b50e0d17dc79c8","status":"1","to":"0x5fbdb2315678afecb367f032d93f642f64180aa3","transactionHash":"0x50a158810c986c80e0bb4222a4d76e5ce005670a003b87a1ba4052b4e5066a69","value":"0"},{"blockHash":"0x45ff35a3e94f6c85b6473d597da4d87f8a8444251afe8876e1444470df8601b1","blockNumber":"11","dataKeccak256":"0xfed87eab8dba926ce944d41c1e1fa6f10dae8628ab9a3f196e549fc72ac76020","from":"0x3c44cdddb6a900fa2b585dd299e03d12fa4293bc","status":"1","to":"0x5fbdb2315678afecb367f032d93f642f64180aa3","transactionHash":"0x82bbd96d0d6396abc26ab79c85e7f7df22af83f794eb48fac4993fe3a7c865cf","value":"0"},{"blockHash":"0xb7d59bd04bbdcb5ddf28468d732d191a181cd86aa5d516a9a505334416af0959","blockNumber":"12","dataKeccak256":"0x6f2a9ccd3fdfdf5cb991dd767d1c5a0393dc3451440db6bf235b765430d3a798","from":"0x70997970c51812dc3a010c7d01b50e0d17dc79c8","status":"1","to":"0xdc64a140aa3e981100a9beca4e685f962f0cf6c9","transactionHash":"0x38d9338948fae774e08c1b1d83c41ff348e06d68e7641b6b72339d41b0388b85","value":"0"},{"blockHash":"0xf00165b6f9cf926da35dfa19a28400508f2e07fb04fcae135fd40a4df7fd184c","blockNumber":"13","dataKeccak256":"0xa28e1f5830bb37d5268ec4b21ef66e66ccd48d482c96c4de07e6d63e2c501560","from":"0x70997970c51812dc3a010c7d01b50e0d17dc79c8","status":"1","to":"0xdc64a140aa3e981100a9beca4e685f962f0cf6c9","transactionHash":"0xe9f2ff4cdc849a3c6adea0599d9de67f453d9a57e7ed8c92bb97e63352280611","value":"0"},{"blockHash":"0xe0ed95ca4c901821f504fdf5d00b031e22b3ccb484528e05828fb9810cccf2d7","blockNumber":"14","dataKeccak256":"0xc33426161fe9c5c5f0ec3a1ec8b444666b46402bcdf5c9cce1988467894d6250","from":"0x70997970c51812dc3a010c7d01b50e0d17dc79c8","status":"1","to":"0xdc64a140aa3e981100a9beca4e685f962f0cf6c9","transactionHash":"0x9d6b7eda0e6bc3df0dba4b43c6b48dc80484c904fb6a9af09a0f49587a0f328e","value":"0"},{"blockHash":"0x2d2c226c5205e73021346dae49f3e58e0f52e0987e0c534ca97637e2425354f8","blockNumber":"15","dataKeccak256":"0xb5aa638ff63c61c632514521757059306eda48bfa89c1fd2c532c5c9a870984c","from":"0x70997970c51812dc3a010c7d01b50e0d17dc79c8","status":"1","to":"0x0165878a594ca255338adfa4d48449f69242eb8f","transactionHash":"0x3edff6b540f9b71f04711c8e11e25a4dfb75be67f48e8e8ad9f198e8ee4b0f59","value":"0"},{"blockHash":"0x69cf7d9ceca3b4d7563b7900ddb82cf38604cf6b06f716c8e6684da8cf7f9782","blockNumber":"16","dataKeccak256":"0x9809755e9c4545be68239418ef7f94b236f78502075731c76659c80259095e79","from":"0x70997970c51812dc3a010c7d01b50e0d17dc79c8","status":"1","to":"0xdc64a140aa3e981100a9beca4e685f962f0cf6c9","transactionHash":"0x57ff3c61d6edbb7b378956f1794c773181160a420ba6441937c5e6f6de1fac7f","value":"0"},{"blockHash":"0xee2d3f338005f4ed9d0123be4feeca6bafb8816416941c28eb9992815d270734","blockNumber":"17","dataKeccak256":"0x395ee004c66c57bd4bc162fac49d3df656fd9333fd18761667e21c568067f8ae","from":"0x70997970c51812dc3a010c7d01b50e0d17dc79c8","status":"1","to":"0xdc64a140aa3e981100a9beca4e685f962f0cf6c9","transactionHash":"0x777ea112b041cca25c7784035f9b7f5273f1953ef02a90be98b39b1eb02265ea","value":"0"},{"blockHash":"0x3ceee0111e8d6e6dc272495484c8b2d8ac34f3ef9692a9b15ff64df6ef5eb053","blockNumber":"18","dataKeccak256":"0xa82382fbe5305a649cf9b6bda763b4de74305ebcfca15425f0ef8d1d54e6214c","from":"0x70997970c51812dc3a010c7d01b50e0d17dc79c8","status":"1","to":"0xdc64a140aa3e981100a9beca4e685f962f0cf6c9","transactionHash":"0x9676ca882326e8abb4655f0ed474b19041f937f3de08336716178262970b4e4d","value":"0"},{"blockHash":"0xd2b0cfed708551e0de69e6056c2f4f47c45e90064e1d773ac9c5352a5c96a2bb","blockNumber":"19","dataKeccak256":"0x5abd8bafb38647735678122b8f92d088b0920605311a58540665f0dc76749a2b","from":"0x70997970c51812dc3a010c7d01b50e0d17dc79c8","status":"1","to":"0x0165878a594ca255338adfa4d48449f69242eb8f","transactionHash":"0x06a41ea59c9abfd08e2a2d9fe2f8302dc28c83ee3b769d02b8b4714f5eb0a5f6","value":"0"},{"blockHash":"0x6d9b751782429ee2d23f084f89231eef29d3b392641c0eead37744a4a198640e","blockNumber":"20","dataKeccak256":"0xf78f7fdd2d9c3ebd6788cb896f6fdaf5f8fd933caf338326a58a17974f03a437","from":"0x70997970c51812dc3a010c7d01b50e0d17dc79c8","status":"1","to":"0xcf7ed3acca5a467e9e704c703e8d87f634fb0fc9","transactionHash":"0xaedcf98518b303846d28b4f01c2dedddd30905e000f0545efc004faf973d7c03","value":"0"},{"blockHash":"0x74ebf527dc19aa9a68e57da6160320f01809f22f49bf27a3d61dfc1c19e7bf11","blockNumber":"22","dataKeccak256":"0xe9240ea1ae047b596ba92d8d81d9543aea3c0793c7bcead76d706a0459503562","from":"0x14dc79964da2c08b23698b3d3cc7ca32193d9955","status":"1","to":"0xcf7ed3acca5a467e9e704c703e8d87f634fb0fc9","transactionHash":"0xfa85ee7498dc6562018ccc4d8461c298a336de593d249784b049d8e61604a433","value":"0"},{"blockHash":"0x25f883278141abe16bfc7703d432a7e7c54c05dbea12f659db70532a49eb71f4","blockNumber":"23","dataKeccak256":"0xa90cecee4a9732f6d3c840a1fe58ef5ada3a19f9d460de608a7923d1373c9787","from":"0x70997970c51812dc3a010c7d01b50e0d17dc79c8","status":"1","to":"0xcf7ed3acca5a467e9e704c703e8d87f634fb0fc9","transactionHash":"0x722b348a2f8bbaf9e2cbfc66615805f9c74bcf93b1150be0bd7b8e88ca9929b4","value":"0"},{"blockHash":"0x61a8105d05f0da2106b0535de4543b5c7fa9a0ed2b1e211334c7725343f6df45","blockNumber":"24","dataKeccak256":"0xa11a4b6e8b7dcaee9966060208301fb958a934b7099015b0c28e13edde605439","from":"0x3c44cdddb6a900fa2b585dd299e03d12fa4293bc","status":"1","to":"0xcf7ed3acca5a467e9e704c703e8d87f634fb0fc9","transactionHash":"0xb6ef48cc4c56cf70716e05653970b092aaa4201e7388d3b269260ad1f9b901b9","value":"0"},{"blockHash":"0xdfe795f28ee533e838b64eb7e5099a584593c5588d69d4d0ebe21638ba1d608c","blockNumber":"26","dataKeccak256":"0x7422579e17ee35d8cfc977aed182a11b6bad79b29c40779d62d79e7c0eb5d019","from":"0x14dc79964da2c08b23698b3d3cc7ca32193d9955","status":"1","to":"0xcf7ed3acca5a467e9e704c703e8d87f634fb0fc9","transactionHash":"0xde4823ff4168fca69eb46d9ef056df27e2ac418a998a139de8713e55dd1fde2e","value":"0"},{"blockHash":"0x3ee95d5c50b5cfa820e8bc0b7c98cb5667d9a37fcba56d45cbb6bcef6c603adf","blockNumber":"27","dataKeccak256":"0x5286b740cdc721a3e9dc2ae1d88219713cd33ff25627772fa69860f1a303c7ac","from":"0x70997970c51812dc3a010c7d01b50e0d17dc79c8","status":"1","to":"0xdc64a140aa3e981100a9beca4e685f962f0cf6c9","transactionHash":"0x1873e1614498cd2cdaf460ce04e730e3302d0224ecedfa884e3d2ee9039b2011","value":"0"},{"blockHash":"0x95324dea05e64c0732be09d9761e8b080d9ac9295fbbce45d40cc3c28c38ccb6","blockNumber":"28","dataKeccak256":"0xcf69111f37719612a6844c795e9bbaf79f6615090522e7e92095c47037e277d9","from":"0x70997970c51812dc3a010c7d01b50e0d17dc79c8","status":"1","to":"0xdc64a140aa3e981100a9beca4e685f962f0cf6c9","transactionHash":"0xab87533243c53a8c3eb4389f1d8768e1d96e272905d610d939b2100aa9faab25","value":"0"},{"blockHash":"0x72791f89ee9ff9b97057e949da422018f6508e848a06414806805d2561abebf6","blockNumber":"29","dataKeccak256":"0xad736b5278d0bdba8e936dd0a0c2048f87f0bd1307e0b465fa18a9583c1564da","from":"0x70997970c51812dc3a010c7d01b50e0d17dc79c8","status":"1","to":"0xdc64a140aa3e981100a9beca4e685f962f0cf6c9","transactionHash":"0x2e9f9ae0ea1384b632c84d1ae1c4703048db3ab5bd118fd41e30d5d7a4b8475a","value":"0"},{"blockHash":"0x8dbe7e1a4c4b1514c61ec086e6f8787927681b3408a0b7d072e14a539198ed96","blockNumber":"30","dataKeccak256":"0xdfe81e70a0cdcdd9dcdaa74d0d79f45d6dc000920a824d3d4cec89a99f8955a8","from":"0x70997970c51812dc3a010c7d01b50e0d17dc79c8","status":"1","to":"0x0165878a594ca255338adfa4d48449f69242eb8f","transactionHash":"0xd30ba6ae79903c2170509aa4900c285bbb7b307e584fe8f0798acf3b5527bd13","value":"0"},{"blockHash":"0xa5f990f0a93e8a6ea52cf8cd8ea40ff41177426b4edab405c88973126dc3ce27","blockNumber":"31","dataKeccak256":"0x86d092508f520375146aed89631eff9d8fd3f61beb5a99174fae2c8d695c4a47","from":"0x70997970c51812dc3a010c7d01b50e0d17dc79c8","status":"1","to":"0xcf7ed3acca5a467e9e704c703e8d87f634fb0fc9","transactionHash":"0xdcdb16698fb4cc040d412996ddb4a22db3528b6a77d14929e747921d0670939a","value":"0"},{"blockHash":"0x8e61bd9f6f5bab6775ea832f6f6e3d10065aab6beab21087cba3d818e1a3985e","blockNumber":"33","dataKeccak256":"0x1cf34a64306726c7e1ef318c41d3ba2f1afda092e792baba1882241accd39098","from":"0x14dc79964da2c08b23698b3d3cc7ca32193d9955","status":"1","to":"0xcf7ed3acca5a467e9e704c703e8d87f634fb0fc9","transactionHash":"0xfe923c46689f356002b8ba74dea370cee463b2653afc6b31a3332ec771e462f5","value":"0"},{"blockHash":"0x28755aa73725747e8f247111641002d057ce6ecf99d1b9707ec4043da725476b","blockNumber":"34","dataKeccak256":"0xcda31a1b38e8912c9e9b117e9866dcbcd55c33153e3095f5ced1d26fcebfeec3","from":"0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266","status":"1","to":"0x5fbdb2315678afecb367f032d93f642f64180aa3","transactionHash":"0xe49851dbf649fdf53e4e6f86f35f54086c0852384594d94d14f88af77dd98c69","value":"0"},{"blockHash":"0xf6bf44411ee1d6b5842e34e14f59672c21944800b84a111180443820f2fa4363","blockNumber":"35","dataKeccak256":"0xcf5a83a8499eb1913da60fd13bd99b1feeee4dfbb7ce42b6a236c80de960a7a1","from":"0x14dc79964da2c08b23698b3d3cc7ca32193d9955","status":"1","to":"0x5fc8d32690cc91d4c39d9d3abcbd16989f875707","transactionHash":"0x0aa5db4186c8333a924a57fa311e45c15589687b53c57b9f2c6dcbeb0f19a6aa","value":"0"},{"blockHash":"0xd2db29a8755d4b866ffd7749aec7e6c72335a99a976de50fd8e5421c95b6cff7","blockNumber":"37","dataKeccak256":"0xeca11aef782231d382d3e3c0051bca3c85cbbe93c4f755936610985c66cf2183","from":"0x14dc79964da2c08b23698b3d3cc7ca32193d9955","status":"1","to":"0x5fc8d32690cc91d4c39d9d3abcbd16989f875707","transactionHash":"0xf99e0000376f6aa5a724901110586290b4853f209e6c969905da8aeee8f5ce8f","value":"0"},{"blockHash":"0xebb34d288c3f5707fa1af142f79c8faef6fc365bf010202d26051e57193baa99","blockNumber":"38","dataKeccak256":"0x583bf89d29224c85fbdf6c3af904331be3a07a85b338b2d05bba2adac82494ae","from":"0x70997970c51812dc3a010c7d01b50e0d17dc79c8","status":"1","to":"0xdc64a140aa3e981100a9beca4e685f962f0cf6c9","transactionHash":"0x6a7a357ccc877899c0fbcb238cda7a2069b210d3a300246a37d735e803b843a0","value":"0"},{"blockHash":"0x3543649feb2deff33b9ecc9bd076d782546de3e9277088a02daa80795b4e4e99","blockNumber":"39","dataKeccak256":"0x99bb311f85e353b1786108aa592bf1a673116b2dabc491067dafd3c3964e4a87","from":"0x70997970c51812dc3a010c7d01b50e0d17dc79c8","status":"1","to":"0xdc64a140aa3e981100a9beca4e685f962f0cf6c9","transactionHash":"0x1edbc5a245b07709431970f7a7b486b81540d16d6826cdf94eb1a778a8b07870","value":"0"},{"blockHash":"0x6912b597dd6ffd4ba5e256408dc35ef28514e1e36e1e6522c04feef600765d3a","blockNumber":"40","dataKeccak256":"0x7a587daf6287acfa79285d9e5a333fd76a10525005f2a7dfc2f0e24fdcef8af1","from":"0x70997970c51812dc3a010c7d01b50e0d17dc79c8","status":"1","to":"0xdc64a140aa3e981100a9beca4e685f962f0cf6c9","transactionHash":"0x73421780df2aa74e738ca55750575e5d6fede097a003ca76e80c93def3295ede","value":"0"},{"blockHash":"0x0f4d656fa299f0fee1b2f0b965ee22eaee852ef974b10c1a7bd67e41cd8f2bbb","blockNumber":"41","dataKeccak256":"0x3b0c69a5bef2272bc21f0e8f43b4b55622744ede37dc149735284fa38af925eb","from":"0x70997970c51812dc3a010c7d01b50e0d17dc79c8","status":"1","to":"0x0165878a594ca255338adfa4d48449f69242eb8f","transactionHash":"0xd7cbbce47a8e232f99c4871afcece9072834caecbf9635645b73537cff3e769e","value":"0"},{"blockHash":"0x57d0ef75c3a8421f53bfc053bc771ff4922bee37c4b4edb42348a63be96f38a2","blockNumber":"42","dataKeccak256":"0xeb76740cc7023018d77addb28717c4a37ddd259ffec0457904c30fd6255867f9","from":"0x70997970c51812dc3a010c7d01b50e0d17dc79c8","status":"1","to":"0xcf7ed3acca5a467e9e704c703e8d87f634fb0fc9","transactionHash":"0xa2abbc3f766659d44726edd0a7cd8e473cc8c30be29b2daee3937826a2210d81","value":"0"},{"blockHash":"0xad84d02654bc068ea6295adc86c1fc8fd63472b3098f5500eecc9e5feccfe73c","blockNumber":"44","dataKeccak256":"0xdbce15840d11dbd08d7a894c23d4e88a3c5b0a11abf6d2d9cd931fa10c0aee6f","from":"0x14dc79964da2c08b23698b3d3cc7ca32193d9955","status":"1","to":"0xcf7ed3acca5a467e9e704c703e8d87f634fb0fc9","transactionHash":"0xe2a904d1ac51b8984c51ba1e858760e5fdaa5511649b07bc859dea9a1f50b345","value":"0"},{"blockHash":"0xac1bef57ed10ac64be87febb31a45d7e881d053fc903dde21fa771c0ebca1359","blockNumber":"45","dataKeccak256":"0xd14d88dbd2c7819deeaa2615289cc782407ab91de95fdc01eebf9b2f771d924c","from":"0x14dc79964da2c08b23698b3d3cc7ca32193d9955","status":"1","to":"0x5fc8d32690cc91d4c39d9d3abcbd16989f875707","transactionHash":"0x7d6828272ef0166ec08f9bf2b988f726de0a61b449162d857f3538177f02f42f","value":"0"},{"blockHash":"0x5cdd48ec6d0a36ea1fd6d37029c53d29ce7d1ab976cea4f474d18c679cf8141e","blockNumber":"47","dataKeccak256":"0x5afc487320a2c3bf171016d91fe06b5e9fe831e493ea6ae60452fbd5a588ce54","from":"0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266","status":"1","to":"0x5fbdb2315678afecb367f032d93f642f64180aa3","transactionHash":"0x779782509827163e881fccc7998a7fdb4afb0882662c016a6494e27fad65752f","value":"0"},{"blockHash":"0x3fa53f78abb13227ac8576ce91dcd8eaad93cf9d8314bd2f33027246b3748ece","blockNumber":"48","dataKeccak256":"0xc0e6d518e87627fe278cc34ae0eabf6d852b18f7441c84a3a350b5ec48088609","from":"0x14dc79964da2c08b23698b3d3cc7ca32193d9955","status":"1","to":"0x5fc8d32690cc91d4c39d9d3abcbd16989f875707","transactionHash":"0xcf2627522504fd05ceb3d7d67a464d67e0952b7ff673281a237161b968334c0a","value":"0"},{"blockHash":"0x27b1d3a4dcdaa73dc7cc706c5fe8d03d8689f76725080b8091b1c2b0d74d3e42","blockNumber":"49","dataKeccak256":"0x2d6ad8f304ef6768c033c7a3d2b193538d430afbddd43cae126d672c7de5cc5e","from":"0x70997970c51812dc3a010c7d01b50e0d17dc79c8","status":"1","to":"0xdc64a140aa3e981100a9beca4e685f962f0cf6c9","transactionHash":"0xb5e718bce96d7b6c1a467904d1ef7064c3fd366f1c7f459a4a90a67ec604f0e3","value":"0"},{"blockHash":"0x222ca91c9c564cda4fdbd21a94950fe85939bcf08ad72436fff125f570e905e1","blockNumber":"50","dataKeccak256":"0x07119cf5de27a19de0db23182d901461ccb19b85f48f6a1d33afea67065c2787","from":"0x70997970c51812dc3a010c7d01b50e0d17dc79c8","status":"1","to":"0xdc64a140aa3e981100a9beca4e685f962f0cf6c9","transactionHash":"0x2fc2a5fc94d80ce604efeacc4b7cad2877e0c79ddf5b69bfc7a8cb49b410c305","value":"0"},{"blockHash":"0x2f95239e6cfcdd4ff46460b649b90346de054ef2b50c47636e9e1aafad4be851","blockNumber":"51","dataKeccak256":"0x7bfcc80e18341d691937d915a742fb397c0604782bf65bd7c047fffa7e2850d0","from":"0x70997970c51812dc3a010c7d01b50e0d17dc79c8","status":"1","to":"0xdc64a140aa3e981100a9beca4e685f962f0cf6c9","transactionHash":"0xa3b67cd8d9f10d59a350cc6e37e5127eb292441aba06c62aa1db5c4860c7462e","value":"0"},{"blockHash":"0x155cb62672102c04c97e19f05e38990bcd1b58961210d95dd888502646b2d85f","blockNumber":"52","dataKeccak256":"0x8cb595cfdea1a6bd9acdd03f32cd8366aad339070adc4529602778e86338e114","from":"0x70997970c51812dc3a010c7d01b50e0d17dc79c8","status":"1","to":"0x0165878a594ca255338adfa4d48449f69242eb8f","transactionHash":"0x75279a3017c56c097069cc960a49e43c405956c4092c18b10a92d1d0c2198214","value":"0"},{"blockHash":"0x449ef1ea0a7fe71ceb84981ccf65054eda4779bd1e6aebf0d24d4c92db7a82a6","blockNumber":"53","dataKeccak256":"0x4522027226cd2dee37468ece26a78fb8bc80a23b83493762a0d41c5dda28c7b9","from":"0x70997970c51812dc3a010c7d01b50e0d17dc79c8","status":"1","to":"0xcf7ed3acca5a467e9e704c703e8d87f634fb0fc9","transactionHash":"0x4d3b438c294d4c8eaeb2bba07b34885704e91f17fe47998795a4fa31871b36db","value":"0"},{"blockHash":"0xffa0d648281d0a6af36595eff8ef078981c08bdd6d7cf642c6db152a0224989f","blockNumber":"55","dataKeccak256":"0x199f67467719de570d93d1160ac80765390e6cc0c9ee0f0fd96fe4e2c5acb049","from":"0x14dc79964da2c08b23698b3d3cc7ca32193d9955","status":"1","to":"0xcf7ed3acca5a467e9e704c703e8d87f634fb0fc9","transactionHash":"0x307cf7f9b04f3fdb1b9e8ef966dc9fed3a74ba064c20699368bf599f1667bdee","value":"0"},{"blockHash":"0x0055f0307d6f3ef7f6195a64d0f8740539127e7ecce8b7920c8a8a551f6744e6","blockNumber":"56","dataKeccak256":"0x2b9686ecb1856537dea10d5575b25a32f42be3e5a875dcc561479a975f422993","from":"0x14dc79964da2c08b23698b3d3cc7ca32193d9955","status":"1","to":"0x5fc8d32690cc91d4c39d9d3abcbd16989f875707","transactionHash":"0x14ca7527d6e4a049e9ede6ef3d0a3ee1fe41e44efb7db76f76471ed20c733ad8","value":"0"},{"blockHash":"0x73b4c00505bc808871ec64721998f838ab2cdcacf4c22dbb36a7790123e8bfba","blockNumber":"58","dataKeccak256":"0x3718d3153a8f4fadcbe44a526c9871ba0885bf38648edccfa210a4304e113551","from":"0x70997970c51812dc3a010c7d01b50e0d17dc79c8","status":"1","to":"0xdc64a140aa3e981100a9beca4e685f962f0cf6c9","transactionHash":"0x8d7fcf0295198976a36cb30059d80a49e44bec4aae7d6ad9a384d73fad381898","value":"0"},{"blockHash":"0x17fd7acd10a76a90f033b77a7ca4d75d7183c6260cd5a5bd2c3af7b11dfcad0c","blockNumber":"59","dataKeccak256":"0xdf60f85d69f071db52b28d4617356bd793f58559228c524eb57cf1d974a08318","from":"0x70997970c51812dc3a010c7d01b50e0d17dc79c8","status":"1","to":"0xdc64a140aa3e981100a9beca4e685f962f0cf6c9","transactionHash":"0x1882a24bfa51220a9717fdda9a3a6f37cf31e6bb8ddc569cc265e3e500d8c0b7","value":"0"},{"blockHash":"0x42ab2c4b9b74486b7a2335194cae8d8aa12c96b761400cbdd94ee6085587dd24","blockNumber":"60","dataKeccak256":"0x5f2e933b78bb44cdb1c3146d484eebfb7ba38e044b6e484a2f05473fe8eeb5b8","from":"0x70997970c51812dc3a010c7d01b50e0d17dc79c8","status":"1","to":"0xdc64a140aa3e981100a9beca4e685f962f0cf6c9","transactionHash":"0x238e559069c1b34cbfe97b085bd52ece84e3b4311e4d54ef3076676ccb31bca0","value":"0"},{"blockHash":"0xdf5e4882fa593f78ba301f84e4724afa3fc43e0950b778d2123ad30c5cc7ce7e","blockNumber":"61","dataKeccak256":"0x965aa2e2876757fdc5d6d71ea971b87231ddca0206b87d8cc5b30d3275747f58","from":"0x70997970c51812dc3a010c7d01b50e0d17dc79c8","status":"1","to":"0x0165878a594ca255338adfa4d48449f69242eb8f","transactionHash":"0x213b5cc9ded74a04ea0afc9c5cab26e365acf7ea04232bf73d32f41ad31285d0","value":"0"},{"blockHash":"0x0ab0d146750d244246522de6cec910b50c97d03ea3a62c7ec41ad5335e4b0541","blockNumber":"62","dataKeccak256":"0x3ef4591776911ab677804d9c9ea71e760e4092b090f5ebb594e16493549c4568","from":"0x70997970c51812dc3a010c7d01b50e0d17dc79c8","status":"1","to":"0xdc64a140aa3e981100a9beca4e685f962f0cf6c9","transactionHash":"0xe76f67fd904e1de25aeee7fd4c5b892fcd0ad649e3eb0e66971a4e1ba0d94c0f","value":"0"},{"blockHash":"0x03ecafd04390fff173baa6ac50f03c8b41b08355d853bbd0c23fd8ad083f53cd","blockNumber":"63","dataKeccak256":"0x4ed192934b4ab614c835e906d105a14b22f63ade672ab2573bfd41912ab3132a","from":"0x70997970c51812dc3a010c7d01b50e0d17dc79c8","status":"1","to":"0xdc64a140aa3e981100a9beca4e685f962f0cf6c9","transactionHash":"0xe07dca8144b40ab04cb7d1fc9f7807bfcbca8fdeb0b8b298d3a8a0392f838ef6","value":"0"},{"blockHash":"0x54d87f39335ad70c04e230b4d3e06e62ec9dae3c8288ce953e3ccb808c71d225","blockNumber":"64","dataKeccak256":"0xca0e89441b8c846428e7c9210a637c98cc5111124c768d4f5513bc1ad16c4b31","from":"0x70997970c51812dc3a010c7d01b50e0d17dc79c8","status":"1","to":"0xdc64a140aa3e981100a9beca4e685f962f0cf6c9","transactionHash":"0xef43ae2a35991e57d57bf42545a6d1cc1fe7d71557b112e858767d72099afae1","value":"0"},{"blockHash":"0xbdf4d624b6c291935215a14bbba7737d36faf924395fd3f6b721419be1b90263","blockNumber":"65","dataKeccak256":"0xad5c1f673afed56c2515744ccdbd5869fdb17cc739f13fa75dac1e5dcc9cbb68","from":"0x70997970c51812dc3a010c7d01b50e0d17dc79c8","status":"1","to":"0x0165878a594ca255338adfa4d48449f69242eb8f","transactionHash":"0x8b63db263cc56221ff4c530a361a015450e67b30945872f4a098408a0a19eea2","value":"0"},{"blockHash":"0x76d5d4360542d5aaddf05a3c01417ac2990917b8ed7bd890d5c7b2f3a1bd507f","blockNumber":"66","dataKeccak256":"0x8c53f126c2b494f0e3f6020248636ad404a4b05644d744d42c1f1f782737808a","from":"0x70997970c51812dc3a010c7d01b50e0d17dc79c8","status":"1","to":"0xcf7ed3acca5a467e9e704c703e8d87f634fb0fc9","transactionHash":"0x89399348f8a4c5041ce17b56cf89cf49529fad4ecc404a87d3b4abaf1d9d2609","value":"0"},{"blockHash":"0x19929c9c7eb9b4923f1861059c94c11fef22d3c1ef7d2708eee39562f59efbbd","blockNumber":"67","dataKeccak256":"0x05d01870849b6d4a71a3a04695b90e4ae6b1a6c880c7dc2aa45f0d9e876af40c","from":"0x3c44cdddb6a900fa2b585dd299e03d12fa4293bc","status":"1","to":"0xcf7ed3acca5a467e9e704c703e8d87f634fb0fc9","transactionHash":"0xf523612a86b414b9250bf67a29b941d0826c4ace848aa07b2738bf7d1619d0cd","value":"0"},{"blockHash":"0x5d1858a48eac493128c47ce0978b5bb60e92ad92b57bd568ba0e88d77d1da34e","blockNumber":"68","dataKeccak256":"0x6c502e8707847846787896039d3cb8bcaaf9fcbd77edcf9087c1b1e38c9da865","from":"0x70997970c51812dc3a010c7d01b50e0d17dc79c8","status":"1","to":"0xcf7ed3acca5a467e9e704c703e8d87f634fb0fc9","transactionHash":"0x735520334b50d0b935b4860fa4ee4806ad05eae445041c839b5656396a0ed879","value":"0"},{"blockHash":"0xf4da9926b27a9c720dff1083a46539949472b12354b987a37866e51359b938e3","blockNumber":"69","dataKeccak256":"0x0f8771789fa5c5660f74002e1a6198dfe414331231ef1572cad8853f6c6e25dd","from":"0x14dc79964da2c08b23698b3d3cc7ca32193d9955","status":"1","to":"0xcf7ed3acca5a467e9e704c703e8d87f634fb0fc9","transactionHash":"0x0960c777380c735cd87945151b2aa07ac135150eb178981a3d3ff5bff9ab51c6","value":"0"},{"blockHash":"0x4c31f5eb7de36b1f45caa51a37c49f21e3ebf29f7d8798224ebb824bffdd2f61","blockNumber":"70","dataKeccak256":"0xfbcae7e32aad20bc1fbb1850d5a35b5d5de462894b85f82a8f29a38cea2cfc27","from":"0x90f79bf6eb2c4f870365e785982e1f101e93b906","status":"1","to":"0xcf7ed3acca5a467e9e704c703e8d87f634fb0fc9","transactionHash":"0x6a739e336b8b6cac57602dd44e75c05350262e052ccc176506a41caebcb404be","value":"0"},{"blockHash":"0x6ab721ef7dd6bed9bcab6cc20b4a9ce18308f18adb80ff860f5def13aefa9871","blockNumber":"71","dataKeccak256":"0xed364c3bc4863eca147e542b05e2c10b761bdebf80f2092854dd8a73601ed3cb","from":"0x70997970c51812dc3a010c7d01b50e0d17dc79c8","status":"1","to":"0xdc64a140aa3e981100a9beca4e685f962f0cf6c9","transactionHash":"0x2cf28d44bf8a214f173ae9f6c7426813ac14d289d4fa164cb63516b4a51b6d20","value":"0"},{"blockHash":"0x5c23a81382c68b96303fdd9755527520d1b1076e0a3f12fcef7cc647c89480a1","blockNumber":"72","dataKeccak256":"0xd292d2d64bbf2f6f5445feb2fa572629a13ef88987cb49760ba010ca6688e111","from":"0x70997970c51812dc3a010c7d01b50e0d17dc79c8","status":"1","to":"0xdc64a140aa3e981100a9beca4e685f962f0cf6c9","transactionHash":"0x0cc891848e191f281afb95a00fa74e41d122ac8fef2ba081133ae9297f027e2e","value":"0"},{"blockHash":"0xc6379783097d262643465a1966aaf73cd69a5630a6ee74017155e69a206d5127","blockNumber":"73","dataKeccak256":"0xcbaab9fbdbf7e73dfccf1bef117baabbcce2140ff5318026cf477a816f701819","from":"0x70997970c51812dc3a010c7d01b50e0d17dc79c8","status":"1","to":"0xdc64a140aa3e981100a9beca4e685f962f0cf6c9","transactionHash":"0xf924acdfd673231a3ef8ee946b6b50b19817c84145b491cbcb656bedcef59291","value":"0"},{"blockHash":"0x1a594db2f0a2eabf64f8c2bfbd3e1ce828214b38b790e83b6b84f4c48ce5486c","blockNumber":"74","dataKeccak256":"0x5edd80ef7d90a365dfed5a1edc4cde642c0f624232c67b6c9cab838fc93c8615","from":"0x70997970c51812dc3a010c7d01b50e0d17dc79c8","status":"1","to":"0x0165878a594ca255338adfa4d48449f69242eb8f","transactionHash":"0xed5c6f89713ba4fc7d306b658785af4db05648073e7653c3d657d44aae736a74","value":"0"},{"blockHash":"0x41ab727f0b5741e31d5e62f46d6824fc0ac6e2d1d9a578c05f1a979c89ada427","blockNumber":"75","dataKeccak256":"0x3335776fd45a41c0c7e592652477d20fa2d34018e72fee11a351012023872368","from":"0x70997970c51812dc3a010c7d01b50e0d17dc79c8","status":"1","to":"0xcf7ed3acca5a467e9e704c703e8d87f634fb0fc9","transactionHash":"0x09ef015ee8526a9ff9b9219cd6caf730e7cbf0a6cfcc2d698a0138bb7540b346","value":"0"},{"blockHash":"0x3f53c542725bd271ca53f3aeb407a4b6bdf6423a62548878ce6a9fa0e4c2775c","blockNumber":"76","dataKeccak256":"0x886f77859eb0e0721fa2942f27e3740664c57db4f15661e635821e7e5e08ee04","from":"0x3c44cdddb6a900fa2b585dd299e03d12fa4293bc","status":"1","to":"0xcf7ed3acca5a467e9e704c703e8d87f634fb0fc9","transactionHash":"0x76f713dda81cc5e0c4eccb795b53caa466f7600379eca304ccdab556340b5c3d","value":"0"},{"blockHash":"0x95aca8317125079c287eb80a102721d63c658124fc187c0cc4d2004a65ae1c21","blockNumber":"77","dataKeccak256":"0x11461d6ffd28c0e8c1c3fce7b4a35f6567f7ba3272900faa00f7af1edf952fda","from":"0x70997970c51812dc3a010c7d01b50e0d17dc79c8","status":"1","to":"0xcf7ed3acca5a467e9e704c703e8d87f634fb0fc9","transactionHash":"0x65a6b846899ef7c141c40a79f6912fddef57242112df46035ac6542ba980638e","value":"0"},{"blockHash":"0xc9417e12876ccbe8da17894fc826faf97b2c67418b8ef2212ac9ed2456afb409","blockNumber":"78","dataKeccak256":"0x0f8771789fa5c5660f74002e1a6198dfe414331231ef1572cad8853f6c6e25dd","from":"0x14dc79964da2c08b23698b3d3cc7ca32193d9955","status":"1","to":"0xcf7ed3acca5a467e9e704c703e8d87f634fb0fc9","transactionHash":"0x9b54eb3d28c6083f2447202525a2ba77b3672a3cc29dd43900097f6164f622a5","value":"0"},{"blockHash":"0xf3255cbad1f7cdb3c7d80b0ffbddd85f711efc012cabf35ec34063202b0eeb4a","blockNumber":"79","dataKeccak256":"0x105df052b49dd8083e211811bd0bea30928f922339bc7c56fe224365577d2399","from":"0x90f79bf6eb2c4f870365e785982e1f101e93b906","status":"1","to":"0xcf7ed3acca5a467e9e704c703e8d87f634fb0fc9","transactionHash":"0xc51657c2b54ad5b278f62731db1cfd1533fc194abc21329f0f0f8a1eca17c69d","value":"0"},{"blockHash":"0xac401cbc4a5fbec89ee17f600031e17bcf50d16337e9a2cc194b56723857a05b","blockNumber":"80","dataKeccak256":"0x971b917fb95a24d2eec36eb35d8bf4aaa5b77754faf42ac1cfe58626799f51a9","from":"0x70997970c51812dc3a010c7d01b50e0d17dc79c8","status":"1","to":"0xdc64a140aa3e981100a9beca4e685f962f0cf6c9","transactionHash":"0xe2de4dc607473d8b88c3117646756eeec2070573c9086bbcc5e3879b2052538e","value":"0"},{"blockHash":"0xec18ab8957a8c48bd70eb392de3b9d6c4904eb00919a47fa22887079a6bdc0cd","blockNumber":"81","dataKeccak256":"0x45be9082826440c388db9303b09b601d6977eb722e2d75bd4a47a1a25d2242da","from":"0x70997970c51812dc3a010c7d01b50e0d17dc79c8","status":"1","to":"0xdc64a140aa3e981100a9beca4e685f962f0cf6c9","transactionHash":"0x0707f3d9245c97714aba233ab9d0d1db47114c24c36e3b5028ec28a2f1c7c7dc","value":"0"},{"blockHash":"0x87942e96f2e3a8bba8c3eed595bc45f8c976b3ec0c1d90057d098eaaee35d2ef","blockNumber":"82","dataKeccak256":"0xc1406efe213ff57de52dbf000c9ff6b22066cedfda9f171f8bccf8f5a809aca5","from":"0x70997970c51812dc3a010c7d01b50e0d17dc79c8","status":"1","to":"0xdc64a140aa3e981100a9beca4e685f962f0cf6c9","transactionHash":"0xc7b4db6734cddcc7c53573c12ad7086a0f198e6ec3c87b2cc15bca1a62f0606b","value":"0"},{"blockHash":"0x4883fe509ee349cd88c22709ab0a2d5883eba156c5442adca5f87febce70a7f5","blockNumber":"83","dataKeccak256":"0xe7a73d7c55c93a442ce57a53101cb8f34733fa768c0748629ab5f5f0dfbe8abc","from":"0x70997970c51812dc3a010c7d01b50e0d17dc79c8","status":"1","to":"0x0165878a594ca255338adfa4d48449f69242eb8f","transactionHash":"0xf367ddfe294bb98de24331584b4604e06cad803d2d248880bd5749b08b0262b9","value":"0"},{"blockHash":"0x9231c1e5253737c3a578d0a63d4cb6a7d53c681c9f39b332b4ca986658af0b07","blockNumber":"84","dataKeccak256":"0xfdea1be45d6d31a31d1dc0ecd16ee51a3c0a10142170bf8adbc9d9db313b39d6","from":"0x70997970c51812dc3a010c7d01b50e0d17dc79c8","status":"1","to":"0xcf7ed3acca5a467e9e704c703e8d87f634fb0fc9","transactionHash":"0x3b7bca3890a382cb3bae2e6b9308f90395d3a4e7cc162308368629bec11edbeb","value":"0"},{"blockHash":"0x94b68ae7d3ecd7a12cf7d78fcdb3adcca4cb9ad5305be0ae4fda3ec506ec14f1","blockNumber":"86","dataKeccak256":"0x5e6f522870fcad9467c576dbbabcec3e9109d08cbea603d7437771b06618debd","from":"0x14dc79964da2c08b23698b3d3cc7ca32193d9955","status":"1","to":"0xcf7ed3acca5a467e9e704c703e8d87f634fb0fc9","transactionHash":"0x05e00b8edca9167588c44416f974f41499ec6090ed111073ff15dd39988f1f3a","value":"0"},{"blockHash":"0xd6068d65871ec333ca3a952b2c529958eb91904c372664dd81b249090d737e56","blockNumber":"89","dataKeccak256":"0x546456f589e92a5b98476fa4f5507906a8257d77fb8ac9ea28b29ec46fdce0ec","from":"0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266","status":"1","to":"0x5fbdb2315678afecb367f032d93f642f64180aa3","transactionHash":"0xf43fe06caf722c227254b135fb83c0bdfd458367f3b1e89a8cf3c28d417b4c5f","value":"0"},{"blockHash":"0xd2ed8606975aee45ad5ea42471f74034cf57eb3c80a8f03bc798c5a927947d85","blockNumber":"91","dataKeccak256":"0xd55db12dd1bc25770ed4fd14dd43a2f96bd1ebc1ab26450f2b9c176189f7d007","from":"0x14dc79964da2c08b23698b3d3cc7ca32193d9955","status":"1","to":"0x5fc8d32690cc91d4c39d9d3abcbd16989f875707","transactionHash":"0x1c3b079119b87415dfd7f6b883a754d18e2d5f60e8bd3224ccf729a0f660006a","value":"0"},{"blockHash":"0xe11a3975d450e087e2ae1ef948fb0dda346a3b6c0ac17cf774bd9fadf3144dc7","blockNumber":"92","dataKeccak256":"0x30d2ff6e0f27af9f66fb7499f983d2ba044e7db3d11e28bdbaec172ccd793789","from":"0x70997970c51812dc3a010c7d01b50e0d17dc79c8","status":"1","to":"0xdc64a140aa3e981100a9beca4e685f962f0cf6c9","transactionHash":"0xbf66d12fa304e16795c3abf73f7c8f2a95417609777a86afc84d6133fa9a3e9f","value":"0"},{"blockHash":"0x0557552342dfd35fa1d9e24f40db2a74243d411dc0890400c6ffdd6655ce4074","blockNumber":"93","dataKeccak256":"0xba7d253ab870aa1d499c1fef174f99d26dbf1588e30019105fb801f15a8db541","from":"0x70997970c51812dc3a010c7d01b50e0d17dc79c8","status":"1","to":"0xdc64a140aa3e981100a9beca4e685f962f0cf6c9","transactionHash":"0x0c67e292f5ba3ee420a54b0275cb70fe454844429853a967aa974566820188f1","value":"0"},{"blockHash":"0x5593f9f8eb8e0d716a7e0ff27a5d62f7b23ead1de6d87bb99b17ff2278584518","blockNumber":"94","dataKeccak256":"0x67e3ca92b2607aad81a3f4defd7c4d6b4fa1074934140d648fa00d4ddf3c1648","from":"0x70997970c51812dc3a010c7d01b50e0d17dc79c8","status":"1","to":"0xdc64a140aa3e981100a9beca4e685f962f0cf6c9","transactionHash":"0x766d9037b06b98533fdebce497a7e004ba6ff8ce0eda7162c1c775e5ec9b18dd","value":"0"},{"blockHash":"0x31f1721679bb9fb40a33ef79ae1fb195db02efbe4fec336d95b4ac61de0cd3df","blockNumber":"95","dataKeccak256":"0x36ac340b4b1b36852ff80e766573251645370b835997150d06681723869c6b32","from":"0x70997970c51812dc3a010c7d01b50e0d17dc79c8","status":"1","to":"0x0165878a594ca255338adfa4d48449f69242eb8f","transactionHash":"0xedb973120e4adba9389bd27b606b910feb366b50f29f93399b0186088f7d92c7","value":"0"},{"blockHash":"0x5c98f6e762edb60183ee68761081ae0e681816f546873b5626d9c212e9abc99f","blockNumber":"96","dataKeccak256":"0x971f9cd590c66bb7578032f04c65ee040c339f45adeb68f3e6a2f27419bd06d9","from":"0x70997970c51812dc3a010c7d01b50e0d17dc79c8","status":"1","to":"0xcf7ed3acca5a467e9e704c703e8d87f634fb0fc9","transactionHash":"0xec28b4f6d74a92a1f9418428d1f3dd785398fcda9d8cbebd739367358226fdff","value":"0"},{"blockHash":"0xda186652d7e06143c1d373fa423b2f586b2c75f55e99ea08402ca080b4f16415","blockNumber":"98","dataKeccak256":"0xdc671584c0ee5eadce2a9ac020caa7a08c5dd13c204578a12af1e8d10dabbbfc","from":"0x14dc79964da2c08b23698b3d3cc7ca32193d9955","status":"1","to":"0xcf7ed3acca5a467e9e704c703e8d87f634fb0fc9","transactionHash":"0x227a036a4ce6e95fecf36bc7c74b26315725ae935f86127a68c50c0aed41eac4","value":"0"},{"blockHash":"0x76c69048f75fc45626b3cf208f7701858479c08093c74e5e6027042308bde544","blockNumber":"99","dataKeccak256":"0x08d66f46e25bf48df105c83831e47a4db9eb7929250a018f9812481b06ad77bd","from":"0x14dc79964da2c08b23698b3d3cc7ca32193d9955","status":"1","to":"0x5fc8d32690cc91d4c39d9d3abcbd16989f875707","transactionHash":"0xfbcd18067314225d02fd9c50f0d13b4dea897824460e420b52cd29c68f4128f1","value":"0"},{"blockHash":"0xcde609b37dfd06a4d01c1625e87408b9be3606b427867fafe1336eac9333f96f","blockNumber":"100","dataKeccak256":"0x3546a8dd832a69cd2a0969a77a61e46930196c0af83bac7d49a1e5deb73a2486","from":"0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266","status":"1","to":"0x5fbdb2315678afecb367f032d93f642f64180aa3","transactionHash":"0xb27f6f94be6ec82dff9541ce97b8be60c4f1d1926559c92803d8607e31b9e9bb","value":"0"},{"blockHash":"0x50724768a494f66212477e1b794fc67cda1e0c0fe314b59fca429f18c4e8ed60","blockNumber":"102","dataKeccak256":"0x9a5a67aecea01970be2fd8c72e3035d2ee160375d23008dcb44ec94b4e82e3c1","from":"0x14dc79964da2c08b23698b3d3cc7ca32193d9955","status":"1","to":"0x5fc8d32690cc91d4c39d9d3abcbd16989f875707","transactionHash":"0x2579b776ea70a80e3e9290a665eeaa32b9293dfe4895b2bb7edc11e1859874a8","value":"0"}],"raceClassifications":[{"actor":"0x14dc79964da2c08b23698b3d3cc7ca32193d9955","classification":"landed","kind":"queue-race","status":"0x1","transactionHash":"0xcdad2b31b5209ec19c59bae9af1e3c9b9cf46a0d4fc23e074e2cbaf7b906d542"},{"actor":"0x23618e81e3f5cdf7f54c3d65f7fbc0abf5b21e8f","classification":"benign-race","kind":"queue-race","status":"0x0","transactionHash":"0x78d323bf20eccdafe410b34221ce57e86e9e0bbf15f690fd636366ae0c726c07"},{"classification":"landed-on-restart","kind":"response-dropped","transactionHash":"0x1c3b079119b87415dfd7f6b883a754d18e2d5f60e8bd3224ccf729a0f660006a"}],"sepoliaFork":[{"blockHash":"0xbbef325a55c007aef3f632a426840dfd16ebcd1f0d7bdc0344bcee6892671cce","blockNumber":"11261291","dataKeccak256":"0xa1548b79599a5ef2fd6de226ea5131c228954c4bf788eb3e4386a2728abe3e8d","from":"0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266","status":"1","to":"0xfff9976782d46cc05630d1f6ebab18b2324d6b14","transactionHash":"0x982297581f853732a51ba940027000e01f30d63ad89ad951a5c450a4632a108f","value":"1000000000000000000"},{"blockHash":"0xe7da2a24aa646e64ac4530da209805173d660389050e925344ba2c060f3ce725","blockNumber":"11261292","dataKeccak256":"0xa1548b79599a5ef2fd6de226ea5131c228954c4bf788eb3e4386a2728abe3e8d","from":"0x70997970c51812dc3a010c7d01b50e0d17dc79c8","status":"1","to":"0xfff9976782d46cc05630d1f6ebab18b2324d6b14","transactionHash":"0xbc0fd8c9b7632d45d3c550f5c0880183092434c96be04c1567e77ae6c6205df0","value":"1000000000000000000"},{"blockHash":"0x0bf8c2fcddc6849697b182342afe6816c6423ed1a75c6a01d4aeb1c6caa72bc6","blockNumber":"11261293","dataKeccak256":"0xa1548b79599a5ef2fd6de226ea5131c228954c4bf788eb3e4386a2728abe3e8d","from":"0x3c44cdddb6a900fa2b585dd299e03d12fa4293bc","status":"1","to":"0xfff9976782d46cc05630d1f6ebab18b2324d6b14","transactionHash":"0xa1f61f195dfba78fd31e8605be8e654fc2a44ad10e615d6f4789a6de3c4f1970","value":"1000000000000000000"},{"blockHash":"0xc0444e3705380b013b7b2f21d0c02e03e856c619ee49e81f0c227db99f20465d","blockNumber":"11261298","dataKeccak256":"0x6b1b77b21710e207cdf6695be543dde3e8da720686f8384a1309bf3afa97cf94","from":"0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266","status":"1","to":"0x367571a4bb72de563f7fc7e9e785ea2e9de8488c","transactionHash":"0xd252ed0cf4d22f74813e66caf331cd828dc8ce15e2a0f1a8bd661098da835f5a","value":"0"},{"blockHash":"0x754d2603ae1383675c9363f37904d8c578ce06e8a5cf4937f92e2187e5080609","blockNumber":"11261300","dataKeccak256":"0xf2ecd9360be285f570898cbd8adc93eaa27a6ee36f48715a71491d34d712973c","from":"0x70997970c51812dc3a010c7d01b50e0d17dc79c8","status":"1","to":"0xfff9976782d46cc05630d1f6ebab18b2324d6b14","transactionHash":"0x31dda4850752217a0252d04bf05202899c9b6736a6f6508846d4017ec3848abb","value":"0"},{"blockHash":"0x6b51a78a722ce54e11dbeba2ab46910f42fe22d31eb9ace641ca6b597f9d7e69","blockNumber":"11261301","dataKeccak256":"0xf2ecd9360be285f570898cbd8adc93eaa27a6ee36f48715a71491d34d712973c","from":"0x3c44cdddb6a900fa2b585dd299e03d12fa4293bc","status":"1","to":"0xfff9976782d46cc05630d1f6ebab18b2324d6b14","transactionHash":"0xe642126892edc3f02dd884ff3f4a842782afef7fe3fbe769e6664c5c7907094e","value":"0"},{"blockHash":"0x6e4b62e7273b441055405b836b355deee7e93d8e9af52e987dfc6876faef9d9f","blockNumber":"11261302","dataKeccak256":"0xa53f8ef74235dc211e2eef4882a5ec1ba70aa72f9f0c3a1a0f1a7d6a2b299e14","from":"0x70997970c51812dc3a010c7d01b50e0d17dc79c8","status":"1","to":"0xc0da7c66c39c154a11f307e10070e44defeec1ed","transactionHash":"0x1540156cba5bfbda7dbb39ab57d19ca973e4e3deee92456926b51828e6b74086","value":"0"},{"blockHash":"0xb1e1a4712dc68082de231da85ceea0e46ab3d9bd8abee05dde5d59989eaa00fa","blockNumber":"11261303","dataKeccak256":"0x824e62a0463b91f7df2570dda5f73d6cac7e899a9d2ef44a11311e0eb40e4c18","from":"0x70997970c51812dc3a010c7d01b50e0d17dc79c8","status":"1","to":"0xc0da7c66c39c154a11f307e10070e44defeec1ed","transactionHash":"0xfbefe36a714d165777024a2f09ceb98df8743dc71f8600255cee7b1e37804999","value":"0"},{"blockHash":"0xb991b57245b26f57f61ce332c88f9ea1695e91049b943bc8b5e683e7613f51a3","blockNumber":"11261304","dataKeccak256":"0xd106d4e61adfa9997d6c228ac179813191ef27b2956b2939ff6681f31b946eb0","from":"0x70997970c51812dc3a010c7d01b50e0d17dc79c8","status":"1","to":"0xc0da7c66c39c154a11f307e10070e44defeec1ed","transactionHash":"0x13a206e4742f728c21ba7382d74cdc673aa08f2f170f2b8f5aa8391cc67a7ca3","value":"0"},{"blockHash":"0x6525b28506002f3e0bd7aa3153a81e0470f3cc81c21489c44897692f3d22e01e","blockNumber":"11261305","dataKeccak256":"0x7d6b87cd53d20ce141aebdbc4ddf20e98aa8338366dede2c634ac4cf8ebbef89","from":"0x70997970c51812dc3a010c7d01b50e0d17dc79c8","status":"1","to":"0x16bba9783b0eb15f7618436b37bea8aa73263ea4","transactionHash":"0xebfdb9056ace2f419ea3e884024bb533ff3af925f0a16c0f0f6ec28aeb7a5a43","value":"0"},{"blockHash":"0x7fe59afb6365088f13f57a9b142330b52ee5330677149f0c31edc5783111214f","blockNumber":"11261306","dataKeccak256":"0x96f2bf981d58b4c65846917b6c83bdb1cc48c9259de5a7fed3273cc04c88ed33","from":"0x70997970c51812dc3a010c7d01b50e0d17dc79c8","status":"1","to":"0x367571a4bb72de563f7fc7e9e785ea2e9de8488c","transactionHash":"0x0a61ff382dfda73a2ca110cd4c1ab83c162e291841e61cceaf9214375905c453","value":"0"},{"blockHash":"0x6fc920fd98b443f6b718cca7706592afe510ee166b62a93eecd08faa03fd49d3","blockNumber":"11261308","dataKeccak256":"0x6a6b6357ca887f907a628f6680e809216dd843b2b4807d69dc3169d73798302f","from":"0x14dc79964da2c08b23698b3d3cc7ca32193d9955","status":"1","to":"0x367571a4bb72de563f7fc7e9e785ea2e9de8488c","transactionHash":"0xa7b0990af43130257bdae1c1114bbf28a84fdfc81709746721ca1b5b63774ac9","value":"0"},{"blockHash":"0x0b28a0381617b98f271c7de7c7ca0e2330b3cd91ea71658dcf3615778439ebf0","blockNumber":"11261309","dataKeccak256":"0x27724335462f8fa816572a0f63082be46af4fee821d23e6345dc2bb8e3959bf2","from":"0x14dc79964da2c08b23698b3d3cc7ca32193d9955","status":"1","to":"0x9e53ccceb400c5466ec14161e5f999adbee78ab3","transactionHash":"0x473ae08e0666e65a61eb8e9349632eeb0c05349e49ca0f20ad36a94fd7d6499d","value":"0"},{"blockHash":"0x7a21e68ff46b63223789e50b49a6e5d1e5c00d5b7cf0cc8b595517ac03f12124","blockNumber":"11261311","dataKeccak256":"0x085bdde6a1cac45546b44e5dd4d880ecefa84a4071651eafa617a54283c00c84","from":"0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266","status":"1","to":"0xfff9976782d46cc05630d1f6ebab18b2324d6b14","transactionHash":"0x6aba61cf37fd469bade41a068c0f7b718c9c31859ea3fe2a580297d2d08cdd62","value":"0"},{"blockHash":"0xb7a782b1c47ca63e4a0ecca779c6a0f084af9c2e1c4d84838632705b279b90f3","blockNumber":"11261312","dataKeccak256":"0x7de72d625468b2f083f15b61cfd8968e8489ca052008b78d849b47de0f51879e","from":"0x14dc79964da2c08b23698b3d3cc7ca32193d9955","status":"1","to":"0x9e53ccceb400c5466ec14161e5f999adbee78ab3","transactionHash":"0xed172f5df7ba9c02965187031359bdd5aaf92b2dcc3b7059bd467841c48d2840","value":"0"}]},"chains":{"anvil":{"arbitration":"0xcf7ed3acca5a467e9e704c703e8d87f634fb0fc9","chainId":31337,"executor":"0x61c36a8d610163660e21a8b7359e1cac0c9133e1","gateway":"0x0165878a594ca255338adfa4d48449f69242eb8f","index":"0xdc64a140aa3e981100a9beca4e685f962f0cf6c9","runtimeCodeKeccak256":{"arbitration":"0xce5d9a04791ba59d3085273eda21c2dc87cba1353f9ccf7808d55094b7a5d90b","executor":"0x9f05f7c27f7c01aceaa4189922b3e9e2ae2322a4ccee8ce7a22a8b61a9f2ff86","gateway":"0xf7a7bbd17771e51dc1a2ce8a4e2869af300faf0ceaca7992576b53f6df6bb1bb","index":"0x2e8a42e082d6ee00eb3edfe933feef37e2eb9a81745c440c51e2af6c9b4ad5e1","vault":"0x6d0a4d60b4a9474e70afe7238d658cd4963185f5dbc155ecd5434f27a34a59d8"},"startBlock":0,"vault":"0x5fc8d32690cc91d4c39d9d3abcbd16989f875707"},"sepoliaFork":{"arbitration":"0x367571a4bb72de563f7fc7e9e785ea2e9de8488c","canonicalWeth":"0xfff9976782d46cc05630d1f6ebab18b2324d6b14","chainId":11155111,"executor":"0x4631f34df916dc11716568b3ec144cb7ea47777f","gateway":"0x16bba9783b0eb15f7618436b37bea8aa73263ea4","index":"0xc0da7c66c39c154a11f307e10070e44defeec1ed","runtimeCodeKeccak256":{"arbitration":"0x92f987742a3e876b9924a67e12c8223e1a2ba14a649f36b9046c5a786742982f","executor":"0xbcf1423428264f3050e1fc221ee5c0c24d4268d16d91f330442206363925aef9","gateway":"0xe1539069ae0d9780104b4401d01e0d6e48560eb1c21c19ec033bc5db1c0e5f4b","index":"0x2e8a42e082d6ee00eb3edfe933feef37e2eb9a81745c440c51e2af6c9b4ad5e1","vault":"0xeae541ff085fbd483ca1666a548e36497d45a3d4f3569358a18a853b8fab0a22"},"startBlock":11261290,"vault":"0x9e53ccceb400c5466ec14161e5f999adbee78ab3"}},"claims":{"demand":false,"guaranteedPayment":false,"liveDeployment":false,"livePayment":false},"documents":{"canonicalBuilder":"tools/agent_documents.py","submissionTerm":"work receipt"},"drills":[{"detail":"digest-only view dedupe; append-only logs","id":1,"status":"pass","tier":"U+F"},{"detail":"copied recipient changes envelope, salt and proposal id","id":2,"status":"pass","tier":"F"},{"detail":"conflicting receipts/envelopes settled independently","id":3,"status":"pass","tier":"A"},{"detail":"self-payment used ordinary timeout and treasury path","id":4,"status":"pass","tier":"A"},{"detail":"field substitution and domain replay rejected","id":5,"status":"pass","tier":"U"},{"detail":"duplicate proposal id reverted without new state","id":6,"status":"pass","tier":"A"},{"detail":"underfunded execution reverted then reconciled after funding","id":7,"status":"pass","tier":"A"},{"detail":"Sepolia-fork underfunded retry reconciled","id":7,"status":"pass","tier":"K"},{"detail":"expired action remained dead; fresh envelope produced a new id","id":8,"status":"pass","tier":"A"},{"detail":"clean replay identical; bad lineage rejected","id":9,"status":"pass","tier":"F"},{"detail":"partial RPC failure sent nothing; retry resumed","id":10,"status":"pass","tier":"F"},{"detail":"signer failure recorded and retried exactly","id":11,"status":"pass","tier":"F"},{"detail":"dropped-response restart observed exactly one landed effect","id":12,"status":"pass","tier":"A"},{"detail":"fresh runner restart matched every lifecycle boundary","id":13,"status":"pass","tier":"A"},{"detail":"two keepers produced one queue winner and one benign loser","id":14,"status":"pass","tier":"A"},{"detail":"hostile document remained inert","id":15,"status":"pass","tier":"U+F"},{"detail":"Sepolia-fork task-to-paid path exactly reconciled","id":16,"status":"pass","tier":"K"}],"forkGolden":{"actionHash":"0xfc5e0d9b7b9637a249d34c5edd0488a597822fd8047814fcbc123728e3eec4ef","attemptLedger":[{"blockHash":"0xbbef325a55c007aef3f632a426840dfd16ebcd1f0d7bdc0344bcee6892671cce","blockNumber":"11261291","dataKeccak256":"0xa1548b79599a5ef2fd6de226ea5131c228954c4bf788eb3e4386a2728abe3e8d","from":"0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266","status":"1","to":"0xfff9976782d46cc05630d1f6ebab18b2324d6b14","transactionHash":"0x982297581f853732a51ba940027000e01f30d63ad89ad951a5c450a4632a108f","value":"1000000000000000000"},{"blockHash":"0xe7da2a24aa646e64ac4530da209805173d660389050e925344ba2c060f3ce725","blockNumber":"11261292","dataKeccak256":"0xa1548b79599a5ef2fd6de226ea5131c228954c4bf788eb3e4386a2728abe3e8d","from":"0x70997970c51812dc3a010c7d01b50e0d17dc79c8","status":"1","to":"0xfff9976782d46cc05630d1f6ebab18b2324d6b14","transactionHash":"0xbc0fd8c9b7632d45d3c550f5c0880183092434c96be04c1567e77ae6c6205df0","value":"1000000000000000000"},{"blockHash":"0x0bf8c2fcddc6849697b182342afe6816c6423ed1a75c6a01d4aeb1c6caa72bc6","blockNumber":"11261293","dataKeccak256":"0xa1548b79599a5ef2fd6de226ea5131c228954c4bf788eb3e4386a2728abe3e8d","from":"0x3c44cdddb6a900fa2b585dd299e03d12fa4293bc","status":"1","to":"0xfff9976782d46cc05630d1f6ebab18b2324d6b14","transactionHash":"0xa1f61f195dfba78fd31e8605be8e654fc2a44ad10e615d6f4789a6de3c4f1970","value":"1000000000000000000"},{"blockHash":"0xc0444e3705380b013b7b2f21d0c02e03e856c619ee49e81f0c227db99f20465d","blockNumber":"11261298","dataKeccak256":"0x6b1b77b21710e207cdf6695be543dde3e8da720686f8384a1309bf3afa97cf94","from":"0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266","status":"1","to":"0x367571a4bb72de563f7fc7e9e785ea2e9de8488c","transactionHash":"0xd252ed0cf4d22f74813e66caf331cd828dc8ce15e2a0f1a8bd661098da835f5a","value":"0"},{"blockHash":"0x754d2603ae1383675c9363f37904d8c578ce06e8a5cf4937f92e2187e5080609","blockNumber":"11261300","dataKeccak256":"0xf2ecd9360be285f570898cbd8adc93eaa27a6ee36f48715a71491d34d712973c","from":"0x70997970c51812dc3a010c7d01b50e0d17dc79c8","status":"1","to":"0xfff9976782d46cc05630d1f6ebab18b2324d6b14","transactionHash":"0x31dda4850752217a0252d04bf05202899c9b6736a6f6508846d4017ec3848abb","value":"0"},{"blockHash":"0x6b51a78a722ce54e11dbeba2ab46910f42fe22d31eb9ace641ca6b597f9d7e69","blockNumber":"11261301","dataKeccak256":"0xf2ecd9360be285f570898cbd8adc93eaa27a6ee36f48715a71491d34d712973c","from":"0x3c44cdddb6a900fa2b585dd299e03d12fa4293bc","status":"1","to":"0xfff9976782d46cc05630d1f6ebab18b2324d6b14","transactionHash":"0xe642126892edc3f02dd884ff3f4a842782afef7fe3fbe769e6664c5c7907094e","value":"0"},{"blockHash":"0x6e4b62e7273b441055405b836b355deee7e93d8e9af52e987dfc6876faef9d9f","blockNumber":"11261302","dataKeccak256":"0xa53f8ef74235dc211e2eef4882a5ec1ba70aa72f9f0c3a1a0f1a7d6a2b299e14","from":"0x70997970c51812dc3a010c7d01b50e0d17dc79c8","status":"1","to":"0xc0da7c66c39c154a11f307e10070e44defeec1ed","transactionHash":"0x1540156cba5bfbda7dbb39ab57d19ca973e4e3deee92456926b51828e6b74086","value":"0"},{"blockHash":"0xb1e1a4712dc68082de231da85ceea0e46ab3d9bd8abee05dde5d59989eaa00fa","blockNumber":"11261303","dataKeccak256":"0x824e62a0463b91f7df2570dda5f73d6cac7e899a9d2ef44a11311e0eb40e4c18","from":"0x70997970c51812dc3a010c7d01b50e0d17dc79c8","status":"1","to":"0xc0da7c66c39c154a11f307e10070e44defeec1ed","transactionHash":"0xfbefe36a714d165777024a2f09ceb98df8743dc71f8600255cee7b1e37804999","value":"0"},{"blockHash":"0xb991b57245b26f57f61ce332c88f9ea1695e91049b943bc8b5e683e7613f51a3","blockNumber":"11261304","dataKeccak256":"0xd106d4e61adfa9997d6c228ac179813191ef27b2956b2939ff6681f31b946eb0","from":"0x70997970c51812dc3a010c7d01b50e0d17dc79c8","status":"1","to":"0xc0da7c66c39c154a11f307e10070e44defeec1ed","transactionHash":"0x13a206e4742f728c21ba7382d74cdc673aa08f2f170f2b8f5aa8391cc67a7ca3","value":"0"},{"blockHash":"0x6525b28506002f3e0bd7aa3153a81e0470f3cc81c21489c44897692f3d22e01e","blockNumber":"11261305","dataKeccak256":"0x7d6b87cd53d20ce141aebdbc4ddf20e98aa8338366dede2c634ac4cf8ebbef89","from":"0x70997970c51812dc3a010c7d01b50e0d17dc79c8","status":"1","to":"0x16bba9783b0eb15f7618436b37bea8aa73263ea4","transactionHash":"0xebfdb9056ace2f419ea3e884024bb533ff3af925f0a16c0f0f6ec28aeb7a5a43","value":"0"},{"blockHash":"0x7fe59afb6365088f13f57a9b142330b52ee5330677149f0c31edc5783111214f","blockNumber":"11261306","dataKeccak256":"0x96f2bf981d58b4c65846917b6c83bdb1cc48c9259de5a7fed3273cc04c88ed33","from":"0x70997970c51812dc3a010c7d01b50e0d17dc79c8","status":"1","to":"0x367571a4bb72de563f7fc7e9e785ea2e9de8488c","transactionHash":"0x0a61ff382dfda73a2ca110cd4c1ab83c162e291841e61cceaf9214375905c453","value":"0"},{"blockHash":"0x6fc920fd98b443f6b718cca7706592afe510ee166b62a93eecd08faa03fd49d3","blockNumber":"11261308","dataKeccak256":"0x6a6b6357ca887f907a628f6680e809216dd843b2b4807d69dc3169d73798302f","from":"0x14dc79964da2c08b23698b3d3cc7ca32193d9955","status":"1","to":"0x367571a4bb72de563f7fc7e9e785ea2e9de8488c","transactionHash":"0xa7b0990af43130257bdae1c1114bbf28a84fdfc81709746721ca1b5b63774ac9","value":"0"},{"blockHash":"0x0b28a0381617b98f271c7de7c7ca0e2330b3cd91ea71658dcf3615778439ebf0","blockNumber":"11261309","dataKeccak256":"0x27724335462f8fa816572a0f63082be46af4fee821d23e6345dc2bb8e3959bf2","from":"0x14dc79964da2c08b23698b3d3cc7ca32193d9955","status":"1","to":"0x9e53ccceb400c5466ec14161e5f999adbee78ab3","transactionHash":"0x473ae08e0666e65a61eb8e9349632eeb0c05349e49ca0f20ad36a94fd7d6499d","value":"0"},{"blockHash":"0x7a21e68ff46b63223789e50b49a6e5d1e5c00d5b7cf0cc8b595517ac03f12124","blockNumber":"11261311","dataKeccak256":"0x085bdde6a1cac45546b44e5dd4d880ecefa84a4071651eafa617a54283c00c84","from":"0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266","status":"1","to":"0xfff9976782d46cc05630d1f6ebab18b2324d6b14","transactionHash":"0x6aba61cf37fd469bade41a068c0f7b718c9c31859ea3fe2a580297d2d08cdd62","value":"0"},{"blockHash":"0xb7a782b1c47ca63e4a0ecca779c6a0f084af9c2e1c4d84838632705b279b90f3","blockNumber":"11261312","dataKeccak256":"0x7de72d625468b2f083f15b61cfd8968e8489ca052008b78d849b47de0f51879e","from":"0x14dc79964da2c08b23698b3d3cc7ca32193d9955","status":"1","to":"0x9e53ccceb400c5466ec14161e5f999adbee78ab3","transactionHash":"0xed172f5df7ba9c02965187031359bdd5aaf92b2dcc3b7059bd467841c48d2840","value":"0"}],"balanceProof":{"afterBlock":11261312,"beforeBlock":11261311,"executorAfter":0,"executorBefore":10000000000000000,"recipientAfter":10000000000000000,"recipientBefore":0},"cleanRpcReplaySha256":"0x8e3dce0e3c5dfb47ea726663f408c2afadfd6973d482744b87b78a41286bc446","proposalId":"114149015381581747379162110876038034003660320288676425769882653396550965708015","publishedDocuments":[{"blockHash":"0x6e4b62e7273b441055405b836b355deee7e93d8e9af52e987dfc6876faef9d9f","blockNumber":"11261302","document":"0x7b22636861696e4964223a223131313535313131222c226b696e64223a2266616f2e7461736b222c2273616c74223a22307830303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030336638222c2273706563223a2252657475726e2065786163742064657465726d696e69737469632065766964656e63652e222c227469746c65223a22416e76696c207461736b203136222c2276223a2231222c227661756c74223a22307839653533636363656234303063353436366563313431363165356639393961646265653738616233227d","documentDigest":"0xfe73fb44686eca0928d4a7a1ac8274d269dfebcaebf56d474bc08b9317bc335f","kind":"0xa87c1f2bd1ee275d3f44c021b929709db51ad8a945c2c34a0857974e28595821","parentDigest":"0x0000000000000000000000000000000000000000000000000000000000000000","publisher":"0x70997970c51812dc3a010c7d01b50e0d17dc79c8","transactionHash":"0x1540156cba5bfbda7dbb39ab57d19ca973e4e3deee92456926b51828e6b74086"},{"blockHash":"0xb1e1a4712dc68082de231da85ceea0e46ab3d9bd8abee05dde5d59989eaa00fa","blockNumber":"11261303","document":"0x7b22617274696661637473223a5b7b22646967657374223a22307830303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030376530222c22757269223a2268747470733a2f2f6578616d706c652e746573742f3136227d5d2c22636861696e4964223a223131313535313131222c226b696e64223a2266616f2e72656365697074222c2273616c74223a22307830303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030626338222c2273756d6d617279223a22416e76696c2065766964656e6365203136222c227461736b223a22307866653733666234343638366563613039323864346137613161633832373464323639646665626361656266353664343734626330386239333137626333333566222c2276223a2231222c227661756c74223a22307839653533636363656234303063353436366563313431363165356639393961646265653738616233222c22776f726b6572223a22307831356433346161663534323637646237643763333637383339616166373161303061326336613635227d","documentDigest":"0x472acf280871e2d75575dd40aa623a65704af15c0bcec81263e5d6ee1d3e2293","kind":"0xe2c91b1ce0f47a0ac033aec054b0ce8dd8a0ca22e4812f61776ed60835734da1","parentDigest":"0xfe73fb44686eca0928d4a7a1ac8274d269dfebcaebf56d474bc08b9317bc335f","publisher":"0x70997970c51812dc3a010c7d01b50e0d17dc79c8","transactionHash":"0xfbefe36a714d165777024a2f09ceb98df8743dc71f8600255cee7b1e37804999"},{"blockHash":"0xb991b57245b26f57f61ce332c88f9ea1695e91049b943bc8b5e683e7613f51a3","blockNumber":"11261304","document":"0x7b22616d6f756e74223a223130303030303030303030303030303030222c226173736574223a22307866666639393736373832643436636330353633306431663665626162313862323332346436623134222c22636861696e4964223a223131313535313131222c226b696e64223a2266616f2e7061796d656e74222c2272656365697074223a22307834373261636632383038373165326437353537356464343061613632336136353730346166313563306263656338313236336535643665653164336532323933222c22726563697069656e74223a22307839393635353037643161353562636332363935633538626131366662333764383139623061346463222c2273616c74223a22307830303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030666230222c227461736b223a22307866653733666234343638366563613039323864346137613161633832373464323639646665626361656266353664343734626330386239333137626333333566222c2276223a2231222c227661756c74223a22307839653533636363656234303063353436366563313431363165356639393961646265653738616233227d","documentDigest":"0x8ea8d53d3755564078b1d04e4ebb9549b95fd08c2b39058391c3df0d83e90585","kind":"0x8161a6637134aa32b72dafb5032097de770b8555713c2415d800ea5af322c7bd","parentDigest":"0x472acf280871e2d75575dd40aa623a65704af15c0bcec81263e5d6ee1d3e2293","publisher":"0x70997970c51812dc3a010c7d01b50e0d17dc79c8","transactionHash":"0x13a206e4742f728c21ba7382d74cdc673aa08f2f170f2b8f5aa8391cc67a7ca3"}],"queueWindow":{"executeAfter":1784000609,"executed":true,"expired":false,"expiresAt":1784605409}},"kind":"fao.agentwork.p1-evidence","lifecycle":[{"acceptanceRoute":null,"accepted":false,"actionHash":"0xf839b0e467d2c57d8b6edf9414dbefb4c7465564e14b5f7a033ad90746d83e01","executable":false,"finalized":{"hash":"0xe11a3975d450e087e2ae1ef948fb0dda346a3b6c0ac17cf774bd9fadf3144dc7","number":92,"timestamp":950472},"lifecycle":"TASK_PUBLISHED","paid":false,"proposal":null,"proposalId":"112275517596501179858915180517694016303137677779416240388647774897325748010497","publicationOccurrences":{"payment":0,"receipt":0,"task":1},"queued":{"executeAfter":0,"executed":false,"expired":false,"expiresAt":0}},{"acceptanceRoute":null,"accepted":false,"actionHash":"0xf839b0e467d2c57d8b6edf9414dbefb4c7465564e14b5f7a033ad90746d83e01","executable":false,"finalized":{"hash":"0x0557552342dfd35fa1d9e24f40db2a74243d411dc0890400c6ffdd6655ce4074","number":93,"timestamp":950472},"lifecycle":"RECEIPT_PUBLISHED","paid":false,"proposal":null,"proposalId":"112275517596501179858915180517694016303137677779416240388647774897325748010497","publicationOccurrences":{"payment":0,"receipt":1,"task":1},"queued":{"executeAfter":0,"executed":false,"expired":false,"expiresAt":0}},{"acceptanceRoute":null,"accepted":false,"actionHash":"0xf839b0e467d2c57d8b6edf9414dbefb4c7465564e14b5f7a033ad90746d83e01","executable":false,"finalized":{"hash":"0x5593f9f8eb8e0d716a7e0ff27a5d62f7b23ead1de6d87bb99b17ff2278584518","number":94,"timestamp":950473},"lifecycle":"PAYMENT_PUBLISHED","paid":false,"proposal":null,"proposalId":"112275517596501179858915180517694016303137677779416240388647774897325748010497","publicationOccurrences":{"payment":1,"receipt":1,"task":1},"queued":{"executeAfter":0,"executed":false,"expired":false,"expiresAt":0}},{"acceptanceRoute":null,"accepted":false,"actionHash":"0xf839b0e467d2c57d8b6edf9414dbefb4c7465564e14b5f7a033ad90746d83e01","executable":false,"finalized":{"hash":"0x31f1721679bb9fb40a33ef79ae1fb195db02efbe4fec336d95b4ac61de0cd3df","number":95,"timestamp":950473},"lifecycle":"PROPOSED","paid":false,"proposal":{"accepted":false,"exists":true,"lastStateChangeAt":950473,"minActivationBond":2,"noBidder":"0x0000000000000000000000000000000000000000","noBondAmount":0,"queuePosition":0,"settled":false,"state":"INACTIVE","yesBidder":"0x0000000000000000000000000000000000000000","yesBondAmount":0},"proposalId":"112275517596501179858915180517694016303137677779416240388647774897325748010497","publicationOccurrences":{"payment":1,"receipt":1,"task":1},"queued":{"executeAfter":0,"executed":false,"expired":false,"expiresAt":0}},{"acceptanceRoute":null,"accepted":false,"actionHash":"0xf839b0e467d2c57d8b6edf9414dbefb4c7465564e14b5f7a033ad90746d83e01","executable":false,"finalized":{"hash":"0x5c98f6e762edb60183ee68761081ae0e681816f546873b5626d9c212e9abc99f","number":96,"timestamp":950473},"lifecycle":"BONDED","paid":false,"proposal":{"accepted":false,"exists":true,"lastStateChangeAt":950473,"minActivationBond":2,"noBidder":"0x0000000000000000000000000000000000000000","noBondAmount":0,"queuePosition":0,"settled":false,"state":"YES","yesBidder":"0x70997970c51812dc3a010c7d01b50e0d17dc79c8","yesBondAmount":2},"proposalId":"112275517596501179858915180517694016303137677779416240388647774897325748010497","publicationOccurrences":{"payment":1,"receipt":1,"task":1},"queued":{"executeAfter":0,"executed":false,"expired":false,"expiresAt":0}},{"acceptanceRoute":"timeout","accepted":true,"actionHash":"0xf839b0e467d2c57d8b6edf9414dbefb4c7465564e14b5f7a033ad90746d83e01","executable":false,"finalized":{"hash":"0xda186652d7e06143c1d373fa423b2f586b2c75f55e99ea08402ca080b4f16415","number":98,"timestamp":950485},"lifecycle":"ACCEPTED","paid":false,"proposal":{"accepted":true,"exists":true,"lastStateChangeAt":950485,"minActivationBond":2,"noBidder":"0x0000000000000000000000000000000000000000","noBondAmount":0,"queuePosition":0,"settled":true,"state":"SETTLED","yesBidder":"0x70997970c51812dc3a010c7d01b50e0d17dc79c8","yesBondAmount":2},"proposalId":"112275517596501179858915180517694016303137677779416240388647774897325748010497","publicationOccurrences":{"payment":1,"receipt":1,"task":1},"queued":{"executeAfter":0,"executed":false,"expired":false,"expiresAt":0}},{"acceptanceRoute":"timeout","accepted":true,"actionHash":"0xf839b0e467d2c57d8b6edf9414dbefb4c7465564e14b5f7a033ad90746d83e01","executable":false,"finalized":{"hash":"0x76c69048f75fc45626b3cf208f7701858479c08093c74e5e6027042308bde544","number":99,"timestamp":950485},"lifecycle":"QUEUED","paid":false,"proposal":{"accepted":true,"exists":true,"lastStateChangeAt":950485,"minActivationBond":2,"noBidder":"0x0000000000000000000000000000000000000000","noBondAmount":0,"queuePosition":0,"settled":true,"state":"SETTLED","yesBidder":"0x70997970c51812dc3a010c7d01b50e0d17dc79c8","yesBondAmount":2},"proposalId":"112275517596501179858915180517694016303137677779416240388647774897325748010497","publicationOccurrences":{"payment":1,"receipt":1,"task":1},"queued":{"executeAfter":1036885,"executed":false,"expired":false,"expiresAt":1641685}},{"acceptanceRoute":"timeout","accepted":true,"actionHash":"0xf839b0e467d2c57d8b6edf9414dbefb4c7465564e14b5f7a033ad90746d83e01","executable":false,"finalized":{"hash":"0x50724768a494f66212477e1b794fc67cda1e0c0fe314b59fca429f18c4e8ed60","number":102,"timestamp":1036885},"lifecycle":"PAID","paid":true,"proposal":{"accepted":true,"exists":true,"lastStateChangeAt":950485,"minActivationBond":2,"noBidder":"0x0000000000000000000000000000000000000000","noBondAmount":0,"queuePosition":0,"settled":true,"state":"SETTLED","yesBidder":"0x70997970c51812dc3a010c7d01b50e0d17dc79c8","yesBondAmount":2},"proposalId":"112275517596501179858915180517694016303137677779416240388647774897325748010497","publicationOccurrences":{"payment":1,"receipt":1,"task":1},"queued":{"executeAfter":1036885,"executed":true,"expired":false,"expiresAt":1641685}}],"pins":{"agentWorkIndexPredictedAddress":"0x07db8c5bd5a2ea12de1d5a322b73cea536afff49","agentWorkIndexRuntimeKeccak256":"0x2e8a42e082d6ee00eb3edfe933feef37e2eb9a81745c440c51e2af6c9b4ad5e1","predictionDeployed":false},"publishedDocuments":{"anvil":[{"blockHash":"0xb7d59bd04bbdcb5ddf28468d732d191a181cd86aa5d516a9a505334416af0959","blockNumber":"12","document":"0x7b22636861696e4964223a223331333337222c226b696e64223a2266616f2e7461736b222c2273616c74223a22307830303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030336539222c2273706563223a2252657475726e2065786163742064657465726d696e69737469632065766964656e63652e222c227469746c65223a22416e76696c207461736b2031222c2276223a2231222c227661756c74223a22307835666338643332363930636339316434633339643964336162636264313639383966383735373037227d","documentDigest":"0xd4493bcc3c104e80ac0bfd8ccf03601f65611a5fa25aac2df8e7293c80237aff","kind":"0xa87c1f2bd1ee275d3f44c021b929709db51ad8a945c2c34a0857974e28595821","parentDigest":"0x0000000000000000000000000000000000000000000000000000000000000000","publisher":"0x70997970c51812dc3a010c7d01b50e0d17dc79c8","transactionHash":"0x38d9338948fae774e08c1b1d83c41ff348e06d68e7641b6b72339d41b0388b85"},{"blockHash":"0xf00165b6f9cf926da35dfa19a28400508f2e07fb04fcae135fd40a4df7fd184c","blockNumber":"13","document":"0x7b22617274696661637473223a5b7b22646967657374223a22307830303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030376431222c22757269223a2268747470733a2f2f6578616d706c652e746573742f31227d5d2c22636861696e4964223a223331333337222c226b696e64223a2266616f2e72656365697074222c2273616c74223a22307830303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030626239222c2273756d6d617279223a22416e76696c2065766964656e63652031222c227461736b223a22307864343439336263633363313034653830616330626664386363663033363031663635363131613566613235616163326466386537323933633830323337616666222c2276223a2231222c227661756c74223a22307835666338643332363930636339316434633339643964336162636264313639383966383735373037222c22776f726b6572223a22307831356433346161663534323637646237643763333637383339616166373161303061326336613635227d","documentDigest":"0x55dd4d4f24d2d1c01924beada148507dadb2f759b21ba7e1c501ec270a3e451a","kind":"0xe2c91b1ce0f47a0ac033aec054b0ce8dd8a0ca22e4812f61776ed60835734da1","parentDigest":"0xd4493bcc3c104e80ac0bfd8ccf03601f65611a5fa25aac2df8e7293c80237aff","publisher":"0x70997970c51812dc3a010c7d01b50e0d17dc79c8","transactionHash":"0xe9f2ff4cdc849a3c6adea0599d9de67f453d9a57e7ed8c92bb97e63352280611"},{"blockHash":"0xe0ed95ca4c901821f504fdf5d00b031e22b3ccb484528e05828fb9810cccf2d7","blockNumber":"14","document":"0x7b22616d6f756e74223a223130222c226173736574223a22307835666264623233313536373861666563623336376630333264393366363432663634313830616133222c22636861696e4964223a223331333337222c226b696e64223a2266616f2e7061796d656e74222c2272656365697074223a22307835356464346434663234643264316330313932346265616461313438353037646164623266373539623231626137653163353031656332373061336534353161222c22726563697069656e74223a22307839393635353037643161353562636332363935633538626131366662333764383139623061346463222c2273616c74223a22307830303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030666131222c227461736b223a22307864343439336263633363313034653830616330626664386363663033363031663635363131613566613235616163326466386537323933633830323337616666222c2276223a2231222c227661756c74223a22307835666338643332363930636339316434633339643964336162636264313639383966383735373037227d","documentDigest":"0x49bbe89f16c3a49188e8396f7a31e559a42cb5094fd4918fd8d9b60dabd4da88","kind":"0x8161a6637134aa32b72dafb5032097de770b8555713c2415d800ea5af322c7bd","parentDigest":"0x55dd4d4f24d2d1c01924beada148507dadb2f759b21ba7e1c501ec270a3e451a","publisher":"0x70997970c51812dc3a010c7d01b50e0d17dc79c8","transactionHash":"0x9d6b7eda0e6bc3df0dba4b43c6b48dc80484c904fb6a9af09a0f49587a0f328e"},{"blockHash":"0x69cf7d9ceca3b4d7563b7900ddb82cf38604cf6b06f716c8e6684da8cf7f9782","blockNumber":"16","document":"0x7b22636861696e4964223a223331333337222c226b696e64223a2266616f2e7461736b222c2273616c74223a22307830303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030336561222c2273706563223a2252657475726e2065786163742064657465726d696e69737469632065766964656e63652e222c227469746c65223a22416e76696c207461736b2032222c2276223a2231222c227661756c74223a22307835666338643332363930636339316434633339643964336162636264313639383966383735373037227d","documentDigest":"0xfcc627a9714121316b92680b2c1a8edf24433752d80adfe0bc5cb5bb679b62a1","kind":"0xa87c1f2bd1ee275d3f44c021b929709db51ad8a945c2c34a0857974e28595821","parentDigest":"0x0000000000000000000000000000000000000000000000000000000000000000","publisher":"0x70997970c51812dc3a010c7d01b50e0d17dc79c8","transactionHash":"0x57ff3c61d6edbb7b378956f1794c773181160a420ba6441937c5e6f6de1fac7f"},{"blockHash":"0xee2d3f338005f4ed9d0123be4feeca6bafb8816416941c28eb9992815d270734","blockNumber":"17","document":"0x7b22617274696661637473223a5b7b22646967657374223a22307830303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030376432222c22757269223a2268747470733a2f2f6578616d706c652e746573742f32227d5d2c22636861696e4964223a223331333337222c226b696e64223a2266616f2e72656365697074222c2273616c74223a22307830303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030626261222c2273756d6d617279223a22416e76696c2065766964656e63652032222c227461736b223a22307866636336323761393731343132313331366239323638306232633161386564663234343333373532643830616466653062633563623562623637396236326131222c2276223a2231222c227661756c74223a22307835666338643332363930636339316434633339643964336162636264313639383966383735373037222c22776f726b6572223a22307831356433346161663534323637646237643763333637383339616166373161303061326336613635227d","documentDigest":"0x6f6f8110d1ba48949645e7f2b9bd8df42a64f0dab8d6e1f637d4b030dd46a437","kind":"0xe2c91b1ce0f47a0ac033aec054b0ce8dd8a0ca22e4812f61776ed60835734da1","parentDigest":"0xfcc627a9714121316b92680b2c1a8edf24433752d80adfe0bc5cb5bb679b62a1","publisher":"0x70997970c51812dc3a010c7d01b50e0d17dc79c8","transactionHash":"0x777ea112b041cca25c7784035f9b7f5273f1953ef02a90be98b39b1eb02265ea"},{"blockHash":"0x3ceee0111e8d6e6dc272495484c8b2d8ac34f3ef9692a9b15ff64df6ef5eb053","blockNumber":"18","document":"0x7b22616d6f756e74223a223131222c226173736574223a22307835666264623233313536373861666563623336376630333264393366363432663634313830616133222c22636861696e4964223a223331333337222c226b696e64223a2266616f2e7061796d656e74222c2272656365697074223a22307836663666383131306431626134383934393634356537663262396264386466343261363466306461623864366531663633376434623033306464343661343337222c22726563697069656e74223a22307839373665613734303236653732363535346462363537666135343736336162643063336130616139222c2273616c74223a22307830303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030666132222c227461736b223a22307866636336323761393731343132313331366239323638306232633161386564663234343333373532643830616466653062633563623562623637396236326131222c2276223a2231222c227661756c74223a22307835666338643332363930636339316434633339643964336162636264313639383966383735373037227d","documentDigest":"0x195f1529bd3a922c3cdd26df552765eb3a63dabc78298a342d995f5894af21f8","kind":"0x8161a6637134aa32b72dafb5032097de770b8555713c2415d800ea5af322c7bd","parentDigest":"0x6f6f8110d1ba48949645e7f2b9bd8df42a64f0dab8d6e1f637d4b030dd46a437","publisher":"0x70997970c51812dc3a010c7d01b50e0d17dc79c8","transactionHash":"0x9676ca882326e8abb4655f0ed474b19041f937f3de08336716178262970b4e4d"},{"blockHash":"0x3ee95d5c50b5cfa820e8bc0b7c98cb5667d9a37fcba56d45cbb6bcef6c603adf","blockNumber":"27","document":"0x7b22636861696e4964223a223331333337222c226b696e64223a2266616f2e7461736b222c2273616c74223a22307830303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030336562222c2273706563223a2252657475726e2065786163742064657465726d696e69737469632065766964656e63652e222c227469746c65223a22416e76696c207461736b2033222c2276223a2231222c227661756c74223a22307835666338643332363930636339316434633339643964336162636264313639383966383735373037227d","documentDigest":"0x7ce6e59aa05ccda98996296faace6cf4d33d6703032c485e647af4b96023a294","kind":"0xa87c1f2bd1ee275d3f44c021b929709db51ad8a945c2c34a0857974e28595821","parentDigest":"0x0000000000000000000000000000000000000000000000000000000000000000","publisher":"0x70997970c51812dc3a010c7d01b50e0d17dc79c8","transactionHash":"0x1873e1614498cd2cdaf460ce04e730e3302d0224ecedfa884e3d2ee9039b2011"},{"blockHash":"0x95324dea05e64c0732be09d9761e8b080d9ac9295fbbce45d40cc3c28c38ccb6","blockNumber":"28","document":"0x7b22617274696661637473223a5b7b22646967657374223a22307830303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030376433222c22757269223a2268747470733a2f2f6578616d706c652e746573742f33227d5d2c22636861696e4964223a223331333337222c226b696e64223a2266616f2e72656365697074222c2273616c74223a22307830303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030626262222c2273756d6d617279223a22416e76696c2065766964656e63652033222c227461736b223a22307837636536653539616130356363646139383939363239366661616365366366346433336436373033303332633438356536343761663462393630323361323934222c2276223a2231222c227661756c74223a22307835666338643332363930636339316434633339643964336162636264313639383966383735373037222c22776f726b6572223a22307831356433346161663534323637646237643763333637383339616166373161303061326336613635227d","documentDigest":"0x901f279291b04ffc00b3e0c9533f779d91b9d15100e809e30bca0f7de75ebc63","kind":"0xe2c91b1ce0f47a0ac033aec054b0ce8dd8a0ca22e4812f61776ed60835734da1","parentDigest":"0x7ce6e59aa05ccda98996296faace6cf4d33d6703032c485e647af4b96023a294","publisher":"0x70997970c51812dc3a010c7d01b50e0d17dc79c8","transactionHash":"0xab87533243c53a8c3eb4389f1d8768e1d96e272905d610d939b2100aa9faab25"},{"blockHash":"0x72791f89ee9ff9b97057e949da422018f6508e848a06414806805d2561abebf6","blockNumber":"29","document":"0x7b22616d6f756e74223a2235222c226173736574223a22307835666264623233313536373861666563623336376630333264393366363432663634313830616133222c22636861696e4964223a223331333337222c226b696e64223a2266616f2e7061796d656e74222c2272656365697074223a22307839303166323739323931623034666663303062336530633935333366373739643931623964313531303065383039653330626361306637646537356562633633222c22726563697069656e74223a22307837303939373937306335313831326463336130313063376430316235306530643137646337396338222c2273616c74223a22307830303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030666133222c227461736b223a22307837636536653539616130356363646139383939363239366661616365366366346433336436373033303332633438356536343761663462393630323361323934222c2276223a2231222c227661756c74223a22307835666338643332363930636339316434633339643964336162636264313639383966383735373037227d","documentDigest":"0x17649d83c79819b32328ed0333a5e687ae94a439f01e4595b447ff39d5036166","kind":"0x8161a6637134aa32b72dafb5032097de770b8555713c2415d800ea5af322c7bd","parentDigest":"0x901f279291b04ffc00b3e0c9533f779d91b9d15100e809e30bca0f7de75ebc63","publisher":"0x70997970c51812dc3a010c7d01b50e0d17dc79c8","transactionHash":"0x2e9f9ae0ea1384b632c84d1ae1c4703048db3ab5bd118fd41e30d5d7a4b8475a"},{"blockHash":"0xebb34d288c3f5707fa1af142f79c8faef6fc365bf010202d26051e57193baa99","blockNumber":"38","document":"0x7b22636861696e4964223a223331333337222c226b696e64223a2266616f2e7461736b222c2273616c74223a22307830303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030336563222c2273706563223a2252657475726e2065786163742064657465726d696e69737469632065766964656e63652e222c227469746c65223a22416e76696c207461736b2034222c2276223a2231222c227661756c74223a22307835666338643332363930636339316434633339643964336162636264313639383966383735373037227d","documentDigest":"0x4434a2d0508b1a819d24fddc14e9e6c5cba7e7a782ddb4ac35cbebf25824d99c","kind":"0xa87c1f2bd1ee275d3f44c021b929709db51ad8a945c2c34a0857974e28595821","parentDigest":"0x0000000000000000000000000000000000000000000000000000000000000000","publisher":"0x70997970c51812dc3a010c7d01b50e0d17dc79c8","transactionHash":"0x6a7a357ccc877899c0fbcb238cda7a2069b210d3a300246a37d735e803b843a0"},{"blockHash":"0x3543649feb2deff33b9ecc9bd076d782546de3e9277088a02daa80795b4e4e99","blockNumber":"39","document":"0x7b22617274696661637473223a5b7b22646967657374223a22307830303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030376434222c22757269223a2268747470733a2f2f6578616d706c652e746573742f34227d5d2c22636861696e4964223a223331333337222c226b696e64223a2266616f2e72656365697074222c2273616c74223a22307830303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030626263222c2273756d6d617279223a22416e76696c2065766964656e63652034222c227461736b223a22307834343334613264303530386231613831396432346664646331346539653663356362613765376137383264646234616333356362656266323538323464393963222c2276223a2231222c227661756c74223a22307835666338643332363930636339316434633339643964336162636264313639383966383735373037222c22776f726b6572223a22307831356433346161663534323637646237643763333637383339616166373161303061326336613635227d","documentDigest":"0xf7946ee2b5c0c7f93dc2a49fcd587d8c79711983cea1b638c23cb763af3065c4","kind":"0xe2c91b1ce0f47a0ac033aec054b0ce8dd8a0ca22e4812f61776ed60835734da1","parentDigest":"0x4434a2d0508b1a819d24fddc14e9e6c5cba7e7a782ddb4ac35cbebf25824d99c","publisher":"0x70997970c51812dc3a010c7d01b50e0d17dc79c8","transactionHash":"0x1edbc5a245b07709431970f7a7b486b81540d16d6826cdf94eb1a778a8b07870"},{"blockHash":"0x6912b597dd6ffd4ba5e256408dc35ef28514e1e36e1e6522c04feef600765d3a","blockNumber":"40","document":"0x7b22616d6f756e74223a223430222c226173736574223a22307835666264623233313536373861666563623336376630333264393366363432663634313830616133222c22636861696e4964223a223331333337222c226b696e64223a2266616f2e7061796d656e74222c2272656365697074223a22307866373934366565326235633063376639336463326134396663643538376438633739373131393833636561316236333863323363623736336166333036356334222c22726563697069656e74223a22307839393635353037643161353562636332363935633538626131366662333764383139623061346463222c2273616c74223a22307830303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030666134222c227461736b223a22307834343334613264303530386231613831396432346664646331346539653663356362613765376137383264646234616333356362656266323538323464393963222c2276223a2231222c227661756c74223a22307835666338643332363930636339316434633339643964336162636264313639383966383735373037227d","documentDigest":"0xecff16c71d62ad7660783fccda9f1c35a5011fa71c81751455e18744504f4cde","kind":"0x8161a6637134aa32b72dafb5032097de770b8555713c2415d800ea5af322c7bd","parentDigest":"0xf7946ee2b5c0c7f93dc2a49fcd587d8c79711983cea1b638c23cb763af3065c4","publisher":"0x70997970c51812dc3a010c7d01b50e0d17dc79c8","transactionHash":"0x73421780df2aa74e738ca55750575e5d6fede097a003ca76e80c93def3295ede"},{"blockHash":"0x27b1d3a4dcdaa73dc7cc706c5fe8d03d8689f76725080b8091b1c2b0d74d3e42","blockNumber":"49","document":"0x7b22636861696e4964223a223331333337222c226b696e64223a2266616f2e7461736b222c2273616c74223a22307830303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030336564222c2273706563223a2252657475726e2065786163742064657465726d696e69737469632065766964656e63652e222c227469746c65223a22416e76696c207461736b2035222c2276223a2231222c227661756c74223a22307835666338643332363930636339316434633339643964336162636264313639383966383735373037227d","documentDigest":"0xb0391a2b7b1c059d4feec4c73d1bbc609d6a7821a0411ff799bf09f4f4e23b1b","kind":"0xa87c1f2bd1ee275d3f44c021b929709db51ad8a945c2c34a0857974e28595821","parentDigest":"0x0000000000000000000000000000000000000000000000000000000000000000","publisher":"0x70997970c51812dc3a010c7d01b50e0d17dc79c8","transactionHash":"0xb5e718bce96d7b6c1a467904d1ef7064c3fd366f1c7f459a4a90a67ec604f0e3"},{"blockHash":"0x222ca91c9c564cda4fdbd21a94950fe85939bcf08ad72436fff125f570e905e1","blockNumber":"50","document":"0x7b22617274696661637473223a5b7b22646967657374223a22307830303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030376435222c22757269223a2268747470733a2f2f6578616d706c652e746573742f35227d5d2c22636861696e4964223a223331333337222c226b696e64223a2266616f2e72656365697074222c2273616c74223a22307830303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030626264222c2273756d6d617279223a22416e76696c2065766964656e63652035222c227461736b223a22307862303339316132623762316330353964346665656334633733643162626336303964366137383231613034313166663739396266303966346634653233623162222c2276223a2231222c227661756c74223a22307835666338643332363930636339316434633339643964336162636264313639383966383735373037222c22776f726b6572223a22307831356433346161663534323637646237643763333637383339616166373161303061326336613635227d","documentDigest":"0x14f9fb4147ecef0e855885037f972c44295cc743d93bb6e6d85bd36b168904b9","kind":"0xe2c91b1ce0f47a0ac033aec054b0ce8dd8a0ca22e4812f61776ed60835734da1","parentDigest":"0xb0391a2b7b1c059d4feec4c73d1bbc609d6a7821a0411ff799bf09f4f4e23b1b","publisher":"0x70997970c51812dc3a010c7d01b50e0d17dc79c8","transactionHash":"0x2fc2a5fc94d80ce604efeacc4b7cad2877e0c79ddf5b69bfc7a8cb49b410c305"},{"blockHash":"0x2f95239e6cfcdd4ff46460b649b90346de054ef2b50c47636e9e1aafad4be851","blockNumber":"51","document":"0x7b22616d6f756e74223a2237222c226173736574223a22307835666264623233313536373861666563623336376630333264393366363432663634313830616133222c22636861696e4964223a223331333337222c226b696e64223a2266616f2e7061796d656e74222c2272656365697074223a22307831346639666234313437656365663065383535383835303337663937326334343239356363373433643933626236653664383562643336623136383930346239222c22726563697069656e74223a22307839393635353037643161353562636332363935633538626131366662333764383139623061346463222c2273616c74223a22307830303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030666135222c227461736b223a22307862303339316132623762316330353964346665656334633733643162626336303964366137383231613034313166663739396266303966346634653233623162222c2276223a2231222c227661756c74223a22307835666338643332363930636339316434633339643964336162636264313639383966383735373037227d","documentDigest":"0x80b6ebee7ceacb46fe34657280622ae798618a8540965c436e4294be0c34e09a","kind":"0x8161a6637134aa32b72dafb5032097de770b8555713c2415d800ea5af322c7bd","parentDigest":"0x14f9fb4147ecef0e855885037f972c44295cc743d93bb6e6d85bd36b168904b9","publisher":"0x70997970c51812dc3a010c7d01b50e0d17dc79c8","transactionHash":"0xa3b67cd8d9f10d59a350cc6e37e5127eb292441aba06c62aa1db5c4860c7462e"},{"blockHash":"0x73b4c00505bc808871ec64721998f838ab2cdcacf4c22dbb36a7790123e8bfba","blockNumber":"58","document":"0x7b22636861696e4964223a223331333337222c226b696e64223a2266616f2e7461736b222c2273616c74223a22307830303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030336565222c2273706563223a2252657475726e2065786163742064657465726d696e69737469632065766964656e63652e222c227469746c65223a22416e76696c207461736b2036222c2276223a2231222c227661756c74223a22307835666338643332363930636339316434633339643964336162636264313639383966383735373037227d","documentDigest":"0xadf9650e51fd22057b46410264def43e3023246320a9abb4b784bbc022620e4d","kind":"0xa87c1f2bd1ee275d3f44c021b929709db51ad8a945c2c34a0857974e28595821","parentDigest":"0x0000000000000000000000000000000000000000000000000000000000000000","publisher":"0x70997970c51812dc3a010c7d01b50e0d17dc79c8","transactionHash":"0x8d7fcf0295198976a36cb30059d80a49e44bec4aae7d6ad9a384d73fad381898"},{"blockHash":"0x17fd7acd10a76a90f033b77a7ca4d75d7183c6260cd5a5bd2c3af7b11dfcad0c","blockNumber":"59","document":"0x7b22617274696661637473223a5b7b22646967657374223a22307830303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030376436222c22757269223a2268747470733a2f2f6578616d706c652e746573742f36227d5d2c22636861696e4964223a223331333337222c226b696e64223a2266616f2e72656365697074222c2273616c74223a22307830303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030626265222c2273756d6d617279223a22416e76696c2065766964656e63652036222c227461736b223a22307861646639363530653531666432323035376234363431303236346465663433653330323332343633323061396162623462373834626263303232363230653464222c2276223a2231222c227661756c74223a22307835666338643332363930636339316434633339643964336162636264313639383966383735373037222c22776f726b6572223a22307831356433346161663534323637646237643763333637383339616166373161303061326336613635227d","documentDigest":"0x470eef33e5b4ea7cd562b34419dcfd92e97997f6c6249a8e3c0cb98b388b0249","kind":"0xe2c91b1ce0f47a0ac033aec054b0ce8dd8a0ca22e4812f61776ed60835734da1","parentDigest":"0xadf9650e51fd22057b46410264def43e3023246320a9abb4b784bbc022620e4d","publisher":"0x70997970c51812dc3a010c7d01b50e0d17dc79c8","transactionHash":"0x1882a24bfa51220a9717fdda9a3a6f37cf31e6bb8ddc569cc265e3e500d8c0b7"},{"blockHash":"0x42ab2c4b9b74486b7a2335194cae8d8aa12c96b761400cbdd94ee6085587dd24","blockNumber":"60","document":"0x7b22616d6f756e74223a2237222c226173736574223a22307835666264623233313536373861666563623336376630333264393366363432663634313830616133222c22636861696e4964223a223331333337222c226b696e64223a2266616f2e7061796d656e74222c2272656365697074223a22307834373065656633336535623465613763643536326233343431396463666439326539373939376636633632343961386533633063623938623338386230323439222c22726563697069656e74223a22307839393635353037643161353562636332363935633538626131366662333764383139623061346463222c2273616c74223a22307830303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030666136222c227461736b223a22307861646639363530653531666432323035376234363431303236346465663433653330323332343633323061396162623462373834626263303232363230653464222c2276223a2231222c227661756c74223a22307835666338643332363930636339316434633339643964336162636264313639383966383735373037227d","documentDigest":"0xa1ebabe4f5208107af55f46df17433e478614d3ff1993df9d0361583103923ee","kind":"0x8161a6637134aa32b72dafb5032097de770b8555713c2415d800ea5af322c7bd","parentDigest":"0x470eef33e5b4ea7cd562b34419dcfd92e97997f6c6249a8e3c0cb98b388b0249","publisher":"0x70997970c51812dc3a010c7d01b50e0d17dc79c8","transactionHash":"0x238e559069c1b34cbfe97b085bd52ece84e3b4311e4d54ef3076676ccb31bca0"},{"blockHash":"0x0ab0d146750d244246522de6cec910b50c97d03ea3a62c7ec41ad5335e4b0541","blockNumber":"62","document":"0x7b22636861696e4964223a223331333337222c226b696e64223a2266616f2e7461736b222c2273616c74223a22307830303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030336566222c2273706563223a2252657475726e2065786163742064657465726d696e69737469632065766964656e63652e222c227469746c65223a22416e76696c207461736b2037222c2276223a2231222c227661756c74223a22307835666338643332363930636339316434633339643964336162636264313639383966383735373037227d","documentDigest":"0xc5d7171396da12b021ea3d0923d24f70e42b7118a236dccae513a5b7a8569395","kind":"0xa87c1f2bd1ee275d3f44c021b929709db51ad8a945c2c34a0857974e28595821","parentDigest":"0x0000000000000000000000000000000000000000000000000000000000000000","publisher":"0x70997970c51812dc3a010c7d01b50e0d17dc79c8","transactionHash":"0xe76f67fd904e1de25aeee7fd4c5b892fcd0ad649e3eb0e66971a4e1ba0d94c0f"},{"blockHash":"0x03ecafd04390fff173baa6ac50f03c8b41b08355d853bbd0c23fd8ad083f53cd","blockNumber":"63","document":"0x7b22617274696661637473223a5b7b22646967657374223a22307830303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030376437222c22757269223a2268747470733a2f2f6578616d706c652e746573742f37227d5d2c22636861696e4964223a223331333337222c226b696e64223a2266616f2e72656365697074222c2273616c74223a22307830303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030626266222c2273756d6d617279223a22416e76696c2065766964656e63652037222c227461736b223a22307863356437313731333936646131326230323165613364303932336432346637306534326237313138613233366463636165353133613562376138353639333935222c2276223a2231222c227661756c74223a22307835666338643332363930636339316434633339643964336162636264313639383966383735373037222c22776f726b6572223a22307831356433346161663534323637646237643763333637383339616166373161303061326336613635227d","documentDigest":"0x11d20479f00988b8a1e1636dcfa6936f89365e361fba6d36440fccd574c44e1c","kind":"0xe2c91b1ce0f47a0ac033aec054b0ce8dd8a0ca22e4812f61776ed60835734da1","parentDigest":"0xc5d7171396da12b021ea3d0923d24f70e42b7118a236dccae513a5b7a8569395","publisher":"0x70997970c51812dc3a010c7d01b50e0d17dc79c8","transactionHash":"0xe07dca8144b40ab04cb7d1fc9f7807bfcbca8fdeb0b8b298d3a8a0392f838ef6"},{"blockHash":"0x54d87f39335ad70c04e230b4d3e06e62ec9dae3c8288ce953e3ccb808c71d225","blockNumber":"64","document":"0x7b22616d6f756e74223a22323030222c226173736574223a22307835666264623233313536373861666563623336376630333264393366363432663634313830616133222c22636861696e4964223a223331333337222c226b696e64223a2266616f2e7061796d656e74222c2272656365697074223a22307831316432303437396630303938386238613165313633366463666136393336663839333635653336316662613664333634343066636364353734633434653163222c22726563697069656e74223a22307839393635353037643161353562636332363935633538626131366662333764383139623061346463222c2273616c74223a22307830303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030666137222c227461736b223a22307863356437313731333936646131326230323165613364303932336432346637306534326237313138613233366463636165353133613562376138353639333935222c2276223a2231222c227661756c74223a22307835666338643332363930636339316434633339643964336162636264313639383966383735373037227d","documentDigest":"0xc70a39a0a6e16e2ab6eb16a78f6c7e80e31f8c6210f9a3fb1452ac91f295b220","kind":"0x8161a6637134aa32b72dafb5032097de770b8555713c2415d800ea5af322c7bd","parentDigest":"0x11d20479f00988b8a1e1636dcfa6936f89365e361fba6d36440fccd574c44e1c","publisher":"0x70997970c51812dc3a010c7d01b50e0d17dc79c8","transactionHash":"0xef43ae2a35991e57d57bf42545a6d1cc1fe7d71557b112e858767d72099afae1"},{"blockHash":"0x6ab721ef7dd6bed9bcab6cc20b4a9ce18308f18adb80ff860f5def13aefa9871","blockNumber":"71","document":"0x7b22636861696e4964223a223331333337222c226b696e64223a2266616f2e7461736b222c2273616c74223a22307830303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030336630222c2273706563223a2252657475726e2065786163742064657465726d696e69737469632065766964656e63652e222c227469746c65223a22416e76696c207461736b2038222c2276223a2231222c227661756c74223a22307835666338643332363930636339316434633339643964336162636264313639383966383735373037227d","documentDigest":"0xde71dbd9a0586eb8de6f3a84550407e05e17d4e795d86d62e97d4668496e18da","kind":"0xa87c1f2bd1ee275d3f44c021b929709db51ad8a945c2c34a0857974e28595821","parentDigest":"0x0000000000000000000000000000000000000000000000000000000000000000","publisher":"0x70997970c51812dc3a010c7d01b50e0d17dc79c8","transactionHash":"0x2cf28d44bf8a214f173ae9f6c7426813ac14d289d4fa164cb63516b4a51b6d20"},{"blockHash":"0x5c23a81382c68b96303fdd9755527520d1b1076e0a3f12fcef7cc647c89480a1","blockNumber":"72","document":"0x7b22617274696661637473223a5b7b22646967657374223a22307830303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030376438222c22757269223a2268747470733a2f2f6578616d706c652e746573742f38227d5d2c22636861696e4964223a223331333337222c226b696e64223a2266616f2e72656365697074222c2273616c74223a22307830303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030626330222c2273756d6d617279223a22416e76696c2065766964656e63652038222c227461736b223a22307864653731646264396130353836656238646536663361383435353034303765303565313764346537393564383664363265393764343636383439366531386461222c2276223a2231222c227661756c74223a22307835666338643332363930636339316434633339643964336162636264313639383966383735373037222c22776f726b6572223a22307831356433346161663534323637646237643763333637383339616166373161303061326336613635227d","documentDigest":"0xe5deae55942abf7137b080449eb264ff03ab4297a901de1a53410ede0e1e1952","kind":"0xe2c91b1ce0f47a0ac033aec054b0ce8dd8a0ca22e4812f61776ed60835734da1","parentDigest":"0xde71dbd9a0586eb8de6f3a84550407e05e17d4e795d86d62e97d4668496e18da","publisher":"0x70997970c51812dc3a010c7d01b50e0d17dc79c8","transactionHash":"0x0cc891848e191f281afb95a00fa74e41d122ac8fef2ba081133ae9297f027e2e"},{"blockHash":"0xc6379783097d262643465a1966aaf73cd69a5630a6ee74017155e69a206d5127","blockNumber":"73","document":"0x7b22616d6f756e74223a22323031222c226173736574223a22307835666264623233313536373861666563623336376630333264393366363432663634313830616133222c22636861696e4964223a223331333337222c226b696e64223a2266616f2e7061796d656e74222c2272656365697074223a22307865356465616535353934326162663731333762303830343439656232363466663033616234323937613930316465316135333431306564653065316531393532222c22726563697069656e74223a22307839393635353037643161353562636332363935633538626131366662333764383139623061346463222c2273616c74223a22307830303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030666138222c227461736b223a22307864653731646264396130353836656238646536663361383435353034303765303565313764346537393564383664363265393764343636383439366531386461222c2276223a2231222c227661756c74223a22307835666338643332363930636339316434633339643964336162636264313639383966383735373037227d","documentDigest":"0x888b5fb2664c15d8bdf3746235126366ed429328347661415f4ef386a3cb787d","kind":"0x8161a6637134aa32b72dafb5032097de770b8555713c2415d800ea5af322c7bd","parentDigest":"0xe5deae55942abf7137b080449eb264ff03ab4297a901de1a53410ede0e1e1952","publisher":"0x70997970c51812dc3a010c7d01b50e0d17dc79c8","transactionHash":"0xf924acdfd673231a3ef8ee946b6b50b19817c84145b491cbcb656bedcef59291"},{"blockHash":"0xac401cbc4a5fbec89ee17f600031e17bcf50d16337e9a2cc194b56723857a05b","blockNumber":"80","document":"0x7b22636861696e4964223a223331333337222c226b696e64223a2266616f2e7461736b222c2273616c74223a22307830303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030336631222c2273706563223a2252657475726e2065786163742064657465726d696e69737469632065766964656e63652e222c227469746c65223a22416e76696c207461736b2039222c2276223a2231222c227661756c74223a22307835666338643332363930636339316434633339643964336162636264313639383966383735373037227d","documentDigest":"0x9ea2b49883c36831205100ac1f0d22295b4a9da8d6af0682f5259418b322b938","kind":"0xa87c1f2bd1ee275d3f44c021b929709db51ad8a945c2c34a0857974e28595821","parentDigest":"0x0000000000000000000000000000000000000000000000000000000000000000","publisher":"0x70997970c51812dc3a010c7d01b50e0d17dc79c8","transactionHash":"0xe2de4dc607473d8b88c3117646756eeec2070573c9086bbcc5e3879b2052538e"},{"blockHash":"0xec18ab8957a8c48bd70eb392de3b9d6c4904eb00919a47fa22887079a6bdc0cd","blockNumber":"81","document":"0x7b22617274696661637473223a5b7b22646967657374223a22307830303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030376439222c22757269223a2268747470733a2f2f6578616d706c652e746573742f39227d5d2c22636861696e4964223a223331333337222c226b696e64223a2266616f2e72656365697074222c2273616c74223a22307830303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030626331222c2273756d6d617279223a22416e76696c2065766964656e63652039222c227461736b223a22307839656132623439383833633336383331323035313030616331663064323232393562346139646138643661663036383266353235393431386233323262393338222c2276223a2231222c227661756c74223a22307835666338643332363930636339316434633339643964336162636264313639383966383735373037222c22776f726b6572223a22307831356433346161663534323637646237643763333637383339616166373161303061326336613635227d","documentDigest":"0x6077036cf4defaa7dac03bad12267de363e86055d1a1237ffe003fd2961f0ead","kind":"0xe2c91b1ce0f47a0ac033aec054b0ce8dd8a0ca22e4812f61776ed60835734da1","parentDigest":"0x9ea2b49883c36831205100ac1f0d22295b4a9da8d6af0682f5259418b322b938","publisher":"0x70997970c51812dc3a010c7d01b50e0d17dc79c8","transactionHash":"0x0707f3d9245c97714aba233ab9d0d1db47114c24c36e3b5028ec28a2f1c7c7dc"},{"blockHash":"0x87942e96f2e3a8bba8c3eed595bc45f8c976b3ec0c1d90057d098eaaee35d2ef","blockNumber":"82","document":"0x7b22616d6f756e74223a2239222c226173736574223a22307835666264623233313536373861666563623336376630333264393366363432663634313830616133222c22636861696e4964223a223331333337222c226b696e64223a2266616f2e7061796d656e74222c2272656365697074223a22307836303737303336636634646566616137646163303362616431323236376465333633653836303535643161313233376666653030336664323936316630656164222c22726563697069656e74223a22307839393635353037643161353562636332363935633538626131366662333764383139623061346463222c2273616c74223a22307830303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030666139222c227461736b223a22307839656132623439383833633336383331323035313030616331663064323232393562346139646138643661663036383266353235393431386233323262393338222c2276223a2231222c227661756c74223a22307835666338643332363930636339316434633339643964336162636264313639383966383735373037227d","documentDigest":"0xa15f517d26dd900ad7916cbb7b36a547f8eadeae13ba1a443e731183414d6700","kind":"0x8161a6637134aa32b72dafb5032097de770b8555713c2415d800ea5af322c7bd","parentDigest":"0x6077036cf4defaa7dac03bad12267de363e86055d1a1237ffe003fd2961f0ead","publisher":"0x70997970c51812dc3a010c7d01b50e0d17dc79c8","transactionHash":"0xc7b4db6734cddcc7c53573c12ad7086a0f198e6ec3c87b2cc15bca1a62f0606b"},{"blockHash":"0xe11a3975d450e087e2ae1ef948fb0dda346a3b6c0ac17cf774bd9fadf3144dc7","blockNumber":"92","document":"0x7b22636861696e4964223a223331333337222c226b696e64223a2266616f2e7461736b222c2273616c74223a22307830303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030336632222c2273706563223a2252657475726e2065786163742064657465726d696e69737469632065766964656e63652e222c227469746c65223a22416e76696c207461736b203130222c2276223a2231222c227661756c74223a22307835666338643332363930636339316434633339643964336162636264313639383966383735373037227d","documentDigest":"0x165b5f01f33004adfd07470e845eed71e331ee74cf45d7b4b25a1e39f1cfb5d5","kind":"0xa87c1f2bd1ee275d3f44c021b929709db51ad8a945c2c34a0857974e28595821","parentDigest":"0x0000000000000000000000000000000000000000000000000000000000000000","publisher":"0x70997970c51812dc3a010c7d01b50e0d17dc79c8","transactionHash":"0xbf66d12fa304e16795c3abf73f7c8f2a95417609777a86afc84d6133fa9a3e9f"},{"blockHash":"0x0557552342dfd35fa1d9e24f40db2a74243d411dc0890400c6ffdd6655ce4074","blockNumber":"93","document":"0x7b22617274696661637473223a5b7b22646967657374223a22307830303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030376461222c22757269223a2268747470733a2f2f6578616d706c652e746573742f3130227d5d2c22636861696e4964223a223331333337222c226b696e64223a2266616f2e72656365697074222c2273616c74223a22307830303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030626332222c2273756d6d617279223a22416e76696c2065766964656e6365203130222c227461736b223a22307831363562356630316633333030346164666430373437306538343565656437316533333165653734636634356437623462323561316533396631636662356435222c2276223a2231222c227661756c74223a22307835666338643332363930636339316434633339643964336162636264313639383966383735373037222c22776f726b6572223a22307831356433346161663534323637646237643763333637383339616166373161303061326336613635227d","documentDigest":"0x9c31355fffc4d33e6a2a028d131ffcdba11fb9b05ba8f906029d5b2ed6794cb3","kind":"0xe2c91b1ce0f47a0ac033aec054b0ce8dd8a0ca22e4812f61776ed60835734da1","parentDigest":"0x165b5f01f33004adfd07470e845eed71e331ee74cf45d7b4b25a1e39f1cfb5d5","publisher":"0x70997970c51812dc3a010c7d01b50e0d17dc79c8","transactionHash":"0x0c67e292f5ba3ee420a54b0275cb70fe454844429853a967aa974566820188f1"},{"blockHash":"0x5593f9f8eb8e0d716a7e0ff27a5d62f7b23ead1de6d87bb99b17ff2278584518","blockNumber":"94","document":"0x7b22616d6f756e74223a2238222c226173736574223a22307835666264623233313536373861666563623336376630333264393366363432663634313830616133222c22636861696e4964223a223331333337222c226b696e64223a2266616f2e7061796d656e74222c2272656365697074223a22307839633331333535666666633464333365366132613032386431333166666364626131316662396230356261386639303630323964356232656436373934636233222c22726563697069656e74223a22307839393635353037643161353562636332363935633538626131366662333764383139623061346463222c2273616c74223a22307830303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030666161222c227461736b223a22307831363562356630316633333030346164666430373437306538343565656437316533333165653734636634356437623462323561316533396631636662356435222c2276223a2231222c227661756c74223a22307835666338643332363930636339316434633339643964336162636264313639383966383735373037227d","documentDigest":"0xb103ed1659bf7955b431fdf57e24b018eb8be3d2c0c0faeac75079faf903b7f8","kind":"0x8161a6637134aa32b72dafb5032097de770b8555713c2415d800ea5af322c7bd","parentDigest":"0x9c31355fffc4d33e6a2a028d131ffcdba11fb9b05ba8f906029d5b2ed6794cb3","publisher":"0x70997970c51812dc3a010c7d01b50e0d17dc79c8","transactionHash":"0x766d9037b06b98533fdebce497a7e004ba6ff8ce0eda7162c1c775e5ec9b18dd"}],"sepoliaFork":[{"blockHash":"0x6e4b62e7273b441055405b836b355deee7e93d8e9af52e987dfc6876faef9d9f","blockNumber":"11261302","document":"0x7b22636861696e4964223a223131313535313131222c226b696e64223a2266616f2e7461736b222c2273616c74223a22307830303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030336638222c2273706563223a2252657475726e2065786163742064657465726d696e69737469632065766964656e63652e222c227469746c65223a22416e76696c207461736b203136222c2276223a2231222c227661756c74223a22307839653533636363656234303063353436366563313431363165356639393961646265653738616233227d","documentDigest":"0xfe73fb44686eca0928d4a7a1ac8274d269dfebcaebf56d474bc08b9317bc335f","kind":"0xa87c1f2bd1ee275d3f44c021b929709db51ad8a945c2c34a0857974e28595821","parentDigest":"0x0000000000000000000000000000000000000000000000000000000000000000","publisher":"0x70997970c51812dc3a010c7d01b50e0d17dc79c8","transactionHash":"0x1540156cba5bfbda7dbb39ab57d19ca973e4e3deee92456926b51828e6b74086"},{"blockHash":"0xb1e1a4712dc68082de231da85ceea0e46ab3d9bd8abee05dde5d59989eaa00fa","blockNumber":"11261303","document":"0x7b22617274696661637473223a5b7b22646967657374223a22307830303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030376530222c22757269223a2268747470733a2f2f6578616d706c652e746573742f3136227d5d2c22636861696e4964223a223131313535313131222c226b696e64223a2266616f2e72656365697074222c2273616c74223a22307830303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030626338222c2273756d6d617279223a22416e76696c2065766964656e6365203136222c227461736b223a22307866653733666234343638366563613039323864346137613161633832373464323639646665626361656266353664343734626330386239333137626333333566222c2276223a2231222c227661756c74223a22307839653533636363656234303063353436366563313431363165356639393961646265653738616233222c22776f726b6572223a22307831356433346161663534323637646237643763333637383339616166373161303061326336613635227d","documentDigest":"0x472acf280871e2d75575dd40aa623a65704af15c0bcec81263e5d6ee1d3e2293","kind":"0xe2c91b1ce0f47a0ac033aec054b0ce8dd8a0ca22e4812f61776ed60835734da1","parentDigest":"0xfe73fb44686eca0928d4a7a1ac8274d269dfebcaebf56d474bc08b9317bc335f","publisher":"0x70997970c51812dc3a010c7d01b50e0d17dc79c8","transactionHash":"0xfbefe36a714d165777024a2f09ceb98df8743dc71f8600255cee7b1e37804999"},{"blockHash":"0xb991b57245b26f57f61ce332c88f9ea1695e91049b943bc8b5e683e7613f51a3","blockNumber":"11261304","document":"0x7b22616d6f756e74223a223130303030303030303030303030303030222c226173736574223a22307866666639393736373832643436636330353633306431663665626162313862323332346436623134222c22636861696e4964223a223131313535313131222c226b696e64223a2266616f2e7061796d656e74222c2272656365697074223a22307834373261636632383038373165326437353537356464343061613632336136353730346166313563306263656338313236336535643665653164336532323933222c22726563697069656e74223a22307839393635353037643161353562636332363935633538626131366662333764383139623061346463222c2273616c74223a22307830303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030666230222c227461736b223a22307866653733666234343638366563613039323864346137613161633832373464323639646665626361656266353664343734626330386239333137626333333566222c2276223a2231222c227661756c74223a22307839653533636363656234303063353436366563313431363165356639393961646265653738616233227d","documentDigest":"0x8ea8d53d3755564078b1d04e4ebb9549b95fd08c2b39058391c3df0d83e90585","kind":"0x8161a6637134aa32b72dafb5032097de770b8555713c2415d800ea5af322c7bd","parentDigest":"0x472acf280871e2d75575dd40aa623a65704af15c0bcec81263e5d6ee1d3e2293","publisher":"0x70997970c51812dc3a010c7d01b50e0d17dc79c8","transactionHash":"0x13a206e4742f728c21ba7382d74cdc673aa08f2f170f2b8f5aa8391cc67a7ca3"}]},"repository":{"commit":"39251707f49e38423ff164a9a5804c5058a3f16b","dirty":true},"testCommand":"/Library/Developer/CommandLineTools/usr/bin/python3 -m unittest tools.test_agent_documents tools.test_agent_runner","testSummary":"ok","v":"1"} diff --git a/metadata/agent-work-p1-evidence.json.sha256 b/metadata/agent-work-p1-evidence.json.sha256 new file mode 100644 index 0000000..a171712 --- /dev/null +++ b/metadata/agent-work-p1-evidence.json.sha256 @@ -0,0 +1 @@ +0x45d0ee726f5ac83cb03cf71090864fd78452a1e6e734b3e838f5f9f5d39a3649 From 040ca173c01d20c2583a3ce770a07570095143ee Mon Sep 17 00:00:00 2001 From: krandder Date: Mon, 13 Jul 2026 00:48:45 -0300 Subject: [PATCH 4/4] Document manifest verifier boundary --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index e76e3f1..3e65200 100644 --- a/README.md +++ b/README.md @@ -144,6 +144,10 @@ deployer and nonce. Economic deployment manifests use schema v4. Both broadcast and finalized-chain reconstruction pin the exact live runtime hashes of the vault, proposal gateway, arbitration, and treasury executor; RPC verification rejects any substituted authority contract before trusting its views. +Schema v4 keeps integer fields as JSON numbers. Browser clients must not recompute full core or FLM +config preimages through ordinary `JSON.parse`, which loses integers above 2^53; they instead root +disclosed hashes in canonical registrar/receipt provenance and verify finalized wiring plus the v4 +runtime hashes. The Python verifier remains the lossless full-preimage verifier. ## Gnosis Liquidity Stack Deploy