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:
parent
b485ef4d16
commit
a2a0f6e15b
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -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]
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,5 +0,0 @@
|
|||
[workspace]
|
||||
members = ["program", "client", "program_stub", "cpi_poster"]
|
||||
|
||||
[patch.crates-io]
|
||||
memmap2 = { path = "memmap2-rs" }
|
|
@ -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"] }
|
|
@ -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(())
|
||||
}
|
||||
}
|
|
@ -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"
|
|
@ -1,2 +0,0 @@
|
|||
[target.bpfel-unknown-unknown.dependencies.std]
|
||||
features = []
|
|
@ -1,3 +0,0 @@
|
|||
pub mod post_message;
|
||||
|
||||
pub use post_message::*;
|
|
@ -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(())
|
||||
}
|
|
@ -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,
|
||||
})
|
||||
}
|
|
@ -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,
|
||||
}
|
|
@ -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"
|
|
@ -1,2 +0,0 @@
|
|||
[target.bpfel-unknown-unknown.dependencies.std]
|
||||
features = []
|
|
@ -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::*;
|
|
@ -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,
|
||||
}
|
|
@ -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(),
|
||||
];
|
||||
}
|
||||
}
|
|
@ -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">;
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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())
|
||||
}
|
||||
}
|
|
@ -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())
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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::*;
|
|
@ -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(())
|
||||
}
|
|
@ -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(())
|
||||
}
|
|
@ -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(())
|
||||
}
|
|
@ -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(())
|
||||
}
|
|
@ -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(())
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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()
|
||||
}
|
|
@ -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,
|
||||
}
|
|
@ -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 {
|
||||
}
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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()
|
||||
}
|
|
@ -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
|
@ -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"
|
|
@ -1,2 +0,0 @@
|
|||
[target.bpfel-unknown-unknown.dependencies.std]
|
||||
features = []
|
|
@ -1,7 +0,0 @@
|
|||
pub mod post_vaa;
|
||||
|
||||
pub use bridge::{
|
||||
initialize::*,
|
||||
post_message::*,
|
||||
};
|
||||
pub use post_vaa::*;
|
|
@ -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(())
|
||||
}
|
|
@ -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,
|
||||
}
|
|
@ -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
|
@ -1,4 +1,4 @@
|
|||
[workspace]
|
||||
members = ["client", "program"]
|
||||
[patch.crates-io]
|
||||
memmap2 = { path = "../bridge/memmap2-rs" }
|
||||
memmap2 = { path = "../memmap2-rs" }
|
|
@ -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"
|
||||
|
|
|
@ -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,
|
||||
);
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
|
|
|
@ -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)?;
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
@ -1,2 +0,0 @@
|
|||
[workspace]
|
||||
members = ["rocksalt", "program", "client"]
|
|
@ -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"
|
|
@ -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>;
|
||||
}
|
|
@ -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"
|
|
@ -1,2 +0,0 @@
|
|||
[target.bpfel-unknown-unknown.dependencies.std]
|
||||
features = []
|
|
@ -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),
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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())
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
|
@ -1,4 +0,0 @@
|
|||
pub mod keyed;
|
||||
pub mod peel;
|
||||
pub mod persist;
|
||||
pub mod seeded;
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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(())
|
||||
}
|
||||
}
|
|
@ -1,5 +0,0 @@
|
|||
use solana_program::pubkey::Pubkey;
|
||||
|
||||
pub trait Persist {
|
||||
fn persist(&self, program_id: &Pubkey) -> crate::Result<()>;
|
||||
}
|
|
@ -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])
|
||||
}
|
|
@ -1,7 +0,0 @@
|
|||
mod accounts;
|
||||
mod context;
|
||||
mod layers;
|
||||
|
||||
pub use accounts::*;
|
||||
pub use context::*;
|
||||
pub use layers::*;
|
|
@ -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())
|
||||
}
|
||||
}
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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) }
|
||||
}
|
||||
}
|
|
@ -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"
|
|
@ -1,2 +0,0 @@
|
|||
[target.bpfel-unknown-unknown.dependencies.std]
|
||||
features = []
|
|
@ -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!(),
|
||||
}
|
||||
}
|
|
@ -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!(),
|
||||
}
|
||||
}
|
|
@ -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"
|
|
@ -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",
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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,
|
||||
{
|
||||
|
@ -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,
|
||||
|
|
Loading…
Reference in New Issue