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:
parent
5997f133c3
commit
bec598b41a
|
@ -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
|
||||
|
|
|
@ -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=
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -103,6 +103,9 @@ pub enum Error {
|
|||
/// Mismatching guardian set
|
||||
#[error("GuardianSetMismatch")]
|
||||
GuardianSetMismatch,
|
||||
/// Insufficient fees
|
||||
#[error("InsufficientFees")]
|
||||
InsufficientFees,
|
||||
}
|
||||
|
||||
impl From<Error> for ProgramError {
|
||||
|
|
|
@ -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"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
||||
|
|
|
@ -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)]
|
||||
|
@ -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
|
||||
|
|
|
@ -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 =
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -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",
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue