Skip to content
Open
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
7 changes: 6 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@ cache-foundry
cache-foundry-local
cache-foundry-tron


# Upgradeability files
.openzeppelin

Expand All @@ -58,6 +57,12 @@ 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
flattened/

# claude
CLAUDE.local.md
4 changes: 2 additions & 2 deletions contracts/SpokePool.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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,
Expand Down
11 changes: 11 additions & 0 deletions contracts/sp1-helios/SP1AutoVerifier.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.25;

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 {}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

do you think we should emit an event here?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Unfortunately we can't since its a pure / view function

}
21 changes: 10 additions & 11 deletions contracts/sp1-helios/SP1Helios.sol
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.30;
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";
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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);
Expand All @@ -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);
}
Expand Down
13 changes: 13 additions & 0 deletions contracts/tron/TronCounterfactualImports.sol
Original file line number Diff line number Diff line change
@@ -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";
8 changes: 8 additions & 0 deletions contracts/tron/TronImports.sol
Original file line number Diff line number Diff line change
@@ -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";
11 changes: 5 additions & 6 deletions foundry.toml
Original file line number Diff line number Diff line change
Expand Up @@ -75,17 +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"
solc = "bin/solc-tron"
evm_version = "cancun"
src = "contracts/periphery/counterfactual"
src = "contracts/tron"
test = "test/evm/foundry/tron"
script = "script/counterfactual"
script = "script/universal/tron"
cache_path = "cache-foundry-tron"
out = "out-tron"
skip = ["sp1-helios"]

# See more config options https://github.com/foundry-rs/foundry/blob/master/crates/config/README.md#all-options
11 changes: 8 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": "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"
},
"dependencies": {
"@across-protocol/constants": "^3.1.100",
Expand All @@ -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"
},
Expand All @@ -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",
Expand All @@ -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"
Expand Down
151 changes: 151 additions & 0 deletions script/universal/tron/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
# 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
mkdir -p bin

# macOS (Apple Silicon)
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/tronprotocol/solidity/releases/download/tv_0.8.25/solc-static-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
```

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

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 <chain-id>

# 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 <chain-id>
```

> **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 <chain-id>
```

## 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<ContractName>.s.sol/<chainId>/`. These track deployed addresses and transaction IDs.

## Chain IDs

| Network | Chain ID |
| ------------ | ---------- |
| Tron Mainnet | 728126428 |
| Tron Nile | 3448148188 |
Loading