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:
parent
5eb7d0b7d0
commit
aa909c218f
|
@ -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.
|
||||
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,2 +0,0 @@
|
|||
[workspace]
|
||||
members = ["agent", "bridge", "cli"]
|
|
@ -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 .
|
||||
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
target
|
|
@ -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
|
||||
|
|
@ -1,2 +0,0 @@
|
|||
cluster = "localnet"
|
||||
wallet = "/root/.config/solana/id.json"
|
|
@ -1,6 +0,0 @@
|
|||
[workspace]
|
||||
members = [
|
||||
"programs/*",
|
||||
"client/",
|
||||
"solitaire-client",
|
||||
]
|
|
@ -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
|
|
@ -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.
|
||||
}
|
|
@ -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);
|
||||
});
|
||||
});
|
File diff suppressed because it is too large
Load Diff
|
@ -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"]
|
|
@ -1,2 +0,0 @@
|
|||
[target.bpfel-unknown-unknown.dependencies.std]
|
||||
features = []
|
|
@ -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"
|
|
@ -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" }
|
|
@ -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"
|
|
@ -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(())
|
||||
}
|
|
@ -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"
|
||||
}
|
||||
}
|
|
@ -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())
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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;
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -1 +0,0 @@
|
|||
target
|
File diff suppressed because it is too large
Load Diff
|
@ -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"
|
|
@ -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
|
@ -0,0 +1,2 @@
|
|||
[workspace]
|
||||
members = ["rocksalt", "program", "client"]
|
|
@ -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"
|
|
@ -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"
|
|
@ -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"
|
Loading…
Reference in New Issue