subsidize guardian transactions using fees (#82)

* subsidize guardian transactions using fees

* reuse transfer function

* evict signature state on inbound transfers

* fix mutability issues due to copying

* add fee refund

* unify fee calculation

* add fee documentation

* Unflip tables

* type annotation
This commit is contained in:
Hendrik Hofstadt 2020-11-19 22:47:09 +01:00 committed by GitHub
parent ee5d07c929
commit 8510140165
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 216 additions and 66 deletions

View File

@ -217,7 +217,80 @@ The user can then get the VAA from the `LockProposal` and submit it on the forei
### Fees ### Fees
TODO \o/ Fees exist for 2 reasons: spam prevention and guardian cost coverage.
Costs for guardians:
Assuming no hosting costs for a guardian operation (blockchain and guardian nodes), the only costs
that need to be covered by a guardian operator are Solana transaction fees as well as rent costs for newly
created account (used to store application information).
**For a transfer from Solana to a foreign chain (20 guardians; 14 quorum):**
Transactions required: `3 (signatures + verify) + 1 (post VAA)`
Accounts created: `1 ClaimedVAA + 1 SignatureState`
Costs:
```
4 TX (14 secp signatures + 4 ed25519) + ClaimedVAA (exemption rent) + SignatureState (exemption rent)
18 * 10_000 + (36+128) * 6962 + (1337+128) * 6962
11521098 lamports = 0.0115 SOL
```
**For a transfer from a foreign chain to Solana (20 guardians; 14 quorum):**
Transactions required: `3 (signatures + verify) + 1 (post VAA)`
Accounts created: `1 ClaimedVAA + 1 SignatureState (temporary; evicted in PostVAA)`
Costs:
```
4 TX (14 secp signatures + 4 ed25519) + ClaimedVAA (exemption rent)
18 * 10_000 + (36+128) * 6962
1321768 lamports = 0.0013 SOL
```
---
In order to cover rent costs there exists a subsidy pool controlled by the bridge to cover rent payments.
While the guardian needs to hold enough SOL to pay for the rent, it is automatically refunded by the pool,
in case the pool has sufficient balance.
This subsidy pool is funded by transaction fees.
Additionally, the subsidy pool subsidizes the transactions fees paid by the guardian submitting the VAA.
As long as the pool has a sufficient balance, it will try to refund transaction fees to the guardian.
Since Wormhole does not require foreign chain users to own SOL, Wormhole can't charge subsidy fees on inbound
transfers. Assuming a balance between inbound and outbound transfers, outbound transfers need to subsidize
inbound Solana transfers.
Additionally, foreign chain contracts might start charging additional fees in the future.
---
The bridge can handle at most <TODO> transactions per second. Therefore, the fees should prevent spam
by dynamically adjusting to load. This is particularly useful on Solana where fees are low and spamming
would be cheap.
Dynamic fees should be cheap while the system is under low and medium load and high while the system is
close or above its capacity.
To prevent sudden fee changes, the fee system has inertia.
Fees scale as follows `fee = (tps/tps_max)^6`.
The result is the fee per transfer in SOL. So at max capacity, the price per transfer is 1SOL.
TPS is measured over a 30 second window.
The minimum fee is the equivalent of 2x the rent of SignatureState and ClaimedVAA to cover the cost
of this transfer and about 10 inbound transfers.
---
The above design can currently not be implemented due to limitations in the Solana BPF VM.
In the current design, tx fees are refunded, rents are subsidized by the bridge and transfers out of Solana
cost a fixed fee of 2x (ClaimedVAA rent + SignatureState rent + VAA submission fee), which will roughly
pay for 1 outbound + ~10 inbound transfers.
### Config changes ### Config changes
#### Guardian set changes #### Guardian set changes

View File

@ -12,7 +12,7 @@ Initializes a new Bridge at `bridge`.
| ----- | ------ | ------------ | ------ | --------- | ----- | ------- | | ----- | ------ | ------------ | ------ | --------- | ----- | ------- |
| 0 | sys | SystemProgram | | | | | | 0 | sys | SystemProgram | | | | |
| 1 | clock | Sysvar | | | | ✅ | | 1 | clock | Sysvar | | | | ✅ |
| 2 | bridge | BridgeConfig | | | ✅ | ✅ | | 2 | bridge | BridgeConfig | | | ✅ | ✅ |
| 3 | guardian_set | GuardianSet | | ✅ | ✅ | ✅ | | 3 | guardian_set | GuardianSet | | ✅ | ✅ | ✅ |
| 4 | payer | Account | ✅ | | | | | 4 | payer | Account | ✅ | | | |
@ -47,9 +47,10 @@ Checks secp checks (in the previous instruction) and stores results.
| 0 | bridge_p | BridgeProgram | | | | | | 0 | bridge_p | BridgeProgram | | | | |
| 1 | sys | SystemProgram | | | | | | 1 | sys | SystemProgram | | | | |
| 2 | instructions | Sysvar | | | | ✅ | | 2 | instructions | Sysvar | | | | ✅ |
| 3 | sig_status | SignatureState | | ✅ | | | | 3 | bridge_config | BridgeConfig | | ✅ | | ✅ |
| 4 | guardian_set | GuardianSet | | | | ✅ | | 4 | sig_status | SignatureState | | ✅ | | |
| 5 | payer | Account | ✅ | | | | | 5 | guardian_set | GuardianSet | | | | ✅ |
| 6 | payer | Account | ✅ | | | |
#### TransferOut #### TransferOut
@ -131,10 +132,10 @@ All require:
| 1 | sys | SystemProgram | | | | | | 1 | sys | SystemProgram | | | | |
| 2 | rent | Sysvar | | | | ✅ | | 2 | rent | Sysvar | | | | ✅ |
| 3 | clock | Sysvar | | | | ✅ | | 3 | clock | Sysvar | | | | ✅ |
| 4 | bridge | BridgeConfig | | | | | | 4 | bridge | BridgeConfig | | | | |
| 5 | guardian_set | GuardianSet | | | | | | 5 | guardian_set | GuardianSet | | | | |
| 6 | claim | ExecutedVAA | | ✅ | ✅ | ✅ | | 6 | claim | ExecutedVAA | | ✅ | ✅ | ✅ |
| 7 | sig_info | SigState | | | ✅ | | | 7 | sig_info | SigState | | | ✅ | |
| 8 | payer | Account | ✅ | | | | | 8 | payer | Account | ✅ | | | |
followed by: followed by:

View File

@ -330,7 +330,7 @@ pub fn transfer_out(
AccountMeta::new_readonly(solana_program::sysvar::rent::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::clock::id(), false),
AccountMeta::new(*token_account, false), AccountMeta::new(*token_account, false),
AccountMeta::new(bridge_key, false), AccountMeta::new_readonly(bridge_key, false),
AccountMeta::new(transfer_key, false), AccountMeta::new(transfer_key, false),
AccountMeta::new(*token_mint, false), AccountMeta::new(*token_mint, false),
AccountMeta::new(*payer, true), AccountMeta::new(*payer, true),
@ -368,6 +368,7 @@ pub fn verify_signatures(
AccountMeta::new_readonly(*program_id, false), AccountMeta::new_readonly(*program_id, false),
AccountMeta::new_readonly(solana_program::system_program::id(), false), AccountMeta::new_readonly(solana_program::system_program::id(), false),
AccountMeta::new_readonly(solana_program::sysvar::instructions::id(), false), AccountMeta::new_readonly(solana_program::sysvar::instructions::id(), false),
AccountMeta::new(bridge_key, false),
AccountMeta::new(*signature_acc, false), AccountMeta::new(*signature_acc, false),
AccountMeta::new_readonly(guardian_set_key, false), AccountMeta::new_readonly(guardian_set_key, false),
AccountMeta::new(*payer, true), AccountMeta::new(*payer, true),
@ -494,7 +495,7 @@ pub fn create_wrapped(
AccountMeta::new_readonly(solana_program::system_program::id(), false), AccountMeta::new_readonly(solana_program::system_program::id(), false),
AccountMeta::new_readonly(spl_token::id(), false), AccountMeta::new_readonly(spl_token::id(), false),
AccountMeta::new_readonly(solana_program::sysvar::rent::id(), false), AccountMeta::new_readonly(solana_program::sysvar::rent::id(), false),
AccountMeta::new(bridge_key, false), AccountMeta::new_readonly(bridge_key, false),
AccountMeta::new(*payer, true), AccountMeta::new(*payer, true),
AccountMeta::new(wrapped_mint_key, false), AccountMeta::new(wrapped_mint_key, false),
AccountMeta::new(wrapped_meta_key, false), AccountMeta::new(wrapped_meta_key, false),

View File

@ -35,6 +35,10 @@ use crate::{
use solana_program::program_pack::Pack; use solana_program::program_pack::Pack;
use std::borrow::BorrowMut; use std::borrow::BorrowMut;
use std::ops::Add; 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 /// SigInfo contains metadata about signers in a VerifySignature ix
struct SigInfo { struct SigInfo {
@ -123,12 +127,13 @@ impl Bridge {
program_id, program_id,
accounts, accounts,
new_bridge_info.key, new_bridge_info.key,
payer_info.key, payer_info,
program_id, program_id,
&bridge_seed, &bridge_seed,
None,
)?; )?;
let mut new_account_data = new_bridge_info.try_borrow_mut_data()?; let mut new_account_data = new_bridge_info.try_borrow_mut_data().map_err(|_| ProgramError::AccountBorrowFailed)?;
let mut bridge: &mut Bridge = Self::unpack_unchecked(&mut new_account_data)?; let mut bridge: &mut Bridge = Self::unpack_unchecked(&mut new_account_data)?;
if bridge.is_initialized { if bridge.is_initialized {
return Err(Error::AlreadyExists.into()); return Err(Error::AlreadyExists.into());
@ -140,12 +145,13 @@ impl Bridge {
program_id, program_id,
accounts, accounts,
new_guardian_info.key, new_guardian_info.key,
payer_info.key, payer_info,
program_id, program_id,
&guardian_seed, &guardian_seed,
None,
)?; )?;
let mut new_guardian_data = new_guardian_info.try_borrow_mut_data()?; let mut new_guardian_data = new_guardian_info.try_borrow_mut_data().map_err(|_| ProgramError::AccountBorrowFailed)?;
let mut guardian_info: &mut GuardianSet = Self::unpack_unchecked(&mut new_guardian_data)?; let mut guardian_info: &mut GuardianSet = Self::unpack_unchecked(&mut new_guardian_data)?;
if guardian_info.is_initialized { if guardian_info.is_initialized {
return Err(Error::AlreadyExists.into()); return Err(Error::AlreadyExists.into());
@ -197,11 +203,13 @@ impl Bridge {
next_account_info(account_info_iter)?; // Bridge program next_account_info(account_info_iter)?; // Bridge program
next_account_info(account_info_iter)?; // System program next_account_info(account_info_iter)?; // System program
let instruction_accounts = next_account_info(account_info_iter)?; 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 sig_info = next_account_info(account_info_iter)?;
let guardian_set_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)?; let payer_info = next_account_info(account_info_iter)?;
let guardian_set: GuardianSet = Self::guardian_set_deserialize(guardian_set_info)?; let guardian_data = guardian_set_info.data.try_borrow().map_err(|_| ProgramError::AccountBorrowFailed)?;
let guardian_set: &GuardianSet = Self::unpack_immutable(&guardian_data)?;
let sig_infos: Vec<SigInfo> = payload let sig_infos: Vec<SigInfo> = payload
.signers .signers
@ -310,9 +318,10 @@ impl Bridge {
program_id, program_id,
accounts, accounts,
sig_info.key, sig_info.key,
payer_info.key, payer_info,
program_id, program_id,
&sig_seeds, &sig_seeds,
Some(bridge_info),
)?; )?;
} else if payload.initial_creation { } else if payload.initial_creation {
return Err(Error::AlreadyExists.into()); return Err(Error::AlreadyExists.into());
@ -377,10 +386,15 @@ impl Bridge {
let payer_info = next_account_info(account_info_iter)?; let payer_info = next_account_info(account_info_iter)?;
let sender = Bridge::token_account_deserialize(sender_account_info)?; let sender = Bridge::token_account_deserialize(sender_account_info)?;
let bridge = Bridge::bridge_deserialize(bridge_info)?; let bridge_data = bridge_info.data.try_borrow().map_err(|_| ProgramError::AccountBorrowFailed)?;
let bridge: &Bridge = Self::unpack_immutable(&bridge_data)?;
let mint = Bridge::mint_deserialize(mint_info)?; let mint = Bridge::mint_deserialize(mint_info)?;
let clock = Clock::from_account_info(clock_info)?; let clock = Clock::from_account_info(clock_info)?;
// Fee handling
let fee = Self::transfer_fee();
Self::transfer_sol(payer_info, bridge_info, fee)?;
// Does the token belong to the mint // Does the token belong to the mint
if sender.mint != *mint_info.key { if sender.mint != *mint_info.key {
return Err(Error::TokenMintMismatch.into()); return Err(Error::TokenMintMismatch.into());
@ -412,9 +426,10 @@ impl Bridge {
program_id, program_id,
accounts, accounts,
transfer_info.key, transfer_info.key,
payer_info.key, payer_info,
program_id, program_id,
&transfer_seed, &transfer_seed,
None,
)?; )?;
// Load transfer account // Load transfer account
@ -472,9 +487,14 @@ impl Bridge {
let sender = Bridge::token_account_deserialize(sender_account_info)?; let sender = Bridge::token_account_deserialize(sender_account_info)?;
let mint = Bridge::mint_deserialize(mint_info)?; let mint = Bridge::mint_deserialize(mint_info)?;
let bridge = Bridge::bridge_deserialize(bridge_info)?; let bridge_data = bridge_info.data.try_borrow().map_err(|_| ProgramError::AccountBorrowFailed)?;
let bridge: &Bridge = Self::unpack_immutable(&bridge_data)?;
let clock = Clock::from_account_info(clock_info)?; let clock = Clock::from_account_info(clock_info)?;
// Fee handling
let fee = Self::transfer_fee();
Self::transfer_sol(payer_info, bridge_info, fee)?;
// Does the token belong to the mint // Does the token belong to the mint
if sender.mint != *mint_info.key { if sender.mint != *mint_info.key {
return Err(Error::TokenMintMismatch.into()); return Err(Error::TokenMintMismatch.into());
@ -494,9 +514,10 @@ impl Bridge {
program_id, program_id,
accounts, accounts,
transfer_info.key, transfer_info.key,
payer_info.key, payer_info,
program_id, program_id,
&transfer_seed, &transfer_seed,
None,
)?; )?;
// Load transfer account // Load transfer account
@ -519,7 +540,8 @@ impl Bridge {
bridge_info.key, bridge_info.key,
custody_info.key, custody_info.key,
mint_info.key, mint_info.key,
payer_info.key, payer_info,
None,
)?; )?;
} }
@ -562,6 +584,25 @@ impl Bridge {
Ok(()) Ok(())
} }
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) + VAA_TX_FEE * 2
}
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 /// Processes a VAA
pub fn process_vaa( pub fn process_vaa(
program_id: &Pubkey, program_id: &Pubkey,
@ -582,9 +623,11 @@ impl Bridge {
let sig_info = next_account_info(account_info_iter)?; let sig_info = next_account_info(account_info_iter)?;
let payer_info = next_account_info(account_info_iter)?; let payer_info = next_account_info(account_info_iter)?;
let mut bridge = Bridge::bridge_deserialize(bridge_info)?; let mut bridge_data = bridge_info.data.try_borrow_mut().map_err(|_| ProgramError::AccountBorrowFailed)?;
let bridge: &mut Bridge = Self::unpack(&mut bridge_data)?;
let clock = Clock::from_account_info(clock_info)?; let clock = Clock::from_account_info(clock_info)?;
let mut guardian_set = Bridge::guardian_set_deserialize(guardian_set_info)?; let mut guardian_data = guardian_set_info.data.try_borrow_mut().map_err(|_| ProgramError::AccountBorrowFailed)?;
let guardian_set: &mut GuardianSet = Bridge::unpack(&mut guardian_data)?;
// Check that the guardian set is valid // Check that the guardian set is valid
let expected_guardian_set = let expected_guardian_set =
@ -599,9 +642,10 @@ impl Bridge {
program_id, program_id,
accounts, accounts,
claim_info.key, claim_info.key,
payer_info.key, payer_info,
program_id, program_id,
&claim_seeds, &claim_seeds,
Some(bridge_info),
)?; )?;
// Check that the guardian set is still active // Check that the guardian set is still active
@ -635,19 +679,23 @@ impl Bridge {
return Err(ProgramError::InvalidArgument); return Err(ProgramError::InvalidArgument);
} }
let mut evict_signatures = false;
let payload = vaa.payload.as_ref().ok_or(Error::InvalidVAAAction)?; let payload = vaa.payload.as_ref().ok_or(Error::InvalidVAAAction)?;
match payload { match payload {
VAABody::UpdateGuardianSet(v) => Self::process_vaa_set_update( VAABody::UpdateGuardianSet(v) => {
evict_signatures = true;
Self::process_vaa_set_update(
program_id, program_id,
accounts, accounts,
account_info_iter, account_info_iter,
&clock, &clock,
bridge_info, bridge_info,
payer_info, payer_info,
&mut bridge, bridge,
&mut guardian_set, guardian_set,
&v, &v,
), )
}
VAABody::Transfer(v) => { VAABody::Transfer(v) => {
if v.source_chain == CHAIN_ID_SOLANA { if v.source_chain == CHAIN_ID_SOLANA {
Self::process_vaa_transfer_post( Self::process_vaa_transfer_post(
@ -660,18 +708,30 @@ impl Bridge {
sig_info.key, sig_info.key,
) )
} else { } else {
evict_signatures = true;
Self::process_vaa_transfer( Self::process_vaa_transfer(
program_id, program_id,
accounts, accounts,
account_info_iter, account_info_iter,
bridge_info, bridge_info,
&mut bridge, bridge,
&v, &v,
) )
} }
} }
}?; }?;
// 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) >= VAA_TX_FEE {
Self::transfer_sol(bridge_info, payer_info, VAA_TX_FEE)?;
}
// Load claim account // Load claim account
let mut claim_data = claim_info.try_borrow_mut_data()?; let mut claim_data = claim_info.try_borrow_mut_data()?;
let claim: &mut ClaimedVAA = Bridge::unpack_unchecked(&mut claim_data)?; let claim: &mut ClaimedVAA = Bridge::unpack_unchecked(&mut claim_data)?;
@ -721,9 +781,10 @@ impl Bridge {
program_id, program_id,
accounts, accounts,
new_guardian_info.key, new_guardian_info.key,
payer_info.key, payer_info,
program_id, program_id,
&guardian_seed, &guardian_seed,
Some(bridge_info),
)?; )?;
let mut guardian_set_new_data = new_guardian_info.try_borrow_mut_data()?; let mut guardian_set_new_data = new_guardian_info.try_borrow_mut_data()?;
@ -891,7 +952,8 @@ impl Bridge {
let mint_info = next_account_info(account_info_iter)?; let mint_info = next_account_info(account_info_iter)?;
let wrapped_meta_info = next_account_info(account_info_iter)?; let wrapped_meta_info = next_account_info(account_info_iter)?;
let bridge = Bridge::bridge_deserialize(bridge_info)?; let bridge_data = bridge_info.data.try_borrow().map_err(|_| ProgramError::AccountBorrowFailed)?;
let bridge: &Bridge = Self::unpack_immutable(&bridge_data)?;
// Foreign chain asset, mint wrapped asset // Foreign chain asset, mint wrapped asset
let expected_mint_address = Bridge::derive_wrapped_asset_id( let expected_mint_address = Bridge::derive_wrapped_asset_id(
@ -912,9 +974,10 @@ impl Bridge {
&bridge.config.token_program, &bridge.config.token_program,
mint_info.key, mint_info.key,
bridge_info.key, bridge_info.key,
payer_info.key, payer_info,
&a, &a,
a.decimals, a.decimals,
None,
)?; )?;
// Check and create wrapped asset meta to allow reverse resolution of info // Check and create wrapped asset meta to allow reverse resolution of info
@ -923,9 +986,10 @@ impl Bridge {
program_id, program_id,
accounts, accounts,
wrapped_meta_info.key, wrapped_meta_info.key,
payer_info.key, payer_info,
program_id, program_id,
&wrapped_meta_seeds, &wrapped_meta_seeds,
None,
)?; )?;
let mut wrapped_meta_data = wrapped_meta_info.try_borrow_mut_data()?; let mut wrapped_meta_data = wrapped_meta_info.try_borrow_mut_data()?;
@ -1030,7 +1094,8 @@ impl Bridge {
bridge: &Pubkey, bridge: &Pubkey,
account: &Pubkey, account: &Pubkey,
mint: &Pubkey, mint: &Pubkey,
payer: &Pubkey, payer: &AccountInfo,
subsidizer: Option<&AccountInfo>,
) -> Result<(), ProgramError> { ) -> Result<(), ProgramError> {
Self::check_and_create_account::<[u8; spl_token::state::Account::LEN]>( Self::check_and_create_account::<[u8; spl_token::state::Account::LEN]>(
program_id, program_id,
@ -1039,6 +1104,7 @@ impl Bridge {
payer, payer,
token_program, token_program,
&Self::derive_custody_seeds(bridge, mint), &Self::derive_custody_seeds(bridge, mint),
subsidizer,
)?; )?;
info!(token_program.to_string().as_str()); info!(token_program.to_string().as_str());
let ix = spl_token::instruction::initialize_account( let ix = spl_token::instruction::initialize_account(
@ -1057,9 +1123,10 @@ impl Bridge {
token_program: &Pubkey, token_program: &Pubkey,
mint: &Pubkey, mint: &Pubkey,
bridge: &Pubkey, bridge: &Pubkey,
payer: &Pubkey, payer: &AccountInfo,
asset: &AssetMeta, asset: &AssetMeta,
decimals: u8, decimals: u8,
subsidizer: Option<&AccountInfo>,
) -> Result<(), ProgramError> { ) -> Result<(), ProgramError> {
Self::check_and_create_account::<[u8; spl_token::state::Mint::LEN]>( Self::check_and_create_account::<[u8; spl_token::state::Mint::LEN]>(
program_id, program_id,
@ -1068,6 +1135,7 @@ impl Bridge {
payer, payer,
token_program, token_program,
&Self::derive_wrapped_asset_seeds(bridge, asset.chain, asset.decimals, asset.address), &Self::derive_wrapped_asset_seeds(bridge, asset.chain, asset.decimals, asset.address),
subsidizer,
)?; )?;
let ix = spl_token::instruction::initialize_mint( let ix = spl_token::instruction::initialize_mint(
token_program, token_program,
@ -1099,14 +1167,21 @@ impl Bridge {
invoke_signed(instruction, account_infos, &[s.as_slice()]) 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 /// Check that a key was derived correctly and create account
pub fn check_and_create_account<T: Sized>( pub fn check_and_create_account<T: Sized>(
program_id: &Pubkey, program_id: &Pubkey,
accounts: &[AccountInfo], accounts: &[AccountInfo],
new_account: &Pubkey, new_account: &Pubkey,
payer: &Pubkey, payer: &AccountInfo,
owner: &Pubkey, owner: &Pubkey,
seeds: &Vec<Vec<u8>>, seeds: &Vec<Vec<u8>>,
subsidizer: Option<&AccountInfo>,
) -> Result<Vec<Vec<u8>>, ProgramError> { ) -> Result<Vec<Vec<u8>>, ProgramError> {
info!("deriving key"); info!("deriving key");
let (expected_key, full_seeds) = Bridge::derive_key(program_id, seeds)?; let (expected_key, full_seeds) = Bridge::derive_key(program_id, seeds)?;
@ -1114,12 +1189,28 @@ impl Bridge {
return Err(Error::InvalidDerivedAccount.into()); return Err(Error::InvalidDerivedAccount.into());
} }
// 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)?;
}
}
}
info!("deploying contract"); info!("deploying contract");
Self::create_account_raw::<T>( Self::create_account_raw::<T>(
program_id, program_id,
accounts, accounts,
new_account, new_account,
payer, payer.key,
owner, owner,
&full_seeds, &full_seeds,
)?; )?;

View File

@ -218,22 +218,6 @@ impl Bridge {
.map_err(|_| Error::ExpectedToken)?) .map_err(|_| Error::ExpectedToken)?)
} }
/// Deserializes a `Bridge`.
pub fn bridge_deserialize(info: &AccountInfo) -> Result<Bridge, Error> {
Ok(*Bridge::unpack(&mut info.data.borrow_mut()).map_err(|_| Error::ExpectedBridge)?)
}
/// Deserializes a `GuardianSet`.
pub fn guardian_set_deserialize(info: &AccountInfo) -> Result<GuardianSet, Error> {
Ok(*Bridge::unpack(&mut info.data.borrow_mut()).map_err(|_| Error::ExpectedGuardianSet)?)
}
/// Deserializes a `WrappedAssetMeta`.
pub fn wrapped_meta_deserialize(info: &AccountInfo) -> Result<WrappedAssetMeta, Error> {
Ok(*Bridge::unpack(&mut info.data.borrow_mut())
.map_err(|_| Error::ExpectedWrappedAssetMeta)?)
}
/// Unpacks a state from a bytes buffer while assuring that the state is initialized. /// Unpacks a state from a bytes buffer while assuring that the state is initialized.
pub fn unpack<T: IsInitialized>(input: &mut [u8]) -> Result<&mut T, ProgramError> { pub fn unpack<T: IsInitialized>(input: &mut [u8]) -> Result<&mut T, ProgramError> {
let mut_ref: &mut T = Self::unpack_unchecked(input)?; let mut_ref: &mut T = Self::unpack_unchecked(input)?;