Skip to content

feat(governance, lazer): add scripts for setting ecdsa signer for lazer solana program #2515

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

Merged
merged 1 commit into from
Mar 24, 2025
Merged
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
70 changes: 70 additions & 0 deletions governance/xc_admin/packages/xc_admin_cli/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -972,4 +972,74 @@ multisigCommand(
);
});

multisigCommand(
"upgrade-program-and-set-trusted-ecdsa-signer",
"Upgrade the Lazer program and set a trusted ECDSA signer",
)
.requiredOption("-b, --buffer <pubkey>", "buffer account for the upgrade")
.requiredOption(
"-s, --signer <address>",
"public address (hex) of the trusted ECDSA signer to add/update",
)
.requiredOption(
"-e, --expiry-time <seconds>",
"expiry time in seconds since Unix epoch. Set to 0 to remove the signer.",
)
.action(async (options: any) => {
const vault = await loadVaultFromOptions(options);
const targetCluster: PythCluster = options.cluster;

const buffer: PublicKey = new PublicKey(options.buffer);
const trustedSigner = Buffer.from(options.signer, "hex");
const expiryTime = new BN(options.expiryTime);

const programId = SOLANA_LAZER_PROGRAM_ID;
const programDataAccount = PublicKey.findProgramAddressSync(
[programId.toBuffer()],
BPF_UPGRADABLE_LOADER,
)[0];

// This is intruction is not in @solana/web3.js, source : https://docs.rs/solana-program/latest/src/solana_program/bpf_loader_upgradeable.rs.html#200
const upgradeInstruction: TransactionInstruction = {
programId: BPF_UPGRADABLE_LOADER,
// 4-bytes instruction discriminator, got it from https://docs.rs/solana-program/latest/src/solana_program/loader_upgradeable_instruction.rs.html#104
data: Buffer.from([3, 0, 0, 0]),
keys: [
{ pubkey: programDataAccount, isSigner: false, isWritable: true },
{ pubkey: programId, isSigner: false, isWritable: true },
{ pubkey: buffer, isSigner: false, isWritable: true },
{ pubkey: vault.wallet.publicKey, isSigner: false, isWritable: true },
{ pubkey: SYSVAR_RENT_PUBKEY, isSigner: false, isWritable: false },
{ pubkey: SYSVAR_CLOCK_PUBKEY, isSigner: false, isWritable: false },
{
pubkey: await vault.getVaultAuthorityPDA(targetCluster),
isSigner: true,
isWritable: false,
},
],
};

// Create Anchor program instance
const lazerProgram = new Program(
lazerIdl as Idl,
SOLANA_LAZER_PROGRAM_ID,
vault.getAnchorProvider(),
);

// Use Anchor to create the instruction
const updateSignerInstruction = await lazerProgram.methods
.updateEcdsaSigner(trustedSigner, expiryTime)
.accounts({
topAuthority: await vault.getVaultAuthorityPDA(targetCluster),
storage: SOLANA_STORAGE_ID,
})
.instruction();

await vault.proposeInstructions(
[upgradeInstruction, updateSignerInstruction],
targetCluster,
DEFAULT_PRIORITY_FEE_CONFIG,
);
});

program.parse();
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,36 @@
}
]
},
{
"name": "updateEcdsaSigner",
"accounts": [
{
"name": "topAuthority",
"isMut": false,
"isSigner": true
},
{
"name": "storage",
"isMut": true,
"isSigner": false
}
],
"args": [
{
"name": "trustedSigner",
"type": {
"array": [
"u8",
20
]
}
},
{
"name": "expiresAt",
"type": "i64"
}
]
},
{
"name": "verifyMessage",
"docs": [
Expand Down
52 changes: 52 additions & 0 deletions lazer/contracts/solana/scripts/add_ecdsa_signer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import * as anchor from "@coral-xyz/anchor";
import { Program } from "@coral-xyz/anchor";
import { PythLazerSolanaContract } from "../target/types/pyth_lazer_solana_contract";
import * as pythLazerSolanaContractIdl from "../target/idl/pyth_lazer_solana_contract.json";
import yargs from "yargs/yargs";
import { readFileSync } from "fs";
import NodeWallet from "@coral-xyz/anchor/dist/cjs/nodewallet";

// Add a trusted signer or change its expiry time.
//
// Example:
// pnpm ts-node scripts/add_ecdsa_signer.ts --url 'https://api.testnet.solana.com' \
// --keypair-path .../key.json --trusted-signer b8d50f0bae75bf6e03c104903d7c3afc4a6596da \
// --expiry-time-seconds 2057930841
async function main() {
let argv = await yargs(process.argv.slice(2))
.options({
url: { type: "string", demandOption: true },
"keypair-path": { type: "string", demandOption: true },
"trusted-signer": { type: "string", demandOption: true },
"expiry-time-seconds": { type: "number", demandOption: true },
})
.parse();

const keypair = anchor.web3.Keypair.fromSecretKey(
new Uint8Array(JSON.parse(readFileSync(argv.keypairPath, "ascii"))),
);

const wallet = new NodeWallet(keypair);
const connection = new anchor.web3.Connection(argv.url, {
commitment: "confirmed",
});
const provider = new anchor.AnchorProvider(connection, wallet);

const program: Program<PythLazerSolanaContract> = new Program(
pythLazerSolanaContractIdl as PythLazerSolanaContract,
provider,
);

await program.methods
.updateEcdsaSigner(
[...Buffer.from(argv.trustedSigner, "hex")],
new anchor.BN(argv.expiryTimeSeconds),
)
.accounts({
payer: wallet.publicKey,
})
.rpc();
console.log("signer updated");
}

main();
34 changes: 31 additions & 3 deletions lazer/contracts/solana/scripts/check_trusted_signer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,37 @@ async function main() {
console.log("\nTrusted Signers:");
console.log("----------------");

for (const signer of storage.trustedSigners) {
if (signer.pubkey.equals(anchor.web3.PublicKey.default)) continue;
console.log(`\nPublic Key: ${signer.pubkey.toBase58()}`);
const trustedSigners = storage.trustedSigners.slice(
0,
storage.numTrustedSigners,
);
for (const signer of trustedSigners) {
console.log(
`\nPublic Key: ${(signer.pubkey as anchor.web3.PublicKey).toBase58()}`,
);
console.log(
`Expires At: ${new Date(
signer.expiresAt.toNumber() * 1000,
).toISOString()}`,
);
console.log(
`Active: ${
signer.expiresAt.toNumber() > Date.now() / 1000 ? "Yes" : "No"
}`,
);
}

console.log("\nTrusted ECDSA Signers:");
console.log("----------------");

const trustedEcdsaSigners = storage.trustedEcdsaSigners.slice(
0,
storage.numTrustedEcdsaSigners,
);
for (const signer of trustedEcdsaSigners) {
console.log(
`\nPublic Address: ${Buffer.from(signer.pubkey as number[]).toString("hex")}`,
);
console.log(
`Expires At: ${new Date(
signer.expiresAt.toNumber() * 1000,
Expand Down
43 changes: 43 additions & 0 deletions lazer/contracts/solana/scripts/verify_ecdsa_message.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import * as anchor from "@coral-xyz/anchor";
import { Program } from "@coral-xyz/anchor";
import { PythLazerSolanaContract } from "../target/types/pyth_lazer_solana_contract";
import * as pythLazerSolanaContractIdl from "../target/idl/pyth_lazer_solana_contract.json";
import yargs from "yargs/yargs";
import { readFileSync } from "fs";
import NodeWallet from "@coral-xyz/anchor/dist/cjs/nodewallet";

async function main() {
let argv = await yargs(process.argv.slice(2))
.options({
url: { type: "string", demandOption: true },
"keypair-path": { type: "string", demandOption: true },
message: { type: "string", demandOption: true },
})
.parse();

const keypair = anchor.web3.Keypair.fromSecretKey(
new Uint8Array(JSON.parse(readFileSync(argv.keypairPath, "ascii"))),
);

const wallet = new NodeWallet(keypair);
const connection = new anchor.web3.Connection(argv.url, {
commitment: "confirmed",
});
const provider = new anchor.AnchorProvider(connection, wallet);

const program: Program<PythLazerSolanaContract> = new Program(
pythLazerSolanaContractIdl as PythLazerSolanaContract,
provider,
);

await program.methods
.verifyEcdsaMessage(Buffer.from(argv.message, "hex"))
.accounts({
payer: wallet.publicKey,
})
.rpc();

console.log("message is valid");
}

main();
Loading