Contains smart contract suite to enable instant token transfers between any two networks. Relays are backstopped by
liquidity held in a central HubPool
on Ethereum, which also serves as the cross-chain administrator of all contracts in the
system. SpokePool
contracts are deployed to any network that wants to originate token deposits or be the final
destination for token transfers, and they are all governed by the HubPool
on Ethereum.
These contracts have been continuously audited by OpenZeppelin and the audit reports can be found here.
The SpokePool contracts are UUPSUpgradeable Proxy contracts which means that their addresses will always be the same but their implementation code can change.
All SpokePools can be upgraded if the "admin" of the contract calls upgradeTo
. The SpokePool's admin is set by implementing the _requireAdminSender()
virtual function in the child contract. For example here are the Arbitrum and Optimism implementations of the admin check.
All SpokePools are implemented such that the admin is the HubPool, and therefore we describe the SpokePools as having "cross-chain ownership". The owner of the HubPool can call this function to send a cross-chain execution of upgradeTo
on any SpokePool in order to upgrade it.
This script is useful for creating the calldata to execute a cross-chain upgrade via the HubPool.
The latest contract deployments can be found in /deployments/deployments.json
.
This repository assumes you have Node installed, with a minimum version of 16.18.0. Depending on what you want to do with the repo you might also need foundry and anchor to also be installed. If you have build issues please ensure these are both installed first.
Note if you get build issues on the initial yarn
command try downgrading to node 20.17 (nvm use 20.17
). If you've never used anchor before you might need to run avm use latest
as well.
yarn
yarn build # Will build all code. Compile solidity & rust (local toolchain), generate ts outputs
yarn build-verified # Will build all code. Compile solidity & rust (verified docker build), generate ts outputs
yarn test # Run all unit tests without gas analysis, using local toolchain SVM build
yarn test-verified # Run all unit tests (without gas analysis) with verified SVM docker build
yarn test:gas-analytics # Run only tests that count gas costs
yarn test:report-gas # Run unit tests with hardhat-gas-reporter enabled
yarn test-evm # Only test EVM code
yarn test-svm # Only test SVM code (local toolchain build)
yarn test-svm-solana-verify # Only test SVM code (verified docker build)
yarn lint
yarn lint-js # Only lint Javascript
yarn lint-rust # Only lint rust
yarn lint-solidity # Only lint solidity
yarn lint-fix
NODE_URL_1=https://mainnet.infura.com/xxx yarn hardhat deploy --tags HubPool --network mainnet
ETHERSCAN_API_KEY=XXX yarn hardhat etherscan-verify --network mainnet --license AGPL-3.0 --force-license --solc-input
Before deploying for the first time make sure all program IDs in lib.rs
and Anchor.toml
are the same as listed when running anchor keys list
. If not, update them to match the deployment keypairs under target/deploy/
and commit the changes.
Make sure to use the verified docker binaries that can be built:
unset IS_TEST # Ensures the production build is used (not the test feature)
yarn build-svm-solana-verify # Builds verified SVM binaries
yarn generate-svm-artifacts # Builds IDLs
Export required environment variables, e.g.:
export RPC_URL=https://api.devnet.solana.com
export KEYPAIR=~/.config/solana/dev-wallet.json
export PROGRAM=svm_spoke # Also repeat the deployment process for multicall_handler
export PROGRAM_ID=$(cat target/idl/$PROGRAM.json | jq -r ".address")
export MULTISIG= # Export the Squads vault, not the multisig address!
For the initial deployment also need these:
export SVM_CHAIN_ID=$(cast to-dec $(cast shr $(cast shl $(cast keccak solana-devnet) 208) 208))
export HUB_POOL=0x14224e63716afAcE30C9a417E0542281869f7d9e # This is for sepolia, update for mainnet
export DEPOSIT_QUOTE_TIME_BUFFER=3600
export FILL_DEADLINE_BUFFER=21600
Deploy the program and set the upgrade authority to the multisig:
solana program deploy \
--url $RPC_URL target/deploy/$PROGRAM.so \
--keypair $KEYPAIR \
--program-id target/deploy/$PROGRAM-keypair.json \
--with-compute-unit-price 50000 \
--max-sign-attempts 100
solana program set-upgrade-authority \
--url $RPC_URL \
--keypair $KEYPAIR \
--skip-new-upgrade-authority-signer-check \
$PROGRAM_ID \
--new-upgrade-authority $MULTISIG
Update and commit deployments/deployments.json
with the deployed program ID and deployment slot.
Upload the IDL and set the upgrade authority to the multisig:
anchor idl init \
--provider.cluster $RPC_URL \
--provider.wallet $KEYPAIR \
--filepath target/idl/$PROGRAM.json \
$PROGRAM_ID
anchor idl set-authority \
--provider.cluster $RPC_URL \
--provider.wallet $KEYPAIR \
--program-id $PROGRAM_ID \
--new-authority $MULTISIG
svm_spoke
also requires initialization and transfer of ownership on the first deployment:
anchor run initialize \
--provider.cluster $RPC_URL \
--provider.wallet $KEYPAIR -- \
--chainId $SVM_CHAIN_ID \
--remoteDomain 0 \
--crossDomainAdmin $HUB_POOL \
--svmAdmin $MULTISIG \
--depositQuoteTimeBuffer $DEPOSIT_QUOTE_TIME_BUFFER \
--fillDeadlineBuffer $FILL_DEADLINE_BUFFER
Create the vault for accepting deposits, e.g.:
export MINT=4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU # This is USDC on devnet, update with address for mainnet
anchor run createVault \
--provider.cluster $RPC_URL \
--provider.wallet $KEYPAIR -- \
--originToken $MINT
Initiate the program upgrade:
solana program write-buffer \
--url $RPC_URL \
--keypair $KEYPAIR \
--with-compute-unit-price 50000 \
--max-sign-attempts 100 \
--use-rpc \
target/deploy/$PROGRAM.so
export BUFFER= # Export the logged buffer address from the command above
solana program set-buffer-authority \
--url $RPC_URL \
--keypair $KEYPAIR \
$BUFFER \
--new-buffer-authority $MULTISIG
Add the program ID to Squads multisig (https://devnet.squads.so/
for devnet and https://app.squads.so/
for mainnet) in the Developers/Programs section. Then add the upgrade filling in the buffer address and buffer refund. After creating the upgrade verify the buffer authority as prompted and proceed with initiating the upgrade. Once all required signers have approved, execute the upgrade in the transactions section.
Start the IDL upgrade by writing it to the buffer:
anchor idl write-buffer \
--provider.cluster $RPC_URL \
--provider.wallet $KEYPAIR \
--filepath target/idl/$PROGRAM.json \
$PROGRAM_ID
export IDL_BUFFER= # Export the logged IDL buffer address from the command above
anchor idl set-authority \
--provider.cluster $RPC_URL \
--provider.wallet $KEYPAIR \
--program-id $PROGRAM_ID \
--new-authority $MULTISIG \
$IDL_BUFFER
Construct the multisig transaction for finalizing the IDL upgrade. Copy the printed base58 encoded transaction from below command and import it into the Squads multisig for approval and execution:
anchor run squadsIdlUpgrade -- \
--programId $PROGRAM_ID \
--idlBuffer $IDL_BUFFER \
--multisig $MULTISIG \
--closeRecipient $(solana address --keypair $KEYPAIR)
Start with verifying locally that the deployed program matches the source code of the public repository:
solana-verify verify-from-repo \
--url $RPC_URL \
--program-id $PROGRAM_ID \
--library-name $PROGRAM \
https://github.com/across-protocol/contracts
When prompted, don't yet upload the verification data to the blockchain as that should be done by the multisig. Proceed with creating the upload transaction and then import and sign/execute it in the Squads multisig:
solana-verify export-pda-tx \
--url $RPC_URL \
--program-id $PROGRAM_ID \
--library-name $PROGRAM \
--uploader $MULTISIG \
https://github.com/across-protocol/contracts
Note that the initial upload transaction might fail if the multisig vault does not have enough SOL for PDA creation. In that case, transfer the required funds to the multisig vault before executing the upload transaction.
Finally, submit the verification to OtterSec API (only works on mainnet):
solana-verify remote submit-job \
--url $RPC_URL \
--program-id $PROGRAM_ID \
--uploader $MULTISIG
yarn hardhat finalize-scroll-claims --l2-address {operatorAddress}
Slither is a Solidity static analysis framework written in Python 3. It runs a suite of vulnerability detectors, prints visual information about contract details, and provides an API to easily write custom analyses. Slither enables developers to find vulnerabilities, enhance their code comprehension, and quickly prototype custom analyses.
Spire-Contracts has been analyzed using Slither@0.9.2
and no major bugs was found. To rerun the analytics, run:
slither contracts/SpokePool.sol
\ --solc-remaps @=node_modules/@
\ --solc-args "--optimize --optimize-runs 1000000"
\ --filter-paths "node_modules"
\ --exclude naming-convention
You can replace SpokePool.sol
with the specific contract you want to analyze.
ZK EVM's typically require a special compiler to convert Solidity into code that can be run on the ZK VM.
There are special instructions for compiling and deploying contracts on zksync
. The compile command will create artifacts-zk
and cache-zk
directories.
This step requires Docker Desktop to be running, as the solc
docker image is fetched as a prerequisite.
yarn compile-zksync
All code in this repository is licensed under BUSL-1.1 unless specified differently in the file. Individual exceptions to this license can be made by Risk Labs, which holds the rights to this software and design. If you are interested in using the code or designs in a derivative work, feel free to reach out to licensing@risklabs.foundation.