[solana] Start to implement the solana receiver contract (#557)
* Start to implement the solana receiver contract * Use "cargo run" instead of the compiled binary under target/debug * Update cargo dependency * Add "pyth-" prefix to crate names * Remove the transfer step in cli which is only necessary for pythnet, not solana
This commit is contained in:
parent
66a7d243ac
commit
16caff1b04
|
@ -0,0 +1,7 @@
|
|||
|
||||
.anchor
|
||||
.DS_Store
|
||||
target
|
||||
**/*.rs.bk
|
||||
node_modules
|
||||
test-ledger
|
|
@ -0,0 +1,8 @@
|
|||
|
||||
.anchor
|
||||
.DS_Store
|
||||
target
|
||||
node_modules
|
||||
dist
|
||||
build
|
||||
test-ledger
|
|
@ -0,0 +1,15 @@
|
|||
[features]
|
||||
seeds = false
|
||||
skip-lint = false
|
||||
[programs.localnet]
|
||||
solana_receiver = "Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS"
|
||||
|
||||
[registry]
|
||||
url = "https://api.apr.dev"
|
||||
|
||||
[provider]
|
||||
cluster = "Localnet"
|
||||
wallet = "/home/yunhao/.config/solana/id.json"
|
||||
|
||||
[scripts]
|
||||
test = "yarn run ts-mocha -p ./tsconfig.json -t 1000000 tests/**/*.ts"
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,14 @@
|
|||
[workspace]
|
||||
members = [
|
||||
"programs/*",
|
||||
"cli/"
|
||||
]
|
||||
|
||||
[profile.release]
|
||||
overflow-checks = true
|
||||
lto = "fat"
|
||||
codegen-units = 1
|
||||
[profile.release.build-override]
|
||||
opt-level = 3
|
||||
incremental = false
|
||||
codegen-units = 1
|
|
@ -0,0 +1,4 @@
|
|||
ccli:
|
||||
cd cli; cargo build
|
||||
post_vaa:
|
||||
cd cli; cargo run post-price-vaa -v AQAAAAABAGL2NH+QGX4mcTw+F2uFuH8lC62485K1o/vxftbGKL1gauMUW6Lzb18JJLF4SJtkc9H5MnqC+Mpai+bDoj2Ztw8BY9p6/AAAAAAAAfNGGVrALzfWDU24/6bvdMsb41UAR1Q6Sp7prPTXhpewAAAAAAeb7UQBUDJXSAADAAAAAQIABQCVQxzC/Q70r0vHyF//ri9j1Rsm0WIXloLRSa5hmxIhwAv8MJRn3vpLGYxrW9WcCNtLnfsn3bzDLzFWDyF7T/j8KwAAACRAHYigAAAAAAPYMSD////4AAAAJCpaeTgAAAAAAqjStgEAAAABAAAAFQAAAABj2nr8AAAAAGPaevwAAAAAY9p6/AAAACRAHYigAAAAAAPYMSD0Kq+ITHsUVIlBcL4Krx2zm0t406VqJ/1JvYs57ywz12UQcfjHqyMhtr3TvHm5SlCEGpKm4GX547i5kmqPtaXRAAAAJO/rVQAAAAAABQG9AP////gAAAAk30Wg6AAAAAAFg7x3AQAAAAEAAAALAAAAAGPaevwAAAAAY9p6/AAAAABj2nr8AAAAJO/rVQAAAAAABQG9ABgB6wOAOvAkRSPuKobD8nsSar6JBNtLRagq21/iFwi0yoC6bcMuCNBvGqiGAR7tHXfHe+nrdhzBDXK30KL9V6YAAAAk8MYnegAAAAAD1cL6////+AAAACTf8vQAAAAAAANX/dkBAAAAAgAAAB4AAAAAY9p6/AAAAABj2nr8AAAAAGPaevwAAAAk8MYnegAAAAAD1cL6fd8Ngq9THwrxCdXpzp7Ce6nwDp7oq3HJEq//oW1xWDa3q9Jadt2v/fhHIk8DGYzLknI/kLJCnPM/Duy5bjUqhgAAACRKX0MAAAAAAIiMq2D////4AAAAJGpZPYAAAAAAf92Z9AEAAAACAAAADgAAAABj2nr8AAAAAGPaevwAAAAAY9p6/AAAACRKX0MAAAAAAIP9X0DVpcLzDga9bzjgHCxMjN18p8HBLUenM25Fn8bbQXG65mD9YbLZDrpH8oFQWoiGm2YTPZ3FjyA7AZ9apH8bOTQ+AAAAAAAAAAAAAAAAAAAAAP////gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAALAAAAAGPaevwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==
|
|
@ -0,0 +1,5 @@
|
|||
# pythnet-solana-receiver
|
||||
|
||||
To compile the cli, run `make ccli`.
|
||||
|
||||
To test post_vaa of the cli, run `make post_vaa`.
|
|
@ -0,0 +1,15 @@
|
|||
[package]
|
||||
name = "pyth-solana-receiver-cli"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0.65"
|
||||
base64 = "0.13.0"
|
||||
shellexpand = "2.1.2"
|
||||
solana-sdk = "1.10.31"
|
||||
solana-client = "1.10.31"
|
||||
anchor-client = "0.26.0"
|
||||
clap = {version ="3.2.22", features = ["derive"]}
|
||||
wormhole-core = { git = "https://github.com/guibescos/wormhole", branch = "reisen/sdk-solana"}
|
||||
wormhole-solana = { git = "https://github.com/guibescos/wormhole", branch = "reisen/sdk-solana"}
|
|
@ -0,0 +1,43 @@
|
|||
//! CLI options
|
||||
use {
|
||||
clap::{
|
||||
Parser,
|
||||
Subcommand,
|
||||
},
|
||||
};
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
#[clap(
|
||||
about = "A cli for the solana receiver contract",
|
||||
author = "Pyth Network Contributors"
|
||||
)]
|
||||
pub struct Cli {
|
||||
#[clap(subcommand)]
|
||||
pub action: Action,
|
||||
}
|
||||
|
||||
#[derive(Subcommand, Debug)]
|
||||
pub enum Action {
|
||||
#[clap(about = "Verify and post the price VAA on solana")]
|
||||
PostPriceVAA {
|
||||
#[clap(short = 'v', long,
|
||||
help = "Price VAA from Pythnet")]
|
||||
vaa: String,
|
||||
#[clap(
|
||||
short = 'k', long,
|
||||
default_value = "~/.config/solana/id.json",
|
||||
help = "Keypair of the transaction's funder"
|
||||
)]
|
||||
keypair: String,
|
||||
},
|
||||
|
||||
#[clap(about = "Invoke the on-chain contract decoding the VAA")]
|
||||
InvokePriceReceiver {
|
||||
#[clap(
|
||||
short = 'k', long,
|
||||
default_value = "~/.config/solana/id.json",
|
||||
help = "Keypair of the transaction's funder"
|
||||
)]
|
||||
keypair: String,
|
||||
},
|
||||
}
|
|
@ -0,0 +1,125 @@
|
|||
pub mod cli;
|
||||
|
||||
use {
|
||||
cli::{
|
||||
Action,
|
||||
Cli,
|
||||
},
|
||||
clap::Parser,
|
||||
anyhow::Result,
|
||||
std::str::FromStr,
|
||||
|
||||
solana_sdk::{
|
||||
pubkey::Pubkey,
|
||||
signature::{
|
||||
read_keypair_file,
|
||||
Keypair,
|
||||
},
|
||||
signer::Signer,
|
||||
instruction::Instruction,
|
||||
transaction::Transaction,
|
||||
},
|
||||
|
||||
wormhole::VAA,
|
||||
wormhole_solana::{
|
||||
Account,
|
||||
GuardianSet,
|
||||
Config as WormholeConfig,
|
||||
instructions::{
|
||||
post_vaa,
|
||||
verify_signatures_txs,
|
||||
PostVAAData,
|
||||
},
|
||||
},
|
||||
|
||||
solana_client::rpc_client::RpcClient,
|
||||
anchor_client::anchor_lang::AnchorDeserialize,
|
||||
};
|
||||
|
||||
fn main() -> Result<()> {
|
||||
let cli = Cli::parse();
|
||||
|
||||
match cli.action {
|
||||
Action::PostPriceVAA { vaa, keypair } => {
|
||||
println!("PostPriceVAA is invoked with vaa\"{}\"", vaa);
|
||||
// Hard-coded strings
|
||||
let rpc_client = RpcClient::new("https://api.devnet.solana.com");
|
||||
// Is RpcClient::new_with_commitment necessary?
|
||||
let wormhole = Pubkey::from_str("3u8hJUVTA4jH1wYAyUur7FFZVQ8H635K3tSHHF4ssjQ5").unwrap();
|
||||
|
||||
println!("Decode the VAA");
|
||||
let vaa_bytes: Vec<u8> = base64::decode(vaa)?;
|
||||
let vaa = VAA::from_bytes(vaa_bytes.clone())?;
|
||||
|
||||
println!("Get wormhole guardian set configuration");
|
||||
let wormhole_config = WormholeConfig::key(&wormhole, ());
|
||||
let wormhole_config_data =
|
||||
WormholeConfig::try_from_slice(&rpc_client.get_account_data(&wormhole_config)?)?;
|
||||
|
||||
let guardian_set = GuardianSet::key(&wormhole, wormhole_config_data.guardian_set_index);
|
||||
let guardian_set_data =
|
||||
GuardianSet::try_from_slice(&rpc_client.get_account_data(&guardian_set)?)?;
|
||||
|
||||
println!("Invoke wormhole on solana to verify the VAA");
|
||||
let payer =
|
||||
read_keypair_file(&*shellexpand::tilde(&keypair)).expect("Keypair not found");
|
||||
let signature_set_keypair = Keypair::new();
|
||||
let verify_txs = verify_signatures_txs(
|
||||
vaa_bytes.as_slice(),
|
||||
guardian_set_data,
|
||||
wormhole,
|
||||
payer.pubkey(),
|
||||
wormhole_config_data.guardian_set_index,
|
||||
signature_set_keypair.pubkey(),
|
||||
)?;
|
||||
|
||||
for tx in verify_txs {
|
||||
process_transaction(&rpc_client, tx, &vec![&payer, &signature_set_keypair])?;
|
||||
}
|
||||
|
||||
println!("Upload the VAA data to a solana account");
|
||||
let post_vaa_data = PostVAAData {
|
||||
version: vaa.version,
|
||||
guardian_set_index: vaa.guardian_set_index,
|
||||
timestamp: vaa.timestamp,
|
||||
nonce: vaa.nonce,
|
||||
emitter_chain: vaa.emitter_chain.into(),
|
||||
emitter_address: vaa.emitter_address,
|
||||
sequence: vaa.sequence,
|
||||
consistency_level: vaa.consistency_level,
|
||||
payload: vaa.payload,
|
||||
};
|
||||
|
||||
process_transaction(
|
||||
&rpc_client,
|
||||
vec![post_vaa(
|
||||
wormhole,
|
||||
payer.pubkey(),
|
||||
signature_set_keypair.pubkey(),
|
||||
post_vaa_data,
|
||||
)?],
|
||||
&vec![&payer],
|
||||
)?;
|
||||
}
|
||||
|
||||
Action::InvokePriceReceiver { keypair } => {
|
||||
println!("TBD, keypair={}", keypair);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn process_transaction(
|
||||
rpc_client: &RpcClient,
|
||||
instructions: Vec<Instruction>,
|
||||
signers: &Vec<&Keypair>,
|
||||
) -> Result<()> {
|
||||
let mut transaction =
|
||||
Transaction::new_with_payer(instructions.as_slice(), Some(&signers[0].pubkey()));
|
||||
transaction.sign(signers, rpc_client.get_latest_blockhash()?);
|
||||
let transaction_signature =
|
||||
rpc_client.send_and_confirm_transaction_with_spinner(&transaction)?;
|
||||
println!("Transaction successful : {transaction_signature:?}");
|
||||
Ok(())
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
// Migrations are an early feature. Currently, they're nothing more than this
|
||||
// single deploy script that's invoked from the CLI, injecting a provider
|
||||
// configured from the workspace's Anchor.toml.
|
||||
|
||||
const anchor = require("@project-serum/anchor");
|
||||
|
||||
module.exports = async function (provider) {
|
||||
// Configure client to use the provider.
|
||||
anchor.setProvider(provider);
|
||||
|
||||
// Add your deploy script here.
|
||||
};
|
|
@ -0,0 +1,19 @@
|
|||
[package]
|
||||
name = "pyth-solana-receiver"
|
||||
version = "0.1.0"
|
||||
description = "Created with Anchor"
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib", "lib"]
|
||||
name = "pyth_solana_receiver"
|
||||
|
||||
[features]
|
||||
no-entrypoint = []
|
||||
no-idl = []
|
||||
no-log-ix-name = []
|
||||
cpi = ["no-entrypoint"]
|
||||
default = []
|
||||
|
||||
[dependencies]
|
||||
anchor-lang = "0.26.0"
|
|
@ -0,0 +1,2 @@
|
|||
[target.bpfel-unknown-unknown.dependencies.std]
|
||||
features = []
|
|
@ -0,0 +1,15 @@
|
|||
use anchor_lang::prelude::*;
|
||||
|
||||
declare_id!("Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS");
|
||||
|
||||
#[program]
|
||||
pub mod solana_receiver {
|
||||
use super::*;
|
||||
|
||||
pub fn initialize(ctx: Context<Initialize>) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Accounts)]
|
||||
pub struct Initialize {}
|
Loading…
Reference in New Issue