Start depending on the upstream out-of-tree Wormhole (#258)

* Add out-of-tree dependency on wormhole v2.8.9

This lets us outsource all wormhole/solitaire code to the upstream
certusone repo

* Docker.client: Use cargo-install to install binaries from github

* p2w: fix solana-program wasm-bindgen

* pyth2wormhole: Fix code-level errors against newer solitaire

* p2w: Use upstream wormhole rust code, bump guardian to 2.8.9

* guardian: ensure solana is on, spy: bump image to 2.8.9

Co-authored-by: Reisen <reisen@morphism.org>
This commit is contained in:
Stanisław Drozd 2022-08-29 16:18:41 +02:00 committed by GitHub
parent b485ef4d16
commit a2a0f6e15b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
93 changed files with 674 additions and 13873 deletions

View File

@ -1,22 +1,19 @@
#syntax=docker/dockerfile:1.2@sha256:e2a8561e419ab1ba6b2fe6cbdf49fd92b95912df1cf7d313c3e2230a333fdbcc
FROM docker.io/library/rust:1.49@sha256:a50165ea96983c21832578afb1c8c028674c965bc1ed43b607871b1f362e06a5
FROM ghcr.io/certusone/solana:1.10.31@sha256:d31e8db926a1d3fbaa9d9211d9979023692614b7b64912651aba0383e8c01bad AS solana
RUN apt-get update && apt-get install -yq libssl-dev libudev-dev pkg-config zlib1g-dev llvm clang ncat
ARG WORMHOLE_TAG=v2.8.9
# libudev is needed by spl-token-cli, and ncat is needed by the devnet setup
# script to be able to signal a health status for tilt
RUN apt-get update && apt-get install -yq libudev-dev ncat
RUN curl -fsSL https://deb.nodesource.com/setup_16.x | bash - && apt-get install -y nodejs
RUN curl -sSfL https://release.solana.com/v1.10.13/install | sh
RUN rustup default nightly-2022-02-01
RUN rustup component add rustfmt
COPY solana /usr/src/solana
WORKDIR /usr/src/solana
RUN --mount=type=cache,target=/root/.cache \
cargo install --version =2.0.12 spl-token-cli
cargo install --version =2.0.12 --locked spl-token-cli
ENV SOLANA_BIN_PATH="/root/.local/share/solana/install/active_release/bin"
ENV PATH="$SOLANA_BIN_PATH:$PATH"
ADD solana /usr/src/solana
WORKDIR /usr/src/solana
RUN solana config set --keypair "/usr/src/solana/keys/solana-devnet.json"
RUN solana config set --url "http://solana-devnet:8899"
@ -25,7 +22,7 @@ ENV EMITTER_ADDRESS="11111111111111111111111111111115"
ENV BRIDGE_ADDRESS="Bridge1p5gheXUvJ6jGWGeCsgPKgnE3YgdGKRVCMY9o"
RUN --mount=type=cache,target=/root/.cache \
--mount=type=cache,target=bridge/target \
--mount=type=cache,target=/usr/local/cargo/registry,id=cargo_registry \
set -xe && \
cargo build --manifest-path ./bridge/Cargo.toml --package client --release --locked && \
cp bridge/target/release/client /usr/local/bin
cargo install bridge_client --git https://github.com/certusone/wormhole --tag $WORMHOLE_TAG --locked --root /usr/local && \
cargo install token_bridge_client --git https://github.com/certusone/wormhole --tag $WORMHOLE_TAG --locked --root /usr/local

View File

@ -13,21 +13,28 @@ RUN apt-get update && \
zlib1g-dev \
&& \
rm -rf /var/lib/apt/lists/* && \
rustup component add rustfmt && \
rustup default nightly-2022-01-31
rustup component add rustfmt
RUN sh -c "$(curl -sSfL https://release.solana.com/v1.10.13/install)"
RUN sh -c "$(curl -sSfL https://release.solana.com/v1.10.31/install)"
ENV PATH="/root/.local/share/solana/install/active_release/bin:$PATH"
ADD solana/rust-toolchain /rust-toolchain
# Solana does a questionable download at the beginning of a *first* build-bpf call. Trigger and layer-cache it explicitly.
RUN cargo init --lib /tmp/decoy-crate && \
cd /tmp/decoy-crate && cargo build-bpf && \
rm -rf /tmp/decoy-crate
# Add bridge contract sources
WORKDIR /usr/src/bridge
ARG WORMHOLE_REV=2.8.9
ADD https://github.com/certusone/wormhole/archive/refs/tags/v${WORMHOLE_REV}.tar.gz .
RUN tar -xvf v${WORMHOLE_REV}.tar.gz
RUN mv wormhole-${WORMHOLE_REV} wormhole
# RUN mkdir -p /usr/src/bridge/wormhole/solana/target
ADD solana solana
ADD third_party/pyth/p2w-sdk/rust third_party/pyth/p2w-sdk/rust
RUN mkdir -p /opt/solana/deps
@ -35,18 +42,17 @@ RUN mkdir -p /opt/solana/deps
ENV EMITTER_ADDRESS="11111111111111111111111111111115"
ENV BRIDGE_ADDRESS="Bridge1p5gheXUvJ6jGWGeCsgPKgnE3YgdGKRVCMY9o"
WORKDIR /usr/src/bridge/solana
# Build Wormhole Solana programs
RUN --mount=type=cache,target=solana/bridge/target \
--mount=type=cache,target=solana/modules/token_bridge/target \
--mount=type=cache,target=solana/modules/nft_bridge/target \
--mount=type=cache,target=solana/pyth2wormhole/target \
--mount=type=cache,target=solana/migration/target \
cargo build-bpf --manifest-path "solana/bridge/program/Cargo.toml" -- --locked && \
cargo build-bpf --manifest-path "solana/bridge/cpi_poster/Cargo.toml" -- --locked && \
cargo build-bpf --manifest-path "solana/pyth2wormhole/program/Cargo.toml" -- --locked && \
cp solana/bridge/target/deploy/bridge.so /opt/solana/deps/bridge.so && \
cp solana/bridge/target/deploy/cpi_poster.so /opt/solana/deps/cpi_poster.so && \
cp solana/pyth2wormhole/target/deploy/pyth2wormhole.so /opt/solana/deps/pyth2wormhole.so
RUN --mount=type=cache,target=../wormhole/solana/target \
--mount=type=cache,target=pyth2wormhole/target \
cargo build-bpf --manifest-path "../wormhole/solana/bridge/program/Cargo.toml" -- --locked && \
cargo build-bpf --manifest-path "../wormhole/solana/bridge/cpi_poster/Cargo.toml" -- --locked && \
cargo build-bpf --manifest-path "pyth2wormhole/program/Cargo.toml" -- --locked && \
cp ../wormhole/solana/target/deploy/bridge.so /opt/solana/deps/bridge.so && \
cp ../wormhole/solana/target/deploy/cpi_poster.so /opt/solana/deps/cpi_poster.so && \
cp pyth2wormhole/target/deploy/pyth2wormhole.so /opt/solana/deps/pyth2wormhole.so
COPY --from=pyth-oracle-copy /home/pyth/pyth-client/target/oracle.so /opt/solana/deps/pyth_oracle.so

View File

@ -5,6 +5,8 @@ RUN apt-get update && \
apt-get install -y libssl-dev libudev-dev pkg-config zlib1g-dev llvm clang && \
apt-get clean && rm -rf /var/lib/apt/lists/*
# We default an older nightly since current rust-toolchain makes the
# wasm-pack build unhappy, we introduce it later for our code
RUN rustup component add rustfmt && \
rustup default nightly-2022-01-02

595
cosmwasm/Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -29,7 +29,6 @@ hex = "0.4.2"
lazy_static = "1.4.0"
bigint = "4"
p2w-sdk = { path = "../../../third_party/pyth/p2w-sdk/rust" }
solana-program = "=1.8.16"
pyth-sdk = "0.4.2"
[dev-dependencies]

View File

@ -53,7 +53,7 @@ spec:
path: bigtable-key.json
containers:
- name: guardiand
image: ghcr.io/certusone/guardiand:v2.8.8.1
image: ghcr.io/certusone/guardiand:v2.8.9
volumeMounts:
- mountPath: /run/node
name: node-rundir
@ -77,8 +77,6 @@ spec:
- ws://eth-devnet:8545
- --auroraRPC
- ws://eth-devnet:8545
- --celoRPC
- ws://eth-devnet:8545
- --fantomRPC
- ws://eth-devnet:8545
- --oasisRPC
@ -89,12 +87,34 @@ spec:
- ws://eth-devnet:8545
- --klaytnRPC
- ws://eth-devnet:8545
- --celoRPC
- ws://eth-devnet:8545
- --moonbeamRPC
- ws://eth-devnet:8545
- --neonRPC
- ws://eth-devnet:8545
- --terraWS
- ws://terra-terrad:26657/websocket
- --terraLCD
- http://terra-terrad:1317
- --terraContract
- terra14hj2tavq8fpesdwxxcu44rty3hh90vhujrvcmstl4zr3txmfvw9ssrc8au
# - --terra2WS
# - ws://terra2-terrad:26657/websocket
# - --terra2LCD
# - http://terra2-terrad:1317
# - --terra2Contract
# - terra14hj2tavq8fpesdwxxcu44rty3hh90vhujrvcmstl4zr3txmfvw9ssrc8au
# - --algorandAppID
# - "4"
# - --algorandIndexerRPC
# - http://algorand:8980
# - --algorandIndexerToken
# - "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
# - --algorandAlgodRPC
# - http://algorand:4001
# - --algorandAlgodToken
# - "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
- --solanaContract
- Bridge1p5gheXUvJ6jGWGeCsgPKgnE3YgdGKRVCMY9o
- --solanaWS
@ -112,7 +132,8 @@ spec:
- /tmp/admin.sock
- --dataDir
- /tmp/data
# - --logLevel=debug
# - --chainGovernorEnabled=true
# - --logLevel=debug
securityContext:
capabilities:
add:

View File

@ -35,7 +35,7 @@ spec:
terminationGracePeriodSeconds: 0
containers:
- name: spy
image: ghcr.io/certusone/guardiand:v2.8.8.1
image: ghcr.io/certusone/guardiand:v2.8.9
command:
- /guardiand
- spy

4271
solana/bridge/Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -1,5 +0,0 @@
[workspace]
members = ["program", "client", "program_stub", "cpi_poster"]
[patch.crates-io]
memmap2 = { path = "memmap2-rs" }

View File

@ -1,22 +0,0 @@
[package]
name = "client"
version = "0.1.0"
authors = ["Stan Drozd <stan@nexantic.com>"]
edition = "2018"
[dependencies]
anyhow = "1.0.40"
borsh = "=0.9.3"
clap = "2.33.0"
cpi-poster = { path = "../cpi_poster", features = ["no-entrypoint"] }
hex = "0.4.3"
rand = "0.7.3"
shellexpand = "2.1.0"
solana-client = "=1.10.13"
solana-program = "=1.10.13"
solana-sdk = "=1.10.13"
solana-cli-config = "=1.10.13"
solana-clap-utils = "=1.10.13"
solitaire = { path = "../../solitaire/program" }
solitaire-client = { path = "../../solitaire/client" }
wormhole-bridge-solana = { path = "../program", features = ["client"] }

View File

@ -1,506 +0,0 @@
#![feature(adt_const_params)]
#![allow(warnings)]
use std::{
fmt::Display,
mem::size_of,
process::exit,
};
use borsh::BorshDeserialize;
use bridge::{
accounts::{
Bridge,
BridgeData,
FeeCollector,
},
};
use clap::{
crate_description,
crate_name,
crate_version,
value_t,
App,
AppSettings,
Arg,
SubCommand,
};
use hex;
use solana_clap_utils::{
input_parsers::{
keypair_of,
pubkey_of,
value_of,
},
input_validators::{
is_keypair,
is_pubkey_or_keypair,
is_url,
},
};
use solana_client::{
rpc_client::RpcClient,
rpc_config::RpcSendTransactionConfig,
};
use solana_sdk::{
commitment_config::{
CommitmentConfig,
CommitmentLevel,
},
native_token::*,
program_error::ProgramError::AccountAlreadyInitialized,
pubkey::Pubkey,
signature::{
read_keypair_file,
Keypair,
Signer,
},
system_instruction::transfer,
transaction::Transaction,
};
use solitaire::{
processors::seeded::Seeded,
AccountState,
Info,
};
use solitaire_client::Derive;
struct Config {
rpc_client: RpcClient,
owner: Keypair,
fee_payer: Keypair,
commitment_config: CommitmentConfig,
}
type Error = Box<dyn std::error::Error>;
type CommmandResult = Result<Option<Transaction>, Error>;
fn command_deploy_bridge(
config: &Config,
bridge: &Pubkey,
initial_guardians: Vec<[u8; 20]>,
guardian_expiration: u32,
message_fee: u64,
) -> CommmandResult {
println!("Initializing Wormhole bridge {}", bridge);
let minimum_balance_for_rent_exemption = config
.rpc_client
.get_minimum_balance_for_rent_exemption(size_of::<BridgeData>())?;
let ix = bridge::instructions::initialize(
*bridge,
config.owner.pubkey(),
message_fee,
guardian_expiration,
initial_guardians.as_slice(),
)
.unwrap();
println!("config account: {}, ", ix.accounts[0].pubkey.to_string());
let mut transaction = Transaction::new_with_payer(&[ix], Some(&config.fee_payer.pubkey()));
let (recent_blockhash, fee_calculator) = config.rpc_client.get_recent_blockhash()?;
check_fee_payer_balance(
config,
minimum_balance_for_rent_exemption + fee_calculator.calculate_fee(&transaction.message()),
)?;
transaction.sign(&[&config.fee_payer, &config.owner], recent_blockhash);
Ok(Some(transaction))
}
fn command_post_message(
config: &Config,
bridge: &Pubkey,
nonce: u32,
payload: Vec<u8>,
commitment: bridge::types::ConsistencyLevel,
proxy: Option<Pubkey>,
) -> CommmandResult {
println!("Posting a message to the wormhole");
// Fetch the message fee
let bridge_config_account = config
.rpc_client
.get_account(&Bridge::<'_, { AccountState::Initialized }>::key(
None, bridge,
))?;
let bridge_config = BridgeData::try_from_slice(bridge_config_account.data.as_slice())?;
let fee = bridge_config.config.fee;
println!("Message fee: {} lamports", fee);
let transfer_ix = transfer(
&config.owner.pubkey(),
&FeeCollector::key(None, bridge),
fee,
);
let message = Keypair::new();
let ix = match proxy {
Some(p) => cpi_poster::instructions::post_message(
p,
*bridge,
config.owner.pubkey(),
config.owner.pubkey(),
message.pubkey(),
nonce,
payload,
commitment,
)
.unwrap(),
None => bridge::instructions::post_message(
*bridge,
config.owner.pubkey(),
config.owner.pubkey(),
message.pubkey(),
nonce,
payload,
commitment,
)
.unwrap(),
};
let mut transaction =
Transaction::new_with_payer(&[transfer_ix, ix], Some(&config.fee_payer.pubkey()));
let (recent_blockhash, fee_calculator) = config.rpc_client.get_recent_blockhash()?;
check_fee_payer_balance(config, fee_calculator.calculate_fee(&transaction.message()))?;
transaction.sign(
&[&config.fee_payer, &config.owner, &message],
recent_blockhash,
);
Ok(Some(transaction))
}
fn main() {
let matches = App::new(crate_name!())
.about(crate_description!())
.version(crate_version!())
.setting(AppSettings::SubcommandRequiredElseHelp)
.arg({
let arg = Arg::with_name("config_file")
.short("C")
.long("config")
.value_name("PATH")
.takes_value(true)
.global(true)
.help("Configuration file to use");
if let Some(ref config_file) = *solana_cli_config::CONFIG_FILE {
arg.default_value(&config_file)
} else {
arg
}
})
.arg(
Arg::with_name("json_rpc_url")
.long("url")
.value_name("URL")
.takes_value(true)
.validator(is_url)
.help("JSON RPC URL for the cluster. Default from the configuration file."),
)
.arg(
Arg::with_name("owner")
.long("owner")
.value_name("KEYPAIR")
.validator(is_keypair)
.takes_value(true)
.help(
"Specify the contract payer account. \
This may be a keypair file, the ASK keyword. \
Defaults to the client keypair.",
),
)
.arg(
Arg::with_name("fee_payer")
.long("fee-payer")
.value_name("KEYPAIR")
.validator(is_keypair)
.takes_value(true)
.help(
"Specify the fee-payer account. \
This may be a keypair file, the ASK keyword. \
Defaults to the client keypair.",
),
)
.subcommand(
SubCommand::with_name("upgrade-authority")
.about("Get the derived signer used for contract upgrades")
.arg(
Arg::with_name("bridge")
.long("bridge")
.value_name("BRIDGE_KEY")
.validator(is_pubkey_or_keypair)
.takes_value(true)
.index(1)
.required(true)
.help("Specify the bridge program address"),
)
)
.subcommand(
SubCommand::with_name("create-bridge")
.about("Create a new bridge")
.arg(
Arg::with_name("bridge")
.long("bridge")
.value_name("BRIDGE_KEY")
.validator(is_pubkey_or_keypair)
.takes_value(true)
.index(1)
.required(true)
.help("Specify the bridge program address"),
)
.arg(
Arg::with_name("guardian")
.validator(is_hex)
.value_name("GUARDIAN_ADDRESS")
.takes_value(true)
.index(2)
.required(true)
.help("Address of the initial guardian"),
)
.arg(
Arg::with_name("guardian_set_expiration")
.validator(is_u32)
.value_name("GUARDIAN_SET_EXPIRATION")
.takes_value(true)
.index(3)
.required(true)
.help("Time in seconds after which a guardian set expires after an update"),
)
.arg(
Arg::with_name("message_fee")
.validator(is_u64)
.value_name("MESSAGE_FEE")
.takes_value(true)
.index(4)
.required(true)
.help("Initial message posting fee"),
),
)
.subcommand(
SubCommand::with_name("post-message")
.about("Post a message via Wormhole")
.arg(
Arg::with_name("bridge")
.long("bridge")
.value_name("BRIDGE_KEY")
.validator(is_pubkey_or_keypair)
.takes_value(true)
.index(1)
.required(true)
.help("Specify the bridge program address"),
)
.arg(
Arg::with_name("nonce")
.validator(is_u32)
.value_name("NONCE")
.takes_value(true)
.index(2)
.required(true)
.help("Nonce of the message"),
)
.arg(
Arg::with_name("consistency_level")
.value_name("CONSISTENCY_LEVEL")
.takes_value(true)
.index(3)
.required(true)
.help("Consistency (Commitment) level at which the VAA should be produced <FINALIZED|CONFIRMED>"),
)
.arg(
Arg::with_name("data")
.validator(is_hex)
.value_name("DATA")
.takes_value(true)
.index(4)
.required(true)
.help("Payload of the message"),
)
.arg(
Arg::with_name("proxy")
.long("proxy")
.validator(is_pubkey_or_keypair)
.value_name("PROXY")
.takes_value(true)
.help("CPI Proxy to use"),
),
)
.get_matches();
let config = {
let cli_config = if let Some(config_file) = matches.value_of("config_file") {
solana_cli_config::Config::load(config_file).unwrap_or_default()
} else {
solana_cli_config::Config::default()
};
let json_rpc_url = value_t!(matches, "json_rpc_url", String)
.unwrap_or_else(|_| cli_config.json_rpc_url.clone());
let client_keypair = || {
read_keypair_file(&cli_config.keypair_path).unwrap_or_else(|err| {
eprintln!("Unable to read {}: {}", cli_config.keypair_path, err);
exit(1)
})
};
let owner = keypair_of(&matches, "owner").unwrap_or_else(client_keypair);
let fee_payer = keypair_of(&matches, "fee_payer").unwrap_or_else(client_keypair);
Config {
rpc_client: RpcClient::new(json_rpc_url),
owner,
fee_payer,
commitment_config: CommitmentConfig::processed(),
}
};
let _ = match matches.subcommand() {
("create-bridge", Some(arg_matches)) => {
let bridge = pubkey_of(arg_matches, "bridge").unwrap();
let initial_guardian: String = value_of(arg_matches, "guardian").unwrap();
let initial_data = hex::decode(initial_guardian).unwrap();
let guardian_expiration: u32 =
value_of(arg_matches, "guardian_set_expiration").unwrap();
let msg_fee: u64 = value_of(arg_matches, "message_fee").unwrap();
let mut guardian = [0u8; 20];
guardian.copy_from_slice(&initial_data);
command_deploy_bridge(
&config,
&bridge,
vec![guardian],
guardian_expiration,
msg_fee,
)
}
("upgrade-authority", Some(arg_matches)) => {
let bridge = pubkey_of(arg_matches, "bridge").unwrap();
let upgrade_auth = <Derive<Info<'_>, "upgrade">>::key(None, &bridge);
println!("Upgrade Key: {}", upgrade_auth);
Ok(None)
}
("post-message", Some(arg_matches)) => {
let bridge = pubkey_of(arg_matches, "bridge").unwrap();
let data_str: String = value_of(arg_matches, "data").unwrap();
let data = hex::decode(data_str).unwrap();
let nonce: u32 = value_of(arg_matches, "nonce").unwrap();
let consistency_level: String = value_of(arg_matches, "consistency_level").unwrap();
let proxy = pubkey_of(arg_matches, "proxy");
command_post_message(
&config,
&bridge,
nonce,
data,
match consistency_level.to_lowercase().as_str() {
"finalized" => bridge::types::ConsistencyLevel::Finalized,
"confirmed" => bridge::types::ConsistencyLevel::Confirmed,
_ => {
eprintln!("Invalid commitment level");
exit(1);
}
},
proxy,
)
}
_ => unreachable!(),
}
.and_then(|transaction| {
if let Some(transaction) = transaction {
let signature = config
.rpc_client
.send_and_confirm_transaction_with_spinner_and_config(
&transaction,
config.commitment_config,
RpcSendTransactionConfig {
skip_preflight: true,
preflight_commitment: None,
encoding: None,
max_retries: None,
},
)?;
println!("Signature: {}", signature);
}
Ok(())
})
.map_err(|err| {
eprintln!("{}", err);
exit(1);
});
}
pub fn is_u8<T>(amount: T) -> Result<(), String>
where
T: AsRef<str> + Display,
{
if amount.as_ref().parse::<u8>().is_ok() {
Ok(())
} else {
Err(format!(
"Unable to parse input amount as integer, provided: {}",
amount
))
}
}
pub fn is_u32<T>(amount: T) -> Result<(), String>
where
T: AsRef<str> + Display,
{
if amount.as_ref().parse::<u32>().is_ok() {
Ok(())
} else {
Err(format!(
"Unable to parse input amount as integer, provided: {}",
amount
))
}
}
pub fn is_u64<T>(amount: T) -> Result<(), String>
where
T: AsRef<str> + Display,
{
if amount.as_ref().parse::<u64>().is_ok() {
Ok(())
} else {
Err(format!(
"Unable to parse input amount as integer, provided: {}",
amount
))
}
}
pub fn is_hex<T>(value: T) -> Result<(), String>
where
T: AsRef<str> + Display,
{
hex::decode(value.to_string())
.map(|_| ())
.map_err(|e| format!("{}", e))
}
fn check_fee_payer_balance(config: &Config, required_balance: u64) -> Result<(), Error> {
let balance = config
.rpc_client
.get_balance_with_commitment(
&config.fee_payer.pubkey(),
CommitmentConfig {
commitment: CommitmentLevel::Processed,
},
)?
.value;
if balance < required_balance {
Err(format!(
"Fee payer, {}, has insufficient balance: {} required, {} available",
config.fee_payer.pubkey(),
lamports_to_sol(required_balance),
lamports_to_sol(balance)
)
.into())
} else {
Ok(())
}
}

View File

@ -1,34 +0,0 @@
[package]
name = "cpi-poster"
version = "0.1.0"
description = "Wormhole bridge core contract"
edition = "2018"
[lib]
crate-type = ["cdylib", "lib"]
name = "cpi_poster"
[features]
client = ["solitaire/client", "solitaire-client", "no-entrypoint"]
cpi = ["no-entrypoint"]
default = []
no-entrypoint = ["solitaire/no-entrypoint"]
trace = ["solitaire/trace"]
[dependencies]
borsh = "=0.9.3"
byteorder = "1.4.3"
primitive-types = { version = "0.9.0", default-features = false }
sha3 = "0.9.1"
solana-program = "=1.10.13"
wormhole-bridge-solana = { path = "../program", features = ["no-entrypoint"] }
solitaire-client = { path = "../../solitaire/client", optional = true }
solitaire = { path = "../../solitaire/program" }
[dev-dependencies]
hex = "*"
rand = "0.7.3"
hex-literal = "0.3.1"
libsecp256k1 = { version = "0.3.5", features = [] }
solana-client = "=1.10.13"
solana-sdk = "=1.10.13"

View File

@ -1,2 +0,0 @@
[target.bpfel-unknown-unknown.dependencies.std]
features = []

View File

@ -1,3 +0,0 @@
pub mod post_message;
pub use post_message::*;

View File

@ -1,66 +0,0 @@
use bridge::types::ConsistencyLevel;
use solana_program::program::invoke;
use solitaire::{
trace,
*,
};
#[derive(FromAccounts)]
pub struct PostMessage<'b> {
/// Bridge config needed for fee calculation.
pub bridge: Mut<Info<'b>>,
/// Account to store the posted message
pub message: Signer<Mut<Info<'b>>>,
/// Emitter of the VAA
pub emitter: MaybeMut<Info<'b>>,
/// Tracker for the emitter sequence
pub sequence: Mut<Info<'b>>,
/// Payer for account creation
pub payer: Mut<Info<'b>>,
/// Account to collect tx fee
pub fee_collector: Mut<Info<'b>>,
pub clock: Info<'b>,
pub bridge_program: Info<'b>,
}
impl<'b> InstructionContext<'b> for PostMessage<'b> {
}
#[derive(BorshDeserialize, BorshSerialize)]
pub struct PostMessageData {
/// Unique nonce for this message
pub nonce: u32,
/// Message payload
pub payload: Vec<u8>,
/// Commitment Level required for an attestation to be produced
pub consistency_level: ConsistencyLevel,
}
pub fn post_message(
ctx: &ExecutionContext,
accs: &mut PostMessage,
data: PostMessageData,
) -> Result<()> {
let ix = bridge::instructions::post_message(
*accs.bridge_program.key,
*accs.payer.key,
*accs.emitter.key,
*accs.message.key,
data.nonce,
data.payload,
data.consistency_level,
)
.unwrap();
invoke(&ix, ctx.accounts)?;
Ok(())
}

View File

@ -1,33 +0,0 @@
use bridge::types::ConsistencyLevel;
use solana_program::{
instruction::{
AccountMeta,
Instruction,
},
pubkey::Pubkey,
};
pub fn post_message(
program_id: Pubkey,
bridge_id: Pubkey,
payer: Pubkey,
emitter: Pubkey,
message: Pubkey,
nonce: u32,
payload: Vec<u8>,
commitment: ConsistencyLevel,
) -> solitaire::Result<Instruction> {
let ix = bridge::instructions::post_message(
bridge_id, payer, emitter, message, nonce, payload, commitment,
)?;
let mut accounts = ix.accounts;
accounts.insert(7, AccountMeta::new_readonly(bridge_id, false));
let mut data = ix.data;
data[0] = 0;
Ok(Instruction {
program_id,
accounts,
data,
})
}

View File

@ -1,21 +0,0 @@
#![feature(adt_const_params)]
#![allow(non_upper_case_globals)]
#![allow(incomplete_features)]
pub mod api;
use solitaire::*;
#[cfg(feature = "no-entrypoint")]
pub mod instructions;
pub use api::{
post_message,
PostMessage,
PostMessageData,
};
solitaire! {
PostMessage(PostMessageData) => post_message,
}

View File

@ -1,36 +0,0 @@
[package]
name = "wormhole-bridge-solana"
version = "0.1.0"
description = "Wormhole bridge core contract"
edition = "2018"
[lib]
crate-type = ["cdylib", "lib"]
name = "bridge"
[features]
client = ["solitaire/client", "solitaire-client", "no-entrypoint"]
cpi = ["no-entrypoint"]
default = []
wasm = ["no-entrypoint", "wasm-bindgen"]
no-entrypoint = ["solitaire/no-entrypoint"]
trace = ["solitaire/trace"]
[dependencies]
borsh = "=0.9.3"
byteorder = "1.4.3"
primitive-types = { version = "0.9.0", default-features = false }
serde = { version = "1.0", features = ["derive"] }
sha3 = "0.9.1"
solana-program = "=1.10.13"
solitaire-client = { path = "../../solitaire/client", optional = true }
solitaire = { path = "../../solitaire/program" }
wasm-bindgen = { version = "0.2.74", features = ["serde-serialize"], optional = true }
[dev-dependencies]
hex = "*"
rand = "0.7.3"
hex-literal = "0.3.1"
libsecp256k1 = { version = "0.3.5", features = [] }
solana-client = "=1.10.13"
solana-sdk = "=1.10.13"

View File

@ -1,2 +0,0 @@
[target.bpfel-unknown-unknown.dependencies.std]
features = []

View File

@ -1,17 +0,0 @@
pub mod bridge;
pub mod claim;
pub mod fee_collector;
pub mod guardian_set;
pub mod posted_message;
pub mod posted_vaa;
pub mod sequence;
pub mod signature_set;
pub use bridge::*;
pub use claim::*;
pub use fee_collector::*;
pub use guardian_set::*;
pub use posted_message::*;
pub use posted_vaa::*;
pub use sequence::*;
pub use signature_set::*;

View File

@ -1,59 +0,0 @@
//! The Bridge account contains the main state for the wormhole bridge, as well as tracking
//! configuration options for how the bridge should behave.
use borsh::{
BorshDeserialize,
BorshSerialize,
};
use serde::{
Deserialize,
Serialize,
};
use solitaire::{
AccountOwner,
AccountState,
Data,
Derive,
Owned,
};
pub type Bridge<'a, const State: AccountState> = Derive<Data<'a, BridgeData, { State }>, "Bridge">;
#[derive(Clone, Default, BorshSerialize, BorshDeserialize, Serialize, Deserialize)]
pub struct BridgeData {
/// The current guardian set index, used to decide which signature sets to accept.
pub guardian_set_index: u32,
/// Lamports in the collection account
pub last_lamports: u64,
/// Bridge configuration, which is set once upon initialization.
pub config: BridgeConfig,
}
#[cfg(not(feature = "cpi"))]
impl Owned for BridgeData {
fn owner(&self) -> AccountOwner {
AccountOwner::This
}
}
#[cfg(feature = "cpi")]
impl Owned for BridgeData {
fn owner(&self) -> AccountOwner {
use std::str::FromStr;
use solana_program::pubkey::Pubkey;
AccountOwner::Other(Pubkey::from_str(env!("BRIDGE_ADDRESS")).unwrap())
}
}
#[derive(Clone, Default, BorshSerialize, BorshDeserialize, Serialize, Deserialize)]
pub struct BridgeConfig {
/// Period for how long a guardian set is valid after it has been replaced by a new one. This
/// guarantees that VAAs issued by that set can still be submitted for a certain period. In
/// this period we still trust the old guardian set.
pub guardian_set_expiration_time: u32,
/// Amount of lamports that needs to be paid to the protocol to post a message
pub fee: u64,
}

View File

@ -1,47 +0,0 @@
//! ClaimData accounts are one off markers that can be combined with other accounts to represent
//! data that can only be used once.
use borsh::{
BorshDeserialize,
BorshSerialize,
};
use serde::{
Deserialize,
Serialize,
};
use solitaire::{
processors::seeded::Seeded,
AccountOwner,
AccountState,
Data,
Owned,
};
pub type Claim<'a, const State: AccountState> = Data<'a, ClaimData, { State }>;
#[derive(Default, Clone, Copy, BorshDeserialize, BorshSerialize, Serialize, Deserialize)]
pub struct ClaimData {
pub claimed: bool,
}
impl Owned for ClaimData {
fn owner(&self) -> AccountOwner {
AccountOwner::This
}
}
pub struct ClaimDerivationData {
pub emitter_address: [u8; 32],
pub emitter_chain: u16,
pub sequence: u64,
}
impl<'b, const State: AccountState> Seeded<&ClaimDerivationData> for Claim<'b, { State }> {
fn seeds(data: &ClaimDerivationData) -> Vec<Vec<u8>> {
return vec![
data.emitter_address.to_vec(),
data.emitter_chain.to_be_bytes().to_vec(),
data.sequence.to_be_bytes().to_vec(),
];
}
}

View File

@ -1,8 +0,0 @@
//! The FeeCollector is a simple account that collects SOL fees.
use solitaire::{
Derive,
Info,
};
pub type FeeCollector<'a> = Derive<Info<'a>, "fee_collector">;

View File

@ -1,65 +0,0 @@
//! GuardianSet represents an account containing information about the current active guardians
//! responsible for signing wormhole VAAs.
use crate::types::GuardianPublicKey;
use borsh::{
BorshDeserialize,
BorshSerialize,
};
use serde::{
Deserialize,
Serialize,
};
use solitaire::{
processors::seeded::Seeded,
AccountOwner,
AccountState,
Data,
Owned,
};
pub type GuardianSet<'b, const State: AccountState> = Data<'b, GuardianSetData, { State }>;
#[derive(Default, BorshSerialize, BorshDeserialize, Serialize, Deserialize)]
pub struct GuardianSetData {
/// Index representing an incrementing version number for this guardian set.
pub index: u32,
/// ETH style public keys
pub keys: Vec<GuardianPublicKey>,
/// Timestamp representing the time this guardian became active.
pub creation_time: u32,
/// Expiration time when VAAs issued by this set are no longer valid.
pub expiration_time: u32,
}
/// GuardianSet account PDAs are indexed by their version number.
pub struct GuardianSetDerivationData {
pub index: u32,
}
impl<'a, const State: AccountState> Seeded<&GuardianSetDerivationData>
for GuardianSet<'a, { State }>
{
fn seeds(data: &GuardianSetDerivationData) -> Vec<Vec<u8>> {
vec![
"GuardianSet".as_bytes().to_vec(),
data.index.to_be_bytes().to_vec(),
]
}
}
impl GuardianSetData {
/// Number of guardians in the set
pub fn num_guardians(&self) -> u8 {
self.keys.iter().filter(|v| **v != [0u8; 20]).count() as u8
}
}
impl Owned for GuardianSetData {
fn owner(&self) -> AccountOwner {
AccountOwner::This
}
}

View File

@ -1,119 +0,0 @@
use borsh::{
BorshDeserialize,
BorshSerialize,
};
use serde::{
Deserialize,
Serialize,
};
use solana_program::pubkey::Pubkey;
use solitaire::{
AccountOwner,
AccountState,
Data,
Owned,
};
use std::{
io::Write,
ops::{
Deref,
DerefMut,
},
};
pub type PostedMessage<'a, const State: AccountState> = Data<'a, PostedMessageData, { State }>;
// This is using the same payload as the PostedVAA for backwards compatibility.
// This will be deprecated in a future release.
#[repr(transparent)]
pub struct PostedMessageData(pub MessageData);
#[derive(Debug, Default, BorshSerialize, BorshDeserialize, Clone, Serialize, Deserialize)]
pub struct MessageData {
/// Header of the posted VAA
pub vaa_version: u8,
/// Level of consistency requested by the emitter
pub consistency_level: u8,
/// Time the vaa was submitted
pub vaa_time: u32,
/// Account where signatures are stored
pub vaa_signature_account: Pubkey,
/// Time the posted message was created
pub submission_time: u32,
/// Unique nonce for this message
pub nonce: u32,
/// Sequence number of this message
pub sequence: u64,
/// Emitter of the message
pub emitter_chain: u16,
/// Emitter of the message
pub emitter_address: [u8; 32],
/// Message payload
pub payload: Vec<u8>,
}
impl BorshSerialize for PostedMessageData {
fn serialize<W: Write>(&self, writer: &mut W) -> std::io::Result<()> {
writer.write(b"msg")?;
BorshSerialize::serialize(&self.0, writer)
}
}
impl BorshDeserialize for PostedMessageData {
fn deserialize(buf: &mut &[u8]) -> std::io::Result<Self> {
*buf = &buf[3..];
Ok(PostedMessageData(
<MessageData as BorshDeserialize>::deserialize(buf)?,
))
}
}
impl Deref for PostedMessageData {
type Target = MessageData;
fn deref(&self) -> &Self::Target {
unsafe { std::mem::transmute(&self.0) }
}
}
impl DerefMut for PostedMessageData {
fn deref_mut(&mut self) -> &mut Self::Target {
unsafe { std::mem::transmute(&mut self.0) }
}
}
impl Default for PostedMessageData {
fn default() -> Self {
PostedMessageData(MessageData::default())
}
}
impl Clone for PostedMessageData {
fn clone(&self) -> Self {
PostedMessageData(self.0.clone())
}
}
#[cfg(not(feature = "cpi"))]
impl Owned for PostedMessageData {
fn owner(&self) -> AccountOwner {
AccountOwner::This
}
}
#[cfg(feature = "cpi")]
impl Owned for PostedMessageData {
fn owner(&self) -> AccountOwner {
use std::str::FromStr;
AccountOwner::Other(Pubkey::from_str(env!("BRIDGE_ADDRESS")).unwrap())
}
}

View File

@ -1,91 +0,0 @@
use crate::MessageData;
use borsh::{
BorshDeserialize,
BorshSerialize,
};
use solitaire::{
processors::seeded::Seeded,
AccountOwner,
AccountState,
Data,
Owned,
};
use std::{
io::Write,
ops::{
Deref,
DerefMut,
},
};
pub type PostedVAA<'b, const State: AccountState> = Data<'b, PostedVAAData, { State }>;
pub struct PostedVAADerivationData {
pub payload_hash: Vec<u8>,
}
impl<'a, const State: AccountState> Seeded<&PostedVAADerivationData> for PostedVAA<'a, { State }> {
fn seeds(data: &PostedVAADerivationData) -> Vec<Vec<u8>> {
vec!["PostedVAA".as_bytes().to_vec(), data.payload_hash.to_vec()]
}
}
#[repr(transparent)]
pub struct PostedVAAData(pub MessageData);
impl BorshSerialize for PostedVAAData {
fn serialize<W: Write>(&self, writer: &mut W) -> std::io::Result<()> {
writer.write(b"vaa")?;
BorshSerialize::serialize(&self.0, writer)
}
}
impl BorshDeserialize for PostedVAAData {
fn deserialize(buf: &mut &[u8]) -> std::io::Result<Self> {
*buf = &buf[3..];
Ok(PostedVAAData(
<MessageData as BorshDeserialize>::deserialize(buf)?,
))
}
}
impl Deref for PostedVAAData {
type Target = MessageData;
fn deref(&self) -> &Self::Target {
unsafe { std::mem::transmute(&self.0) }
}
}
impl DerefMut for PostedVAAData {
fn deref_mut(&mut self) -> &mut Self::Target {
unsafe { std::mem::transmute(&mut self.0) }
}
}
impl Default for PostedVAAData {
fn default() -> Self {
PostedVAAData(MessageData::default())
}
}
impl Clone for PostedVAAData {
fn clone(&self) -> Self {
PostedVAAData(self.0.clone())
}
}
#[cfg(not(feature = "cpi"))]
impl Owned for PostedVAAData {
fn owner(&self) -> AccountOwner {
AccountOwner::This
}
}
#[cfg(feature = "cpi")]
impl Owned for PostedVAAData {
fn owner(&self) -> AccountOwner {
use std::str::FromStr;
use solana_program::pubkey::Pubkey;
AccountOwner::Other(Pubkey::from_str(env!("BRIDGE_ADDRESS")).unwrap())
}
}

View File

@ -1,38 +0,0 @@
use borsh::{
BorshDeserialize,
BorshSerialize,
};
use solana_program::pubkey::Pubkey;
use solitaire::{
processors::seeded::Seeded,
AccountState,
AccountOwner,
Data,
Owned,
};
pub type Sequence<'b> = Data<'b, SequenceTracker, { AccountState::MaybeInitialized }>;
#[derive(Default, Clone, Copy, BorshDeserialize, BorshSerialize)]
pub struct SequenceTracker {
pub sequence: u64,
}
pub struct SequenceDerivationData<'a> {
pub emitter_key: &'a Pubkey,
}
impl<'b> Seeded<&SequenceDerivationData<'b>> for Sequence<'b> {
fn seeds(data: &SequenceDerivationData) -> Vec<Vec<u8>> {
vec![
"Sequence".as_bytes().to_vec(),
data.emitter_key.to_bytes().to_vec(),
]
}
}
impl Owned for SequenceTracker {
fn owner(&self) -> AccountOwner {
AccountOwner::This
}
}

View File

@ -1,32 +0,0 @@
//! PostedMessage
use borsh::{
BorshDeserialize,
BorshSerialize,
};
use solitaire::{
AccountOwner,
AccountState,
Data,
Owned,
};
pub type SignatureSet<'b, const State: AccountState> = Data<'b, SignatureSetData, { State }>;
#[derive(Default, BorshSerialize, BorshDeserialize)]
pub struct SignatureSetData {
/// Signatures of validators
pub signatures: Vec<bool>,
/// Hash of the data
pub hash: [u8; 32],
/// Index of the guardian set
pub guardian_set_index: u32,
}
impl Owned for SignatureSetData {
fn owner(&self) -> AccountOwner {
AccountOwner::This
}
}

View File

@ -1,11 +0,0 @@
pub mod governance;
pub mod initialize;
pub mod post_message;
pub mod post_vaa;
pub mod verify_signature;
pub use governance::*;
pub use initialize::*;
pub use post_message::*;
pub use post_vaa::*;
pub use verify_signature::*;

View File

@ -1,299 +0,0 @@
use solitaire::*;
use solana_program::{
log::sol_log,
program::invoke_signed,
program_error::ProgramError,
pubkey::Pubkey,
sysvar::{
clock::Clock,
rent::Rent,
},
};
use solitaire::{
processors::seeded::Seeded,
CreationLamports::Exempt,
};
use crate::{
accounts::{
Bridge,
GuardianSet,
GuardianSetDerivationData,
},
error::Error::{
InvalidFeeRecipient,
InvalidGovernanceKey,
InvalidGovernanceWithdrawal,
InvalidGuardianSetUpgrade,
},
types::{
GovernancePayloadGuardianSetChange,
GovernancePayloadSetMessageFee,
GovernancePayloadTransferFees,
GovernancePayloadUpgrade,
},
vaa::ClaimableVAA,
DeserializePayload,
CHAIN_ID_SOLANA,
};
fn verify_governance<'a, T>(vaa: &ClaimableVAA<'a, T>) -> Result<()>
where
T: DeserializePayload,
{
let expected_emitter = std::option_env!("EMITTER_ADDRESS").ok_or_else(|| {
sol_log("EMITTER_ADDRESS not set at compile-time");
ProgramError::UninitializedAccount
})?;
let current_emitter = format!(
"{}",
Pubkey::new_from_array(vaa.message.meta().emitter_address)
);
// Fail if the emitter is not the known governance key, or the emitting chain is not Solana.
if expected_emitter != current_emitter || vaa.message.meta().emitter_chain != CHAIN_ID_SOLANA {
Err(InvalidGovernanceKey.into())
} else {
Ok(())
}
}
#[derive(FromAccounts)]
pub struct UpgradeContract<'b> {
/// Payer for account creation (vaa-claim)
pub payer: Mut<Signer<Info<'b>>>,
/// Bridge config
pub bridge: Mut<Bridge<'b, { AccountState::Initialized }>>,
/// GuardianSet change VAA
pub vaa: ClaimableVAA<'b, GovernancePayloadUpgrade>,
/// PDA authority for the loader
pub upgrade_authority: Derive<Info<'b>, "upgrade">,
/// Spill address for the upgrade excess lamports
pub spill: Mut<Info<'b>>,
/// New contract address.
pub buffer: Mut<Info<'b>>,
/// Required by the upgradeable uploader.
pub program_data: Mut<Info<'b>>,
/// Our own address, required by the upgradeable loader.
pub own_address: Mut<Info<'b>>,
// Various sysvar/program accounts needed for the upgradeable loader.
pub rent: Sysvar<'b, Rent>,
pub clock: Sysvar<'b, Clock>,
pub bpf_loader: Info<'b>,
pub system: Info<'b>,
}
impl<'b> InstructionContext<'b> for UpgradeContract<'b> {
}
#[derive(BorshDeserialize, BorshSerialize, Default)]
pub struct UpgradeContractData {}
pub fn upgrade_contract(
ctx: &ExecutionContext,
accs: &mut UpgradeContract,
_data: UpgradeContractData,
) -> Result<()> {
verify_governance(&accs.vaa)?;
accs.vaa.verify(ctx.program_id)?;
accs.vaa.claim(ctx, accs.payer.key)?;
let upgrade_ix = solana_program::bpf_loader_upgradeable::upgrade(
ctx.program_id,
&accs.vaa.message.new_contract,
accs.upgrade_authority.key,
accs.spill.key,
);
let seeds = accs
.upgrade_authority
.self_bumped_seeds(None, ctx.program_id);
let seeds: Vec<&[u8]> = seeds.iter().map(|item| item.as_slice()).collect();
let seeds = seeds.as_slice();
invoke_signed(&upgrade_ix, ctx.accounts, &[seeds])?;
Ok(())
}
#[derive(FromAccounts)]
pub struct UpgradeGuardianSet<'b> {
/// Payer for account creation (vaa-claim)
pub payer: Mut<Signer<Info<'b>>>,
/// Bridge config
pub bridge: Mut<Bridge<'b, { AccountState::Initialized }>>,
/// GuardianSet change VAA
pub vaa: ClaimableVAA<'b, GovernancePayloadGuardianSetChange>,
/// Old guardian set
pub guardian_set_old: Mut<GuardianSet<'b, { AccountState::Initialized }>>,
/// New guardian set
pub guardian_set_new: Mut<GuardianSet<'b, { AccountState::Uninitialized }>>,
}
impl<'b> InstructionContext<'b> for UpgradeGuardianSet<'b> {
}
#[derive(BorshDeserialize, BorshSerialize, Default)]
pub struct UpgradeGuardianSetData {}
pub fn upgrade_guardian_set(
ctx: &ExecutionContext,
accs: &mut UpgradeGuardianSet,
_data: UpgradeGuardianSetData,
) -> Result<()> {
// Enforce single increments when upgrading.
if accs.guardian_set_old.index != accs.vaa.new_guardian_set_index - 1 {
return Err(InvalidGuardianSetUpgrade.into());
}
// Confirm that the version the bridge has active is the previous version.
if accs.bridge.guardian_set_index != accs.vaa.new_guardian_set_index - 1 {
return Err(InvalidGuardianSetUpgrade.into());
}
verify_governance(&accs.vaa)?;
accs.vaa.verify(ctx.program_id)?;
accs.guardian_set_old.verify_derivation(
ctx.program_id,
&GuardianSetDerivationData {
index: accs.vaa.new_guardian_set_index - 1,
},
)?;
accs.guardian_set_new.verify_derivation(
ctx.program_id,
&GuardianSetDerivationData {
index: accs.vaa.new_guardian_set_index,
},
)?;
accs.vaa.claim(ctx, accs.payer.key)?;
// Set expiration time for the old set
accs.guardian_set_old.expiration_time =
accs.vaa.meta().vaa_time + accs.bridge.config.guardian_set_expiration_time;
// Initialize new guardian Set
accs.guardian_set_new.index = accs.vaa.new_guardian_set_index;
accs.guardian_set_new.creation_time = accs.vaa.meta().vaa_time;
accs.guardian_set_new.keys = accs.vaa.new_guardian_set.clone();
// Create new guardian set
// This is done after populating it to properly allocate space according to key vec length.
accs.guardian_set_new.create(
&GuardianSetDerivationData {
index: accs.guardian_set_new.index,
},
ctx,
accs.payer.key,
Exempt,
)?;
// Set guardian set index
accs.bridge.guardian_set_index = accs.vaa.new_guardian_set_index;
Ok(())
}
#[derive(FromAccounts)]
pub struct SetFees<'b> {
/// Payer for account creation (vaa-claim)
pub payer: Mut<Signer<Info<'b>>>,
/// Bridge config
pub bridge: Mut<Bridge<'b, { AccountState::Initialized }>>,
/// Governance VAA
pub vaa: ClaimableVAA<'b, GovernancePayloadSetMessageFee>,
}
impl<'b> InstructionContext<'b> for SetFees<'b> {
}
#[derive(BorshDeserialize, BorshSerialize, Default)]
pub struct SetFeesData {}
pub fn set_fees(ctx: &ExecutionContext, accs: &mut SetFees, _data: SetFeesData) -> Result<()> {
verify_governance(&accs.vaa)?;
accs.vaa.verify(ctx.program_id)?;
accs.vaa.claim(ctx, accs.payer.key)?;
accs.bridge.config.fee = accs.vaa.fee.as_u64();
Ok(())
}
#[derive(FromAccounts)]
pub struct TransferFees<'b> {
/// Payer for account creation (vaa-claim)
pub payer: Mut<Signer<Info<'b>>>,
/// Bridge config
pub bridge: Bridge<'b, { AccountState::Initialized }>,
/// Governance VAA
pub vaa: ClaimableVAA<'b, GovernancePayloadTransferFees>,
/// Account collecting tx fees
pub fee_collector: Mut<Derive<Info<'b>, "fee_collector">>,
/// Fee recipient
pub recipient: Mut<Info<'b>>,
/// Rent calculator to check transfer sizes.
pub rent: Sysvar<'b, Rent>,
}
impl<'b> InstructionContext<'b> for TransferFees<'b> {
}
#[derive(BorshDeserialize, BorshSerialize, Default)]
pub struct TransferFeesData {}
pub fn transfer_fees(
ctx: &ExecutionContext,
accs: &mut TransferFees,
_data: TransferFeesData,
) -> Result<()> {
// Make sure the account loaded to receive funds is equal to the one the VAA requested.
if accs.vaa.to != accs.recipient.key.to_bytes() {
return Err(InvalidFeeRecipient.into());
}
verify_governance(&accs.vaa)?;
accs.vaa.verify(ctx.program_id)?;
if accs
.fee_collector
.lamports()
.saturating_sub(accs.vaa.amount.as_u64())
< accs.rent.minimum_balance(accs.fee_collector.data_len())
{
return Err(InvalidGovernanceWithdrawal.into());
}
accs.vaa.claim(ctx, accs.payer.key)?;
// Transfer fees
let transfer_ix = solana_program::system_instruction::transfer(
accs.fee_collector.key,
accs.recipient.key,
accs.vaa.amount.as_u64(),
);
let seeds = accs.fee_collector.self_bumped_seeds(None, ctx.program_id);
let seeds: Vec<&[u8]> = seeds.iter().map(|item| item.as_slice()).collect();
let seeds = seeds.as_slice();
invoke_signed(&transfer_ix, ctx.accounts, &[seeds])?;
Ok(())
}

View File

@ -1,98 +0,0 @@
use crate::{
accounts::{
Bridge,
BridgeConfig,
FeeCollector,
GuardianSet,
GuardianSetDerivationData,
},
error::Error::TooManyGuardians,
MAX_LEN_GUARDIAN_KEYS,
};
use solana_program::sysvar::clock::Clock;
use solitaire::{
CreationLamports::Exempt,
*,
};
type Payer<'a> = Signer<Info<'a>>;
#[derive(FromAccounts)]
pub struct Initialize<'b> {
/// Bridge config.
pub bridge: Mut<Bridge<'b, { AccountState::Uninitialized }>>,
/// Location the new guardian set will be allocated at.
pub guardian_set: Mut<GuardianSet<'b, { AccountState::Uninitialized }>>,
/// Location of the fee collector that users will need to pay.
pub fee_collector: Mut<FeeCollector<'b>>,
/// Payer for account creation.
pub payer: Mut<Payer<'b>>,
/// Clock used for recording the initialization time.
pub clock: Sysvar<'b, Clock>,
}
impl<'b> InstructionContext<'b> for Initialize<'b> {
}
#[derive(BorshDeserialize, BorshSerialize, Default)]
pub struct InitializeData {
/// Period for how long a guardian set is valid after it has been replaced by a new one. This
/// guarantees that VAAs issued by that set can still be submitted for a certain period. In
/// this period we still trust the old guardian set.
pub guardian_set_expiration_time: u32,
/// Amount of lamports that needs to be paid to the protocol to post a message
pub fee: u64,
/// Initial Guardian Set
pub initial_guardians: Vec<[u8; 20]>,
}
pub fn initialize(
ctx: &ExecutionContext,
accs: &mut Initialize,
data: InitializeData,
) -> Result<()> {
let index = 0;
if data.initial_guardians.len() > MAX_LEN_GUARDIAN_KEYS {
return Err(TooManyGuardians.into());
}
// Allocate initial guardian set with the provided keys.
accs.guardian_set.index = index;
accs.guardian_set.creation_time = accs.clock.unix_timestamp as u32;
accs.guardian_set.keys.extend(&data.initial_guardians);
// Initialize Guardian Set
accs.guardian_set.create(
&GuardianSetDerivationData { index },
ctx,
accs.payer.key,
Exempt,
)?;
// Initialize the Bridge state for the first time.
accs.bridge.create(ctx, accs.payer.key, Exempt)?;
accs.bridge.guardian_set_index = index;
accs.bridge.config = BridgeConfig {
guardian_set_expiration_time: data.guardian_set_expiration_time,
fee: data.fee,
};
// Initialize the fee collector account so it's rent exempt and will keep funds
accs.fee_collector.create(
ctx,
accs.payer.key,
Exempt,
0,
&solana_program::system_program::id(),
)?;
accs.bridge.last_lamports = accs.fee_collector.lamports();
Ok(())
}

View File

@ -1,146 +0,0 @@
use crate::{
accounts::{
Bridge,
FeeCollector,
PostedMessage,
Sequence,
SequenceDerivationData,
},
error::Error::{
InsufficientFees,
MathOverflow,
},
types::ConsistencyLevel,
CHAIN_ID_SOLANA,
};
use solana_program::{
msg,
sysvar::clock::Clock,
};
use solitaire::{
processors::seeded::Seeded,
trace,
CreationLamports::Exempt,
*,
};
pub type UninitializedMessage<'b> = PostedMessage<'b, { AccountState::Uninitialized }>;
impl<'a> From<&PostMessage<'a>> for SequenceDerivationData<'a> {
fn from(accs: &PostMessage<'a>) -> Self {
SequenceDerivationData {
emitter_key: accs.emitter.key,
}
}
}
#[derive(FromAccounts)]
pub struct PostMessage<'b> {
/// Bridge config needed for fee calculation.
pub bridge: Mut<Bridge<'b, { AccountState::Initialized }>>,
/// Account to store the posted message
pub message: Signer<Mut<UninitializedMessage<'b>>>,
/// Emitter of the VAA
pub emitter: Signer<MaybeMut<Info<'b>>>,
/// Tracker for the emitter sequence
pub sequence: Mut<Sequence<'b>>,
/// Payer for account creation
pub payer: Mut<Signer<Info<'b>>>,
/// Account to collect tx fee
pub fee_collector: Mut<FeeCollector<'b>>,
pub clock: Sysvar<'b, Clock>,
}
impl<'b> InstructionContext<'b> for PostMessage<'b> {
}
#[derive(BorshDeserialize, BorshSerialize)]
pub struct PostMessageData {
/// Unique nonce for this message
pub nonce: u32,
/// Message payload
pub payload: Vec<u8>,
/// Commitment Level required for an attestation to be produced
pub consistency_level: ConsistencyLevel,
}
pub fn post_message(
ctx: &ExecutionContext,
accs: &mut PostMessage,
data: PostMessageData,
) -> Result<()> {
trace!("Message Address: {}", accs.message.info().key);
trace!("Emitter Address: {}", accs.emitter.info().key);
trace!("Nonce: {}", data.nonce);
accs.sequence
.verify_derivation(ctx.program_id, &(&*accs).into())?;
let fee = accs.bridge.config.fee;
// Fee handling, checking previously known balance allows us to not care who is the payer of
// this submission.
if accs
.fee_collector
.lamports()
.checked_sub(accs.bridge.last_lamports)
.ok_or(MathOverflow)?
< fee
{
trace!(
"Expected fee not found: fee, last_lamports, collector: {} {} {}",
fee,
accs.bridge.last_lamports,
accs.fee_collector.lamports(),
);
return Err(InsufficientFees.into());
}
accs.bridge.last_lamports = accs.fee_collector.lamports();
// Init sequence tracker if it does not exist yet.
if !accs.sequence.is_initialized() {
trace!("Initializing Sequence account to 0.");
accs.sequence
.create(&(&*accs).into(), ctx, accs.payer.key, Exempt)?;
}
// DO NOT REMOVE - CRITICAL OUTPUT
msg!("Sequence: {}", accs.sequence.sequence);
// Initialize transfer
trace!("Setting Message Details");
accs.message.submission_time = accs.clock.unix_timestamp as u32;
accs.message.emitter_chain = CHAIN_ID_SOLANA;
accs.message.emitter_address = accs.emitter.key.to_bytes();
accs.message.nonce = data.nonce;
accs.message.payload = data.payload;
accs.message.sequence = accs.sequence.sequence;
accs.message.consistency_level = match data.consistency_level {
ConsistencyLevel::Confirmed => 1,
ConsistencyLevel::Finalized => 32,
};
// Create message account
let size = accs.message.size();
let ix = solana_program::system_instruction::create_account(
accs.payer.key,
accs.message.info().key,
Exempt.amount(size),
size as u64,
ctx.program_id,
);
solana_program::program::invoke(&ix, ctx.accounts)?;
// Bump sequence number
trace!("New Sequence: {}", accs.sequence.sequence + 1);
accs.sequence.sequence += 1;
Ok(())
}

View File

@ -1,220 +0,0 @@
use solitaire::*;
use borsh::{
BorshDeserialize,
BorshSerialize,
};
use solana_program::{
self,
sysvar::clock::Clock,
};
use crate::{
accounts::{
Bridge,
GuardianSet,
GuardianSetDerivationData,
PostedVAA,
PostedVAADerivationData,
SignatureSet,
},
error::Error::{
GuardianSetMismatch,
PostVAAConsensusFailed,
PostVAAGuardianSetExpired,
},
};
use byteorder::{
BigEndian,
WriteBytesExt,
};
use serde::{
Deserialize,
Serialize,
};
use sha3::Digest;
use solana_program::program_error::ProgramError;
use solitaire::{
processors::seeded::Seeded,
CreationLamports::Exempt,
};
use std::io::{
Cursor,
Write,
};
impl From<&PostVAAData> for GuardianSetDerivationData {
fn from(data: &PostVAAData) -> Self {
GuardianSetDerivationData {
index: data.guardian_set_index,
}
}
}
#[derive(FromAccounts)]
pub struct PostVAA<'b> {
/// Information about the current guardian set.
pub guardian_set: GuardianSet<'b, { AccountState::Initialized }>,
/// Bridge Info
pub bridge_info: Bridge<'b, { AccountState::Initialized }>,
/// Signature Info
pub signature_set: SignatureSet<'b, { AccountState::Initialized }>,
/// Message the VAA is associated with.
pub message: Mut<PostedVAA<'b, { AccountState::MaybeInitialized }>>,
/// Account used to pay for auxillary instructions.
pub payer: Mut<Signer<Info<'b>>>,
/// Clock used for timestamping.
pub clock: Sysvar<'b, Clock>,
}
impl<'b> InstructionContext<'b> for PostVAA<'b> {
}
#[derive(Default, BorshSerialize, BorshDeserialize)]
pub struct Signature {
pub index: u8,
pub r: [u8; 32],
pub s: [u8; 32],
pub v: u8,
}
pub type ForeignAddress = [u8; 32];
#[derive(Default, BorshSerialize, BorshDeserialize, Clone, Serialize, Deserialize)]
pub struct PostVAAData {
// Header part
pub version: u8,
pub guardian_set_index: u32,
// Body part
pub timestamp: u32,
pub nonce: u32,
pub emitter_chain: u16,
pub emitter_address: ForeignAddress,
pub sequence: u64,
pub consistency_level: u8,
pub payload: Vec<u8>,
}
pub fn post_vaa(ctx: &ExecutionContext, accs: &mut PostVAA, vaa: PostVAAData) -> Result<()> {
let msg_derivation = PostedVAADerivationData {
payload_hash: accs.signature_set.hash.to_vec(),
};
accs.message
.verify_derivation(ctx.program_id, &msg_derivation)?;
accs.guardian_set
.verify_derivation(ctx.program_id, &(&vaa).into())?;
if accs.message.is_initialized() {
return Ok(());
}
// Verify any required invariants before we process the instruction.
check_active(&accs.guardian_set, &accs.clock)?;
check_valid_sigs(&accs.guardian_set, &accs.signature_set)?;
check_integrity(&vaa, &accs.signature_set)?;
// Count the number of signatures currently present.
let signature_count: usize = accs.signature_set.signatures.iter().filter(|v| **v).count();
// Calculate how many signatures are required to reach consensus. This calculation is in
// expanded form to ease auditing.
let required_consensus_count = {
let len = accs.guardian_set.keys.len();
// Fixed point number transformation with one decimal to deal with rounding.
let len = (len * 10) / 3;
// Multiplication by two to get a 2/3 quorum.
let len = len * 2;
// Division to bring number back into range.
len / 10 + 1
};
if signature_count < required_consensus_count {
return Err(PostVAAConsensusFailed.into());
}
// Persist VAA data
accs.message.nonce = vaa.nonce;
accs.message.emitter_chain = vaa.emitter_chain;
accs.message.emitter_address = vaa.emitter_address;
accs.message.sequence = vaa.sequence;
accs.message.payload = vaa.payload;
accs.message.consistency_level = vaa.consistency_level;
accs.message.vaa_version = vaa.version;
accs.message.vaa_time = vaa.timestamp;
accs.message.vaa_signature_account = *accs.signature_set.info().key;
accs.message
.create(&msg_derivation, ctx, accs.payer.key, Exempt)?;
Ok(())
}
/// A guardian set must not have expired.
#[inline(always)]
fn check_active<'r>(
guardian_set: &GuardianSet<'r, { AccountState::Initialized }>,
clock: &Sysvar<'r, Clock>,
) -> Result<()> {
// IMPORTANT - this is a fix for mainnet wormhole
// The initial guardian set was never expired so we block it here.
if guardian_set.index == 0 && guardian_set.creation_time == 1628099186 {
return Err(PostVAAGuardianSetExpired.into());
}
if guardian_set.expiration_time != 0
&& (guardian_set.expiration_time as i64) < clock.unix_timestamp
{
return Err(PostVAAGuardianSetExpired.into());
}
Ok(())
}
/// The signatures in this instruction must be from the right guardian set.
#[inline(always)]
fn check_valid_sigs<'r>(
guardian_set: &GuardianSet<'r, { AccountState::Initialized }>,
signatures: &SignatureSet<'r, { AccountState::Initialized }>,
) -> Result<()> {
if signatures.guardian_set_index != guardian_set.index {
return Err(GuardianSetMismatch.into());
}
Ok(())
}
#[inline(always)]
fn check_integrity<'r>(
vaa: &PostVAAData,
signatures: &SignatureSet<'r, { AccountState::Initialized }>,
) -> Result<()> {
// Serialize the VAA body into an array of bytes.
let body = {
let mut v = Cursor::new(Vec::new());
v.write_u32::<BigEndian>(vaa.timestamp)?;
v.write_u32::<BigEndian>(vaa.nonce)?;
v.write_u16::<BigEndian>(vaa.emitter_chain)?;
v.write(&vaa.emitter_address)?;
v.write_u64::<BigEndian>(vaa.sequence)?;
v.write_u8(vaa.consistency_level)?;
v.write(&vaa.payload)?;
v.into_inner()
};
// Hash this body, which is expected to be the same as the hash currently stored in the
// signature account, binding that set of signatures to this VAA.
let body_hash: [u8; 32] = {
let mut h = sha3::Keccak256::default();
h.write(body.as_slice())
.map_err(|_| ProgramError::InvalidArgument)?;
h.finalize().into()
};
if signatures.hash != body_hash {
return Err(ProgramError::InvalidAccountData.into());
}
Ok(())
}

View File

@ -1,219 +0,0 @@
use solitaire::*;
use crate::{
error::Error::{
GuardianSetMismatch,
InstructionAtWrongIndex,
InvalidHash,
InvalidSecpInstruction,
},
GuardianSet,
GuardianSetDerivationData,
SignatureSet,
MAX_LEN_GUARDIAN_KEYS,
};
use byteorder::ByteOrder;
use solana_program::program_error::ProgramError;
use solitaire::{
processors::seeded::Seeded,
CreationLamports::Exempt,
};
#[derive(FromAccounts)]
pub struct VerifySignatures<'b> {
/// Payer for account creation
pub payer: Mut<Signer<Info<'b>>>,
/// Guardian set of the signatures
pub guardian_set: GuardianSet<'b, { AccountState::Initialized }>,
/// Signature Account
pub signature_set: Mut<Signer<SignatureSet<'b, { AccountState::MaybeInitialized }>>>,
/// Instruction reflection account (special sysvar)
pub instruction_acc: Info<'b>,
}
impl<'b> InstructionContext<'b> for VerifySignatures<'b> {
}
impl From<&VerifySignatures<'_>> for GuardianSetDerivationData {
fn from(data: &VerifySignatures<'_>) -> Self {
GuardianSetDerivationData {
index: data.guardian_set.index,
}
}
}
#[derive(Default, BorshSerialize, BorshDeserialize)]
pub struct VerifySignaturesData {
/// instruction indices of signers (-1 for missing)
pub signers: [i8; MAX_LEN_GUARDIAN_KEYS],
}
/// SigInfo contains metadata about signers in a VerifySignature ix
struct SigInfo {
/// index of the signer in the guardianset
signer_index: u8,
/// index of the signature in the secp instruction
sig_index: u8,
}
struct SecpInstructionPart<'a> {
address: &'a [u8],
msg_offset: u16,
msg_size: u16,
}
pub fn verify_signatures(
ctx: &ExecutionContext,
accs: &mut VerifySignatures,
data: VerifySignaturesData,
) -> Result<()> {
accs.guardian_set
.verify_derivation(ctx.program_id, &(&*accs).into())?;
let sig_infos: Vec<SigInfo> = data
.signers
.iter()
.enumerate()
.filter_map(|(i, p)| {
if *p == -1 {
return None;
}
return Some(SigInfo {
sig_index: *p as u8,
signer_index: i as u8,
});
})
.collect();
let current_instruction = solana_program::sysvar::instructions::load_current_index_checked(
&accs.instruction_acc,
)?;
if current_instruction == 0 {
return Err(InstructionAtWrongIndex.into());
}
// The previous ix must be a secp verification instruction
let secp_ix_index = (current_instruction - 1) as u8;
let secp_ix = solana_program::sysvar::instructions::load_instruction_at_checked(
secp_ix_index as usize,
&accs.instruction_acc,
)
.map_err(|_| ProgramError::InvalidAccountData)?;
// Check that the instruction is actually for the secp program
if secp_ix.program_id != solana_program::secp256k1_program::id() {
return Err(InvalidSecpInstruction.into());
}
let secp_data_len = secp_ix.data.len();
if secp_data_len < 2 {
return Err(InvalidSecpInstruction.into());
}
let sig_len = secp_ix.data[0];
let mut index = 1;
let mut secp_ixs: Vec<SecpInstructionPart> = Vec::with_capacity(sig_len as usize);
for i in 0..sig_len {
let _sig_offset = byteorder::LE::read_u16(&secp_ix.data[index..index + 2]) as usize;
index += 2;
let sig_ix = secp_ix.data[index];
index += 1;
let address_offset = byteorder::LE::read_u16(&secp_ix.data[index..index + 2]) as usize;
index += 2;
let address_ix = secp_ix.data[index];
index += 1;
let msg_offset = byteorder::LE::read_u16(&secp_ix.data[index..index + 2]);
index += 2;
let msg_size = byteorder::LE::read_u16(&secp_ix.data[index..index + 2]);
index += 2;
let msg_ix = secp_ix.data[index];
index += 1;
if address_ix != secp_ix_index || msg_ix != secp_ix_index || sig_ix != secp_ix_index {
return Err(InvalidSecpInstruction.into());
}
let address: &[u8] = &secp_ix.data[address_offset..address_offset + 20];
// Make sure that all messages are equal
if i > 0 {
if msg_offset != secp_ixs[0].msg_offset || msg_size != secp_ixs[0].msg_size {
return Err(InvalidSecpInstruction.into());
}
}
secp_ixs.push(SecpInstructionPart {
address,
msg_offset,
msg_size,
});
}
if sig_infos.len() != secp_ixs.len() {
return Err(ProgramError::InvalidArgument.into());
}
// Data must be a hash
if secp_ixs[0].msg_size != 32 {
return Err(ProgramError::InvalidArgument.into());
}
// Extract message which is encoded in Solana Secp256k1 instruction data.
let message = &secp_ix.data
[secp_ixs[0].msg_offset as usize..(secp_ixs[0].msg_offset + secp_ixs[0].msg_size) as usize];
// Hash the message part, which contains the serialized VAA body.
let mut msg_hash: [u8; 32] = [0u8; 32];
msg_hash.copy_from_slice(message);
if !accs.signature_set.is_initialized() {
accs.signature_set.signatures = vec![false; accs.guardian_set.keys.len()];
accs.signature_set.guardian_set_index = accs.guardian_set.index;
accs.signature_set.hash = msg_hash;
let size = accs.signature_set.size();
let ix = solana_program::system_instruction::create_account(
accs.payer.key,
accs.signature_set.info().key,
Exempt.amount(size),
size as u64,
ctx.program_id,
);
solana_program::program::invoke(&ix, ctx.accounts)?;
} else {
// If the account already existed, check that the parameters match
if accs.signature_set.guardian_set_index != accs.guardian_set.index {
return Err(GuardianSetMismatch.into());
}
if accs.signature_set.hash != msg_hash {
return Err(InvalidHash.into());
}
}
// Write sigs of checked addresses into sig_state
for s in sig_infos {
if s.signer_index > accs.guardian_set.num_guardians() {
return Err(ProgramError::InvalidArgument.into());
}
if s.sig_index + 1 > sig_len {
return Err(ProgramError::InvalidArgument.into());
}
let key = accs.guardian_set.keys[s.signer_index as usize];
// Check key in ix
if key != secp_ixs[s.sig_index as usize].address {
return Err(ProgramError::InvalidArgument.into());
}
// Overwritten content should be zeros except double signs by the signer or harmless replays
accs.signature_set.signatures[s.signer_index as usize] = true;
}
Ok(())
}

View File

@ -1,35 +0,0 @@
//! Define application level errors that can be returned by the various instruction handlers that
//! make up the wormhole bridge.
use crate::trace;
use solitaire::SolitaireError;
#[derive(Debug)]
pub enum Error {
GuardianSetMismatch,
InstructionAtWrongIndex,
InsufficientFees,
InvalidFeeRecipient,
InvalidGovernanceAction,
InvalidGovernanceChain,
InvalidGovernanceKey,
InvalidGovernanceModule,
InvalidGovernanceWithdrawal,
InvalidGuardianSetUpgrade,
InvalidHash,
InvalidSecpInstruction,
MathOverflow,
PostVAAConsensusFailed,
PostVAAGuardianSetExpired,
TooManyGuardians,
VAAAlreadyExecuted,
}
/// Errors thrown by the program will bubble up to the solitaire wrapper, which needs a way to
/// translate these errors into something Solitaire can log and handle.
impl From<Error> for SolitaireError {
fn from(e: Error) -> SolitaireError {
trace!("ProgramError: {:?}", e);
SolitaireError::Custom(e as u64)
}
}

View File

@ -1,405 +0,0 @@
use borsh::BorshSerialize;
use solana_program::{
instruction::{
AccountMeta,
Instruction,
},
pubkey::Pubkey,
sysvar,
};
use byteorder::{
BigEndian,
WriteBytesExt,
};
use sha3::Digest;
use solitaire::{
processors::seeded::Seeded,
AccountState,
};
use std::io::{
Cursor,
Write,
};
use crate::{
accounts::{
Bridge,
Claim,
ClaimDerivationData,
FeeCollector,
GuardianSet,
GuardianSetDerivationData,
PostedVAA,
PostedVAADerivationData,
Sequence,
SequenceDerivationData,
},
types::ConsistencyLevel,
InitializeData,
PostMessageData,
PostVAAData,
SetFeesData,
TransferFeesData,
UpgradeContractData,
UpgradeGuardianSetData,
VerifySignaturesData,
CHAIN_ID_SOLANA,
};
pub fn initialize(
program_id: Pubkey,
payer: Pubkey,
fee: u64,
guardian_set_expiration_time: u32,
initial_guardians: &[[u8; 20]],
) -> solitaire::Result<Instruction> {
let bridge = Bridge::<'_, { AccountState::Uninitialized }>::key(None, &program_id);
let guardian_set = GuardianSet::<'_, { AccountState::Uninitialized }>::key(
&GuardianSetDerivationData { index: 0 },
&program_id,
);
let fee_collector = FeeCollector::key(None, &program_id);
Ok(Instruction {
program_id,
accounts: vec![
AccountMeta::new(bridge, false),
AccountMeta::new(guardian_set, false),
AccountMeta::new(fee_collector, false),
AccountMeta::new(payer, true),
AccountMeta::new_readonly(sysvar::clock::id(), false),
AccountMeta::new_readonly(sysvar::rent::id(), false),
AccountMeta::new_readonly(solana_program::system_program::id(), false),
],
data: (
crate::instruction::Instruction::Initialize,
InitializeData {
initial_guardians: initial_guardians.to_vec(),
fee,
guardian_set_expiration_time,
},
)
.try_to_vec()?,
})
}
pub fn post_message(
program_id: Pubkey,
payer: Pubkey,
emitter: Pubkey,
message: Pubkey,
nonce: u32,
payload: Vec<u8>,
commitment: ConsistencyLevel,
) -> solitaire::Result<Instruction> {
let bridge = Bridge::<'_, { AccountState::Uninitialized }>::key(None, &program_id);
let fee_collector = FeeCollector::<'_>::key(None, &program_id);
let sequence = Sequence::<'_>::key(
&SequenceDerivationData {
emitter_key: &emitter,
},
&program_id,
);
Ok(Instruction {
program_id,
accounts: vec![
AccountMeta::new(bridge, false),
AccountMeta::new(message, true),
AccountMeta::new_readonly(emitter, true),
AccountMeta::new(sequence, false),
AccountMeta::new(payer, true),
AccountMeta::new(fee_collector, false),
AccountMeta::new_readonly(sysvar::clock::id(), false),
AccountMeta::new_readonly(sysvar::rent::id(), false),
AccountMeta::new_readonly(solana_program::system_program::id(), false),
],
data: (
crate::instruction::Instruction::PostMessage,
PostMessageData {
nonce,
payload: payload.clone(),
consistency_level: commitment,
},
)
.try_to_vec()?,
})
}
pub fn verify_signatures(
program_id: Pubkey,
payer: Pubkey,
guardian_set_index: u32,
signature_set: Pubkey,
data: VerifySignaturesData,
) -> solitaire::Result<Instruction> {
let guardian_set = GuardianSet::<'_, { AccountState::Uninitialized }>::key(
&GuardianSetDerivationData {
index: guardian_set_index,
},
&program_id,
);
Ok(Instruction {
program_id,
accounts: vec![
AccountMeta::new(payer, true),
AccountMeta::new_readonly(guardian_set, false),
AccountMeta::new(signature_set, true),
AccountMeta::new_readonly(sysvar::instructions::id(), false),
AccountMeta::new_readonly(sysvar::rent::id(), false),
AccountMeta::new_readonly(solana_program::system_program::id(), false),
],
data: (crate::instruction::Instruction::VerifySignatures, data).try_to_vec()?,
})
}
pub fn post_vaa(
program_id: Pubkey,
payer: Pubkey,
signature_set: Pubkey,
vaa: PostVAAData,
) -> Instruction {
let bridge = Bridge::<'_, { AccountState::Uninitialized }>::key(None, &program_id);
let guardian_set = GuardianSet::<'_, { AccountState::Uninitialized }>::key(
&GuardianSetDerivationData {
index: vaa.guardian_set_index,
},
&program_id,
);
let msg_derivation_data = &PostedVAADerivationData {
payload_hash: hash_vaa(&vaa).to_vec(),
};
let message =
PostedVAA::<'_, { AccountState::MaybeInitialized }>::key(&msg_derivation_data, &program_id);
Instruction {
program_id,
accounts: vec![
AccountMeta::new_readonly(guardian_set, false),
AccountMeta::new_readonly(bridge, false),
AccountMeta::new_readonly(signature_set, false),
AccountMeta::new(message, false),
AccountMeta::new(payer, true),
AccountMeta::new_readonly(sysvar::clock::id(), false),
AccountMeta::new_readonly(sysvar::rent::id(), false),
AccountMeta::new_readonly(solana_program::system_program::id(), false),
],
data: (crate::instruction::Instruction::PostVAA, vaa)
.try_to_vec()
.unwrap(),
}
}
pub fn upgrade_contract(
program_id: Pubkey,
payer: Pubkey,
payload_message: Pubkey,
emitter: Pubkey,
new_contract: Pubkey,
spill: Pubkey,
sequence: u64,
) -> Instruction {
let bridge = Bridge::<'_, { AccountState::Initialized }>::key(None, &program_id);
let claim = Claim::<'_, { AccountState::Uninitialized }>::key(
&ClaimDerivationData {
emitter_address: emitter.to_bytes(),
emitter_chain: CHAIN_ID_SOLANA,
sequence,
},
&program_id,
);
let (upgrade_authority, _) = Pubkey::find_program_address(&["upgrade".as_bytes()], &program_id);
let (program_data, _) = Pubkey::find_program_address(
&[program_id.as_ref()],
&solana_program::bpf_loader_upgradeable::id(),
);
Instruction {
program_id,
accounts: vec![
AccountMeta::new(payer, true),
AccountMeta::new(bridge, false),
AccountMeta::new_readonly(payload_message, false),
AccountMeta::new(claim, false),
AccountMeta::new_readonly(upgrade_authority, false),
AccountMeta::new(spill, false),
AccountMeta::new(new_contract, false),
AccountMeta::new(program_data, false),
AccountMeta::new(program_id, false),
AccountMeta::new_readonly(sysvar::rent::id(), false),
AccountMeta::new_readonly(sysvar::clock::id(), false),
AccountMeta::new_readonly(solana_program::bpf_loader_upgradeable::id(), false),
AccountMeta::new_readonly(solana_program::system_program::id(), false),
],
data: (
crate::instruction::Instruction::UpgradeContract,
UpgradeContractData {},
)
.try_to_vec()
.unwrap(),
}
}
pub fn upgrade_guardian_set(
program_id: Pubkey,
payer: Pubkey,
payload_message: Pubkey,
emitter: Pubkey,
old_index: u32,
new_index: u32,
sequence: u64,
) -> Instruction {
let bridge = Bridge::<'_, { AccountState::Uninitialized }>::key(None, &program_id);
let claim = Claim::<'_, { AccountState::Uninitialized }>::key(
&ClaimDerivationData {
emitter_address: emitter.to_bytes(),
emitter_chain: CHAIN_ID_SOLANA,
sequence: sequence,
},
&program_id,
);
let guardian_set_old = GuardianSet::<'_, { AccountState::Initialized }>::key(
&GuardianSetDerivationData { index: old_index },
&program_id,
);
let guardian_set_new = GuardianSet::<'_, { AccountState::Uninitialized }>::key(
&GuardianSetDerivationData { index: new_index },
&program_id,
);
Instruction {
program_id,
accounts: vec![
AccountMeta::new(payer, true),
AccountMeta::new(bridge, false),
AccountMeta::new_readonly(payload_message, false),
AccountMeta::new(claim, false),
AccountMeta::new(guardian_set_old, false),
AccountMeta::new(guardian_set_new, false),
AccountMeta::new_readonly(solana_program::system_program::id(), false),
],
data: (
crate::instruction::Instruction::UpgradeGuardianSet,
UpgradeGuardianSetData {},
)
.try_to_vec()
.unwrap(),
}
}
pub fn set_fees(
program_id: Pubkey,
payer: Pubkey,
message: Pubkey,
emitter: Pubkey,
sequence: u64,
) -> Instruction {
let bridge = Bridge::<'_, { AccountState::Uninitialized }>::key(None, &program_id);
let claim = Claim::<'_, { AccountState::Uninitialized }>::key(
&ClaimDerivationData {
emitter_address: emitter.to_bytes(),
emitter_chain: CHAIN_ID_SOLANA,
sequence,
},
&program_id,
);
Instruction {
program_id,
accounts: vec![
AccountMeta::new(payer, true),
AccountMeta::new(bridge, false),
AccountMeta::new_readonly(message, false),
AccountMeta::new(claim, false),
AccountMeta::new_readonly(solana_program::system_program::id(), false),
],
data: (crate::instruction::Instruction::SetFees, SetFeesData {})
.try_to_vec()
.unwrap(),
}
}
pub fn transfer_fees(
program_id: Pubkey,
payer: Pubkey,
message: Pubkey,
emitter: Pubkey,
sequence: u64,
recipient: Pubkey,
) -> Instruction {
let bridge = Bridge::<'_, { AccountState::Uninitialized }>::key(None, &program_id);
let claim = Claim::<'_, { AccountState::Uninitialized }>::key(
&ClaimDerivationData {
emitter_address: emitter.to_bytes(),
emitter_chain: CHAIN_ID_SOLANA,
sequence,
},
&program_id,
);
let fee_collector = FeeCollector::key(None, &program_id);
Instruction {
program_id,
accounts: vec![
AccountMeta::new(payer, true),
AccountMeta::new_readonly(bridge, false),
AccountMeta::new_readonly(message, false),
AccountMeta::new(claim, false),
AccountMeta::new(fee_collector, false),
AccountMeta::new(recipient, false),
AccountMeta::new_readonly(sysvar::rent::id(), false),
AccountMeta::new_readonly(solana_program::system_program::id(), false),
],
data: (
crate::instruction::Instruction::TransferFees,
TransferFeesData {},
)
.try_to_vec()
.unwrap(),
}
}
// Convert a full VAA structure into the serialization of its unique components, this structure is
// what is hashed and verified by Guardians.
pub fn serialize_vaa(vaa: &PostVAAData) -> Vec<u8> {
let mut v = Cursor::new(Vec::new());
v.write_u32::<BigEndian>(vaa.timestamp).unwrap();
v.write_u32::<BigEndian>(vaa.nonce).unwrap();
v.write_u16::<BigEndian>(vaa.emitter_chain).unwrap();
v.write(&vaa.emitter_address).unwrap();
v.write_u64::<BigEndian>(vaa.sequence).unwrap();
v.write_u8(vaa.consistency_level).unwrap();
v.write(&vaa.payload).unwrap();
v.into_inner()
}
// Hash a VAA, this combines serialization and hashing.
pub fn hash_vaa(vaa: &PostVAAData) -> [u8; 32] {
let body = serialize_vaa(vaa);
let mut h = sha3::Keccak256::default();
h.write(body.as_slice()).unwrap();
h.finalize().into()
}

View File

@ -1,96 +0,0 @@
#![feature(adt_const_params)]
#![allow(non_upper_case_globals)]
#![allow(incomplete_features)]
use solitaire::*;
pub const MAX_LEN_GUARDIAN_KEYS: usize = 19;
pub const CHAIN_ID_SOLANA: u16 = 1;
#[cfg(feature = "no-entrypoint")]
pub mod instructions;
#[cfg(feature = "wasm")]
#[cfg(all(target_arch = "wasm32", target_os = "unknown"))]
extern crate wasm_bindgen;
#[cfg(feature = "wasm")]
#[cfg(all(target_arch = "wasm32", target_os = "unknown"))]
pub mod wasm;
pub mod accounts;
pub use accounts::{
BridgeConfig,
BridgeData,
Claim,
ClaimData,
ClaimDerivationData,
FeeCollector,
GuardianSet,
GuardianSetData,
GuardianSetDerivationData,
PostedMessage,
PostedMessageData,
MessageData,
PostedVAA,
PostedVAAData,
Sequence,
SequenceTracker,
SequenceDerivationData,
SignatureSet,
SignatureSetData,
};
pub mod api;
pub use api::{
initialize,
post_message,
post_vaa,
set_fees,
transfer_fees,
upgrade_contract,
upgrade_guardian_set,
verify_signatures,
Initialize,
InitializeData,
PostMessage,
PostMessageData,
PostVAA,
PostVAAData,
SetFees,
SetFeesData,
Signature,
TransferFees,
TransferFeesData,
UninitializedMessage,
UpgradeContract,
UpgradeContractData,
UpgradeGuardianSet,
UpgradeGuardianSetData,
VerifySignatures,
VerifySignaturesData,
};
pub mod error;
pub mod types;
pub mod vaa;
pub use vaa::{
DeserializeGovernancePayload,
DeserializePayload,
PayloadMessage,
SerializeGovernancePayload,
SerializePayload,
};
solitaire! {
Initialize(InitializeData) => initialize,
PostMessage(PostMessageData) => post_message,
PostVAA(PostVAAData) => post_vaa,
SetFees(SetFeesData) => set_fees,
TransferFees(TransferFeesData) => transfer_fees,
UpgradeContract(UpgradeContractData) => upgrade_contract,
UpgradeGuardianSet(UpgradeGuardianSetData) => upgrade_guardian_set,
VerifySignatures(VerifySignaturesData) => verify_signatures,
}

View File

@ -1,235 +0,0 @@
use crate::{
api::ForeignAddress,
vaa::{
DeserializeGovernancePayload,
DeserializePayload,
SerializeGovernancePayload,
SerializePayload,
},
};
use borsh::{
BorshDeserialize,
BorshSerialize,
};
use byteorder::{
BigEndian,
ReadBytesExt,
};
use primitive_types::U256;
use serde::{
Deserialize,
Serialize,
};
use solana_program::{
program_error::ProgramError::InvalidAccountData,
pubkey::Pubkey,
};
use solitaire::SolitaireError;
use std::{
self,
io::{
Cursor,
Read,
Write,
},
};
/// Type representing an Ethereum style public key for Guardians.
pub type GuardianPublicKey = [u8; 20];
#[repr(u8)]
#[derive(BorshSerialize, BorshDeserialize, Clone, Serialize, Deserialize)]
pub enum ConsistencyLevel {
Confirmed,
Finalized,
}
pub struct GovernancePayloadUpgrade {
// Address of the new Implementation
pub new_contract: Pubkey,
}
impl SerializePayload for GovernancePayloadUpgrade {
fn serialize<W: Write>(&self, v: &mut W) -> std::result::Result<(), SolitaireError> {
v.write(&self.new_contract.to_bytes())?;
Ok(())
}
}
impl DeserializePayload for GovernancePayloadUpgrade
where
Self: DeserializeGovernancePayload,
{
fn deserialize(buf: &mut &[u8]) -> Result<Self, SolitaireError> {
let mut c = Cursor::new(buf);
Self::check_governance_header(&mut c)?;
let mut addr = [0u8; 32];
c.read_exact(&mut addr)?;
if c.position() != c.into_inner().len() as u64 {
return Err(InvalidAccountData.into());
}
Ok(GovernancePayloadUpgrade {
new_contract: Pubkey::new(&addr[..]),
})
}
}
impl SerializeGovernancePayload for GovernancePayloadUpgrade {
const MODULE: &'static str = "Core";
const ACTION: u8 = 1;
}
impl DeserializeGovernancePayload for GovernancePayloadUpgrade {
}
pub struct GovernancePayloadGuardianSetChange {
// New GuardianSetIndex
pub new_guardian_set_index: u32,
// New GuardianSet
pub new_guardian_set: Vec<[u8; 20]>,
}
impl SerializePayload for GovernancePayloadGuardianSetChange {
fn serialize<W: Write>(&self, v: &mut W) -> std::result::Result<(), SolitaireError> {
use byteorder::WriteBytesExt;
v.write_u32::<BigEndian>(self.new_guardian_set_index)?;
v.write_u8(self.new_guardian_set.len() as u8)?;
for key in self.new_guardian_set.iter() {
v.write(key)?;
}
Ok(())
}
}
impl DeserializePayload for GovernancePayloadGuardianSetChange
where
Self: DeserializeGovernancePayload,
{
fn deserialize(buf: &mut &[u8]) -> Result<Self, SolitaireError> {
let mut c = Cursor::new(buf);
Self::check_governance_header(&mut c)?;
let new_index = c.read_u32::<BigEndian>()?;
let keys_len = c.read_u8()?;
let mut keys = Vec::with_capacity(keys_len as usize);
for _ in 0..keys_len {
let mut key: [u8; 20] = [0; 20];
c.read(&mut key)?;
keys.push(key);
}
if c.position() != c.into_inner().len() as u64 {
return Err(InvalidAccountData.into());
}
Ok(GovernancePayloadGuardianSetChange {
new_guardian_set_index: new_index,
new_guardian_set: keys,
})
}
}
impl SerializeGovernancePayload for GovernancePayloadGuardianSetChange {
const MODULE: &'static str = "Core";
const ACTION: u8 = 2;
}
impl DeserializeGovernancePayload for GovernancePayloadGuardianSetChange {
}
pub struct GovernancePayloadSetMessageFee {
// New fee in lamports
pub fee: U256,
}
impl SerializePayload for GovernancePayloadSetMessageFee {
fn serialize<W: Write>(&self, v: &mut W) -> std::result::Result<(), SolitaireError> {
let mut fee_data = [0u8; 32];
self.fee.to_big_endian(&mut fee_data);
v.write(&fee_data[..])?;
Ok(())
}
}
impl DeserializePayload for GovernancePayloadSetMessageFee
where
Self: DeserializeGovernancePayload,
{
fn deserialize(buf: &mut &[u8]) -> Result<Self, SolitaireError> {
let mut c = Cursor::new(buf);
Self::check_governance_header(&mut c)?;
let mut fee_data: [u8; 32] = [0; 32];
c.read_exact(&mut fee_data)?;
let fee = U256::from_big_endian(&fee_data);
if c.position() != c.into_inner().len() as u64 {
return Err(InvalidAccountData.into());
}
Ok(GovernancePayloadSetMessageFee { fee })
}
}
impl SerializeGovernancePayload for GovernancePayloadSetMessageFee {
const MODULE: &'static str = "Core";
const ACTION: u8 = 3;
}
impl DeserializeGovernancePayload for GovernancePayloadSetMessageFee {
}
pub struct GovernancePayloadTransferFees {
// Amount to be transferred
pub amount: U256,
// Recipient
pub to: ForeignAddress,
}
impl SerializePayload for GovernancePayloadTransferFees {
fn serialize<W: Write>(&self, v: &mut W) -> std::result::Result<(), SolitaireError> {
let mut amount_data = [0u8; 32];
self.amount.to_big_endian(&mut amount_data);
v.write(&amount_data)?;
v.write(&self.to)?;
Ok(())
}
}
impl DeserializePayload for GovernancePayloadTransferFees
where
Self: DeserializeGovernancePayload,
{
fn deserialize(buf: &mut &[u8]) -> Result<Self, SolitaireError> {
let mut c = Cursor::new(buf);
Self::check_governance_header(&mut c)?;
let mut amount_data: [u8; 32] = [0; 32];
c.read_exact(&mut amount_data)?;
let amount = U256::from_big_endian(&amount_data);
let mut to = ForeignAddress::default();
c.read_exact(&mut to)?;
if c.position() != c.into_inner().len() as u64 {
return Err(InvalidAccountData.into());
}
Ok(GovernancePayloadTransferFees { amount, to })
}
}
impl SerializeGovernancePayload for GovernancePayloadTransferFees {
const MODULE: &'static str = "Core";
const ACTION: u8 = 4;
}
impl DeserializeGovernancePayload for GovernancePayloadTransferFees {
}

View File

@ -1,293 +0,0 @@
use crate::{
api::{
post_vaa::PostVAAData,
ForeignAddress,
},
error::Error::{
InvalidGovernanceAction,
InvalidGovernanceChain,
InvalidGovernanceModule,
VAAAlreadyExecuted,
},
Claim,
ClaimDerivationData,
PostedVAAData,
Result,
CHAIN_ID_SOLANA,
};
use byteorder::{
BigEndian,
ReadBytesExt,
};
use serde::{
Deserialize,
Serialize,
};
use solana_program::pubkey::Pubkey;
use solitaire::{
processors::seeded::Seeded,
trace,
Context,
CreationLamports::Exempt,
Data,
ExecutionContext,
Peel,
SolitaireError,
*,
};
use std::{
io::{
Cursor,
Read,
Write,
},
ops::Deref,
};
pub trait SerializePayload: Sized {
fn serialize<W: Write>(&self, writer: &mut W) -> std::result::Result<(), SolitaireError>;
fn try_to_vec(&self) -> std::result::Result<Vec<u8>, SolitaireError> {
let mut result = Vec::with_capacity(256);
self.serialize(&mut result)?;
Ok(result)
}
}
pub trait DeserializePayload: Sized {
fn deserialize(buf: &mut &[u8]) -> std::result::Result<Self, SolitaireError>;
}
pub trait SerializeGovernancePayload: SerializePayload {
const MODULE: &'static str;
const ACTION: u8;
fn try_to_vec(&self) -> std::result::Result<Vec<u8>, SolitaireError> {
let mut result = Vec::with_capacity(256);
self.write_governance_header(&mut result)?;
self.serialize(&mut result)?;
Ok(result)
}
fn write_governance_header<W: Write>(
&self,
c: &mut W,
) -> std::result::Result<(), SolitaireError> {
use byteorder::WriteBytesExt;
let module = format!("{:\0>32}", Self::MODULE);
let module = module.as_bytes();
c.write(&module)?;
c.write_u8(Self::ACTION)?;
c.write_u16::<BigEndian>(CHAIN_ID_SOLANA)?;
Ok(())
}
}
pub trait DeserializeGovernancePayload: DeserializePayload + SerializeGovernancePayload {
fn check_governance_header(
c: &mut Cursor<&mut &[u8]>,
) -> std::result::Result<(), SolitaireError> {
let mut module = [0u8; 32];
c.read_exact(&mut module)?;
if module != format!("{:\0>32}", Self::MODULE).as_bytes() {
return Err(InvalidGovernanceModule.into());
}
let action = c.read_u8()?;
if action != Self::ACTION {
return Err(InvalidGovernanceAction.into());
}
let chain = c.read_u16::<BigEndian>()?;
if chain != CHAIN_ID_SOLANA && chain != 0 {
return Err(InvalidGovernanceChain.into());
}
Ok(())
}
}
pub struct PayloadMessage<'b, T: DeserializePayload>(
Data<'b, PostedVAAData, { AccountState::Initialized }>,
T,
);
impl<'a, 'b: 'a, 'c, T: DeserializePayload> Peel<'a, 'b, 'c> for PayloadMessage<'b, T> {
fn peel<I>(ctx: &'c mut Context<'a, 'b, 'c, I>) -> Result<Self>
where
Self: Sized,
{
// Deserialize wrapped payload
let data: Data<'b, PostedVAAData, { AccountState::Initialized }> = Data::peel(ctx)?;
let payload = DeserializePayload::deserialize(&mut &data.payload[..])?;
Ok(PayloadMessage(data, payload))
}
fn deps() -> Vec<Pubkey> {
Data::<'b, PostedVAAData, { AccountState::Initialized }>::deps()
}
fn persist(&self, program_id: &Pubkey) -> Result<()> {
Data::persist(&self.0, program_id)
}
}
impl<'b, T: DeserializePayload> Deref for PayloadMessage<'b, T> {
type Target = T;
fn deref(&self) -> &Self::Target {
&self.1
}
}
impl<'b, T: DeserializePayload> PayloadMessage<'b, T> {
pub fn meta(&self) -> &PostedVAAData {
&self.0
}
}
#[derive(FromAccounts)]
pub struct ClaimableVAA<'b, T: DeserializePayload> {
// Signed message for the transfer
pub message: PayloadMessage<'b, T>,
// Claim account to prevent double spending
pub claim: Mut<Claim<'b, { AccountState::Uninitialized }>>,
}
impl<'b, T: DeserializePayload> Deref for ClaimableVAA<'b, T> {
type Target = PayloadMessage<'b, T>;
fn deref(&self) -> &Self::Target {
&self.message
}
}
impl<'b, T: DeserializePayload> ClaimableVAA<'b, T> {
pub fn verify(&self, program_id: &Pubkey) -> Result<()> {
trace!("Seq: {}", self.message.meta().sequence);
// Verify that the claim account is derived correctly
self.claim.verify_derivation(
program_id,
&ClaimDerivationData {
emitter_address: self.message.meta().emitter_address,
emitter_chain: self.message.meta().emitter_chain,
sequence: self.message.meta().sequence,
},
)?;
Ok(())
}
}
impl<'b, T: DeserializePayload> ClaimableVAA<'b, T> {
pub fn is_claimed(&self) -> bool {
self.claim.claimed
}
pub fn claim(&mut self, ctx: &ExecutionContext, payer: &Pubkey) -> Result<()> {
if self.is_claimed() {
return Err(VAAAlreadyExecuted.into());
}
self.claim.create(
&ClaimDerivationData {
emitter_address: self.message.meta().emitter_address,
emitter_chain: self.message.meta().emitter_chain,
sequence: self.message.meta().sequence,
},
ctx,
payer,
Exempt,
)?;
self.claim.claimed = true;
Ok(())
}
}
pub struct SignatureItem {
pub signature: Vec<u8>,
pub key: [u8; 20],
pub index: u8,
}
#[derive(Serialize, Deserialize, Default, Clone)]
pub struct VAASignature {
pub signature: Vec<u8>,
pub guardian_index: u8,
}
#[derive(Serialize, Deserialize, Default, Clone)]
pub struct VAA {
// Header part
pub version: u8,
pub guardian_set_index: u32,
pub signatures: Vec<VAASignature>,
// Body part
pub timestamp: u32,
pub nonce: u32,
pub emitter_chain: u16,
pub emitter_address: ForeignAddress,
pub sequence: u64,
pub consistency_level: u8,
pub payload: Vec<u8>,
}
impl VAA {
pub const HEADER_LEN: usize = 6;
pub const SIGNATURE_LEN: usize = 66;
pub fn deserialize(data: &[u8]) -> std::result::Result<VAA, std::io::Error> {
let mut rdr = Cursor::new(data);
let mut v = VAA::default();
v.version = rdr.read_u8()?;
v.guardian_set_index = rdr.read_u32::<BigEndian>()?;
let len_sig = rdr.read_u8()?;
let mut sigs: Vec<VAASignature> = Vec::with_capacity(len_sig as usize);
for _i in 0..len_sig {
let mut sig = VAASignature::default();
sig.guardian_index = rdr.read_u8()?;
let mut signature_data = [0u8; 65];
rdr.read_exact(&mut signature_data)?;
sig.signature = signature_data.to_vec();
sigs.push(sig);
}
v.signatures = sigs;
v.timestamp = rdr.read_u32::<BigEndian>()?;
v.nonce = rdr.read_u32::<BigEndian>()?;
v.emitter_chain = rdr.read_u16::<BigEndian>()?;
let mut emitter_address = [0u8; 32];
rdr.read_exact(&mut emitter_address)?;
v.emitter_address = emitter_address;
v.sequence = rdr.read_u64::<BigEndian>()?;
v.consistency_level = rdr.read_u8()?;
rdr.read_to_end(&mut v.payload)?;
Ok(v)
}
}
impl From<VAA> for PostVAAData {
fn from(vaa: VAA) -> Self {
PostVAAData {
version: vaa.version,
guardian_set_index: vaa.guardian_set_index,
timestamp: vaa.timestamp,
nonce: vaa.nonce,
emitter_chain: vaa.emitter_chain,
emitter_address: vaa.emitter_address,
sequence: vaa.sequence,
consistency_level: vaa.consistency_level,
payload: vaa.payload,
}
}
}

View File

@ -1,372 +0,0 @@
use solana_program::{
instruction::Instruction,
pubkey::Pubkey,
};
use std::str::FromStr;
use crate::vaa::{
DeserializePayload,
SignatureItem,
VAA,
};
use borsh::BorshDeserialize;
use byteorder::WriteBytesExt;
use sha3::Digest;
use solitaire::{
processors::seeded::Seeded,
AccountState,
};
use std::io::Write;
use crate::{
accounts::{
Bridge,
BridgeData,
FeeCollector,
GuardianSet,
GuardianSetData,
GuardianSetDerivationData,
PostedVAA,
PostedVAAData,
PostedVAADerivationData,
},
instructions::{
hash_vaa,
post_message,
post_vaa,
set_fees,
transfer_fees,
upgrade_contract,
upgrade_guardian_set,
verify_signatures,
},
types::{
ConsistencyLevel,
GovernancePayloadGuardianSetChange,
GovernancePayloadTransferFees,
GovernancePayloadUpgrade,
},
Claim,
ClaimDerivationData,
PostVAAData,
VerifySignaturesData,
};
use byteorder::LittleEndian;
use wasm_bindgen::prelude::*;
#[wasm_bindgen]
pub fn post_message_ix(
program_id: String,
payer: String,
emitter: String,
message: String,
nonce: u32,
msg: Vec<u8>,
consistency: String,
) -> JsValue {
let consistency_level = match consistency.as_str() {
"CONFIRMED" => ConsistencyLevel::Confirmed,
"FINALIZED" => ConsistencyLevel::Finalized,
_ => panic!("invalid consistency level"),
};
let ix = post_message(
Pubkey::from_str(program_id.as_str()).unwrap(),
Pubkey::from_str(payer.as_str()).unwrap(),
Pubkey::from_str(emitter.as_str()).unwrap(),
Pubkey::from_str(message.as_str()).unwrap(),
nonce,
msg,
consistency_level,
)
.unwrap();
return JsValue::from_serde(&ix).unwrap();
}
#[wasm_bindgen]
pub fn post_vaa_ix(
program_id: String,
payer: String,
signature_set: String,
vaa: Vec<u8>,
) -> JsValue {
let vaa = VAA::deserialize(vaa.as_slice()).unwrap();
let vaa = PostVAAData {
version: vaa.version,
guardian_set_index: vaa.guardian_set_index,
timestamp: vaa.timestamp,
nonce: vaa.nonce,
emitter_chain: vaa.emitter_chain,
emitter_address: vaa.emitter_address,
sequence: vaa.sequence,
consistency_level: vaa.consistency_level,
payload: vaa.payload,
};
let ix = post_vaa(
Pubkey::from_str(program_id.as_str()).unwrap(),
Pubkey::from_str(payer.as_str()).unwrap(),
Pubkey::from_str(signature_set.as_str()).unwrap(),
vaa,
);
return JsValue::from_serde(&ix).unwrap();
}
#[wasm_bindgen]
pub fn update_guardian_set_ix(program_id: String, payer: String, vaa: Vec<u8>) -> JsValue {
let program_id = Pubkey::from_str(program_id.as_str()).unwrap();
let vaa = VAA::deserialize(vaa.as_slice()).unwrap();
let payload =
GovernancePayloadGuardianSetChange::deserialize(&mut vaa.payload.as_slice()).unwrap();
let message_key = PostedVAA::<'_, { AccountState::Uninitialized }>::key(
&PostedVAADerivationData {
payload_hash: hash_vaa(&vaa.clone().into()).to_vec(),
},
&program_id,
);
let ix = upgrade_guardian_set(
program_id,
Pubkey::from_str(payer.as_str()).unwrap(),
message_key,
Pubkey::new(&vaa.emitter_address),
payload.new_guardian_set_index - 1,
payload.new_guardian_set_index,
vaa.sequence,
);
return JsValue::from_serde(&ix).unwrap();
}
#[wasm_bindgen]
pub fn set_fees_ix(program_id: String, payer: String, vaa: Vec<u8>) -> JsValue {
let program_id = Pubkey::from_str(program_id.as_str()).unwrap();
let vaa = VAA::deserialize(vaa.as_slice()).unwrap();
let message_key = PostedVAA::<'_, { AccountState::Uninitialized }>::key(
&PostedVAADerivationData {
payload_hash: hash_vaa(&vaa.clone().into()).to_vec(),
},
&program_id,
);
let ix = set_fees(
program_id,
Pubkey::from_str(payer.as_str()).unwrap(),
message_key,
Pubkey::new(&vaa.emitter_address),
vaa.sequence,
);
return JsValue::from_serde(&ix).unwrap();
}
#[wasm_bindgen]
pub fn transfer_fees_ix(program_id: String, payer: String, vaa: Vec<u8>) -> JsValue {
let program_id = Pubkey::from_str(program_id.as_str()).unwrap();
let vaa = VAA::deserialize(vaa.as_slice()).unwrap();
let payload = GovernancePayloadTransferFees::deserialize(&mut vaa.payload.as_slice()).unwrap();
let message_key = PostedVAA::<'_, { AccountState::Uninitialized }>::key(
&PostedVAADerivationData {
payload_hash: hash_vaa(&vaa.clone().into()).to_vec(),
},
&program_id,
);
let ix = transfer_fees(
program_id,
Pubkey::from_str(payer.as_str()).unwrap(),
message_key,
Pubkey::new(&vaa.emitter_address),
vaa.sequence,
Pubkey::new(&payload.to[..]),
);
return JsValue::from_serde(&ix).unwrap();
}
#[wasm_bindgen]
pub fn upgrade_contract_ix(
program_id: String,
payer: String,
spill: String,
vaa: Vec<u8>,
) -> JsValue {
let program_id = Pubkey::from_str(program_id.as_str()).unwrap();
let spill = Pubkey::from_str(spill.as_str()).unwrap();
let vaa = VAA::deserialize(vaa.as_slice()).unwrap();
let payload = GovernancePayloadUpgrade::deserialize(&mut vaa.payload.as_slice()).unwrap();
let message_key = PostedVAA::<'_, { AccountState::Uninitialized }>::key(
&PostedVAADerivationData {
payload_hash: hash_vaa(&vaa.clone().into()).to_vec(),
},
&program_id,
);
let ix = upgrade_contract(
program_id,
Pubkey::from_str(payer.as_str()).unwrap(),
message_key,
Pubkey::new(&vaa.emitter_address),
payload.new_contract,
spill,
vaa.sequence,
);
return JsValue::from_serde(&ix).unwrap();
}
#[wasm_bindgen]
pub fn verify_signatures_ix(
program_id: String,
payer: String,
guardian_set_index: u32,
guardian_set: JsValue,
signature_set: String,
vaa_data: Vec<u8>,
) -> JsValue {
let program_id = Pubkey::from_str(program_id.as_str()).unwrap();
let payer = Pubkey::from_str(payer.as_str()).unwrap();
let signature_set = Pubkey::from_str(signature_set.as_str()).unwrap();
let guardian_set: GuardianSetData = guardian_set.into_serde().unwrap();
let vaa = VAA::deserialize(vaa_data.as_slice()).unwrap();
// Map signatures to guardian set
let mut signature_items: Vec<SignatureItem> = Vec::new();
for s in vaa.signatures.iter() {
let mut item = SignatureItem {
signature: s.signature.clone(),
key: [0; 20],
index: s.guardian_index as u8,
};
item.key = guardian_set.keys[s.guardian_index as usize];
signature_items.push(item);
}
let vaa_body = &vaa_data[VAA::HEADER_LEN + VAA::SIGNATURE_LEN * vaa.signatures.len()..];
let body_hash: [u8; 32] = {
let mut h = sha3::Keccak256::default();
h.write(vaa_body).unwrap();
h.finalize().into()
};
let mut verify_txs: Vec<Vec<Instruction>> = Vec::new();
for (_tx_index, chunk) in signature_items.chunks(7).enumerate() {
let mut secp_payload = Vec::new();
let mut signature_status = [-1i8; 19];
let data_offset = 1 + chunk.len() * 11;
let message_offset = data_offset + chunk.len() * 85;
// 1 number of signatures
secp_payload.write_u8(chunk.len() as u8).unwrap();
// Secp signature info description (11 bytes * n)
for (i, s) in chunk.iter().enumerate() {
secp_payload
.write_u16::<LittleEndian>((data_offset + 85 * i) as u16)
.unwrap();
secp_payload.write_u8(0).unwrap();
secp_payload
.write_u16::<LittleEndian>((data_offset + 85 * i + 65) as u16)
.unwrap();
secp_payload.write_u8(0).unwrap();
secp_payload
.write_u16::<LittleEndian>(message_offset as u16)
.unwrap();
secp_payload
.write_u16::<LittleEndian>(body_hash.len() as u16)
.unwrap();
secp_payload.write_u8(0).unwrap();
signature_status[s.index as usize] = i as i8;
}
// Write signatures and addresses
for s in chunk.iter() {
secp_payload.write(&s.signature).unwrap();
secp_payload.write(&s.key).unwrap();
}
// Write body
secp_payload.write(&body_hash).unwrap();
let secp_ix = Instruction {
program_id: solana_program::secp256k1_program::id(),
data: secp_payload,
accounts: vec![],
};
let payload = VerifySignaturesData {
signers: signature_status,
};
let verify_ix = match verify_signatures(
program_id,
payer,
guardian_set_index,
signature_set,
payload,
) {
Ok(v) => v,
Err(e) => panic!("{:?}", e),
};
verify_txs.push(vec![secp_ix, verify_ix])
}
JsValue::from_serde(&verify_txs).unwrap()
}
#[wasm_bindgen]
pub fn guardian_set_address(bridge: String, index: u32) -> Vec<u8> {
let program_id = Pubkey::from_str(bridge.as_str()).unwrap();
let guardian_key = GuardianSet::<'_, { AccountState::Initialized }>::key(
&GuardianSetDerivationData { index: index },
&program_id,
);
guardian_key.to_bytes().to_vec()
}
#[wasm_bindgen]
pub fn parse_guardian_set(data: Vec<u8>) -> JsValue {
JsValue::from_serde(&GuardianSetData::try_from_slice(data.as_slice()).unwrap()).unwrap()
}
#[wasm_bindgen]
pub fn state_address(bridge: String) -> Vec<u8> {
let program_id = Pubkey::from_str(bridge.as_str()).unwrap();
let bridge_key = Bridge::<'_, { AccountState::Initialized }>::key(None, &program_id);
bridge_key.to_bytes().to_vec()
}
#[wasm_bindgen]
pub fn parse_state(data: Vec<u8>) -> JsValue {
JsValue::from_serde(&BridgeData::try_from_slice(data.as_slice()).unwrap()).unwrap()
}
#[wasm_bindgen]
pub fn fee_collector_address(bridge: String) -> Vec<u8> {
let program_id = Pubkey::from_str(bridge.as_str()).unwrap();
let bridge_key = FeeCollector::key(None, &program_id);
bridge_key.to_bytes().to_vec()
}
#[wasm_bindgen]
pub fn claim_address(program_id: String, vaa: Vec<u8>) -> Vec<u8> {
let program_id = Pubkey::from_str(program_id.as_str()).unwrap();
let vaa = VAA::deserialize(vaa.as_slice()).unwrap();
let claim_key = Claim::<'_, { AccountState::Initialized }>::key(
&ClaimDerivationData {
emitter_address: vaa.emitter_address,
emitter_chain: vaa.emitter_chain,
sequence: vaa.sequence,
},
&program_id,
);
claim_key.to_bytes().to_vec()
}
#[wasm_bindgen]
pub fn parse_posted_message(data: Vec<u8>) -> JsValue {
JsValue::from_serde(&PostedVAAData::try_from_slice(data.as_slice()).unwrap().0).unwrap()
}
#[wasm_bindgen]
pub fn parse_vaa(data: Vec<u8>) -> JsValue {
JsValue::from_serde(&VAA::deserialize(data.as_slice()).unwrap()).unwrap()
}

View File

@ -1,478 +0,0 @@
#![allow(warnings)]
use borsh::{
BorshDeserialize,
BorshSerialize,
};
use byteorder::{
BigEndian,
WriteBytesExt,
};
use hex_literal::hex;
use secp256k1::{
Message as Secp256k1Message,
PublicKey,
SecretKey,
};
use sha3::Digest;
use solana_client::{
client_error::ClientError,
rpc_client::RpcClient,
rpc_config::RpcSendTransactionConfig,
};
use solana_program::{
borsh::try_from_slice_unchecked,
hash,
instruction::{
AccountMeta,
Instruction,
},
program_pack::Pack,
pubkey::Pubkey,
system_instruction::{
self,
create_account,
},
system_program,
sysvar,
};
use solana_sdk::{
commitment_config::CommitmentConfig,
secp256k1_instruction::new_secp256k1_instruction,
signature::{
read_keypair_file,
Keypair,
Signature,
Signer,
},
transaction::Transaction,
};
use std::{
convert::TryInto,
env,
io::{
Cursor,
Write,
},
time::{
Duration,
SystemTime,
},
};
use bridge::{
accounts::{
BridgeConfig,
FeeCollector,
GuardianSet,
GuardianSetDerivationData,
PostedVAAData,
PostedVAADerivationData,
Sequence,
SequenceDerivationData,
SequenceTracker,
SignatureSet,
},
instruction,
instructions,
types::ConsistencyLevel,
Initialize,
InitializeData,
PostMessageData,
PostVAAData,
UninitializedMessage,
VerifySignaturesData,
};
use solitaire::{
processors::seeded::Seeded,
AccountState,
};
pub use helpers::*;
/// Simple API wrapper for quickly preparing and sending transactions.
pub fn execute(
client: &RpcClient,
payer: &Keypair,
signers: &[&Keypair],
instructions: &[Instruction],
commitment_level: CommitmentConfig,
) -> Result<Signature, ClientError> {
let mut transaction = Transaction::new_with_payer(instructions, Some(&payer.pubkey()));
let recent_blockhash = client.get_recent_blockhash().unwrap().0;
transaction.sign(&signers.to_vec(), recent_blockhash);
client.send_and_confirm_transaction_with_spinner_and_config(
&transaction,
commitment_level,
RpcSendTransactionConfig {
skip_preflight: true,
preflight_commitment: None,
encoding: None,
},
)
}
mod helpers {
use super::*;
/// Initialize the test environment, spins up a solana-test-validator in the background so that
/// each test has a fresh environment to work within.
pub fn setup() -> (Keypair, RpcClient, Pubkey) {
let payer = env::var("BRIDGE_PAYER").unwrap_or("./payer.json".to_string());
let rpc_address = env::var("BRIDGE_RPC").unwrap_or("http://127.0.0.1:8899".to_string());
let payer = read_keypair_file(payer).unwrap();
let rpc = RpcClient::new(rpc_address);
let program = env::var("BRIDGE_PROGRAM")
.unwrap_or("Bridge1p5gheXUvJ6jGWGeCsgPKgnE3YgdGKRVCMY9o".to_string())
.parse::<Pubkey>()
.unwrap();
(payer, rpc, program)
}
/// Wait for a single transaction to fully finalize, guaranteeing chain state has been
/// confirmed. Useful for consistently fetching data during state checks.
pub fn sync(client: &RpcClient, payer: &Keypair) {
execute(
client,
payer,
&[payer],
&[system_instruction::transfer(
&payer.pubkey(),
&payer.pubkey(),
1,
)],
CommitmentConfig::finalized(),
)
.unwrap();
}
/// Fetch account data, the loop is there to re-attempt until data is available.
pub fn get_account_data<T: BorshDeserialize>(client: &RpcClient, account: &Pubkey) -> T {
let account = client.get_account(account).unwrap();
T::try_from_slice(&account.data).unwrap()
}
/// Generate `count` secp256k1 private keys, along with their ethereum-styled public key
/// encoding: 0x0123456789ABCDEF01234
pub fn generate_keys(count: u8) -> (Vec<[u8; 20]>, Vec<SecretKey>) {
use rand::Rng;
use sha3::Digest;
let mut rng = rand::thread_rng();
// Generate Guardian Keys
let secret_keys: Vec<SecretKey> = std::iter::repeat_with(|| SecretKey::random(&mut rng))
.take(count as usize)
.collect();
(
secret_keys
.iter()
.map(|key| {
let public_key = PublicKey::from_secret_key(&key);
let mut h = sha3::Keccak256::default();
h.write(&public_key.serialize()[1..]).unwrap();
let key: [u8; 32] = h.finalize().into();
let mut address = [0u8; 20];
address.copy_from_slice(&key[12..]);
address
})
.collect(),
secret_keys,
)
}
/// Utility function for generating VAA's from message data.
pub fn generate_vaa(
emitter: &Keypair,
data: Vec<u8>,
nonce: u32,
guardian_set_index: u32,
emitter_chain: u16,
) -> (PostVAAData, [u8; 32], [u8; 32]) {
let mut vaa = PostVAAData {
version: 0,
guardian_set_index,
// Body part
emitter_chain,
emitter_address: emitter.pubkey().to_bytes(),
sequence: 0,
payload: data,
timestamp: SystemTime::now()
.duration_since(SystemTime::UNIX_EPOCH)
.unwrap()
.as_secs() as u32,
nonce,
consistency_level: ConsistencyLevel::Confirmed as u8,
};
// Hash data, the thing we wish to actually sign.
let body = {
let mut v = Cursor::new(Vec::new());
v.write_u32::<BigEndian>(vaa.timestamp).unwrap();
v.write_u32::<BigEndian>(vaa.nonce).unwrap();
v.write_u16::<BigEndian>(vaa.emitter_chain).unwrap();
v.write(&vaa.emitter_address).unwrap();
v.write_u64::<BigEndian>(vaa.sequence).unwrap();
v.write_u8(vaa.consistency_level).unwrap();
v.write(&vaa.payload).unwrap();
v.into_inner()
};
// Hash this body, which is expected to be the same as the hash currently stored in the
// signature account, binding that set of signatures to this VAA.
let body: [u8; 32] = {
let mut h = sha3::Keccak256::default();
h.write(body.as_slice()).unwrap();
h.finalize().into()
};
let body_hash: [u8; 32] = {
let mut h = sha3::Keccak256::default();
h.write(&body).unwrap();
h.finalize().into()
};
(vaa, body, body_hash)
}
pub fn transfer(
client: &RpcClient,
from: &Keypair,
to: &Pubkey,
lamports: u64,
) -> Result<Signature, ClientError> {
execute(
client,
from,
&[from],
&[system_instruction::transfer(&from.pubkey(), to, lamports)],
CommitmentConfig::processed(),
)
}
pub fn initialize(
client: &RpcClient,
program: &Pubkey,
payer: &Keypair,
initial_guardians: &[[u8; 20]],
fee: u64,
) -> Result<Signature, ClientError> {
execute(
client,
payer,
&[payer],
&[instructions::initialize(
*program,
payer.pubkey(),
fee,
2_000_000_000,
initial_guardians,
)
.unwrap()],
CommitmentConfig::processed(),
)
}
pub fn post_message(
client: &RpcClient,
program: &Pubkey,
payer: &Keypair,
emitter: &Keypair,
nonce: u32,
data: Vec<u8>,
fee: u64,
) -> Result<Pubkey, ClientError> {
// Transfer money into the fee collector as it needs a balance/must exist.
let fee_collector = FeeCollector::<'_>::key(None, program);
let message = Keypair::new();
// Capture the resulting message, later functions will need this.
let instruction = instructions::post_message(
*program,
payer.pubkey(),
emitter.pubkey(),
message.pubkey(),
nonce,
data,
ConsistencyLevel::Confirmed,
)
.unwrap();
execute(
client,
payer,
&[payer, emitter, &message],
&[
system_instruction::transfer(&payer.pubkey(), &fee_collector, fee),
instruction,
],
CommitmentConfig::processed(),
)?;
Ok(message.pubkey())
}
pub fn verify_signatures(
client: &RpcClient,
program: &Pubkey,
payer: &Keypair,
body: [u8; 32],
secret_keys: &[SecretKey],
guardian_set_version: u32,
) -> Result<Pubkey, ClientError> {
let signature_set = Keypair::new();
let tx_signers = &[payer, &signature_set];
// Push Secp256k1 instructions for each signature we want to verify.
for (i, key) in secret_keys.iter().enumerate() {
// Set this signers signature position as present at 0.
let mut signers = [-1; 19];
signers[i] = 0;
execute(
client,
payer,
tx_signers,
&vec![
new_secp256k1_instruction(&key, &body),
instructions::verify_signatures(
*program,
payer.pubkey(),
guardian_set_version,
signature_set.pubkey(),
VerifySignaturesData { signers },
)
.unwrap(),
],
CommitmentConfig::processed(),
)?;
}
Ok(signature_set.pubkey())
}
pub fn post_vaa(
client: &RpcClient,
program: &Pubkey,
payer: &Keypair,
signature_set: Pubkey,
vaa: PostVAAData,
) -> Result<Signature, ClientError> {
execute(
client,
payer,
&[payer],
&[instructions::post_vaa(
*program,
payer.pubkey(),
signature_set,
vaa,
)],
CommitmentConfig::processed(),
)
}
pub fn upgrade_guardian_set(
client: &RpcClient,
program: &Pubkey,
payer: &Keypair,
payload_message: Pubkey,
emitter: Pubkey,
old_index: u32,
new_index: u32,
sequence: u64,
) -> Result<Signature, ClientError> {
execute(
client,
payer,
&[payer],
&[instructions::upgrade_guardian_set(
*program,
payer.pubkey(),
payload_message,
emitter,
old_index,
new_index,
sequence,
)],
CommitmentConfig::processed(),
)
}
pub fn upgrade_contract(
client: &RpcClient,
program: &Pubkey,
payer: &Keypair,
payload_message: Pubkey,
emitter: Pubkey,
new_contract: Pubkey,
spill: Pubkey,
sequence: u64,
) -> Result<Signature, ClientError> {
execute(
client,
payer,
&[payer],
&[instructions::upgrade_contract(
*program,
payer.pubkey(),
payload_message,
emitter,
new_contract,
spill,
sequence,
)],
CommitmentConfig::processed(),
)
}
pub fn set_fees(
client: &RpcClient,
program: &Pubkey,
payer: &Keypair,
message: Pubkey,
emitter: Pubkey,
sequence: u64,
) -> Result<Signature, ClientError> {
execute(
client,
payer,
&[payer],
&[instructions::set_fees(
*program,
payer.pubkey(),
message,
emitter,
sequence,
)],
CommitmentConfig::processed(),
)
}
pub fn transfer_fees(
client: &RpcClient,
program: &Pubkey,
payer: &Keypair,
message: Pubkey,
emitter: Pubkey,
recipient: Pubkey,
sequence: u64,
) -> Result<Signature, ClientError> {
execute(
client,
payer,
&[payer],
&[instructions::transfer_fees(
*program,
payer.pubkey(),
message,
emitter,
sequence,
recipient,
)],
CommitmentConfig::processed(),
)
}
}

File diff suppressed because it is too large Load Diff

View File

@ -1,34 +0,0 @@
[package]
name = "bridge_stub"
version = "0.1.0"
description = "Wormhole bridge core contract"
edition = "2018"
[lib]
crate-type = ["cdylib", "lib"]
name = "bridge_stub"
[features]
client = ["solitaire/client", "solitaire-client", "no-entrypoint"]
cpi = ["no-entrypoint"]
default = []
no-entrypoint = ["solitaire/no-entrypoint"]
trace = ["solitaire/trace"]
[dependencies]
borsh = "=0.9.3"
byteorder = "1.4.3"
primitive-types = { version = "0.9.0", default-features = false }
sha3 = "0.9.1"
solana-program = "=1.10.13"
solitaire-client = { path = "../../solitaire/client", optional = true }
solitaire = { path = "../../solitaire/program" }
wormhole-bridge-solana = { path = "../program", features = ["no-entrypoint"] }
[dev-dependencies]
hex = "*"
rand = "0.7.3"
hex-literal = "0.3.1"
libsecp256k1 = { version = "0.3.5", features = [] }
solana-client = "=1.10.13"
solana-sdk = "=1.10.13"

View File

@ -1,2 +0,0 @@
[target.bpfel-unknown-unknown.dependencies.std]
features = []

View File

@ -1,7 +0,0 @@
pub mod post_vaa;
pub use bridge::{
initialize::*,
post_message::*,
};
pub use post_vaa::*;

View File

@ -1,88 +0,0 @@
use solitaire::*;
use borsh::{
BorshDeserialize,
BorshSerialize,
};
use solana_program::{
self,
sysvar::clock::Clock,
};
use bridge::{
accounts::{
Bridge,
GuardianSetDerivationData,
PostedVAA,
PostedVAADerivationData,
},
instructions::hash_vaa,
PostVAAData,
CHAIN_ID_SOLANA,
};
use solitaire::{
processors::seeded::Seeded,
CreationLamports::Exempt,
};
#[derive(FromAccounts)]
pub struct PostVAA<'b> {
/// Information about the current guardian set.
pub guardian_set: Info<'b>,
/// Bridge Info
pub bridge_info: Bridge<'b, { AccountState::Initialized }>,
/// Signature Info
pub signature_set: Info<'b>,
/// Message the VAA is associated with.
pub message: Mut<PostedVAA<'b, { AccountState::MaybeInitialized }>>,
/// Account used to pay for auxillary instructions.
pub payer: Mut<Signer<Info<'b>>>,
/// Clock used for timestamping.
pub clock: Sysvar<'b, Clock>,
}
impl<'b> InstructionContext<'b> for PostVAA<'b> {
}
#[derive(Default, BorshSerialize, BorshDeserialize)]
pub struct Signature {
pub index: u8,
pub r: [u8; 32],
pub s: [u8; 32],
pub v: u8,
}
pub type ForeignAddress = [u8; 32];
pub fn post_vaa(ctx: &ExecutionContext, accs: &mut PostVAA, vaa: PostVAAData) -> Result<()> {
let mut msg_derivation = PostedVAADerivationData {
payload_hash: hash_vaa(&vaa).to_vec(),
};
accs.message
.verify_derivation(ctx.program_id, &msg_derivation)?;
// If the VAA originates from another chain we need to create the account and populate all fields
if !accs.message.is_initialized() {
accs.message.nonce = vaa.nonce;
accs.message.emitter_chain = vaa.emitter_chain;
accs.message.emitter_address = vaa.emitter_address;
accs.message.sequence = vaa.sequence;
accs.message.payload = vaa.payload;
accs.message.consistency_level = vaa.consistency_level;
accs.message
.create(&msg_derivation, ctx, accs.payer.key, Exempt)?;
}
// Store VAA data in associated message.
accs.message.vaa_version = vaa.version;
accs.message.vaa_time = vaa.timestamp;
accs.message.vaa_signature_account = *accs.signature_set.info().key;
Ok(())
}

View File

@ -1,29 +0,0 @@
#![feature(adt_const_params)]
#![allow(non_upper_case_globals)]
#![allow(incomplete_features)]
pub mod api;
use solitaire::*;
pub use api::{
initialize,
post_message,
post_vaa,
Initialize,
InitializeData,
PostMessage,
PostMessageData,
PostVAA,
Signature,
UninitializedMessage,
};
use bridge::PostVAAData;
solitaire! {
Initialize(InitializeData) => initialize,
PostMessage(PostMessageData) => post_message,
PostVAA(PostVAAData) => post_vaa,
}

View File

@ -25,7 +25,7 @@ retry solana airdrop 1000
# Create the bridge contract at a known address
# OK to fail on subsequent attempts (already created).
retry client create-bridge "$bridge_address" "$initial_guardian" 86400 100
retry bridge_client create-bridge "$bridge_address" "$initial_guardian" 86400 100
# Let k8s startup probe succeed
nc -k -l -p 2000

File diff suppressed because it is too large Load Diff

View File

@ -1,4 +1,4 @@
[workspace]
members = ["client", "program"]
[patch.crates-io]
memmap2 = { path = "../bridge/memmap2-rs" }
memmap2 = { path = "../memmap2-rs" }

View File

@ -16,23 +16,23 @@ borsh = "=0.9.3"
clap = {version = "3.1.18", features = ["derive"]}
env_logger = "0.8.4"
log = "0.4.14"
wormhole-bridge-solana = {path = "../../bridge/program"}
wormhole-bridge-solana = {git = "https://github.com/certusone/wormhole", tag = "v2.8.9"}
pyth2wormhole = {path = "../program"}
p2w-sdk = { path = "../../../third_party/pyth/p2w-sdk/rust", features=["solana"] }
pyth-sdk-solana = "0.4.0"
serde = "1"
serde_yaml = "0.8"
shellexpand = "2.1.0"
solana-client = "=1.10.13"
solana-program = "=1.10.13"
solana-sdk = "=1.10.13"
solana-transaction-status = "=1.10.13"
solitaire-client = {path = "../../solitaire/client"}
solitaire = {path = "../../solitaire/program"}
solana-client = "=1.10.31"
solana-program = "=1.10.31"
solana-sdk = "=1.10.31"
solana-transaction-status = "=1.10.31"
# solitaire-client = {path = "../../solitaire/client"}
solitaire = {git = "https://github.com/certusone/wormhole", tag = "v2.8.9"}
tokio = {version = "1", features = ["sync", "rt", "time"]}
futures = "0.3.21"
[dev-dependencies]
pyth-client = "0.5.0"
solana-program-test = "=1.10.13"
solana-sdk = "=1.10.13"
solana-program-test = "=1.10.31"
solana-sdk = "=1.10.31"

View File

@ -20,18 +20,12 @@ use solana_program::{
rent,
},
};
use solana_sdk::transaction::Transaction;
use solana_sdk::{transaction::Transaction, signer::{Signer, keypair::Keypair}};
use solitaire::{
processors::seeded::Seeded,
AccountState,
ErrBox,
};
use solitaire_client::{
AccEntry,
Keypair,
SolSigner,
ToInstruction,
};
use bridge::{
accounts::{
@ -47,10 +41,7 @@ use p2w_sdk::P2WEmitter;
use pyth2wormhole::{
attest::P2W_MAX_BATCH_SIZE,
config::P2WConfigAccount,
initialize::InitializeAccounts,
migrate::MigrateAccounts,
set_config::SetConfigAccounts,
config::{OldP2WConfigAccount, P2WConfigAccount},
AttestData,
};
@ -76,23 +67,33 @@ pub fn gen_init_tx(
config: Pyth2WormholeConfig,
latest_blockhash: Hash,
) -> Result<Transaction, ErrBox> {
use AccEntry::*;
let payer_pubkey = payer.pubkey();
let accs = InitializeAccounts {
payer: Signer(payer),
new_config: Derived(p2w_addr),
};
let acc_metas = vec![
// new_config
AccountMeta::new(P2WConfigAccount::<{AccountState::Uninitialized}>::key(None, &p2w_addr), false),
// payer
AccountMeta::new(payer.pubkey(), true),
// system_program
AccountMeta::new(system_program::id(), false),
];
let ix_data = (pyth2wormhole::instruction::Instruction::Initialize, config);
let (ix, signers) = accs.to_ix(p2w_addr, ix_data.try_to_vec()?.as_slice())?;
let ix = Instruction::new_with_bytes(
p2w_addr,
ix_data
.try_to_vec()?
.as_slice(),
acc_metas,
);
let signers = vec![&payer];
let tx_signed = Transaction::new_signed_with_payer::<Vec<&Keypair>>(
&[ix],
Some(&payer_pubkey),
signers.iter().collect::<Vec<_>>().as_ref(),
&signers,
latest_blockhash,
);
Ok(tx_signed)
@ -105,27 +106,35 @@ pub fn gen_set_config_tx(
new_config: Pyth2WormholeConfig,
latest_blockhash: Hash,
) -> Result<Transaction, ErrBox> {
use AccEntry::*;
let payer_pubkey = payer.pubkey();
let accs = SetConfigAccounts {
payer: Signer(payer),
current_owner: Signer(owner),
config: Derived(p2w_addr),
};
let acc_metas = vec![
// config
AccountMeta::new(P2WConfigAccount::<{AccountState::Initialized}>::key(None, &p2w_addr), false),
// current_owner
AccountMeta::new(owner.pubkey(), true),
// payer
AccountMeta::new(payer.pubkey(), true),
];
let ix_data = (
pyth2wormhole::instruction::Instruction::SetConfig,
new_config,
);
let (ix, signers) = accs.to_ix(p2w_addr, ix_data.try_to_vec()?.as_slice())?;
let ix = Instruction::new_with_bytes(
p2w_addr,
ix_data
.try_to_vec()?
.as_slice(),
acc_metas,
);
let signers = vec![&owner, &payer];
let tx_signed = Transaction::new_signed_with_payer::<Vec<&Keypair>>(
&[ix],
Some(&payer_pubkey),
signers.iter().collect::<Vec<_>>().as_ref(),
&signers,
latest_blockhash,
);
Ok(tx_signed)
@ -137,28 +146,41 @@ pub fn gen_migrate_tx(
owner: Keypair,
latest_blockhash: Hash,
) -> Result<Transaction, ErrBox> {
use AccEntry::*;
let payer_pubkey = payer.pubkey();
let accs = MigrateAccounts {
new_config: Derived(p2w_addr),
old_config: Derived(p2w_addr),
current_owner: Signer(owner),
payer: Signer(payer),
};
let acc_metas = vec![
// new_config
AccountMeta::new(P2WConfigAccount::<{AccountState::Uninitialized}>::key(None, &p2w_addr), false),
// old_config
AccountMeta::new(OldP2WConfigAccount::key(None, &p2w_addr), false),
// owner
AccountMeta::new(owner.pubkey(), true),
// payer
AccountMeta::new(payer.pubkey(), true),
// system_program
AccountMeta::new(system_program::id(), false),
];
let ix_data = (
pyth2wormhole::instruction::Instruction::Migrate,
(),
);
let (ix, signers) = accs.to_ix(p2w_addr, ix_data.try_to_vec()?.as_slice())?;
let ix = Instruction::new_with_bytes(
p2w_addr,
ix_data
.try_to_vec()?
.as_slice(),
acc_metas,
);
let signers = vec![&owner, &payer];
let tx_signed = Transaction::new_signed_with_payer::<Vec<&Keypair>>(
&[ix],
Some(&payer_pubkey),
signers.iter().collect::<Vec<_>>().as_ref(),
&signers,
latest_blockhash,
);
Ok(tx_signed)
@ -279,8 +301,7 @@ pub fn gen_attest_tx(
let ix = Instruction::new_with_bytes(
p2w_addr,
ix_data
.try_to_vec()
.map_err(|e| -> ErrBoxSend { Box::new(e) })?
.try_to_vec()?
.as_slice(),
acc_metas,
);

View File

@ -37,13 +37,13 @@ use solana_sdk::{
read_keypair_file,
Signature,
},
signer::keypair::Keypair,
};
use solana_transaction_status::UiTransactionEncoding;
use solitaire::{
processors::seeded::Seeded,
ErrBox,
};
use solitaire_client::Keypair;
use tokio::{
sync::Semaphore,
task::JoinHandle,

View File

@ -89,6 +89,8 @@ async fn test_migrate_works() -> Result<(), solitaire::ErrBox> {
// Add system program because the contract creates an account for new configuration account
passthrough::add_passthrough(&mut p2w_test, "system", system_program::id());
info!("System program under {}", system_program::id());
info!("Before start_with_context");
let mut ctx = p2w_test.start_with_context().await;

View File

@ -10,20 +10,18 @@ name = "pyth2wormhole"
[features]
default = ["wormhole-bridge-solana/no-entrypoint"]
client = ["solitaire/client", "solitaire-client", "no-entrypoint"]
client = ["solitaire/client", "no-entrypoint"]
trace = ["solitaire/trace", "wormhole-bridge-solana/trace"]
no-entrypoint = []
[dependencies]
wormhole-bridge-solana = {path = "../../bridge/program"}
solitaire = { path = "../../solitaire/program" }
solitaire-client = { path = "../../solitaire/client", optional = true }
rocksalt = { path = "../../solitaire/rocksalt" }
solana-program = "=1.10.13"
wormhole-bridge-solana = { git = "https://github.com/certusone/wormhole", tag = "v2.8.9" }
solitaire = { git = "https://github.com/certusone/wormhole", tag = "v2.8.9"}
rocksalt = { git = "https://github.com/certusone/wormhole", tag = "v2.8.9"}
solana-program = "=1.10.31"
borsh = "=0.9.3"
pyth-client = "0.2.2"
p2w-sdk = { path = "../../../third_party/pyth/p2w-sdk/rust", features = ["solana"] }
serde = { version = "1", optional = true}
serde_derive = { version = "1", optional = true}
serde_json = { version = "1", optional = true}

View File

@ -22,6 +22,7 @@ use p2w_sdk::{
BatchPriceAttestation,
P2WEmitter,
PriceAttestation,
Identifier,
};
use bridge::{
@ -38,7 +39,6 @@ use solitaire::{
ExecutionContext,
FromAccounts,
Info,
InstructionContext,
Keyed,
Mut,
Peel,
@ -47,7 +47,6 @@ use solitaire::{
Signer,
SolitaireError,
Sysvar,
ToInstruction,
};
/// Important: must be manually maintained until native Solitaire
@ -59,7 +58,7 @@ use solitaire::{
/// correct value dynamically.
pub const P2W_MAX_BATCH_SIZE: u16 = 5;
#[derive(FromAccounts, ToInstruction)]
#[derive(FromAccounts)]
pub struct Attest<'b> {
// Payer also used for wormhole
pub payer: Mut<Signer<Info<'b>>>,
@ -133,12 +132,6 @@ pub struct AttestData {
pub consistency_level: ConsistencyLevel,
}
impl<'b> InstructionContext<'b> for Attest<'b> {
fn deps(&self) -> Vec<Pubkey> {
vec![solana_program::system_program::id()]
}
}
pub fn attest(ctx: &ExecutionContext, accs: &mut Attest, data: AttestData) -> SoliResult<()> {
if !accs.config.is_active {
// msg instead of trace makes sure we're not silent about this in prod
@ -211,7 +204,7 @@ pub fn attest(ctx: &ExecutionContext, accs: &mut Attest, data: AttestData) -> So
}
let attestation = PriceAttestation::from_pyth_price_bytes(
price.key.clone(),
Identifier::new(product.key.to_bytes()),
accs.clock.unix_timestamp,
&*price.try_borrow_data()?,
)
@ -228,7 +221,7 @@ pub fn attest(ctx: &ExecutionContext, accs: &mut Attest, data: AttestData) -> So
// Failing to verify the product/price relationship could lead
// to mismatched product/price metadata, which would result in
// a false attestation.
if &attestation.product_id != product.key {
if attestation.product_id.to_bytes() != product.key.to_bytes() {
trace!(&format!(
"Price's product_id does not match the pased account (points at {:?} instead)",
attestation.product_id

View File

@ -6,13 +6,11 @@ use solitaire::{
ExecutionContext,
FromAccounts,
Info,
InstructionContext,
Keyed,
Mut,
Peel,
Result as SoliResult,
Signer,
ToInstruction,
};
use crate::config::{
@ -20,16 +18,11 @@ use crate::config::{
Pyth2WormholeConfig,
};
#[derive(FromAccounts, ToInstruction)]
#[derive(FromAccounts)]
pub struct Initialize<'b> {
pub new_config: Mut<P2WConfigAccount<'b, { AccountState::Uninitialized }>>,
pub payer: Mut<Signer<Info<'b>>>,
}
impl<'b> InstructionContext<'b> for Initialize<'b> {
fn deps(&self) -> Vec<Pubkey> {
vec![]
}
pub system_program: Info<'b>,
}
/// Must be called right after deployment

View File

@ -29,8 +29,8 @@ pub use set_config::{
pub use pyth_client;
solitaire! {
Attest(AttestData) => attest,
Initialize(Pyth2WormholeConfig) => initialize,
SetConfig(Pyth2WormholeConfig) => set_config,
Migrate(()) => migrate,
Attest => attest,
Initialize => initialize,
SetConfig => set_config,
Migrate => migrate,
}

View File

@ -3,6 +3,7 @@
use solana_program::{
program_error::ProgramError,
pubkey::Pubkey,
system_program,
};
use solitaire::{
@ -12,14 +13,12 @@ use solitaire::{
ExecutionContext,
FromAccounts,
Info,
InstructionContext,
Keyed,
Mut,
Peel,
Result as SoliResult,
Signer,
SolitaireError,
ToInstruction,
};
use crate::config::{
@ -33,7 +32,7 @@ use crate::config::{
///
/// NOTE: This account struct assumes Solitaire is able to validate the
/// Uninitialized requirement on the new_config account
#[derive(FromAccounts, ToInstruction)]
#[derive(FromAccounts)]
pub struct Migrate<'b> {
/// New config account to be populated. Must be unused.
pub new_config: Mut<P2WConfigAccount<'b, { AccountState::Uninitialized }>>,
@ -43,12 +42,8 @@ pub struct Migrate<'b> {
pub current_owner: Mut<Signer<Info<'b>>>,
/// Payer account for updating the account data
pub payer: Mut<Signer<Info<'b>>>,
}
impl<'b> InstructionContext<'b> for Migrate<'b> {
fn deps(&self) -> Vec<Pubkey> {
vec![]
}
/// For creating the new config account
pub system_program: Info<'b>,
}
pub fn migrate(ctx: &ExecutionContext, accs: &mut Migrate, data: ()) -> SoliResult<()> {
@ -64,6 +59,18 @@ pub fn migrate(ctx: &ExecutionContext, accs: &mut Migrate, data: ()) -> SoliResu
));
}
if *accs.system_program.key != system_program::id() {
trace!(
"Invalid system program, expected {:?}), found {}",
system_program::id(),
accs.system_program.key
);
return Err(SolitaireError::InvalidSigner(
accs.system_program.key.clone(),
));
}
// Populate new config
accs.new_config
.create(ctx, accs.payer.info().key, CreationLamports::Exempt)?;

View File

@ -5,14 +5,12 @@ use solitaire::{
ExecutionContext,
FromAccounts,
Info,
InstructionContext,
Keyed,
Mut,
Peel,
Result as SoliResult,
Signer,
SolitaireError,
ToInstruction,
};
use crate::config::{
@ -20,7 +18,7 @@ use crate::config::{
Pyth2WormholeConfig,
};
#[derive(FromAccounts, ToInstruction)]
#[derive(FromAccounts)]
pub struct SetConfig<'b> {
/// Current config used by the program
pub config: Mut<P2WConfigAccount<'b, { AccountState::Initialized }>>,
@ -30,12 +28,6 @@ pub struct SetConfig<'b> {
pub payer: Mut<Signer<Info<'b>>>,
}
impl<'b> InstructionContext<'b> for SetConfig<'b> {
fn deps(&self) -> Vec<Pubkey> {
vec![]
}
}
/// Alters the current settings of pyth2wormhole
pub fn set_config(
_ctx: &ExecutionContext,

6
solana/rust-toolchain Normal file
View File

@ -0,0 +1,6 @@
# This version should be kept up to date with the value in
# ci/rust-version.sh in the branch of the solana repo that corresponds
# with the version we're using.
[toolchain]
channel = "nightly-2022-02-24"
profile = "minimal"

File diff suppressed because it is too large Load Diff

View File

@ -1,2 +0,0 @@
[workspace]
members = ["rocksalt", "program", "client"]

View File

@ -1,12 +0,0 @@
[package]
name = "solitaire-client"
version = "0.1.0"
edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
solana-sdk = "=1.10.13"
solana-program = "=1.10.13"
solitaire = {path = "../program", features = ["no-entrypoint"]}
borsh = "=0.9.3"

View File

@ -1,227 +0,0 @@
#![feature(adt_const_params)]
#![feature(const_generics_defaults)]
#![allow(warnings)]
//! Client-specific code
pub use solana_program::pubkey::Pubkey;
use solana_program::sysvar::Sysvar as SolSysvar;
pub use solana_sdk;
pub use solana_sdk::{
instruction::{
AccountMeta,
Instruction,
},
signature::{
Keypair,
Signer as SolSigner,
},
};
use borsh::BorshSerialize;
pub use solitaire::{
processors::seeded::Seeded,
Data,
Derive,
Keyed,
Owned,
Signer,
};
use solitaire::{
AccountState,
Info,
Mut,
Sysvar,
};
type StdResult<T, E> = std::result::Result<T, E>;
pub type ErrBox = Box<dyn std::error::Error>;
/// The sum type for clearly specifying the accounts required on client side.
#[derive(Debug)]
pub enum AccEntry {
/// Least privileged account.
Unprivileged(Pubkey),
/// Least privileged account, read-only.
UnprivilegedRO(Pubkey),
/// Accounts that need to sign a Solana call
Signer(Keypair),
/// Accounts that need to sign a Solana call, read-only.
SignerRO(Keypair),
/// Program addresses for unprivileged cross calls
CPIProgram(Pubkey),
/// Program addresses for privileged cross calls
CPIProgramSigner(Keypair),
/// Key decided from SPL constants
Sysvar(Pubkey),
/// Key derived from constants and/or program address
Derived(Pubkey),
/// Key derived from constants and/or program address, read-only.
DerivedRO(Pubkey),
/// Empty value for nullables
Empty,
}
/// Types implementing Wrap are those that can be turned into a
/// partial account vector for a program call.
pub trait Wrap {
fn wrap(_: &AccEntry) -> StdResult<Vec<AccountMeta>, ErrBox>;
/// If the implementor wants to sign using other AccEntry
/// variants, they should override this.
fn keypair(a: AccEntry) -> Option<Keypair> {
use AccEntry::*;
match a {
Signer(pair) => Some(pair),
SignerRO(pair) => Some(pair),
_other => None,
}
}
}
impl<T: Wrap> Wrap for Option<T> {
fn wrap(a: &AccEntry) -> StdResult<Vec<AccountMeta>, ErrBox> {
match a {
AccEntry::Empty => Ok(vec![AccountMeta::new_readonly(Pubkey::new_from_array([0u8; 32]), false)]),
other => T::wrap(other)
}
}
}
impl<'a, 'b: 'a, T> Wrap for Signer<T>
where
T: Keyed<'a, 'b>,
{
fn wrap(a: &AccEntry) -> StdResult<Vec<AccountMeta>, ErrBox> {
use AccEntry::*;
match a {
Signer(pair) => Ok(vec![AccountMeta::new(pair.pubkey(), true)]),
SignerRO(pair) => Ok(vec![AccountMeta::new_readonly(pair.pubkey(), true)]),
other => Err(format!(
"{} must be passed as Signer or SignerRO",
std::any::type_name::<Self>()
)
.into()),
}
}
}
impl<'a, 'b: 'a, T, const Seed: &'static str> Wrap for Derive<T, Seed> {
fn wrap(a: &AccEntry) -> StdResult<Vec<AccountMeta>, ErrBox> {
match a {
AccEntry::Derived(program_id) => {
let k = Self::key(None, program_id);
Ok(vec![AccountMeta::new(k, false)])
}
AccEntry::DerivedRO(program_id) => {
let k = Self::key(None, program_id);
Ok(vec![AccountMeta::new_readonly(k, false)])
}
other => Err(format!(
"{} must be passed as Derived or DerivedRO",
std::any::type_name::<Self>()
)
.into()),
}
}
}
impl<'a, T, const IsInitialized: AccountState> Wrap for Data<'a, T, IsInitialized>
where
T: BorshSerialize + Owned + Default,
{
fn wrap(a: &AccEntry) -> StdResult<Vec<AccountMeta>, ErrBox> {
use AccEntry::*;
use AccountState::*;
match IsInitialized {
Initialized => match a {
Unprivileged(k) => Ok(vec![AccountMeta::new(*k, false)]),
UnprivilegedRO(k) => Ok(vec![AccountMeta::new_readonly(*k, false)]),
Signer(pair) => Ok(vec![AccountMeta::new(pair.pubkey(), true)]),
SignerRO(pair) => Ok(vec![AccountMeta::new_readonly(pair.pubkey(), true)]),
_other => Err(format!("{} with IsInitialized = {:?} must be passed as Unprivileged, Signer or the respective read-only variant", std::any::type_name::<Self>(), a).into())
},
Uninitialized => match a {
Unprivileged(k) => Ok(vec![AccountMeta::new(*k, false)]),
Signer(pair) => Ok(vec![AccountMeta::new(pair.pubkey(), true)]),
_other => Err(format!("{} with IsInitialized = {:?} must be passed as Unprivileged or Signer (write access required for initialization)", std::any::type_name::<Self>(), a).into())
}
MaybeInitialized => match a {
Unprivileged(k) => Ok(vec![AccountMeta::new(*k, false)]),
Signer(pair) => Ok(vec![AccountMeta::new(pair.pubkey(), true)]),
_other => Err(format!("{} with IsInitialized = {:?} must be passed as Unprivileged or Signer (write access required in case of initialization)", std::any::type_name::<Self>(), a).into())
}
}
}
}
impl<'b, Var> Wrap for Sysvar<'b, Var>
where
Var: SolSysvar,
{
fn wrap(a: &AccEntry) -> StdResult<Vec<AccountMeta>, ErrBox> {
if let AccEntry::Sysvar(k) = a {
if Var::check_id(k) {
Ok(vec![AccountMeta::new_readonly(k.clone(), false)])
} else {
Err(format!(
"{} does not point at sysvar {}",
k,
std::any::type_name::<Var>()
)
.into())
}
} else {
Err(format!("{} must be passed as Sysvar", std::any::type_name::<Self>()).into())
}
}
}
impl<'b> Wrap for Info<'b> {
fn wrap(a: &AccEntry) -> StdResult<Vec<AccountMeta>, ErrBox> {
match a {
AccEntry::UnprivilegedRO(k) => Ok(vec![AccountMeta::new_readonly(k.clone(), false)]),
AccEntry::Unprivileged(k) => Ok(vec![AccountMeta::new(k.clone(), false)]),
_other => Err(format!(
"{} must be passed as Unprivileged or UnprivilegedRO",
std::any::type_name::<Self>()
)
.into()),
}
}
}
impl<T: Wrap> Wrap for Mut<T> {
fn wrap(a: &AccEntry) -> StdResult<Vec<AccountMeta>, ErrBox> {
match a {
AccEntry::Unprivileged(_) | AccEntry::Signer(_) | AccEntry::Derived(_) => {
Ok(T::wrap(a)?)
}
_other => Err(format!(
"{} must be passed as Unprivileged, Signer or Derived (Must be mutable on-chain)",
std::any::type_name::<Self>()
)
.into()),
}
}
}
/// Trait used on client side to easily validate a program accounts + ix_data for a bare Solana call
pub trait ToInstruction {
fn to_ix(
self,
program_id: Pubkey,
ix_data: &[u8],
) -> StdResult<(Instruction, Vec<Keypair>), ErrBox>;
}

View File

@ -1,24 +0,0 @@
[package]
name = "solitaire"
version = "0.1.0"
description = "Solana program framework"
edition = "2018"
[lib]
crate-type = ["cdylib", "lib"]
name = "solitaire"
[features]
client = ["no-entrypoint"]
cpi = ["no-entrypoint"]
default = []
no-entrypoint = []
trace = []
[dependencies]
borsh = "=0.9.3"
byteorder = "1.4.3"
rocksalt = { path = "../../solitaire/rocksalt" }
sha3 = "0.9.1"
solana-program = "=1.10.13"

View File

@ -1,2 +0,0 @@
[target.bpfel-unknown-unknown.dependencies.std]
features = []

View File

@ -1,74 +0,0 @@
use solana_program::{
program_error::ProgramError,
pubkey::Pubkey,
};
/// Quality of life Result type for the Solitaire stack.
pub type Result<T> = std::result::Result<T, SolitaireError>;
/// Quality of life type alias for wrapping up boxed errors.
pub type ErrBox = Box<dyn std::error::Error>;
/// There are several places in Solitaire that might fail, we want descriptive errors.
#[derive(Debug)]
pub enum SolitaireError {
/// The AccountInfo parser expected a mutable key where a readonly
/// was found, or vice versa. Second item is the found value.
InvalidMutability(Pubkey, bool),
/// The AccountInfo parser expected a Signer, but the account did not sign.
InvalidSigner(Pubkey),
/// The AccountInfo parser expected a Sysvar, but the key was invalid.
InvalidSysvar(Pubkey),
/// The AccountInfo parser tried to derive the provided key, but it did not match.
InvalidDerive(Pubkey, Pubkey),
/// The AccountInfo has an invalid owner.
InvalidOwner(Pubkey),
/// The AccountInfo is non-writeable where a writeable key was expected.
NonWriteableAccount(Pubkey),
/// The instruction payload itself could not be deserialized.
InstructionDeserializeFailed(std::io::Error),
/// An IO error was captured, wrap it up and forward it along.
IoError(std::io::Error),
/// An solana program error
ProgramError(ProgramError),
/// Owner of the account is ambiguous
AmbiguousOwner,
/// Account has already been initialized
AlreadyInitialized(Pubkey),
/// An instruction that wasn't recognised was sent.
UnknownInstruction(u8),
Custom(u64),
}
impl From<ProgramError> for SolitaireError {
fn from(e: ProgramError) -> Self {
SolitaireError::ProgramError(e)
}
}
impl From<std::io::Error> for SolitaireError {
fn from(e: std::io::Error) -> Self {
SolitaireError::IoError(e)
}
}
impl Into<ProgramError> for SolitaireError {
fn into(self) -> ProgramError {
match self {
SolitaireError::ProgramError(e) => return e,
_ => ProgramError::Custom(0),
}
}
}

View File

@ -1,127 +0,0 @@
#![feature(adt_const_params)]
#![allow(warnings)]
pub use rocksalt::*;
// Lacking:
//
// - Error is a lacking as its just a basic enum, maybe use errorcode.
// - Client generation incomplete.
// We need a few Solana things in scope in order to properly abstract Solana.
use solana_program::{
account_info::{
next_account_info,
AccountInfo,
},
entrypoint,
entrypoint::ProgramResult,
instruction::{
AccountMeta,
Instruction,
},
program::invoke_signed,
program_error::ProgramError,
program_pack::Pack,
pubkey::Pubkey,
rent::Rent,
system_instruction,
system_program,
sysvar::{
self,
SysvarId,
},
};
use std::{
io::{
ErrorKind,
Write,
},
marker::PhantomData,
ops::{
Deref,
DerefMut,
},
slice::Iter,
string::FromUtf8Error,
};
pub use borsh::{
BorshDeserialize,
BorshSerialize,
};
// Expose all submodules for consumption.
pub mod error;
pub mod macros;
pub mod processors;
pub mod types;
// We can also re-export a set of types at module scope, this defines the intended API we expect
// people to be able to use from top-level.
pub use crate::{
error::{
ErrBox,
Result,
SolitaireError,
},
macros::*,
processors::{
keyed::Keyed,
peel::Peel,
persist::Persist,
seeded::{
invoke_seeded,
AccountOwner,
AccountSize,
Creatable,
Owned,
Seeded,
},
},
types::*,
};
/// Library name and version to print in entrypoint. Must be evaluated in this crate in order to do the right thing
pub const PKG_NAME_VERSION: &'static str =
concat!(env!("CARGO_PKG_NAME"), " ", env!("CARGO_PKG_VERSION"));
pub struct ExecutionContext<'a, 'b: 'a> {
/// A reference to the program_id of the current program.
pub program_id: &'a Pubkey,
/// All accounts passed into the program
pub accounts: &'a [AccountInfo<'b>],
}
/// Lamports to pay to an account being created
pub enum CreationLamports {
Exempt,
Amount(u64),
}
impl CreationLamports {
/// Amount of lamports to be paid in account creation
pub fn amount(self, size: usize) -> u64 {
match self {
CreationLamports::Exempt => Rent::default().minimum_balance(size),
CreationLamports::Amount(v) => v,
}
}
}
pub trait InstructionContext<'a> {
fn deps(&self) -> Vec<Pubkey> {
vec![]
}
}
/// Trait definition that describes types that can be constructed from a list of solana account
/// references. A list of dependent accounts is produced as a side effect of the parsing stage.
pub trait FromAccounts<'a, 'b: 'a, 'c> {
fn from<T>(_: &'a Pubkey, _: &'c mut Iter<'a, AccountInfo<'b>>, _: &'a T) -> Result<Self>
where
Self: Sized;
}

View File

@ -1,165 +0,0 @@
use std::ops::{
Deref,
DerefMut,
};
/// A wrapper around Solana's `msg!` macro that is a no-op by default, allows for adding traces
/// through the application that can be toggled during tests.
#[macro_export]
macro_rules! trace {
( $($arg:tt)* ) => { $crate::trace_impl!( $($arg)* ) };
}
#[cfg(feature = "trace")]
#[macro_export]
macro_rules! trace_impl {
( $($arg:tt)* ) => { solana_program::msg!( $($arg)* ) };
}
#[cfg(not(feature = "trace"))]
#[macro_export]
macro_rules! trace_impl {
( $($arg:tt)* ) => {};
}
/// This is our main codegen macro. It takes as input a list of enum-like variants mapping field
/// types to function calls. The generated code produces:
///
/// - An `Instruction` enum with the enum variants passed in.
/// - A set of functions which take as arguments the enum fields.
/// - A Dispatcher that deserializes bytes into the enum and dispatches the function call.
/// - A set of client calls scoped to the module `api` that can generate instructions.
#[macro_export]
macro_rules! solitaire {
{ $($row:ident($kind:ty) => $fn:ident),+ $(,)* } => {
pub mod instruction {
use super::*;
use borsh::{
BorshDeserialize,
BorshSerialize,
};
use solana_program::{
account_info::AccountInfo,
entrypoint::ProgramResult,
program_error::ProgramError,
pubkey::Pubkey,
};
use solitaire::{
trace,
ExecutionContext,
FromAccounts,
Persist,
Result,
SolitaireError,
};
$(
// Generated module wrapping instruction handler.
//
// These are needed to force the compiler to generate a new function that has not
// been inlined, this provides a new stack frame. Without this, the stack frame for
// deserialization and the handler is the same as that used by solitaire, leading
// to bust stacks.
#[allow(non_snake_case)]
pub mod $row {
use super::*;
#[inline(never)]
pub fn execute<'a, 'b: 'a, 'c>(p: &Pubkey, a: &'c [AccountInfo<'b>], d: &[u8]) -> Result<()> {
let ix_data: $kind = BorshDeserialize::try_from_slice(d).map_err(|e| SolitaireError::InstructionDeserializeFailed(e))?;
let mut accounts = FromAccounts::from(p, &mut a.iter(), &())?;
$fn(&ExecutionContext{program_id: p, accounts: a}, &mut accounts, ix_data)?;
Persist::persist(&accounts, p)?;
Ok(())
}
}
)*
/// Generated:
/// This Instruction contains a 1-1 mapping for each enum variant to function call. The
/// function calls can be found below in the `api` module.
#[repr(u8)]
#[derive(BorshSerialize, BorshDeserialize)]
pub enum Instruction {
$($row,)*
}
/// This entrypoint is generated from the enum above, it deserializes incoming bytes
/// and automatically dispatches to the correct method.
pub fn dispatch<'a, 'b: 'a, 'c>(p: &Pubkey, a: &'c [AccountInfo<'b>], d: &[u8]) -> Result<()> {
match d[0] {
$(
n if n == Instruction::$row as u8 => $row::execute(p, a, &d[1..]),
)*
other => {
Err(SolitaireError::UnknownInstruction(other))
}
}
}
pub fn solitaire(p: &Pubkey, a: &[AccountInfo], d: &[u8]) -> ProgramResult {
trace!("{} {} built with {}", env!("CARGO_PKG_NAME"), env!("CARGO_PKG_VERSION"), solitaire::PKG_NAME_VERSION);
if let Err(err) = dispatch(p, a, d) {
solana_program::msg!("Error: {:?}", err);
return Err(err.into());
}
Ok(())
}
}
pub use instruction::solitaire;
#[cfg(not(feature = "no-entrypoint"))]
solana_program::entrypoint!(solitaire);
}
}
#[macro_export]
macro_rules! pack_type {
($name:ident, $embed:ty, $owner:expr) => {
#[repr(transparent)]
pub struct $name(pub $embed);
impl BorshDeserialize for $name {
fn deserialize(buf: &mut &[u8]) -> std::io::Result<Self> {
let acc = $name(
solana_program::program_pack::Pack::unpack(buf)
.map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e))?,
);
// We need to clear the buf to show to Borsh that we've read all data
*buf = &buf[..0];
Ok(acc)
}
}
impl BorshSerialize for $name {
fn serialize<W: std::io::Write>(&self, writer: &mut W) -> std::io::Result<()> {
let mut data = [0u8; <$embed as solana_program::program_pack::Pack>::LEN];
solana_program::program_pack::Pack::pack_into_slice(&self.0, &mut data);
writer.write(&data)?;
Ok(())
}
}
impl solitaire::processors::seeded::Owned for $name {
fn owner(&self) -> solitaire::processors::seeded::AccountOwner {
return $owner;
}
}
impl std::ops::Deref for $name {
type Target = $embed;
fn deref(&self) -> &Self::Target {
unsafe { std::mem::transmute(&self.0) }
}
}
impl std::default::Default for $name {
fn default() -> Self {
$name(<$embed>::default())
}
}
};
}

View File

@ -1,4 +0,0 @@
pub mod keyed;
pub mod peel;
pub mod persist;
pub mod seeded;

View File

@ -1,76 +0,0 @@
use solana_program::{
pubkey::Pubkey,
sysvar::Sysvar as SolanaSysvar,
};
use crate::{
processors::seeded::Owned,
AccountState,
Data,
Derive,
Info,
Mut,
Signer,
System,
Sysvar,
};
pub trait Keyed<'a, 'b: 'a> {
fn info(&'a self) -> &Info<'b>;
}
impl<'a, 'b: 'a, T: Owned + Default, const IsInitialized: AccountState> Keyed<'a, 'b>
for Data<'b, T, IsInitialized>
{
fn info(&'a self) -> &'a Info<'b> {
&self.0
}
}
impl<'a, 'b: 'a, T> Keyed<'a, 'b> for Signer<T>
where
T: Keyed<'a, 'b>,
{
fn info(&'a self) -> &'a Info<'b> {
self.0.info()
}
}
impl<'a, 'b: 'a, Var: SolanaSysvar> Keyed<'a, 'b> for Sysvar<'b, Var> {
fn info(&'a self) -> &'a Info<'b> {
&self.0
}
}
impl<'a, 'b: 'a, T> Keyed<'a, 'b> for System<T>
where
T: Keyed<'a, 'b>,
{
fn info(&'a self) -> &'a Info<'b> {
self.0.info()
}
}
impl<'a, 'b: 'a, T, const Seed: &'static str> Keyed<'a, 'b> for Derive<T, Seed>
where
T: Keyed<'a, 'b>,
{
fn info(&'a self) -> &'a Info<'b> {
self.0.info()
}
}
impl<'a, 'b: 'a, T> Keyed<'a, 'b> for Mut<T>
where
T: Keyed<'a, 'b>,
{
fn info(&'a self) -> &'a Info<'b> {
self.0.info()
}
}
impl<'a, 'b: 'a> Keyed<'a, 'b> for Info<'b> {
fn info(&'a self) -> &'a Info<'b> {
self
}
}

View File

@ -1,288 +0,0 @@
//! Peeling.
//!
//! The accounts in Solitaire programs are defined via layers of types, when each layer is peeled
//! off it performs checks, parsing, and any other desired side-effect. The mechanism for this is
//! the peel trait, which defines a set of types that recursively construct the desired type.
use borsh::BorshDeserialize;
use solana_program::{
pubkey::Pubkey,
system_program,
sysvar::{
self,
Sysvar as SolanaSysvar,
SysvarId,
},
};
use std::marker::PhantomData;
use crate::{
trace,
processors::seeded::{
AccountOwner,
Owned,
},
types::*,
AccountState::MaybeInitialized,
Context,
Result,
SolitaireError,
};
use borsh::BorshSerialize;
/// Generic Peel trait. This provides a way to describe what each "peeled"
/// layer of our constraints should check.
pub trait Peel<'a, 'b: 'a, 'c> {
fn peel<I>(ctx: &'c mut Context<'a, 'b, 'c, I>) -> Result<Self>
where
Self: Sized;
fn deps() -> Vec<Pubkey>;
fn persist(&self, program_id: &Pubkey) -> Result<()>;
}
/// Peel a nullable value (0-account means None)
impl<'a, 'b: 'a, 'c, T: Peel<'a, 'b, 'c>> Peel<'a, 'b, 'c> for Option<T> {
fn peel<I>(ctx: &'c mut Context<'a, 'b, 'c, I>) -> Result<Self> {
// Check for 0-account
if ctx.info().key == &Pubkey::new_from_array([0u8; 32]) {
trace!(&format!("Peeled {} is None, returning", std::any::type_name::<Option<T>>()));
Ok(None)
} else {
Ok(Some(T::peel(ctx)?))
}
}
fn deps() -> Vec<Pubkey> {
T::deps()
}
fn persist(&self, program_id: &Pubkey) -> Result<()> {
if let Some(s) = self.as_ref() {
T::persist(s, program_id)
} else {
trace!(&format!("Peeled {} is None, not persisting", std::any::type_name::<Option<T>>()));
Ok(())
}
}
}
/// Peel a Derived Key
impl<'a, 'b: 'a, 'c, T: Peel<'a, 'b, 'c>, const Seed: &'static str> Peel<'a, 'b, 'c>
for Derive<T, Seed>
{
fn peel<I>(ctx: &'c mut Context<'a, 'b, 'c, I>) -> Result<Self> {
// Attempt to Derive Seed
let (derived, bump) = Pubkey::find_program_address(&[Seed.as_ref()], ctx.this);
match derived == *ctx.info().key {
true => T::peel(ctx).map(|v| Derive(v)),
_ => Err(SolitaireError::InvalidDerive(*ctx.info().key, derived).into()),
}
}
fn deps() -> Vec<Pubkey> {
T::deps()
}
fn persist(&self, program_id: &Pubkey) -> Result<()> {
T::persist(self, program_id)
}
}
/// Peel a Mutable key.
impl<'a, 'b: 'a, 'c, T: Peel<'a, 'b, 'c>> Peel<'a, 'b, 'c> for Mut<T> {
fn peel<I>(mut ctx: &'c mut Context<'a, 'b, 'c, I>) -> Result<Self> {
ctx.immutable = false;
match ctx.info().is_writable {
true => T::peel(ctx).map(|v| Mut(v)),
_ => Err(
SolitaireError::InvalidMutability(*ctx.info().key, ctx.info().is_writable).into(),
),
}
}
fn deps() -> Vec<Pubkey> {
T::deps()
}
fn persist(&self, program_id: &Pubkey) -> Result<()> {
T::persist(self, program_id)
}
}
impl<'a, 'b: 'a, 'c, T: Peel<'a, 'b, 'c>> Peel<'a, 'b, 'c> for MaybeMut<T> {
fn peel<I>(mut ctx: &'c mut Context<'a, 'b, 'c, I>) -> Result<Self> {
ctx.immutable = !ctx.info().is_writable;
T::peel(ctx).map(|v| MaybeMut(v))
}
fn deps() -> Vec<Pubkey> {
T::deps()
}
fn persist(&self, program_id: &Pubkey) -> Result<()> {
T::persist(self, program_id)
}
}
/// Peel a Signer.
impl<'a, 'b: 'a, 'c, T: Peel<'a, 'b, 'c>> Peel<'a, 'b, 'c> for Signer<T> {
fn peel<I>(ctx: &'c mut Context<'a, 'b, 'c, I>) -> Result<Self> {
match ctx.info().is_signer {
true => T::peel(ctx).map(|v| Signer(v)),
_ => Err(SolitaireError::InvalidSigner(*ctx.info().key).into()),
}
}
fn deps() -> Vec<Pubkey> {
T::deps()
}
fn persist(&self, program_id: &Pubkey) -> Result<()> {
T::persist(self, program_id)
}
}
/// Expicitly depend upon the System account.
impl<'a, 'b: 'a, 'c, T: Peel<'a, 'b, 'c>> Peel<'a, 'b, 'c> for System<T> {
fn peel<I>(ctx: &'c mut Context<'a, 'b, 'c, I>) -> Result<Self> {
match true {
true => T::peel(ctx).map(|v| System(v)),
_ => panic!(),
}
}
fn deps() -> Vec<Pubkey> {
T::deps()
}
fn persist(&self, program_id: &Pubkey) -> Result<()> {
T::persist(self, program_id)
}
}
/// Peel a Sysvar
impl<'a, 'b: 'a, 'c, Var> Peel<'a, 'b, 'c> for Sysvar<'b, Var>
where
Var: SolanaSysvar,
{
fn peel<I>(ctx: &'c mut Context<'a, 'b, 'c, I>) -> Result<Self> {
match Var::check_id(ctx.info().key) {
true => Ok(Sysvar(
ctx.info().clone(),
Var::from_account_info(ctx.info())?,
)),
_ => Err(SolitaireError::InvalidSysvar(*ctx.info().key).into()),
}
}
fn deps() -> Vec<Pubkey> {
vec![]
}
fn persist(&self, _program_id: &Pubkey) -> Result<()> {
Ok(())
}
}
/// This is our structural recursion base case, the trait system will stop generating new nested
/// calls here.
impl<'a, 'b: 'a, 'c> Peel<'a, 'b, 'c> for Info<'b> {
fn peel<I>(ctx: &'c mut Context<'a, 'b, 'c, I>) -> Result<Self> {
if ctx.immutable && ctx.info().is_writable {
return Err(
SolitaireError::InvalidMutability(*ctx.info().key, ctx.info().is_writable).into(),
);
}
Ok(ctx.info().clone())
}
fn deps() -> Vec<Pubkey> {
vec![]
}
fn persist(&self, _program_id: &Pubkey) -> Result<()> {
Ok(())
}
}
/// This is our structural recursion base case, the trait system will stop generating new nested
/// calls here.
impl<
'a,
'b: 'a,
'c,
T: BorshDeserialize + BorshSerialize + Owned + Default,
const IsInitialized: AccountState,
> Peel<'a, 'b, 'c> for Data<'b, T, IsInitialized>
{
fn peel<I>(ctx: &'c mut Context<'a, 'b, 'c, I>) -> Result<Self> {
if ctx.immutable && ctx.info().is_writable {
return Err(
SolitaireError::InvalidMutability(*ctx.info().key, ctx.info().is_writable).into(),
);
}
// If we're initializing the type, we should emit system/rent as deps.
let (initialized, data): (bool, T) = match IsInitialized {
AccountState::Uninitialized => {
if **ctx.info().lamports.borrow() != 0 {
return Err(SolitaireError::AlreadyInitialized(*ctx.info().key));
}
(false, T::default())
}
AccountState::Initialized => {
(true, T::try_from_slice(&mut *ctx.info().data.borrow_mut())?)
}
AccountState::MaybeInitialized => {
if **ctx.info().lamports.borrow() == 0 {
(false, T::default())
} else {
(true, T::try_from_slice(&mut *ctx.info().data.borrow_mut())?)
}
}
};
if initialized {
match data.owner() {
AccountOwner::This => {
if ctx.info().owner != ctx.this {
return Err(SolitaireError::InvalidOwner(*ctx.info().owner));
}
}
AccountOwner::Other(v) => {
if *ctx.info().owner != v {
return Err(SolitaireError::InvalidOwner(*ctx.info().owner));
}
}
AccountOwner::Any => {}
};
}
Ok(Data(Box::new(ctx.info().clone()), data))
}
fn deps() -> Vec<Pubkey> {
if IsInitialized == AccountState::Initialized {
return vec![];
}
vec![sysvar::rent::ID, system_program::ID]
}
fn persist(&self, program_id: &Pubkey) -> Result<()> {
// TODO: Introduce Mut<> to solve the check we really want to make here.
if self.0.owner != program_id {
return Ok(());
}
// It is also a malformed program to attempt to write to a non-writeable account.
if !self.0.is_writable {
return Ok(());
}
self.1.serialize(&mut *self.0.data.borrow_mut())?;
Ok(())
}
}

View File

@ -1,5 +0,0 @@
use solana_program::pubkey::Pubkey;
pub trait Persist {
fn persist(&self, program_id: &Pubkey) -> crate::Result<()>;
}

View File

@ -1,169 +0,0 @@
use super::keyed::Keyed;
use crate::{
system_instruction,
AccountInfo,
AccountState,
CreationLamports,
Data,
Deref,
Derive,
ExecutionContext,
FromAccounts,
Info,
Peel,
Result,
Signer,
SolitaireError,
System,
Sysvar,
};
use borsh::{
BorshSchema,
BorshSerialize,
};
use solana_program::{
entrypoint::ProgramResult,
instruction::Instruction,
msg,
program::invoke_signed,
pubkey::Pubkey,
};
pub trait AccountSize {
fn size(&self) -> usize;
}
pub enum AccountOwner {
This,
Other(Pubkey),
Any,
}
pub trait Owned {
fn owner(&self) -> AccountOwner;
fn owner_pubkey(&self, program_id: &Pubkey) -> Result<Pubkey> {
match self.owner() {
AccountOwner::This => Ok(*program_id),
AccountOwner::Other(v) => Ok(v),
AccountOwner::Any => Err(SolitaireError::AmbiguousOwner),
}
}
}
impl<'a, T: Owned + Default, const IsInitialized: AccountState> Owned
for Data<'a, T, IsInitialized>
{
fn owner(&self) -> AccountOwner {
self.1.owner()
}
}
pub trait Seeded<I> {
fn seeds(accs: I) -> Vec<Vec<u8>>;
fn self_seeds(&self, accs: I) -> Vec<Vec<u8>> {
Self::seeds(accs)
}
fn key(accs: I, program_id: &Pubkey) -> Pubkey {
let mut seeds = Self::seeds(accs);
let mut s: Vec<&[u8]> = seeds.iter().map(|item| item.as_slice()).collect();
let mut seed_slice = s.as_slice();
let (addr, _) = Pubkey::find_program_address(seed_slice, program_id);
addr
}
fn bumped_seeds(accs: I, program_id: &Pubkey) -> Vec<Vec<u8>> {
let mut seeds = Self::seeds(accs);
let mut s: Vec<&[u8]> = seeds.iter().map(|item| item.as_slice()).collect();
let mut seed_slice = s.as_slice();
let (_, bump_seed) = Pubkey::find_program_address(seed_slice, program_id);
seeds.push(vec![bump_seed]);
seeds
}
fn self_bumped_seeds(&self, accs: I, program_id: &Pubkey) -> Vec<Vec<u8>> {
Self::bumped_seeds(accs, program_id)
}
fn verify_derivation<'a, 'b: 'a>(&'a self, program_id: &'a Pubkey, accs: I) -> Result<()>
where
Self: Keyed<'a, 'b>,
{
let seeds = Self::seeds(accs);
let s: Vec<&[u8]> = seeds.iter().map(|item| item.as_slice()).collect();
let seed_slice = s.as_slice();
let (derived, bump) = Pubkey::find_program_address(seed_slice, program_id);
if &derived == self.info().key {
Ok(())
} else {
Err(SolitaireError::InvalidDerive(*self.info().key, derived))
}
}
}
pub trait Creatable<'a, I> {
fn create(
&'a self,
accs: I,
ctx: &'a ExecutionContext,
payer: &'a Pubkey,
lamports: CreationLamports,
) -> Result<()>;
}
impl<T: BorshSerialize + Owned + Default, const IsInitialized: AccountState> AccountSize
for Data<'_, T, IsInitialized>
{
fn size(&self) -> usize {
self.1.try_to_vec().unwrap().len()
}
}
impl<'a, 'b: 'a, K, T: AccountSize + Seeded<K> + Keyed<'a, 'b> + Owned> Creatable<'a, K> for T {
fn create(
&'a self,
accs: K,
ctx: &'a ExecutionContext<'_, '_>,
payer: &'a Pubkey,
lamports: CreationLamports,
) -> Result<()> {
let seeds = T::bumped_seeds(accs, ctx.program_id);
let size = self.size();
let mut s: Vec<&[u8]> = seeds.iter().map(|item| item.as_slice()).collect();
let mut seed_slice = s.as_slice();
let ix = system_instruction::create_account(
payer,
self.info().key,
lamports.amount(size),
size as u64,
&self.owner_pubkey(ctx.program_id)?,
);
Ok(invoke_signed(&ix, ctx.accounts, &[seed_slice])?)
}
}
impl<'a, const Seed: &'static str, T> Seeded<Option<()>> for Derive<T, Seed> {
fn seeds(accs: Option<()>) -> Vec<Vec<u8>> {
vec![Seed.as_bytes().to_vec()]
}
}
pub fn invoke_seeded<I, T: Seeded<I>>(
instruction: &Instruction,
context: &ExecutionContext,
seeded_acc: &T,
accs: I,
) -> ProgramResult {
let seeds = seeded_acc.self_bumped_seeds(accs, context.program_id);
let s: Vec<&[u8]> = seeds.iter().map(|item| item.as_slice()).collect();
let seed_slice = s.as_slice();
invoke_signed(instruction, context.accounts, &[seed_slice])
}

View File

@ -1,7 +0,0 @@
mod accounts;
mod context;
mod layers;
pub use accounts::*;
pub use context::*;
pub use layers::*;

View File

@ -1,128 +0,0 @@
//! Accounts.
//!
//! Solana provides a single primitive `AccountInfo` that represents an account on Solana. It
//! provides no information about what the account means however. This file provides a set of
//! types that describe different kinds of accounts to target.
use borsh::BorshSerialize;
use solana_program::{
account_info::AccountInfo,
program::invoke_signed,
pubkey::Pubkey,
system_instruction,
sysvar::Sysvar as SolanaSysvar,
};
use std::ops::{
Deref,
DerefMut,
};
use crate::{
processors::seeded::Owned,
CreationLamports,
Derive,
ExecutionContext,
Result,
};
/// A short alias for AccountInfo.
pub type Info<'r> = AccountInfo<'r>;
#[derive(Debug, Eq, PartialEq)]
pub enum AccountState {
Initialized,
Uninitialized,
MaybeInitialized,
}
/// An account that is known to contain serialized data.
///
/// Note on const generics:
///
/// Solana's Rust version is JUST old enough that it cannot use constant variables in its default
/// parameter assignments. But these DO work in the consumption side so a user can still happily
/// use this type by writing for example:
///
/// Data<(), { AccountState::Uninitialized }>
#[rustfmt::skip]
pub struct Data<'r, T: Owned + Default, const IsInitialized: AccountState> (
pub Box<Info<'r>>,
pub T,
);
impl<'r, T: Owned + Default, const IsInitialized: AccountState> Deref
for Data<'r, T, IsInitialized>
{
type Target = T;
fn deref(&self) -> &Self::Target {
&self.1
}
}
impl<'r, T: Owned + Default, const IsInitialized: AccountState> DerefMut
for Data<'r, T, IsInitialized>
{
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.1
}
}
impl<'r, T: Owned + Default, const IsInitialized: AccountState> Data<'r, T, IsInitialized> {
/// Is the account already initialized / created
pub fn is_initialized(&self) -> bool {
**self.0.lamports.borrow() != 0
}
}
pub struct Sysvar<'b, Var: SolanaSysvar>(pub AccountInfo<'b>, pub Var);
impl<'b, Var: SolanaSysvar> Deref for Sysvar<'b, Var> {
type Target = Var;
fn deref(&self) -> &Self::Target {
&self.1
}
}
impl<const Seed: &'static str> Derive<AccountInfo<'_>, Seed> {
pub fn create(
&self,
ctx: &ExecutionContext,
payer: &Pubkey,
lamports: CreationLamports,
space: usize,
owner: &Pubkey,
) -> Result<()> {
let ix = system_instruction::create_account(
payer,
self.0.key,
lamports.amount(space),
space as u64,
owner,
);
let (_, bump_seed) = Pubkey::find_program_address(&[Seed.as_bytes()][..], ctx.program_id);
invoke_signed(&ix, ctx.accounts, &[&[Seed.as_bytes(), &[bump_seed]]]).map_err(|e| e.into())
}
}
impl<const Seed: &'static str, T: BorshSerialize + Owned + Default>
Derive<Data<'_, T, { AccountState::Uninitialized }>, Seed>
{
pub fn create(
&self,
ctx: &ExecutionContext,
payer: &Pubkey,
lamports: CreationLamports,
) -> Result<()> {
// Get serialized struct size
let size = self.0.try_to_vec().unwrap().len();
let ix = system_instruction::create_account(
payer,
self.0 .0.key,
lamports.amount(size),
size as u64,
ctx.program_id,
);
let (_, bump_seed) = Pubkey::find_program_address(&[Seed.as_bytes()][..], ctx.program_id);
invoke_signed(&ix, ctx.accounts, &[&[Seed.as_bytes(), &[bump_seed]]]).map_err(|e| e.into())
}
}

View File

@ -1,54 +0,0 @@
use crate::trace;
use solana_program::{
account_info::{
next_account_info,
AccountInfo,
},
pubkey::Pubkey,
};
use std::slice::Iter;
/// The context is threaded through each check. Include anything within this structure that you
/// would like to have access to as each layer of dependency is peeled off.
pub struct Context<'a, 'b: 'a, 'c, T> {
/// A reference to the program_id of the current program.
pub this: &'a Pubkey,
/// A reference to the instructions account list, one or more keys may be extracted during
/// the peeling process.
pub iter: &'c mut Iter<'a, AccountInfo<'b>>,
/// Reference to the data passed to the current instruction.
pub data: &'a T,
/// An optional account info for this Peelable item, some fields may be other structures that
/// do not themselves have an account info associated with the field.
pub info: Option<&'a AccountInfo<'b>>,
/// Whether to enforce immutability.
pub immutable: bool,
}
impl<'a, 'b: 'a, 'c, T> Context<'a, 'b, 'c, T> {
pub fn new(program: &'a Pubkey, iter: &'c mut Iter<'a, AccountInfo<'b>>, data: &'a T) -> Self {
Context {
this: program,
info: None,
immutable: true,
iter,
data,
}
}
pub fn info<'d>(&'d mut self) -> &'a AccountInfo<'b> {
match self.info {
None => {
let info = next_account_info(self.iter).unwrap();
trace!("{}", info.key);
self.info = Some(info);
info
}
Some(v) => v,
}
}
}

View File

@ -1,107 +0,0 @@
//! This file contains several single-field wrapper structs. Each one represents a layer that must
//! be checked in order to parse a Solana account.
//!
//! These structs are always single field (or single + PhantomData) and so can be represented with
//! the transparent repr layout. When each layer is removed the data can be transmuted safely to
//! the layer below, allowing for optimized recursion.
use std::{
io::{
ErrorKind,
Write,
},
marker::PhantomData,
ops::{
Deref,
DerefMut,
},
};
use crate::Info;
use borsh::{
BorshDeserialize,
BorshSerialize,
};
#[repr(transparent)]
pub struct Mut<Next>(pub Next);
#[repr(transparent)]
pub struct MaybeMut<Next>(pub Next);
#[repr(transparent)]
pub struct Signer<Next>(pub Next);
#[repr(transparent)]
pub struct System<Next>(pub Next);
#[repr(transparent)]
pub struct Derive<Next, const Seed: &'static str>(pub Next);
// Several traits are required for types defined here, they cannot be defined in another file due
// to orphan instance limitations.
impl<T> Deref for Signer<T> {
type Target = T;
fn deref(&self) -> &Self::Target {
unsafe { std::mem::transmute(&self.0) }
}
}
impl<T> DerefMut for Signer<T> {
fn deref_mut(&mut self) -> &mut Self::Target {
unsafe { std::mem::transmute(&mut self.0) }
}
}
impl<T> Deref for Mut<T> {
type Target = T;
fn deref(&self) -> &Self::Target {
unsafe { std::mem::transmute(&self.0) }
}
}
impl<T> DerefMut for Mut<T> {
fn deref_mut(&mut self) -> &mut Self::Target {
unsafe { std::mem::transmute(&mut self.0) }
}
}
impl<T> Deref for MaybeMut<T> {
type Target = T;
fn deref(&self) -> &Self::Target {
unsafe { std::mem::transmute(&self.0) }
}
}
impl<T> DerefMut for MaybeMut<T> {
fn deref_mut(&mut self) -> &mut Self::Target {
unsafe { std::mem::transmute(&mut self.0) }
}
}
impl<T> Deref for System<T> {
type Target = T;
fn deref(&self) -> &Self::Target {
unsafe { std::mem::transmute(&self.0) }
}
}
impl<T> DerefMut for System<T> {
fn deref_mut(&mut self) -> &mut Self::Target {
unsafe { std::mem::transmute(&mut self.0) }
}
}
impl<T, const Seed: &'static str> Deref for Derive<T, Seed> {
type Target = T;
fn deref(&self) -> &Self::Target {
unsafe { std::mem::transmute(&self.0) }
}
}
impl<T, const Seed: &'static str> DerefMut for Derive<T, Seed> {
fn deref_mut(&mut self) -> &mut Self::Target {
unsafe { std::mem::transmute(&mut self.0) }
}
}

View File

@ -1,21 +0,0 @@
[package]
name = "rocksalt"
version = "0.1.0"
description = "Created with Rocksalt"
edition = "2018"
[lib]
proc-macro = true
name = "rocksalt"
[features]
no-entrypoint = []
default = []
[dependencies]
byteorder = "1.4.3"
proc-macro2 = "1.0"
quote = "1.0"
sha3 = "0.9.1"
solana-program = "=1.10.13"
syn = "1.0"

View File

@ -1,2 +0,0 @@
[target.bpfel-unknown-unknown.dependencies.std]
features = []

View File

@ -1,286 +0,0 @@
#![allow(warnings)]
mod to_instruction;
use to_instruction::*;
use solana_program::{
account_info::AccountInfo,
entrypoint,
entrypoint::ProgramResult,
pubkey::Pubkey,
};
use proc_macro::TokenStream;
use proc_macro2::{
Span,
TokenStream as TokenStream2,
};
use quote::{
quote,
quote_spanned,
ToTokens,
};
use std::borrow::BorrowMut;
use syn::{
parse_macro_input,
parse_quote,
spanned::Spanned,
Data,
DeriveInput,
Fields,
GenericParam,
Generics,
Index,
};
#[proc_macro_derive(ToInstruction)]
pub fn derive_to_instruction(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as DeriveInput);
let name = input.ident;
// Type params of the instruction context account
let type_params: Vec<GenericParam> = input
.generics
.type_params()
.map(|v| GenericParam::Type(v.clone()))
.collect();
// Generics lifetimes of the peel type
let mut peel_g = input.generics.clone();
peel_g.params = parse_quote!('a, 'b: 'a, 'c);
let (_, peel_type_g, _) = peel_g.split_for_impl();
// Params of the instruction context
let mut type_generics = input.generics.clone();
type_generics.params = parse_quote!('b);
for x in &type_params {
type_generics.params.push(x.clone());
}
let (type_impl_g, type_g, _) = type_generics.split_for_impl();
// Combined lifetimes of peel and the instruction context
let mut combined_generics = Generics::default();
combined_generics.params = peel_g.params.clone();
for x in &type_params {
combined_generics.params.push(x.clone());
}
let (combined_impl_g, _, _) = combined_generics.split_for_impl();
let expanded = generate_to_instruction(&name, &combined_impl_g, &input.data);
TokenStream::from(expanded)
}
/// Generate a FromAccounts implementation for a product of accounts. Each field is constructed by
/// a call to the Verify::verify instance of its type.
#[proc_macro_derive(FromAccounts)]
pub fn derive_from_accounts(input: TokenStream) -> TokenStream {
let mut input = parse_macro_input!(input as DeriveInput);
let name = input.ident;
// Type params of the instruction context account
let type_params: Vec<GenericParam> = input
.generics
.type_params()
.map(|v| GenericParam::Type(v.clone()))
.collect();
// Generics lifetimes of the peel type
let mut peel_g = input.generics.clone();
peel_g.params = parse_quote!('a, 'b: 'a, 'c);
let (_, peel_type_g, _) = peel_g.split_for_impl();
// Params of the instruction context
let mut type_generics = input.generics.clone();
type_generics.params = parse_quote!('b);
for x in &type_params {
type_generics.params.push(x.clone());
}
let (type_impl_g, type_g, _) = type_generics.split_for_impl();
// Combined lifetimes of peel and the instruction context
let mut combined_generics = Generics::default();
combined_generics.params = peel_g.params.clone();
for x in &type_params {
combined_generics.params.push(x.clone());
}
let (combined_impl_g, _, _) = combined_generics.split_for_impl();
let from_method = generate_fields(&name, &input.data);
let persist_method = generate_persist(&name, &input.data);
let deps_method = generate_deps_fields(&name, &input.data);
let expanded = quote! {
/// Macro generated implementation of FromAccounts by Solitaire.
impl #combined_impl_g solitaire::FromAccounts #peel_type_g for #name #type_g {
fn from<DataType>(pid: &'a solana_program::pubkey::Pubkey, iter: &'c mut std::slice::Iter<'a, solana_program::account_info::AccountInfo<'b>>, data: &'a DataType) -> solitaire::Result<Self> {
#from_method
}
}
impl #combined_impl_g solitaire::Peel<'a, 'b, 'c> for #name #type_g {
fn peel<I>(ctx: &'c mut solitaire::Context<'a, 'b, 'c, I>) -> solitaire::Result<Self> where Self: Sized {
let v: #name #type_g = FromAccounts::from(ctx.this, ctx.iter, ctx.data)?;
Ok(v)
}
fn deps() -> Vec<solana_program::pubkey::Pubkey> {
#deps_method
}
fn persist(&self, program_id: &solana_program::pubkey::Pubkey) -> solitaire::Result<()> {
solitaire::Persist::persist(self, program_id)
}
}
/// Macro generated implementation of Persist by Solitaire.
impl #type_impl_g solitaire::Persist for #name #type_g {
fn persist(&self, program_id: &solana_program::pubkey::Pubkey) -> solitaire::Result<()> {
#persist_method
}
}
};
// Hand the output tokens back to the compiler
TokenStream::from(expanded)
}
/// This function does the heavy lifting of generating the field parsers.
fn generate_fields(name: &syn::Ident, data: &Data) -> TokenStream2 {
match *data {
// We only care about structures.
Data::Struct(ref data) => {
// We want to inspect its fields.
match data.fields {
// For now, we only care about struct { a: T } forms, not struct(T);
Fields::Named(ref fields) => {
// For each field, generate an expression that parses an account info field
// from the Solana accounts list. This relies on Verify::verify to do most of
// the work.
let recurse = fields.named.iter().map(|f| {
// Field name, to assign to.
let name = &f.ident;
let ty = &f.ty;
quote! {
trace!(stringify!(#name));
let #name: #ty = solitaire::Peel::peel(&mut solitaire::Context::new(
pid,
iter,
data,
))?;
}
});
let names = fields.named.iter().map(|f| {
let name = &f.ident;
quote!(#name)
});
// Write out our iterator and return the filled structure.
quote! {
use solana_program::account_info::next_account_info;
use solitaire::trace;
trace!("Peeling:");
#(#recurse;)*
Ok(#name { #(#names,)* })
}
}
Fields::Unnamed(_) => {
unimplemented!()
}
Fields::Unit => {
unimplemented!()
}
}
}
Data::Enum(_) | Data::Union(_) => unimplemented!(),
}
}
/// This function does the heavy lifting of generating the field parsers.
fn generate_deps_fields(name: &syn::Ident, data: &Data) -> TokenStream2 {
match *data {
// We only care about structures.
Data::Struct(ref data) => {
// We want to inspect its fields.
match data.fields {
// For now, we only care about struct { a: T } forms, not struct(T);
Fields::Named(ref fields) => {
// For each field, generate an expression appends it deps
let recurse = fields.named.iter().map(|f| {
let ty = &f.ty;
quote! {
deps.append(&mut <#ty as Peel>::deps());
}
});
// Write out our iterator and return the filled structure.
quote! {
let mut deps = Vec::new();
#(#recurse;)*
deps
}
}
Fields::Unnamed(_) => {
unimplemented!()
}
Fields::Unit => {
unimplemented!()
}
}
}
Data::Enum(_) | Data::Union(_) => unimplemented!(),
}
}
/// This function does the heavy lifting of generating the field parsers.
fn generate_persist(name: &syn::Ident, data: &Data) -> TokenStream2 {
match *data {
// We only care about structures.
Data::Struct(ref data) => {
// We want to inspect its fields.
match data.fields {
// For now, we only care about struct { a: T } forms, not struct(T);
Fields::Named(ref fields) => {
// For each field, generate an expression that parses an account info field
// from the Solana accounts list. This relies on Verify::verify to do most of
// the work.
let recurse = fields.named.iter().map(|f| {
// Field name, to assign to.
let name = &f.ident;
let ty = &f.ty;
quote! {
trace!(stringify!(#name));
Peel::persist(&self.#name, program_id)?;
}
});
// Write out our iterator and return the filled structure.
quote! {
use solitaire::trace;
trace!("Persisting:");
#(#recurse;)*
Ok(())
}
}
Fields::Unnamed(_) => {
unimplemented!()
}
Fields::Unit => {
unimplemented!()
}
}
}
Data::Enum(_) | Data::Union(_) => unimplemented!(),
}
}

View File

@ -1,118 +0,0 @@
//! Derive macro logic for ToInstruction
use proc_macro::TokenStream;
use proc_macro2::{
Span,
TokenStream as TokenStream2,
};
use quote::{
quote,
quote_spanned,
};
use syn::{
parse_macro_input,
parse_quote,
spanned::Spanned,
Data,
DataStruct,
DeriveInput,
Fields,
GenericParam,
Generics,
Index,
};
pub fn generate_to_instruction(
name: &syn::Ident,
impl_generics: &syn::ImplGenerics,
data: &Data,
) -> TokenStream2 {
match *data {
Data::Struct(DataStruct {
fields: Fields::Named(ref fields),
..
}) => {
let expanded_appends = fields.named.iter().map(|field| {
let name = &field.ident;
let ty = &field.ty;
quote! {
deps.append(&mut <#ty as solitaire::Peel>::deps());
account_metas.append(&mut <#ty as solitaire_client::Wrap>::wrap(&self.#name)?);
if let Some(pair) = <#ty as solitaire_client::Wrap>::keypair(self.#name) {
signers.push(pair);
}
}
});
let client_struct_name =
syn::Ident::new(&format!("{}Accounts", name.to_string()), Span::call_site());
let client_struct_decl = generate_clientside_struct(&name, &client_struct_name, &data);
quote! {
/// Solitaire-generated client-side #name representation
#[cfg(feature = "client")]
#client_struct_decl
/// Solitaire-generatied ToInstruction implementation
#[cfg(feature = "client")]
impl #impl_generics solitaire_client::ToInstruction for #client_struct_name {
fn to_ix(
self,
program_id: solana_program::pubkey::Pubkey,
ix_data: &[u8]) -> std::result::Result<
(solitaire_client::Instruction, Vec<solitaire_client::Keypair>),
solitaire::ErrBox
> {
use solana_program::{pubkey::Pubkey, instruction::Instruction};
let mut account_metas = Vec::new();
let mut signers = Vec::new();
let mut deps = Vec::new();
#(#expanded_appends;)*
// Add dependencies
deps.dedup();
let mut dep_ams = deps.iter().map(|v| solana_program::instruction::AccountMeta::new_readonly(*v, false)).collect();
account_metas.append(&mut dep_ams);
Ok((solana_program::instruction::Instruction::new_with_bytes(program_id,
ix_data,
account_metas), signers))
}
}
}
}
_ => unimplemented!(),
}
}
pub fn generate_clientside_struct(
name: &syn::Ident,
client_struct_name: &syn::Ident,
data: &Data,
) -> TokenStream2 {
match *data {
Data::Struct(DataStruct {
fields: Fields::Named(ref fields),
..
}) => {
let expanded_fields = fields.named.iter().map(|field| {
let field_name = &field.ident;
quote! {
#field_name: solitaire_client::AccEntry
}
});
quote! {
pub struct #client_struct_name {
#(pub #expanded_fields,)*
}
}
}
_ => unimplemented!(),
}
}

View File

@ -1,11 +0,0 @@
# Merge similar crates together to avoid multiple use statements.
imports_granularity = "Crate"
# Consistency in formatting makes tool based searching/editing better.
empty_item_single_line = false
# Easier editing when arbitrary mixed use statements do not collapse.
imports_layout = "Vertical"
# Default rustfmt formatting of match arms with branches is awful.
match_arm_leading_pipes = "Preserve"

View File

@ -269,11 +269,10 @@ dependencies = [
[[package]]
name = "crossbeam-epoch"
version = "0.9.8"
version = "0.9.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1145cf131a2c6ba0615079ab6a638f7e1973ac9c2634fcbeaaad6114246efe8c"
checksum = "4ec02e091aa634e2c3ada4a392989e7c3116673ef0ac5b72232439094d73b7fd"
dependencies = [
"autocfg",
"cfg-if",
"crossbeam-utils",
"lazy_static",
@ -559,11 +558,10 @@ dependencies = [
[[package]]
name = "lock_api"
version = "0.4.7"
version = "0.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "327fa5b6a6940e4699ec49a9beae1ea4845c6bab9314e4f84ac68742139d8c53"
checksum = "88943dd7ef4a2e5a4bfa2753aaab3013e34ce2533d1996fb18ef591e315e2b3b"
dependencies = [
"autocfg",
"scopeguard",
]
@ -641,6 +639,7 @@ name = "p2w-sdk"
version = "0.1.1"
dependencies = [
"hex",
"pyth-sdk",
"pyth-sdk-solana",
"serde",
"solana-program",
@ -826,6 +825,7 @@ dependencies = [
[[package]]
name = "rocksalt"
version = "0.1.0"
source = "git+https://github.com/certusone/wormhole?tag=v2.8.9#e47f9e481ef84d4dea7a94c9eafbf3b180892466"
dependencies = [
"byteorder",
"proc-macro2",
@ -1007,9 +1007,9 @@ checksum = "f2dd574626839106c320a323308629dcb1acfc96e32a8cba364ddc61ac23ee83"
[[package]]
name = "solana-frozen-abi"
version = "1.10.13"
version = "1.10.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e7dd1cefedcc43251a0618c902b8a5ce7ae6c2a5103264633a65b1b40b6ba259"
checksum = "68f2b153f8eb8c4d22f2b739d3d31bac4122ca17376869cb717bf3a45200ea63"
dependencies = [
"bs58",
"bv",
@ -1029,9 +1029,9 @@ dependencies = [
[[package]]
name = "solana-frozen-abi-macro"
version = "1.10.13"
version = "1.10.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b37c61a1bb5dd9ac1b8f6b4fd276ea4833822668e791f74ae8c45dd792167f4"
checksum = "0cd23aad847403a28dd1452611490b5e8f040470ed251882cca0492c5e566280"
dependencies = [
"proc-macro2",
"quote",
@ -1041,9 +1041,9 @@ dependencies = [
[[package]]
name = "solana-program"
version = "1.10.13"
version = "1.10.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9452f34caedc50eeb0752c5f9ea7992ec8f618c2041acbbd455e70186f362d51"
checksum = "37be82a1fe85b24aa036153650053fd9628489c07c834b6b2dc027c4052bdbe5"
dependencies = [
"base64 0.13.0",
"bincode",
@ -1083,9 +1083,9 @@ dependencies = [
[[package]]
name = "solana-sdk-macro"
version = "1.10.13"
version = "1.10.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cee7e7c63938c587870f33bd6b81a9c2913773009802ba3eed57116e9f24694a"
checksum = "275c52edaaaa86ce649a226c03f75579d570c01880a43ee1de77a973994754ce"
dependencies = [
"bs58",
"proc-macro2",
@ -1097,6 +1097,7 @@ dependencies = [
[[package]]
name = "solitaire"
version = "0.1.0"
source = "git+https://github.com/certusone/wormhole?tag=v2.8.9#e47f9e481ef84d4dea7a94c9eafbf3b180892466"
dependencies = [
"borsh",
"byteorder",

View File

@ -5,18 +5,23 @@ authors = ["Wormhole Contributors <contact@certus.one>"]
edition = "2018"
description = "Pyth to Wormhole SDK"
[lib]
crate-type = ["cdylib", "rlib"]
[features]
solana = ["solitaire"]
wasm = ["wasm-bindgen", "solana"]
default = []
solana = ["solitaire", "solana-program", "pyth-sdk-solana"]
wasm = ["wasm-bindgen", "solana"]
[dependencies]
hex = "0.4.3"
serde = { version = "1.0.103", default-features = false, features = ["derive"] }
pyth-sdk-solana = "0.4.0"
pyth-sdk = "*"
pyth-sdk-solana = { version = "0.4.0", optional = true }
wasm-bindgen = { version = "0.2.74", features = ["serde-serialize"], optional = true}
solitaire = { path = "../../../../solana/solitaire/program", optional = true }
solana-program = "1.8.16"
solitaire = { git = "https://github.com/certusone/wormhole", tag = "v2.8.9", optional = true}
solana-program = { version = "=1.10.31", optional = true }
[dev-dependencies]
solana-program = "=1.10.31"
pyth-sdk-solana = "0.4.0"

View File

@ -18,7 +18,11 @@ use std::io::Read;
use std::iter::Iterator;
use std::mem;
use pyth_sdk_solana::state::PriceStatus;
pub use pyth_sdk::{
Identifier,
PriceStatus,
UnixTimestamp,
};
#[cfg(feature = "solana")]
use solitaire::{
@ -26,9 +30,6 @@ use solitaire::{
Info,
};
use solana_program::clock::UnixTimestamp;
use solana_program::pubkey::Pubkey;
#[cfg(feature = "wasm")]
#[cfg(all(target_arch = "wasm32", target_os = "unknown"))]
pub mod wasm;
@ -66,8 +67,6 @@ pub enum PayloadId {
PriceBatchAttestation = 2,
}
// On-chain data types
/// The main attestation data type.
///
/// Important: For maximum security, *both* product_id and price_id
@ -80,9 +79,9 @@ pub enum PayloadId {
#[serde(rename_all = "camelCase")]
pub struct PriceAttestation {
#[serde(serialize_with = "pubkey_to_hex")]
pub product_id: Pubkey,
pub product_id: Identifier,
#[serde(serialize_with = "pubkey_to_hex")]
pub price_id: Pubkey,
pub price_id: Identifier,
#[serde(serialize_with = "use_to_string")]
pub price: i64,
#[serde(serialize_with = "use_to_string")]
@ -113,7 +112,7 @@ where
s.serialize_str(&val.to_string())
}
pub fn pubkey_to_hex<S>(val: &Pubkey, s: S) -> Result<S::Ok, S::Error>
pub fn pubkey_to_hex<S>(val: &Identifier, s: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
@ -137,7 +136,7 @@ impl BatchPriceAttestation {
// major_version
buf.extend_from_slice(&P2W_FORMAT_VER_MAJOR.to_be_bytes()[..]);
// minor_version
// minor_version
buf.extend_from_slice(&P2W_FORMAT_VER_MINOR.to_be_bytes()[..]);
// hdr_size
@ -279,22 +278,23 @@ impl BatchPriceAttestation {
// On-chain data types
impl PriceAttestation {
#[cfg(feature = "solana")]
pub fn from_pyth_price_bytes(
price_id: Pubkey,
price_id: Identifier,
attestation_time: UnixTimestamp,
value: &[u8],
) -> Result<Self, ErrBox> {
let price = pyth_sdk_solana::state::load_price_account(value)?;
Ok(PriceAttestation {
product_id: Pubkey::new(&price.prod.val[..]),
product_id: Identifier::new(price.prod.val),
price_id,
price: price.agg.price,
conf: price.agg.conf,
expo: price.expo,
ema_price: price.ema_price.val,
ema_conf: price.ema_conf.val as u64,
status: price.agg.status.into(),
status: price.agg.status,
num_publishers: price.num_qt,
max_num_publishers: price.num,
attestation_time,
@ -379,11 +379,11 @@ impl PriceAttestation {
pub fn deserialize(mut bytes: impl Read) -> Result<Self, ErrBox> {
let mut product_id_vec = vec![0u8; PUBKEY_LEN];
bytes.read_exact(product_id_vec.as_mut_slice())?;
let product_id = Pubkey::new(product_id_vec.as_slice());
let product_id = Identifier::new(product_id_vec.as_slice().try_into()?);
let mut price_id_vec = vec![0u8; PUBKEY_LEN];
bytes.read_exact(price_id_vec.as_mut_slice())?;
let price_id = Pubkey::new(price_id_vec.as_slice());
let price_id = Identifier::new(price_id_vec.as_slice().try_into()?);
let mut price_vec = vec![0u8; mem::size_of::<i64>()];
bytes.read_exact(price_vec.as_mut_slice())?;
@ -479,8 +479,8 @@ mod tests {
let product_id_bytes = prod.unwrap_or([21u8; 32]);
let price_id_bytes = price.unwrap_or([222u8; 32]);
PriceAttestation {
product_id: Pubkey::new_from_array(product_id_bytes),
price_id: Pubkey::new_from_array(price_id_bytes),
product_id: Identifier::new(product_id_bytes),
price_id: Identifier::new(price_id_bytes),
price: 0x2bad2feed7,
conf: 101,
ema_price: -42,