Skip to content

Commit 8355870

Browse files
authored
feat: svm deploy instructions (#1006)
* fix: remove keypairs only in ci Signed-off-by: Reinis Martinsons <[email protected]> * fix: deployment instructions Signed-off-by: Reinis Martinsons <[email protected]> * fix: solana verification instructions Signed-off-by: Reinis Martinsons <[email protected]> * fix: readme Signed-off-by: Reinis Martinsons <[email protected]> * fix Signed-off-by: Reinis Martinsons <[email protected]> --------- Signed-off-by: Reinis Martinsons <[email protected]>
1 parent d1d1183 commit 8355870

File tree

6 files changed

+293
-14
lines changed

6 files changed

+293
-14
lines changed

Anchor.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ publicKeyToAddress = "NODE_NO_WARNINGS=1 yarn run ts-node ./scripts/svm/publicKe
5454
findFillStatusPdaFromEvent = "NODE_NO_WARNINGS=1 yarn run ts-node ./scripts/svm/findFillStatusPdaFromEvent.ts"
5555
findFillStatusFromFillStatusPda = "NODE_NO_WARNINGS=1 yarn run ts-node ./scripts/svm/findFillStatusFromFillStatusPda.ts"
5656
nativeDeposit = "NODE_NO_WARNINGS=1 yarn run ts-node ./scripts/svm/nativeDeposit.ts"
57+
squadsIdlUpgrade = "NODE_NO_WARNINGS=1 yarn run ts-node ./scripts/svm/squadsIdlUpgrade.ts"
5758

5859
[test.validator]
5960
url = "https://api.mainnet-beta.solana.com"

README.md

Lines changed: 182 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,17 +31,20 @@ Note if you get build issues on the initial `yarn` command try downgrading to no
3131

3232
```shell
3333
yarn
34-
yarn build # Will build all code. Compile solidity & rust, generate ts outputs
34+
yarn build # Will build all code. Compile solidity & rust (local toolchain), generate ts outputs
35+
yarn build-verified # Will build all code. Compile solidity & rust (verified docker build), generate ts outputs
3536
```
3637

3738
## Test
3839

3940
```shell
40-
yarn test # Run all unit tests without gas analysis
41+
yarn test # Run all unit tests without gas analysis, using local toolchain SVM build
42+
yarn test-verified # Run all unit tests (without gas analysis) with verified SVM docker build
4143
yarn test:gas-analytics # Run only tests that count gas costs
4244
yarn test:report-gas # Run unit tests with hardhat-gas-reporter enabled
4345
yarn test-evm # Only test EVM code
44-
yarn test-svm # Only test SVM code
46+
yarn test-svm # Only test SVM code (local toolchain build)
47+
yarn test-svm-solana-verify # Only test SVM code (verified docker build)
4548
```
4649

4750
## Lint
@@ -56,11 +59,187 @@ yarn lint-fix
5659

5760
## Deploy and Verify
5861

62+
### EVM
63+
5964
```shell
6065
NODE_URL_1=https://mainnet.infura.com/xxx yarn hardhat deploy --tags HubPool --network mainnet
6166
ETHERSCAN_API_KEY=XXX yarn hardhat etherscan-verify --network mainnet --license AGPL-3.0 --force-license --solc-input
6267
```
6368

69+
### SVM
70+
71+
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.
72+
73+
Make sure to use the verified docker binaries that can be built:
74+
75+
```shell
76+
unset IS_TEST # Ensures the production build is used (not the test feature)
77+
yarn build-svm-solana-verify # Builds verified SVM binaries
78+
yarn generate-svm-artifacts # Builds IDLs
79+
```
80+
81+
Export required environment variables, e.g.:
82+
83+
```shell
84+
export RPC_URL=https://api.devnet.solana.com
85+
export KEYPAIR=~/.config/solana/dev-wallet.json
86+
export PROGRAM=svm_spoke # Also repeat the deployment process for multicall_handler
87+
export PROGRAM_ID=$(cat target/idl/$PROGRAM.json | jq -r ".address")
88+
export MULTISIG= # Export the Squads vault, not the multisig address!
89+
```
90+
91+
For the initial deployment also need these:
92+
93+
```shell
94+
export SVM_CHAIN_ID=$(cast to-dec $(cast shr $(cast shl $(cast keccak solana-devnet) 208) 208))
95+
export HUB_POOL=0x14224e63716afAcE30C9a417E0542281869f7d9e # This is for sepolia, update for mainnet
96+
export DEPOSIT_QUOTE_TIME_BUFFER=3600
97+
export FILL_DEADLINE_BUFFER=21600
98+
```
99+
100+
#### Initial deployment
101+
102+
Deploy the program and set the upgrade authority to the multisig:
103+
104+
```shell
105+
solana program deploy \
106+
--url $RPC_URL target/deploy/$PROGRAM.so \
107+
--keypair $KEYPAIR \
108+
--program-id target/deploy/$PROGRAM-keypair.json \
109+
--with-compute-unit-price 50000 \
110+
--max-sign-attempts 100
111+
solana program set-upgrade-authority \
112+
--url $RPC_URL \
113+
--keypair $KEYPAIR \
114+
--skip-new-upgrade-authority-signer-check \
115+
$PROGRAM_ID \
116+
--new-upgrade-authority $MULTISIG
117+
```
118+
119+
Update and commit `deployments/deployments.json` with the deployed program ID and deployment slot.
120+
121+
Upload the IDL and set the upgrade authority to the multisig:
122+
123+
```shell
124+
anchor idl init \
125+
--provider.cluster $RPC_URL \
126+
--provider.wallet $KEYPAIR \
127+
--filepath target/idl/$PROGRAM.json \
128+
$PROGRAM_ID
129+
anchor idl set-authority \
130+
--provider.cluster $RPC_URL \
131+
--provider.wallet $KEYPAIR \
132+
--program-id $PROGRAM_ID \
133+
--new-authority $MULTISIG
134+
```
135+
136+
`svm_spoke` also requires initialization and transfer of ownership on the first deployment:
137+
138+
```shell
139+
anchor run initialize \
140+
--provider.cluster $RPC_URL \
141+
--provider.wallet $KEYPAIR -- \
142+
--chainId $SVM_CHAIN_ID \
143+
--remoteDomain 0 \
144+
--crossDomainAdmin $HUB_POOL \
145+
--svmAdmin $MULTISIG \
146+
--depositQuoteTimeBuffer $DEPOSIT_QUOTE_TIME_BUFFER \
147+
--fillDeadlineBuffer $FILL_DEADLINE_BUFFER
148+
```
149+
150+
Create the vault for accepting deposits, e.g.:
151+
152+
```shell
153+
export MINT=4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU # This is USDC on devnet, update with address for mainnet
154+
anchor run createVault \
155+
--provider.cluster $RPC_URL \
156+
--provider.wallet $KEYPAIR -- \
157+
--originToken $MINT
158+
```
159+
160+
#### Upgrades
161+
162+
Initiate the program upgrade:
163+
164+
```shell
165+
solana program write-buffer \
166+
--url $RPC_URL \
167+
--keypair $KEYPAIR \
168+
--with-compute-unit-price 50000 \
169+
--max-sign-attempts 100 \
170+
--use-rpc \
171+
target/deploy/$PROGRAM.so
172+
export BUFFER= # Export the logged buffer address from the command above
173+
solana program set-buffer-authority \
174+
--url $RPC_URL \
175+
--keypair $KEYPAIR \
176+
$BUFFER \
177+
--new-buffer-authority $MULTISIG
178+
```
179+
180+
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.
181+
182+
Start the IDL upgrade by writing it to the buffer:
183+
184+
```shell
185+
anchor idl write-buffer \
186+
--provider.cluster $RPC_URL \
187+
--provider.wallet $KEYPAIR \
188+
--filepath target/idl/$PROGRAM.json \
189+
$PROGRAM_ID
190+
export IDL_BUFFER= # Export the logged IDL buffer address from the command above
191+
anchor idl set-authority \
192+
--provider.cluster $RPC_URL \
193+
--provider.wallet $KEYPAIR \
194+
--program-id $PROGRAM_ID \
195+
--new-authority $MULTISIG \
196+
$IDL_BUFFER
197+
```
198+
199+
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:
200+
201+
```shell
202+
anchor run squadsIdlUpgrade -- \
203+
--programId $PROGRAM_ID \
204+
--idlBuffer $IDL_BUFFER \
205+
--multisig $MULTISIG \
206+
--closeRecipient $(solana address --keypair $KEYPAIR)
207+
```
208+
209+
#### Verify
210+
211+
Start with verifying locally that the deployed program matches the source code of the public repository:
212+
213+
```shell
214+
solana-verify verify-from-repo \
215+
--url $RPC_URL \
216+
--program-id $PROGRAM_ID \
217+
--library-name $PROGRAM \
218+
https://github.com/across-protocol/contracts
219+
```
220+
221+
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:
222+
223+
```shell
224+
solana-verify export-pda-tx \
225+
--url $RPC_URL \
226+
--program-id $PROGRAM_ID \
227+
--library-name $PROGRAM \
228+
--uploader $MULTISIG \
229+
https://github.com/across-protocol/contracts
230+
```
231+
232+
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.
233+
234+
Finally, submit the verification to OtterSec API (only works on mainnet):
235+
236+
```shell
237+
solana-verify remote submit-job \
238+
--url $RPC_URL \
239+
--program-id $PROGRAM_ID \
240+
--uploader $MULTISIG
241+
```
242+
64243
## Miscellaneous topics
65244

66245
### Manually Finalizing Scroll Claims from L2 -> L1 (Mainnet | Sepolia)

scripts/svm/buildHelpers/buildSolanaVerify.sh

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,9 @@ for program in programs/*; do
1919

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

2527
done

scripts/svm/createVault.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import { ASSOCIATED_TOKEN_PROGRAM_ID, TOKEN_PROGRAM_ID, getOrCreateAssociatedTok
88
import { PublicKey } from "@solana/web3.js";
99
import yargs from "yargs";
1010
import { hideBin } from "yargs/helpers";
11-
import { getSpokePoolProgram } from "../../src/svm/web3-v1";
11+
import { getSpokePoolProgram, SOLANA_SPOKE_STATE_SEED } from "../../src/svm/web3-v1";
1212

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

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

2626
async function createVault(): Promise<void> {
2727
const resolvedArgv = await argv;
28-
const seed = new BN(resolvedArgv.seed);
28+
const seed = resolvedArgv.seed ? new BN(resolvedArgv.seed) : SOLANA_SPOKE_STATE_SEED;
2929
const originToken = new PublicKey(resolvedArgv.originToken);
3030

3131
// Define the state account PDA

scripts/svm/initialize.ts

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { AnchorProvider, BN } from "@coral-xyz/anchor";
55
import { PublicKey, SystemProgram } from "@solana/web3.js";
66
import yargs from "yargs";
77
import { hideBin } from "yargs/helpers";
8-
import { evmAddressToPublicKey, getSpokePoolProgram } from "../../src/svm/web3-v1";
8+
import { evmAddressToPublicKey, getSpokePoolProgram, SOLANA_SPOKE_STATE_SEED } from "../../src/svm/web3-v1";
99

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

1616
// Parse arguments
1717
const argv = yargs(hideBin(process.argv))
18-
.option("seed", { type: "string", demandOption: true, describe: "Seed for the state account PDA" })
19-
.option("initNumbDeposits", { type: "string", demandOption: true, describe: "Init numb of deposits" })
18+
.option("seed", { type: "string", demandOption: false, describe: "Seed for the state account PDA" })
19+
.option("initNumbDeposits", { type: "string", demandOption: false, describe: "Init numb of deposits" })
2020
.option("chainId", { type: "string", demandOption: true, describe: "Chain ID" })
2121
.option("remoteDomain", { type: "number", demandOption: true, describe: "CCTP domain for Mainnet Ethereum" })
2222
.option("crossDomainAdmin", { type: "string", demandOption: true, describe: "HubPool on Mainnet Ethereum" })
23+
.option("svmAdmin", { type: "string", demandOption: false, describe: "SVM admin" })
2324
.option("depositQuoteTimeBuffer", {
2425
type: "number",
2526
demandOption: false,
@@ -29,17 +30,18 @@ const argv = yargs(hideBin(process.argv))
2930
.option("fillDeadlineBuffer", {
3031
type: "number",
3132
demandOption: false,
32-
default: 3600 * 4,
33+
default: 3600 * 6,
3334
describe: "Fill deadline buffer",
3435
}).argv;
3536

3637
async function initialize(): Promise<void> {
3738
const resolvedArgv = await argv;
38-
const seed = new BN(resolvedArgv.seed);
39-
const initialNumberOfDeposits = new BN(resolvedArgv.initNumbDeposits);
39+
const seed = resolvedArgv.seed ? new BN(resolvedArgv.seed) : SOLANA_SPOKE_STATE_SEED;
40+
const initialNumberOfDeposits = resolvedArgv.initNumbDeposits ? new BN(resolvedArgv.initNumbDeposits) : new BN(0);
4041
const chainId = new BN(resolvedArgv.chainId);
4142
const remoteDomain = resolvedArgv.remoteDomain;
4243
const crossDomainAdmin = evmAddressToPublicKey(resolvedArgv.crossDomainAdmin); // Use the function to cast the value
44+
const svmAdmin = resolvedArgv.svmAdmin ? new PublicKey(resolvedArgv.svmAdmin) : provider.wallet.publicKey;
4345
const depositQuoteTimeBuffer = resolvedArgv.depositQuoteTimeBuffer;
4446
const fillDeadlineBuffer = resolvedArgv.fillDeadlineBuffer;
4547

@@ -64,6 +66,7 @@ async function initialize(): Promise<void> {
6466
{ Property: "chainId", Value: chainId.toString() },
6567
{ Property: "remoteDomain", Value: remoteDomain.toString() },
6668
{ Property: "crossDomainAdmin", Value: crossDomainAdmin.toString() },
69+
{ Property: "svmAdmin", Value: svmAdmin.toString() },
6770
{ Property: "depositQuoteTimeBuffer", Value: depositQuoteTimeBuffer.toString() },
6871
{ Property: "fillDeadlineBuffer", Value: fillDeadlineBuffer.toString() },
6972
]);
@@ -87,6 +90,19 @@ async function initialize(): Promise<void> {
8790
.rpc();
8891

8992
console.log("Transaction signature:", tx);
93+
94+
if (!svmAdmin.equals(provider.wallet.publicKey)) {
95+
console.log("Transferring ownership to SVM admin...");
96+
const tx = await program.methods
97+
.transferOwnership(svmAdmin)
98+
.accountsPartial({
99+
state: statePda,
100+
signer: signer,
101+
})
102+
.rpc();
103+
104+
console.log("Transfer ownership transaction signature:", tx);
105+
}
90106
}
91107

92108
// Run the initialize function

0 commit comments

Comments
 (0)