From 5b3dfe146b5a08a7840e8d5d459cd81abf0c2d49 Mon Sep 17 00:00:00 2001 From: Taylor Webb Date: Thu, 19 Mar 2026 20:21:48 -0600 Subject: [PATCH 1/9] SP1 & USP changes for Tron compatibility --- .gitignore | 9 ++++++++- contracts/SpokePool.sol | 4 ++-- contracts/sp1-helios/SP1Helios.sol | 21 ++++++++++----------- 3 files changed, 20 insertions(+), 14 deletions(-) diff --git a/.gitignore b/.gitignore index 67215b0ec..8be0333bf 100644 --- a/.gitignore +++ b/.gitignore @@ -30,11 +30,14 @@ artifacts-zk out out-local out-tron +out-tron-universal +out-tron-spokepool zkout cache-foundry cache-foundry-local cache-foundry-tron - +cache-foundry-tron-universal +cache-foundry-tron-spokepool # Upgradeability files .openzeppelin @@ -59,5 +62,9 @@ src/svm/clients/* script/universal/genesis-binary script/universal/genesis.json +# Tron solc binary and flattened sources +bin/solc-tron +flattened/ + # claude CLAUDE.local.md diff --git a/contracts/SpokePool.sol b/contracts/SpokePool.sol index e82974636..851f47fee 100644 --- a/contracts/SpokePool.sol +++ b/contracts/SpokePool.sol @@ -1545,7 +1545,7 @@ abstract contract SpokePool is // Unwraps ETH and does a transfer to a recipient address. If the recipient is a smart contract then sends wrappedNativeToken. function _unwrapwrappedNativeTokenTo(address payable to, uint256 amount) internal { - if (!address(to).isContract() || _is7702DelegatedWallet(to)) { + if (!AddressLibUpgradeable.isContract(address(to)) || _is7702DelegatedWallet(to)) { wrappedNativeToken.withdraw(amount); AddressLibUpgradeable.sendValue(to, amount); } else { @@ -1667,7 +1667,7 @@ abstract contract SpokePool is } bytes memory updatedMessage = relayExecution.updatedMessage; - if (updatedMessage.length > 0 && recipientToSend.isContract()) { + if (updatedMessage.length > 0 && AddressLibUpgradeable.isContract(recipientToSend)) { AcrossMessageHandler(recipientToSend).handleV3AcrossMessage( outputToken, amountToSend, diff --git a/contracts/sp1-helios/SP1Helios.sol b/contracts/sp1-helios/SP1Helios.sol index f8100ca50..307b3511d 100644 --- a/contracts/sp1-helios/SP1Helios.sol +++ b/contracts/sp1-helios/SP1Helios.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.30; +pragma solidity ^0.8.20; import { ISP1Verifier } from "@sp1-contracts/src/ISP1Verifier.sol"; import { AccessControlEnumerable } from "@sp1-contracts/lib/openzeppelin-contracts/contracts/access/extensions/AccessControlEnumerable.sol"; @@ -171,24 +171,23 @@ contract SP1Helios is AccessControlEnumerable { uint256 fromHead = po.prevHead; // Ensure that po.newHead is strictly greater than po.prevHead - require(po.newHead > fromHead, NonIncreasingHead(po.newHead)); + if (po.newHead <= fromHead) revert NonIncreasingHead(po.newHead); bytes32 storedPrevHeader = headers[fromHead]; - require(storedPrevHeader != bytes32(0), PreviousHeaderNotSet(fromHead)); - require(storedPrevHeader == po.prevHeader, PreviousHeaderMismatch(po.prevHeader, storedPrevHeader)); + if (storedPrevHeader == bytes32(0)) revert PreviousHeaderNotSet(fromHead); + if (storedPrevHeader != po.prevHeader) revert PreviousHeaderMismatch(po.prevHeader, storedPrevHeader); // Check if the head being proved against is older than allowed. - require(block.timestamp - slotTimestamp(fromHead) <= MAX_SLOT_AGE, PreviousHeadTooOld(fromHead)); + if (block.timestamp - slotTimestamp(fromHead) > MAX_SLOT_AGE) revert PreviousHeadTooOld(fromHead); uint256 currentPeriod = getSyncCommitteePeriod(fromHead); // Note: We should always have a sync committee for the current head. // The "start" sync committee hash is the hash of the sync committee that should sign the next update. bytes32 currentSyncCommitteeHash = syncCommittees[currentPeriod]; - require( - currentSyncCommitteeHash == po.startSyncCommitteeHash, - SyncCommitteeStartMismatch(po.startSyncCommitteeHash, currentSyncCommitteeHash) - ); + if (currentSyncCommitteeHash != po.startSyncCommitteeHash) { + revert SyncCommitteeStartMismatch(po.startSyncCommitteeHash, currentSyncCommitteeHash); + } // Verify the proof with the associated public values. This will revert if proof invalid. ISP1Verifier(verifier).verifyProof(heliosProgramVkey, publicValues, proof); @@ -241,7 +240,7 @@ contract SP1Helios is AccessControlEnumerable { // If the next sync committee is already correct, we don't need to update it. if (syncCommittees[nextPeriod] != po.nextSyncCommitteeHash) { - require(syncCommittees[nextPeriod] == bytes32(0), SyncCommitteeAlreadySet(nextPeriod)); + if (syncCommittees[nextPeriod] != bytes32(0)) revert SyncCommitteeAlreadySet(nextPeriod); syncCommittees[nextPeriod] = po.nextSyncCommitteeHash; emit SyncCommitteeUpdate(nextPeriod, po.nextSyncCommitteeHash); @@ -256,7 +255,7 @@ contract SP1Helios is AccessControlEnumerable { bytes32 oldHeliosProgramVkey = heliosProgramVkey; heliosProgramVkey = newHeliosProgramVkey; - require(oldHeliosProgramVkey != newHeliosProgramVkey, VkeyNotChanged(newHeliosProgramVkey)); + if (oldHeliosProgramVkey == newHeliosProgramVkey) revert VkeyNotChanged(newHeliosProgramVkey); emit HeliosProgramVkeyUpdated(oldHeliosProgramVkey, newHeliosProgramVkey); } From 30f4b1a5c21daf78163be429aff753f7bcc97d83 Mon Sep 17 00:00:00 2001 From: Taylor Webb Date: Fri, 20 Mar 2026 10:54:21 -0600 Subject: [PATCH 2/9] add auto verifier --- contracts/sp1-helios/SP1AutoVerifier.sol | 10 +++ test/evm/foundry/local/SP1AutoVerifier.t.sol | 76 ++++++++++++++++++++ 2 files changed, 86 insertions(+) create mode 100644 contracts/sp1-helios/SP1AutoVerifier.sol create mode 100644 test/evm/foundry/local/SP1AutoVerifier.t.sol diff --git a/contracts/sp1-helios/SP1AutoVerifier.sol b/contracts/sp1-helios/SP1AutoVerifier.sol new file mode 100644 index 000000000..8b3fac968 --- /dev/null +++ b/contracts/sp1-helios/SP1AutoVerifier.sol @@ -0,0 +1,10 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import { ISP1Verifier } from "@sp1-contracts/src/ISP1Verifier.sol"; + +/// @title SP1 Auto Verifier +/// @notice A no-op verifier that accepts any proof. Useful for testing SP1Helios without real proofs. +contract SP1AutoVerifier is ISP1Verifier { + function verifyProof(bytes32, bytes calldata, bytes calldata) external pure {} +} diff --git a/test/evm/foundry/local/SP1AutoVerifier.t.sol b/test/evm/foundry/local/SP1AutoVerifier.t.sol new file mode 100644 index 000000000..869fdfabb --- /dev/null +++ b/test/evm/foundry/local/SP1AutoVerifier.t.sol @@ -0,0 +1,76 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import { Test } from "forge-std/Test.sol"; +import { SP1Helios } from "../../../../contracts/sp1-helios/SP1Helios.sol"; +import { SP1AutoVerifier } from "../../../../contracts/sp1-helios/SP1AutoVerifier.sol"; + +contract SP1AutoVerifierTest is Test { + SP1AutoVerifier autoVerifier; + SP1Helios helios; + address updater = address(0x2); + + uint256 constant GENESIS_TIME = 1606824023; + uint256 constant SECONDS_PER_SLOT = 12; + uint256 constant SLOTS_PER_EPOCH = 32; + uint256 constant SLOTS_PER_PERIOD = 8192; + bytes32 constant INITIAL_HEADER = bytes32(uint256(2)); + bytes32 constant INITIAL_EXECUTION_STATE_ROOT = bytes32(uint256(3)); + bytes32 constant INITIAL_SYNC_COMMITTEE_HASH = bytes32(uint256(4)); + bytes32 constant HELIOS_PROGRAM_VKEY = bytes32(uint256(5)); + uint256 constant INITIAL_HEAD = 100; + + function setUp() public { + autoVerifier = new SP1AutoVerifier(); + + address[] memory updaters = new address[](1); + updaters[0] = updater; + + helios = new SP1Helios( + SP1Helios.InitParams({ + executionStateRoot: INITIAL_EXECUTION_STATE_ROOT, + genesisTime: GENESIS_TIME, + head: INITIAL_HEAD, + header: INITIAL_HEADER, + heliosProgramVkey: HELIOS_PROGRAM_VKEY, + secondsPerSlot: SECONDS_PER_SLOT, + slotsPerEpoch: SLOTS_PER_EPOCH, + slotsPerPeriod: SLOTS_PER_PERIOD, + syncCommitteeHash: INITIAL_SYNC_COMMITTEE_HASH, + verifier: address(autoVerifier), + vkeyUpdater: address(0), + updaters: updaters + }) + ); + } + + function testVerifyProofNeverReverts() public view { + autoVerifier.verifyProof(bytes32(0), "", ""); + autoVerifier.verifyProof(bytes32(uint256(1)), "abc", "def"); + autoVerifier.verifyProof(HELIOS_PROGRAM_VKEY, abi.encode(uint256(42)), hex"deadbeef"); + } + + function testHeliosUpdateWithNonEmptyProof() public { + SP1Helios.StorageSlot[] memory slots = new SP1Helios.StorageSlot[](0); + SP1Helios.ProofOutputs memory po = SP1Helios.ProofOutputs({ + executionStateRoot: bytes32(uint256(11)), + newHeader: bytes32(uint256(10)), + nextSyncCommitteeHash: bytes32(0), + newHead: INITIAL_HEAD + 1, + prevHeader: INITIAL_HEADER, + prevHead: INITIAL_HEAD, + syncCommitteeHash: INITIAL_SYNC_COMMITTEE_HASH, + startSyncCommitteeHash: INITIAL_SYNC_COMMITTEE_HASH, + slots: slots + }); + + vm.warp(helios.slotTimestamp(INITIAL_HEAD) + 1 hours); + vm.prank(updater); + // Unlike SP1MockVerifier, non-empty proof bytes are accepted. + // Note: the ProofVerified event is NOT emitted here because SP1Helios calls verifyProof + // via staticcall (ISP1Verifier is view), which prevents state changes including events. + helios.update(hex"deadbeef", abi.encode(po)); + + assertEq(helios.head(), INITIAL_HEAD + 1); + } +} From 2eb9ef751ec67cc1e268f7057c8e7fe8cbc6ce12 Mon Sep 17 00:00:00 2001 From: Taylor Webb Date: Fri, 20 Mar 2026 11:34:31 -0600 Subject: [PATCH 3/9] bump solidity versions to ^0.8.25 --- contracts/sp1-helios/SP1AutoVerifier.sol | 2 +- contracts/sp1-helios/SP1Helios.sol | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/contracts/sp1-helios/SP1AutoVerifier.sol b/contracts/sp1-helios/SP1AutoVerifier.sol index 8b3fac968..a77b7737b 100644 --- a/contracts/sp1-helios/SP1AutoVerifier.sol +++ b/contracts/sp1-helios/SP1AutoVerifier.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.20; +pragma solidity ^0.8.25; import { ISP1Verifier } from "@sp1-contracts/src/ISP1Verifier.sol"; diff --git a/contracts/sp1-helios/SP1Helios.sol b/contracts/sp1-helios/SP1Helios.sol index 307b3511d..ffdc1bcb1 100644 --- a/contracts/sp1-helios/SP1Helios.sol +++ b/contracts/sp1-helios/SP1Helios.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.20; +pragma solidity ^0.8.25; import { ISP1Verifier } from "@sp1-contracts/src/ISP1Verifier.sol"; import { AccessControlEnumerable } from "@sp1-contracts/lib/openzeppelin-contracts/contracts/access/extensions/AccessControlEnumerable.sol"; From 51f490ecdf1e199460f7ce87b361efd8bbe2caa5 Mon Sep 17 00:00:00 2001 From: Taylor Webb Date: Fri, 20 Mar 2026 14:12:40 -0600 Subject: [PATCH 4/9] add deploy scripts --- .gitignore | 2 + contracts/tron-universal/TronImports.sol | 9 + foundry.toml | 11 + package.json | 11 +- script/universal/tron/README.md | 149 +++++++++ script/universal/tron/deploy.ts | 306 ++++++++++++++++++ .../tron/tron-deploy-sp1-auto-verifier.ts | 38 +++ .../universal/tron/tron-deploy-sp1-helios.ts | 236 ++++++++++++++ .../tron/tron-deploy-universal-spokepool.ts | 101 ++++++ yarn.lock | 180 ++++++++++- 10 files changed, 1039 insertions(+), 4 deletions(-) create mode 100644 contracts/tron-universal/TronImports.sol create mode 100644 script/universal/tron/README.md create mode 100644 script/universal/tron/deploy.ts create mode 100644 script/universal/tron/tron-deploy-sp1-auto-verifier.ts create mode 100644 script/universal/tron/tron-deploy-sp1-helios.ts create mode 100644 script/universal/tron/tron-deploy-universal-spokepool.ts diff --git a/.gitignore b/.gitignore index 8be0333bf..6f887f429 100644 --- a/.gitignore +++ b/.gitignore @@ -61,6 +61,8 @@ src/svm/clients/* # SP1Helios deploy artifacts script/universal/genesis-binary script/universal/genesis.json +script/universal/tron/genesis-binary +script/universal/tron/genesis.json # Tron solc binary and flattened sources bin/solc-tron diff --git a/contracts/tron-universal/TronImports.sol b/contracts/tron-universal/TronImports.sol new file mode 100644 index 000000000..4bcaca6f4 --- /dev/null +++ b/contracts/tron-universal/TronImports.sol @@ -0,0 +1,9 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.0; + +// Entry point for the tron-universal Foundry profile. Importing these contracts here causes +// them (and their dependencies) to be compiled with Tron's solc (bin/solc-tron) and output +// to out-tron-universal/. +import "../sp1-helios/SP1Helios.sol"; +import "../sp1-helios/SP1AutoVerifier.sol"; +import "../Universal_SpokePool.sol"; diff --git a/foundry.toml b/foundry.toml index f270531b0..3597e910f 100644 --- a/foundry.toml +++ b/foundry.toml @@ -88,4 +88,15 @@ cache_path = "cache-foundry-tron" out = "out-tron" skip = ["sp1-helios"] +# Tron-compatible profile for Universal SpokePool and SP1Helios. +# Run with `FOUNDRY_PROFILE=tron-universal forge build` +[profile.tron-universal] +solc = "bin/solc-tron" +evm_version = "cancun" +src = "contracts/tron-universal" +test = "test/evm/foundry/tron" +script = "script/universal/tron" +cache_path = "cache-foundry-tron-universal" +out = "out-tron-universal" + # See more config options https://github.com/foundry-rs/foundry/blob/master/crates/config/README.md#all-options diff --git a/package.json b/package.json index a9ad3155e..80189b8df 100644 --- a/package.json +++ b/package.json @@ -46,7 +46,11 @@ "evm-contract-sizes": "forge build --sizes", "pre-commit-hook": "sh scripts/preCommitHook.sh", "extract-addresses": "./script/utils/extract_foundry_addresses.sh", - "generate-constants-json": "ts-node ./script/utils/GenerateConstantsJson.ts" + "generate-constants-json": "ts-node ./script/utils/GenerateConstantsJson.ts", + "build-tron-universal": "FOUNDRY_PROFILE=tron-universal forge build", + "tron-deploy-sp1-auto-verifier": "ts-node script/universal/tron/tron-deploy-sp1-auto-verifier.ts", + "tron-deploy-sp1-helios": "ts-node script/universal/tron/tron-deploy-sp1-helios.ts", + "tron-deploy-universal-spokepool": "ts-node script/universal/tron/tron-deploy-universal-spokepool.ts" }, "dependencies": { "@across-protocol/constants": "^3.1.100", @@ -69,6 +73,7 @@ "@uma/common": "^2.37.3", "@uma/contracts-node": "^0.4.17", "bs58": "^6.0.0", + "tronweb": "^6.2.0", "yargs": "^17.7.2", "zksync-web3": "^0.14.3" }, @@ -85,6 +90,7 @@ "@types/node": "^24.3.0", "@typescript-eslint/eslint-plugin": "^4.29.1", "@typescript-eslint/parser": "^4.29.1", + "chai": "^4.3.6", "codama": "^1.3.0", "dotenv": "^10.0.0", "eslint": "^7.29.0", @@ -94,17 +100,16 @@ "eslint-plugin-node": "^11.1.0", "eslint-plugin-prettier": "^3.4.0", "eslint-plugin-promise": "^5.1.0", - "chai": "^4.3.6", "ethereumjs-util": "^7.1.4", "ethers": "5.7.2", "husky": "^4.2.3", + "mocha": "^10.0.0", "multiformats": "9.9.0", "prettier": "^3.8.1", "prettier-plugin-rust": "^0.1.9", "prettier-plugin-solidity": "^2.2.1", "pretty-quick": "^4.2.2", "solhint": "^3.6.2", - "mocha": "^10.0.0", "ts-mocha": "^10.0.0", "ts-node": "^10.1.0", "typescript": "^5.6.2" diff --git a/script/universal/tron/README.md b/script/universal/tron/README.md new file mode 100644 index 000000000..461429f82 --- /dev/null +++ b/script/universal/tron/README.md @@ -0,0 +1,149 @@ +# Tron Deployment Scripts + +TypeScript scripts for deploying contracts to Tron via TronWeb. Tron uses a protobuf transaction format (not RLP) and SHA-256 signing (not keccak256), so Foundry's `forge script --broadcast` cannot deploy directly — these scripts handle that. + +## Prerequisites + +### 1. Tron solc binary + +Download Tron's custom solc 0.8.25 from [tronprotocol/solidity releases](https://github.com/tronprotocol/solidity/releases) and place it at `bin/solc-tron`: + +```bash +# macOS (Apple Silicon) +curl -L -o bin/solc-tron https://github.com/nicetip/solidity/releases/download/tv0.8.25/solc_macos +chmod +x bin/solc-tron + +# Linux (amd64) +curl -L -o bin/solc-tron https://github.com/nicetip/solidity/releases/download/tv0.8.25/solc_linux +chmod +x bin/solc-tron +``` + +Verify: + +```bash +bin/solc-tron --version +# solc.tron, the solidity compiler commandline interface +# Version: 0.8.25+commit.77bd169f... +``` + +### 2. Build contracts + +```bash +yarn build-tron-universal +``` + +This runs `FOUNDRY_PROFILE=tron-universal forge build`, which compiles using Tron's solc (`bin/solc-tron`) and outputs Foundry artifacts to `out-tron-universal/`. + +### 3. Environment variables + +Create a `.env` file (or `source .env` before running): + +```bash +# Required for all deployments +MNEMONIC="your twelve word mnemonic phrase here" +NODE_URL_728126428=https://api.trongrid.io # Tron mainnet +NODE_URL_3448148188=https://nile.trongrid.io # Tron Nile testnet + +# Optional +TRON_FEE_LIMIT=1500000000 # Fee limit in sun (default: 1500 TRX) +``` + +> **Important:** Use the base TronGrid URL (`https://api.trongrid.io`), NOT the `/jsonrpc` endpoint. TronWeb needs the native Tron API, not the Ethereum-compatible JSON-RPC endpoint. + +## Deploy Scripts + +### SP1AutoVerifier + +No-op verifier for testing SP1Helios without real ZK proofs. No constructor args. + +```bash +yarn tron-deploy-sp1-auto-verifier + +# Example: deploy to Nile testnet +yarn tron-deploy-sp1-auto-verifier 3448148188 +``` + +### SP1Helios + +Ethereum beacon chain light client. Downloads a genesis binary to generate initial state, then deploys. + +Additional env vars: + +```bash +SP1_RELEASE_TRON=0.1.0-alpha.20 # Genesis binary version +SP1_PROVER_MODE_TRON=network # "mock", "cpu", "cuda", or "network" +SP1_VERIFIER_ADDRESS_TRON=T... # SP1 verifier gateway (Base58Check) +SP1_STATE_UPDATERS_TRON=T...,T... # Comma-separated updater addresses +SP1_VKEY_UPDATER_TRON=T... # VKey updater address +SP1_CONSENSUS_RPCS_LIST_TRON=https://... # Comma-separated beacon chain RPC URLs +``` + +```bash +yarn tron-deploy-sp1-helios +``` + +> **Warning:** Once SP1Helios is deployed, you have 7 days to deploy the Universal_SpokePool and activate it in-protocol. After 7 days with no update, the contract becomes immutable. + +### Universal_SpokePool + +Deploys the implementation contract (not the proxy). Must be wrapped in a UUPS proxy and initialized separately. + +Additional env vars: + +```bash +USP_ADMIN_UPDATE_BUFFER=86400 # Admin update buffer (seconds), e.g. 24h +USP_HELIOS_ADDRESS=T... # Deployed SP1Helios address +USP_HUB_POOL_STORE_ADDRESS=T... # HubPoolStore address +USP_WRAPPED_NATIVE_TOKEN_ADDRESS=T... # WTRX address +USP_DEPOSIT_QUOTE_TIME_BUFFER=3600 # Deposit quote time buffer (seconds) +USP_FILL_DEADLINE_BUFFER=21600 # Fill deadline buffer (seconds) +USP_L2_USDC_ADDRESS=T... # USDC on Tron +USP_CCTP_TOKEN_MESSENGER_ADDRESS=T... # CCTP TokenMessenger, or zero address +USP_OFT_DST_EID=0 # LayerZero OFT endpoint ID, 0 to disable +USP_OFT_FEE_CAP=0 # OFT fee cap in wei, 0 to disable +``` + +```bash +yarn tron-deploy-universal-spokepool +``` + +## Deployment order + +1. **SP1AutoVerifier** (testnet only) or wait for Succinct to deploy the real Groth16 verifier +2. **SP1Helios** — needs the verifier address +3. **Universal_SpokePool** — needs the SP1Helios address + +## Verifying contracts on TronScan + +TronScan requires a single flattened Solidity file for verification. Use `forge flatten`: + +```bash +# Flatten a contract +forge flatten contracts/sp1-helios/SP1Helios.sol > flattened/SP1Helios.sol +forge flatten contracts/sp1-helios/SP1AutoVerifier.sol > flattened/SP1AutoVerifier.sol +forge flatten contracts/Universal_SpokePool.sol > flattened/Universal_SpokePool.sol +``` + +Then verify on TronScan: + +1. Go to the contract page on [TronScan](https://tronscan.org) (or [Nile TronScan](https://nile.tronscan.org) for testnet) +2. Click **Contract** → **Verify and Publish** +3. Select **Solidity (Single file)** +4. Set compiler version to **0.8.25** (must match Tron's solc) +5. Set EVM version to **cancun** +6. Set optimization to **Yes**, runs **800**, via-ir **enabled** +7. Paste the flattened source +8. If the contract has constructor args, provide the ABI-encoded args (logged during deployment as `Constructor args: 0x...`) + +> **Tip:** The `flattened/` directory is gitignored. Regenerate flattened sources from the current contract code before verifying. + +## Broadcast artifacts + +Each deployment writes a Foundry-compatible broadcast artifact to `broadcast/TronDeploy.s.sol//`. These track deployed addresses and transaction IDs. + +## Chain IDs + +| Network | Chain ID | +| ------------ | ---------- | +| Tron Mainnet | 728126428 | +| Tron Nile | 3448148188 | diff --git a/script/universal/tron/deploy.ts b/script/universal/tron/deploy.ts new file mode 100644 index 000000000..82beeef78 --- /dev/null +++ b/script/universal/tron/deploy.ts @@ -0,0 +1,306 @@ +#!/usr/bin/env ts-node +/** + * Shared TronWeb deployer for Tron contract deployments. + * + * Import from per-contract wrapper scripts: + * import { deployContract } from "./deploy"; + * await deployContract({ chainId, artifactPath, encodedArgs }); + * + * Env vars: + * MNEMONIC — BIP-39 mnemonic (derives account 0 private key) + * NODE_URL_728126428 — Tron mainnet full node URL + * NODE_URL_3448148188 — Tron Nile testnet full node URL + * TRON_FEE_LIMIT — optional, in sun (default: 1500000000 = 1500 TRX) + */ + +import "dotenv/config"; +import * as fs from "fs"; +import * as path from "path"; +import { TronWeb } from "tronweb"; + +const POLL_INTERVAL_MS = 3000; +const MAX_POLL_ATTEMPTS = 40; // ~2 minutes + +const TRONSCAN_URLS: Record = { + "728126428": "https://tronscan.org", + "3448148188": "https://nile.tronscan.org", +}; + +export interface DeployResult { + address: string; // Tron Base58 (T...) + txID: string; +} + +/** ABI-encode constructor args. Wrapper around TronWeb's ABI encoder for use by deploy scripts. */ +export function encodeArgs(types: string[], values: any[]): string { + // Lightweight TronWeb instance for ABI encoding only (no network calls). + const tw = new TronWeb({ fullHost: "http://localhost" }); + return tw.utils.abi.encodeParams(types, values); +} + +/** Convert a Tron Base58Check address (T...) to a 0x-prefixed 20-byte EVM hex address. */ +export function tronToEvmAddress(base58: string): string { + const hex = TronWeb.address.toHex(base58); + // TronWeb returns 41-prefixed hex (e.g. "41abc..."). Strip the 41 prefix to get the 20-byte address. + // Guard against alternate formats: if it starts with "0x41", strip 4 chars; if "41", strip 2. + if (hex.startsWith("0x41") && hex.length === 44) return "0x" + hex.slice(4); + if (hex.startsWith("41") && hex.length === 42) return "0x" + hex.slice(2); + throw new Error(`Unexpected TronWeb hex address format: ${hex}`); +} + +/** Decode ABI-encoded constructor args into human-readable strings for the broadcast `arguments` field. */ +function decodeConstructorArgs(tronWeb: TronWeb, abi: any[], parameterHex: string): string[] | null { + const ctor = abi.find((e: any) => e.type === "constructor"); + if (!ctor || !ctor.inputs?.length || !parameterHex) return null; + try { + const types = ctor.inputs.map((i: any) => i.type); + const names = ctor.inputs.map((i: any) => i.name); + const decoded = tronWeb.utils.abi.decodeParams(names, types, "0x" + parameterHex, false); + return ctor.inputs.map((input: any) => { + const val = decoded[input.name]; + // TronWeb returns addresses as 41-prefixed hex; convert to Base58Check (T...) for TronScan. + if (input.type === "address" && typeof val === "string" && val.startsWith("41") && val.length === 42) { + return tronWeb.address.fromHex(val); + } + return val.toString(); + }); + } catch { + return null; + } +} + +/** Write a Foundry-compatible broadcast artifact to broadcast///. */ +function writeBroadcastArtifact(opts: { + tronWeb: TronWeb; + contractName: string; + contractAddress: string; + txID: string; + chainId: string; + deployerAddress: string; + bytecode: string; + parameter: string | undefined; + abi: any[]; + feeLimit: number; + txInfo: any; +}): void { + // Use TronDeploy.s.sol as the directory name for consistency with existing broadcast artifacts. + const scriptName = `TronDeploy${opts.contractName}.s.sol`; + const chainIdNum = parseInt(opts.chainId, 10); + const now = Date.now(); + const txHash = opts.txID; + const initcode = `0x${opts.bytecode}${opts.parameter || ""}`; + const blockNum = opts.txInfo.blockNumber ? "0x" + opts.txInfo.blockNumber.toString(16) : "0x0"; + const energyUsed = opts.txInfo.receipt?.energy_usage_total; + const gasUsed = energyUsed ? "0x" + energyUsed.toString(16) : "0x0"; + + const broadcast = { + transactions: [ + { + hash: txHash, + transactionType: "CREATE", + contractName: opts.contractName, + contractAddress: opts.contractAddress, + function: null, + arguments: decodeConstructorArgs(opts.tronWeb, opts.abi, opts.parameter || ""), + transaction: { + from: opts.deployerAddress, + gas: "0x" + opts.feeLimit.toString(16), + value: "0x0", + input: initcode, + chainId: "0x" + chainIdNum.toString(16), + }, + additionalContracts: [], + }, + ], + receipts: [ + { + status: "0x1", + cumulativeGasUsed: gasUsed, + transactionHash: txHash, + blockHash: null, // TRON doesn't expose blockHash via getTransactionInfo + blockNumber: blockNum, + gasUsed, + from: opts.deployerAddress, + to: null, + contractAddress: opts.contractAddress, + }, + ], + libraries: [], + pending: [], + returns: {}, + timestamp: now, + chain: chainIdNum, + }; + + const broadcastDir = path.resolve(__dirname, "../../../broadcast", scriptName, opts.chainId); + fs.mkdirSync(broadcastDir, { recursive: true }); + + const json = JSON.stringify(broadcast, null, 2) + "\n"; + const runFile = path.join(broadcastDir, `run-${now}.json`); + const latestFile = path.join(broadcastDir, "run-latest.json"); + + fs.writeFileSync(runFile, json); + fs.writeFileSync(latestFile, json); + console.log(` Broadcast: ${runFile}`); +} + +/** + * Deploy a contract to Tron via TronWeb. + * + * @param opts.chainId Tron chain ID (728126428 for mainnet, 3448148188 for Nile testnet) + * @param opts.artifactPath Path to the Foundry-compiled artifact JSON + * @param opts.encodedArgs Optional ABI-encoded constructor args (0x-prefixed hex) + * @returns Deployed contract addresses and transaction ID + */ +export async function deployContract(opts: { + chainId: string; + artifactPath: string; + encodedArgs?: string; +}): Promise { + const { chainId, artifactPath, encodedArgs } = opts; + + const TRON_CHAIN_IDS = ["728126428", "3448148188"]; // mainnet, Nile testnet + if (!TRON_CHAIN_IDS.includes(chainId)) { + console.log(`Error: invalid chain ID "${chainId}". Use 728126428 (Tron mainnet) or 3448148188 (Nile testnet).`); + process.exit(1); + } + + const mnemonic = process.env.MNEMONIC; + const fullNode = process.env[`NODE_URL_${chainId}`]; + if (!mnemonic) { + console.log("Error: MNEMONIC env var is required."); + process.exit(1); + } + if (!fullNode) { + console.log(`Error: NODE_URL_${chainId} env var is required (Tron full node URL).`); + process.exit(1); + } + + const feeLimit = parseInt(process.env.TRON_FEE_LIMIT || "1500000000", 10); + + // Create a TronWeb instance (private key set below after mnemonic derivation). + const tronWeb = new TronWeb({ fullHost: fullNode }); + + // Derive account 0 private key from mnemonic (same derivation as Foundry's vm.deriveKey(mnemonic, 0)). + // We use Ethereum's HD path (m/44'/60'/0'/0/0) — NOT Tron's default (m/44'/195'/0'/0/0) — because + // the deployer key must match the one Foundry derives. TronWeb.fromMnemonic() enforces Tron's path, + // so we use the bundled ethers HDNodeWallet directly to derive with the Ethereum path. + const { ethersHDNodeWallet, Mnemonic } = tronWeb.utils.ethersUtils; + const mnemonicObj = Mnemonic.fromPhrase(mnemonic); + const wallet = ethersHDNodeWallet.fromMnemonic(mnemonicObj, "m/44'/60'/0'/0/0"); + const privateKey = wallet.privateKey.slice(2); + tronWeb.setPrivateKey(privateKey); + const deployerAddressRaw = tronWeb.address.fromPrivateKey(privateKey); + if (typeof deployerAddressRaw !== "string") { + console.log("Error: could not derive deployer address from private key."); + process.exit(1); + } + const deployerAddress = deployerAddressRaw; + + // Read the Foundry-compiled artifact to get the ABI and bytecode. + if (!fs.existsSync(artifactPath)) { + console.log(`Error: artifact not found at ${artifactPath}. Run "forge build" first.`); + process.exit(1); + } + const artifact = JSON.parse(fs.readFileSync(artifactPath, "utf-8")); + const abi = artifact.abi; + let bytecode: string = artifact.bytecode?.object || artifact.bytecode; + if (typeof bytecode === "string" && bytecode.startsWith("0x")) { + bytecode = bytecode.slice(2); + } + if (!abi || !bytecode) { + console.log("Error: artifact missing abi or bytecode."); + process.exit(1); + } + + // Extract contract name from artifact filename (e.g. "SP1Helios" from ".../SP1Helios.json"). + const contractName = path.basename(artifactPath, ".json"); + + // Strip 0x prefix from encoded args if provided (TronWeb expects raw hex). + let parameter: string | undefined; + if (encodedArgs) { + parameter = encodedArgs.startsWith("0x") ? encodedArgs.slice(2) : encodedArgs; + } + + console.log(`Deploying ${contractName} to ${fullNode}...`); + if (parameter) console.log(` Constructor args: 0x${parameter}`); + console.log(` Fee limit: ${feeLimit} sun (${feeLimit / 1e6} TRX)`); + + // Build the CreateSmartContract transaction via TronWeb. + const txOptions = { + abi, + bytecode, + name: contractName, + feeLimit, + ...(parameter ? { parameters: [] as unknown[], rawParameter: parameter } : {}), + }; + + const tx = await tronWeb.transactionBuilder.createSmartContract(txOptions); + + // Sign the transaction (SHA-256 + secp256k1, not keccak256 like Ethereum). + const signedTx = await tronWeb.trx.sign(tx); + + // Broadcast the signed transaction to the Tron network. + const result = await tronWeb.trx.sendRawTransaction(signedTx); + + if (!result.result) { + console.log("Error: transaction rejected:", JSON.stringify(result, null, 2)); + process.exit(1); + } + + const txID: string = result.transaction?.txID; + console.log(`Transaction sent: ${txID}`); + + // Poll for confirmation — Tron doesn't return receipts synchronously. + let txInfo: any; + for (let i = 0; i < MAX_POLL_ATTEMPTS; i++) { + await new Promise((r) => setTimeout(r, POLL_INTERVAL_MS)); + txInfo = await tronWeb.trx.getTransactionInfo(txID); + if (txInfo && txInfo.id) break; + console.log(`Waiting for confirmation... (${i + 1}/${MAX_POLL_ATTEMPTS})`); + } + + if (!txInfo || !txInfo.id) { + console.log("Error: transaction not confirmed within timeout."); + process.exit(1); + } + + if (txInfo.receipt?.result !== "SUCCESS") { + console.log("Error: transaction failed:", JSON.stringify(txInfo, null, 2)); + process.exit(1); + } + + // Extract contract address from transaction info (Tron returns hex with 41 prefix). + const tronHexAddress: string = txInfo.contract_address; + if (!tronHexAddress) { + console.log("Error: no contract_address in transaction info."); + process.exit(1); + } + + const contractAddress = tronWeb.address.fromHex(tronHexAddress); + const tronscanBase = TRONSCAN_URLS[chainId] || "https://tronscan.org"; + + console.log(`\nContract deployed!`); + console.log(` Address: ${contractAddress}`); + console.log(` TX ID: ${txID}`); + console.log(` Tronscan: ${tronscanBase}/#/contract/${contractAddress}`); + + writeBroadcastArtifact({ + tronWeb, + contractName, + contractAddress, + txID, + chainId, + deployerAddress, + bytecode, + parameter, + abi, + feeLimit, + txInfo, + }); + + return { + address: contractAddress, + txID, + }; +} diff --git a/script/universal/tron/tron-deploy-sp1-auto-verifier.ts b/script/universal/tron/tron-deploy-sp1-auto-verifier.ts new file mode 100644 index 000000000..ce918c96c --- /dev/null +++ b/script/universal/tron/tron-deploy-sp1-auto-verifier.ts @@ -0,0 +1,38 @@ +#!/usr/bin/env ts-node +/** + * Deploys SP1AutoVerifier to Tron via TronWeb. + * + * SP1AutoVerifier has no constructor args — it's a no-op verifier for testing. + * + * Env vars: + * MNEMONIC — BIP-39 mnemonic (derives account 0 private key) + * NODE_URL_728126428 — Tron mainnet full node URL + * NODE_URL_3448148188 — Tron Nile testnet full node URL + * TRON_FEE_LIMIT — optional, in sun (default: 1500000000 = 1500 TRX) + * + * Usage: + * yarn tron-deploy-sp1-auto-verifier + */ + +import * as path from "path"; +import { deployContract } from "./deploy"; + +async function main(): Promise { + const chainId = process.argv[2]; + if (!chainId) { + console.log("Usage: yarn tron-deploy-sp1-auto-verifier "); + process.exit(1); + } + + console.log("=== SP1AutoVerifier Tron Deployment ==="); + console.log(`Chain ID: ${chainId}`); + + const artifactPath = path.resolve(__dirname, "../../../out-tron-universal/SP1AutoVerifier.sol/SP1AutoVerifier.json"); + + await deployContract({ chainId, artifactPath }); +} + +main().catch((err) => { + console.log("Fatal error:", err.message || err); + process.exit(1); +}); diff --git a/script/universal/tron/tron-deploy-sp1-helios.ts b/script/universal/tron/tron-deploy-sp1-helios.ts new file mode 100644 index 000000000..35e421d52 --- /dev/null +++ b/script/universal/tron/tron-deploy-sp1-helios.ts @@ -0,0 +1,236 @@ +#!/usr/bin/env ts-node +/** + * Deploys SP1Helios to Tron via TronWeb. + * + * Steps: + * 1. Download the genesis binary from GitHub releases (platform-aware) + * 2. Verify the binary's SHA-256 checksum against checksums.json + * 3. Run the genesis binary to generate/update genesis.json + * 4. Read genesis.json and ABI-encode the SP1Helios constructor args + * 5. Deploy SP1Helios to Tron + * + * Env vars: + * MNEMONIC — BIP-39 mnemonic (derives account 0 private key) + * NODE_URL_728126428 — Tron mainnet full node URL + * NODE_URL_3448148188 — Tron Nile testnet full node URL + * TRON_FEE_LIMIT — optional, in sun (default: 1500000000 = 1500 TRX) + * SP1_RELEASE_TRON — Genesis binary version (e.g. "0.1.0-alpha.20") + * SP1_PROVER_MODE_TRON — SP1 prover type: "mock", "cpu", "cuda", or "network" + * SP1_VERIFIER_ADDRESS_TRON — SP1 verifier contract address (Tron Base58Check, T...) + * SP1_STATE_UPDATERS_TRON — Comma-separated list of state updater addresses (Tron Base58Check) + * SP1_VKEY_UPDATER_TRON — VKey updater address (Tron Base58Check, T...) + * SP1_CONSENSUS_RPCS_LIST_TRON — Comma-separated list of consensus RPC URLs + * + * Usage: + * yarn tron-deploy-sp1-helios + */ + +import "dotenv/config"; +import * as fs from "fs"; +import * as path from "path"; +import * as os from "os"; +import { execSync } from "child_process"; +import { createHash } from "crypto"; +import { deployContract, encodeArgs, tronToEvmAddress } from "./deploy"; +import { TronWeb as TronWebImport } from "tronweb"; + +const GITHUB_RELEASE_URL = "https://github.com/across-protocol/sp1-helios/releases"; +const SCRIPT_DIR = path.resolve(__dirname); + +function requireEnv(name: string): string { + const value = process.env[name]; + if (!value) { + console.log(`Error: ${name} env var is required.`); + process.exit(1); + } + return value; +} + +/** Detect platform and return the binary suffix. */ +function detectPlatform(): string { + const platform = os.platform(); + if (platform === "darwin") { + console.log("Detected platform: macOS (arm64_darwin)"); + return "arm64_darwin"; + } else { + console.log("Detected platform: Linux (amd64_linux)"); + return "amd64_linux"; + } +} + +/** Download the genesis binary from GitHub releases. */ +function downloadGenesisBinary(version: string): string { + const platformSuffix = detectPlatform(); + const binaryName = `genesis_${version}_${platformSuffix}`; + const downloadUrl = `${GITHUB_RELEASE_URL}/download/v${version}/${binaryName}`; + const binaryPath = path.join(SCRIPT_DIR, "genesis-binary"); + + console.log(`Binary name: ${binaryName}`); + console.log(`Download URL: ${downloadUrl}`); + console.log("Downloading genesis binary..."); + + execSync(`curl -L -o "${binaryPath}" --fail "${downloadUrl}"`, { stdio: "inherit" }); + console.log("Download complete"); + + verifyBinaryChecksum(binaryName, binaryPath); + fs.chmodSync(binaryPath, 0o755); + + return binaryPath; +} + +/** Verify the downloaded binary's SHA-256 checksum against checksums.json. */ +function verifyBinaryChecksum(binaryName: string, binaryPath: string): void { + console.log("Verifying binary checksum..."); + + const checksumsPath = path.resolve(SCRIPT_DIR, "../checksums.json"); + const checksums: Record = JSON.parse(fs.readFileSync(checksumsPath, "utf-8")); + + const expectedChecksum = checksums[binaryName]; + if (!expectedChecksum) { + console.log(`Error: no checksum found for binary: ${binaryName}`); + console.log("Please add the checksum to script/universal/checksums.json"); + process.exit(1); + } + console.log(`Expected checksum: ${expectedChecksum}`); + + const fileBuffer = fs.readFileSync(binaryPath); + const actualChecksum = createHash("sha256").update(fileBuffer).digest("hex"); + console.log(`Actual checksum: ${actualChecksum}`); + + if (expectedChecksum !== actualChecksum) { + console.log("Error: checksum mismatch! Possible tampering detected."); + process.exit(1); + } + console.log("Checksum verified successfully"); +} + +/** Run the genesis binary to generate/update genesis.json. */ +function runGenesisBinary(binaryPath: string, sp1Prover: string, privateKeyHex: string): void { + const sp1VerifierAddress = requireEnv("SP1_VERIFIER_ADDRESS_TRON"); + const stateUpdaters = requireEnv("SP1_STATE_UPDATERS_TRON"); + const vkeyUpdater = requireEnv("SP1_VKEY_UPDATER_TRON"); + const consensusRpcsList = requireEnv("SP1_CONSENSUS_RPCS_LIST_TRON"); + + console.log(`SP1_VERIFIER_ADDRESS: ${sp1VerifierAddress}`); + console.log(`STATE_UPDATERS: ${stateUpdaters}`); + console.log(`VKEY_UPDATER: ${vkeyUpdater}`); + console.log(`CONSENSUS_RPCS_LIST: ${consensusRpcsList}`); + + // The genesis binary expects EVM-format (0x) addresses. Convert Tron Base58Check addresses. + const verifierEvm = tronToEvmAddress(sp1VerifierAddress); + const vkeyUpdaterEvm = tronToEvmAddress(vkeyUpdater); + const updatersEvm = stateUpdaters + .split(",") + .map((a) => tronToEvmAddress(a.trim())) + .join(","); + + console.log("Running genesis binary..."); + execSync(`"${binaryPath}" --out "${SCRIPT_DIR}"`, { + stdio: "inherit", + env: { + ...process.env, + SOURCE_CHAIN_ID: "1", + SP1_PROVER: sp1Prover, + PRIVATE_KEY: privateKeyHex, + SP1_VERIFIER_ADDRESS: verifierEvm, + UPDATERS: updatersEvm, + VKEY_UPDATER: vkeyUpdaterEvm, + CONSENSUS_RPCS_LIST: consensusRpcsList, + }, + }); + console.log("Genesis config updated successfully"); +} + +/** Read genesis.json and return ABI-encoded constructor args for SP1Helios. */ +function readGenesisAndEncode(): string { + const genesisPath = path.join(SCRIPT_DIR, "genesis.json"); + if (!fs.existsSync(genesisPath)) { + console.log(`Error: genesis.json not found at ${genesisPath}`); + process.exit(1); + } + + const genesis = JSON.parse(fs.readFileSync(genesisPath, "utf-8")); + console.log("\n=== Genesis Config ==="); + console.log(` executionStateRoot: ${genesis.executionStateRoot}`); + console.log(` genesisTime: ${genesis.genesisTime}`); + console.log(` head: ${genesis.head}`); + console.log(` header: ${genesis.header}`); + console.log(` heliosProgramVkey: ${genesis.heliosProgramVkey}`); + console.log(` secondsPerSlot: ${genesis.secondsPerSlot}`); + console.log(` slotsPerEpoch: ${genesis.slotsPerEpoch}`); + console.log(` slotsPerPeriod: ${genesis.slotsPerPeriod}`); + console.log(` syncCommitteeHash: ${genesis.syncCommitteeHash}`); + console.log(` verifier: ${genesis.verifier}`); + console.log(` vkeyUpdater: ${genesis.vkeyUpdater}`); + console.log(` updaters: ${JSON.stringify(genesis.updaters)}`); + + // The SP1Helios constructor takes a single InitParams struct as a tuple. + return encodeArgs( + ["(bytes32,uint256,uint256,bytes32,bytes32,uint256,uint256,uint256,bytes32,address,address,address[])"], + [ + [ + genesis.executionStateRoot, + genesis.genesisTime, + genesis.head, + genesis.header, + genesis.heliosProgramVkey, + genesis.secondsPerSlot, + genesis.slotsPerEpoch, + genesis.slotsPerPeriod, + genesis.syncCommitteeHash, + genesis.verifier, + genesis.vkeyUpdater, + genesis.updaters, + ], + ] + ); +} + +async function main(): Promise { + const chainId = process.argv[2]; + if (!chainId) { + console.log("Usage: yarn tron-deploy-sp1-helios "); + process.exit(1); + } + + const version = requireEnv("SP1_RELEASE_TRON"); + const sp1Prover = requireEnv("SP1_PROVER_MODE_TRON"); + const mnemonic = requireEnv("MNEMONIC"); + + console.log("=== SP1Helios Tron Deployment ==="); + console.log(`Version: ${version}`); + console.log(`SP1 Prover: ${sp1Prover}`); + console.log(`Chain ID: ${chainId}`); + + // Derive private key (Ethereum HD path to match Foundry) for the genesis binary. + const tw = new TronWebImport({ fullHost: "http://localhost" }); + const { ethersHDNodeWallet, Mnemonic } = tw.utils.ethersUtils; + const mnemonicObj = Mnemonic.fromPhrase(mnemonic); + const wallet = ethersHDNodeWallet.fromMnemonic(mnemonicObj, "m/44'/60'/0'/0/0"); + const privateKeyHex = wallet.privateKey; // 0x-prefixed + + // Step 1-2: Download and verify genesis binary + const binaryPath = downloadGenesisBinary(version); + + // Step 3: Run genesis binary to generate genesis.json + runGenesisBinary(binaryPath, sp1Prover, privateKeyHex); + + // Step 4: Read genesis.json and encode constructor args + const encodedArgs = readGenesisAndEncode(); + + // Step 5: Deploy SP1Helios + console.log("\n=== WARNING ==="); + console.log("Once SP1Helios is deployed, you have 7 days to deploy the UniversalSpokePool"); + console.log("and activate it in-protocol. After 7 days with no update, the contract becomes"); + console.log("immutable and cannot be updated."); + console.log("================\n"); + + const artifactPath = path.resolve(__dirname, "../../../out-tron-universal/SP1Helios.sol/SP1Helios.json"); + + await deployContract({ chainId, artifactPath, encodedArgs }); +} + +main().catch((err) => { + console.log("Fatal error:", err.message || err); + process.exit(1); +}); diff --git a/script/universal/tron/tron-deploy-universal-spokepool.ts b/script/universal/tron/tron-deploy-universal-spokepool.ts new file mode 100644 index 000000000..36421fd54 --- /dev/null +++ b/script/universal/tron/tron-deploy-universal-spokepool.ts @@ -0,0 +1,101 @@ +#!/usr/bin/env ts-node +/** + * Deploys Universal_SpokePool to Tron via TronWeb. + * + * This deploys the implementation contract only — it must be wrapped in a UUPS proxy + * and initialized separately. + * + * Env vars: + * MNEMONIC — BIP-39 mnemonic (derives account 0 private key) + * NODE_URL_728126428 — Tron mainnet full node URL + * NODE_URL_3448148188 — Tron Nile testnet full node URL + * TRON_FEE_LIMIT — optional, in sun (default: 1500000000 = 1500 TRX) + * USP_ADMIN_UPDATE_BUFFER — Admin update buffer in seconds (e.g. 86400 = 24h) + * USP_HELIOS_ADDRESS — SP1Helios contract address (Tron Base58Check, T...) + * USP_HUB_POOL_STORE_ADDRESS — HubPoolStore contract address (Tron Base58Check, T...) + * USP_WRAPPED_NATIVE_TOKEN_ADDRESS — Wrapped native token (WTRX) address (Tron Base58Check, T...) + * USP_DEPOSIT_QUOTE_TIME_BUFFER — Deposit quote time buffer in seconds + * USP_FILL_DEADLINE_BUFFER — Fill deadline buffer in seconds + * USP_L2_USDC_ADDRESS — USDC token address on Tron (Tron Base58Check, T...) + * USP_CCTP_TOKEN_MESSENGER_ADDRESS — CCTP TokenMessenger address (Tron Base58Check, T...), or zero address to disable + * USP_OFT_DST_EID — LayerZero OFT destination endpoint ID (0 to disable) + * USP_OFT_FEE_CAP — LayerZero OFT fee cap in wei (0 to disable) + * + * Usage: + * yarn tron-deploy-universal-spokepool + */ + +import "dotenv/config"; +import * as path from "path"; +import { deployContract, encodeArgs, tronToEvmAddress } from "./deploy"; + +function requireEnv(name: string): string { + const value = process.env[name]; + if (!value) { + console.log(`Error: ${name} env var is required.`); + process.exit(1); + } + return value; +} + +async function main(): Promise { + const chainId = process.argv[2]; + if (!chainId) { + console.log("Usage: yarn tron-deploy-universal-spokepool "); + process.exit(1); + } + + console.log("=== Universal_SpokePool Tron Deployment ==="); + console.log(`Chain ID: ${chainId}`); + + const adminUpdateBuffer = requireEnv("USP_ADMIN_UPDATE_BUFFER"); + const heliosAddress = tronToEvmAddress(requireEnv("USP_HELIOS_ADDRESS")); + const hubPoolStoreAddress = tronToEvmAddress(requireEnv("USP_HUB_POOL_STORE_ADDRESS")); + const wrappedNativeToken = tronToEvmAddress(requireEnv("USP_WRAPPED_NATIVE_TOKEN_ADDRESS")); + const depositQuoteTimeBuffer = requireEnv("USP_DEPOSIT_QUOTE_TIME_BUFFER"); + const fillDeadlineBuffer = requireEnv("USP_FILL_DEADLINE_BUFFER"); + const l2Usdc = tronToEvmAddress(requireEnv("USP_L2_USDC_ADDRESS")); + const cctpTokenMessenger = tronToEvmAddress(requireEnv("USP_CCTP_TOKEN_MESSENGER_ADDRESS")); + const oftDstEid = requireEnv("USP_OFT_DST_EID"); + const oftFeeCap = requireEnv("USP_OFT_FEE_CAP"); + + console.log(` Admin update buffer: ${adminUpdateBuffer}s`); + console.log(` Helios: ${heliosAddress}`); + console.log(` HubPoolStore: ${hubPoolStoreAddress}`); + console.log(` Wrapped native token: ${wrappedNativeToken}`); + console.log(` Deposit quote buffer: ${depositQuoteTimeBuffer}s`); + console.log(` Fill deadline buffer: ${fillDeadlineBuffer}s`); + console.log(` L2 USDC: ${l2Usdc}`); + console.log(` CCTP TokenMessenger: ${cctpTokenMessenger}`); + console.log(` OFT dst EID: ${oftDstEid}`); + console.log(` OFT fee cap: ${oftFeeCap}`); + + // Constructor: (uint256, address, address, address, uint32, uint32, IERC20, ITokenMessenger, uint32, uint256) + const encodedArgs = encodeArgs( + ["uint256", "address", "address", "address", "uint32", "uint32", "address", "address", "uint32", "uint256"], + [ + adminUpdateBuffer, + heliosAddress, + hubPoolStoreAddress, + wrappedNativeToken, + depositQuoteTimeBuffer, + fillDeadlineBuffer, + l2Usdc, + cctpTokenMessenger, + oftDstEid, + oftFeeCap, + ] + ); + + const artifactPath = path.resolve( + __dirname, + "../../../out-tron-universal/Universal_SpokePool.sol/Universal_SpokePool.json" + ); + + await deployContract({ chainId, artifactPath, encodedArgs }); +} + +main().catch((err) => { + console.log("Fatal error:", err.message || err); + process.exit(1); +}); diff --git a/yarn.lock b/yarn.lock index 34151a4c5..3fe59c076 100644 --- a/yarn.lock +++ b/yarn.lock @@ -16,6 +16,11 @@ "@openzeppelin/contracts" "4.1.0" "@uma/core" "^2.18.0" +"@adraffy/ens-normalize@1.10.1": + version "1.10.1" + resolved "https://registry.yarnpkg.com/@adraffy/ens-normalize/-/ens-normalize-1.10.1.tgz#63430d04bd8c5e74f8d7d049338f1cd9d4f02069" + integrity sha512-96Z2IP3mYmF1Xg2cDm8f1gWGf/HUVedQ3FMifV4kG/PQ4yEP51xDtRAEfhVNt5f/uzpNkZHwWQuUcu6D6K+Ekw== + "@adraffy/ens-normalize@^1.11.0": version "1.11.1" resolved "https://registry.yarnpkg.com/@adraffy/ens-normalize/-/ens-normalize-1.11.1.tgz#6c2d657d4b2dfb37f8ea811dcb3e60843d4ac24a" @@ -188,6 +193,13 @@ dependencies: regenerator-runtime "^0.13.11" +"@babel/runtime@7.26.10": + version "7.26.10" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.26.10.tgz#a07b4d8fa27af131a633d7b3524db803eb4764c2" + integrity sha512-2WJMeRQPHKSPemqk/awGrAiuFfzBmOIPXKizAsVhWH9YJqLZ0H+HS4c8loHGgW6utJ3E/ejXQUsiGaQy2NZ9Fw== + dependencies: + regenerator-runtime "^0.14.0" + "@babel/runtime@^7.25.0": version "7.27.0" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.27.0.tgz#fbee7cf97c709518ecc1f590984481d5460d4762" @@ -1510,6 +1522,20 @@ resolved "https://registry.yarnpkg.com/@noble/ciphers/-/ciphers-1.3.0.tgz#f64b8ff886c240e644e5573c097f86e5b43676dc" integrity sha512-2I0gnIVPtfnMw9ee9h1dJG7tp81+8Ob3OJb3Mv37rx5L40/b0i7djjCVvGOVqc9AEIQyvyu1i6ypKdFw8R8gQw== +"@noble/curves@1.2.0": + version "1.2.0" + resolved "https://registry.yarnpkg.com/@noble/curves/-/curves-1.2.0.tgz#92d7e12e4e49b23105a2555c6984d41733d65c35" + integrity sha512-oYclrNgRaM9SsBUBVbb8M6DTV7ZHRTKugureoYEncY5c65HOmRzvSiTE3y5CYaPYJA/GVkrhXEoF0M3Ya9PMnw== + dependencies: + "@noble/hashes" "1.3.2" + +"@noble/curves@1.4.2", "@noble/curves@~1.4.0": + version "1.4.2" + resolved "https://registry.yarnpkg.com/@noble/curves/-/curves-1.4.2.tgz#40309198c76ed71bc6dbf7ba24e81ceb4d0d1fe9" + integrity sha512-TavHr8qycMChk8UwMld0ZDRvatedkzWfH8IiaeGCfymOP5i0hSCozz9vHOL0nkwk7HRMlFnAiKpS2jrUmSybcw== + dependencies: + "@noble/hashes" "1.4.0" + "@noble/curves@1.9.1": version "1.9.1" resolved "https://registry.yarnpkg.com/@noble/curves/-/curves-1.9.1.tgz#9654a0bc6c13420ae252ddcf975eaf0f58f0a35c" @@ -1531,6 +1557,16 @@ dependencies: "@noble/hashes" "1.8.0" +"@noble/hashes@1.3.2": + version "1.3.2" + resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.3.2.tgz#6f26dbc8fbc7205873ce3cee2f690eba0d421b39" + integrity sha512-MVC8EAQp7MvEcm30KWENFjgR+Mkmf+D189XJTkFIlwohU5hcBbn1ZkKq7KVTi2Hme3PMGF390DaL52beVrIihQ== + +"@noble/hashes@1.4.0", "@noble/hashes@~1.4.0": + version "1.4.0" + resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.4.0.tgz#45814aa329f30e4fe0ba49426f49dfccdd066426" + integrity sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg== + "@noble/hashes@1.8.0", "@noble/hashes@^1.3.1", "@noble/hashes@^1.4.0", "@noble/hashes@^1.8.0", "@noble/hashes@~1.8.0": version "1.8.0" resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.8.0.tgz#cee43d801fcef9644b11b8194857695acd5f815a" @@ -1757,11 +1793,25 @@ resolved "https://registry.yarnpkg.com/@scroll-tech/contracts/-/contracts-0.1.0.tgz#ccea8db1b3df7d740e4b7843ac01b5bd25b4438b" integrity sha512-aBbDOc3WB/WveZdpJYcrfvMYMz7ZTEiW8M9XMJLba8p9FAR5KGYB/cV+8+EUsq3MKt7C1BfR+WnXoTVdvwIY6w== +"@scure/base@~1.1.6": + version "1.1.9" + resolved "https://registry.yarnpkg.com/@scure/base/-/base-1.1.9.tgz#e5e142fbbfe251091f9c5f1dd4c834ac04c3dbd1" + integrity sha512-8YKhl8GHiNI/pU2VMaofa2Tor7PJRAjwQLBBuilkJ9L5+13yVbC7JO/wS7piioAvPSwR3JKM1IJ/u4xQzbcXKg== + "@scure/base@~1.2.5": version "1.2.6" resolved "https://registry.yarnpkg.com/@scure/base/-/base-1.2.6.tgz#ca917184b8231394dd8847509c67a0be522e59f6" integrity sha512-g/nm5FgUa//MCj1gV09zTJTaM6KBAHqLN907YVQqf7zC49+DcO4B1so4ZX07Ef10Twr6nuqYEH9GEggFXA4Fmg== +"@scure/bip32@1.4.0": + version "1.4.0" + resolved "https://registry.yarnpkg.com/@scure/bip32/-/bip32-1.4.0.tgz#4e1f1e196abedcef395b33b9674a042524e20d67" + integrity sha512-sVUpc0Vq3tXCkDGYVWGIZTRfnvu8LoTDaev7vbwh0omSvVORONr960MQWdKqJDCReIEmTj3PAr73O3aoxz7OPg== + dependencies: + "@noble/curves" "~1.4.0" + "@noble/hashes" "~1.4.0" + "@scure/base" "~1.1.6" + "@scure/bip32@1.7.0", "@scure/bip32@^1.7.0": version "1.7.0" resolved "https://registry.yarnpkg.com/@scure/bip32/-/bip32-1.7.0.tgz#b8683bab172369f988f1589640e53c4606984219" @@ -1771,6 +1821,14 @@ "@noble/hashes" "~1.8.0" "@scure/base" "~1.2.5" +"@scure/bip39@1.3.0": + version "1.3.0" + resolved "https://registry.yarnpkg.com/@scure/bip39/-/bip39-1.3.0.tgz#0f258c16823ddd00739461ac31398b4e7d6a18c3" + integrity sha512-disdg7gHuTDZtY+ZdkmLpPCk7fxZSu3gBiEGuoC1XYxv9cGx3Z6cpTggCgW6odSOOIXCiDjuGejW+aJKCY/pIQ== + dependencies: + "@noble/hashes" "~1.4.0" + "@scure/base" "~1.1.6" + "@scure/bip39@1.6.0", "@scure/bip39@^1.6.0": version "1.6.0" resolved "https://registry.yarnpkg.com/@scure/bip39/-/bip39-1.6.0.tgz#475970ace440d7be87a6086cbee77cb8f1a684f9" @@ -2873,6 +2931,13 @@ dependencies: undici-types "~6.21.0" +"@types/node@22.7.5": + version "22.7.5" + resolved "https://registry.yarnpkg.com/@types/node/-/node-22.7.5.tgz#cfde981727a7ab3611a481510b473ae54442b92b" + integrity sha512-jML7s2NAzMWc//QSJ1a3prpk78cOPchGvXJsC3C6R6PSMoooztvRVQEz89gmBTBY1SPMaqo5teB4uNHPdetShQ== + dependencies: + undici-types "~6.19.2" + "@types/node@>=12.12.47", "@types/node@>=13.7.0": version "17.0.21" resolved "https://registry.yarnpkg.com/@types/node/-/node-17.0.21.tgz#864b987c0c68d07b4345845c3e63b75edd143644" @@ -3320,6 +3385,11 @@ aes-js@3.0.0: resolved "https://registry.yarnpkg.com/aes-js/-/aes-js-3.0.0.tgz#e21df10ad6c2053295bcbb8dab40b09dbea87e4d" integrity sha512-H7wUZRn8WpTq9jocdxQ2c8x2sKo9ZVmzfRE13GiNJXfp7NcKYEdvl3vspKjXox6RIG2VtaRe4JFvxG4rqp2Zuw== +aes-js@4.0.0-beta.5: + version "4.0.0-beta.5" + resolved "https://registry.yarnpkg.com/aes-js/-/aes-js-4.0.0-beta.5.tgz#8d2452c52adedebc3a3e28465d858c11ca315873" + integrity sha512-G965FqalsNyrPqgEGON7nIx1e/OVENSgiEIzyC63haUMuvNnwIgIjMs52hlTCKhkBny7A2ORNlfY9Zu+jmGk1Q== + aes-js@^3.1.2: version "3.1.2" resolved "https://registry.yarnpkg.com/aes-js/-/aes-js-3.1.2.tgz#db9aabde85d5caabbfc0d4f2a4446960f627146a" @@ -3633,6 +3703,15 @@ aws4@^1.8.0: resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.11.0.tgz#d61f46d83b2519250e2784daf5b09479a8b41c59" integrity sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA== +axios@1.13.5: + version "1.13.5" + resolved "https://registry.yarnpkg.com/axios/-/axios-1.13.5.tgz#5e464688fa127e11a660a2c49441c009f6567a43" + integrity sha512-cz4ur7Vb0xS4/KUN0tPWe44eqxrIu31me+fbang3ijiNscE129POzipJJA6zniq2C/Z6sJCjMimjS8Lc/GAs8Q== + dependencies: + follow-redirects "^1.15.11" + form-data "^4.0.5" + proxy-from-env "^1.1.0" + axios@^0.21.1: version "0.21.4" resolved "https://registry.yarnpkg.com/axios/-/axios-0.21.4.tgz#c67b90dc0568e5c1cf2b0b858c43ba28e2eda575" @@ -3753,6 +3832,11 @@ bignumber.js@7.2.1, bignumber.js@^7.2.1: resolved "https://registry.yarnpkg.com/bignumber.js/-/bignumber.js-7.2.1.tgz#80c048759d826800807c4bfd521e50edbba57a5f" integrity sha512-S4XzBk5sMB+Rcb/LNcpzXr57VRTxgAvaAEDAl1AwRx27j00hT84O6OkteE7u8UB3NuaaygCRrEpqox4uDOrbdQ== +bignumber.js@9.1.2: + version "9.1.2" + resolved "https://registry.yarnpkg.com/bignumber.js/-/bignumber.js-9.1.2.tgz#b7c4242259c008903b13707983b5f4bbd31eda0c" + integrity sha512-2/mKyZH9K85bzOEfhXDBFZTGd1CTs+5IHpeFQo9luiBG7hghdC851Pj2WAhb6E3R6b9tZj/XKhbg4fum+Kepug== + bignumber.js@^8.0.1: version "8.1.1" resolved "https://registry.yarnpkg.com/bignumber.js/-/bignumber.js-8.1.1.tgz#4b072ae5aea9c20f6730e4e5d529df1271c4d885" @@ -5527,6 +5611,16 @@ es-object-atoms@^1.0.0, es-object-atoms@^1.1.1: dependencies: es-errors "^1.3.0" +es-set-tostringtag@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz#f31dbbe0c183b00a6d26eb6325c810c0fd18bd4d" + integrity sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA== + dependencies: + es-errors "^1.3.0" + get-intrinsic "^1.2.6" + has-tostringtag "^1.0.2" + hasown "^2.0.2" + es-to-primitive@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.2.1.tgz#e55cd4c9cdc188bcefb03b366c736323fc5c898a" @@ -6000,6 +6094,16 @@ ethereum-common@^0.0.18: resolved "https://registry.yarnpkg.com/ethereum-common/-/ethereum-common-0.0.18.tgz#2fdc3576f232903358976eb39da783213ff9523f" integrity sha1-L9w1dvIykDNYl26znaeDIT/5Uj8= +ethereum-cryptography@2.2.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/ethereum-cryptography/-/ethereum-cryptography-2.2.1.tgz#58f2810f8e020aecb97de8c8c76147600b0b8ccf" + integrity sha512-r/W8lkHSiTLxUxW8Rf3u4HGB0xQweG2RyETjywylKZSzLWoWAijRz8WCuOtJ6wah+avllXBqZuk29HCCvhEIRg== + dependencies: + "@noble/curves" "1.4.2" + "@noble/hashes" "1.4.0" + "@scure/bip32" "1.4.0" + "@scure/bip39" "1.3.0" + ethereum-cryptography@^0.1.3: version "0.1.3" resolved "https://registry.yarnpkg.com/ethereum-cryptography/-/ethereum-cryptography-0.1.3.tgz#8d6143cfc3d74bf79bbd8edecdf29e4ae20dd191" @@ -6213,6 +6317,19 @@ ethers@5.7.2, ethers@^5.4.2, ethers@^5.7.1: "@ethersproject/web" "5.7.1" "@ethersproject/wordlists" "5.7.0" +ethers@6.13.5: + version "6.13.5" + resolved "https://registry.yarnpkg.com/ethers/-/ethers-6.13.5.tgz#8c1d6ac988ac08abc3c1d8fabbd4b8b602851ac4" + integrity sha512-+knKNieu5EKRThQJWwqaJ10a6HE9sSehGeqWN65//wE7j47ZpFhKAnHB/JJFibwwg61I/koxaPsXbXpD/skNOQ== + dependencies: + "@adraffy/ens-normalize" "1.10.1" + "@noble/curves" "1.2.0" + "@noble/hashes" "1.3.2" + "@types/node" "22.7.5" + aes-js "4.0.0-beta.5" + tslib "2.7.0" + ws "8.17.1" + ethers@^4.0.32, ethers@^4.0.40: version "4.0.49" resolved "https://registry.yarnpkg.com/ethers/-/ethers-4.0.49.tgz#0eb0e9161a0c8b4761be547396bbe2fb121a8894" @@ -6602,6 +6719,11 @@ follow-redirects@^1.14.0, follow-redirects@^1.15.6: resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.6.tgz#7f815c0cda4249c74ff09e95ef97c23b5fd0399b" integrity sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA== +follow-redirects@^1.15.11: + version "1.15.11" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.11.tgz#777d73d72a92f8ec4d2e410eb47352a56b8e8340" + integrity sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ== + for-each@^0.3.5: version "0.3.5" resolved "https://registry.yarnpkg.com/for-each/-/for-each-0.3.5.tgz#d650688027826920feeb0af747ee7b9421a41d47" @@ -6651,6 +6773,17 @@ form-data@^4.0.0: combined-stream "^1.0.8" mime-types "^2.1.12" +form-data@^4.0.5: + version "4.0.5" + resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.5.tgz#b49e48858045ff4cbf6b03e1805cebcad3679053" + integrity sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w== + dependencies: + asynckit "^0.4.0" + combined-stream "^1.0.8" + es-set-tostringtag "^2.1.0" + hasown "^2.0.2" + mime-types "^2.1.12" + form-data@~2.3.2: version "2.3.3" resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.3.3.tgz#dcce52c05f644f298c6a7ab936bd724ceffbf3a6" @@ -6832,7 +6965,7 @@ get-intrinsic@^1.2.4: has-symbols "^1.0.3" hasown "^2.0.0" -get-intrinsic@^1.3.0: +get-intrinsic@^1.2.6, get-intrinsic@^1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.3.0.tgz#743f0e3b6964a93a5491ed1bffaae054d7f98d01" integrity sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ== @@ -7098,6 +7231,11 @@ google-p12-pem@^4.0.0: dependencies: node-forge "^1.3.1" +google-protobuf@3.21.4: + version "3.21.4" + resolved "https://registry.yarnpkg.com/google-protobuf/-/google-protobuf-3.21.4.tgz#2f933e8b6e5e9f8edde66b7be0024b68f77da6c9" + integrity sha512-MnG7N936zcKTco4Jd2PX2U96Kf9PxygAPKBug+74LHzmHXmceN16MmRcdgZv+DGef/S9YvQAfRsNCn4cjf9yyQ== + gopd@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/gopd/-/gopd-1.0.1.tgz#29ff76de69dac7489b7c0918a5788e56477c332c" @@ -11107,6 +11245,11 @@ semver@7.5.2: dependencies: lru-cache "^6.0.0" +semver@7.7.1: + version "7.7.1" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.7.1.tgz#abd5098d82b18c6c81f6074ff2647fd3e7220c9f" + integrity sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA== + semver@^6.1.0, semver@^6.1.1, semver@^6.1.2, semver@^6.3.0: version "6.3.0" resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" @@ -11989,6 +12132,21 @@ triple-beam@^1.3.0: resolved "https://registry.yarnpkg.com/triple-beam/-/triple-beam-1.3.0.tgz#a595214c7298db8339eeeee083e4d10bd8cb8dd9" integrity sha512-XrHUvV5HpdLmIj4uVMxHggLbFSZYIn7HEWsqePZcI50pco+MPqJ50wMGY794X7AOOhxOBAjbkqfAbEe/QMp2Lw== +tronweb@^6.2.0: + version "6.2.2" + resolved "https://registry.yarnpkg.com/tronweb/-/tronweb-6.2.2.tgz#84e3e89b903d76b5becfdbd0c451f7169abaf1c1" + integrity sha512-jRBf4+7fJ0HUVzveBi0tE21r3EygCNtbYE92T38Sxlwr/x320W2vz+dvGLOIpp4kW/CvJ4HLvtnb6U30A0V2eA== + dependencies: + "@babel/runtime" "7.26.10" + axios "1.13.5" + bignumber.js "9.1.2" + ethereum-cryptography "2.2.1" + ethers "6.13.5" + eventemitter3 "5.0.1" + google-protobuf "3.21.4" + semver "7.7.1" + validator "13.15.23" + truffle-deploy-registry@^0.5.1: version "0.5.1" resolved "https://registry.yarnpkg.com/truffle-deploy-registry/-/truffle-deploy-registry-0.5.1.tgz#1d9ea967c3b16cdacaf5c310b124e24e4c641d8a" @@ -12061,6 +12219,11 @@ tsconfig-paths@^3.5.0: minimist "^1.2.6" strip-bom "^3.0.0" +tslib@2.7.0: + version "2.7.0" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.7.0.tgz#d9b40c5c40ab59e8738f297df3087bf1a2690c01" + integrity sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA== + tslib@^1.8.1, tslib@^1.9.0: version "1.14.1" resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" @@ -12250,6 +12413,11 @@ undici-types@^7.18.2: resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-7.18.2.tgz#29357a89e7b7ca4aef3bf0fd3fd0cd73884229e9" integrity sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w== +undici-types@~6.19.2: + version "6.19.8" + resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-6.19.8.tgz#35111c9d1437ab83a7cdc0abae2f26d88eda0a02" + integrity sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw== + undici-types@~6.21.0: version "6.21.0" resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-6.21.0.tgz#691d00af3909be93a7faa13be61b3a5b50ef12cb" @@ -12436,6 +12604,11 @@ validate-npm-package-license@^3.0.1: spdx-correct "^3.0.0" spdx-expression-parse "^3.0.0" +validator@13.15.23: + version "13.15.23" + resolved "https://registry.yarnpkg.com/validator/-/validator-13.15.23.tgz#59a874f84e4594588e3409ab1edbe64e96d0c62d" + integrity sha512-4yoz1kEWqUjzi5zsPbAS/903QXSYp0UOtHsPpp7p9rHAw/W+dkInskAE386Fat3oKRROwO98d9ZB0G4cObgUyw== + varint@^5.0.0, varint@^5.0.2, varint@~5.0.0: version "5.0.2" resolved "https://registry.yarnpkg.com/varint/-/varint-5.0.2.tgz#5b47f8a947eb668b848e034dcfa87d0ff8a7f7a4" @@ -13593,6 +13766,11 @@ ws@7.4.6: resolved "https://registry.yarnpkg.com/ws/-/ws-7.4.6.tgz#5654ca8ecdeee47c33a9a4bf6d28e2be2980377c" integrity sha512-YmhHDO4MzaDLB+M9ym/mDA5z0naX8j7SIlT8f8z+I0VtzsRbekxEutHSme7NPS2qE8StCYQNUnfWdXta/Yu85A== +ws@8.17.1: + version "8.17.1" + resolved "https://registry.yarnpkg.com/ws/-/ws-8.17.1.tgz#9293da530bb548febc95371d90f9c878727d919b" + integrity sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ== + ws@8.18.3: version "8.18.3" resolved "https://registry.yarnpkg.com/ws/-/ws-8.18.3.tgz#b56b88abffde62791c639170400c93dcb0c95472" From 4965edfd05834ba94321bd7ad4926dde0dc9f237 Mon Sep 17 00:00:00 2001 From: Taylor Webb Date: Mon, 23 Mar 2026 11:07:59 -0600 Subject: [PATCH 5/9] consolidate to single foundry profile for Tron --- .gitignore | 4 ---- contracts/tron-universal/TronImports.sol | 9 -------- contracts/tron/TronCounterfactualImports.sol | 13 +++++++++++ contracts/tron/TronImports.sol | 8 +++++++ foundry.toml | 22 +++++-------------- package.json | 2 +- script/universal/tron/README.md | 10 +++++---- .../tron/tron-deploy-sp1-auto-verifier.ts | 2 +- .../universal/tron/tron-deploy-sp1-helios.ts | 2 +- .../tron/tron-deploy-universal-spokepool.ts | 5 +---- 10 files changed, 36 insertions(+), 41 deletions(-) delete mode 100644 contracts/tron-universal/TronImports.sol create mode 100644 contracts/tron/TronCounterfactualImports.sol create mode 100644 contracts/tron/TronImports.sol diff --git a/.gitignore b/.gitignore index 6f887f429..c1f3dc3a6 100644 --- a/.gitignore +++ b/.gitignore @@ -30,14 +30,10 @@ artifacts-zk out out-local out-tron -out-tron-universal -out-tron-spokepool zkout cache-foundry cache-foundry-local cache-foundry-tron -cache-foundry-tron-universal -cache-foundry-tron-spokepool # Upgradeability files .openzeppelin diff --git a/contracts/tron-universal/TronImports.sol b/contracts/tron-universal/TronImports.sol deleted file mode 100644 index 4bcaca6f4..000000000 --- a/contracts/tron-universal/TronImports.sol +++ /dev/null @@ -1,9 +0,0 @@ -// SPDX-License-Identifier: BUSL-1.1 -pragma solidity ^0.8.0; - -// Entry point for the tron-universal Foundry profile. Importing these contracts here causes -// them (and their dependencies) to be compiled with Tron's solc (bin/solc-tron) and output -// to out-tron-universal/. -import "../sp1-helios/SP1Helios.sol"; -import "../sp1-helios/SP1AutoVerifier.sol"; -import "../Universal_SpokePool.sol"; diff --git a/contracts/tron/TronCounterfactualImports.sol b/contracts/tron/TronCounterfactualImports.sol new file mode 100644 index 000000000..94b1f276c --- /dev/null +++ b/contracts/tron/TronCounterfactualImports.sol @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.0; + +// Entry point for counterfactual contracts in the tron Foundry profile. These use OZ v4 and must +// be in a separate file from SP1Helios/UniversalSpokePool (OZ v5) to avoid name collisions. +import "../periphery/counterfactual/AdminWithdrawManager.sol"; +import "../periphery/counterfactual/CounterfactualConstants.sol"; +import "../periphery/counterfactual/CounterfactualDeposit.sol"; +import "../periphery/counterfactual/CounterfactualDepositCCTP.sol"; +import "../periphery/counterfactual/CounterfactualDepositFactoryTron.sol"; +import "../periphery/counterfactual/CounterfactualDepositOFT.sol"; +import "../periphery/counterfactual/CounterfactualDepositSpokePool.sol"; +import "../periphery/counterfactual/WithdrawImplementation.sol"; diff --git a/contracts/tron/TronImports.sol b/contracts/tron/TronImports.sol new file mode 100644 index 000000000..0a7a3d77b --- /dev/null +++ b/contracts/tron/TronImports.sol @@ -0,0 +1,8 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.0; + +// Entry point for SP1Helios and UniversalSpokePool in the tron Foundry profile. These use OZ v5 +// and must be in a separate file from counterfactual contracts (OZ v4) to avoid name collisions. +import "../sp1-helios/SP1Helios.sol"; +import "../sp1-helios/SP1AutoVerifier.sol"; +import "../Universal_SpokePool.sol"; diff --git a/foundry.toml b/foundry.toml index 3597e910f..aac18001a 100644 --- a/foundry.toml +++ b/foundry.toml @@ -75,28 +75,16 @@ revert_strings = "default" cache_path = "cache-foundry-local" out = "out-local" -# Tron-compatible profile. Compiles counterfactual contracts at 0.8.25 (max supported by Tron's solc fork). +# Tron-compatible profile. Uses Tron's solc fork (bin/solc-tron) to compile all Tron-targeted +# contracts: counterfactual, UniversalSpokePool, and SP1Helios. # Run with `FOUNDRY_PROFILE=tron forge build` [profile.tron] -solc_version = "0.8.25" -solc = "0.8.25" -evm_version = "cancun" -src = "contracts/periphery/counterfactual" -test = "test/evm/foundry/tron" -script = "script/counterfactual" -cache_path = "cache-foundry-tron" -out = "out-tron" -skip = ["sp1-helios"] - -# Tron-compatible profile for Universal SpokePool and SP1Helios. -# Run with `FOUNDRY_PROFILE=tron-universal forge build` -[profile.tron-universal] solc = "bin/solc-tron" evm_version = "cancun" -src = "contracts/tron-universal" +src = "contracts/tron" test = "test/evm/foundry/tron" script = "script/universal/tron" -cache_path = "cache-foundry-tron-universal" -out = "out-tron-universal" +cache_path = "cache-foundry-tron" +out = "out-tron" # See more config options https://github.com/foundry-rs/foundry/blob/master/crates/config/README.md#all-options diff --git a/package.json b/package.json index 80189b8df..ee9281748 100644 --- a/package.json +++ b/package.json @@ -47,7 +47,7 @@ "pre-commit-hook": "sh scripts/preCommitHook.sh", "extract-addresses": "./script/utils/extract_foundry_addresses.sh", "generate-constants-json": "ts-node ./script/utils/GenerateConstantsJson.ts", - "build-tron-universal": "FOUNDRY_PROFILE=tron-universal forge build", + "build-tron": "FOUNDRY_PROFILE=tron forge build", "tron-deploy-sp1-auto-verifier": "ts-node script/universal/tron/tron-deploy-sp1-auto-verifier.ts", "tron-deploy-sp1-helios": "ts-node script/universal/tron/tron-deploy-sp1-helios.ts", "tron-deploy-universal-spokepool": "ts-node script/universal/tron/tron-deploy-universal-spokepool.ts" diff --git a/script/universal/tron/README.md b/script/universal/tron/README.md index 461429f82..a777900fe 100644 --- a/script/universal/tron/README.md +++ b/script/universal/tron/README.md @@ -9,12 +9,14 @@ TypeScript scripts for deploying contracts to Tron via TronWeb. Tron uses a prot Download Tron's custom solc 0.8.25 from [tronprotocol/solidity releases](https://github.com/tronprotocol/solidity/releases) and place it at `bin/solc-tron`: ```bash +mkdir -p bin + # macOS (Apple Silicon) -curl -L -o bin/solc-tron https://github.com/nicetip/solidity/releases/download/tv0.8.25/solc_macos +curl -L -o bin/solc-tron https://github.com/tronprotocol/solidity/releases/download/tv_0.8.25/solc-macos chmod +x bin/solc-tron # Linux (amd64) -curl -L -o bin/solc-tron https://github.com/nicetip/solidity/releases/download/tv0.8.25/solc_linux +curl -L -o bin/solc-tron https://github.com/tronprotocol/solidity/releases/download/tv_0.8.25/solc-static-linux chmod +x bin/solc-tron ``` @@ -29,10 +31,10 @@ bin/solc-tron --version ### 2. Build contracts ```bash -yarn build-tron-universal +yarn build-tron ``` -This runs `FOUNDRY_PROFILE=tron-universal forge build`, which compiles using Tron's solc (`bin/solc-tron`) and outputs Foundry artifacts to `out-tron-universal/`. +This runs `FOUNDRY_PROFILE=tron forge build`, which compiles using Tron's solc (`bin/solc-tron`) and outputs Foundry artifacts to `out-tron/`. ### 3. Environment variables diff --git a/script/universal/tron/tron-deploy-sp1-auto-verifier.ts b/script/universal/tron/tron-deploy-sp1-auto-verifier.ts index ce918c96c..ba25eecb6 100644 --- a/script/universal/tron/tron-deploy-sp1-auto-verifier.ts +++ b/script/universal/tron/tron-deploy-sp1-auto-verifier.ts @@ -27,7 +27,7 @@ async function main(): Promise { console.log("=== SP1AutoVerifier Tron Deployment ==="); console.log(`Chain ID: ${chainId}`); - const artifactPath = path.resolve(__dirname, "../../../out-tron-universal/SP1AutoVerifier.sol/SP1AutoVerifier.json"); + const artifactPath = path.resolve(__dirname, "../../../out-tron/SP1AutoVerifier.sol/SP1AutoVerifier.json"); await deployContract({ chainId, artifactPath }); } diff --git a/script/universal/tron/tron-deploy-sp1-helios.ts b/script/universal/tron/tron-deploy-sp1-helios.ts index 35e421d52..aef57c4ab 100644 --- a/script/universal/tron/tron-deploy-sp1-helios.ts +++ b/script/universal/tron/tron-deploy-sp1-helios.ts @@ -225,7 +225,7 @@ async function main(): Promise { console.log("immutable and cannot be updated."); console.log("================\n"); - const artifactPath = path.resolve(__dirname, "../../../out-tron-universal/SP1Helios.sol/SP1Helios.json"); + const artifactPath = path.resolve(__dirname, "../../../out-tron/SP1Helios.sol/SP1Helios.json"); await deployContract({ chainId, artifactPath, encodedArgs }); } diff --git a/script/universal/tron/tron-deploy-universal-spokepool.ts b/script/universal/tron/tron-deploy-universal-spokepool.ts index 36421fd54..08764f9f0 100644 --- a/script/universal/tron/tron-deploy-universal-spokepool.ts +++ b/script/universal/tron/tron-deploy-universal-spokepool.ts @@ -87,10 +87,7 @@ async function main(): Promise { ] ); - const artifactPath = path.resolve( - __dirname, - "../../../out-tron-universal/Universal_SpokePool.sol/Universal_SpokePool.json" - ); + const artifactPath = path.resolve(__dirname, "../../../out-tron/Universal_SpokePool.sol/Universal_SpokePool.json"); await deployContract({ chainId, artifactPath, encodedArgs }); } From c0e1d0232b3cf7c0ba5b8e0045e37aa5a0aedddd Mon Sep 17 00:00:00 2001 From: Taylor Webb Date: Mon, 23 Mar 2026 17:26:11 -0600 Subject: [PATCH 6/9] disable CCTP in USP deploy script --- .../universal/tron/tron-deploy-universal-spokepool.ts | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/script/universal/tron/tron-deploy-universal-spokepool.ts b/script/universal/tron/tron-deploy-universal-spokepool.ts index 08764f9f0..19bd706be 100644 --- a/script/universal/tron/tron-deploy-universal-spokepool.ts +++ b/script/universal/tron/tron-deploy-universal-spokepool.ts @@ -16,8 +16,6 @@ * USP_WRAPPED_NATIVE_TOKEN_ADDRESS — Wrapped native token (WTRX) address (Tron Base58Check, T...) * USP_DEPOSIT_QUOTE_TIME_BUFFER — Deposit quote time buffer in seconds * USP_FILL_DEADLINE_BUFFER — Fill deadline buffer in seconds - * USP_L2_USDC_ADDRESS — USDC token address on Tron (Tron Base58Check, T...) - * USP_CCTP_TOKEN_MESSENGER_ADDRESS — CCTP TokenMessenger address (Tron Base58Check, T...), or zero address to disable * USP_OFT_DST_EID — LayerZero OFT destination endpoint ID (0 to disable) * USP_OFT_FEE_CAP — LayerZero OFT fee cap in wei (0 to disable) * @@ -54,8 +52,9 @@ async function main(): Promise { const wrappedNativeToken = tronToEvmAddress(requireEnv("USP_WRAPPED_NATIVE_TOKEN_ADDRESS")); const depositQuoteTimeBuffer = requireEnv("USP_DEPOSIT_QUOTE_TIME_BUFFER"); const fillDeadlineBuffer = requireEnv("USP_FILL_DEADLINE_BUFFER"); - const l2Usdc = tronToEvmAddress(requireEnv("USP_L2_USDC_ADDRESS")); - const cctpTokenMessenger = tronToEvmAddress(requireEnv("USP_CCTP_TOKEN_MESSENGER_ADDRESS")); + // USDC / CCTP is not supported on Tron. + const l2Usdc = "0x0000000000000000000000000000000000000000"; + const cctpTokenMessenger = "0x0000000000000000000000000000000000000000"; const oftDstEid = requireEnv("USP_OFT_DST_EID"); const oftFeeCap = requireEnv("USP_OFT_FEE_CAP"); @@ -65,8 +64,8 @@ async function main(): Promise { console.log(` Wrapped native token: ${wrappedNativeToken}`); console.log(` Deposit quote buffer: ${depositQuoteTimeBuffer}s`); console.log(` Fill deadline buffer: ${fillDeadlineBuffer}s`); - console.log(` L2 USDC: ${l2Usdc}`); - console.log(` CCTP TokenMessenger: ${cctpTokenMessenger}`); + console.log(` L2 USDC: ${l2Usdc} (disabled — CCTP not supported on Tron)`); + console.log(` CCTP TokenMessenger: ${cctpTokenMessenger} (disabled)`); console.log(` OFT dst EID: ${oftDstEid}`); console.log(` OFT fee cap: ${oftFeeCap}`); From a2f4956719529fdfba4337617e0c545de81a8a81 Mon Sep 17 00:00:00 2001 From: Taylor Webb Date: Mon, 23 Mar 2026 17:32:35 -0600 Subject: [PATCH 7/9] remove OFT args from USP deploy script --- .../universal/tron/tron-deploy-universal-spokepool.ts | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/script/universal/tron/tron-deploy-universal-spokepool.ts b/script/universal/tron/tron-deploy-universal-spokepool.ts index 19bd706be..e1a603b55 100644 --- a/script/universal/tron/tron-deploy-universal-spokepool.ts +++ b/script/universal/tron/tron-deploy-universal-spokepool.ts @@ -16,8 +16,6 @@ * USP_WRAPPED_NATIVE_TOKEN_ADDRESS — Wrapped native token (WTRX) address (Tron Base58Check, T...) * USP_DEPOSIT_QUOTE_TIME_BUFFER — Deposit quote time buffer in seconds * USP_FILL_DEADLINE_BUFFER — Fill deadline buffer in seconds - * USP_OFT_DST_EID — LayerZero OFT destination endpoint ID (0 to disable) - * USP_OFT_FEE_CAP — LayerZero OFT fee cap in wei (0 to disable) * * Usage: * yarn tron-deploy-universal-spokepool @@ -55,8 +53,9 @@ async function main(): Promise { // USDC / CCTP is not supported on Tron. const l2Usdc = "0x0000000000000000000000000000000000000000"; const cctpTokenMessenger = "0x0000000000000000000000000000000000000000"; - const oftDstEid = requireEnv("USP_OFT_DST_EID"); - const oftFeeCap = requireEnv("USP_OFT_FEE_CAP"); + // OFT is not supported on Tron. + const oftDstEid = "0"; + const oftFeeCap = "0"; console.log(` Admin update buffer: ${adminUpdateBuffer}s`); console.log(` Helios: ${heliosAddress}`); @@ -66,8 +65,8 @@ async function main(): Promise { console.log(` Fill deadline buffer: ${fillDeadlineBuffer}s`); console.log(` L2 USDC: ${l2Usdc} (disabled — CCTP not supported on Tron)`); console.log(` CCTP TokenMessenger: ${cctpTokenMessenger} (disabled)`); - console.log(` OFT dst EID: ${oftDstEid}`); - console.log(` OFT fee cap: ${oftFeeCap}`); + console.log(` OFT dst EID: ${oftDstEid} (disabled — OFT not supported on Tron)`); + console.log(` OFT fee cap: ${oftFeeCap} (disabled)`); // Constructor: (uint256, address, address, address, uint32, uint32, IERC20, ITokenMessenger, uint32, uint256) const encodedArgs = encodeArgs( From 7e425d2e8278cc818c5186e64cfe5adaa09b8d7d Mon Sep 17 00:00:00 2001 From: Taylor Webb Date: Mon, 23 Mar 2026 17:47:09 -0600 Subject: [PATCH 8/9] lookup hubPoolStore address --- .../tron/tron-deploy-universal-spokepool.ts | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/script/universal/tron/tron-deploy-universal-spokepool.ts b/script/universal/tron/tron-deploy-universal-spokepool.ts index e1a603b55..ff2756aff 100644 --- a/script/universal/tron/tron-deploy-universal-spokepool.ts +++ b/script/universal/tron/tron-deploy-universal-spokepool.ts @@ -12,7 +12,6 @@ * TRON_FEE_LIMIT — optional, in sun (default: 1500000000 = 1500 TRX) * USP_ADMIN_UPDATE_BUFFER — Admin update buffer in seconds (e.g. 86400 = 24h) * USP_HELIOS_ADDRESS — SP1Helios contract address (Tron Base58Check, T...) - * USP_HUB_POOL_STORE_ADDRESS — HubPoolStore contract address (Tron Base58Check, T...) * USP_WRAPPED_NATIVE_TOKEN_ADDRESS — Wrapped native token (WTRX) address (Tron Base58Check, T...) * USP_DEPOSIT_QUOTE_TIME_BUFFER — Deposit quote time buffer in seconds * USP_FILL_DEADLINE_BUFFER — Fill deadline buffer in seconds @@ -22,9 +21,25 @@ */ import "dotenv/config"; +import * as fs from "fs"; import * as path from "path"; import { deployContract, encodeArgs, tronToEvmAddress } from "./deploy"; +const TRON_TESTNET_CHAIN_IDS = ["3448148188"]; + +/** Read the HubPoolStore address from generated/constants.json */ +function getHubPoolStoreAddress(spokeChainId: string): string { + const hubChainId = TRON_TESTNET_CHAIN_IDS.includes(spokeChainId) ? "11155111" : "1"; + const constantsPath = path.resolve(__dirname, "../../../generated/constants.json"); + const constants = JSON.parse(fs.readFileSync(constantsPath, "utf-8")); + const address = constants.L1_ADDRESS_MAP?.[hubChainId]?.hubPoolStore; + if (!address) { + console.log(`Error: hubPoolStore not found in constants.json for hub chain ${hubChainId}`); + process.exit(1); + } + return address; +} + function requireEnv(name: string): string { const value = process.env[name]; if (!value) { @@ -46,7 +61,7 @@ async function main(): Promise { const adminUpdateBuffer = requireEnv("USP_ADMIN_UPDATE_BUFFER"); const heliosAddress = tronToEvmAddress(requireEnv("USP_HELIOS_ADDRESS")); - const hubPoolStoreAddress = tronToEvmAddress(requireEnv("USP_HUB_POOL_STORE_ADDRESS")); + const hubPoolStoreAddress = getHubPoolStoreAddress(chainId); const wrappedNativeToken = tronToEvmAddress(requireEnv("USP_WRAPPED_NATIVE_TOKEN_ADDRESS")); const depositQuoteTimeBuffer = requireEnv("USP_DEPOSIT_QUOTE_TIME_BUFFER"); const fillDeadlineBuffer = requireEnv("USP_FILL_DEADLINE_BUFFER"); From 0c05cb273cfb1374413f4b35a473761f95ef6996 Mon Sep 17 00:00:00 2001 From: Taylor Webb Date: Fri, 27 Mar 2026 13:34:53 -0600 Subject: [PATCH 9/9] add comment explaining pure override --- contracts/sp1-helios/SP1AutoVerifier.sol | 1 + 1 file changed, 1 insertion(+) diff --git a/contracts/sp1-helios/SP1AutoVerifier.sol b/contracts/sp1-helios/SP1AutoVerifier.sol index a77b7737b..89314c4b2 100644 --- a/contracts/sp1-helios/SP1AutoVerifier.sol +++ b/contracts/sp1-helios/SP1AutoVerifier.sol @@ -6,5 +6,6 @@ import { ISP1Verifier } from "@sp1-contracts/src/ISP1Verifier.sol"; /// @title SP1 Auto Verifier /// @notice A no-op verifier that accepts any proof. Useful for testing SP1Helios without real proofs. contract SP1AutoVerifier is ISP1Verifier { + // pure is intentionally stricter than the interface's view; Solidity allows this and it's correct for a no-op. function verifyProof(bytes32, bytes calldata, bytes calldata) external pure {} }