Skip to content

feat(registry): record arctic-1's deployed contracts - #76

Open
bdchatham wants to merge 2 commits into
brandon2/registry-commits-arctic-1from
brandon2/registry-arctic-1-entry
Open

feat(registry): record arctic-1's deployed contracts#76
bdchatham wants to merge 2 commits into
brandon2/registry-commits-arctic-1from
brandon2/registry-arctic-1-entry

Conversation

@bdchatham

Copy link
Copy Markdown
Contributor

Sixth on the stack. The first chain file this repo ships.

defi-amm         0x225af59603bb554686adfbb2869af4cec12488a1
tokenops-erc20   0xe66344c8ed6dbde610725cd7e3359b1fe4d7ff26
tokenops-erc721  0x815299db5f8e3c6c42356655429cf2701e502bea

A deployment now binds these instead of deploying its own on every restart.

How they were produced

A local run, as registry/chains/README.md prescribes — not from a deployed pod,
which could not produce a file the image then contains. The binary was built from
this branch, so the recorded code hashes match the bindings production runs.

The run reported 3 deployed, 0 copied unverified, so nothing here is carried
through from a file it did not check itself.

Verified three ways, none of them trusting the run that wrote it

The code is ours. Every address holds code; the recorded hash is keccak256 of
what the chain serves; and that code is byte-identical to what the pinned
compiler produces locally.

name             on-chain  hash matches  is our compiled contract
defi-amm         1029 B    true          yes
tokenops-erc20   2012 B    true          yes
tokenops-erc721  3514 B    true          yes

The entry binds. A binary carrying this file, run against arctic-1:

📖 contract registry: matched arctic-1 (chain 713715) from embedded, 3 contract(s)
📖 tokenops-erc20:  bound the recorded address 0xe66344c8…, no deployment sent
📖 tokenops-erc721: bound the recorded address 0x815299Db…, no deployment sent
📖 defi-amm:        bound the recorded address 0x225AF596…, no deployment sent

The transactions execute. trackReceipts was off, so "sent" would not have
proved anything. On-chain state after the run:

AMM reserveA      1000010302605699354462822   (floor is 1000000000000000000000000)
ERC721 ownerOf(1) 0xd8736582e8026be44d54ca0482ab90fc5640b90a

The reserve moved off its floor and token 1 has an owner, so the swaps and mints
ran rather than reverting.

It also validated the gas work against a real chain

This is the first run of #73 and #74 against arctic-1 rather than a state
override. The quotes match what I measured by injection, within a few hundred
gas:

scenario quoted live measured earlier
ERC20 175,242 175,097
ERC721 175,000 174,782
AMM 185,711 ~185,200

And the fee cap resolved from the chain: gas price 11000000000 wei, fee cap 55000000000 wei (x5.0) — where the constant this stack removed declared 20 gwei.

The test

TestLoadWithNoPathsReadsOnlyTheBinary asserted the compiled-in registry was
empty, and its failure message said what to do about it:

A chain file in registry/chains/ needs its own test naming it.

It has one. It pins the chain id, the genesis hash, all three addresses, and that
each entry carries a code hash for Verify to check. It also asserts a wrong
genesis hash does not match, because keying on the chain id alone is the failure
this file's whole design exists to prevent.

Three mutations, three caught: a changed address, a changed genesis hash, a
dropped contract.

Known, tracked

PLT-1107: ERC721's token counter restarts at 1 per process, so a second run
against this recorded contract mints into already-owned ids until it passes the
first run's high-water mark. That is throughput waste, not a failure — #73 prices
the scenario against a deliberately unminted id, so the limit still covers the
expensive shape.

PLT-1106: this bootstrap was a hand-shaped profile around a step that is not the
point. A deploy-only entrypoint would remove the four constraints an operator has
to get right at once.

🤖 Generated with Claude Code

Three contracts deployed to arctic-1 from a local run, so a deployment binds them
instead of deploying its own on every restart.

  defi-amm         0x225af59603bb554686adfbb2869af4cec12488a1
  tokenops-erc20   0xe66344c8ed6dbde610725cd7e3359b1fe4d7ff26
  tokenops-erc721  0x815299db5f8e3c6c42356655429cf2701e502bea

The run recorded 3 deployed and 0 copied unverified, so nothing here is carried
through from a file this run did not check.

Verified independently of the run that wrote it. Every address holds code, the
recorded hash is keccak256 of the code the chain serves, and that code is
byte-identical to what the pinned compiler produces locally. So these are our
contracts, not merely addresses that hold something.

Verified end to end: a binary carrying this file bound all three and sent no
deployment, and the transactions it then sent moved the contracts' state.
arctic-1's AMM reserve is no longer at its floor and its ERC721 token 1 has an
owner, so the transactions executed rather than reverting.

The tripwire test asserting an empty compiled-in registry said a chain file needs
its own test naming it. It has one. Three mutations, three caught: a changed
address, a changed genesis hash, a dropped contract.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@cursor

cursor Bot commented Aug 29, 2026

Copy link
Copy Markdown

PR Summary

Medium Risk
Wrong registry metadata would break startup binding/verification on arctic-1, and StorageRW gas limit changes affect whether read txs exhaust their limit under load.

Overview
This PR adds the first embedded chain registry entry for arctic-1 (chain id 713715), recording genesis hash and three pre-deployed contracts (defi-amm, tokenops-erc20, tokenops-erc721) so runs bind those addresses instead of redeploying. Registry tests now expect one compiled-in chain and pin id, genesis hash, addresses, and code-hash presence, including that lookups fail on a wrong genesis hash.

Separately, the StorageRW scenario adds fixed storageRWReadHeadroom (4,200 gas) on top of the priced model limit, because gas estimation cannot hit read’s expensive shape (cold slot read after a prior write). Headroom is a constant—not gasMargin—so a margin of 1 still covers that peak. Tests gain margin-aware pricing helpers and assert limits sit between model+cold read and model+4× cold read.

Reviewed by Cursor Bugbot for commit b0e54d4. Bugbot is set up for automated code reviews on this repo. Configure here.

@seidroid seidroid Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ships the first compiled-in chain file (arctic-1) plus a test naming its contents; the JSON parses and validates cleanly and the test pins the chain id, genesis hash, a negative genesis-hash match, the contract count, and the addresses. No blockers — the remaining notes are a vacuous code-hash assertion and documentation/profile text that the new file makes stale.

Findings: 0 blocking | 3 non-blocking | 1 posted inline

Blockers

  • None at the file/PR level.

Non-blocking

  • [suggestion] Several comments and docs now state the opposite of what the binary ships: registry/chains/README.md:7 ("This directory is empty of chain files today. No contracts exist on arctic-1, atlantic-2 or pacific-1 yet"), registry/registry.go:18-20 ("The directory ships empty"), registry/chains_test.go:16 (same claim, and its found == 0 log branch is now dead), and generator/registry_test.go:214 ("The compiled-in registry ships empty"). Worth updating in this PR so the first committed chain file doesn't leave four stale claims behind.
  • [suggestion] profiles/arctic-1.json still names chainId: 713715 with no genesisHash, so it cannot bind the addresses this PR records. Today it only runs EVMTransfer (no contract bindings, so no resolve happens), but adding any contract scenario to it will now fail at startup via the HasChainID guard in generator/prepare.go:250 — "the registry describes chain 713715 but this run names no genesisHash". Adding "genesisHash": "8ef5b0c0…bf035" to the profile now makes the recorded contracts usable and avoids a confusing first failure.
  • 1 suggestion(s)/nit(s) flagged inline on specific lines.

Comment thread registry/registry_test.go
if got := strings.ToLower(contract.Address.Hex()); got != address {
t.Errorf("%s is recorded at %s, want %s", name, got, address)
}
if contract.CodeHash == (common.Hash{}) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[suggestion] This assertion can never fire: Chain.validate already rejects a zero codeHash (registry/registry.go:185), so any chain file that reached Load without erroring has a non-zero hash on every contract. The test therefore pins the addresses but leaves the code hashes entirely unpinned — the one field whose corruption makes every cell reading this image fail Verify at startup can be edited without any test changing, which is the drift this test's doc comment says it exists to prevent.

Pin the exact hashes alongside the addresses, e.g. widen want to a struct:

want := map[string]struct{ address, codeHash string }{
	"defi-amm": {
		"0x225af59603bb554686adfbb2869af4cec12488a1",
		"0xd3b745d66f41b203732768f63c3f58a08be56a4c007d4ed88686d5230d7d54cd",
	},
	// ...
}

and compare strings.ToLower(contract.CodeHash.Hex()) against it.

Review found that StorageRW's doc claimed the margin absorbs the gap between its
priced calls and read's expensive shape. Settings.Validate accepts a GasMargin of
1, and at 1 nothing absorbs it: the first read of a written slot would land in a
block having burned its whole limit, which is the failure this sizing exists to
remove.

read costs most against a slot that already holds a value, and every call the
scenario prices reads an untouched one, so no measurement reaches that shape.
What the write and rmw models miss is a cold slot read, which EIP-2929 prices at
2,100 and which is not one of the costs Sei moves. The scenario adds twice that
as a constant, so correctness no longer depends on how an operator sets a knob.

Two mutations, two caught: the headroom removed, and the headroom shrunk below a
cold read.

Also moved gasProbeSlot out of CreateContractTransaction's doc comment. Inserting
it there left the function undocumented and turned a warning about that
function's PRNG draw order into commentary on a package-level big.Int.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant