Fix subsidization and fees (#127)

* Revert "solana: partially revert #82 subsidization changes"

This reverts commit 2967653e

* fix subsidization

* fix deleted grpc tag dependency

* revert devnet changes

* verify system instruction action

* ┬─┬ノ(ಠ_ಠノ)
This commit is contained in:
Hendrik Hofstadt 2020-11-30 11:09:08 +01:00 committed by GitHub
parent 5997f133c3
commit bec598b41a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 26253 additions and 158 deletions

View File

@ -16,7 +16,6 @@ require (
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b
github.com/golang/protobuf v1.4.3
github.com/golang/snappy v0.0.2 // indirect
github.com/googleapis/gnostic v0.5.3 // indirect
github.com/gorilla/websocket v1.4.2
github.com/hashicorp/errwrap v1.1.0 // indirect
github.com/imdario/mergo v0.3.11 // indirect
@ -60,12 +59,11 @@ require (
go.opencensus.io v0.22.5 // indirect
go.uber.org/multierr v1.6.0 // indirect
go.uber.org/zap v1.16.0
golang.org/x/build v0.0.0-20190111050920-041ab4dc3f9d
golang.org/x/crypto v0.0.0-20201012173705-84dcc777aaee
golang.org/x/net v0.0.0-20201016165138-7b1cca2348c0 // indirect
golang.org/x/sys v0.0.0-20201016160150-f659759dc4ca
google.golang.org/genproto v0.0.0-20201019141844-1ed22bb0c154
google.golang.org/grpc v1.33.0
google.golang.org/grpc v1.33.1
google.golang.org/protobuf v1.25.0
k8s.io/api v0.19.4
k8s.io/apimachinery v0.19.4

View File

@ -224,6 +224,7 @@ github.com/edsrzf/mmap-go v0.0.0-20160512033002-935e0e8a636c/go.mod h1:YO35OhQPt
github.com/edsrzf/mmap-go v1.0.0 h1:CEBF7HpRnUCSJgGUb5h1Gm7e3VkmVDrR8lvWVLtrOFw=
github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M=
github.com/elastic/gosigar v0.10.5/go.mod h1:cdorVVzy1fhmEqmtgqkoE3bYtCfSCkVyjTyCIo22xvs=
github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153 h1:yUdfgN0XgIJw7foRItutHYUIhlcKzcSf5vDpdhQAKTc=
github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc=
github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs=
github.com/envoyproxy/go-control-plane v0.6.9/go.mod h1:SBwIajubJHhxtWwsL9s8ss4safvEdbitLhGGK48rN6g=
@ -380,8 +381,6 @@ github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
github.com/googleapis/gnostic v0.4.1 h1:DLJCy1n/vrD4HPjOvYcT8aYQXpPIzoRZONaYwyycI+I=
github.com/googleapis/gnostic v0.4.1/go.mod h1:LRhVm6pbyptWbWbuZ38d1eyptfvIytN3ir6b65WBswg=
github.com/googleapis/gnostic v0.5.3 h1:2qsuRm+bzgwSIKikigPASa2GhW8H2Dn4Qq7UxD8K/48=
github.com/googleapis/gnostic v0.5.3/go.mod h1:TRWw1s4gxBGjSe301Dai3c7wXJAZy57+/6tawkOvqHQ=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
@ -1147,7 +1146,6 @@ github.com/steakknife/bloomfilter v0.0.0-20180922174646-6819c0d2a570 h1:gIlAHnH1
github.com/steakknife/bloomfilter v0.0.0-20180922174646-6819c0d2a570/go.mod h1:8OR4w3TdeIHIh1g6EMY5p0gVNOovcWC+1vpc7naMuAw=
github.com/steakknife/hamming v0.0.0-20180906055917-c99c65617cd3 h1:njlZPzLwU639dk2kqnCPPv+wNjq7Xb6EfUxe/oX0/NM=
github.com/steakknife/hamming v0.0.0-20180906055917-c99c65617cd3/go.mod h1:hpGUWaI9xL8pRQCTXQgocU38Qw1g0Us7n5PxxTwTCYU=
github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8=
github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw=
github.com/streadway/amqp v0.0.0-20190827072141-edfb9018d271/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw=
github.com/streadway/handy v0.0.0-20190108123426-d5acb3125c2a/go.mod h1:qNTQ5P5JnDBl6z3cMAg/SywNDC5ABu5ApDIw6lUbRmI=
@ -1563,8 +1561,8 @@ google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8
google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60=
google.golang.org/grpc v1.28.1/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60=
google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.33.0 h1:IBKSUNL2uBS2DkJBncPP+TwT0sp9tgA8A75NjHt6umg=
google.golang.org/grpc v1.33.0/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0=
google.golang.org/grpc v1.33.1 h1:DGeFlSan2f+WEtCERJ4J9GJWk15TxUi8QGagfI87Xyc=
google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
@ -1623,8 +1621,6 @@ gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 h1:tQIYjPdBoyREyB9XMu+nnTclpTYkz2zFM+lzLJFO4gQ=
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw=
grpc.go4.org v0.0.0-20170609214715-11d0a25b4919/go.mod h1:77eQGdRu53HpSqPFJFmuJdjuHRquDANNeA4x7B8WQ9o=
honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=

View File

@ -58,6 +58,9 @@ Burns a wrapped asset `token` from `sender` on the Solana chain.
The transfer proposal will be tracked at a new account `proposal` where VAAs will be submitted by guardians.
This instruction needs to be preceded by a SOL Transfer instruction that transfers the fee to the BridgeConfig.
The fee can be calculated using the rules explained in the protocol documentation and `Bridge::transfer_fee()`.
Parameters:
| Index | Name | Type | signer | writeable | empty | derived |
@ -66,12 +69,13 @@ Parameters:
| 1 | sys | SystemProgram | | | | |
| 2 | token_program | SplToken | | | | |
| 3 | rent | Sysvar | | | | ✅ |
| 4 | clock | Sysvar | | | ✅ | |
| 5 | token_account | TokenAccount | | ✅ | | |
| 6 | bridge | BridgeConfig | | | | |
| 7 | proposal | TransferOutProposal | | ✅ | ✅ | ✅ |
| 8 | token | WrappedAsset | | ✅ | | ✅ |
| 9 | payer | Account | ✅ | | | |
| 4 | clock | Sysvar | | | | ✅ |
| 5 | instructions | Sysvar | | | | ✅ |
| 6 | token_account | TokenAccount | | ✅ | | |
| 7 | bridge | BridgeConfig | | | | |
| 8 | proposal | TransferOutProposal | | ✅ | ✅ | ✅ |
| 9 | token | WrappedAsset | | ✅ | | ✅ |
| 10 | payer | Account | ✅ | | | |
#### TransferOutNative
@ -80,6 +84,9 @@ Locks a Solana native token (spl-token) `token` from `sender` on the Solana chai
The transfer proposal will be tracked at a new account `proposal` where a VAA will be submitted by guardians.
This instruction needs to be preceded by a SOL Transfer instruction that transfers the fee to the BridgeConfig.
The fee can be calculated using the rules explained in the protocol documentation and `Bridge::transfer_fee()`.
| Index | Name | Type | signer | writeable | empty | derived |
| ----- | --------------- | ------------------- | ------ | --------- | ----- | ------- |
| 0 | bridge_p | BridgeProgram | | | | |
@ -87,12 +94,13 @@ The transfer proposal will be tracked at a new account `proposal` where a VAA wi
| 2 | token_program | SplToken | | | | |
| 3 | rent | Sysvar | | | | ✅ |
| 4 | clock | Sysvar | | | | ✅ |
| 5 | token_account | TokenAccount | | ✅ | | |
| 6 | bridge | BridgeConfig | | | | |
| 7 | proposal | TransferOutProposal | | ✅ | ✅ | ✅ |
| 8 | token | Mint | | ✅ | | |
| 9 | payer | Account | ✅ | | | |
| 10 | custody_account | TokenAccount | | ✅ | opt | ✅ |
| 5 | instructions | Sysvar | | | | ✅ |
| 6 | token_account | TokenAccount | | ✅ | | |
| 7 | bridge | BridgeConfig | | | | |
| 8 | proposal | TransferOutProposal | | ✅ | ✅ | ✅ |
| 9 | token | Mint | | ✅ | | |
| 10 | payer | Account | ✅ | | | |
| 11 | custody_account | TokenAccount | | ✅ | opt | ✅ |
#### EvictTransferOut

View File

@ -103,6 +103,9 @@ pub enum Error {
/// Mismatching guardian set
#[error("GuardianSetMismatch")]
GuardianSetMismatch,
/// Insufficient fees
#[error("InsufficientFees")]
InsufficientFees,
}
impl From<Error> for ProgramError {

View File

@ -39,6 +39,7 @@ impl PrintProgramError for Error {
Error::CannotWrapNative => info!("Error: CannotWrapNative"),
Error::VAAAlreadySubmitted => info!("Error: VAAAlreadySubmitted"),
Error::GuardianSetMismatch => info!("Error: GuardianSetMismatch"),
Error::InsufficientFees => info!("Error: InsufficientFees"),
}
}
}

View File

@ -196,7 +196,7 @@ impl BridgeInstruction {
output.resize(size_of::<InitializePayload>() + 1, 0);
output[0] = 0;
#[allow(clippy::cast_ptr_alignment)]
let value = unsafe {
let value = unsafe {
&mut *(&mut output[size_of::<u8>()] as *mut u8 as *mut InitializePayload)
};
*value = payload;
@ -205,7 +205,7 @@ impl BridgeInstruction {
output.resize(size_of::<TransferOutPayloadRaw>() + 1, 0);
output[0] = 1;
#[allow(clippy::cast_ptr_alignment)]
let value = unsafe {
let value = unsafe {
&mut *(&mut output[size_of::<u8>()] as *mut u8 as *mut TransferOutPayloadRaw)
};
@ -224,7 +224,7 @@ impl BridgeInstruction {
output.resize(1, 0);
output[0] = 2;
#[allow(clippy::cast_ptr_alignment)]
output.extend_from_slice(&payload);
output.extend_from_slice(&payload);
}
Self::EvictTransferOut() => {
output.resize(1, 0);
@ -242,7 +242,7 @@ impl BridgeInstruction {
output.resize(size_of::<VerifySigPayload>() + 1, 0);
output[0] = 6;
#[allow(clippy::cast_ptr_alignment)]
let value = unsafe {
let value = unsafe {
&mut *(&mut output[size_of::<u8>()] as *mut u8 as *mut VerifySigPayload)
};
*value = payload;
@ -251,7 +251,7 @@ impl BridgeInstruction {
output.resize(size_of::<AssetMeta>() + 1, 0);
output[0] = 7;
#[allow(clippy::cast_ptr_alignment)]
let value =
let value =
unsafe { &mut *(&mut output[size_of::<u8>()] as *mut u8 as *mut AssetMeta) };
*value = payload;
}
@ -280,7 +280,7 @@ pub fn initialize(
len_guardians: initial_guardian.len() as u8,
initial_guardian: initial_g,
})
.serialize()?;
.serialize()?;
let bridge_key = Bridge::derive_bridge_id(program_id)?;
let guardian_set_key = Bridge::derive_guardian_set_id(program_id, &bridge_key, 0)?;
@ -329,6 +329,7 @@ pub fn transfer_out(
AccountMeta::new_readonly(spl_token::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(*token_account, false),
AccountMeta::new_readonly(bridge_key, false),
AccountMeta::new(transfer_key, false),
@ -368,6 +369,7 @@ pub fn verify_signatures(
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),
@ -530,6 +532,6 @@ pub fn unpack<T>(input: &[u8]) -> Result<&T, ProgramError> {
return Err(ProgramError::InvalidInstructionData);
}
#[allow(clippy::cast_ptr_alignment)]
let val: &T = unsafe { &*(&input[1] as *const u8 as *const T) };
let val: &T = unsafe { &*(&input[1] as *const u8 as *const T) };
Ok(val)
}

View File

@ -37,9 +37,6 @@ use std::borrow::BorrowMut;
use std::ops::Add;
use solana_program::fee_calculator::FeeCalculator;
/// Tx fee of Signature checks and PostVAA (see docs for calculation)
const VAA_TX_FEE: u64 = 18 * 10000;
/// SigInfo contains metadata about signers in a VerifySignature ix
struct SigInfo {
/// index of the signer in the guardianset
@ -127,9 +124,10 @@ impl Bridge {
program_id,
accounts,
new_bridge_info.key,
payer_info.key,
payer_info,
program_id,
&bridge_seed,
None,
)?;
let mut new_account_data = new_bridge_info.try_borrow_mut_data()?;
@ -144,9 +142,10 @@ impl Bridge {
program_id,
accounts,
new_guardian_info.key,
payer_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)?;
@ -201,6 +200,7 @@ impl Bridge {
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 = next_account_info(account_info_iter)?;
let sig_info = next_account_info(account_info_iter)?;
let guardian_set_info = next_account_info(account_info_iter)?;
let payer_info = next_account_info(account_info_iter)?;
@ -315,9 +315,10 @@ impl Bridge {
program_id,
accounts,
sig_info.key,
payer_info.key,
payer_info,
program_id,
&sig_seeds,
Some(bridge_info),
)?;
} else if payload.initial_creation {
return Err(Error::AlreadyExists.into());
@ -375,6 +376,7 @@ impl Bridge {
next_account_info(account_info_iter)?; // Token program
next_account_info(account_info_iter)?; // Rent sysvar
let clock_info = next_account_info(account_info_iter)?;
let instructions_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)?;
@ -387,6 +389,10 @@ impl Bridge {
let mint = Bridge::mint_deserialize(mint_info)?;
let clock = Clock::from_account_info(clock_info)?;
// Fee handling
let fee = Self::transfer_fee();
Self::check_fees(instructions_info, bridge_info, fee)?;
// Does the token belong to the mint
if sender.mint != *mint_info.key {
return Err(Error::TokenMintMismatch.into());
@ -418,9 +424,10 @@ impl Bridge {
program_id,
accounts,
transfer_info.key,
payer_info.key,
payer_info,
program_id,
&transfer_seed,
None,
)?;
// Load transfer account
@ -469,6 +476,7 @@ impl Bridge {
next_account_info(account_info_iter)?; // Token program
next_account_info(account_info_iter)?; // Rent sysvar
let clock_info = next_account_info(account_info_iter)?;
let instructions_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)?;
@ -482,6 +490,8 @@ impl Bridge {
let bridge: &Bridge = Self::unpack_immutable(&bridge_data)?;
let clock = Clock::from_account_info(clock_info)?;
let fee = Self::transfer_fee();
Self::check_fees(instructions_info, bridge_info, fee)?;
// Does the token belong to the mint
if sender.mint != *mint_info.key {
@ -502,9 +512,10 @@ impl Bridge {
program_id,
accounts,
transfer_info.key,
payer_info.key,
payer_info,
program_id,
&transfer_seed,
None,
)?;
// Load transfer account
@ -527,7 +538,8 @@ impl Bridge {
bridge_info.key,
custody_info.key,
mint_info.key,
payer_info.key,
payer_info,
None,
)?;
}
@ -570,6 +582,77 @@ impl Bridge {
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,
@ -601,17 +684,6 @@ impl Bridge {
return Err(Error::InvalidDerivedAccount.into());
}
// Check and create claim
let claim_seeds = Bridge::derive_claim_seeds(bridge_info.key, vaa.signature_body()?);
Bridge::check_and_create_account::<ClaimedVAA>(
program_id,
accounts,
claim_info.key,
payer_info.key,
program_id,
&claim_seeds,
)?;
// Check that the guardian set is still active
if (guardian_set.expiration_time as i64) > clock.unix_timestamp {
return Err(Error::GuardianSetExpired.into());
@ -643,12 +715,14 @@ impl Bridge {
return Err(ProgramError::InvalidArgument);
}
let mut evict_signatures = false;
let payload = vaa.payload.as_ref().ok_or(Error::InvalidVAAAction)?;
match payload {
VAABody::UpdateGuardianSet(v) => {
let mut bridge_data = bridge_info.try_borrow_mut_data()?;
let bridge: &mut Bridge = Self::unpack(&mut bridge_data)?;
evict_signatures = true;
Self::process_vaa_set_update(
program_id,
accounts,
@ -675,6 +749,7 @@ impl Bridge {
} else {
let bridge_data = bridge_info.try_borrow_data()?;
let bridge: &Bridge = Self::unpack_immutable(&bridge_data)?;
evict_signatures = true;
Self::process_vaa_transfer(
program_id,
accounts,
@ -687,6 +762,29 @@ impl Bridge {
}
}?;
// Check and create claim
let claim_seeds = Bridge::derive_claim_seeds(bridge_info.key, vaa.signature_body()?);
Bridge::check_and_create_account::<ClaimedVAA>(
program_id,
accounts,
claim_info.key,
payer_info,
program_id,
&claim_seeds,
Some(bridge_info),
)?;
// If the signatures are not needed anymore, evict them and reclaim rent.
// This should cover most of the costs of the guardian.
if evict_signatures {
Self::transfer_sol(sig_info, payer_info, sig_info.lamports())?;
}
// 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_unchecked(&mut claim_data)?;
@ -736,9 +834,10 @@ impl Bridge {
program_id,
accounts,
new_guardian_info.key,
payer_info.key,
payer_info,
program_id,
&guardian_seed,
Some(bridge_info),
)?;
let mut guardian_set_new_data = new_guardian_info.try_borrow_mut_data()?;
@ -928,9 +1027,10 @@ impl Bridge {
&bridge.config.token_program,
mint_info.key,
bridge_info.key,
payer_info.key,
payer_info,
&a,
a.decimals,
None,
)?;
// Check and create wrapped asset meta to allow reverse resolution of info
@ -939,9 +1039,10 @@ impl Bridge {
program_id,
accounts,
wrapped_meta_info.key,
payer_info.key,
payer_info,
program_id,
&wrapped_meta_seeds,
None,
)?;
let mut wrapped_meta_data = wrapped_meta_info.try_borrow_mut_data()?;
@ -1046,7 +1147,8 @@ impl Bridge {
bridge: &Pubkey,
account: &Pubkey,
mint: &Pubkey,
payer: &Pubkey,
payer: &AccountInfo,
subsidizer: Option<&AccountInfo>,
) -> Result<(), ProgramError> {
Self::check_and_create_account::<[u8; spl_token::state::Account::LEN]>(
program_id,
@ -1055,6 +1157,7 @@ impl Bridge {
payer,
token_program,
&Self::derive_custody_seeds(bridge, mint),
subsidizer,
)?;
info!(token_program.to_string().as_str());
let ix = spl_token::instruction::initialize_account(
@ -1073,9 +1176,10 @@ impl Bridge {
token_program: &Pubkey,
mint: &Pubkey,
bridge: &Pubkey,
payer: &Pubkey,
payer: &AccountInfo,
asset: &AssetMeta,
decimals: u8,
subsidizer: Option<&AccountInfo>,
) -> Result<(), ProgramError> {
Self::check_and_create_account::<[u8; spl_token::state::Mint::LEN]>(
program_id,
@ -1084,6 +1188,7 @@ impl Bridge {
payer,
token_program,
&Self::derive_wrapped_asset_seeds(bridge, asset.chain, asset.decimals, asset.address),
subsidizer,
)?;
let ix = spl_token::instruction::initialize_mint(
token_program,
@ -1115,14 +1220,21 @@ impl Bridge {
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::<BridgeConfig>() 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: &Pubkey,
payer: &AccountInfo,
owner: &Pubkey,
seeds: &Vec<Vec<u8>>,
subsidizer: Option<&AccountInfo>,
) -> Result<Vec<Vec<u8>>, ProgramError> {
info!("deriving key");
let (expected_key, full_seeds) = Bridge::derive_key(program_id, seeds)?;
@ -1135,11 +1247,27 @@ impl Bridge {
program_id,
accounts,
new_account,
payer,
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)
}

View File

@ -12,6 +12,7 @@ use crate::{
vaa::BodyTransfer,
};
use solana_program::program_pack::Pack;
use solana_program::rent::Rent;
/// fee rate as a ratio
#[repr(C)]
@ -233,7 +234,7 @@ impl Bridge {
return Err(ProgramError::InvalidAccountData);
}
#[allow(clippy::cast_ptr_alignment)]
Ok(unsafe { &mut *(&mut input[0] as *mut u8 as *mut T) })
Ok(unsafe { &mut *(&mut input[0] as *mut u8 as *mut T) })
}
/// Unpacks a state from a bytes buffer while assuring that the state is initialized.
@ -251,7 +252,7 @@ impl Bridge {
return Err(ProgramError::InvalidAccountData);
}
#[allow(clippy::cast_ptr_alignment)]
Ok(unsafe { &*(&input[0] as *const u8 as *const T) })
Ok(unsafe { &*(&input[0] as *const u8 as *const T) })
}
}
@ -324,7 +325,7 @@ impl Bridge {
vec!["claim".as_bytes().to_vec(), bridge.to_bytes().to_vec()],
body.chunks(32).map(|v| v.to_vec()).collect(),
]
.concat()
.concat()
}
/// Calculates derived seeds for a wrapped asset meta entry
@ -392,7 +393,7 @@ impl Bridge {
program_id,
&Self::derive_guardian_set_seeds(bridge_key, guardian_set_index),
)?
.0)
.0)
}
/// Calculates a derived seeds for a wrapped asset
@ -407,7 +408,7 @@ impl Bridge {
program_id,
&Self::derive_wrapped_asset_seeds(bridge_key, asset_chain, asset_decimal, asset),
)?
.0)
.0)
}
/// Calculates a derived address for a transfer out
@ -433,7 +434,7 @@ impl Bridge {
slot,
),
)?
.0)
.0)
}
/// Calculates derived address for a signature account
@ -447,7 +448,7 @@ impl Bridge {
program_id,
&Self::derive_signature_seeds(bridge, hash, guardian_index),
)?
.0)
.0)
}
pub fn derive_key(
@ -478,6 +479,15 @@ impl Bridge {
}
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
}
}
/// Check is a token state is initialized

View File

@ -190,6 +190,7 @@ fn command_lock_tokens(
&[],
amount,
)?,
system_instruction::transfer(&config.owner.pubkey(), &bridge_key, Bridge::transfer_fee()),
transfer_out(
bridge,
&config.owner.pubkey(),
@ -207,7 +208,7 @@ fn command_lock_tokens(
println!(
"custody: {}, ",
instructions[1].accounts[8].pubkey.to_string()
instructions[2].accounts[8].pubkey.to_string()
);
let mut transaction =
@ -232,7 +233,7 @@ fn check_fee_payer_balance(config: &Config, required_balance: u64) -> Result<(),
lamports_to_sol(required_balance),
lamports_to_sol(balance)
)
.into())
.into())
} else {
Ok(())
}
@ -247,7 +248,7 @@ fn check_owner_balance(config: &Config, required_balance: u64) -> Result<(), Err
lamports_to_sol(required_balance),
lamports_to_sol(balance)
)
.into())
.into())
} else {
Ok(())
}
@ -1313,31 +1314,31 @@ fn main() {
}
_ => 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);
});
.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);
});
}
fn keypair_from_seed_arg(arg_matches: &ArgMatches) -> Keypair {
@ -1352,8 +1353,8 @@ fn keypair_from_seed_arg(arg_matches: &ArgMatches) -> Keypair {
}
pub fn is_u8<T>(amount: T) -> Result<(), String>
where
T: AsRef<str> + Display,
where
T: AsRef<str> + Display,
{
if amount.as_ref().parse::<u8>().is_ok() {
Ok(())
@ -1366,8 +1367,8 @@ where
}
pub fn is_u32<T>(amount: T) -> Result<(), String>
where
T: AsRef<str> + Display,
where
T: AsRef<str> + Display,
{
if amount.as_ref().parse::<u32>().is_ok() {
Ok(())
@ -1380,8 +1381,8 @@ where
}
pub fn is_hex<T>(value: T) -> Result<(), String>
where
T: AsRef<str> + Display,
where
T: AsRef<str> + Display,
{
hex::decode(value.to_string())
.map(|_| ())

26047
web/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -5,7 +5,7 @@
"dependencies": {
"@project-serum/sol-wallet-adapter": "^0.1.1",
"@solana/spl-token": "^0.0.11",
"@solana/web3.js": "^0.80.2",
"@solana/web3.js": "^0.87.1",
"@testing-library/jest-dom": "^4.2.4",
"@testing-library/react": "^9.3.2",
"@testing-library/user-event": "^7.1.2",

View File

@ -192,11 +192,17 @@ function Assistant() {
decimals: transferData.fromCoinInfo.decimals,
}, Math.random() * 100000);
let ix = spl.Token.createApproveInstruction(TOKEN_PROGRAM, new PublicKey(transferData.fromCoinInfo.address), await bridge.getConfigKey(), k.publicKey, [], transferData.amount.toNumber())
let bridge_account = await bridge.getConfigKey();
let fee_ix = solanaWeb3.SystemProgram.transfer({
fromPubkey: k.publicKey,
toPubkey: bridge_account,
lamports: await bridge.getTransferFee()
});
let recentHash = await c.getRecentBlockhash();
let tx = new Transaction();
tx.recentBlockhash = recentHash.blockhash
tx.add(ix)
tx.add(fee_ix)
tx.add(lock_ix)
tx.sign(k)
try {

View File

@ -78,11 +78,18 @@ function TransferSolana() {
decimals: Math.min(coinInfo.decimals, 9)
}, Math.random() * 100000);
let ix = spl.Token.createApproveInstruction(TOKEN_PROGRAM, fromAccount, await bridge.getConfigKey(), k.publicKey, [], transferAmount.toNumber())
let bridge_account = await bridge.getConfigKey();
let fee_ix = solanaWeb3.SystemProgram.transfer({
fromPubkey: k.publicKey,
toPubkey: bridge_account,
lamports: await bridge.getTransferFee()
});
let recentHash = await c.getRecentBlockhash();
let tx = new Transaction();
tx.recentBlockhash = recentHash.blockhash
tx.add(ix)
tx.add(fee_ix)
tx.add(lock_ix)
tx.sign(k)
try {

View File

@ -105,6 +105,7 @@ class SolanaBridge {
{pubkey: this.tokenProgram, isSigner: false, isWritable: false},
{pubkey: solanaWeb3.SYSVAR_RENT_PUBKEY, isSigner: false, isWritable: false},
{pubkey: solanaWeb3.SYSVAR_CLOCK_PUBKEY, isSigner: false, isWritable: false},
{pubkey: solanaWeb3.SYSVAR_INSTRUCTIONS_PUBKEY, isSigner: false, isWritable: false},
{pubkey: tokenAccount, isSigner: false, isWritable: true},
{pubkey: configKey, isSigner: false, isWritable: false},
@ -418,6 +419,11 @@ class SolanaBridge {
// @ts-ignore
return (await solanaWeb3.PublicKey.findProgramAddress(seeds, this.programID))[0];
}
async getTransferFee(): Promise<number> {
// Reference processor.rs::Bridge::transfer_fee
return (await this.connection.getMinimumBalanceForRentExemption((37 + 1337) * 2)) + 18 * 10000 * 2
}
}
// Taken from https://github.com/solana-labs/solana-program-library