Skip to content

feat: svm deploy instructions #1006

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 5 commits into
base: master
Choose a base branch
from
Draft
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
1 change: 1 addition & 0 deletions Anchor.toml
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ publicKeyToAddress = "NODE_NO_WARNINGS=1 yarn run ts-node ./scripts/svm/publicKe
findFillStatusPdaFromEvent = "NODE_NO_WARNINGS=1 yarn run ts-node ./scripts/svm/findFillStatusPdaFromEvent.ts"
findFillStatusFromFillStatusPda = "NODE_NO_WARNINGS=1 yarn run ts-node ./scripts/svm/findFillStatusFromFillStatusPda.ts"
nativeDeposit = "NODE_NO_WARNINGS=1 yarn run ts-node ./scripts/svm/nativeDeposit.ts"
squadsIdlUpgrade = "NODE_NO_WARNINGS=1 yarn run ts-node ./scripts/svm/squadsIdlUpgrade.ts"

[test.validator]
url = "https://api.mainnet-beta.solana.com"
Expand Down
185 changes: 182 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,17 +31,20 @@ Note if you get build issues on the initial `yarn` command try downgrading to no

```shell
yarn
yarn build # Will build all code. Compile solidity & rust, generate ts outputs
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
```

## Test

```shell
yarn test # Run all unit tests without gas analysis
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
yarn test-svm # Only test SVM code (local toolchain build)
yarn test-svm-solana-verify # Only test SVM code (verified docker build)
```

## Lint
Expand All @@ -56,11 +59,187 @@ yarn lint-fix

## Deploy and Verify

### EVM

```shell
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
```

### SVM

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:

```shell
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.:

```shell
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:

```shell
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
```

#### Initial deployment

Deploy the program and set the upgrade authority to the multisig:

```shell
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:

```shell
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:

```shell
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.:

```shell
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
```

#### Upgrades

Initiate the program upgrade:

```shell
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:

```shell
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:

```shell
anchor run squadsIdlUpgrade -- \
--programId $PROGRAM_ID \
--idlBuffer $IDL_BUFFER \
--multisig $MULTISIG \
--closeRecipient $(solana address --keypair $KEYPAIR)
```

#### Verify

Start with verifying locally that the deployed program matches the source code of the public repository:

```shell
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:

```shell
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):

```shell
solana-verify remote submit-job \
--url $RPC_URL \
--program-id $PROGRAM_ID \
--uploader $MULTISIG
```

## Miscellaneous topics

### Manually Finalizing Scroll Claims from L2 -> L1 (Mainnet | Sepolia)
Expand Down
6 changes: 4 additions & 2 deletions scripts/svm/buildHelpers/buildSolanaVerify.sh
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,9 @@ for program in programs/*; do

# We don't need keypair files from the verified build and they cause permission issues on CI when Swatinem/rust-cache
# tries to delete them.
echo "Removing target/deploy/$program_name-keypair.json"
sudo rm -f "target/deploy/$program_name-keypair.json"
if [[ "${CI:-}" == "true" ]]; then
echo "Removing target/deploy/$program_name-keypair.json"
sudo rm -f "target/deploy/$program_name-keypair.json"
fi

done
6 changes: 3 additions & 3 deletions scripts/svm/createVault.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { ASSOCIATED_TOKEN_PROGRAM_ID, TOKEN_PROGRAM_ID, getOrCreateAssociatedTok
import { PublicKey } from "@solana/web3.js";
import yargs from "yargs";
import { hideBin } from "yargs/helpers";
import { getSpokePoolProgram } from "../../src/svm/web3-v1";
import { getSpokePoolProgram, SOLANA_SPOKE_STATE_SEED } from "../../src/svm/web3-v1";

// Set up the provider
const provider = AnchorProvider.env();
Expand All @@ -20,12 +20,12 @@ console.log("SVM-Spoke Program ID:", programId.toString());

// Parse arguments
const argv = yargs(hideBin(process.argv))
.option("seed", { type: "string", demandOption: true, describe: "Seed for the state account PDA" })
.option("seed", { type: "string", demandOption: false, describe: "Seed for the state account PDA" })
.option("originToken", { type: "string", demandOption: true, describe: "Origin token public key" }).argv;

async function createVault(): Promise<void> {
const resolvedArgv = await argv;
const seed = new BN(resolvedArgv.seed);
const seed = resolvedArgv.seed ? new BN(resolvedArgv.seed) : SOLANA_SPOKE_STATE_SEED;
const originToken = new PublicKey(resolvedArgv.originToken);

// Define the state account PDA
Expand Down
28 changes: 22 additions & 6 deletions scripts/svm/initialize.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { AnchorProvider, BN } from "@coral-xyz/anchor";
import { PublicKey, SystemProgram } from "@solana/web3.js";
import yargs from "yargs";
import { hideBin } from "yargs/helpers";
import { evmAddressToPublicKey, getSpokePoolProgram } from "../../src/svm/web3-v1";
import { evmAddressToPublicKey, getSpokePoolProgram, SOLANA_SPOKE_STATE_SEED } from "../../src/svm/web3-v1";

// Set up the provider
const provider = AnchorProvider.env();
Expand All @@ -15,11 +15,12 @@ const programId = program.programId;

// Parse arguments
const argv = yargs(hideBin(process.argv))
.option("seed", { type: "string", demandOption: true, describe: "Seed for the state account PDA" })
.option("initNumbDeposits", { type: "string", demandOption: true, describe: "Init numb of deposits" })
.option("seed", { type: "string", demandOption: false, describe: "Seed for the state account PDA" })
.option("initNumbDeposits", { type: "string", demandOption: false, describe: "Init numb of deposits" })
.option("chainId", { type: "string", demandOption: true, describe: "Chain ID" })
.option("remoteDomain", { type: "number", demandOption: true, describe: "CCTP domain for Mainnet Ethereum" })
.option("crossDomainAdmin", { type: "string", demandOption: true, describe: "HubPool on Mainnet Ethereum" })
.option("svmAdmin", { type: "string", demandOption: false, describe: "SVM admin" })
.option("depositQuoteTimeBuffer", {
type: "number",
demandOption: false,
Expand All @@ -29,17 +30,18 @@ const argv = yargs(hideBin(process.argv))
.option("fillDeadlineBuffer", {
type: "number",
demandOption: false,
default: 3600 * 4,
default: 3600 * 6,
describe: "Fill deadline buffer",
}).argv;

async function initialize(): Promise<void> {
const resolvedArgv = await argv;
const seed = new BN(resolvedArgv.seed);
const initialNumberOfDeposits = new BN(resolvedArgv.initNumbDeposits);
const seed = resolvedArgv.seed ? new BN(resolvedArgv.seed) : SOLANA_SPOKE_STATE_SEED;
const initialNumberOfDeposits = resolvedArgv.initNumbDeposits ? new BN(resolvedArgv.initNumbDeposits) : new BN(0);
const chainId = new BN(resolvedArgv.chainId);
const remoteDomain = resolvedArgv.remoteDomain;
const crossDomainAdmin = evmAddressToPublicKey(resolvedArgv.crossDomainAdmin); // Use the function to cast the value
const svmAdmin = resolvedArgv.svmAdmin ? new PublicKey(resolvedArgv.svmAdmin) : provider.wallet.publicKey;
const depositQuoteTimeBuffer = resolvedArgv.depositQuoteTimeBuffer;
const fillDeadlineBuffer = resolvedArgv.fillDeadlineBuffer;

Expand All @@ -64,6 +66,7 @@ async function initialize(): Promise<void> {
{ Property: "chainId", Value: chainId.toString() },
{ Property: "remoteDomain", Value: remoteDomain.toString() },
{ Property: "crossDomainAdmin", Value: crossDomainAdmin.toString() },
{ Property: "svmAdmin", Value: svmAdmin.toString() },
{ Property: "depositQuoteTimeBuffer", Value: depositQuoteTimeBuffer.toString() },
{ Property: "fillDeadlineBuffer", Value: fillDeadlineBuffer.toString() },
]);
Expand All @@ -87,6 +90,19 @@ async function initialize(): Promise<void> {
.rpc();

console.log("Transaction signature:", tx);

if (!svmAdmin.equals(provider.wallet.publicKey)) {
console.log("Transferring ownership to SVM admin...");
const tx = await program.methods
.transferOwnership(svmAdmin)
.accountsPartial({
state: statePda,
signer: signer,
})
.rpc();

console.log("Transfer ownership transaction signature:", tx);
}
}

// Run the initialize function
Expand Down
Loading
Loading