diff --git a/lazer/Cargo.lock b/lazer/Cargo.lock index 77f356219..69f622701 100644 --- a/lazer/Cargo.lock +++ b/lazer/Cargo.lock @@ -3176,7 +3176,7 @@ dependencies = [ [[package]] name = "pyth-lazer-protocol" -version = "0.1.1" +version = "0.1.2" dependencies = [ "anyhow", "byteorder", diff --git a/lazer/contracts/solana/package.json b/lazer/contracts/solana/package.json index a8273a2c2..cb52148d7 100644 --- a/lazer/contracts/solana/package.json +++ b/lazer/contracts/solana/package.json @@ -7,7 +7,8 @@ "test:format": "prettier --check **/*.*", "test:anchor": "CARGO_TARGET_DIR=\"$PWD/target\" anchor test", "test": "pnpm run test:format && pnpm run test:anchor", - "setup": "anchor build && pnpm ts-node scripts/setup.ts" + "setup": "anchor build && pnpm ts-node scripts/setup.ts", + "migrate_from_0_1_0": "pnpm ts-node scripts/migrate_from_0_1_0.ts" }, "dependencies": { "@coral-xyz/anchor": "^0.30.1" diff --git a/lazer/contracts/solana/programs/pyth-lazer-solana-contract/Cargo.toml b/lazer/contracts/solana/programs/pyth-lazer-solana-contract/Cargo.toml index d04b57e95..f4c43cfa9 100644 --- a/lazer/contracts/solana/programs/pyth-lazer-solana-contract/Cargo.toml +++ b/lazer/contracts/solana/programs/pyth-lazer-solana-contract/Cargo.toml @@ -19,7 +19,7 @@ no-log-ix-name = [] idl-build = ["anchor-lang/idl-build"] [dependencies] -pyth-lazer-protocol = { version = "0.1.0", path = "../../../../sdk/rust/protocol" } +pyth-lazer-protocol = { version = "0.1.2", path = "../../../../sdk/rust/protocol" } anchor-lang = "0.30.1" bytemuck = "1.20.0" diff --git a/lazer/contracts/solana/scripts/migrate_from_0_1_0.ts b/lazer/contracts/solana/scripts/migrate_from_0_1_0.ts new file mode 100644 index 000000000..4aae0a9ce --- /dev/null +++ b/lazer/contracts/solana/scripts/migrate_from_0_1_0.ts @@ -0,0 +1,75 @@ +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"; + +// This script tops up the storage PDA and calls `migrateFrom010` on the contract. +async function main() { + let argv = await yargs(process.argv.slice(2)) + .options({ + url: { type: "string", demandOption: true }, + "keypair-path": { type: "string", demandOption: true }, + treasury: { 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 = new Program( + pythLazerSolanaContractIdl as PythLazerSolanaContract, + provider + ); + + const storagePdaKey = new anchor.web3.PublicKey( + "3rdJbqfnagQ4yx9HXJViD4zc4xpiSqmFsKpPuSCQVyQL" + ); + const storagePdaInfo = await provider.connection.getAccountInfo( + storagePdaKey + ); + const newStorageSize = 381; + if (storagePdaInfo.data.length == newStorageSize) { + console.log("Already migrated"); + const storage = await program.account.storage.all(); + console.log("storage account: ", storage); + return; + } + const minBalance = + await provider.connection.getMinimumBalanceForRentExemption(newStorageSize); + if (storagePdaInfo.lamports < minBalance) { + console.log("storage PDA needs top-up"); + const transaction = new anchor.web3.Transaction().add( + anchor.web3.SystemProgram.transfer({ + fromPubkey: keypair.publicKey, + toPubkey: storagePdaKey, + lamports: minBalance - storagePdaInfo.lamports, + }) + ); + const signature = await anchor.web3.sendAndConfirmTransaction( + provider.connection, + transaction, + [keypair] + ); + console.log("signature:", signature); + } else { + console.log("storage PDA doesn't need top-up"); + } + + console.log("executing migration"); + const signature2 = await program.methods + .migrateFrom010(new anchor.web3.PublicKey(argv.treasury)) + .accounts({}) + .rpc(); + console.log("signature:", signature2); +} + +main(); diff --git a/lazer/sdk/rust/protocol/Cargo.toml b/lazer/sdk/rust/protocol/Cargo.toml index 15b08c91e..1fc6d4a89 100644 --- a/lazer/sdk/rust/protocol/Cargo.toml +++ b/lazer/sdk/rust/protocol/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "pyth-lazer-protocol" -version = "0.1.1" +version = "0.1.2" edition = "2021" description = "Pyth Lazer SDK - protocol types." license = "Apache-2.0" diff --git a/lazer/sdk/rust/protocol/src/lib.rs b/lazer/sdk/rust/protocol/src/lib.rs index f81fca98a..bce03b4ee 100644 --- a/lazer/sdk/rust/protocol/src/lib.rs +++ b/lazer/sdk/rust/protocol/src/lib.rs @@ -1,5 +1,6 @@ //! Protocol types. +pub mod message; pub mod payload; pub mod publisher; pub mod router; diff --git a/lazer/sdk/rust/protocol/src/message.rs b/lazer/sdk/rust/protocol/src/message.rs new file mode 100644 index 000000000..c3413fe65 --- /dev/null +++ b/lazer/sdk/rust/protocol/src/message.rs @@ -0,0 +1,113 @@ +use { + crate::payload::{EVM_FORMAT_MAGIC, SOLANA_FORMAT_MAGIC_LE}, + anyhow::bail, + byteorder::{ReadBytesExt, WriteBytesExt, BE, LE}, + std::io::{Cursor, Read, Write}, +}; + +/// EVM signature enveope. +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct EvmMessage { + pub payload: Vec, + pub signature: [u8; 64], + pub recovery_id: u8, +} + +impl EvmMessage { + pub fn serialize(&self, mut writer: impl Write) -> anyhow::Result<()> { + writer.write_u32::(EVM_FORMAT_MAGIC)?; + writer.write_all(&self.signature)?; + writer.write_u8(self.recovery_id)?; + writer.write_u16::(self.payload.len().try_into()?)?; + writer.write_all(&self.payload)?; + Ok(()) + } + + pub fn deserialize_slice(data: &[u8]) -> anyhow::Result { + Self::deserialize(Cursor::new(data)) + } + + pub fn deserialize(mut reader: impl Read) -> anyhow::Result { + let magic = reader.read_u32::()?; + if magic != EVM_FORMAT_MAGIC { + bail!("magic mismatch"); + } + let mut signature = [0u8; 64]; + reader.read_exact(&mut signature)?; + let recovery_id = reader.read_u8()?; + let payload_len: usize = reader.read_u16::()?.into(); + let mut payload = vec![0u8; payload_len]; + reader.read_exact(&mut payload)?; + Ok(Self { + payload, + signature, + recovery_id, + }) + } +} + +/// Solana signature envelope. +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct SolanaMessage { + pub payload: Vec, + pub signature: [u8; 64], + pub public_key: [u8; 32], +} + +impl SolanaMessage { + pub fn serialize(&self, mut writer: impl Write) -> anyhow::Result<()> { + writer.write_u32::(SOLANA_FORMAT_MAGIC_LE)?; + writer.write_all(&self.signature)?; + writer.write_all(&self.public_key)?; + writer.write_u16::(self.payload.len().try_into()?)?; + writer.write_all(&self.payload)?; + Ok(()) + } + + pub fn deserialize_slice(data: &[u8]) -> anyhow::Result { + Self::deserialize(Cursor::new(data)) + } + + pub fn deserialize(mut reader: impl Read) -> anyhow::Result { + let magic = reader.read_u32::()?; + if magic != SOLANA_FORMAT_MAGIC_LE { + bail!("magic mismatch"); + } + let mut signature = [0u8; 64]; + reader.read_exact(&mut signature)?; + let mut public_key = [0u8; 32]; + reader.read_exact(&mut public_key)?; + let payload_len: usize = reader.read_u16::()?.into(); + let mut payload = vec![0u8; payload_len]; + reader.read_exact(&mut payload)?; + Ok(Self { + payload, + signature, + public_key, + }) + } +} + +#[test] +fn test_evm_serde() { + let m1 = EvmMessage { + payload: vec![1, 2, 4, 3], + signature: [5; 64], + recovery_id: 1, + }; + let mut buf = Vec::new(); + m1.serialize(&mut buf).unwrap(); + assert_eq!(m1, EvmMessage::deserialize_slice(&buf).unwrap()); +} + +#[test] +fn test_solana_serde() { + let m1 = SolanaMessage { + payload: vec![1, 2, 4, 3], + signature: [5; 64], + public_key: [6; 32], + }; + let mut buf = Vec::new(); + m1.serialize(&mut buf).unwrap(); + assert_eq!(m1, SolanaMessage::deserialize_slice(&buf).unwrap()); +}