Restructure workspace

This will make is easier to separate modules and solitaire in the future.
We also get rid of the old bridge code.

Change-Id: I4d663c36739dfec77cd5d3f1ed6b51f422fe0c91
This commit is contained in:
Hendrik Hofstadt 2021-06-21 10:24:52 +02:00
parent 5eb7d0b7d0
commit aa909c218f
70 changed files with 3159 additions and 12446 deletions

View File

@ -48,11 +48,12 @@ your printer on fire or startle your cat. Cryptocurrencies are a high-risk inves
See DEVELOP.md for usage.
- **[solana/](solana/)** — Solana sidecar agent, contract and CLI.
- **[agent/](solana/agent/)** — Rust agent sidecar deployed alongside each Guardian node. It serves
- **[bridge/](solana/bridge/)** - Solana Wormhole bridge components
- **[agent/](solana/bridge/agent/)** — Rust agent sidecar deployed alongside each Guardian node. It serves
a local gRPC API to interface with the Solana blockchain. This is far easier to maintain than a
pure-Go Solana client.
- **[bridge/](solana/bridge/)** — Solana Wormhole smart contract code.
- [cli/](solana/cli/) — Wormhole user CLI tool for interaction with the smart contract.
- **[program/](solana/bridge/program)** — Solana Wormhole smart contract code.
- [client/](solana/bridge/cli/) — Wormhole user CLI tool for interaction with the smart contract.
- [devnet_setup.sh](solana/devnet_setup.sh) — Devnet initialization and example code for a lockup program
(the Solana equivalent to the Ganache migration + send-lockups.js). Runs as a sidecar alongside the Solana devnet.

4506
solana/Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -1,2 +0,0 @@
[workspace]
members = ["agent", "bridge", "cli"]

View File

@ -12,8 +12,8 @@ ENV PATH="/root/.local/share/solana/install/active_release/bin:$PATH"
ENV RUST_LOG="solana_runtime::system_instruction_processor=trace,solana_runtime::message_processor=trace,solana_bpf_loader=debug,solana_rbpf=debug"
COPY bridge bridge
COPY agent agent
COPY cli cli
COPY bridge/agent agent
COPY bridge/cli cli
COPY Cargo.toml .
COPY Cargo.lock .

View File

@ -1 +0,0 @@
target

View File

@ -1,9 +0,0 @@
# ignore Mac OS noise
.DS_Store
# ignore the build directory for Rust/Anchor
target
# Ignore backup files creates by cargo fmt.
**/*.rs.bk

View File

@ -1,2 +0,0 @@
cluster = "localnet"
wallet = "/root/.config/solana/id.json"

View File

@ -1,6 +0,0 @@
[workspace]
members = [
"programs/*",
"client/",
"solitaire-client",
]

View File

@ -1,32 +0,0 @@
# syntax=docker/dockerfile:1.2
FROM rust:1.52@sha256:9c106c1222abe1450f45774273f36246ebf257623ed51280dbc458632d14c9fc as rust-with-deps-and-src
RUN rustup default nightly-2021-06-02
RUN rm -f /etc/apt/apt.conf.d/docker-clean; echo 'Binary::apt::APT::Keep-Downloaded-Packages "true";' > /etc/apt/apt.conf.d/keep-cache
ENV DEBIAN_FRONTEND=noninteractive
RUN --mount=type=cache,target=/var/cache/apt --mount=type=cache,target=/var/lib/apt \
apt update && \
apt-get install -y \
build-essential \
cmake \
curl \
git \
libudev-dev \
libudev1 \
pkg-config
RUN sh -c "$(curl -sSfL https://release.solana.com/v1.7.1/install)"
ENV PATH=/root/.local/share/solana/install/active_release/bin:$PATH
WORKDIR code
ADD . .
FROM rust-with-deps-and-src as cargo-check
RUN --mount=type=cache,target=target cargo check
FROM rust-with-deps-and-src as cargo-build-bpf
RUN --mount=type=cache,target=target cargo build-bpf

View File

@ -1,13 +0,0 @@
// Migrations are an early feature. Currently, they're nothing more than this
// single deploy script that's invoked from the CLI, injecting a provider
// configured from the workspace's Anchor.toml.
const anchor = require("@project-serum/anchor");
module.exports = async function (provider) {
// Configure client to use the provider.
anchor.setProvider(provider);
// Add your deploy script here.
}

View File

@ -1,14 +0,0 @@
const anchor = require('@project-serum/anchor');
describe('anchor-bridge', () => {
// Configure the client to use the local cluster.
anchor.setProvider(anchor.Provider.env());
it('Is initialized!', async () => {
// Add your test here.
const program = anchor.workspace.AnchorBridge;
const tx = await program.rpc.initialize();
console.log("Your transaction signature", tx);
});
});

3549
solana/bridge/Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -1,35 +1,2 @@
# Note: This crate must be built using do.sh
[package]
name = "wormhole-bridge"
version = "0.1.0"
description = "Solana Program Library Token Swap"
authors = ["Certus One Team <info@certus.one>"]
repository = "https://github.com/solana-labs/solana-program-library"
license = "Apache-2.0"
edition = "2018"
[features]
default = ["program"]
no-entrypoint = []
program = []
[dependencies]
num-derive = "0.2"
num-traits = "0.2"
remove_dir_all = "=0.5.0"
solana-program = "=1.5.5"
thiserror = "1.0"
byteorder = "1.3.4"
zerocopy = "0.3.0"
sha3 = "0.9.1"
primitive-types = { version = "0.7.2", default-features = false }
solana-sdk = "1.7.0"
[dev-dependencies]
rand = { version = "0.7.0" }
hex = "0.4.2"
[lib]
name = "spl_bridge"
crate-type = ["cdylib", "lib"]
[workspace]
members = ["agent", "program", "client"]

View File

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

View File

@ -9,11 +9,12 @@ tonic = "0.3.0"
tokio = { version = "0.2", features = ["rt-threaded", "time", "stream", "fs", "macros", "uds"] }
prost = "0.6"
prost-types = "0.6"
solana-sdk = { version = "=1.5.5" }
solana-client = { version = "=1.5.5" }
solana-faucet = "=1.5.5"
spl-token = {version = "=3.0.1", features = ["no-entrypoint"]}
wormhole-bridge = { path = "../bridge", default-features = false, features = ["no-entrypoint"] }
solana-client = "=1.7.0"
solana-program = "=1.7.0"
solana-sdk = "=1.7.0"
solitaire = { path = "../../solitaire/program" }
solitaire-client = {path = "../../solitaire/client" }
bridge = { path = "../program", default-features = false, features = ["no-entrypoint"] }
primitive-types = { version = "0.7.2" }
hex = "0.4.2"
thiserror = "1.0.20"

View File

@ -7,12 +7,12 @@ edition = "2018"
[dependencies]
anyhow = "1.0.40"
borsh = "0.8.1"
bridge = {path = "../programs/bridge", features = ["no-idl", "no-entrypoint"]}
bridge = {path = "../program", features = ["no-idl", "no-entrypoint"]}
clap = "3.0.0-beta.2"
rand = "0.7.3"
shellexpand = "2.1.0"
solana-client = "=1.7.0"
solana-program = "=1.7.0"
solana-sdk = "=1.7.0"
solitaire = { path = "../programs/solitaire" }
solitaire-client = {path = "../solitaire-client"}
solitaire = { path = "../../solitaire/program" }
solitaire-client = {path = "../../solitaire/client" }

View File

@ -18,8 +18,8 @@ default = []
[dependencies]
borsh = "0.8.1"
byteorder = "1.4.3"
solitaire = { path = "../solitaire"}
solitaire-client = { path = "../../solitaire-client", optional = true}
solitaire = { path = "../../solitaire/program" }
solitaire-client = { path = "../../solitaire/client", optional = true}
sha3 = "0.9.1"
primitive-types = { version = "0.9.0", default-features = false }
solana-program = "=1.7.0"

View File

@ -1,25 +0,0 @@
//! Program entrypoint definitions
#![cfg(feature = "program")]
#![cfg(not(feature = "no-entrypoint"))]
use solana_program::{
account_info::AccountInfo, entrypoint, entrypoint::ProgramResult,
program_error::PrintProgramError, pubkey::Pubkey,
};
use crate::{error::Error, state::Bridge};
entrypoint!(process_instruction);
fn process_instruction<'a>(
program_id: &Pubkey,
accounts: &'a [AccountInfo<'a>],
instruction_data: &[u8],
) -> ProgramResult {
msg!("In bridge entrypoint");
if let Err(error) = Bridge::process(program_id, accounts, instruction_data) {
// catch the error so we can print it
error.print::<Error>();
return Err(error);
}
Ok(())
}

View File

@ -1,153 +0,0 @@
//! Error types
use num_derive::FromPrimitive;
use num_traits::FromPrimitive;
use solana_program::program_error::PrintProgramError;
use solana_program::{decode_error::DecodeError, program_error::ProgramError};
use thiserror::Error;
/// Errors that may be returned by the TokenSwap program.
#[derive(Clone, Debug, Eq, Error, FromPrimitive, PartialEq)]
pub enum VAAError {
/// The given action is unknown or invalid
#[error("InvalidAction")]
InvalidAction,
/// An io error occurred
#[error("IOError")]
IOError,
}
/// Errors that may be returned by the TokenSwap program.
#[derive(Clone, Debug, Eq, Error, FromPrimitive, PartialEq)]
pub enum Error {
/// The deserialization of the Token state returned something besides State::Token.
#[error("ExpectedToken")]
ExpectedToken,
/// The deserialization of the Bridge returned something besides State::Bridge.
#[error("ExpectedBridge")]
ExpectedBridge,
/// The deserialization of the Token state returned something besides State::Account.
#[error("ExpectedAccount")]
ExpectedAccount,
/// The deserialization of the GuardianSet state returned something besides State::GuardianSet.
#[error("ExpectedGuardianSet")]
ExpectedGuardianSet,
/// The program address provided doesn't match the value generated by the program.
#[error("InvalidProgramAddress")]
InvalidProgramAddress,
/// The submitted VAA is invalid
#[error("InvalidVAAFormat")]
InvalidVAAFormat,
/// The submitted VAA is invalid form
#[error("InvalidVAAAction")]
InvalidVAAAction,
/// The submitted VAA has an invalid signature
#[error("InvalidVAASignature")]
InvalidVAASignature,
/// The account is already initialized
#[error("AlreadyExists")]
AlreadyExists,
/// An account was not derived correctly
#[error("InvalidDerivedAccount")]
InvalidDerivedAccount,
/// A given bridge account does not belong to the program
#[error("WrongBridgeOwner")]
WrongBridgeOwner,
/// A parsing operation failed
#[error("ParseFailed")]
ParseFailed,
/// The guardian set that signed this VAA has expired
#[error("GuardianSetExpired")]
GuardianSetExpired,
/// The given VAA has already been claimed
#[error("VAAClaimed")]
VAAClaimed,
/// The given VAA was not signed by the latest guardian set
#[error("OldGuardianSet")]
OldGuardianSet,
/// The guardian set index must increase in steps of 1 on update
#[error("GuardianIndexNotIncreasing")]
GuardianIndexNotIncreasing,
/// The given VAA does not match the proposal
#[error("VAAProposalMismatch")]
VAAMessageMismatch,
/// VAA is longer than the maximum size
#[error("VAATooLong")]
VAATooLong,
/// VAA for this transfer has already been submitted
#[error("VAAAlreadySubmitted")]
VAAAlreadySubmitted,
/// Mismatching guardian set
#[error("GuardianSetMismatch")]
GuardianSetMismatch,
/// Insufficient fees
#[error("InsufficientFees")]
InsufficientFees,
/// Invalid owner
#[error("InvalidOwner")]
InvalidOwner,
/// Invalid Sysvar
#[error("InvalidSysvar")]
InvalidSysvar,
/// Invalid Chain
#[error("InvalidChain")]
InvalidChain,
/// Emitter is not a signer
#[error("EmitterNotSigner")]
EmitterNotSigner,
}
#[cfg(feature = "program")]
impl PrintProgramError for Error {
fn print<E>(&self)
where
E: 'static + std::error::Error + DecodeError<E> + PrintProgramError + FromPrimitive,
{
match self {
Error::ExpectedToken => msg!("Error: ExpectedToken"),
Error::ExpectedAccount => msg!("Error: ExpectedAccount"),
Error::ExpectedBridge => msg!("Error: ExpectedBridge"),
Error::ExpectedGuardianSet => msg!("Error: ExpectedGuardianSet"),
Error::InvalidProgramAddress => msg!("Error: InvalidProgramAddress"),
Error::InvalidVAAFormat => msg!("Error: InvalidVAAFormat"),
Error::InvalidVAASignature => msg!("Error: InvalidVAASignature"),
Error::AlreadyExists => msg!("Error: AlreadyExists"),
Error::InvalidDerivedAccount => msg!("Error: InvalidDerivedAccount"),
Error::ParseFailed => msg!("Error: ParseFailed"),
Error::GuardianSetExpired => msg!("Error: GuardianSetExpired"),
Error::VAAClaimed => msg!("Error: VAAClaimed"),
Error::WrongBridgeOwner => msg!("Error: WrongBridgeOwner"),
Error::OldGuardianSet => msg!("Error: OldGuardianSet"),
Error::GuardianIndexNotIncreasing => msg!("Error: GuardianIndexNotIncreasing"),
Error::VAAMessageMismatch => msg!("Error: VAAProposalMismatch"),
Error::VAATooLong => msg!("Error: VAATooLong"),
Error::VAAAlreadySubmitted => msg!("Error: VAAAlreadySubmitted"),
Error::GuardianSetMismatch => msg!("Error: GuardianSetMismatch"),
Error::InsufficientFees => msg!("Error: InsufficientFees"),
Error::InvalidOwner => msg!("Error: InvalidOwner"),
Error::InvalidSysvar => msg!("Error: InvalidSysvar"),
Error::InvalidChain => msg!("Error: InvalidChain"),
Error::InvalidVAAAction => msg!("Error: InvalidVAAAction"),
Error::EmitterNotSigner => msg!("Error: EmitterNotSigner"),
}
}
}
impl From<Error> for ProgramError {
fn from(e: Error) -> Self {
ProgramError::Custom(e as u32)
}
}
impl From<std::io::Error> for Error {
fn from(_: std::io::Error) -> Self {
Error::ParseFailed
}
}
impl<T> DecodeError<T> for Error {
fn type_of() -> &'static str {
"Swap Error"
}
}

View File

@ -1,107 +0,0 @@
use solana_program::pubkey::Pubkey;
use std::io::{Cursor, Write, Read};
use crate::error::Error;
use byteorder::{ReadBytesExt, WriteBytesExt, BigEndian};
#[derive(Clone, Debug, PartialEq)]
pub enum GovernanceCommand {
UpdateGuardianSet(BodyUpdateGuardianSet),
UpgradeContract(BodyContractUpgrade),
}
impl GovernanceCommand {
fn action_id(&self) -> u8 {
match self {
GovernanceCommand::UpdateGuardianSet(_) => 0x01,
GovernanceCommand::UpgradeContract(_) => 0x02,
}
}
fn deserialize(data: &Vec<u8>) -> Result<GovernanceCommand, Error> {
let mut payload_data = Cursor::new(data);
let action = payload_data.read_u8()?;
let payload = match action {
0x01 => {
GovernanceCommand::UpdateGuardianSet(BodyUpdateGuardianSet::deserialize(&mut payload_data)?)
}
0x02 => GovernanceCommand::UpgradeContract(BodyContractUpgrade::deserialize(&mut payload_data)?),
_ => {
return Err(Error::InvalidVAAAction);
}
};
Ok(payload)
}
fn serialize(&self) -> Result<Vec<u8>, Error> {
match self {
GovernanceCommand::UpdateGuardianSet(b) => b.serialize(),
GovernanceCommand::UpgradeContract(b) => b.serialize(),
}
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct BodyUpdateGuardianSet {
pub new_index: u32,
pub new_keys: Vec<[u8; 20]>,
}
#[derive(Clone, Debug, PartialEq)]
pub struct BodyContractUpgrade {
pub chain_id: u8,
pub buffer: Pubkey,
}
impl BodyContractUpgrade {
fn deserialize(data: &mut Cursor<&Vec<u8>>) -> Result<BodyContractUpgrade, Error> {
let chain_id = data.read_u8()?;
let mut key: [u8; 32] = [0; 32];
data.read(&mut key[..])?;
Ok(BodyContractUpgrade {
chain_id,
buffer: Pubkey::new(&key[..]),
})
}
fn serialize(&self) -> Result<Vec<u8>, Error> {
let mut v: Cursor<Vec<u8>> = Cursor::new(Vec::new());
v.write_u8(self.chain_id)?;
v.write(&self.buffer.to_bytes())?;
Ok(v.into_inner())
}
}
impl BodyUpdateGuardianSet {
fn deserialize(data: &mut Cursor<&Vec<u8>>) -> Result<BodyUpdateGuardianSet, Error> {
let new_index = data.read_u32::<BigEndian>()?;
let keys_len = data.read_u8()?;
let mut keys = Vec::with_capacity(keys_len as usize);
for _ in 0..keys_len {
let mut key: [u8; 20] = [0; 20];
data.read(&mut key)?;
keys.push(key);
}
Ok(BodyUpdateGuardianSet {
new_index,
new_keys: keys,
})
}
fn serialize(&self) -> Result<Vec<u8>, Error> {
let mut v: Cursor<Vec<u8>> = Cursor::new(Vec::new());
v.write_u32::<BigEndian>(self.new_index)?;
v.write_u8(self.new_keys.len() as u8)?;
for k in self.new_keys.iter() {
v.write(k)?;
}
Ok(v.into_inner())
}
}

View File

@ -1,384 +0,0 @@
#![allow(clippy::too_many_arguments)]
//! Instruction types
use std::mem::size_of;
use solana_program::{
instruction::{AccountMeta, Instruction},
program_error::ProgramError,
pubkey::Pubkey,
};
use crate::instruction::BridgeInstruction::PublishMessage;
use crate::{
instruction::BridgeInstruction::{Initialize, PostVAA, VerifySignatures},
state::{Bridge, BridgeConfig},
vaa::{VAABody, VAA},
};
use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
use std::io::{Cursor, Read, Write};
/// chain id of this chain
pub const CHAIN_ID_SOLANA: u8 = 1;
/// maximum number of guardians
pub const MAX_LEN_GUARDIAN_KEYS: usize = 20;
/// maximum size of a posted VAA
pub const MAX_VAA_SIZE: usize = 1000;
/// maximum size of a posted VAA
pub const MAX_PAYLOAD_SIZE: usize = 400;
/// size of a foreign address in bytes
const FOREIGN_ADDRESS_SIZE: usize = 32;
/// serialized VAA data
pub type VAAData = Vec<u8>;
/// X and Y point of P for guardians
pub type GuardianKey = [u8; 64];
/// address on a foreign chain
pub type ForeignAddress = [u8; FOREIGN_ADDRESS_SIZE];
#[repr(C)]
#[derive(Clone, Copy)]
pub struct InitializePayload {
/// number of initial guardians
pub len_guardians: u8,
/// guardians that are allowed to sign mints
pub initial_guardian: [[u8; 20]; MAX_LEN_GUARDIAN_KEYS],
/// config for the bridge
pub config: BridgeConfig,
}
pub struct PublishMessagePayload {
/// unique nonce for this message
pub nonce: u32,
/// message payload
pub payload: Vec<u8>,
}
impl Clone for PublishMessagePayload {
fn clone(&self) -> PublishMessagePayload {
let payload = self.payload.clone();
return PublishMessagePayload {
payload,
nonce: self.nonce,
};
}
}
#[derive(Clone, Copy, Debug)]
pub struct VerifySigPayload {
/// hash of the VAA
pub hash: [u8; 32],
/// instruction indices of signers (-1 for missing)
pub signers: [i8; MAX_LEN_GUARDIAN_KEYS],
/// indicates whether this verification should only succeed if the sig account does not exist
pub initial_creation: bool,
}
/// Instructions supported by the SwapInfo program.
#[repr(C)]
pub enum BridgeInstruction {
/// Initializes a new Bridge
/// Accounts expected by this instruction:
///
/// 0. `[writable, derived]` The bridge to initialize.
/// 1. `[]` The System program
/// 2. `[]` The clock SysVar
/// 3. `[writable, derived]` The initial guardian set account
/// 4. `[signer]` The fee payer for new account creation
Initialize(InitializePayload),
/// Publishes a message over the Wormhole network.
/// See docs for accounts
PublishMessage(PublishMessagePayload),
/// Submits a VAA signed by `guardian` on a valid `proposal`.
/// See docs for accounts
PostVAA(VAAData),
/// Verifies signature instructions
VerifySignatures(VerifySigPayload),
}
impl BridgeInstruction {
/// Deserializes a byte buffer into a BridgeInstruction
pub fn deserialize(input: &[u8]) -> Result<Self, ProgramError> {
if input.len() < size_of::<u8>() {
return Err(ProgramError::InvalidAccountData);
}
Ok(match input[0] {
0 => {
let payload: &InitializePayload = unpack(input)?;
Initialize(*payload)
}
1 => {
let mut payload_data = Cursor::new(input);
let nonce = payload_data
.read_u32::<BigEndian>()
.map_err(|_| ProgramError::InvalidArgument)?;
let mut message_payload: Vec<u8> = vec![];
payload_data
.read(&mut message_payload)
.map_err(|_| ProgramError::InvalidArgument)?;
let payload: PublishMessagePayload = PublishMessagePayload {
nonce,
payload: message_payload,
};
PublishMessage(payload)
}
2 => {
let payload: VAAData = input[1..].to_vec();
PostVAA(payload)
}
3 => {
let payload: &VerifySigPayload = unpack(input)?;
VerifySignatures(*payload)
}
_ => return Err(ProgramError::InvalidInstructionData),
})
}
/// Serializes a BridgeInstruction into a byte buffer.
pub fn serialize(self: Self) -> Result<Vec<u8>, ProgramError> {
let mut output = Vec::with_capacity(size_of::<BridgeInstruction>());
match self {
Self::Initialize(payload) => {
output.resize(size_of::<InitializePayload>() + 1, 0);
output[0] = 0;
#[allow(clippy::cast_ptr_alignment)]
let value = unsafe {
&mut *(&mut output[size_of::<u8>()] as *mut u8 as *mut InitializePayload)
};
*value = payload;
}
Self::PublishMessage(payload) => {
let mut v: Cursor<Vec<u8>> = Cursor::new(Vec::new());
v.write_u8(1).map_err(|_| ProgramError::InvalidArgument)?;
v.write_u32::<BigEndian>(payload.nonce)
.map_err(|_| ProgramError::InvalidArgument)?;
v.write(&payload.payload)
.map_err(|_| ProgramError::InvalidArgument)?;
output = v.into_inner();
}
Self::PostVAA(payload) => {
output.resize(1, 0);
output[0] = 2;
#[allow(clippy::cast_ptr_alignment)]
output.extend_from_slice(&payload);
}
Self::VerifySignatures(payload) => {
output.resize(size_of::<VerifySigPayload>() + 1, 0);
output[0] = 3;
#[allow(clippy::cast_ptr_alignment)]
let value = unsafe {
&mut *(&mut output[size_of::<u8>()] as *mut u8 as *mut VerifySigPayload)
};
*value = payload;
}
}
Ok(output)
}
}
/// Creates an 'initialize' instruction.
#[cfg(not(target_arch = "bpf"))]
pub fn initialize(
program_id: &Pubkey,
sender: &Pubkey,
initial_guardian: Vec<[u8; 20]>,
config: &BridgeConfig,
) -> Result<Instruction, ProgramError> {
if initial_guardian.len() > MAX_LEN_GUARDIAN_KEYS {
return Err(ProgramError::InvalidArgument);
}
let mut initial_g = [[0u8; 20]; MAX_LEN_GUARDIAN_KEYS];
for (i, key) in initial_guardian.iter().enumerate() {
initial_g[i] = *key;
}
let data = BridgeInstruction::Initialize(InitializePayload {
config: *config,
len_guardians: initial_guardian.len() as u8,
initial_guardian: initial_g,
})
.serialize()?;
let bridge_key = Bridge::derive_bridge_id(program_id)?;
let guardian_set_key = Bridge::derive_guardian_set_id(program_id, &bridge_key, 0)?;
let accounts = vec![
AccountMeta::new_readonly(solana_program::system_program::id(), false),
AccountMeta::new_readonly(solana_program::sysvar::clock::id(), false),
AccountMeta::new(bridge_key, false),
AccountMeta::new(guardian_set_key, false),
AccountMeta::new(*sender, true),
];
Ok(Instruction {
program_id: *program_id,
accounts,
data,
})
}
/// Creates an 'PublishMessage' instruction.
#[cfg(not(target_arch = "bpf"))]
pub fn publish_message(
program_id: &Pubkey,
payer: &Pubkey,
t: &PublishMessagePayload,
) -> Result<Instruction, ProgramError> {
let bridge_key = Bridge::derive_bridge_id(program_id)?;
let message_key = Bridge::derive_message_id(
program_id,
&bridge_key,
CHAIN_ID_SOLANA,
payer.to_bytes(),
t.nonce,
&t.payload,
)?;
let accounts = vec![
AccountMeta::new_readonly(*program_id, false),
AccountMeta::new_readonly(solana_program::system_program::id(), false),
AccountMeta::new_readonly(solana_program::sysvar::rent::id(), false),
AccountMeta::new_readonly(solana_program::sysvar::clock::id(), false),
AccountMeta::new_readonly(solana_program::sysvar::instructions::id(), false),
AccountMeta::new_readonly(bridge_key, false),
AccountMeta::new(message_key, false),
AccountMeta::new(*payer, true),
];
let data = BridgeInstruction::PublishMessage(t.clone()).serialize()?;
Ok(Instruction {
program_id: *program_id,
accounts,
data,
})
}
/// Creates a 'VerifySignatures' instruction.
#[cfg(not(target_arch = "bpf"))]
pub fn verify_signatures(
program_id: &Pubkey,
signature_acc: &Pubkey,
payer: &Pubkey,
guardian_set_id: u32,
p: &VerifySigPayload,
) -> Result<Instruction, ProgramError> {
let data = BridgeInstruction::VerifySignatures(*p).serialize()?;
let bridge_key = Bridge::derive_bridge_id(program_id)?;
let guardian_set_key =
Bridge::derive_guardian_set_id(program_id, &bridge_key, guardian_set_id)?;
let accounts = vec![
AccountMeta::new_readonly(*program_id, false),
AccountMeta::new_readonly(solana_program::system_program::id(), false),
AccountMeta::new_readonly(solana_program::sysvar::instructions::id(), false),
AccountMeta::new(bridge_key, false),
AccountMeta::new(*signature_acc, false),
AccountMeta::new_readonly(guardian_set_key, false),
AccountMeta::new(*payer, true),
];
Ok(Instruction {
program_id: *program_id,
accounts,
data,
})
}
/// Creates a 'PostVAA' instruction.
#[cfg(not(target_arch = "bpf"))]
pub fn post_vaa(
program_id: &Pubkey,
payer: &Pubkey,
v: VAAData,
) -> Result<Instruction, ProgramError> {
let mut data = v.clone();
data.insert(0, 2);
// Parse VAA
let vaa = VAA::deserialize(&v[..])?;
let bridge_key = Bridge::derive_bridge_id(program_id)?;
let guardian_set_key =
Bridge::derive_guardian_set_id(program_id, &bridge_key, vaa.guardian_set_index)?;
let claim_key = Bridge::derive_claim_id(program_id, &bridge_key, vaa.signature_body()?)?;
let signature_acc = Bridge::derive_signature_id(
program_id,
&bridge_key,
&vaa.body_hash()?,
vaa.guardian_set_index,
)?;
let mut accounts = vec![
AccountMeta::new_readonly(*program_id, false),
AccountMeta::new_readonly(solana_program::system_program::id(), false),
AccountMeta::new_readonly(solana_program::sysvar::rent::id(), false),
AccountMeta::new_readonly(solana_program::sysvar::clock::id(), false),
AccountMeta::new(bridge_key, false),
AccountMeta::new(guardian_set_key, false),
AccountMeta::new(claim_key, false),
AccountMeta::new(signature_acc, false),
AccountMeta::new(*payer, true),
];
match vaa.payload.unwrap() {
VAABody::UpdateGuardianSet(u) => {
let guardian_set_key =
Bridge::derive_guardian_set_id(program_id, &bridge_key, u.new_index)?;
accounts.push(AccountMeta::new(guardian_set_key, false));
}
VAABody::UpgradeContract(u) => {
// Make program writeable
accounts[0] = AccountMeta::new(*program_id, false);
accounts.push(AccountMeta::new(u.buffer, false));
let (programdata_address, _) = Pubkey::find_program_address(
&[program_id.as_ref()],
&solana_program::bpf_loader_upgradeable::id(),
);
accounts.push(AccountMeta::new(programdata_address, false));
accounts.push(AccountMeta::new_readonly(
solana_program::bpf_loader_upgradeable::id(),
false,
));
}
VAABody::Message(t) => {
let message_key = Bridge::derive_message_id(
program_id,
&bridge_key,
t.emitter_chain,
t.emitter_address,
t.nonce,
&t.data,
)?;
accounts.push(AccountMeta::new(message_key, false))
}
}
Ok(Instruction {
program_id: *program_id,
accounts,
data,
})
}
/// Unpacks a reference from a bytes buffer.
pub fn unpack<T>(input: &[u8]) -> Result<&T, ProgramError> {
if input.len() < size_of::<u8>() + size_of::<T>() {
return Err(ProgramError::InvalidInstructionData);
}
#[allow(clippy::cast_ptr_alignment)]
let val: &T = unsafe { &*(&input[1] as *const u8 as *const T) };
Ok(val)
}

View File

@ -1,12 +0,0 @@
#[macro_use]
extern crate zerocopy;
#[macro_use]
extern crate solana_program;
pub mod entrypoint;
pub mod error;
pub mod instruction;
pub mod processor;
pub mod state;
pub mod vaa;
pub mod governance;

View File

@ -1,834 +0,0 @@
//! Program instruction processing logic
#![cfg(feature = "program")]
use std::{io::Write, mem::size_of, slice::Iter};
use byteorder::ByteOrder;
use num_traits::AsPrimitive;
use sha3::Digest;
use solana_program::program::invoke_signed;
use solana_program::{
account_info::{next_account_info, AccountInfo},
clock::Clock,
entrypoint::ProgramResult,
instruction::Instruction,
program_error::ProgramError,
pubkey::Pubkey,
rent::Rent,
system_instruction::{create_account},
sysvar::Sysvar,
};
use crate::instruction::PublishMessagePayload;
use crate::vaa::{BodyContractUpgrade, BodyMessage};
use crate::{
error::Error,
instruction::{
BridgeInstruction, BridgeInstruction::*, VAAData, VerifySigPayload, CHAIN_ID_SOLANA,
MAX_LEN_GUARDIAN_KEYS, MAX_VAA_SIZE,
},
state::*,
vaa::{BodyUpdateGuardianSet, VAABody, VAA},
};
use crate::governance::{BodyUpdateGuardianSet, BodyContractUpgrade};
/// 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],
signature: &'a [u8],
msg_offset: u16,
msg_size: u16,
}
/// Instruction processing logic
impl Bridge {
/// Processes an [Instruction](enum.Instruction.html).
pub fn process(program_id: &Pubkey, accounts: &[AccountInfo], input: &[u8]) -> ProgramResult {
let instruction = BridgeInstruction::deserialize(input)?;
match instruction {
Initialize(payload) => {
msg!("Instruction: Initialize");
Self::process_initialize(
program_id,
accounts,
payload.len_guardians,
payload.initial_guardian,
payload.config,
)
}
PublishMessage(p) => {
msg!("Instruction: PublishMessage");
Self::process_publish_message(program_id, accounts, &p)
}
PostVAA(vaa_body) => {
msg!("Instruction: PostVAA");
let vaa = VAA::deserialize(&vaa_body)?;
Self::process_vaa(program_id, accounts, vaa_body, &vaa)
}
VerifySignatures(p) => {
msg!("Instruction: VerifySignatures");
Self::process_verify_signatures(program_id, accounts, &p)
}
}
}
/// Unpacks a token state from a bytes buffer while assuring that the state is initialized.
pub fn process_initialize(
program_id: &Pubkey,
accounts: &[AccountInfo],
len_guardians: u8,
initial_guardian_key: [[u8; 20]; MAX_LEN_GUARDIAN_KEYS],
config: BridgeConfig,
) -> ProgramResult {
let account_info_iter = &mut accounts.iter();
next_account_info(account_info_iter)?; // System program
let clock_info = next_account_info(account_info_iter)?;
let new_bridge_info = next_account_info(account_info_iter)?;
let new_guardian_info = next_account_info(account_info_iter)?;
let payer_info = next_account_info(account_info_iter)?;
let clock = Clock::from_account_info(clock_info)?;
// Create bridge account
let bridge_seed = Bridge::derive_bridge_seeds();
// Will fail if the account already exists
Bridge::check_and_create_account::<Bridge>(
program_id,
accounts,
new_bridge_info.key,
payer_info,
program_id,
&bridge_seed,
None,
)?;
let mut new_account_data = new_bridge_info.try_borrow_mut_data()?;
let mut bridge: &mut Bridge = Self::unpack(&mut new_account_data)?;
// Create guardian set account
let guardian_seed = Bridge::derive_guardian_set_seeds(new_bridge_info.key, 0);
// Will fail if the account already exists
Bridge::check_and_create_account::<GuardianSet>(
program_id,
accounts,
new_guardian_info.key,
payer_info,
program_id,
&guardian_seed,
None,
)?;
let mut new_guardian_data = new_guardian_info.try_borrow_mut_data().map_err(|_| ProgramError::AccountBorrowFailed)?;
let mut guardian_info: &mut GuardianSet = Self::unpack(&mut new_guardian_data)?;
if len_guardians > MAX_LEN_GUARDIAN_KEYS as u8 {
return Err(ProgramError::InvalidInstructionData);
}
// Initialize bridge params
bridge.guardian_set_index = 0;
bridge.config = config;
// Initialize the initial guardian set
guardian_info.index = 0;
guardian_info.creation_time = clock.unix_timestamp.as_();
guardian_info.keys = initial_guardian_key;
guardian_info.len_keys = len_guardians;
Ok(())
}
/// Processes signature verifications
pub fn process_verify_signatures(
program_id: &Pubkey,
accounts: &[AccountInfo],
payload: &VerifySigPayload,
) -> ProgramResult {
let account_info_iter = &mut accounts.iter();
next_account_info(account_info_iter)?; // Bridge program
next_account_info(account_info_iter)?; // System program
let instruction_accounts = next_account_info(account_info_iter)?;
let bridge_info = Self::next_account_info_with_owner(account_info_iter, program_id)?;
let sig_info = next_account_info(account_info_iter)?;
let guardian_set_info = Self::next_account_info_with_owner(account_info_iter, program_id)?;
let payer_info = next_account_info(account_info_iter)?;
if *instruction_accounts.key != solana_program::sysvar::instructions::id() {
return Err(Error::InvalidSysvar.into());
}
// Verify bridge key because it is used as subsidizer
let expected_bridge_key = Self::derive_bridge_id(program_id)?;
if *bridge_info.key != expected_bridge_key {
return Err(ProgramError::InvalidAccountData);
}
let guardian_data = guardian_set_info.try_borrow_data()?;
let guardian_set: &GuardianSet = Self::unpack_immutable(&guardian_data)?;
let sig_infos: Vec<SigInfo> = payload
.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(
&instruction_accounts.try_borrow_mut_data()?,
);
if current_instruction == 0 {
return Err(ProgramError::InvalidInstructionData);
}
// 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(
secp_ix_index as usize,
&instruction_accounts.try_borrow_mut_data()?,
)
.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(ProgramError::InvalidArgument);
}
let secp_data_len = secp_ix.data.len();
if secp_data_len < 2 {
return Err(ProgramError::InvalidAccountData);
}
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(ProgramError::InvalidArgument);
}
let address: &[u8] = &secp_ix.data[address_offset..address_offset + 20];
let signature: &[u8] = &secp_ix.data[sig_offset..sig_offset + 65];
// 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(ProgramError::InvalidArgument);
}
}
secp_ixs.push(SecpInstructionPart {
address,
signature,
msg_offset,
msg_size,
});
}
if sig_infos.len() != secp_ixs.len() {
return Err(ProgramError::InvalidArgument);
}
// Check message
let message = &secp_ix.data[secp_ixs[0].msg_offset as usize
..(secp_ixs[0].msg_offset + secp_ixs[0].msg_size) as usize];
let mut h = sha3::Keccak256::default();
if let Err(_) = h.write(message) {
return Err(ProgramError::InvalidArgument);
};
let msg_hash: [u8; 32] = h.finalize().into();
if msg_hash != payload.hash {
return Err(ProgramError::InvalidArgument);
}
// Track whether the account needs initialization
let mut initialize_account = false;
// Prepare message/payload-specific sig_info account
if sig_info.data_is_empty() {
let bridge_key = Bridge::derive_bridge_id(program_id)?;
let sig_seeds =
Bridge::derive_signature_seeds(&bridge_key, &msg_hash, guardian_set.index);
Bridge::check_and_create_account::<SignatureState>(
program_id,
accounts,
sig_info.key,
payer_info,
program_id,
&sig_seeds,
Some(bridge_info),
)?;
initialize_account = true;
} else if payload.initial_creation {
return Err(Error::AlreadyExists.into());
}
let mut sig_state_data = sig_info.try_borrow_mut_data()?;
let mut sig_state: &mut SignatureState = Self::unpack(&mut sig_state_data)?;
if initialize_account {
sig_state.guardian_set_index = guardian_set.index;
sig_state.hash = payload.hash;
} else {
// If the account already existed, check that the parameters match
if sig_state.guardian_set_index != guardian_set.index {
return Err(Error::GuardianSetMismatch.into());
}
if sig_state.hash != payload.hash {
return Err(ProgramError::InvalidArgument);
}
}
// Write sigs of checked addresses into sig_state
for s in sig_infos {
if s.signer_index > guardian_set.len_keys {
return Err(ProgramError::InvalidArgument);
}
if s.sig_index + 1 > sig_len {
return Err(ProgramError::InvalidArgument);
}
let key = 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);
}
// Overwritten content should be zeros except double signs by the signer or harmless replays
sig_state.signatures[s.signer_index as usize]
.copy_from_slice(secp_ixs[s.sig_index as usize].signature);
}
Ok(())
}
/// Publish a message to the Wormhole protocol
pub fn process_publish_message(
program_id: &Pubkey,
accounts: &[AccountInfo],
t: &PublishMessagePayload,
) -> ProgramResult {
msg!("publishing message");
let account_info_iter = &mut accounts.iter();
// Unused accounts.
next_account_info(account_info_iter)?; // Bridge program
next_account_info(account_info_iter)?; // System program
next_account_info(account_info_iter)?; // Rent sysvar
// solana_program::sysvar::clock::id() verified by Clock::from_account_info.
let clock_info = next_account_info(account_info_iter)?;
// solana_program::sysvar::instructions::id() verified below.
let instructions_info = next_account_info(account_info_iter)?;
// owner == bridge program asserted by Self::next_account_info_with_owner.
let bridge_info = Self::next_account_info_with_owner(account_info_iter, program_id)?;
// Derived account verified to match the expected pubkey via Bridge::check_and_create_account.
let message_info = next_account_info(account_info_iter)?;
// No need to verify - only used as the fee payer for account creation.
let payer_info = next_account_info(account_info_iter)?;
// The emitter, only used as metadata. We verify that the account is a signer
// to prevent messages from being spoofed.
let emitter_info = next_account_info(account_info_iter)?;
let clock = Clock::from_account_info(clock_info)?;
if *instructions_info.key != solana_program::sysvar::instructions::id() {
return Err(Error::InvalidSysvar.into());
}
// The message emitter must be a signer
if !emitter_info.is_signer {
return Err(Error::EmitterNotSigner.into());
}
// Fee handling
let fee = Self::transfer_fee();
Self::check_fees(instructions_info, bridge_info, fee)?;
// Create transfer account
let message_seed = Bridge::derive_message_seeds(
bridge_info.key,
CHAIN_ID_SOLANA,
emitter_info.key.to_bytes(),
t.nonce,
&t.payload,
);
Bridge::check_and_create_account::<PostedMessage>(
program_id,
accounts,
message_info.key,
payer_info,
program_id,
&message_seed,
None,
)?;
let mut message_data = message_info.try_borrow_mut_data()?;
let mut message: &mut PostedMessage = Self::unpack(&mut message_data)?;
// Initialize transfer
message.submission_time = clock.unix_timestamp as u32;
message.emitter_chain = CHAIN_ID_SOLANA;
message.emitter_address = emitter_info.key.to_bytes();
message.nonce = t.nonce;
message.payload = t.payload;
Ok(())
}
/// Verify that a certain fee was sent to the bridge in the preceding instruction
pub fn check_fees(
instructions_info: &AccountInfo,
bridge_info: &AccountInfo,
fee: u64,
) -> Result<(), ProgramError> {
let current_instruction = solana_program::sysvar::instructions::load_current_index(
&instructions_info.try_borrow_mut_data()?,
);
if current_instruction == 0 {
return Err(ProgramError::InvalidInstructionData);
}
// The previous ix must be a transfer instruction
let transfer_ix_index = (current_instruction - 1) as u8;
let transfer_ix = solana_program::sysvar::instructions::load_instruction_at(
transfer_ix_index as usize,
&instructions_info.try_borrow_mut_data()?,
)
.map_err(|_| ProgramError::InvalidAccountData)?;
// Check that the instruction is actually for the system program
if transfer_ix.program_id != solana_program::system_program::id() {
return Err(ProgramError::InvalidArgument);
}
if transfer_ix.accounts.len() != 2 {
return Err(ProgramError::InvalidInstructionData);
}
// Check that the fee was transferred to the bridge config.
// We only care that the fee was sent to the bridge, not by whom it was sent.
if transfer_ix.accounts[1].pubkey != *bridge_info.key {
return Err(ProgramError::InvalidArgument);
}
// The transfer instruction is serialized using bincode (little endian)
// uint32 ix_type = 2 (Transfer)
// uint64 lamports
// LEN: 4 + 8 = 12 bytes
if transfer_ix.data.len() != 12 {
return Err(ProgramError::InvalidAccountData);
}
// Verify action
if transfer_ix.data[..4] != [2, 0, 0, 0] {
return Err(ProgramError::InvalidInstructionData);
}
// Parse amount
let mut fixed_data = [0u8; 8];
fixed_data.copy_from_slice(&transfer_ix.data[4..]);
let amount = u64::from_le_bytes(fixed_data);
// Verify fee amount
if amount < fee {
return Err(Error::InsufficientFees.into());
}
Ok(())
}
pub fn transfer_sol(
payer_account: &AccountInfo,
recipient_account: &AccountInfo,
amount: u64,
) -> ProgramResult {
let mut payer_balance = payer_account.try_borrow_mut_lamports()?;
**payer_balance = payer_balance
.checked_sub(amount)
.ok_or(ProgramError::InsufficientFunds)?;
let mut recipient_balance = recipient_account.try_borrow_mut_lamports()?;
**recipient_balance = recipient_balance
.checked_add(amount)
.ok_or(ProgramError::InvalidArgument)?;
Ok(())
}
/// Processes a VAA
pub fn process_vaa(
program_id: &Pubkey,
accounts: &[AccountInfo],
vaa_data: VAAData,
vaa: &VAA,
) -> ProgramResult {
let account_info_iter = &mut accounts.iter();
// Load VAA processing default accounts
next_account_info(account_info_iter)?; // Bridge program
next_account_info(account_info_iter)?; // System program
next_account_info(account_info_iter)?; // Rent sysvar
let clock_info = next_account_info(account_info_iter)?;
let bridge_info = Self::next_account_info_with_owner(account_info_iter, program_id)?;
let guardian_set_info = Self::next_account_info_with_owner(account_info_iter, program_id)?;
let claim_info = next_account_info(account_info_iter)?;
let sig_info = Self::next_account_info_with_owner(account_info_iter, program_id)?;
let payer_info = next_account_info(account_info_iter)?;
let clock = Clock::from_account_info(clock_info)?;
let mut guardian_data = guardian_set_info.try_borrow_mut_data()?;
let guardian_set: &mut GuardianSet = Bridge::unpack(&mut guardian_data)?;
// Verify bridge key because it is used as subsidizer and for key derivation
let expected_bridge_key = Self::derive_bridge_id(program_id)?;
if *bridge_info.key != expected_bridge_key {
return Err(ProgramError::InvalidAccountData);
}
// Check that the guardian set is valid
let expected_guardian_set =
Bridge::derive_guardian_set_id(program_id, bridge_info.key, vaa.guardian_set_index)?;
if expected_guardian_set != *guardian_set_info.key {
return Err(Error::InvalidDerivedAccount.into());
}
// Check that the guardian set is still active
if guardian_set.expiration_time != 0
&& (guardian_set.expiration_time as i64) < clock.unix_timestamp
{
return Err(Error::GuardianSetExpired.into());
}
// Verify sig state
let mut sig_state_data = sig_info.try_borrow_mut_data()?;
let sig_state: &SignatureState = Self::unpack(&mut sig_state_data)?;
// Verify that signatures were made using the correct set
if sig_state.guardian_set_index != guardian_set.index {
return Err(Error::GuardianSetMismatch.into());
}
let hash = vaa.body_hash()?;
if sig_state.hash != hash {
return Err(ProgramError::InvalidAccountData);
}
let signature_count = sig_state
.signatures
.iter()
.filter(|v| v.iter().filter(|v| **v != 0).count() != 0)
.count() as u8;
// Check quorum
// We're using a fixed point number transformation with 1 decimal to deal with rounding.
// The cast to u16 exists to prevent issues where len_keys * 10 might overflow.
if (signature_count as u16) < ((((guardian_set.len_keys as u16) * 10 / 3) * 2) / 10 + 1) {
return Err(ProgramError::InvalidArgument);
}
// Process the message posting
Self::process_vaa_message_post(
program_id,
account_info_iter,
bridge_info,
vaa,
sig_info.key,
)?;
// Refund tx fee if possible
if bridge_info
.lamports()
.checked_sub(Self::MIN_BRIDGE_BALANCE)
.unwrap_or(0)
>= Self::VAA_TX_FEE
{
Self::transfer_sol(bridge_info, payer_info, Self::VAA_TX_FEE)?;
}
// Load claim account
let mut claim_data = claim_info.try_borrow_mut_data()?;
let claim: &mut ClaimedVAA = Bridge::unpack(&mut claim_data)?;
// Set claimed
claim.vaa_time = clock.unix_timestamp as u32;
Ok(())
}
/// Processes a Guardian set update
pub fn process_governance_guardian_update(
program_id: &Pubkey,
accounts: &[AccountInfo],
account_info_iter: &mut Iter<AccountInfo>,
clock: &Clock,
bridge_info: &AccountInfo,
payer_info: &AccountInfo,
bridge: &mut Bridge,
old_guardian_set: &mut GuardianSet,
b: &BodyUpdateGuardianSet,
) -> ProgramResult {
let new_guardian_info = next_account_info(account_info_iter)?;
// The new guardian set must be signed by the current one
if bridge.guardian_set_index != old_guardian_set.index {
return Err(Error::OldGuardianSet.into());
}
// The new guardian set must have an index > current
// We don't check +1 because we trust the set to not set something close to max(u32)
if bridge.guardian_set_index + 1 != b.new_index {
return Err(Error::GuardianIndexNotIncreasing.into());
}
// Set the exirity on the old guardian set
old_guardian_set.expiration_time =
(clock.unix_timestamp as u32) + bridge.config.guardian_set_expiration_time;
// Check whether the new guardian set was derived correctly
let guardian_seed = Bridge::derive_guardian_set_seeds(bridge_info.key, b.new_index);
// Will fail if the guardianset already exists
Bridge::check_and_create_account::<GuardianSet>(
program_id,
accounts,
new_guardian_info.key,
payer_info,
program_id,
&guardian_seed,
None,
)?;
let mut guardian_set_new_data = new_guardian_info.try_borrow_mut_data()?;
let guardian_set_new: &mut GuardianSet =
Bridge::unpack(&mut guardian_set_new_data)?;
if b.new_keys.len() == 0 {
return Err(Error::InvalidVAAFormat.into());
}
if b.new_keys.len() > MAX_LEN_GUARDIAN_KEYS {
return Err(Error::InvalidVAAFormat.into());
}
// Force the new guardian set to not expire
guardian_set_new.expiration_time = 0;
guardian_set_new.index = b.new_index;
let mut new_guardians = [[0u8; 20]; MAX_LEN_GUARDIAN_KEYS];
for n in 0..b.new_keys.len() {
new_guardians[n] = b.new_keys[n]
}
guardian_set_new.keys = new_guardians;
guardian_set_new.len_keys = b.new_keys.len() as u8;
guardian_set_new.creation_time = clock.unix_timestamp as u32;
// Update the bridge guardian set id
bridge.guardian_set_index = b.new_index;
Ok(())
}
/// Processes a VAA post for data availability (for Solana -> foreign transfers)
pub fn process_vaa_message_post(
program_id: &Pubkey,
account_info_iter: &mut Iter<AccountInfo>,
bridge_info: &AccountInfo,
vaa: &VAA,
sig_account: &Pubkey,
) -> ProgramResult {
msg!("posting VAA message");
let message_account = Self::next_account_info_with_owner(account_info_iter, program_id)?;
// Check whether the proposal was derived correctly
let expected_message_address = Bridge::derive_message_id(
program_id,
bridge_info.key,
b.emitter_chain,
b.emitter_address,
b.nonce,
&b.data,
)?;
if expected_message_address != *message_account.key {
return Err(Error::InvalidDerivedAccount.into());
}
let mut message_data = message_account.try_borrow_mut_data()?;
let mut message: &mut PostedMessage = Self::unpack(&mut message_data)?;
if !message.matches_vaa(b) {
return Err(Error::VAAMessageMismatch.into());
}
if message.vaa_time != 0 {
return Err(Error::VAAAlreadySubmitted.into());
}
message.vaa_version = vaa.version;
message.vaa_time = vaa.timestamp;
message.vaa_signature_account = *sig_account;
Ok(())
}
/// Processes a VAA contract upgrade
pub fn process_governance_upgrade(
program_id: &Pubkey,
accounts: &[AccountInfo],
bridge_info: &AccountInfo,
b: &BodyContractUpgrade,
) -> ProgramResult {
// Invoke upgrade
let upgrade_ix = solana_program::bpf_loader_upgradeable::upgrade(
program_id,
&b.buffer,
bridge_info.key,
bridge_info.key,
);
Self::invoke_as_bridge(program_id, &upgrade_ix, accounts)?;
Ok(())
}
}
/// Implementation of actions
impl Bridge {
pub fn invoke_as_bridge<'a>(
program_id: &Pubkey,
instruction: &Instruction,
account_infos: &[AccountInfo<'a>],
) -> ProgramResult {
let (_, seeds) =
Self::find_program_address(&vec!["bridge".as_bytes().to_vec()], program_id);
Self::invoke_vec_seed(program_id, instruction, account_infos, &seeds)
}
pub fn invoke_vec_seed<'a>(
_program_id: &Pubkey,
instruction: &Instruction,
account_infos: &[AccountInfo<'a>],
seeds: &Vec<Vec<u8>>,
) -> ProgramResult {
let s: Vec<_> = seeds.iter().map(|item| item.as_slice()).collect();
invoke_signed(instruction, account_infos, &[s.as_slice()])
}
/// The amount of sol that needs to be held in the BridgeConfig account in order to make it
/// exempt of rent payments.
const MIN_BRIDGE_BALANCE: u64 =
(((solana_program::rent::ACCOUNT_STORAGE_OVERHEAD + size_of::<Bridge>() as u64)
* solana_program::rent::DEFAULT_LAMPORTS_PER_BYTE_YEAR) as f64
* solana_program::rent::DEFAULT_EXEMPTION_THRESHOLD) as u64;
/// Check that a key was derived correctly and create account
pub fn check_and_create_account<T: Sized>(
program_id: &Pubkey,
accounts: &[AccountInfo],
new_account: &Pubkey,
payer: &AccountInfo,
owner: &Pubkey,
seeds: &Vec<Vec<u8>>,
subsidizer: Option<&AccountInfo>,
) -> Result<Vec<Vec<u8>>, ProgramError> {
msg!("deriving key");
let (expected_key, full_seeds) = Bridge::derive_key(program_id, seeds)?;
if expected_key != *new_account {
return Err(Error::InvalidDerivedAccount.into());
}
msg!("deploying contract");
Self::create_account_raw::<T>(
program_id,
accounts,
new_account,
payer.key,
owner,
&full_seeds,
)?;
// The subsidizer refunds the rent that needs to be paid to create the account.
// This mechanism is intended to reduce the cost of operating a guardian.
// The subsidizer account should be of the type BridgeConfig and will only pay out
// the subsidy if the account holds at least MIN_BRIDGE_BALANCE+rent
match subsidizer {
None => {}
Some(v) => {
let bal = v.try_lamports()?;
let rent = Rent::default().minimum_balance(size_of::<T>());
if bal
.checked_sub(Self::MIN_BRIDGE_BALANCE)
.ok_or(ProgramError::InsufficientFunds)?
>= rent
{
// Refund rent to payer
Self::transfer_sol(v, payer, rent)?;
}
}
}
Ok(full_seeds)
}
/// Create a new account
fn create_account_raw<T: Sized>(
_program_id: &Pubkey,
accounts: &[AccountInfo],
new_account: &Pubkey,
payer: &Pubkey,
owner: &Pubkey,
seeds: &Vec<Vec<u8>>,
) -> Result<(), ProgramError> {
let size = size_of::<T>();
let ix = create_account(
payer,
new_account,
Rent::default().minimum_balance(size as usize),
size as u64,
owner,
);
let s: Vec<_> = seeds.iter().map(|item| item.as_slice()).collect();
invoke_signed(&ix, accounts, &[s.as_slice()])
}
/// Get the next account info from the iterator and check that it has the given owner
pub fn next_account_info_with_owner<'a, 'b, I: Iterator<Item=&'a AccountInfo<'b>>>(
iter: &mut I,
owner: &Pubkey,
) -> Result<I::Item, ProgramError> {
let acc = iter.next().ok_or(ProgramError::NotEnoughAccountKeys)?;
if acc.owner != owner {
return Err(Error::InvalidOwner.into());
}
Ok(acc)
}
}

View File

@ -1,329 +0,0 @@
//! Bridge transition types
use std::mem::size_of;
use solana_program::{program_error::ProgramError, pubkey::Pubkey};
use zerocopy::AsBytes;
use crate::instruction::MAX_PAYLOAD_SIZE;
use crate::vaa::BodyMessage;
use crate::{
error::Error,
instruction::{ForeignAddress, MAX_LEN_GUARDIAN_KEYS, MAX_VAA_SIZE},
};
use solana_program::rent::Rent;
/// fee rate as a ratio
#[repr(C)]
#[derive(Clone, Copy)]
pub struct Fee {
/// denominator of the fee ratio
pub denominator: u64,
/// numerator of the fee ratio
pub numerator: u64,
}
/// guardian set
#[repr(C)]
#[derive(Clone, Copy)]
pub struct GuardianSet {
/// index of the set
pub index: u32,
/// number of keys stored
pub len_keys: u8,
/// public key hashes of the guardian set
pub keys: [[u8; 20]; MAX_LEN_GUARDIAN_KEYS],
/// creation time
pub creation_time: u32,
/// expiration time when VAAs issued by this set are no longer valid
pub expiration_time: u32,
}
/// record of a posted wormhole message
#[repr(C)]
pub struct PostedMessage {
/// header of the posted VAA
pub vaa_version: 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,
/// emitter of the message
pub emitter_chain: u8,
/// emitter of the message
pub emitter_address: ForeignAddress,
/// message payload
pub payload: [u8; MAX_PAYLOAD_SIZE],
}
impl PostedMessage {
pub fn matches_vaa(&self, b: &BodyMessage) -> bool {
return b.nonce == self.nonce
&& b.emitter_address == self.emitter_address
&& b.emitter_chain == self.emitter_chain
&& b.data == self.payload.to_vec();
}
}
/// record of a claimed VAA
#[repr(C)]
#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub struct ClaimedVAA {
/// hash of the vaa
pub hash: [u8; 32],
/// time the vaa was submitted
pub vaa_time: u32,
}
/// Config for a bridge.
#[repr(C)]
#[derive(Clone, Copy, Debug, Default, PartialEq)]
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,
}
/// Bridge state.
#[repr(C)]
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct Bridge {
/// the currently active guardian set
pub guardian_set_index: u32,
/// read-only config parameters for a bridge instance.
pub config: BridgeConfig,
}
/// Signature state
#[repr(C)]
#[derive(Clone, Copy)]
pub struct SignatureState {
/// signatures of validators
pub signatures: [[u8; 65]; MAX_LEN_GUARDIAN_KEYS],
/// hash of the data
pub hash: [u8; 32],
/// index of the guardian set
pub guardian_set_index: u32,
}
/// Implementation of serialization functions
impl Bridge {
/// Unpacks a state from a bytes buffer.
pub fn unpack<T>(input: &mut [u8]) -> Result<&mut T, ProgramError> {
if input.len() != size_of::<T>() {
return Err(ProgramError::InvalidAccountData);
}
#[allow(clippy::cast_ptr_alignment)]
Ok(unsafe { &mut *(&mut input[0] as *mut u8 as *mut T) })
}
/// Unpacks a state from a bytes buffer.
pub fn unpack_immutable<T>(input: &[u8]) -> Result<&T, ProgramError> {
if input.len() != size_of::<T>() {
return Err(ProgramError::InvalidAccountData);
}
#[allow(clippy::cast_ptr_alignment)]
Ok(unsafe { &*(&input[0] as *const u8 as *const T) })
}
}
/// Implementation of derivations
impl Bridge {
/// Calculates derived seeds for a guardian set
pub fn derive_guardian_set_seeds(bridge_key: &Pubkey, guardian_set_index: u32) -> Vec<Vec<u8>> {
vec![
"guardian".as_bytes().to_vec(),
bridge_key.to_bytes().to_vec(),
guardian_set_index.as_bytes().to_vec(),
]
}
/// Calculates derived seeds for a wrapped asset
pub fn derive_wrapped_asset_seeds(
bridge_key: &Pubkey,
asset_chain: u8,
asset_decimal: u8,
asset: ForeignAddress,
) -> Vec<Vec<u8>> {
vec![
"wrapped".as_bytes().to_vec(),
bridge_key.to_bytes().to_vec(),
asset_chain.as_bytes().to_vec(),
asset_decimal.as_bytes().to_vec(),
asset.as_bytes().to_vec(),
]
}
/// Calculates derived seeds for a transfer out
pub fn derive_message_seeds(
bridge_key: &Pubkey,
emitter_chain: u8,
emitter_address: ForeignAddress,
nonce: u32,
payload: &Vec<u8>,
) -> Vec<Vec<u8>> {
[
vec![
"message".as_bytes().to_vec(),
bridge_key.to_bytes().to_vec(),
nonce.as_bytes().to_vec(),
emitter_chain.as_bytes().to_vec(),
emitter_address.to_vec(),
],
payload.chunks(32).map(|v| v.to_vec()).collect(),
]
.concat()
}
/// Calculates derived seeds for a bridge
pub fn derive_bridge_seeds() -> Vec<Vec<u8>> {
vec!["bridge".as_bytes().to_vec()]
}
/// Calculates derived seeds for a claim
pub fn derive_claim_seeds<'a>(bridge: &Pubkey, body: Vec<u8>) -> Vec<Vec<u8>> {
[
vec!["claim".as_bytes().to_vec(), bridge.to_bytes().to_vec()],
body.chunks(32).map(|v| v.to_vec()).collect(),
]
.concat()
}
/// Calculates derived seeds for a wrapped asset meta entry
pub fn derive_wrapped_meta_seeds<'a>(bridge: &Pubkey, mint: &Pubkey) -> Vec<Vec<u8>> {
vec![
"meta".as_bytes().to_vec(),
bridge.to_bytes().to_vec(),
mint.to_bytes().to_vec(),
]
}
/// Calculates derived seeds for a signature account
pub fn derive_signature_seeds<'a>(
bridge: &Pubkey,
hash: &[u8; 32],
guardian_index: u32,
) -> Vec<Vec<u8>> {
vec![
"sig".as_bytes().to_vec(),
bridge.to_bytes().to_vec(),
hash.to_vec(),
guardian_index.to_le_bytes().to_vec(),
]
}
/// Calculates a derived address for this program
pub fn derive_bridge_id(program_id: &Pubkey) -> Result<Pubkey, Error> {
Ok(Self::derive_key(program_id, &Self::derive_bridge_seeds())?.0)
}
/// Calculates a derived address for a claim account
pub fn derive_claim_id(
program_id: &Pubkey,
bridge: &Pubkey,
body: Vec<u8>,
) -> Result<Pubkey, Error> {
Ok(Self::derive_key(program_id, &Self::derive_claim_seeds(bridge, body))?.0)
}
/// Calculates a derived address for a wrapped asset meta entry
pub fn derive_wrapped_meta_id(
program_id: &Pubkey,
bridge: &Pubkey,
mint: &Pubkey,
) -> Result<Pubkey, Error> {
Ok(Self::derive_key(program_id, &Self::derive_wrapped_meta_seeds(bridge, mint))?.0)
}
/// Calculates a derived address for this program
pub fn derive_guardian_set_id(
program_id: &Pubkey,
bridge_key: &Pubkey,
guardian_set_index: u32,
) -> Result<Pubkey, Error> {
Ok(Self::derive_key(
program_id,
&Self::derive_guardian_set_seeds(bridge_key, guardian_set_index),
)?
.0)
}
/// Calculates a derived address for a transfer out
pub fn derive_message_id(
program_id: &Pubkey,
bridge_key: &Pubkey,
emitter_chain: u8,
emitter_address: ForeignAddress,
nonce: u32,
payload: &Vec<u8>,
) -> Result<Pubkey, Error> {
Ok(Self::derive_key(
program_id,
&Self::derive_message_seeds(bridge_key, emitter_chain, emitter_address, nonce, payload),
)?
.0)
}
/// Calculates derived address for a signature account
pub fn derive_signature_id<'a>(
program_id: &Pubkey,
bridge: &Pubkey,
hash: &[u8; 32],
guardian_index: u32,
) -> Result<Pubkey, Error> {
Ok(Self::derive_key(
program_id,
&Self::derive_signature_seeds(bridge, hash, guardian_index),
)?
.0)
}
pub fn derive_key(
program_id: &Pubkey,
seeds: &Vec<Vec<u8>>,
) -> Result<(Pubkey, Vec<Vec<u8>>), Error> {
Ok(Self::find_program_address(seeds, program_id))
}
pub fn find_program_address(
seeds: &Vec<Vec<u8>>,
program_id: &Pubkey,
) -> (Pubkey, Vec<Vec<u8>>) {
let mut nonce = [255u8];
for _ in 0..std::u8::MAX {
{
let mut seeds_with_nonce = seeds.to_vec();
seeds_with_nonce.push(nonce.to_vec());
let s: Vec<_> = seeds_with_nonce
.iter()
.map(|item| item.as_slice())
.collect();
if let Ok(address) = Pubkey::create_program_address(&s, program_id) {
return (address, seeds_with_nonce);
}
}
nonce[0] -= 1;
}
panic!("Unable to find a viable program address nonce");
}
/// Tx fee of Signature checks and PostVAA (see docs for calculation)
pub const VAA_TX_FEE: u64 = 18 * 10000;
pub fn transfer_fee() -> u64 {
// Pay for 2 signature state and Claimed VAA rents + 2 * guardian tx fees
// This will pay for this transfer and ~10 inbound ones
Rent::default().minimum_balance((size_of::<SignatureState>() + size_of::<ClaimedVAA>()) * 2)
+ Self::VAA_TX_FEE * 2
}
}

View File

@ -1,128 +0,0 @@
use std::io::{Cursor, Read, Write};
use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
use sha3::Digest;
use solana_program::program_error::ProgramError;
use crate::error::Error;
use solana_program::pubkey::Pubkey;
pub type ForeignAddress = [u8; 32];
#[derive(Clone, Debug, Default, PartialEq)]
pub struct VAA {
// Header part
pub version: u8,
pub guardian_set_index: u32,
pub signatures: Vec<Signature>,
// Body part
pub timestamp: u32,
pub nonce: u32,
pub emitter_chain: u8,
pub emitter_address: ForeignAddress,
pub payload: Vec<u8>,
}
#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub struct Signature {
pub index: u8,
pub r: [u8; 32],
pub s: [u8; 32],
pub v: u8,
}
impl VAA {
pub fn new() -> VAA {
return VAA {
version: 0,
guardian_set_index: 0,
signatures: vec![],
timestamp: 0,
emitter_chain: 0,
emitter_address: [0u8; 32],
nonce: 0,
payload: vec![],
};
}
pub fn body_hash(&self) -> Result<[u8; 32], ProgramError> {
let body = match self.signature_body() {
Ok(v) => v,
Err(_) => {
return Err(ProgramError::InvalidArgument);
}
};
let mut h = sha3::Keccak256::default();
if let Err(_) = h.write(body.as_slice()) {
return Err(ProgramError::InvalidArgument);
};
Ok(h.finalize().into())
}
pub fn serialize(&self) -> Result<Vec<u8>, Error> {
let mut v = Cursor::new(Vec::new());
v.write_u8(self.version)?;
v.write_u32::<BigEndian>(self.guardian_set_index)?;
v.write_u8(self.signatures.len() as u8)?;
for s in self.signatures.iter() {
v.write_u8(s.index)?;
v.write(&s.r)?;
v.write(&s.s)?;
v.write_u8(s.v)?;
}
v.write_u32::<BigEndian>(self.timestamp)?;
v.write_u32::<BigEndian>(self.nonce)?;
v.write_u8(self.emitter_chain)?;
v.write(&self.emitter_address)?;
v.write(&self.payload)?;
Ok(v.into_inner())
}
pub fn signature_body(&self) -> Result<Vec<u8>, Error> {
let mut v = Cursor::new(Vec::new());
v.write_u32::<BigEndian>(self.timestamp)?;
v.write_u32::<BigEndian>(self.nonce)?;
v.write_u8(self.emitter_chain)?;
v.write(&self.emitter_address)?;
v.write(&self.payload)?;
Ok(v.into_inner())
}
pub fn deserialize(data: &[u8]) -> Result<VAA, Error> {
let mut rdr = Cursor::new(data);
let mut v = VAA::new();
v.version = rdr.read_u8()?;
v.guardian_set_index = rdr.read_u32::<BigEndian>()?;
let len_sig = rdr.read_u8()?;
let mut sigs: Vec<Signature> = Vec::with_capacity(len_sig as usize);
for _i in 0..len_sig {
let mut sig = Signature::default();
sig.index = rdr.read_u8()?;
rdr.read_exact(&mut sig.r)?;
rdr.read_exact(&mut sig.s)?;
sig.v = rdr.read_u8()?;
sigs.push(sig);
}
v.signatures = sigs;
v.timestamp = rdr.read_u32::<BigEndian>()?;
v.nonce = rdr.read_u32::<BigEndian>()?;
v.emitter_chain = rdr.read_u8()?;
rdr.read_exact(&mut v.emitter_address)?;
rdr.read_to_end(&mut v.payload)?;
Ok(v)
}
}

View File

@ -1 +0,0 @@
target

3230
solana/cli/Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -1,30 +0,0 @@
[package]
name = "cli"
version = "0.1.0"
authors = ["Hendrik Hofstadt <hendrik@nexantic.com>"]
edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
clap = "2.33.0"
solana-clap-utils = { version = "=1.5.5" }
solana-cli-config = { version = "=1.5.5" }
solana-logger = { version = "=1.5.5" }
solana-sdk = { version = "=1.5.5" }
solana-client = { version = "=1.5.5" }
solana-faucet = "=1.5.5"
solana-account-decoder = { version = "=1.5.5" }
spl-token = {version = "=3.0.1", features = ["no-entrypoint"]}
wormhole-bridge = { path = "../bridge", default-features = false, features = ["no-entrypoint"] }
primitive-types = { version = "0.7.2" }
hex = "0.4.2"
thiserror = "1.0.20"
tungstenite = "0.11.1"
serde = "1.0.103"
url = "2.1.1"
serde_bytes = "0.11.5"
log = "0.4.8"
serde_derive = "1.0.103"
serde_json = "1.0.57"
rand = "0.7.3"

View File

@ -1,232 +0,0 @@
use std::{fmt::Display, mem::size_of, net::ToSocketAddrs, ops::Deref, process::exit};
use clap::{
App, AppSettings, Arg, ArgMatches, crate_description, crate_name, crate_version, SubCommand,
value_t, value_t_or_exit,
};
use hex;
use primitive_types::U256;
use rand::{
CryptoRng,
prelude::StdRng,
RngCore, rngs::{mock::StepRng, ThreadRng},
};
use solana_account_decoder::{parse_token::TokenAccountType, UiAccountData};
use solana_clap_utils::{
input_parsers::{keypair_of, pubkey_of, value_of},
input_validators::{is_amount, is_keypair, is_pubkey_or_keypair, is_url},
};
use solana_client::{
rpc_client::RpcClient, rpc_config::RpcSendTransactionConfig, rpc_request::TokenAccountsFilter,
};
use solana_sdk::{
commitment_config::CommitmentConfig,
native_token::*,
pubkey::Pubkey,
signature::{Keypair, read_keypair_file, Signer},
system_instruction,
transaction::Transaction,
};
use solana_sdk::program_pack::Pack;
use spl_token::native_mint;
use spl_token::state::Mint;
use spl_bridge::{instruction::*, state::*};
struct Config {
rpc_client: RpcClient,
fee_payer: Keypair,
commitment_config: CommitmentConfig,
}
type Error = Box<dyn std::error::Error>;
type CommandResult = Result<Option<Transaction>, Error>;
fn command_deploy_bridge(
config: &Config,
bridge: &Pubkey,
initial_guardian: Vec<[u8; 20]>,
) -> CommandResult {
println!("Deploying bridge program {}", bridge);
let minimum_balance_for_rent_exemption = config
.rpc_client
.get_minimum_balance_for_rent_exemption(size_of::<Mint>())?;
let ix = initialize(
bridge,
&config.fee_payer.pubkey(),
initial_guardian,
&BridgeConfig {
guardian_set_expiration_time: 200000000,
},
)?;
println!("bridge: {}, ", ix.accounts[2].pubkey.to_string());
println!("payer: {}, ", ix.accounts[3].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], recent_blockhash);
Ok(Some(transaction))
}
fn check_fee_payer_balance(config: &Config, required_balance: u64) -> Result<(), Error> {
let balance = config.rpc_client.get_balance(&config.fee_payer.pubkey())?;
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(())
}
}
fn main() {
let default_decimals = &format!("{}", native_mint::DECIMALS);
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("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("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"),
))
.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 fee_payer = keypair_of(&matches, "fee_payer").unwrap_or_else(client_keypair);
Config {
rpc_client: RpcClient::new(json_rpc_url),
fee_payer,
commitment_config: CommitmentConfig::processed(),
}
};
solana_logger::setup_with_default("solana=info");
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 mut guardian = [0u8; 20];
guardian.copy_from_slice(&initial_data);
command_deploy_bridge(&config, &bridge, vec![guardian])
}
_ => unreachable!(),
}
.and_then(|transaction| {
if let Some(transaction) = transaction {
// TODO: Upgrade to solana-client 1.3 and
// `send_and_confirm_transaction_with_spinner_and_commitment()` with single
// confirmation by default for better UX
let signature = config
.rpc_client
.send_and_confirm_transaction_with_spinner_and_config(
&transaction,
config.commitment_config,
RpcSendTransactionConfig {
// TODO: move to https://github.com/solana-labs/solana/pull/11792
skip_preflight: true,
preflight_commitment: None,
encoding: None,
},
)?;
println!("Signature: {}", signature);
}
Ok(())
})
.map_err(|err| {
eprintln!("{}", err);
exit(1);
});
}
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))
}

File diff suppressed because it is too large Load Diff

View File

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

View File

@ -8,5 +8,5 @@ edition = "2018"
[dependencies]
solana-sdk = "=1.7.0"
solana-program = "=1.7.0"
solitaire = {path = "../programs/solitaire", features = ["no-entrypoint"]}
solitaire = {path = "../program", features = ["no-entrypoint"]}
borsh = "0.8.1"

View File

@ -17,6 +17,6 @@ default = []
[dependencies]
borsh = "0.8.1"
byteorder = "1.4.3"
rocksalt = { path = "../../rocksalt" }
rocksalt = { path = "../../solitaire/rocksalt" }
sha3 = "0.9.1"
solana-program = "=1.7.0"

View File

@ -5,7 +5,7 @@ imports_granularity = "Crate"
empty_item_single_line = false
# Easier editing when arbitrary mixed use statements do not collapse.
imports_layout = "HorizontalVertical"
imports_layout = "Vertical"
# Default rustfmt formatting of match arms with branches is awful.
match_arm_leading_pipes = "Preserve"