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:
parent
ee5d07c929
commit
8510140165
|
@ -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
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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),
|
||||||
|
|
|
@ -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,
|
||||||
)?;
|
)?;
|
||||||
|
|
|
@ -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)?;
|
||||||
|
|
Loading…
Reference in New Issue