add instruction constructors

This commit is contained in:
Hendrik Hofstadt 2020-08-07 16:10:30 +02:00
parent 86dd02e0e6
commit ff0b4766ae
7 changed files with 203 additions and 68 deletions

View File

@ -28,13 +28,11 @@ Parameters:
| ----- | -------- | ------------------- | ------ | --------- | ----- | ------- |
| 0 | sys | SystemProgram | | | | |
| 1 | token_program | SplToken | | | | |
| 2 | clock | Sysvar | | | | ✅ |
| 3 | sender | TokenAccount | | ✅ | | |
| 4 | bridge | BridgeConfig | | | | |
| 5 | proposal | TransferOutProposal | | ✅ | ✅ | ✅ |
| 6 | token | WrappedAsset | | ✅ | | ✅ |
| 7 | payer | Account | ✅ | | | |
| 8-n | sender_owner | Account | ✅ | | | |
| 2 | token_account | TokenAccount | | ✅ | | |
| 3 | bridge | BridgeConfig | | | | |
| 4 | proposal | TransferOutProposal | | ✅ | ✅ | ✅ |
| 5 | token | WrappedAsset | | ✅ | | ✅ |
| 6 | payer | Account | ✅ | | | |
#### TransferOutNative
@ -47,14 +45,12 @@ The transfer proposal will be tracked at a new account `proposal` where a VAA wi
| ----- | --------------- | ------------------- | ------ | --------- | ----- | ------- |
| 0 | sys | SystemProgram | | | | |
| 1 | token_program | SplToken | | | | |
| 2 | clock | Sysvar | | | | ✅ |
| 3 | sender | TokenAccount | | ✅ | | |
| 4 | bridge | BridgeConfig | | | | |
| 5 | proposal | TransferOutProposal | | ✅ | ✅ | ✅ |
| 6 | token | Mint | | ✅ | | |
| 2 | token_account | TokenAccount | | ✅ | | |
| 3 | bridge | BridgeConfig | | | | |
| 4 | proposal | TransferOutProposal | | ✅ | ✅ | ✅ |
| 5 | token | Mint | | ✅ | | |
| 6 | payer | Account | ✅ | | | |
| 7 | custody_account | TokenAccount | | ✅ | opt | ✅ |
| 8 | payer | Account | ✅ | | | |
| 9-n | sender_owner | Account | ✅ | | | |
#### EvictTransferOut
@ -108,9 +104,8 @@ followed by:
| Index | Name | Type | signer | writeable | empty | derived |
| ----- | ------------ | ------------ | ------ | --------- | ----- | ------- |
| 6 | token_program | SplToken | | | | |
| 7 | token | WrappedAsset | | | opt | ✅ |
| 7 | token | WrappedAsset | | | opt | ✅ |
| 8 | destination | TokenAccount | | ✅ | opt | |
| 9 | sender | Account | ✅ | | | |
##### Transfer: Ethereum (wrapped) -> Solana (native)
@ -125,7 +120,6 @@ followed by:
| Index | Name | Type | signer | writeable | empty | derived |
| ----- | ------------ | ------------------- | ------ | --------- | ----- | ------- |
| 6 | token_program | SplToken | | | | |
| 7 | out_proposal | TransferOutProposal | | ✅ | | ✅ |
| 8 | sender | Account | ✅ | | | |

View File

@ -91,6 +91,9 @@ pub enum Error {
/// Invalid transfer with src=dst
#[error("SameChainTransfer")]
SameChainTransfer,
/// VAA is longer than the maximum size
#[error("VAATooLong")]
VAATooLong,
}
impl From<Error> for ProgramError {

View File

@ -35,6 +35,7 @@ impl PrintProgramError for Error {
Error::ExpectedTransferOutProposal => info!("Error: ExpectedTransferOutProposal"),
Error::VAAProposalMismatch => info!("Error: VAAProposalMismatch"),
Error::SameChainTransfer => info!("Error: SameChainTransfer"),
Error::VAATooLong => info!("Error: VAATooLong"),
}
}
}

View File

@ -10,21 +10,23 @@ use solana_sdk::{
pubkey::Pubkey,
};
use crate::error::Error::VAATooLong;
use crate::instruction::BridgeInstruction::Initialize;
use crate::state::{AssetMeta, BridgeConfig};
use crate::state::{AssetMeta, Bridge, BridgeConfig};
use crate::syscalls::RawKey;
use crate::vaa::{VAABody, VAA};
/// chain id of this chain
pub const CHAIN_ID_SOLANA: u8 = 1;
/// size of a VAA in bytes
const VAA_SIZE: usize = 100;
const VAA_SIZE: usize = 200;
/// size of a foreign address in bytes
const FOREIGN_ADDRESS_SIZE: usize = 32;
/// validator payment approval
pub type VAA_BODY = [u8; VAA_SIZE];
/// length-prefixed serialized validator payment approval data
pub type VAAData = [u8; VAA_SIZE];
/// X and Y point of P for guardians
pub type GuardianKey = [u8; 64];
/// address on a foreign chain
@ -50,6 +52,8 @@ pub struct TransferOutPayload {
pub asset: AssetMeta,
/// address on the foreign chain to transfer to
pub target: ForeignAddress,
/// unique nonce of the transfer
pub nonce: u32,
}
/// Instructions supported by the SwapInfo program.
@ -91,7 +95,7 @@ pub enum BridgeInstruction {
/// Submits a VAA signed by `guardian` on a valid `proposal`.
/// See docs for accounts
PostVAA(VAA_BODY),
PostVAA(VAAData),
/// Deletes a `proposal` after the `VAA_EXPIRATION_TIME` is over to free up space on chain.
/// This returns the rent to the sender.
@ -143,7 +147,7 @@ impl BridgeInstruction {
output[0] = 2;
#[allow(clippy::cast_ptr_alignment)]
let value =
unsafe { &mut *(&mut output[size_of::<u8>()] as *mut u8 as *mut VAA_BODY) };
unsafe { &mut *(&mut output[size_of::<u8>()] as *mut u8 as *mut VAAData) };
*value = payload;
}
Self::EvictTransferOut() => {
@ -161,7 +165,6 @@ impl BridgeInstruction {
pub fn initialize(
program_id: &Pubkey,
sender: &Pubkey,
bridge: &Pubkey,
initial_guardian: RawKey,
config: &BridgeConfig,
) -> Result<Instruction, ProgramError> {
@ -171,9 +174,15 @@ pub fn initialize(
})
.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_sdk::system_program::id(), false),
AccountMeta::new_readonly(solana_sdk::sysvar::clock::id(), false),
AccountMeta::new(bridge_key, false),
AccountMeta::new(guardian_set_key, false),
AccountMeta::new(*sender, true),
AccountMeta::new(*bridge, false),
];
Ok(Instruction {
@ -183,6 +192,138 @@ pub fn initialize(
})
}
/// Creates an 'TransferOut' instruction.
pub fn transfer_out(
program_id: &Pubkey,
payer: &Pubkey,
token_account: &Pubkey,
token_mint: &Pubkey,
t: &TransferOutPayload,
) -> Result<Instruction, ProgramError> {
let data = BridgeInstruction::TransferOut(*t).serialize()?;
let bridge_key = Bridge::derive_bridge_id(program_id)?;
let transfer_key = Bridge::derive_transfer_id(
program_id,
&bridge_key,
t.asset.chain,
t.asset.address,
t.chain_id,
t.target,
token_account.to_bytes(),
t.nonce,
)?;
let mut accounts = vec![
AccountMeta::new_readonly(solana_sdk::system_program::id(), false),
AccountMeta::new_readonly(spl_token::id(), false),
AccountMeta::new(*token_account, false),
AccountMeta::new(bridge_key, false),
AccountMeta::new(transfer_key, false),
AccountMeta::new(*token_mint, false),
AccountMeta::new(*payer, true),
];
// If the token is a native solana token add a custody account
if t.asset.chain == CHAIN_ID_SOLANA {
let custody_key = Bridge::derive_custody_id(program_id, &bridge_key, token_mint)?;
accounts.push(AccountMeta::new(custody_key, false));
}
Ok(Instruction {
program_id: *program_id,
accounts,
data,
})
}
/// Creates a 'PostVAA' instruction.
pub fn post_vaa(
program_id: &Pubkey,
payer: &Pubkey,
v: &[u8],
) -> Result<Instruction, ProgramError> {
// VAA must be <= VAA_SIZE-1 to allow for the length prefix
if v.len() > VAA_SIZE - 1 {
return Err(VAATooLong.into());
}
// Convert data to length-prefixed on-chain format
let mut vaa_data: Vec<u8> = vec![];
vaa_data.push(v.len() as u8);
vaa_data.append(&mut v.to_vec());
let mut vaa_chain: [u8; 200] = [0; 200];
vaa_chain.copy_from_slice(vaa_data.as_slice());
let data = BridgeInstruction::PostVAA(vaa_chain).serialize()?;
// 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.body_hash()?)?;
let mut accounts = vec![
AccountMeta::new_readonly(solana_sdk::system_program::id(), false),
AccountMeta::new_readonly(solana_sdk::sysvar::clock::id(), false),
AccountMeta::new(bridge_key, false),
AccountMeta::new(guardian_set_key, false),
AccountMeta::new(claim_key, 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::Transfer(t) => {
if t.source_chain == CHAIN_ID_SOLANA {
// Solana (any) -> Ethereum (any)
let transfer_key = Bridge::derive_transfer_id(
program_id,
&bridge_key,
t.asset.chain,
t.asset.address,
t.target_chain,
t.target_address,
t.source_address,
t.nonce,
)?;
accounts.push(AccountMeta::new(transfer_key, false))
} else if t.asset.chain == CHAIN_ID_SOLANA {
// Foreign (wrapped) -> Solana (native)
let mint_key = Pubkey::new(&t.asset.address);
let custody_key = Bridge::derive_custody_id(program_id, &bridge_key, &mint_key)?;
accounts.push(AccountMeta::new_readonly(spl_token::id(), false));
accounts.push(AccountMeta::new(mint_key, false));
accounts.push(AccountMeta::new(Pubkey::new(&t.target_address), false));
accounts.push(AccountMeta::new(custody_key, false));
} else {
// Foreign (native) -> Solana (wrapped)
let wrapped_key = Bridge::derive_wrapped_asset_id(
program_id,
&bridge_key,
t.asset.chain,
t.asset.address,
)?;
accounts.push(AccountMeta::new_readonly(spl_token::id(), false));
accounts.push(AccountMeta::new(wrapped_key, false));
accounts.push(AccountMeta::new(Pubkey::new(&t.target_address), 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>() {

View File

@ -1,13 +1,11 @@
//! Program instruction processing logic
#![cfg(feature = "program")]
use std::io::Write;
use std::mem::size_of;
use std::slice::Iter;
use num_traits::AsPrimitive;
use primitive_types::U256;
use sha3::Digest;
use solana_sdk::clock::Clock;
#[cfg(not(target_arch = "bpf"))]
use solana_sdk::instruction::Instruction;
@ -24,7 +22,7 @@ use spl_token::state::Mint;
use crate::error::Error;
use crate::instruction::BridgeInstruction::*;
use crate::instruction::{BridgeInstruction, TransferOutPayload, CHAIN_ID_SOLANA};
use crate::instruction::{BridgeInstruction, TransferOutPayload, VAAData, CHAIN_ID_SOLANA};
use crate::state::*;
use crate::syscalls::RawKey;
use crate::vaa::{BodyTransfer, BodyUpdateGuardianSet, VAABody, VAA};
@ -59,13 +57,9 @@ impl Bridge {
let vaa_data = &vaa_body[..len];
let vaa = VAA::deserialize(vaa_data)?;
let mut k = sha3::Keccak256::default();
if let Err(_) = k.write(vaa_data) {
return Err(Error::ParseFailed.into());
};
let hash = k.finalize();
let hash = vaa.body_hash()?;
Self::process_vaa(program_id, accounts, &vaa_body, &vaa, hash.as_ref())
Self::process_vaa(program_id, accounts, vaa_body, &vaa, &hash)
}
_ => panic!(""),
}
@ -88,7 +82,7 @@ impl Bridge {
let clock = Clock::from_account_info(clock_info)?;
// Create bridge account
let bridge_seed = Bridge::derive_bridge_seeds(program_id);
let bridge_seed = Bridge::derive_bridge_seeds();
Bridge::check_and_create_account::<BridgeConfig>(
program_id,
accounts,
@ -142,7 +136,6 @@ impl Bridge {
let account_info_iter = &mut accounts.iter();
next_account_info(account_info_iter)?; // System program
next_account_info(account_info_iter)?; // Token program
let clock_info = next_account_info(account_info_iter)?;
let sender_account_info = next_account_info(account_info_iter)?;
let bridge_info = next_account_info(account_info_iter)?;
let transfer_info = next_account_info(account_info_iter)?;
@ -150,7 +143,6 @@ impl Bridge {
let payer_info = next_account_info(account_info_iter)?;
let authority_info = next_account_info(account_info_iter)?;
let clock = Clock::from_account_info(clock_info)?;
let sender = Bridge::token_account_deserialize(sender_account_info)?;
let bridge = Bridge::bridge_deserialize(bridge_info)?;
let mint = Bridge::mint_deserialize(mint_info)?;
@ -184,7 +176,7 @@ impl Bridge {
t.chain_id,
t.target,
sender.owner.to_bytes(),
clock.slot.as_(), //TODO use nonce
t.nonce,
);
Bridge::check_and_create_account::<TransferOutProposal>(
program_id,
@ -229,16 +221,13 @@ impl Bridge {
let account_info_iter = &mut accounts.iter();
next_account_info(account_info_iter)?; // System program
next_account_info(account_info_iter)?; // Token program
let clock_info = next_account_info(account_info_iter)?;
let sender_account_info = next_account_info(account_info_iter)?;
let bridge_info = next_account_info(account_info_iter)?;
let transfer_info = next_account_info(account_info_iter)?;
let mint_info = next_account_info(account_info_iter)?;
let custody_info = next_account_info(account_info_iter)?;
let payer_info = next_account_info(account_info_iter)?;
let sender_info = next_account_info(account_info_iter)?;
let custody_info = next_account_info(account_info_iter)?;
let clock = Clock::from_account_info(clock_info)?;
let sender = Bridge::token_account_deserialize(sender_account_info)?;
let bridge = Bridge::bridge_deserialize(bridge_info)?;
let mint = Bridge::mint_deserialize(mint_info)?;
@ -260,8 +249,8 @@ impl Bridge {
t.asset.address,
t.chain_id,
t.target,
sender.owner.to_bytes(),
clock.slot.as_(), //TODO use nonce
sender_account_info.key.to_bytes(),
t.nonce,
);
Bridge::check_and_create_account::<TransferOutProposal>(
program_id,
@ -310,7 +299,7 @@ impl Bridge {
&bridge.config.token_program,
sender_account_info.key,
custody_info.key,
sender_info.key,
bridge_info.key,
t.amount,
)?;
@ -333,7 +322,7 @@ impl Bridge {
pub fn process_vaa(
program_id: &Pubkey,
accounts: &[AccountInfo],
vaa_data: &[u8; 100],
vaa_data: VAAData,
vaa: &VAA,
hash: &[u8; 32],
) -> ProgramResult {
@ -508,6 +497,7 @@ impl Bridge {
bridge: &mut Bridge,
b: &BodyTransfer,
) -> ProgramResult {
next_account_info(account_info_iter)?; // Token program
let mint_info = next_account_info(account_info_iter)?;
let destination_info = next_account_info(account_info_iter)?;
@ -578,7 +568,7 @@ impl Bridge {
bridge_info: &AccountInfo,
vaa: &VAA,
b: &BodyTransfer,
vaa_data: &[u8; 100],
vaa_data: VAAData,
) -> ProgramResult {
let proposal_info = next_account_info(account_info_iter)?;
@ -603,7 +593,7 @@ impl Bridge {
}
// Set vaa
proposal.vaa = *vaa_data;
proposal.vaa = vaa_data;
proposal.vaa_time = vaa.timestamp;
Ok(())
@ -671,7 +661,7 @@ impl Bridge {
all_signers.as_slice(),
amount,
)?;
invoke_signed(&ix, accounts, &[])
invoke_signed(&ix, accounts, &[&["bridge".as_bytes()]])
}
/// Mint a wrapped asset to account
@ -691,7 +681,7 @@ impl Bridge {
&[],
amount,
)?;
invoke_signed(&ix, accounts, &[&[&bridge.to_bytes()[..32]][..]])
invoke_signed(&ix, accounts, &[&["bridge".as_bytes()]])
}
/// Transfer tokens from a caller
@ -703,19 +693,15 @@ impl Bridge {
authority: &Pubkey,
amount: U256,
) -> Result<(), ProgramError> {
let all_signers: Vec<&Pubkey> = accounts
.iter()
.filter_map(|item| if item.is_signer { Some(item.key) } else { None })
.collect();
let ix = spl_token::instruction::transfer(
token_program_id,
source,
destination,
authority,
all_signers.as_slice(),
&[],
amount,
)?;
invoke_signed(&ix, accounts, &[])
invoke_signed(&ix, accounts, &[&["bridge".as_bytes()]])
}
/// Transfer tokens from a custody account
@ -735,7 +721,7 @@ impl Bridge {
&[],
amount,
)?;
invoke_signed(&ix, accounts, &[&[&bridge.to_bytes()[..32]][..]])
invoke_signed(&ix, accounts, &[&["bridge".as_bytes()]])
}
/// Create a new account
@ -756,7 +742,7 @@ impl Bridge {
&Self::derive_custody_seeds(bridge, mint),
)?;
let ix = spl_token::instruction::initialize_account(token_program, account, mint, bridge)?;
invoke_signed(&ix, accounts, &[&[&bridge.to_bytes()[..32]][..]])
invoke_signed(&ix, accounts, &[&["bridge".as_bytes()]])
}
/// Create a mint for a wrapped asset
@ -784,7 +770,7 @@ impl Bridge {
U256::from(0),
8,
)?;
invoke_signed(&ix, accounts, &[&[&bridge.to_bytes()[..32]][..]])
invoke_signed(&ix, accounts, &[&["bridge".as_bytes()]])
}
/// Check that a key was derived correctly and create account

View File

@ -2,15 +2,14 @@
use std::mem::size_of;
use primitive_types::U256;
use solana_sdk::{account_info::AccountInfo, program_error::ProgramError, pubkey::Pubkey};
use crate::instruction::{ForeignAddress, VAA_BODY};
use zerocopy::AsBytes;
use crate::error::Error;
use crate::instruction::{ForeignAddress, VAAData};
use crate::syscalls::RawKey;
use crate::vaa::BodyTransfer;
use primitive_types::U256;
use zerocopy::AsBytes;
/// fee rate as a ratio
#[repr(C)]
@ -58,7 +57,7 @@ pub struct TransferOutProposal {
/// asset that is being transferred
pub asset: AssetMeta,
/// vaa to unlock the tokens on the foreign chain
pub vaa: VAA_BODY,
pub vaa: VAAData,
/// time the vaa was submitted
pub vaa_time: u32,
@ -244,8 +243,8 @@ impl Bridge {
}
/// Calculates derived seeds for a bridge
pub fn derive_bridge_seeds(program_id: &Pubkey) -> Vec<Vec<u8>> {
vec![program_id.to_bytes().to_vec()]
pub fn derive_bridge_seeds() -> Vec<Vec<u8>> {
vec!["bridge".as_bytes().to_vec()]
}
/// Calculates derived seeds for a custody account
@ -268,7 +267,7 @@ impl Bridge {
/// Calculates a derived address for this program
pub fn derive_bridge_id(program_id: &Pubkey) -> Result<Pubkey, Error> {
Self::derive_key(program_id, &Self::derive_bridge_seeds(program_id))
Self::derive_key(program_id, &Self::derive_bridge_seeds())
}
/// Calculates a derived address for a custody account

View File

@ -55,6 +55,18 @@ impl VAA {
sol_verify_schnorr(&schnorr_input)
}
pub fn body_hash(&self) -> Result<[u8; 32], Error> {
let body_bytes = self.signature_body()?;
let mut k = sha3::Keccak256::default();
if let Err(_) = k.write(body_bytes.as_slice()) {
return Err(Error::ParseFailed.into());
};
let hash = k.finalize();
return Ok(hash.into());
}
pub fn serialize(&self) -> Result<Vec<u8>, Error> {
let mut v = Cursor::new(Vec::new());
@ -252,7 +264,6 @@ impl BodyTransfer {
#[cfg(test)]
mod tests {
use hex;
use primitive_types::U256;