bankml / testing /test_chain.py
Gregory-L's picture
bankML: the whole source (github.com/cryptoAGI/bankml @ 12ae409) and its page, with the bankML persona; the live engine (Dockerfile, hf/start.sh) ready for Docker hardware
28c70af verified
Raw History Blame Contribute Delete
6.77 kB
#!/usr/bin/env python3
# SPDX-License-Identifier: MIT OR Apache-2.0
"""The chain side against a throwaway local EVM: anvil on a free port, the house iNFT_7857 (DeltaVerse iNFT4) deployed
from its compiled artifact, a bankml agent minted from its THOT bundle and loaded back. Nothing leaves this computer.
Skips (exit 0) when anvil or the artifact is absent (CI).
run: python3 testing/test_chain.py"""
import json, os, shutil, socket, subprocess, sys, tempfile, time
from pathlib import Path
ART = Path(os.environ.get("BANKML_INFT_ARTIFACT", Path.home() / "DeltaVerse/deploy/iNFT4/out/iNFT_7857.sol/iNFT_7857.json"))
ANVIL = shutil.which("anvil") or str(Path.home() / ".foundry/bin/anvil")
if not ART.is_file() or not Path(ANVIL).is_file():
print("skip anvil or the iNFT_7857 artifact is not here")
sys.exit(0)
tmp = Path(tempfile.mkdtemp(prefix="bankml-chain-"))
os.environ.update(BANKML_AGENTS=str(tmp / "agents"), BANKML_UI_STATE=str(tmp / "state"))
sys.dont_write_bytecode = True
sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "sAGI"))
import agents, chain, thot # noqa: E402
s = socket.socket(); s.bind(("127.0.0.1", 0)); port = s.getsockname()[1]; s.close()
url = f"http://127.0.0.1:{port}"
proc = subprocess.Popen([ANVIL, "--port", str(port), "--silent"], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
fails = 0
def check(name, ok):
global fails
fails += not ok
print(("ok " if ok else "FAIL ") + name)
try:
for _ in range(100):
try:
chain.chain_id(url)
break
except OSError:
time.sleep(0.1)
accts = chain.rpc(url, "eth_accounts", [])
minter, holder, stranger = accts[0], accts[1], accts[2]
check("anvil is a devnet (chain id 31337)", chain.chain_id(url) == 31337)
art = json.loads(ART.read_text())
ctor = chain.encode(["string", "string", "address", "address", "uint96", "address", "address", "uint256"],
["bankml agents (devnet)", "BKA", minter, minter, 0, "0x" + "00" * 20, "0x" + "00" * 20, 0])
h = chain.rpc(url, "eth_sendTransaction", [{"from": minter, "data": art["bytecode"]["object"] + ctor.hex(), "gas": hex(12_000_000)}])
rc = chain.receipt(url, h)
inft = rc.get("contractAddress")
check("iNFT_7857 deployed from its artifact", int(rc["status"], 16) == 1 and inft)
tpl = {"persona": "tpl", "name": "Template", "source": "s", "format": "f", "system_prompt": "You are the template.", "mantra": "m", "oath": "o",
"bdi": {"beliefs": []}, "skills": {"primary": "p", "taxonomy": [], "defer_triggers": [], "validation": []},
"safety": {}, "embodiment": {}, "token": {"intelligence": {"tool_allowlist": []}}}
slug = agents.derive(tpl, {}, "Minted Mira", system_prompt="You are Mira.")
m1 = thot.build(slug)
plan = chain.plan_mint(slug, to=holder)
check("the plan's contentRoot is the THOT manifest's", plan["args"]["contentRoot"] == m1["identity"]["contentRoot"])
sim = chain.simulate(url, inft, minter, plan)
check(f"simulate as the minter: token {sim.get('tokenId')}", sim["ok"])
bad = chain.simulate(url, inft, stranger, plan)
check(f"simulate without MINTER_ROLE is refused, decoded: {bad.get('error', '')[:60]}", not bad["ok"] and "AccessControl" in bad.get("error", ""))
ut = chain.unsigned_tx(url, inft, minter, plan)
check("unsigned tx for the owner: chain id, calldata, gas estimate, cast line", ut["tx"]["chainId"] == 31337 and ut["tx"]["data"] == plan["calldata"]
and "gas" in ut["tx"] and ut["cast"].startswith("cast send"))
sent = chain.send_devnet(url, inft, minter, plan)
check("minted on the devnet", sent["status"] == 1)
tok = chain.read_token(url, inft, sim["tokenId"])
check("the token reads back: contentRoot, storageURI, open, owner", tok["contentRoot"] == m1["identity"]["contentRoot"]
and tok["storageURI"] == plan["args"]["storageURI"] and tok["open"] and tok["owner"].lower() == holder.lower()
and tok["dimensions"] == 768 and tok["metadataRoot"] == plan["args"]["metadataRoot"])
again = chain.simulate(url, inft, minter, plan)
check(f"the same content root cannot be minted twice: {again.get('error', '')[:40]}", not again["ok"] and "ContentRootAlreadyMinted" in again.get("error", ""))
ld = chain.load_from_chain(url, inft, sim["tokenId"])
check("load from chain finds the agent by contentRoot and verifies it", ld["agent"] == slug and ld["verified"] and ld["generation_on_chain_is_current"])
for i in range(2): # the agent evolves; the token keeps pointing at the genesis
agents.files(slug)["history"].write_text(agents.files(slug)["history"].read_text() + json.dumps({"n": i}) + "\n")
thot.build(slug)
ld2 = chain.load_from_chain(url, inft, sim["tokenId"])
check("after two generations the token still loads: lineage walks back to the minted genesis", ld2["agent"] == slug and ld2["verified"]
and ld2["minted_generation"] == 1 and ld2["current_generation"] == 3 and ld2["descends_from_minted"] and not ld2["generation_on_chain_is_current"])
saved = chain.DEVNET_CHAIN_IDS
chain.DEVNET_CHAIN_IDS = set() # pretend this is a public chain
try:
chain.send_devnet(url, inft, minter, chain.plan_mint(slug, to=holder))
check("send is refused off a devnet (the owner signs)", False)
except PermissionError:
check("send is refused off a devnet (the owner signs)", True)
finally:
chain.DEVNET_CHAIN_IDS = saved
try:
chain.send_devnet(url.replace("127.0.0.1", "devnet.example.com"), inft, minter, plan)
check("send is refused to a node that is not on this machine, whatever chain id it reports", False)
except PermissionError:
check("send is refused to a node that is not on this machine, whatever chain id it reports", True)
try:
chain.encode(["address"], ["0x" + "11" * 19])
check("a 19-byte address is refused (it would shift every later argument)", False)
except ValueError:
check("a 19-byte address is refused (it would shift every later argument)", True)
ut2 = chain.unsigned_tx(url, inft, minter, {**plan, "args": {**plan["args"], "tokenURI": "x\"; rm -rf ~; echo \""}})
check("the cast line quotes every argument", "'x\"; rm -rf ~; echo \"'" in ut2["cast"])
try:
chain.plan_mint(slug, to=holder, dimensions=100)
check("an invalid dimension is refused before any call", False)
except ValueError:
check("an invalid dimension is refused before any call", True)
finally:
proc.terminate()
proc.wait(timeout=10)
shutil.rmtree(tmp, ignore_errors=True)
print("all ok" if not fails else f"{fails} FAILED")
sys.exit(1 if fails else 0)