Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ cd subgraphs/hcce && npm run test # Run subgraph tests
- `POST /getreport` - Serves reports after on-chain verification

4. **Smart Contracts** (`contracts/`) - On-chain coordination:
- `ProcessorEndpoint.sol` - Request handling, ERC-20 deposits/withdrawals (with EIP-2612 permit and facilitator path), per-app locked funds, deploy descriptor and allowed-deployer roles. Sits close to the EIP-170 size limit: state is declared in `ProcessorEndpointStorage.sol`, and the entry points that are off the per-request hot path (`submitRequestFor`, deploy submission, the operator resets, the admin setters) are hosted in `ProcessorEndpointExtension.sol` and reached by `delegatecall`. Read-only functions **cannot** move: a `view` function may not `delegatecall`, so they would need a generic `fallback()` and a merged ABI. Declare new state **only** in the storage base, and run `npm run check:layout` after touching it — a layout divergence corrupts storage silently instead of reverting, and an extension error the endpoint does not declare reaches clients as undecodable revert data. Deploy the extension first; its address is the endpoint's last constructor argument, readable afterwards via `extension()`. The compiler targets `evmVersion: 'cancun'` for the bytes, so deployment chains must be at Cancun or later. See `docs/design/PROCESSOR_ENDPOINT_SPLIT.md`
- `ProcessorEndpoint.sol` - Request handling, ERC-20 deposits/withdrawals (with EIP-2612 permit and facilitator path), per-app locked funds, deploy descriptor and allowed-deployer roles. Sits close to the EIP-170 size limit: state is declared in `ProcessorEndpointStorage.sol`, and the entry points that are off the per-request hot path (`submitRequestFor`, deploy submission, the operator resets, the admin setters) are hosted in `ProcessorEndpointExtension.sol` and reached by `delegatecall`. Read-only functions **cannot** move: a `view` function may not `delegatecall`, so they would need a generic `fallback()` and a merged ABI. Declare new state **only** in the storage base, and run `npm run check:layout` after touching it — a layout divergence corrupts storage silently instead of reverting, and an extension error the endpoint does not declare reaches clients as undecodable revert data. Deploy the extension first; its address is the endpoint's last constructor argument, readable afterwards via `extension()`. Changing the extension means deploying a new one and passing its address to `scripts/upgrade/processorEndpoint.ts` in `PROCESSOR_ENDPOINT_EXTENSION` — the script defaults to the extension the current implementation uses and refuses the upgrade when that no longer matches the local build, because reusing stale extension code does not revert. The compiler targets `evmVersion: 'cancun'` for the bytes, so deployment chains must be at Cancun or later. See `docs/design/PROCESSOR_ENDPOINT_SPLIT.md`
- `TeeAuthenticator.sol` - TEE attestation verification (PCR-based, including WASM fingerprint for deploys)
- `AuthorityRegistry.sol` - Authority management

Expand Down
38 changes: 28 additions & 10 deletions NOTICES
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,12 @@ orderedmap
Source: https://github.com/elliotchance/orderedmap
Notice: Copyright (c) 2020 Elliot Chance.

cbor
Version: v2.2.0
License: MIT
Source: https://github.com/fxamacker/cbor
Notice: Copyright (c) 2019-2023 Faye Amacker.

go-cmp
Version: v0.7.0
License: BSD-3-Clause
Expand Down Expand Up @@ -163,12 +169,6 @@ gogo/protobuf
Source: https://github.com/gogo/protobuf
Notice: Copyright (c) 2013 The GoGo Authors. Copyright 2010 The Go Authors.

cbor
Version: v2.2.0
License: MIT
Source: https://github.com/fxamacker/cbor
Notice: Copyright (c) 2019-2023 Faye Amacker.

golang/protobuf
Version: v1.5.4
License: BSD-3-Clause
Expand All @@ -192,29 +192,47 @@ PERMISSIVE DEPENDENCIES - npm (contracts/)
================================================================================

@openzeppelin/contracts
Version: ^5.3.0
Version: 5.6.1
License: MIT
Source: https://github.com/OpenZeppelin/openzeppelin-contracts
Notice: Copyright (c) 2016-2024 zOS Global Limited and contributors.

@openzeppelin/contracts-upgradeable
Version: 5.6.1
License: MIT
Source: https://github.com/OpenZeppelin/openzeppelin-contracts-upgradeable
Notice: Copyright (c) 2016-2024 zOS Global Limited and contributors.

dotenv
Version: ^17.0.0
Version: 17.0.0
License: BSD-2-Clause
Source: https://github.com/motdotla/dotenv
Notice: Copyright (c) 2015 Scott Motte.

hardhat (dev dependency)
Version: ^2.25.0
Version: 2.27.2
License: MIT
Source: https://github.com/NomicFoundation/hardhat
Notice: Copyright (c) 2019 Nomic Labs LLC.

@nomicfoundation/hardhat-toolbox (dev dependency)
Version: ^6.0.0
Version: 6.0.0
License: MIT
Source: https://github.com/NomicFoundation/hardhat
Notice: Copyright (c) 2022 Nomic Foundation.

@openzeppelin/hardhat-upgrades (dev dependency)
Version: 3.9.1
License: MIT
Source: https://github.com/OpenZeppelin/openzeppelin-upgrades
Notice: Copyright (c) OpenZeppelin.

hardhat-abi-exporter (dev dependency)
Version: 2.11.0
License: MIT
Source: https://github.com/ItsNickBarry/hardhat-abi-exporter
Notice: Copyright (c) Nick Barry.

================================================================================
PERMISSIVE DEPENDENCIES - npm (subgraphs/hcce/)
================================================================================
Expand Down
40 changes: 39 additions & 1 deletion contracts/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ Because that contract lives against the size limit, the compiler targets `evmVer

See `../docs/design/PROCESSOR_ENDPOINT_SPLIT.md`.

`ProcessorEndpoint`, `AuthorityRegistry` and `TeeAuthenticator` are all deployed behind a UUPS proxy (`ERC1967Proxy`), so the address printed by a deploy script — and the one every off-chain consumer should use — is the **proxy** address, not the implementation's. `NoAttestationTeeAuthenticator` (dev/test mode) is the one exception and is not upgradable. See `../docs/design/UPGRADABLE_CONTRACTS_DESIGN.md` for the full design and [Upgrading](#upgrading) below for how to push a new implementation.

## Manual deploy

- Create an .env file with the correct properties:
Expand All @@ -37,10 +39,46 @@ npx hardhat run scripts/deploy/all.ts
npx hardhat run scripts/deploy/authorityRegistry.ts
```

`scripts/deploy/processorEndpoint.ts` deploys `ProcessorEndpointExtension` together with the endpoint, in that order; there is no script that deploys the endpoint against a pre-existing extension.
`scripts/deploy/processorEndpoint.ts` deploys `ProcessorEndpointExtension` together with the endpoint, in that order; there is no script that deploys the endpoint against a pre-existing extension. `scripts/deploy/processorEndpointExtension.ts` deploys the extension alone, which is what an upgrade that changes it needs — see [Upgrading](#upgrading).

- when `DEPLOY_OUTPUT_DIR` is set, `all.ts` writes the deployed addresses to `deployed_addresses.env`, including `CHAIN_PROCESSOR_EXTENSION_ADDRESS`. Nothing reads that one at runtime, but it is the only record of which extension an endpoint delegates to.

## Upgrading

Each upgradable contract has a script in `scripts/upgrade/` that deploys a new implementation and points the existing proxy at it via `upgradeToAndCall`, keeping the proxy's address and all of its state (see `../docs/design/UPGRADABLE_CONTRACTS_DESIGN.md`). The caller must be the account holding the upgrade authority — `ADMIN` role for `ProcessorEndpoint`, `owner()` for `AuthorityRegistry` and `TeeAuthenticator`.

The scripts need more than the proxy address and the right signer. `upgradeProxy` validates the new implementation's storage layout against the deployed one, and it reads the deployed layout from the OpenZeppelin upgrades manifest — that layout is not recoverable from chain. **The manifest for the target network must be present and current before an upgrade script is run**, otherwise the upgrade aborts with `Deployment at address ... is not registered`.

- **Public networks** — the deploy writes the manifest to `.openzeppelin/`, and it **must be committed**, so the layout history is a repo artifact rather than a file on whoever happened to run the deploy. Without it an upgrade cannot be run from CI or by anyone else. The plugin names the file after the chain: `sepolia.json` for `sepolia-testnet`, and `unknown-<chainId>.json` for `horizen-l3-testnet`, whose chain id is not in the plugin's network-name map. That second name looks disposable but is the one worth keeping — never ignore `.openzeppelin/unknown-*`.
- **Local (`hardhat` / `anvil`)** — nothing lands in the repo. The manifest goes to `$TMPDIR/openzeppelin-upgrades/<hardhat|anvil>-31337-<instanceId>.json`, keyed by the node's instance id, so it does not survive an `anvil` restart even with `--state` persistence. A local upgrade after a restart needs the recovery below.
- **Recovery** — `upgrades.forceImport(proxyAddress, Factory, { kind: 'uups' })` re-registers a proxy whose manifest entry is lost. It takes the layout from whatever source is currently compiled, so it is only correct if that source matches what is actually deployed; importing against the wrong source yields a manifest that passes the layout check while the real layouts diverge.

- `AuthorityRegistry`:
- PROXY_AUTHORITY_REGISTRY: address of the deployed proxy
```bash
npx hardhat run scripts/upgrade/authorityRegistry.ts
```

- `TeeAuthenticator` (not applicable to `NoAttestationTeeAuthenticator`, which is not upgradable):
- PROXY_TEE_AUTHENTICATOR: address of the deployed proxy
```bash
npx hardhat run scripts/upgrade/teeAuthenticator.ts
```

- `ProcessorEndpoint`:
- PROXY_PROCESSOR_ENDPOINT: address of the deployed proxy
- PROCESSOR_ENDPOINT_EXTENSION (optional): address of the `ProcessorEndpointExtension` the new implementation's constructor should point at. Defaults to the extension the current implementation already uses — set this when the upgrade also changes the extension, after deploying the new one with `npx hardhat run scripts/deploy/processorEndpointExtension.ts`.
- ALLOW_STALE_EXTENSION (optional): set to `true` to downgrade the extension bytecode check below to a warning.
```bash
npx hardhat run scripts/upgrade/processorEndpoint.ts
```

Before upgrading, the script checks that the code deployed at whichever extension address it is using matches the locally compiled `ProcessorEndpointExtension`, and refuses the upgrade if it does not. **Changing `ProcessorEndpointExtension.sol` means deploying a new extension and passing its address**: the extension is a separate contract that the endpoint reaches by `delegatecall`, so an upgrade that reuses the old address leaves the new endpoint logic live while every delegated entry point (`submitRequestFor`, deploy submission, `initialize`, `invokeTrigger`, the operator resets, the admin setters) keeps running the old extension code against the proxy's storage — without reverting. Nothing else catches that: the endpoint's constructor only rejects an address with no code, `upgradeProxy` never inspects `constructorArgs`, and `npm run check:layout` compares compiled artifacts without touching the chain.

A compiler or optimiser-settings change alters the extension's bytecode even when its source is untouched, so the check also fires when the deployed extension is functionally the one intended. That is the case `ALLOW_STALE_EXTENSION=true` exists for — the message says whether the two differ in length (a different source) or only in content (a different build of it).

Each script prints the implementation address before and after the upgrade so it can be checked against what was intended to be deployed.

## Management scripts

The `scripts/management/` folder contains utilities to operate the deployed contracts from the admin account, for example:
Expand Down
22 changes: 19 additions & 3 deletions contracts/contracts/AuthorityRegistry.sol
Original file line number Diff line number Diff line change
@@ -1,27 +1,43 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.28;

import '@openzeppelin/contracts/access/Ownable.sol';
import '@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol';
import '@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol';
import './interfaces/IAuthorityChecker.sol';
import './interfaces/IAuthorityRegistry.sol';

/// @title AuthorityRegistry
/// @notice Registry for application-specific authority checkers.
contract AuthorityRegistry is Ownable, IAuthorityRegistry {
/// @dev Deployed behind a UUPS proxy (`docs/design/UPGRADABLE_CONTRACTS_DESIGN.md`).
contract AuthorityRegistry is OwnableUpgradeable, UUPSUpgradeable, IAuthorityRegistry {
// mapping appId -> custom authority contract
mapping(uint256 => IAuthorityChecker) public appAuthorityContracts;

// default authority contract (fallback)
IAuthorityChecker public defaultAuthorityContract;

/// @dev Reserved storage for future versions (see `docs/design/UPGRADABLE_CONTRACTS_DESIGN.md`).
/// Must be reduced by the number of slots any new variable added above it consumes, and
/// must always remain the last declaration in this contract.
uint256[50] private __gap;

/// @custom:oz-upgrades-unsafe-allow constructor
constructor() {
_disableInitializers();
}

/// @param owner Owner address.
/// @param defaultAuthority Default authority checker.
constructor(address owner, address defaultAuthority) Ownable(owner) {
function initialize(address owner, address defaultAuthority) external initializer {
__Ownable_init(owner);
if (defaultAuthority == address(0)) revert AddressCantBeZero();
defaultAuthorityContract = IAuthorityChecker(defaultAuthority);
emit DefaultAuthorityContractSet(defaultAuthority);
}

/// @inheritdoc UUPSUpgradeable
function _authorizeUpgrade(address newImplementation) internal override onlyOwner {}

/// @inheritdoc IAuthorityRegistry
function setAppAuthorityContract(
uint256 applicationId,
Expand Down
Loading
Loading