bridge: add secp check instruction (#41)
* bridge: add secp check instruction * solana: update to secp solana upstream * solana: iteration on secp * solana: fix secp instruction serialization indices were off and secp ix data was serialized twice * solana: optimize ix serialization * agent: send multiple chunks of signatures * doc: update protocol spec * solana: store signatures in siginfo; reconstruct signed VAA in webinterface * solana: reformat * solana: add rustfmt config
This commit is contained in:
parent
3701d16b84
commit
25533f0264
|
@ -31,42 +31,18 @@ There are multiple ways to measure whether enough validators have approved a dec
|
|||
#### Multiple signatures - MultiSig
|
||||
|
||||
The most simple solution is by using a *MultiSig* mechanism. This means that each guardian would sign a message
|
||||
and submit it to a smart contract on-chain with reference to a *decision* that the guardians need to make (e.g. a transfer).
|
||||
Since a transaction itself is already signed, we can simplify this to using the transaction itself as proof.
|
||||
and submit it via a P2P gossip network.
|
||||
|
||||
Said smart contract will count the number of guardians that have submitted a transaction for a *decision*.
|
||||
Once the consensus threshold has been reached, the contract will execute the action the guardians have agreed on.
|
||||
Once the consensus threshold has been reached, a guardian will aggregate all signatures into a VAA and execute/submit it
|
||||
on the chain.
|
||||
|
||||
The issue with this schema is that it requires at least `n=2/3*m+1` transactions for `m` validators. On Ethereum for
|
||||
example one such transaction would cost `21k+20k+x` gas (base + `SSTORE` \[to track the tx] + additional compute).
|
||||
With `n` txs and 20 guardians threshold (`2/3m+1`) the cost would be `n*(41k+x)` which is `820k+20x`.
|
||||
The downside here is that gas costs increase with larger guardian sets bringing verification costs to
|
||||
`(5k+5k)*n` (`ECRECOVER+GTXDATANONZERO*72`).
|
||||
|
||||
At a gas price of `50 Gwei` this would mean total tx costs of `0.041 ETH` at `x=0`. At an ETH price of `300$` that
|
||||
means costs of `12.3$`.
|
||||
|
||||
These prices will require the guardians to charge significant fees. If these fees are not covered by the user, bridge
|
||||
transactions would stall and time out.
|
||||
|
||||
There are a couple of other issues with this concept:
|
||||
|
||||
1. There is no way for the Solana Bridge program to verify whether the guardians have actually unlocked the tokens on
|
||||
the foreign chain.
|
||||
2. Users cannot cover gas costs themselves because transactions are not "portable". I.e. the require serialized nonces.
|
||||
If a guardian submits a transaction with nonce 20 to the user but in the meantime issues another transaction with the
|
||||
same nonce, the user tx will be invalid even though the Solana program might successfully verify the tx (as it does not
|
||||
know the state of ETH).
|
||||
|
||||
There is an alternative way by using portable ECDSA signatures that approve an action i.e. a transfer. The guardians
|
||||
could submit all of those signatures to the lock proposal and the user or another participant in the network could relay
|
||||
them to Ethereum.
|
||||
That way the Solana program can verify that the signatures and signed action are valid, being sure that if there is a
|
||||
quorum (i.e. enough signatures), the user could use these signatures to trigger the execution of the signed action on
|
||||
the foreign chain.
|
||||
|
||||
The downside here is that this makes tracking and synchronizing guardian changes highly complex and further increases
|
||||
gas costs by about `(5k+5k)*n` (`ECRECOVER+GTXDATANONZERO*72`) for the additional `ecrecover` calls that need to be made.
|
||||
However since all signatures can be aggregate into one tx, we'll save `(n-1)*21k` leading to an effective gas saving of
|
||||
`~10k*n`. Still, transfers would be considerably expensive applying the aforementioned assumptions.
|
||||
To prevent lagging and complex gas price handling by validators or relayers, we always submit VAAs to Solana where txs
|
||||
are negligibly cheap. In the case of a Solana -> ETH transfer. Guardians would publish a signed VAA on Solana and a user
|
||||
or independently paid relayer would publish said VAA on Ethereum, paying for gas costs. This mechanism is similar to a
|
||||
check issued by the guardians (a VAA) which can be used on another chain to claim assets.
|
||||
|
||||
#### Threshold signatures
|
||||
|
||||
|
@ -114,7 +90,7 @@ A great overview can be found [here](https://github.com/Turing-Chain/TSSKit-Thre
|
|||
|
||||
#### Design choices
|
||||
|
||||
For transfers we implement a Schnorr-Threshold signature schema based on the implementation from Chainlink.
|
||||
For transfers we implement a simple MultiSig schema.
|
||||
We'll create a portable "action blob" with a threshold signature to allow anyone to relay action approvals
|
||||
between chains. We call this structure: **VAA** (Verifiable Action Approval).
|
||||
|
||||
|
@ -159,8 +135,6 @@ set.
|
|||
|
||||
ID: `0x01`
|
||||
|
||||
Size: `32 byte`
|
||||
|
||||
Payload:
|
||||
|
||||
```
|
||||
|
@ -176,8 +150,6 @@ desynchronization between the any of the chains in the system.
|
|||
|
||||
ID: `0x10`
|
||||
|
||||
Size: `75 byte`
|
||||
|
||||
Payload:
|
||||
|
||||
```
|
||||
|
@ -195,9 +167,6 @@ uint256 amount
|
|||
|
||||
#### Transfer of assets Foreign Chain -> Root Chain
|
||||
|
||||
If this is the first time the asset is transferred to the root chain, the user inititates a `CreateWrapped` instruction
|
||||
on the root chain to initialize the wrapped asset.
|
||||
|
||||
The user creates a token account for the wrapped asset on the root chain.
|
||||
|
||||
The user sends a chain native asset to the bridge on the foreign chain using the `Lock` function.
|
||||
|
@ -210,7 +179,7 @@ They check for the validity, parse it and will then initiate a threshold signatu
|
|||
produced VAA (`Transfer`) testifying that they have seen a foreign lockup. They will post this VAA on the root chain
|
||||
using the `SubmitVAA` instruction.
|
||||
|
||||
This instruction will either mint a new wrapped assetor released tokens from custody.
|
||||
This instruction will either mint a new wrapped asset or release tokens from custody.
|
||||
Custody is used for Solana-native tokens that have previously been transferred to a foreign chain, minting will be used
|
||||
to create new units of a wrapped foreign-chain asset.
|
||||
|
||||
|
@ -233,19 +202,14 @@ Guardians will pick up the **LockProposal** once it has enough confirmations on
|
|||
full confirmation (i.e. the max lockup, currently 32 slots), but can be changed to a different commitment levels
|
||||
on each guardian's discretion.
|
||||
|
||||
They check for the validity of the tx, parse it and will initiate an off-chain threshold signature ceremony which will
|
||||
They check for the validity of the tx, parse it and will initiate an off-chain signature aggregation ceremony which will
|
||||
output a **VAA** that can be used with a foreign chain smart contract to reclaim an unwrapped local asset or mint a
|
||||
wrapped `spl-token`.
|
||||
|
||||
This VAA will be posted on Solana by one of the guardians using the `SubmitVAA` instruction and will be stored in the
|
||||
`LockProposal`.
|
||||
|
||||
Depending on whether the fees are sufficient for **guardians** or **relayers** to cover the foreign chain fees, they
|
||||
will also post the VAA on the foreign chain, completing the transfer.
|
||||
|
||||
If no fee or an insufficient fee is specified, the user can pick up the VAA from the `LockProposal` and submit it on the foreign chain themselves.
|
||||
|
||||
VAAs for conducting transfers to a foreign chain are submitted using `FinalizeTransfer`.
|
||||
The user can then get the VAA from the `LockProposal` and submit it on the foreign chain.
|
||||
|
||||
### Fees
|
||||
|
||||
|
@ -254,11 +218,6 @@ TODO \o/
|
|||
### Config changes
|
||||
#### Guardian set changes
|
||||
|
||||
Since we use a *TSS* (Threshold signature scheme) for VAAs, changes to the guardian list are finalized by setting a
|
||||
new aggregate public key that's derived from a distributed key generation ("DKG") ceremony of the new guardian set.
|
||||
|
||||
This new public key is set via a VAA with the `UPDATE_GUARDIANS` action that is signed by the previous guardians.
|
||||
|
||||
The guardians need to make sure that the sets are synchronized between all chains.
|
||||
If the guardian set is changed, the guardian must also be replaced on all foreign chains. Therefore we
|
||||
conduct these changes via VAAs that are universally valid on all chains.
|
||||
|
@ -269,4 +228,4 @@ chains.
|
|||
|
||||
If all VAAs issued by the previous guardian set would immediately become invalid once a new guardian set takes over, that would
|
||||
lead to some payments being "stuck". Therefore we track a list of previous guardian sets. VAAs issued by old
|
||||
guardian sets stay valid for one day from the time that the change happens.
|
||||
guardian sets stay valid for one day from the time that the change happens in the default configuration.
|
||||
|
|
|
@ -24,6 +24,17 @@ Pokes a `TransferOutProposal` so it is reprocessed by the guardians.
|
|||
| ----- | ------ | ------------ | ------ | --------- | ----- | ------- |
|
||||
| 0 | proposal | TransferOutProposal | | ✅ | ️ | ✅ |
|
||||
|
||||
#### VerifySignatures
|
||||
|
||||
Checks secp checks (in the previous instruction) and stores results.
|
||||
|
||||
| Index | Name | Type | signer | writeable | empty | derived |
|
||||
| ----- | ------ | ------------ | ------ | --------- | ----- | ------- |
|
||||
| 0 | bridge_p | BridgeProgram | | | ️ | |
|
||||
| 1 | instructions | Sysvar | | | ️ | ✅ |
|
||||
| 2 | sig_status | SignatureState | | ✅ | ️ | |
|
||||
| 3 | guardian_set | GuardianSet | | | ️ | ✅ |
|
||||
|
||||
#### TransferOut
|
||||
|
||||
Burns a wrapped asset `token` from `sender` on the Solana chain.
|
||||
|
@ -37,12 +48,13 @@ Parameters:
|
|||
| 0 | bridge_p | BridgeProgram | | | ️ | |
|
||||
| 1 | sys | SystemProgram | | | ️ | |
|
||||
| 2 | token_program | SplToken | | | ️ | |
|
||||
| 3 | clock | Sysvar | | | ️ | ✅ |
|
||||
| 4 | token_account | TokenAccount | | ✅ | | |
|
||||
| 5 | bridge | BridgeConfig | | | | |
|
||||
| 6 | proposal | TransferOutProposal | | ✅ | ✅ | ✅ |
|
||||
| 7 | token | WrappedAsset | | ✅ | | ✅ |
|
||||
| 8 | payer | Account | ✅ | | | |
|
||||
| 3 | rent | Sysvar | | | ️ | ✅ |
|
||||
| 4 | clock | Sysvar | | | ️ | ✅ |
|
||||
| 5 | token_account | TokenAccount | | ✅ | | |
|
||||
| 6 | bridge | BridgeConfig | | | | |
|
||||
| 7 | proposal | TransferOutProposal | | ✅ | ✅ | ✅ |
|
||||
| 8 | token | WrappedAsset | | ✅ | | ✅ |
|
||||
| 9 | payer | Account | ✅ | | | |
|
||||
|
||||
#### TransferOutNative
|
||||
|
||||
|
@ -56,13 +68,14 @@ The transfer proposal will be tracked at a new account `proposal` where a VAA wi
|
|||
| 0 | bridge_p | BridgeProgram | | | ️ | |
|
||||
| 1 | sys | SystemProgram | | | ️ | |
|
||||
| 2 | token_program | SplToken | | | ️ | |
|
||||
| 3 | clock | Sysvar | | | ️ | ✅ |
|
||||
| 4 | token_account | TokenAccount | | ✅ | | |
|
||||
| 5 | bridge | BridgeConfig | | | | |
|
||||
| 6 | proposal | TransferOutProposal | | ✅ | ✅ | ✅ |
|
||||
| 7 | token | Mint | | ✅ | | |
|
||||
| 8 | payer | Account | ✅ | | | |
|
||||
| 9 | custody_account | TokenAccount | | ✅ | opt | ✅ |
|
||||
| 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 | ✅ |
|
||||
|
||||
#### EvictTransferOut
|
||||
|
||||
|
@ -88,20 +101,6 @@ Deletes a `ClaimedVAA` after the `VAA_EXPIRATION_TIME` to free up space on chain
|
|||
| 3 | bridge | BridgeConfig | | | | |
|
||||
| 4 | claim | ClaimedVAA | | ✅ | | ✅ |
|
||||
|
||||
#### CreateWrappedAsset
|
||||
|
||||
Creates a new `WrappedAsset` to be used to create accounts and later receive transfers on chain.
|
||||
|
||||
| Index | Name | Type | signer | writeable | empty | derived |
|
||||
| ----- | -------- | ------------------- | ------ | --------- | ----- | ------- |
|
||||
| 0 | bridge_p | BridgeProgram | | | ️ | |
|
||||
| 1 | sys | SystemProgram | | | ️ | |
|
||||
| 2 | token_program | SplToken | | | ️ | |
|
||||
| 3 | bridge | BridgeConfig | | | | |
|
||||
| 4 | payer | Account | ✅ | | ️ | |
|
||||
| 5 | wrapped_mint | WrappedAsset | | | ✅ | ✅ |
|
||||
| 6 | wrapped_meta_account | WrappedAssetMeta | | ✅ | ✅ | ✅ |
|
||||
|
||||
#### SubmitVAA
|
||||
|
||||
Submits a VAA signed by the guardians to perform an action.
|
||||
|
@ -114,11 +113,13 @@ All require:
|
|||
| ----- | ------------ | ------------ | ------ | --------- | ----- | ------- |
|
||||
| 0 | bridge_p | BridgeProgram | | | ️ | |
|
||||
| 1 | sys | SystemProgram | | | ️ | |
|
||||
| 2 | clock | Sysvar | | | ️ | ✅ |
|
||||
| 3 | bridge | BridgeConfig | | | | |
|
||||
| 4 | guardian_set | GuardianSet | | | | |
|
||||
| 5 | claim | ExecutedVAA | | ✅ | ✅ | ✅ |
|
||||
| 6 | payer | Account | ✅ | | | |
|
||||
| 2 | rent | Sysvar | | | ️ | ✅ |
|
||||
| 3 | clock | Sysvar | | | ️ | ✅ |
|
||||
| 4 | bridge | BridgeConfig | | | | |
|
||||
| 5 | guardian_set | GuardianSet | | | | |
|
||||
| 6 | claim | ExecutedVAA | | ✅ | ✅ | ✅ |
|
||||
| 7 | sig_info | SigState | | | ✅ | |
|
||||
| 8 | payer | Account | ✅ | | | |
|
||||
|
||||
followed by:
|
||||
|
||||
|
@ -126,31 +127,31 @@ followed by:
|
|||
|
||||
| Index | Name | Type | signer | writeable | empty | derived |
|
||||
| ----- | ------------ | ------------------- | ------ | --------- | ----- | ------- |
|
||||
| 7 | guardian_set_new | GuardianSet | | ✅ | ✅ | ✅ |
|
||||
| 9 | guardian_set_new | GuardianSet | | ✅ | ✅ | ✅ |
|
||||
|
||||
##### Transfer: Ethereum (native) -> Solana (wrapped)
|
||||
|
||||
| Index | Name | Type | signer | writeable | empty | derived |
|
||||
| ----- | ------------ | ------------ | ------ | --------- | ----- | ------- |
|
||||
| 7 | token_program | SplToken | | | ️ | |
|
||||
| 8 | token | WrappedAsset | | | | ✅ |
|
||||
| 9 | destination | TokenAccount | | ✅ | | |
|
||||
| 10 | wrapped_meta | WrappedMeta | | ✅ | opt | ✅ |
|
||||
| 9 | token_program | SplToken | | | ️ | |
|
||||
| 10 | token | WrappedAsset | | | | ✅ |
|
||||
| 11 | destination | TokenAccount | | ✅ | | |
|
||||
| 12 | wrapped_meta | WrappedMeta | | ✅ | opt | ✅ |
|
||||
|
||||
##### Transfer: Ethereum (wrapped) -> Solana (native)
|
||||
|
||||
| Index | Name | Type | signer | writeable | empty | derived |
|
||||
| ----- | ------------ | ------------ | ------ | --------- | ----- | ------- |
|
||||
| 7 | token_program | SplToken | | | ️ | |
|
||||
| 8 | token | Mint | | | | ✅ |
|
||||
| 9 | destination | TokenAccount | | ✅ | opt | |
|
||||
| 10 | custody_src | TokenAccount | | ✅ | | ✅ |
|
||||
| 9 | token_program | SplToken | | | ️ | |
|
||||
| 10 | token | Mint | | | | ✅ |
|
||||
| 11 | destination | TokenAccount | | ✅ | opt | |
|
||||
| 12 | custody_src | TokenAccount | | ✅ | | ✅ |
|
||||
|
||||
##### Transfer: Solana (any) -> Ethereum (any)
|
||||
|
||||
| Index | Name | Type | signer | writeable | empty | derived |
|
||||
| ----- | ------------ | ------------------- | ------ | --------- | ----- | ------- |
|
||||
| 7 | out_proposal | TransferOutProposal | | ✅ | | ✅ |
|
||||
| 9 | out_proposal | TransferOutProposal | | ✅ | | ✅ |
|
||||
|
||||
## Accounts
|
||||
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -3,3 +3,5 @@ members = ["agent", "bridge", "cli"]
|
|||
|
||||
[patch.crates-io]
|
||||
solana-sdk = { git="https://github.com/solana-labs/solana", branch="master" }
|
||||
solana-client = { git="https://github.com/solana-labs/solana", branch="master" }
|
||||
solana-account-decoder = { git="https://github.com/solana-labs/solana", branch="master" }
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -9,23 +9,23 @@ tonic = "0.3.0"
|
|||
tokio = { version = "0.2", features = ["rt-threaded", "time", "stream", "fs", "macros", "uds"] }
|
||||
prost = "0.6"
|
||||
prost-types = "0.6"
|
||||
solana-sdk = { version = "1.3.3" }
|
||||
solana-client = { version = "1.3.3" }
|
||||
solana-faucet = "1.3.3"
|
||||
solana-transaction-status = "1.3.3"
|
||||
spl-token = { package = "spl-token", git = "https://github.com/solana-labs/solana-program-library" }
|
||||
solana-sdk = { version = "1.3.11" }
|
||||
solana-client = { version = "1.3.11" }
|
||||
solana-faucet = "1.3.11"
|
||||
spl-token = "=2.0.3"
|
||||
wormhole-bridge = { path = "../bridge" }
|
||||
primitive-types = {version ="0.7.2"}
|
||||
primitive-types = { version = "0.7.2" }
|
||||
hex = "0.4.2"
|
||||
thiserror = "1.0.20"
|
||||
tungstenite = "0.11.1"
|
||||
serde = "1.0.103"
|
||||
url="2.1.1"
|
||||
serde_bytes ="0.11.5"
|
||||
log ="0.4.8"
|
||||
url = "2.1.1"
|
||||
serde_bytes = "0.11.5"
|
||||
log = "0.4.8"
|
||||
serde_derive = "1.0.103"
|
||||
serde_json = "1.0.57"
|
||||
bs58 = "0.3.1"
|
||||
byteorder = "1.3.4"
|
||||
|
||||
[build-dependencies]
|
||||
tonic-build = { version = "0.3.0", features = ["prost"] }
|
||||
|
|
|
@ -1,37 +1,41 @@
|
|||
use std::env;
|
||||
use std::fs::File;
|
||||
use std::mem::size_of;
|
||||
use std::rc::Rc;
|
||||
use std::str::FromStr;
|
||||
use std::sync::mpsc::RecvError;
|
||||
use std::thread::sleep;
|
||||
|
||||
use solana_client::client_error::ClientError;
|
||||
use solana_client::rpc_client::RpcClient;
|
||||
use solana_client::rpc_config::RpcSendTransactionConfig;
|
||||
use std::{io::Write, mem::size_of};
|
||||
|
||||
use std::str::FromStr;
|
||||
|
||||
use byteorder::{BigEndian, LittleEndian, ReadBytesExt, WriteBytesExt};
|
||||
use solana_client::{
|
||||
client_error::ClientError, rpc_client::RpcClient, rpc_config::RpcSendTransactionConfig,
|
||||
};
|
||||
use solana_sdk::commitment_config::{CommitmentConfig, CommitmentLevel};
|
||||
use solana_sdk::fee_calculator::FeeCalculator;
|
||||
|
||||
use solana_sdk::instruction::Instruction;
|
||||
use solana_sdk::program_error::ProgramError;
|
||||
use solana_sdk::pubkey::Pubkey;
|
||||
use solana_sdk::signature::{read_keypair_file, write_keypair_file, Keypair, Signer};
|
||||
use solana_sdk::transaction::Transaction;
|
||||
use solana_transaction_status::UiTransactionEncoding;
|
||||
use spl_token::state::Account;
|
||||
use tokio::stream::Stream;
|
||||
|
||||
use solana_sdk::{
|
||||
pubkey::Pubkey,
|
||||
signature::{read_keypair_file, write_keypair_file, Keypair, Signature, Signer},
|
||||
system_instruction::create_account,
|
||||
transaction::Transaction,
|
||||
};
|
||||
|
||||
use tokio::sync::mpsc;
|
||||
use tokio::time::Duration;
|
||||
|
||||
use tonic::{transport::Server, Code, Request, Response, Status};
|
||||
|
||||
use service::agent_server::{Agent, AgentServer};
|
||||
use service::{
|
||||
lockup_event::Event, Empty, LockupEvent, LockupEventNew, LockupEventVaaPosted,
|
||||
SubmitVaaRequest, SubmitVaaResponse, WatchLockupsRequest,
|
||||
agent_server::{Agent, AgentServer},
|
||||
lockup_event::Event,
|
||||
Empty, LockupEvent, LockupEventNew, LockupEventVaaPosted, SubmitVaaRequest, SubmitVaaResponse,
|
||||
WatchLockupsRequest,
|
||||
};
|
||||
use spl_bridge::{
|
||||
instruction::{post_vaa, verify_signatures, VerifySigPayload, CHAIN_ID_SOLANA},
|
||||
state::{Bridge, GuardianSet, SignatureState, TransferOutProposal},
|
||||
vaa::VAA,
|
||||
};
|
||||
use spl_bridge::instruction::{post_vaa, CHAIN_ID_SOLANA};
|
||||
use spl_bridge::state::{Bridge, TransferOutProposal};
|
||||
|
||||
use crate::monitor::{ProgramNotificationMessage, PubsubClient};
|
||||
use crate::monitor::PubsubClient;
|
||||
|
||||
mod monitor;
|
||||
|
||||
|
@ -47,6 +51,12 @@ pub struct AgentImpl {
|
|||
key: Keypair,
|
||||
}
|
||||
|
||||
pub struct SignatureItem {
|
||||
signature: [u8; 64 + 1],
|
||||
key: [u8; 20],
|
||||
index: u8,
|
||||
}
|
||||
|
||||
#[tonic::async_trait]
|
||||
impl Agent for AgentImpl {
|
||||
async fn submit_vaa(
|
||||
|
@ -56,42 +66,58 @@ impl Agent for AgentImpl {
|
|||
// Hack to clone keypair
|
||||
let b = self.key.to_bytes();
|
||||
let key = Keypair::from_bytes(&b).unwrap();
|
||||
let bridge = self.bridge.clone();
|
||||
|
||||
let ix = match post_vaa(&self.bridge, &key.pubkey(), request.get_ref().vaa.clone()) {
|
||||
Ok(v) => v,
|
||||
Err(e) => {
|
||||
return Err(Status::new(
|
||||
Code::InvalidArgument,
|
||||
format!("could not create instruction: {}", e),
|
||||
));
|
||||
}
|
||||
};
|
||||
|
||||
let mut transaction = Transaction::new_with_payer(&[ix], Some(&key.pubkey()));
|
||||
let rpc_url = self.rpc_url.clone();
|
||||
|
||||
// we need to spawn an extra thread because tokio does not allow nested runtimes
|
||||
std::thread::spawn(move || {
|
||||
let rpc = RpcClient::new(rpc_url);
|
||||
let (recent_blockhash, fee_calculator) = match rpc.get_recent_blockhash() {
|
||||
|
||||
let sig_key = solana_sdk::signature::Keypair::new();
|
||||
|
||||
let mut vaa = match VAA::deserialize(&request.get_ref().vaa) {
|
||||
Ok(v) => v,
|
||||
Err(e) => {
|
||||
return Err(Status::new(
|
||||
Code::Unavailable,
|
||||
format!("could not fetch recent blockhash: {}", e),
|
||||
Code::InvalidArgument,
|
||||
format!("could not parse VAA: {}", e),
|
||||
));
|
||||
}
|
||||
};
|
||||
transaction.sign(&[&key], recent_blockhash);
|
||||
match rpc.send_and_confirm_transaction_with_spinner_and_config(
|
||||
&transaction,
|
||||
CommitmentConfig {
|
||||
commitment: CommitmentLevel::Single,
|
||||
},
|
||||
RpcSendTransactionConfig {
|
||||
skip_preflight: true,
|
||||
},
|
||||
let verify_txs = pack_sig_verification_txs(&rpc, &bridge, &vaa, &key, &sig_key)?;
|
||||
|
||||
// Strip signatures
|
||||
vaa.signatures = Vec::new();
|
||||
let ix = match post_vaa(
|
||||
&bridge,
|
||||
&key.pubkey(),
|
||||
&sig_key.pubkey(),
|
||||
vaa.serialize().unwrap(),
|
||||
) {
|
||||
Ok(v) => v,
|
||||
Err(e) => {
|
||||
return Err(Status::new(
|
||||
Code::InvalidArgument,
|
||||
format!("could not create post_vaa instruction: {}", e),
|
||||
));
|
||||
}
|
||||
};
|
||||
let mut transaction2 = Transaction::new_with_payer(&[ix], Some(&key.pubkey()));
|
||||
|
||||
for (mut tx, signers) in verify_txs {
|
||||
match sign_and_send(&rpc, &mut tx, signers) {
|
||||
Ok(_) => (),
|
||||
Err(e) => {
|
||||
return Err(Status::new(
|
||||
Code::Unavailable,
|
||||
format!("tx sending failed: {}", e),
|
||||
));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
match sign_and_send(&rpc, &mut transaction2, vec![&key]) {
|
||||
Ok(s) => Ok(Response::new(SubmitVaaResponse {
|
||||
signature: s.to_string(),
|
||||
})),
|
||||
|
@ -115,15 +141,15 @@ impl Agent for AgentImpl {
|
|||
|
||||
async fn watch_lockups(
|
||||
&self,
|
||||
req: Request<WatchLockupsRequest>,
|
||||
_req: Request<WatchLockupsRequest>,
|
||||
) -> Result<Response<Self::WatchLockupsStream>, Status> {
|
||||
let (mut tx, mut rx) = mpsc::channel(1);
|
||||
let (mut tx, rx) = mpsc::channel(1);
|
||||
let url = self.url.clone();
|
||||
let bridge = self.bridge.clone();
|
||||
let rpc_url = self.rpc_url.clone();
|
||||
|
||||
tokio::spawn(async move {
|
||||
let rpc = RpcClient::new(rpc_url.to_string());
|
||||
let _rpc = RpcClient::new(rpc_url.to_string());
|
||||
let sub = PubsubClient::program_subscribe(&url, &bridge).unwrap();
|
||||
// looping and sending our response using stream
|
||||
loop {
|
||||
|
@ -225,6 +251,176 @@ impl Agent for AgentImpl {
|
|||
}
|
||||
}
|
||||
|
||||
fn pack_sig_verification_txs<'a>(
|
||||
rpc: &RpcClient,
|
||||
bridge: &Pubkey,
|
||||
vaa: &VAA,
|
||||
sender_keypair: &'a Keypair,
|
||||
sign_keypair: &'a Keypair,
|
||||
) -> Result<Vec<(Transaction, Vec<&'a Keypair>)>, Status> {
|
||||
// Load guardian set
|
||||
let bridge_key = Bridge::derive_bridge_id(bridge).unwrap();
|
||||
let guardian_key =
|
||||
Bridge::derive_guardian_set_id(bridge, &bridge_key, vaa.guardian_set_index).unwrap();
|
||||
let guardian_account = rpc
|
||||
.get_account_with_commitment(
|
||||
&guardian_key,
|
||||
CommitmentConfig {
|
||||
commitment: CommitmentLevel::Single,
|
||||
},
|
||||
)
|
||||
.unwrap()
|
||||
.value
|
||||
.unwrap_or_default();
|
||||
let data = guardian_account.data;
|
||||
let guardian_set: &GuardianSet = Bridge::unpack_immutable(data.as_slice()).unwrap();
|
||||
|
||||
// Map signatures to guardian set
|
||||
let mut signature_items: Vec<SignatureItem> = Vec::new();
|
||||
for s in vaa.signatures.iter() {
|
||||
let mut item = SignatureItem {
|
||||
signature: [0; 64 + 1],
|
||||
key: [0; 20],
|
||||
index: s.index,
|
||||
};
|
||||
|
||||
item.signature[0..32].copy_from_slice(&s.r);
|
||||
item.signature[32..64].copy_from_slice(&s.s);
|
||||
item.signature[64] = s.v;
|
||||
item.key = guardian_set.keys[s.index as usize];
|
||||
|
||||
signature_items.push(item);
|
||||
}
|
||||
|
||||
let vaa_hash = match vaa.body_hash() {
|
||||
Ok(v) => v,
|
||||
Err(e) => {
|
||||
return Err(Status::new(
|
||||
Code::InvalidArgument,
|
||||
format!("could get vaa body hash: {}", e),
|
||||
));
|
||||
}
|
||||
};
|
||||
let vaa_body = match vaa.signature_body() {
|
||||
Ok(v) => v,
|
||||
Err(e) => {
|
||||
return Err(Status::new(
|
||||
Code::InvalidArgument,
|
||||
format!("could get vaa body: {}", e),
|
||||
));
|
||||
}
|
||||
};
|
||||
|
||||
let mut verify_txs: Vec<(Transaction, Vec<&Keypair>)> = Vec::new();
|
||||
for (tx_index, chunk) in signature_items.chunks(6).enumerate() {
|
||||
let mut secp_payload = Vec::new();
|
||||
let mut signature_status = [-1i8; 20];
|
||||
|
||||
let data_offset = 1 + chunk.len() * 11;
|
||||
let message_offset = data_offset + chunk.len() * 85;
|
||||
|
||||
// 1 number of signatures
|
||||
secp_payload.write_u8(chunk.len() as u8);
|
||||
|
||||
let secp_ix_index = if tx_index == 0 { 1u8 } else { 0u8 };
|
||||
// Secp signature info description (11 bytes * n)
|
||||
for (i, s) in chunk.iter().enumerate() {
|
||||
secp_payload.write_u16::<LittleEndian>((data_offset + 85 * i) as u16);
|
||||
secp_payload.write_u8(secp_ix_index);
|
||||
secp_payload.write_u16::<LittleEndian>((data_offset + 85 * i + 65) as u16);
|
||||
secp_payload.write_u8(secp_ix_index);
|
||||
secp_payload.write_u16::<LittleEndian>(message_offset as u16);
|
||||
secp_payload.write_u16::<LittleEndian>(vaa_body.len() as u16);
|
||||
secp_payload.write_u8(secp_ix_index);
|
||||
signature_status[s.index as usize] = i as i8;
|
||||
}
|
||||
|
||||
// Write signatures and addresses
|
||||
for s in chunk.iter() {
|
||||
secp_payload.write(&s.signature);
|
||||
secp_payload.write(&s.key);
|
||||
}
|
||||
|
||||
// Write body
|
||||
secp_payload.write(&vaa_body);
|
||||
|
||||
let secp_ix = Instruction {
|
||||
program_id: solana_sdk::secp256k1_program::id(),
|
||||
data: secp_payload,
|
||||
accounts: vec![],
|
||||
};
|
||||
|
||||
let payload = VerifySigPayload {
|
||||
signers: signature_status,
|
||||
hash: vaa_hash,
|
||||
};
|
||||
let verify_ix = match verify_signatures(
|
||||
&bridge,
|
||||
&sign_keypair.pubkey(),
|
||||
vaa.guardian_set_index,
|
||||
&payload,
|
||||
) {
|
||||
Ok(v) => v,
|
||||
Err(e) => {
|
||||
return Err(Status::new(
|
||||
Code::InvalidArgument,
|
||||
format!("could not create verify instruction: {}", e),
|
||||
));
|
||||
}
|
||||
};
|
||||
|
||||
if tx_index == 0 {
|
||||
// Instruction for creating the signature status account
|
||||
let min_sig_rent = rpc
|
||||
.get_minimum_balance_for_rent_exemption(size_of::<SignatureState>())
|
||||
.unwrap();
|
||||
let create_ix = create_account(
|
||||
&sender_keypair.pubkey(),
|
||||
&sign_keypair.pubkey(),
|
||||
min_sig_rent,
|
||||
size_of::<SignatureState>() as u64,
|
||||
bridge,
|
||||
);
|
||||
|
||||
verify_txs.push((
|
||||
Transaction::new_with_payer(
|
||||
&[create_ix, secp_ix, verify_ix],
|
||||
Some(&sender_keypair.pubkey()),
|
||||
),
|
||||
vec![sender_keypair, sign_keypair],
|
||||
))
|
||||
} else {
|
||||
verify_txs.push((
|
||||
Transaction::new_with_payer(&[secp_ix, verify_ix], Some(&sender_keypair.pubkey())),
|
||||
vec![sender_keypair],
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
Ok(verify_txs)
|
||||
}
|
||||
|
||||
fn sign_and_send(
|
||||
rpc: &RpcClient,
|
||||
tx: &mut Transaction,
|
||||
keys: Vec<&Keypair>,
|
||||
) -> Result<Signature, ClientError> {
|
||||
let (recent_blockhash, _fee_calculator) = rpc.get_recent_blockhash()?;
|
||||
|
||||
tx.sign(&keys, recent_blockhash);
|
||||
|
||||
rpc.send_and_confirm_transaction_with_spinner_and_config(
|
||||
&tx,
|
||||
CommitmentConfig {
|
||||
commitment: CommitmentLevel::Single,
|
||||
},
|
||||
RpcSendTransactionConfig {
|
||||
skip_preflight: false,
|
||||
preflight_commitment: Some(CommitmentLevel::SingleGossip),
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let args: Vec<String> = env::args().collect();
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use std::str::FromStr;
|
||||
use std::{
|
||||
marker::PhantomData,
|
||||
str::FromStr,
|
||||
sync::{
|
||||
atomic::{AtomicBool, Ordering},
|
||||
mpsc::{channel, Receiver},
|
||||
|
@ -11,14 +11,16 @@ use std::{
|
|||
|
||||
use bs58;
|
||||
use log::*;
|
||||
use serde::{de::DeserializeOwned, de::Error, Deserialize, Deserializer, Serialize};
|
||||
use serde::{
|
||||
de::{DeserializeOwned, Error},
|
||||
Deserialize, Deserializer, Serialize,
|
||||
};
|
||||
use serde_json::{
|
||||
json,
|
||||
value::Value::{Number, Object},
|
||||
Map, Value,
|
||||
};
|
||||
use solana_sdk::account::Account;
|
||||
use solana_sdk::account_info::AccountInfo;
|
||||
|
||||
use solana_sdk::pubkey::Pubkey;
|
||||
use thiserror::Error;
|
||||
use tungstenite::{client::AutoStream, connect, Message, WebSocket};
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -19,8 +19,8 @@ default = ["solana-sdk/default", "spl-token/default"]
|
|||
num-derive = "0.2"
|
||||
num-traits = "0.2"
|
||||
remove_dir_all = "=0.5.0"
|
||||
solana-sdk = { version = "1.3.3", default-features = false, optional = true }
|
||||
spl-token = { package = "spl-token", git = "https://github.com/solana-labs/solana-program-library", default-features = false, optional = true }
|
||||
solana-sdk = { version = "1.3.11", default-features = false, optional = true }
|
||||
spl-token = { version = "=2.0.3", default-features = false, optional = true }
|
||||
thiserror = "1.0"
|
||||
byteorder = "1.3.4"
|
||||
zerocopy = "0.3.0"
|
||||
|
|
|
@ -100,6 +100,9 @@ pub enum Error {
|
|||
/// VAA for this transfer has already been submitted
|
||||
#[error("VAAAlreadySubmitted")]
|
||||
VAAAlreadySubmitted,
|
||||
/// Mismatching guardian set
|
||||
#[error("GuardianSetMismatch")]
|
||||
GuardianSetMismatch,
|
||||
}
|
||||
|
||||
impl From<Error> for ProgramError {
|
||||
|
|
|
@ -38,6 +38,7 @@ impl PrintProgramError for Error {
|
|||
Error::VAATooLong => info!("Error: VAATooLong"),
|
||||
Error::CannotWrapNative => info!("Error: CannotWrapNative"),
|
||||
Error::VAAAlreadySubmitted => info!("Error: VAAAlreadySubmitted"),
|
||||
Error::GuardianSetMismatch => info!("Error: GuardianSetMismatch"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,10 +1,8 @@
|
|||
#![allow(clippy::too_many_arguments)]
|
||||
//! Instruction types
|
||||
|
||||
use std::io::{Cursor, Read, Write};
|
||||
use std::mem::size_of;
|
||||
|
||||
use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
|
||||
use primitive_types::U256;
|
||||
use solana_sdk::{
|
||||
instruction::{AccountMeta, Instruction},
|
||||
|
@ -12,11 +10,13 @@ use solana_sdk::{
|
|||
pubkey::Pubkey,
|
||||
};
|
||||
|
||||
use crate::error::Error;
|
||||
use crate::error::Error::VAATooLong;
|
||||
use crate::instruction::BridgeInstruction::{Initialize, PokeProposal, PostVAA, TransferOut};
|
||||
use crate::state::{AssetMeta, Bridge, BridgeConfig};
|
||||
use crate::vaa::{VAABody, VAA};
|
||||
use crate::{
|
||||
instruction::BridgeInstruction::{
|
||||
Initialize, PokeProposal, PostVAA, TransferOut, VerifySignatures,
|
||||
},
|
||||
state::{AssetMeta, Bridge, BridgeConfig},
|
||||
vaa::{VAABody, VAA},
|
||||
};
|
||||
|
||||
/// chain id of this chain
|
||||
pub const CHAIN_ID_SOLANA: u8 = 1;
|
||||
|
@ -75,6 +75,14 @@ pub struct TransferOutPayloadRaw {
|
|||
pub nonce: u32,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct VerifySigPayload {
|
||||
/// hash of the VAA
|
||||
pub hash: [u8; 32],
|
||||
/// instruction indices of signers (-1 for missing)
|
||||
pub signers: [i8; MAX_LEN_GUARDIAN_KEYS],
|
||||
}
|
||||
|
||||
/// Instructions supported by the SwapInfo program.
|
||||
#[repr(C)]
|
||||
pub enum BridgeInstruction {
|
||||
|
@ -126,6 +134,9 @@ pub enum BridgeInstruction {
|
|||
|
||||
/// Pokes a proposal with no valid VAAs attached so guardians reprocess it.
|
||||
PokeProposal(),
|
||||
|
||||
/// Verifies signature instructions
|
||||
VerifySignatures(VerifySigPayload),
|
||||
}
|
||||
|
||||
impl BridgeInstruction {
|
||||
|
@ -157,16 +168,22 @@ impl BridgeInstruction {
|
|||
PostVAA(payload)
|
||||
}
|
||||
5 => PokeProposal(),
|
||||
6 => {
|
||||
let payload: &VerifySigPayload = unpack(input)?;
|
||||
|
||||
VerifySignatures(*payload)
|
||||
}
|
||||
_ => return Err(ProgramError::InvalidInstructionData),
|
||||
})
|
||||
}
|
||||
|
||||
/// Serializes a BridgeInstruction into a byte buffer.
|
||||
pub fn serialize(self: Self) -> Result<Vec<u8>, ProgramError> {
|
||||
let mut output = vec![0u8; size_of::<BridgeInstruction>()];
|
||||
let mut output = Vec::with_capacity(size_of::<BridgeInstruction>());
|
||||
|
||||
match self {
|
||||
Self::Initialize(payload) => {
|
||||
output.resize(size_of::<InitializePayload>() + 1, 0);
|
||||
output[0] = 0;
|
||||
#[allow(clippy::cast_ptr_alignment)]
|
||||
let value = unsafe {
|
||||
|
@ -175,6 +192,7 @@ impl BridgeInstruction {
|
|||
*value = payload;
|
||||
}
|
||||
Self::TransferOut(payload) => {
|
||||
output.resize(size_of::<TransferOutPayloadRaw>() + 1, 0);
|
||||
output[0] = 1;
|
||||
#[allow(clippy::cast_ptr_alignment)]
|
||||
let value = unsafe {
|
||||
|
@ -193,21 +211,32 @@ impl BridgeInstruction {
|
|||
};
|
||||
}
|
||||
Self::PostVAA(payload) => {
|
||||
output.resize(1, 0);
|
||||
output[0] = 2;
|
||||
#[allow(clippy::cast_ptr_alignment)]
|
||||
let value =
|
||||
unsafe { &mut *(&mut output[size_of::<u8>()] as *mut u8 as *mut VAAData) };
|
||||
*value = payload;
|
||||
output.extend_from_slice(&payload);
|
||||
}
|
||||
Self::EvictTransferOut() => {
|
||||
output.resize(1, 0);
|
||||
output[0] = 3;
|
||||
}
|
||||
Self::EvictClaimedVAA() => {
|
||||
output.resize(1, 0);
|
||||
output[0] = 4;
|
||||
}
|
||||
Self::PokeProposal() => {
|
||||
output.resize(1, 0);
|
||||
output[0] = 5;
|
||||
}
|
||||
Self::VerifySignatures(payload) => {
|
||||
output.resize(size_of::<VerifySigPayload>() + 1, 0);
|
||||
output[0] = 6;
|
||||
#[allow(clippy::cast_ptr_alignment)]
|
||||
let value = unsafe {
|
||||
&mut *(&mut output[size_of::<u8>()] as *mut u8 as *mut VerifySigPayload)
|
||||
};
|
||||
*value = payload;
|
||||
}
|
||||
}
|
||||
Ok(output)
|
||||
}
|
||||
|
@ -280,6 +309,7 @@ pub fn transfer_out(
|
|||
AccountMeta::new_readonly(*program_id, false),
|
||||
AccountMeta::new_readonly(solana_sdk::system_program::id(), false),
|
||||
AccountMeta::new_readonly(spl_token::id(), false),
|
||||
AccountMeta::new_readonly(solana_sdk::sysvar::rent::id(), false),
|
||||
AccountMeta::new_readonly(solana_sdk::sysvar::clock::id(), false),
|
||||
AccountMeta::new(*token_account, false),
|
||||
AccountMeta::new(bridge_key, false),
|
||||
|
@ -301,11 +331,40 @@ pub fn transfer_out(
|
|||
})
|
||||
}
|
||||
|
||||
/// Creates a 'VerifySignatures' instruction.
|
||||
#[cfg(not(target_arch = "bpf"))]
|
||||
pub fn verify_signatures(
|
||||
program_id: &Pubkey,
|
||||
signature_acc: &Pubkey,
|
||||
guardian_set_id: u32,
|
||||
p: &VerifySigPayload,
|
||||
) -> Result<Instruction, ProgramError> {
|
||||
let data = BridgeInstruction::VerifySignatures(*p).serialize()?;
|
||||
|
||||
let bridge_key = Bridge::derive_bridge_id(program_id)?;
|
||||
let guardian_set_key =
|
||||
Bridge::derive_guardian_set_id(program_id, &bridge_key, guardian_set_id)?;
|
||||
|
||||
let accounts = vec![
|
||||
AccountMeta::new_readonly(*program_id, false),
|
||||
AccountMeta::new_readonly(solana_sdk::sysvar::instructions::id(), false),
|
||||
AccountMeta::new(*signature_acc, false),
|
||||
AccountMeta::new_readonly(guardian_set_key, false),
|
||||
];
|
||||
|
||||
Ok(Instruction {
|
||||
program_id: *program_id,
|
||||
accounts,
|
||||
data,
|
||||
})
|
||||
}
|
||||
|
||||
/// Creates a 'PostVAA' instruction.
|
||||
#[cfg(not(target_arch = "bpf"))]
|
||||
pub fn post_vaa(
|
||||
program_id: &Pubkey,
|
||||
payer: &Pubkey,
|
||||
signature_key: &Pubkey,
|
||||
v: VAAData,
|
||||
) -> Result<Instruction, ProgramError> {
|
||||
let mut data = v.clone();
|
||||
|
@ -322,10 +381,12 @@ pub fn post_vaa(
|
|||
let mut accounts = vec![
|
||||
AccountMeta::new_readonly(*program_id, false),
|
||||
AccountMeta::new_readonly(solana_sdk::system_program::id(), false),
|
||||
AccountMeta::new_readonly(solana_sdk::sysvar::rent::id(), false),
|
||||
AccountMeta::new_readonly(solana_sdk::sysvar::clock::id(), false),
|
||||
AccountMeta::new(bridge_key, false),
|
||||
AccountMeta::new(guardian_set_key, false),
|
||||
AccountMeta::new(claim_key, false),
|
||||
AccountMeta::new(*signature_key, false),
|
||||
AccountMeta::new(*payer, true),
|
||||
];
|
||||
|
||||
|
@ -390,7 +451,7 @@ pub fn poke_proposal(
|
|||
) -> Result<Instruction, ProgramError> {
|
||||
let data = BridgeInstruction::PokeProposal().serialize()?;
|
||||
|
||||
let mut accounts = vec![AccountMeta::new(*transfer_proposal, false)];
|
||||
let accounts = vec![AccountMeta::new(*transfer_proposal, false)];
|
||||
|
||||
Ok(Instruction {
|
||||
program_id: *program_id,
|
||||
|
|
|
@ -7,5 +7,4 @@ pub mod error_program;
|
|||
pub mod instruction;
|
||||
pub mod processor;
|
||||
pub mod state;
|
||||
pub mod syscalls;
|
||||
pub mod vaa;
|
||||
|
|
|
@ -1,32 +1,53 @@
|
|||
//! Program instruction processing logic
|
||||
#![cfg(feature = "program")]
|
||||
|
||||
use std::borrow::Borrow;
|
||||
use std::cell::RefCell;
|
||||
use std::mem::size_of;
|
||||
use std::slice::Iter;
|
||||
use std::{borrow::Borrow, cell::RefCell, io::Write, mem::size_of, slice::Iter};
|
||||
|
||||
use byteorder::ByteOrder;
|
||||
use num_traits::AsPrimitive;
|
||||
use primitive_types::U256;
|
||||
use solana_sdk::clock::Clock;
|
||||
use solana_sdk::hash::Hasher;
|
||||
use sha3::Digest;
|
||||
#[cfg(target_arch = "bpf")]
|
||||
use solana_sdk::program::invoke_signed;
|
||||
use solana_sdk::rent::Rent;
|
||||
use solana_sdk::system_instruction::{create_account, SystemInstruction};
|
||||
use solana_sdk::sysvar::Sysvar;
|
||||
use solana_sdk::{
|
||||
account_info::next_account_info, account_info::AccountInfo, entrypoint::ProgramResult, info,
|
||||
instruction::Instruction, program_error::ProgramError, pubkey::Pubkey,
|
||||
account_info::{next_account_info, AccountInfo},
|
||||
clock::Clock,
|
||||
entrypoint::ProgramResult,
|
||||
hash::Hasher,
|
||||
info,
|
||||
instruction::Instruction,
|
||||
program_error::ProgramError,
|
||||
pubkey::Pubkey,
|
||||
rent::Rent,
|
||||
system_instruction::{create_account, SystemInstruction},
|
||||
sysvar::Sysvar,
|
||||
};
|
||||
use spl_token::state::Mint;
|
||||
use spl_token::{pack::Pack, state::Mint};
|
||||
|
||||
use crate::error::Error;
|
||||
use crate::instruction::BridgeInstruction::*;
|
||||
use crate::instruction::{BridgeInstruction, TransferOutPayload, VAAData, CHAIN_ID_SOLANA};
|
||||
use crate::instruction::{MAX_LEN_GUARDIAN_KEYS, MAX_VAA_SIZE};
|
||||
use crate::state::*;
|
||||
use crate::vaa::{BodyTransfer, BodyUpdateGuardianSet, VAABody, VAA};
|
||||
use crate::{
|
||||
error::Error,
|
||||
instruction::{
|
||||
BridgeInstruction, BridgeInstruction::*, TransferOutPayload, VAAData, VerifySigPayload,
|
||||
CHAIN_ID_SOLANA, MAX_LEN_GUARDIAN_KEYS, MAX_VAA_SIZE,
|
||||
},
|
||||
state::*,
|
||||
vaa::{BodyTransfer, BodyUpdateGuardianSet, VAABody, VAA},
|
||||
};
|
||||
|
||||
/// SigInfo contains metadata about signers in a VerifySignature ix
|
||||
struct SigInfo {
|
||||
/// index of the signer in the guardianset
|
||||
signer_index: u8,
|
||||
/// index of the signature in the secp instruction
|
||||
sig_index: u8,
|
||||
}
|
||||
|
||||
struct SecpInstructionPart<'a> {
|
||||
address: &'a [u8],
|
||||
signature: &'a [u8],
|
||||
msg_offset: u16,
|
||||
msg_size: u16,
|
||||
}
|
||||
|
||||
/// Instruction processing logic
|
||||
impl Bridge {
|
||||
|
@ -64,6 +85,11 @@ impl Bridge {
|
|||
|
||||
Self::process_poke(program_id, accounts)
|
||||
}
|
||||
VerifySignatures(p) => {
|
||||
info!("Instruction: VerifySignatures");
|
||||
|
||||
Self::process_verify_signatures(program_id, accounts, &p)
|
||||
}
|
||||
_ => panic!(""),
|
||||
}
|
||||
}
|
||||
|
@ -155,6 +181,158 @@ impl Bridge {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
/// Processes signature verifications
|
||||
pub fn process_verify_signatures(
|
||||
program_id: &Pubkey,
|
||||
accounts: &[AccountInfo],
|
||||
payload: &VerifySigPayload,
|
||||
) -> ProgramResult {
|
||||
let account_info_iter = &mut accounts.iter();
|
||||
next_account_info(account_info_iter)?; // Bridge program
|
||||
let instruction_accounts = 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: GuardianSet = Self::guardian_set_deserialize(guardian_set_info)?;
|
||||
|
||||
let mut sig_state_data = sig_info.data.borrow_mut();
|
||||
let mut sig_state: &mut SignatureState = Self::unpack_unchecked(&mut sig_state_data)?;
|
||||
|
||||
if sig_state.is_initialized {
|
||||
if sig_state.guardian_set_index != guardian_set.index {
|
||||
return Err(Error::GuardianSetMismatch.into());
|
||||
}
|
||||
if sig_state.hash != payload.hash {
|
||||
return Err(ProgramError::InvalidArgument);
|
||||
}
|
||||
} else {
|
||||
sig_state.is_initialized = true;
|
||||
sig_state.guardian_set_index = guardian_set.index;
|
||||
sig_state.hash = payload.hash;
|
||||
}
|
||||
|
||||
let sig_infos: Vec<SigInfo> = payload
|
||||
.signers
|
||||
.iter()
|
||||
.enumerate()
|
||||
.filter_map(|(i, p)| {
|
||||
if *p == -1 {
|
||||
return None;
|
||||
}
|
||||
|
||||
return Some(SigInfo {
|
||||
sig_index: *p as u8,
|
||||
signer_index: i as u8,
|
||||
});
|
||||
})
|
||||
.collect();
|
||||
|
||||
let current_instruction = solana_sdk::sysvar::instructions::load_current_index(
|
||||
&instruction_accounts.try_borrow_data()?,
|
||||
);
|
||||
if current_instruction == 0 {
|
||||
return Err(ProgramError::InvalidInstructionData);
|
||||
}
|
||||
|
||||
// The previous ix must be a secp verification instruction
|
||||
let secp_ix_index = (current_instruction - 1) as u8;
|
||||
let secp_ix = solana_sdk::sysvar::instructions::load_instruction_at(
|
||||
secp_ix_index as usize,
|
||||
&instruction_accounts.try_borrow_data()?,
|
||||
)
|
||||
.map_err(|_| ProgramError::InvalidAccountData)?;
|
||||
|
||||
// Check that the instruction is actually for the secp program
|
||||
if secp_ix.program_id != solana_sdk::secp256k1_program::id() {
|
||||
return Err(ProgramError::InvalidArgument);
|
||||
}
|
||||
|
||||
let secp_data_len = secp_ix.data.len();
|
||||
if secp_data_len < 2 {
|
||||
return Err(ProgramError::InvalidAccountData);
|
||||
}
|
||||
|
||||
let sig_len = secp_ix.data[0];
|
||||
let mut index = 1;
|
||||
|
||||
let mut secp_ixs: Vec<SecpInstructionPart> = Vec::with_capacity(sig_len as usize);
|
||||
for i in 0..sig_len {
|
||||
let sig_offset = byteorder::LE::read_u16(&secp_ix.data[index..index + 2]) as usize;
|
||||
index += 2;
|
||||
let sig_ix = secp_ix.data[index];
|
||||
index += 1;
|
||||
let address_offset = byteorder::LE::read_u16(&secp_ix.data[index..index + 2]) as usize;
|
||||
index += 2;
|
||||
let address_ix = secp_ix.data[index];
|
||||
index += 1;
|
||||
let msg_offset = byteorder::LE::read_u16(&secp_ix.data[index..index + 2]);
|
||||
index += 2;
|
||||
let msg_size = byteorder::LE::read_u16(&secp_ix.data[index..index + 2]);
|
||||
index += 2;
|
||||
let msg_ix = secp_ix.data[index];
|
||||
index += 1;
|
||||
|
||||
if address_ix != secp_ix_index || msg_ix != secp_ix_index || sig_ix != secp_ix_index {
|
||||
return Err(ProgramError::InvalidArgument);
|
||||
}
|
||||
|
||||
let address: &[u8] = &secp_ix.data[address_offset..address_offset + 20];
|
||||
let signature: &[u8] = &secp_ix.data[sig_offset..sig_offset + 65];
|
||||
|
||||
// Make sure that all messages are equal
|
||||
if i > 0 {
|
||||
if msg_offset != secp_ixs[0].msg_offset || msg_size != secp_ixs[0].msg_size {
|
||||
return Err(ProgramError::InvalidArgument);
|
||||
}
|
||||
}
|
||||
secp_ixs.push(SecpInstructionPart {
|
||||
address,
|
||||
signature,
|
||||
msg_offset,
|
||||
msg_size,
|
||||
});
|
||||
}
|
||||
|
||||
if sig_infos.len() != secp_ixs.len() {
|
||||
return Err(ProgramError::InvalidArgument);
|
||||
}
|
||||
|
||||
// Check message
|
||||
let message = &secp_ix.data[secp_ixs[0].msg_offset as usize
|
||||
..(secp_ixs[0].msg_offset + secp_ixs[0].msg_size) as usize];
|
||||
|
||||
let mut h = sha3::Keccak256::default();
|
||||
if let Err(_) = h.write(message) {
|
||||
return Err(ProgramError::InvalidArgument);
|
||||
};
|
||||
let msg_hash: [u8; 32] = h.finalize().into();
|
||||
if msg_hash != payload.hash {
|
||||
return Err(ProgramError::InvalidArgument);
|
||||
}
|
||||
|
||||
// Check addresses
|
||||
for s in sig_infos {
|
||||
if s.signer_index > guardian_set.len_keys {
|
||||
return Err(ProgramError::InvalidArgument);
|
||||
}
|
||||
|
||||
if s.sig_index + 1 > sig_len {
|
||||
return Err(ProgramError::InvalidArgument);
|
||||
}
|
||||
|
||||
let key = guardian_set.keys[s.signer_index as usize];
|
||||
// Check key in ix
|
||||
if key != secp_ixs[s.sig_index as usize].address {
|
||||
return Err(ProgramError::InvalidArgument);
|
||||
}
|
||||
|
||||
sig_state.signatures[s.signer_index as usize]
|
||||
.copy_from_slice(secp_ixs[s.sig_index as usize].signature);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Transfers a wrapped asset out
|
||||
pub fn process_transfer_out(
|
||||
program_id: &Pubkey,
|
||||
|
@ -166,6 +344,7 @@ impl Bridge {
|
|||
next_account_info(account_info_iter)?; // Bridge program
|
||||
next_account_info(account_info_iter)?; // System program
|
||||
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 sender_account_info = next_account_info(account_info_iter)?;
|
||||
let bridge_info = next_account_info(account_info_iter)?;
|
||||
|
@ -223,6 +402,7 @@ impl Bridge {
|
|||
accounts,
|
||||
&bridge.config.token_program,
|
||||
sender_account_info.key,
|
||||
mint_info.key,
|
||||
t.amount,
|
||||
)?;
|
||||
|
||||
|
@ -256,6 +436,7 @@ impl Bridge {
|
|||
next_account_info(account_info_iter)?; // Bridge program
|
||||
next_account_info(account_info_iter)?; // System program
|
||||
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 sender_account_info = next_account_info(account_info_iter)?;
|
||||
let bridge_info = next_account_info(account_info_iter)?;
|
||||
|
@ -368,10 +549,12 @@ impl Bridge {
|
|||
// Load VAA processing default accounts
|
||||
next_account_info(account_info_iter)?; // Bridge program
|
||||
next_account_info(account_info_iter)?; // System program
|
||||
next_account_info(account_info_iter)?; // Rent sysvar
|
||||
let clock_info = next_account_info(account_info_iter)?;
|
||||
let bridge_info = next_account_info(account_info_iter)?;
|
||||
let guardian_set_info = next_account_info(account_info_iter)?;
|
||||
let claim_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 mut bridge = Bridge::bridge_deserialize(bridge_info)?;
|
||||
|
@ -401,9 +584,29 @@ impl Bridge {
|
|||
return Err(Error::GuardianSetExpired.into());
|
||||
}
|
||||
|
||||
// Verify VAA signature
|
||||
if !vaa.verify(&guardian_set.keys[..guardian_set.len_keys as usize]) {
|
||||
return Err(Error::InvalidVAASignature.into());
|
||||
// Verify sig state
|
||||
let mut sig_state_data = sig_info.data.borrow_mut();
|
||||
let sig_state: &SignatureState = Self::unpack(&mut sig_state_data)?;
|
||||
|
||||
// Verify that signatures were made using the correct set
|
||||
if sig_state.guardian_set_index != guardian_set.index {
|
||||
return Err(Error::GuardianSetMismatch.into());
|
||||
}
|
||||
|
||||
let hash = vaa.body_hash()?;
|
||||
if sig_state.hash != hash {
|
||||
return Err(ProgramError::InvalidAccountData);
|
||||
}
|
||||
|
||||
// Check quorum
|
||||
if (sig_state
|
||||
.signatures
|
||||
.iter()
|
||||
.filter(|v| v.iter().filter(|v| **v != 0).count() != 0)
|
||||
.count() as u8)
|
||||
< (((guardian_set.len_keys / 4) * 3) + 1)
|
||||
{
|
||||
return Err(ProgramError::InvalidArgument);
|
||||
}
|
||||
|
||||
let payload = vaa.payload.as_ref().ok_or(Error::InvalidVAAAction)?;
|
||||
|
@ -428,6 +631,7 @@ impl Bridge {
|
|||
vaa,
|
||||
&v,
|
||||
vaa_data,
|
||||
sig_info.key,
|
||||
)
|
||||
} else {
|
||||
Self::process_vaa_transfer(
|
||||
|
@ -635,6 +839,7 @@ impl Bridge {
|
|||
vaa: &VAA,
|
||||
b: &BodyTransfer,
|
||||
vaa_data: VAAData,
|
||||
sig_account: &Pubkey,
|
||||
) -> ProgramResult {
|
||||
info!("posting VAA");
|
||||
let proposal_info = next_account_info(account_info_iter)?;
|
||||
|
@ -673,6 +878,7 @@ impl Bridge {
|
|||
// Stop byte
|
||||
proposal.vaa[vaa_data.len()] = 0xff;
|
||||
proposal.vaa_time = vaa.timestamp;
|
||||
proposal.signature_account = *sig_account;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
@ -686,11 +892,13 @@ impl Bridge {
|
|||
accounts: &[AccountInfo],
|
||||
token_program_id: &Pubkey,
|
||||
token_account: &Pubkey,
|
||||
mint_account: &Pubkey,
|
||||
amount: U256,
|
||||
) -> Result<(), ProgramError> {
|
||||
let ix = spl_token::instruction::burn(
|
||||
token_program_id,
|
||||
token_account,
|
||||
mint_account,
|
||||
&Self::derive_bridge_id(program_id)?,
|
||||
&[],
|
||||
amount.as_u64(),
|
||||
|
@ -769,7 +977,7 @@ impl Bridge {
|
|||
mint: &Pubkey,
|
||||
payer: &Pubkey,
|
||||
) -> Result<(), ProgramError> {
|
||||
Self::check_and_create_account::<spl_token::state::Account>(
|
||||
Self::check_and_create_account::<[u8; spl_token::state::Account::LEN]>(
|
||||
program_id,
|
||||
accounts,
|
||||
account,
|
||||
|
@ -777,7 +985,6 @@ impl Bridge {
|
|||
token_program,
|
||||
&Self::derive_custody_seeds(bridge, mint),
|
||||
)?;
|
||||
info!("bababu");
|
||||
info!(token_program.to_string().as_str());
|
||||
let ix = spl_token::instruction::initialize_account(
|
||||
token_program,
|
||||
|
@ -799,7 +1006,7 @@ impl Bridge {
|
|||
asset: &AssetMeta,
|
||||
decimals: u8,
|
||||
) -> Result<(), ProgramError> {
|
||||
Self::check_and_create_account::<Mint>(
|
||||
Self::check_and_create_account::<[u8; spl_token::state::Mint::LEN]>(
|
||||
program_id,
|
||||
accounts,
|
||||
mint,
|
||||
|
@ -810,9 +1017,8 @@ impl Bridge {
|
|||
let ix = spl_token::instruction::initialize_mint(
|
||||
token_program,
|
||||
mint,
|
||||
&Self::derive_bridge_id(program_id)?,
|
||||
None,
|
||||
Some(&Self::derive_bridge_id(program_id)?),
|
||||
0,
|
||||
decimals,
|
||||
)?;
|
||||
invoke_signed(&ix, accounts, &[])
|
||||
|
|
|
@ -1,18 +1,16 @@
|
|||
//! Bridge transition types
|
||||
|
||||
use std::io::{Cursor, Read, Write};
|
||||
use std::mem::size_of;
|
||||
use std::ops::Deref;
|
||||
|
||||
use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
|
||||
use primitive_types::U256;
|
||||
use solana_sdk::pubkey::{PubkeyError, MAX_SEED_LEN};
|
||||
use solana_sdk::{account_info::AccountInfo, program_error::ProgramError, pubkey::Pubkey};
|
||||
use zerocopy::AsBytes;
|
||||
|
||||
use crate::error::Error;
|
||||
use crate::instruction::{ForeignAddress, VAAData, MAX_LEN_GUARDIAN_KEYS, MAX_VAA_SIZE};
|
||||
use crate::vaa::BodyTransfer;
|
||||
use crate::{
|
||||
error::Error,
|
||||
instruction::{ForeignAddress, MAX_LEN_GUARDIAN_KEYS, MAX_VAA_SIZE},
|
||||
vaa::BodyTransfer,
|
||||
};
|
||||
|
||||
/// fee rate as a ratio
|
||||
#[repr(C)]
|
||||
|
@ -73,6 +71,8 @@ pub struct TransferOutProposal {
|
|||
pub lockup_time: u32,
|
||||
/// times the proposal has been poked
|
||||
pub poke_counter: u8,
|
||||
/// Account where signatures are stored
|
||||
pub signature_account: Pubkey,
|
||||
|
||||
/// Is `true` if this structure has been initialized.
|
||||
pub is_initialized: bool,
|
||||
|
@ -178,19 +178,42 @@ impl IsInitialized for Bridge {
|
|||
}
|
||||
}
|
||||
|
||||
/// Signature state
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct SignatureState {
|
||||
/// signatures of validators
|
||||
pub signatures: [[u8; 65]; MAX_LEN_GUARDIAN_KEYS],
|
||||
|
||||
/// hash of the data
|
||||
pub hash: [u8; 32],
|
||||
|
||||
/// index of the guardian set
|
||||
pub guardian_set_index: u32,
|
||||
|
||||
/// Is `true` if this structure has been initialized.
|
||||
pub is_initialized: bool,
|
||||
}
|
||||
|
||||
impl IsInitialized for SignatureState {
|
||||
fn is_initialized(&self) -> bool {
|
||||
self.is_initialized
|
||||
}
|
||||
}
|
||||
|
||||
/// Implementation of serialization functions
|
||||
impl Bridge {
|
||||
/// Deserializes a spl_token `Account`.
|
||||
pub fn token_account_deserialize(
|
||||
info: &AccountInfo,
|
||||
) -> Result<spl_token::state::Account, Error> {
|
||||
Ok(*spl_token::state::unpack(&mut info.data.borrow_mut())
|
||||
Ok(spl_token::pack::Pack::unpack(&mut info.data.borrow_mut())
|
||||
.map_err(|_| Error::ExpectedAccount)?)
|
||||
}
|
||||
|
||||
/// Deserializes a spl_token `Mint`.
|
||||
pub fn mint_deserialize(info: &AccountInfo) -> Result<spl_token::state::Mint, Error> {
|
||||
Ok(*spl_token::state::unpack(&mut info.data.borrow_mut())
|
||||
Ok(spl_token::pack::Pack::unpack(&mut info.data.borrow_mut())
|
||||
.map_err(|_| Error::ExpectedToken)?)
|
||||
}
|
||||
|
||||
|
|
|
@ -1,47 +0,0 @@
|
|||
use solana_sdk::program_error::ProgramError;
|
||||
|
||||
use crate::error::Error;
|
||||
|
||||
#[repr(C)]
|
||||
pub struct EcrecoverInput {
|
||||
pub r: [u8; 32],
|
||||
pub s: [u8; 32],
|
||||
pub v: u8,
|
||||
pub message: [u8; 32],
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
pub struct EcrecoverOutput {
|
||||
pub address: [u8; 20],
|
||||
}
|
||||
|
||||
impl EcrecoverInput {
|
||||
pub fn new(r: [u8; 32], s: [u8; 32], v: u8, message: [u8; 32]) -> EcrecoverInput {
|
||||
EcrecoverInput { r, s, v, message }
|
||||
}
|
||||
}
|
||||
|
||||
/// Verify an ETH optimized Schnorr signature
|
||||
///
|
||||
/// @param input - Input for signature verification
|
||||
//#[cfg(target_arch = "bpf")]
|
||||
#[inline]
|
||||
pub fn sol_syscall_ecrecover(input: &EcrecoverInput) -> Result<EcrecoverOutput, Error> {
|
||||
let mut output = EcrecoverOutput { address: [0; 20] };
|
||||
let res = unsafe {
|
||||
sol_ecrecover(
|
||||
input as *const _ as *const u8,
|
||||
(&mut output) as *mut _ as *mut u8,
|
||||
)
|
||||
};
|
||||
if res == 1 {
|
||||
Ok(output)
|
||||
} else {
|
||||
Err(Error::InvalidVAASignature)
|
||||
}
|
||||
}
|
||||
|
||||
//#[cfg(target_arch = "bpf")]
|
||||
extern "C" {
|
||||
fn sol_ecrecover(input: *const u8, output: *mut u8) -> u64;
|
||||
}
|
|
@ -3,11 +3,9 @@ use std::io::{Cursor, Read, Write};
|
|||
use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
|
||||
use primitive_types::U256;
|
||||
use sha3::Digest;
|
||||
use solana_sdk::program_error::ProgramError;
|
||||
|
||||
use crate::error::Error;
|
||||
use crate::error::Error::InvalidVAAFormat;
|
||||
use crate::state::AssetMeta;
|
||||
use crate::syscalls::{sol_syscall_ecrecover, EcrecoverInput, EcrecoverOutput};
|
||||
use crate::{error::Error, state::AssetMeta};
|
||||
|
||||
pub type ForeignAddress = [u8; 32];
|
||||
|
||||
|
@ -42,50 +40,19 @@ impl VAA {
|
|||
};
|
||||
}
|
||||
|
||||
pub fn verify(&self, guardian_keys: &[[u8; 20]]) -> bool {
|
||||
pub fn body_hash(&self) -> Result<[u8; 32], ProgramError> {
|
||||
let body = match self.signature_body() {
|
||||
Ok(v) => v,
|
||||
Err(_) => {
|
||||
return false;
|
||||
return Err(ProgramError::InvalidArgument);
|
||||
}
|
||||
};
|
||||
|
||||
let mut h = sha3::Keccak256::default();
|
||||
if let Err(_) = h.write(body.as_slice()) {
|
||||
return false;
|
||||
return Err(ProgramError::InvalidArgument);
|
||||
};
|
||||
let hash = h.finalize().into();
|
||||
|
||||
// Check quorum
|
||||
if self.signatures.len() < (((guardian_keys.len() / 4) * 3) + 1 as usize) {
|
||||
return false;
|
||||
}
|
||||
|
||||
let mut last_index: i16 = -1;
|
||||
for sig in self.signatures.iter() {
|
||||
// Prevent multiple sinatures by the same guardian
|
||||
if sig.index as i16 <= last_index {
|
||||
return false;
|
||||
}
|
||||
last_index = sig.index as i16;
|
||||
|
||||
let ecrecover_input = EcrecoverInput::new(sig.r, sig.s, sig.v, hash);
|
||||
let res = match sol_syscall_ecrecover(&ecrecover_input) {
|
||||
Ok(v) => v,
|
||||
Err(_) => {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
if sig.index >= guardian_keys.len() as u8 {
|
||||
return false;
|
||||
}
|
||||
if res.address != guardian_keys[sig.index as usize] {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
true
|
||||
Ok(h.finalize().into())
|
||||
}
|
||||
|
||||
pub fn serialize(&self) -> Result<Vec<u8>, Error> {
|
||||
|
@ -136,7 +103,7 @@ impl VAA {
|
|||
|
||||
let len_sig = rdr.read_u8()?;
|
||||
let mut sigs: Vec<Signature> = Vec::with_capacity(len_sig as usize);
|
||||
for i in 0..len_sig {
|
||||
for _i in 0..len_sig {
|
||||
let mut sig = Signature::default();
|
||||
|
||||
sig.index = rdr.read_u8()?;
|
||||
|
@ -302,8 +269,10 @@ mod tests {
|
|||
use hex;
|
||||
use primitive_types::U256;
|
||||
|
||||
use crate::state::AssetMeta;
|
||||
use crate::vaa::{BodyTransfer, BodyUpdateGuardianSet, Signature, VAABody, VAA};
|
||||
use crate::{
|
||||
state::AssetMeta,
|
||||
vaa::{BodyTransfer, BodyUpdateGuardianSet, Signature, VAABody, VAA},
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn serialize_deserialize_vaa_transfer() {
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -8,15 +8,14 @@ edition = "2018"
|
|||
|
||||
[dependencies]
|
||||
clap = "2.33.0"
|
||||
solana-clap-utils = { version = "1.3.3"}
|
||||
solana-cli-config = { version = "1.3.3" }
|
||||
solana-logger = { version = "1.3.3" }
|
||||
solana-sdk = { version = "1.3.3" }
|
||||
solana-client = { version = "=1.3.3" }
|
||||
solana-faucet = "1.3.3"
|
||||
solana-transaction-status = "1.3.3"
|
||||
solana-account-decoder = { version = "1.3.3" }
|
||||
spl-token = { package = "spl-token", git = "https://github.com/solana-labs/solana-program-library" }
|
||||
solana-clap-utils = { version = "1.3.11"}
|
||||
solana-cli-config = { version = "1.3.11" }
|
||||
solana-logger = { version = "1.3.11" }
|
||||
solana-sdk = { version = "1.3.11" }
|
||||
solana-client = { version = "1.3.11" }
|
||||
solana-faucet = "1.3.11"
|
||||
solana-account-decoder = { version = "1.3.11" }
|
||||
spl-token = "=2.0.3"
|
||||
wormhole-bridge = { path = "../bridge" }
|
||||
primitive-types = {version ="0.7.2"}
|
||||
hex = "0.4.2"
|
||||
|
|
|
@ -1,14 +1,15 @@
|
|||
use crate::CommmandResult;
|
||||
use std::{error, net::SocketAddr, thread::sleep, time::Duration};
|
||||
|
||||
use solana_client::rpc_client::RpcClient;
|
||||
use solana_faucet::faucet::request_airdrop_transaction;
|
||||
use solana_sdk::hash::Hash;
|
||||
use solana_sdk::pubkey::Pubkey;
|
||||
use solana_sdk::signature::{Signature, Signer, SignerError};
|
||||
use solana_sdk::transaction::Transaction;
|
||||
use std::error;
|
||||
use std::net::SocketAddr;
|
||||
use std::thread::sleep;
|
||||
use std::time::Duration;
|
||||
use solana_sdk::{
|
||||
hash::Hash,
|
||||
pubkey::Pubkey,
|
||||
signature::{Signature, Signer, SignerError},
|
||||
transaction::Transaction,
|
||||
};
|
||||
|
||||
use crate::CommmandResult;
|
||||
|
||||
// Quick and dirty Keypair that assumes the client will do retries but not update the
|
||||
// blockhash. If the client updates the blockhash, the signature will be invalid.
|
||||
|
|
|
@ -1,7 +1,4 @@
|
|||
use std::fmt::Display;
|
||||
use std::net::{SocketAddr, ToSocketAddrs};
|
||||
use std::str::FromStr;
|
||||
use std::{mem::size_of, process::exit};
|
||||
use std::{fmt::Display, mem::size_of, net::ToSocketAddrs, process::exit};
|
||||
|
||||
use clap::{
|
||||
crate_description, crate_name, crate_version, value_t, value_t_or_exit, App, AppSettings, Arg,
|
||||
|
@ -10,16 +7,13 @@ use clap::{
|
|||
use hex;
|
||||
use primitive_types::U256;
|
||||
use solana_account_decoder::{parse_token::TokenAccountType, UiAccountData};
|
||||
use solana_clap_utils::input_parsers::value_of;
|
||||
use solana_clap_utils::input_validators::is_derivation;
|
||||
use solana_clap_utils::{
|
||||
input_parsers::{keypair_of, pubkey_of},
|
||||
input_parsers::{keypair_of, pubkey_of, value_of},
|
||||
input_validators::{is_amount, is_keypair, is_pubkey_or_keypair, is_url},
|
||||
};
|
||||
use solana_client::client_error::ClientError;
|
||||
use solana_client::rpc_config::RpcSendTransactionConfig;
|
||||
use solana_client::{rpc_client::RpcClient, rpc_request::TokenAccountsFilter};
|
||||
use solana_sdk::system_instruction::create_account;
|
||||
use solana_client::{
|
||||
rpc_client::RpcClient, rpc_config::RpcSendTransactionConfig, rpc_request::TokenAccountsFilter,
|
||||
};
|
||||
use solana_sdk::{
|
||||
commitment_config::CommitmentConfig,
|
||||
native_token::*,
|
||||
|
@ -32,11 +26,11 @@ use spl_token::{
|
|||
self,
|
||||
instruction::*,
|
||||
native_mint,
|
||||
pack::Pack,
|
||||
state::{Account, Mint},
|
||||
};
|
||||
|
||||
use spl_bridge::instruction::*;
|
||||
use spl_bridge::state::*;
|
||||
use spl_bridge::{instruction::*, state::*};
|
||||
|
||||
use crate::faucet::request_and_confirm_airdrop;
|
||||
|
||||
|
@ -79,7 +73,8 @@ fn command_deploy_bridge(
|
|||
let (recent_blockhash, fee_calculator) = config.rpc_client.get_recent_blockhash()?;
|
||||
check_fee_payer_balance(
|
||||
config,
|
||||
minimum_balance_for_rent_exemption + fee_calculator.calculate_fee(&transaction.message()),
|
||||
minimum_balance_for_rent_exemption
|
||||
+ fee_calculator.calculate_fee(&transaction.message(), None),
|
||||
)?;
|
||||
transaction.sign(&[&config.fee_payer, &config.owner], recent_blockhash);
|
||||
Ok(Some(transaction))
|
||||
|
@ -92,7 +87,10 @@ fn command_poke_proposal(config: &Config, bridge: &Pubkey, proposal: &Pubkey) ->
|
|||
let mut transaction = Transaction::new_with_payer(&[ix], Some(&config.fee_payer.pubkey()));
|
||||
|
||||
let (recent_blockhash, fee_calculator) = config.rpc_client.get_recent_blockhash()?;
|
||||
check_fee_payer_balance(config, fee_calculator.calculate_fee(&transaction.message()))?;
|
||||
check_fee_payer_balance(
|
||||
config,
|
||||
fee_calculator.calculate_fee(&transaction.message(), None),
|
||||
)?;
|
||||
transaction.sign(&[&config.fee_payer, &config.owner], recent_blockhash);
|
||||
Ok(Some(transaction))
|
||||
}
|
||||
|
@ -127,14 +125,14 @@ fn command_lock_tokens(
|
|||
decimals: 0,
|
||||
}
|
||||
}
|
||||
Err(e) => AssetMeta {
|
||||
Err(_e) => AssetMeta {
|
||||
address: token.to_bytes(),
|
||||
chain: CHAIN_ID_SOLANA,
|
||||
decimals: 0,
|
||||
},
|
||||
};
|
||||
|
||||
let mut instructions = vec![
|
||||
let instructions = vec![
|
||||
approve(
|
||||
&spl_token::id(),
|
||||
&account,
|
||||
|
@ -169,27 +167,8 @@ fn command_lock_tokens(
|
|||
let (recent_blockhash, fee_calculator) = config.rpc_client.get_recent_blockhash()?;
|
||||
check_fee_payer_balance(
|
||||
config,
|
||||
minimum_balance_for_rent_exemption + fee_calculator.calculate_fee(&transaction.message()),
|
||||
)?;
|
||||
transaction.sign(&[&config.fee_payer, &config.owner], recent_blockhash);
|
||||
Ok(Some(transaction))
|
||||
}
|
||||
|
||||
fn command_submit_vaa(config: &Config, bridge: &Pubkey, vaa: &[u8]) -> CommmandResult {
|
||||
println!("Submitting VAA");
|
||||
|
||||
let minimum_balance_for_rent_exemption = config
|
||||
.rpc_client
|
||||
.get_minimum_balance_for_rent_exemption(size_of::<Mint>())?;
|
||||
|
||||
let ix = post_vaa(bridge, &config.owner.pubkey(), vaa.to_vec())?;
|
||||
|
||||
let mut transaction = Transaction::new_with_payer(&[ix], Some(&config.fee_payer.pubkey()));
|
||||
|
||||
let (recent_blockhash, fee_calculator) = config.rpc_client.get_recent_blockhash()?;
|
||||
check_fee_payer_balance(
|
||||
config,
|
||||
minimum_balance_for_rent_exemption + fee_calculator.calculate_fee(&transaction.message()),
|
||||
minimum_balance_for_rent_exemption
|
||||
+ fee_calculator.calculate_fee(&transaction.message(), None),
|
||||
)?;
|
||||
transaction.sign(&[&config.fee_payer, &config.owner], recent_blockhash);
|
||||
Ok(Some(transaction))
|
||||
|
@ -231,7 +210,7 @@ fn command_create_token(config: &Config, decimals: u8) -> CommmandResult {
|
|||
|
||||
let minimum_balance_for_rent_exemption = config
|
||||
.rpc_client
|
||||
.get_minimum_balance_for_rent_exemption(size_of::<Mint>())?;
|
||||
.get_minimum_balance_for_rent_exemption(Mint::LEN)?;
|
||||
|
||||
let mut transaction = Transaction::new_with_payer(
|
||||
&[
|
||||
|
@ -239,15 +218,14 @@ fn command_create_token(config: &Config, decimals: u8) -> CommmandResult {
|
|||
&config.fee_payer.pubkey(),
|
||||
&token.pubkey(),
|
||||
minimum_balance_for_rent_exemption,
|
||||
size_of::<Mint>() as u64,
|
||||
Mint::LEN as u64,
|
||||
&spl_token::id(),
|
||||
),
|
||||
initialize_mint(
|
||||
&spl_token::id(),
|
||||
&token.pubkey(),
|
||||
&config.owner.pubkey(),
|
||||
None,
|
||||
Some(&config.owner.pubkey()),
|
||||
0,
|
||||
decimals,
|
||||
)?,
|
||||
],
|
||||
|
@ -257,7 +235,8 @@ fn command_create_token(config: &Config, decimals: u8) -> CommmandResult {
|
|||
let (recent_blockhash, fee_calculator) = config.rpc_client.get_recent_blockhash()?;
|
||||
check_fee_payer_balance(
|
||||
config,
|
||||
minimum_balance_for_rent_exemption + fee_calculator.calculate_fee(&transaction.message()),
|
||||
minimum_balance_for_rent_exemption
|
||||
+ fee_calculator.calculate_fee(&transaction.message(), None),
|
||||
)?;
|
||||
transaction.sign(
|
||||
&[&config.fee_payer, &config.owner, &token],
|
||||
|
@ -272,7 +251,7 @@ fn command_create_account(config: &Config, token: Pubkey) -> CommmandResult {
|
|||
|
||||
let minimum_balance_for_rent_exemption = config
|
||||
.rpc_client
|
||||
.get_minimum_balance_for_rent_exemption(size_of::<Account>())?;
|
||||
.get_minimum_balance_for_rent_exemption(Account::LEN)?;
|
||||
|
||||
let mut transaction = Transaction::new_with_payer(
|
||||
&[
|
||||
|
@ -280,7 +259,7 @@ fn command_create_account(config: &Config, token: Pubkey) -> CommmandResult {
|
|||
&config.fee_payer.pubkey(),
|
||||
&account.pubkey(),
|
||||
minimum_balance_for_rent_exemption,
|
||||
size_of::<Account>() as u64,
|
||||
Account::LEN as u64,
|
||||
&spl_token::id(),
|
||||
),
|
||||
initialize_account(
|
||||
|
@ -296,7 +275,8 @@ fn command_create_account(config: &Config, token: Pubkey) -> CommmandResult {
|
|||
let (recent_blockhash, fee_calculator) = config.rpc_client.get_recent_blockhash()?;
|
||||
check_fee_payer_balance(
|
||||
config,
|
||||
minimum_balance_for_rent_exemption + fee_calculator.calculate_fee(&transaction.message()),
|
||||
minimum_balance_for_rent_exemption
|
||||
+ fee_calculator.calculate_fee(&transaction.message(), None),
|
||||
)?;
|
||||
transaction.sign(
|
||||
&[&config.fee_payer, &config.owner, &account],
|
||||
|
@ -314,10 +294,11 @@ fn command_assign(config: &Config, account: Pubkey, new_owner: Pubkey) -> Commma
|
|||
);
|
||||
|
||||
let mut transaction = Transaction::new_with_payer(
|
||||
&[set_owner(
|
||||
&[spl_token::instruction::set_authority(
|
||||
&spl_token::id(),
|
||||
&account,
|
||||
&new_owner,
|
||||
Some(&new_owner),
|
||||
AuthorityType::AccountOwner,
|
||||
&config.owner.pubkey(),
|
||||
&[],
|
||||
)?],
|
||||
|
@ -325,7 +306,10 @@ fn command_assign(config: &Config, account: Pubkey, new_owner: Pubkey) -> Commma
|
|||
);
|
||||
|
||||
let (recent_blockhash, fee_calculator) = config.rpc_client.get_recent_blockhash()?;
|
||||
check_fee_payer_balance(config, fee_calculator.calculate_fee(&transaction.message()))?;
|
||||
check_fee_payer_balance(
|
||||
config,
|
||||
fee_calculator.calculate_fee(&transaction.message(), None),
|
||||
)?;
|
||||
transaction.sign(&[&config.fee_payer, &config.owner], recent_blockhash);
|
||||
Ok(Some(transaction))
|
||||
}
|
||||
|
@ -361,7 +345,10 @@ fn command_transfer(
|
|||
);
|
||||
|
||||
let (recent_blockhash, fee_calculator) = config.rpc_client.get_recent_blockhash()?;
|
||||
check_fee_payer_balance(config, fee_calculator.calculate_fee(&transaction.message()))?;
|
||||
check_fee_payer_balance(
|
||||
config,
|
||||
fee_calculator.calculate_fee(&transaction.message(), None),
|
||||
)?;
|
||||
transaction.sign(&[&config.fee_payer, &config.owner], recent_blockhash);
|
||||
Ok(Some(transaction))
|
||||
}
|
||||
|
@ -373,12 +360,19 @@ fn command_burn(config: &Config, source: Pubkey, ui_amount: f64) -> CommmandResu
|
|||
.rpc_client
|
||||
.get_token_account_balance_with_commitment(&source, config.commitment_config)?
|
||||
.value;
|
||||
|
||||
let source_account = config
|
||||
.rpc_client
|
||||
.get_account_with_commitment(&source, config.commitment_config)?
|
||||
.value
|
||||
.unwrap_or_default();
|
||||
let data = source_account.data.to_vec();
|
||||
let mint_pubkey = Account::unpack_from_slice(&data)?.mint;
|
||||
let amount = spl_token::ui_amount_to_amount(ui_amount, source_token_balance.decimals);
|
||||
let mut transaction = Transaction::new_with_payer(
|
||||
&[burn(
|
||||
&spl_token::id(),
|
||||
&source,
|
||||
&mint_pubkey,
|
||||
&config.owner.pubkey(),
|
||||
&[],
|
||||
amount,
|
||||
|
@ -387,7 +381,10 @@ fn command_burn(config: &Config, source: Pubkey, ui_amount: f64) -> CommmandResu
|
|||
);
|
||||
|
||||
let (recent_blockhash, fee_calculator) = config.rpc_client.get_recent_blockhash()?;
|
||||
check_fee_payer_balance(config, fee_calculator.calculate_fee(&transaction.message()))?;
|
||||
check_fee_payer_balance(
|
||||
config,
|
||||
fee_calculator.calculate_fee(&transaction.message(), None),
|
||||
)?;
|
||||
transaction.sign(&[&config.fee_payer, &config.owner], recent_blockhash);
|
||||
Ok(Some(transaction))
|
||||
}
|
||||
|
@ -422,7 +419,10 @@ fn command_mint(
|
|||
);
|
||||
|
||||
let (recent_blockhash, fee_calculator) = config.rpc_client.get_recent_blockhash()?;
|
||||
check_fee_payer_balance(config, fee_calculator.calculate_fee(&transaction.message()))?;
|
||||
check_fee_payer_balance(
|
||||
config,
|
||||
fee_calculator.calculate_fee(&transaction.message(), None),
|
||||
)?;
|
||||
transaction.sign(&[&config.fee_payer, &config.owner], recent_blockhash);
|
||||
Ok(Some(transaction))
|
||||
}
|
||||
|
@ -453,7 +453,10 @@ fn command_wrap(config: &Config, sol: f64) -> CommmandResult {
|
|||
|
||||
let (recent_blockhash, fee_calculator) = config.rpc_client.get_recent_blockhash()?;
|
||||
check_owner_balance(config, lamports)?;
|
||||
check_fee_payer_balance(config, fee_calculator.calculate_fee(&transaction.message()))?;
|
||||
check_fee_payer_balance(
|
||||
config,
|
||||
fee_calculator.calculate_fee(&transaction.message(), None),
|
||||
)?;
|
||||
transaction.sign(
|
||||
&[&config.fee_payer, &config.owner, &account],
|
||||
recent_blockhash,
|
||||
|
@ -486,7 +489,10 @@ fn command_unwrap(config: &Config, address: Pubkey) -> CommmandResult {
|
|||
);
|
||||
|
||||
let (recent_blockhash, fee_calculator) = config.rpc_client.get_recent_blockhash()?;
|
||||
check_fee_payer_balance(config, fee_calculator.calculate_fee(&transaction.message()))?;
|
||||
check_fee_payer_balance(
|
||||
config,
|
||||
fee_calculator.calculate_fee(&transaction.message(), None),
|
||||
)?;
|
||||
transaction.sign(&[&config.fee_payer, &config.owner], recent_blockhash);
|
||||
Ok(Some(transaction))
|
||||
}
|
||||
|
@ -1148,12 +1154,6 @@ fn main() {
|
|||
&config, &bridge, account, token, amount, chain, recipient, nonce,
|
||||
)
|
||||
}
|
||||
("postvaa", Some(arg_matches)) => {
|
||||
let bridge = pubkey_of(arg_matches, "bridge").unwrap();
|
||||
let vaa_string: String = value_of(arg_matches, "vaa").unwrap();
|
||||
let vaa = hex::decode(vaa_string).unwrap();
|
||||
command_submit_vaa(&config, &bridge, vaa.as_slice())
|
||||
}
|
||||
("poke", Some(arg_matches)) => {
|
||||
let bridge = pubkey_of(arg_matches, "bridge").unwrap();
|
||||
let proposal = pubkey_of(arg_matches, "proposal").unwrap();
|
||||
|
@ -1189,6 +1189,7 @@ fn main() {
|
|||
RpcSendTransactionConfig {
|
||||
// TODO: move to https://github.com/solana-labs/solana/pull/11792
|
||||
skip_preflight: true,
|
||||
preflight_commitment: None,
|
||||
},
|
||||
)?;
|
||||
println!("Signature: {}", signature);
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
merge_imports = true
|
|
@ -1,722 +1,3 @@
|
|||
Index: Cargo.lock
|
||||
IDEA additional info:
|
||||
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
|
||||
<+>UTF-8
|
||||
===================================================================
|
||||
--- Cargo.lock (revision c8b40da7abf11c1d880436b0b3ed51261900a986)
|
||||
+++ Cargo.lock (revision d0901065ee3d9b73483be7d8101ef124a2ee7dda)
|
||||
@@ -274,12 +274,22 @@
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c0940dc441f31689269e10ac70eb1002a3a1d3ad1390e030043662eb7fe4688b"
|
||||
dependencies = [
|
||||
- "block-padding",
|
||||
+ "block-padding 0.1.5",
|
||||
"byte-tools",
|
||||
"byteorder",
|
||||
"generic-array 0.12.3",
|
||||
]
|
||||
|
||||
+[[package]]
|
||||
+name = "block-buffer"
|
||||
+version = "0.9.0"
|
||||
+source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
+checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4"
|
||||
+dependencies = [
|
||||
+ "block-padding 0.2.1",
|
||||
+ "generic-array 0.14.3",
|
||||
+]
|
||||
+
|
||||
[[package]]
|
||||
name = "block-padding"
|
||||
version = "0.1.5"
|
||||
@@ -289,6 +299,12 @@
|
||||
"byte-tools",
|
||||
]
|
||||
|
||||
+[[package]]
|
||||
+name = "block-padding"
|
||||
+version = "0.2.1"
|
||||
+source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
+checksum = "8d696c370c750c948ada61c69a0ee2cbbb9c50b1019ddb86d9317157a99c2cae"
|
||||
+
|
||||
[[package]]
|
||||
name = "bs58"
|
||||
version = "0.3.1"
|
||||
@@ -633,6 +649,12 @@
|
||||
"lazy_static",
|
||||
]
|
||||
|
||||
+[[package]]
|
||||
+name = "crunchy"
|
||||
+version = "0.2.2"
|
||||
+source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
+checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7"
|
||||
+
|
||||
[[package]]
|
||||
name = "crypto-mac"
|
||||
version = "0.7.0"
|
||||
@@ -682,7 +704,7 @@
|
||||
checksum = "5d85653f070353a16313d0046f173f70d1aadd5b42600a14de626f0dfb3473a5"
|
||||
dependencies = [
|
||||
"byteorder",
|
||||
- "digest",
|
||||
+ "digest 0.8.1",
|
||||
"rand_core 0.5.1",
|
||||
"subtle 2.2.2",
|
||||
"zeroize",
|
||||
@@ -725,6 +747,15 @@
|
||||
"generic-array 0.12.3",
|
||||
]
|
||||
|
||||
+[[package]]
|
||||
+name = "digest"
|
||||
+version = "0.9.0"
|
||||
+source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
+checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066"
|
||||
+dependencies = [
|
||||
+ "generic-array 0.14.3",
|
||||
+]
|
||||
+
|
||||
[[package]]
|
||||
name = "dir-diff"
|
||||
version = "0.3.2"
|
||||
@@ -1316,7 +1347,18 @@
|
||||
checksum = "5dcb5e64cda4c23119ab41ba960d1e170a774c8e4b9d9e6a9bc18aabf5e59695"
|
||||
dependencies = [
|
||||
"crypto-mac",
|
||||
- "digest",
|
||||
+ "digest 0.8.1",
|
||||
+]
|
||||
+
|
||||
+[[package]]
|
||||
+name = "hmac-drbg"
|
||||
+version = "0.2.0"
|
||||
+source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
+checksum = "c6e570451493f10f6581b48cdd530413b63ea9e780f544bfd3bdcaa0d89d1a7b"
|
||||
+dependencies = [
|
||||
+ "digest 0.8.1",
|
||||
+ "generic-array 0.12.3",
|
||||
+ "hmac",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1750,6 +1792,12 @@
|
||||
"ws",
|
||||
]
|
||||
|
||||
+[[package]]
|
||||
+name = "keccak"
|
||||
+version = "0.1.0"
|
||||
+source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
+checksum = "67c21572b4949434e4fc1e1978b99c5f77064153c59d998bf13ecd96fb5ecba7"
|
||||
+
|
||||
[[package]]
|
||||
name = "kernel32-sys"
|
||||
version = "0.2.2"
|
||||
@@ -1818,6 +1866,22 @@
|
||||
"libc",
|
||||
]
|
||||
|
||||
+[[package]]
|
||||
+name = "libsecp256k1"
|
||||
+version = "0.3.5"
|
||||
+source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
+checksum = "1fc1e2c808481a63dc6da2074752fdd4336a3c8fcc68b83db6f1fd5224ae7962"
|
||||
+dependencies = [
|
||||
+ "arrayref",
|
||||
+ "crunchy",
|
||||
+ "digest 0.8.1",
|
||||
+ "hmac-drbg",
|
||||
+ "rand 0.7.3",
|
||||
+ "sha2",
|
||||
+ "subtle 2.2.2",
|
||||
+ "typenum",
|
||||
+]
|
||||
+
|
||||
[[package]]
|
||||
name = "linked-hash-map"
|
||||
version = "0.5.3"
|
||||
@@ -2146,6 +2210,12 @@
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2839e79665f131bdb5782e51f2c6c9599c133c6098982a54c794358bf432529c"
|
||||
|
||||
+[[package]]
|
||||
+name = "opaque-debug"
|
||||
+version = "0.3.0"
|
||||
+source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
+checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5"
|
||||
+
|
||||
[[package]]
|
||||
name = "openssl"
|
||||
version = "0.10.29"
|
||||
@@ -3098,10 +3168,10 @@
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f7d94d0bede923b3cea61f3f1ff57ff8cdfd77b400fb8f9998949e0cf04163df"
|
||||
dependencies = [
|
||||
- "block-buffer",
|
||||
- "digest",
|
||||
+ "block-buffer 0.7.3",
|
||||
+ "digest 0.8.1",
|
||||
"fake-simd",
|
||||
- "opaque-debug",
|
||||
+ "opaque-debug 0.2.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3116,10 +3186,22 @@
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a256f46ea78a0c0d9ff00077504903ac881a1dafdc20da66545699e7776b3e69"
|
||||
dependencies = [
|
||||
- "block-buffer",
|
||||
- "digest",
|
||||
+ "block-buffer 0.7.3",
|
||||
+ "digest 0.8.1",
|
||||
"fake-simd",
|
||||
- "opaque-debug",
|
||||
+ "opaque-debug 0.2.3",
|
||||
+]
|
||||
+
|
||||
+[[package]]
|
||||
+name = "sha3"
|
||||
+version = "0.9.1"
|
||||
+source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
+checksum = "f81199417d4e5de3f04b1e871023acea7389672c4135918f05aa9cbf2f2fa809"
|
||||
+dependencies = [
|
||||
+ "block-buffer 0.9.0",
|
||||
+ "digest 0.9.0",
|
||||
+ "keccak",
|
||||
+ "opaque-debug 0.3.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3376,12 +3458,16 @@
|
||||
name = "solana-bpf-loader-program"
|
||||
version = "1.4.0"
|
||||
dependencies = [
|
||||
+ "arrayref",
|
||||
"bincode",
|
||||
"byteorder",
|
||||
+ "hex",
|
||||
+ "libsecp256k1",
|
||||
"num-derive 0.3.0",
|
||||
"num-traits",
|
||||
"rand 0.7.3",
|
||||
"rustversion",
|
||||
+ "sha3",
|
||||
"solana-runtime",
|
||||
"solana-sdk 1.4.0",
|
||||
"solana_rbpf",
|
||||
Index: programs/bpf_loader/Cargo.toml
|
||||
IDEA additional info:
|
||||
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
|
||||
<+>UTF-8
|
||||
===================================================================
|
||||
--- programs/bpf_loader/Cargo.toml (revision c8b40da7abf11c1d880436b0b3ed51261900a986)
|
||||
+++ programs/bpf_loader/Cargo.toml (revision d0901065ee3d9b73483be7d8101ef124a2ee7dda)
|
||||
@@ -17,6 +17,10 @@
|
||||
solana-sdk = { path = "../../sdk", version = "1.4.0" }
|
||||
solana_rbpf = "=0.1.28"
|
||||
thiserror = "1.0"
|
||||
+libsecp256k1 = "0.3.5"
|
||||
+sha3 = "0.9.1"
|
||||
+arrayref = "0.3.6"
|
||||
+hex = "0.4.2"
|
||||
|
||||
[dev-dependencies]
|
||||
rand = "0.7.3"
|
||||
Index: programs/bpf_loader/src/crypto.rs
|
||||
IDEA additional info:
|
||||
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
|
||||
<+>UTF-8
|
||||
===================================================================
|
||||
--- programs/bpf_loader/src/crypto.rs (revision d0901065ee3d9b73483be7d8101ef124a2ee7dda)
|
||||
+++ programs/bpf_loader/src/crypto.rs (revision d0901065ee3d9b73483be7d8101ef124a2ee7dda)
|
||||
@@ -0,0 +1,171 @@
|
||||
+pub extern crate secp256k1;
|
||||
+
|
||||
+use std::io::Write;
|
||||
+
|
||||
+use num_traits::AsPrimitive;
|
||||
+use secp256k1::curve::{Affine, ECMULT_CONTEXT, ECMULT_GEN_CONTEXT, ECMultGenContext, Field, Jacobian, Scalar};
|
||||
+use sha3::Digest;
|
||||
+
|
||||
+use self::secp256k1::{Error, PublicKey};
|
||||
+
|
||||
+pub enum SchnorrError {
|
||||
+ InvalidPubKey
|
||||
+}
|
||||
+
|
||||
+#[repr(C)]
|
||||
+pub struct EcrecoverInput {
|
||||
+ pub signature: [u8; 65],
|
||||
+ pub message: [u8; 32],
|
||||
+}
|
||||
+
|
||||
+#[repr(C)]
|
||||
+pub struct EcrecoverOutput {
|
||||
+ pub address: [u8; 20],
|
||||
+}
|
||||
+
|
||||
+#[repr(C)]
|
||||
+pub struct SchnorrifyInput {
|
||||
+ message: [u8; 32],
|
||||
+ addr: [u8; 20],
|
||||
+ signature: [u8; 32],
|
||||
+ pub_key: [u8; 64],
|
||||
+}
|
||||
+
|
||||
+impl SchnorrifyInput {
|
||||
+ pub fn verify(&self) -> bool {
|
||||
+ let sig = SchnorrSignature {
|
||||
+ address: self.addr,
|
||||
+ signature: self.signature,
|
||||
+ };
|
||||
+
|
||||
+ let pub_key = SchnorrPublicKey::deserialize(&self.pub_key);
|
||||
+
|
||||
+ match sig.verify_signature(&self.message, &pub_key) {
|
||||
+ Ok(res) => res,
|
||||
+ Err(_) => false
|
||||
+ }
|
||||
+ }
|
||||
+}
|
||||
+
|
||||
+pub struct SchnorrPublicKey {
|
||||
+ x: Field,
|
||||
+ y: Field,
|
||||
+}
|
||||
+
|
||||
+pub struct SchnorrSignature {
|
||||
+ pub address: [u8; 20],
|
||||
+ pub signature: [u8; 32],
|
||||
+}
|
||||
+
|
||||
+impl SchnorrPublicKey {
|
||||
+ pub fn deserialize(d: &[u8; 64]) -> SchnorrPublicKey {
|
||||
+ let mut x = Field::default();
|
||||
+ let mut y = Field::default();
|
||||
+ x.set_b32(array_ref![d,0,32]);
|
||||
+ y.set_b32(array_ref![d,32,32]);
|
||||
+ x.normalize();
|
||||
+ y.normalize();
|
||||
+ SchnorrPublicKey {
|
||||
+ x,
|
||||
+ y,
|
||||
+ }
|
||||
+ }
|
||||
+}
|
||||
+
|
||||
+impl SchnorrSignature {
|
||||
+ pub fn verify_signature(&self, msg: &[u8; 32], pub_key: &SchnorrPublicKey) -> Result<bool, SchnorrError> {
|
||||
+ let mut af = Affine::default();
|
||||
+ af.set_xy(&pub_key.x, &pub_key.y);
|
||||
+ let mut pubkey_j: Jacobian = Jacobian::default();
|
||||
+ pubkey_j.set_ge(&af);
|
||||
+
|
||||
+ // Verify that the pubkey is a curve point
|
||||
+ let mut elem = Affine::default();
|
||||
+ if !elem.set_xo_var(&pub_key.x, pub_key.y.is_odd()) {
|
||||
+ return Err(SchnorrError::InvalidPubKey);
|
||||
+ }
|
||||
+ elem.y.normalize();
|
||||
+
|
||||
+ // Make sure that the ordinates are equal
|
||||
+ if elem.y.b32() != pub_key.y.b32() {
|
||||
+ return Err(SchnorrError::InvalidPubKey);
|
||||
+ }
|
||||
+
|
||||
+ // Generate the challenge
|
||||
+ let mut h = sha3::Keccak256::default();
|
||||
+ h.write(&pub_key.x.b32()); // pub key x coordinate
|
||||
+ h.write(&[(pub_key.y.is_odd()).as_()]); // y parity
|
||||
+ h.write(msg); // msg
|
||||
+ h.write(&self.address); // nonceTimesGeneratorAddress
|
||||
+ let challenge = h.finalize();
|
||||
+
|
||||
+ let mut e = Scalar::default();
|
||||
+ e.set_b32(array_ref![challenge,0,32]);
|
||||
+
|
||||
+ let mut s = Scalar::default();
|
||||
+ s.set_b32(&self.signature);
|
||||
+
|
||||
+ // Calculate s x G + e x P
|
||||
+ let mut k: Jacobian = Jacobian::default();
|
||||
+ ECMULT_CONTEXT.ecmult(&mut k, &pubkey_j, &e, &s);
|
||||
+
|
||||
+ let r = jacobian_to_normalized_affine(&k);
|
||||
+
|
||||
+ // Generate Ethereum address from calculated point
|
||||
+ let eth_addr = affine_to_eth_address(&r);
|
||||
+
|
||||
+ // Verify that addr(k) == sig.address
|
||||
+ Ok(eth_addr == self.address)
|
||||
+ }
|
||||
+}
|
||||
+
|
||||
+fn affine_to_eth_address(a: &Affine) -> [u8; 20] {
|
||||
+ let mut h = sha3::Keccak256::default();
|
||||
+ h.write(a.x.b32().as_ref()); // result key x coordinate
|
||||
+ h.write(a.y.b32().as_ref()); // result key y coordinate
|
||||
+
|
||||
+ let out = h.finalize();
|
||||
+ let mut out_addr = [0; 20];
|
||||
+ out_addr.copy_from_slice(&out[12..]);
|
||||
+
|
||||
+ out_addr
|
||||
+}
|
||||
+
|
||||
+fn jacobian_to_normalized_affine(j: &Jacobian) -> Affine {
|
||||
+ let mut r = Affine::default();
|
||||
+ r.set_gej(j);
|
||||
+ r.x.normalize();
|
||||
+ r.y.normalize();
|
||||
+
|
||||
+ r
|
||||
+}
|
||||
+
|
||||
+#[cfg(test)]
|
||||
+mod tests {
|
||||
+ use std::io::Write;
|
||||
+
|
||||
+ use hex;
|
||||
+
|
||||
+ use crate::crypto::{SchnorrPublicKey, SchnorrSignature};
|
||||
+
|
||||
+ #[test]
|
||||
+ fn verify_signature() {
|
||||
+ let msggg = hex::decode("0194fdc2fa2ffcc041d3ff12045b73c86e4ff95ff662a5eee82abdf44a2d0b75").unwrap();
|
||||
+ let siggg = hex::decode("ee5884a66454baca985f4453c05394214a75dc38956ea39f12cc429f081aae4b").unwrap();
|
||||
+ let addr = hex::decode("9addd8a38fea7e1b94550e5bc249309a633dfa63").unwrap();
|
||||
+ let pb = hex::decode("ae92ce7553993f04400c6976f8cd4540ae076bf0131eec8b35ae0ff9fc577a901de834d0f62ae6ecbeec2124595b06bce078b8133b4dda3855cf346feb2b2ca2").unwrap();
|
||||
+
|
||||
+ let sig = SchnorrSignature {
|
||||
+ address: *array_ref![addr,0,20],
|
||||
+ signature: *array_ref![siggg,0,32],
|
||||
+ };
|
||||
+
|
||||
+ let pub_key = SchnorrPublicKey::deserialize(array_ref![pb,0,64]);
|
||||
+
|
||||
+ let msg = array_ref![msggg,0,32];
|
||||
+ match sig.verify_signature(msg, &pub_key) {
|
||||
+ Ok(res) => assert!(res, "signature should be valid"),
|
||||
+ Err(err) => assert!(false, "signature verification failed")
|
||||
+ }
|
||||
+ }
|
||||
+}
|
||||
Index: programs/bpf_loader/src/lib.rs
|
||||
IDEA additional info:
|
||||
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
|
||||
<+>UTF-8
|
||||
===================================================================
|
||||
--- programs/bpf_loader/src/lib.rs (revision c8b40da7abf11c1d880436b0b3ed51261900a986)
|
||||
+++ programs/bpf_loader/src/lib.rs (revision 745a97685408ff4249d96333a7885cd214639b70)
|
||||
@@ -4,6 +4,10 @@
|
||||
pub mod deprecated;
|
||||
pub mod serialization;
|
||||
pub mod syscalls;
|
||||
+pub mod crypto;
|
||||
+
|
||||
+#[macro_use]
|
||||
+extern crate arrayref;
|
||||
|
||||
use crate::{
|
||||
bpf_verifier::VerifierError,
|
||||
@@ -65,7 +69,7 @@
|
||||
) -> Result<(EbpfVm<'a, BPFError>, MemoryRegion), EbpfError<BPFError>> {
|
||||
let mut vm = EbpfVm::new(None)?;
|
||||
vm.set_verifier(bpf_verifier::check)?;
|
||||
- vm.set_max_instruction_count(100_000)?;
|
||||
+ vm.set_max_instruction_count(1_000_000)?;
|
||||
vm.set_elf(&prog)?;
|
||||
|
||||
let heap_region = syscalls::register_syscalls(&mut vm, parameter_accounts, invoke_context)?;
|
||||
Index: programs/bpf_loader/src/syscalls.rs
|
||||
IDEA additional info:
|
||||
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
|
||||
<+>UTF-8
|
||||
===================================================================
|
||||
--- programs/bpf_loader/src/syscalls.rs (revision c8b40da7abf11c1d880436b0b3ed51261900a986)
|
||||
+++ programs/bpf_loader/src/syscalls.rs (revision d0901065ee3d9b73483be7d8101ef124a2ee7dda)
|
||||
@@ -1,10 +1,23 @@
|
||||
-use crate::{alloc, BPFError};
|
||||
-use alloc::Alloc;
|
||||
+use std::{
|
||||
+ alloc::Layout,
|
||||
+ cell::{RefCell, RefMut},
|
||||
+ convert::TryFrom,
|
||||
+ mem::{align_of, size_of},
|
||||
+ rc::Rc,
|
||||
+ slice::from_raw_parts_mut,
|
||||
+ str::{from_utf8, Utf8Error},
|
||||
+};
|
||||
+
|
||||
+use secp256k1::{Error, PublicKey, RecoveryId, Signature};
|
||||
+use sha3::Digest;
|
||||
use solana_rbpf::{
|
||||
- ebpf::{EbpfError, SyscallObject, ELF_INSN_DUMP_OFFSET, MM_HEAP_START},
|
||||
- memory_region::{translate_addr, MemoryRegion},
|
||||
+ ebpf::{EbpfError, ELF_INSN_DUMP_OFFSET, MM_HEAP_START, SyscallObject},
|
||||
EbpfVm,
|
||||
+ memory_region::{MemoryRegion, translate_addr},
|
||||
};
|
||||
+use thiserror::Error as ThisError;
|
||||
+
|
||||
+use alloc::Alloc;
|
||||
use solana_runtime::message_processor::MessageProcessor;
|
||||
use solana_sdk::{
|
||||
account::Account,
|
||||
@@ -18,16 +31,16 @@
|
||||
program_error::ProgramError,
|
||||
pubkey::{Pubkey, PubkeyError},
|
||||
};
|
||||
-use std::{
|
||||
- alloc::Layout,
|
||||
- cell::{RefCell, RefMut},
|
||||
- convert::TryFrom,
|
||||
- mem::{align_of, size_of},
|
||||
- rc::Rc,
|
||||
- slice::from_raw_parts_mut,
|
||||
- str::{from_utf8, Utf8Error},
|
||||
-};
|
||||
-use thiserror::Error as ThisError;
|
||||
+
|
||||
+use crate::{alloc, BPFError};
|
||||
+/// Program heap allocators are intended to allocate/free from a given
|
||||
+/// chunk of memory. The specific allocator implementation is
|
||||
+/// selectable at build-time.
|
||||
+/// Only one allocator is currently supported
|
||||
+
|
||||
+/// Simple bump allocator, never frees
|
||||
+use crate::allocator_bump::BPFAllocator;
|
||||
+use crate::crypto::{EcrecoverInput, EcrecoverOutput, SchnorrifyInput};
|
||||
|
||||
/// Error definitions
|
||||
#[derive(Debug, ThisError)]
|
||||
@@ -53,20 +66,13 @@
|
||||
#[error("Unaligned pointer")]
|
||||
UnalignedPointer,
|
||||
}
|
||||
+
|
||||
impl From<SyscallError> for EbpfError<BPFError> {
|
||||
fn from(error: SyscallError) -> Self {
|
||||
EbpfError::UserError(error.into())
|
||||
}
|
||||
}
|
||||
|
||||
-/// Program heap allocators are intended to allocate/free from a given
|
||||
-/// chunk of memory. The specific allocator implementation is
|
||||
-/// selectable at build-time.
|
||||
-/// Only one allocator is currently supported
|
||||
-
|
||||
-/// Simple bump allocator, never frees
|
||||
-use crate::allocator_bump::BPFAllocator;
|
||||
-
|
||||
/// Default program heap size, allocators
|
||||
/// are expected to enforce this
|
||||
const DEFAULT_HEAP_SIZE: usize = 32 * 1024;
|
||||
@@ -114,6 +120,16 @@
|
||||
)?;
|
||||
}
|
||||
|
||||
+ // Signature verification
|
||||
+ vm.register_syscall_with_context_ex(
|
||||
+ "sol_verify_ethschnorr",
|
||||
+ Box::new(SyscallSchorrify {}),
|
||||
+ )?;
|
||||
+ vm.register_syscall_with_context_ex(
|
||||
+ "sol_ecrecover",
|
||||
+ Box::new(SyscallEcrecover {}),
|
||||
+ )?;
|
||||
+
|
||||
// Memory allocator
|
||||
let heap = vec![0_u8; DEFAULT_HEAP_SIZE];
|
||||
let heap_region = MemoryRegion::new_from_slice(&heap, MM_HEAP_START);
|
||||
@@ -255,6 +271,7 @@
|
||||
pub struct SyscallLog {
|
||||
logger: Rc<RefCell<dyn Logger>>,
|
||||
}
|
||||
+
|
||||
impl SyscallObject<BPFError> for SyscallLog {
|
||||
fn call(
|
||||
&mut self,
|
||||
@@ -284,6 +301,7 @@
|
||||
pub struct SyscallLogU64 {
|
||||
logger: Rc<RefCell<dyn Logger>>,
|
||||
}
|
||||
+
|
||||
impl SyscallObject<BPFError> for SyscallLogU64 {
|
||||
fn call(
|
||||
&mut self,
|
||||
@@ -318,6 +336,7 @@
|
||||
pub struct SyscallSolAllocFree {
|
||||
allocator: BPFAllocator,
|
||||
}
|
||||
+
|
||||
impl SyscallObject<BPFError> for SyscallSolAllocFree {
|
||||
fn call(
|
||||
&mut self,
|
||||
@@ -380,6 +399,87 @@
|
||||
Ok(0)
|
||||
}
|
||||
|
||||
+/// Verify a ETH optimized Schnorr Signature
|
||||
+pub struct SyscallEcrecover {
|
||||
+}
|
||||
+
|
||||
+impl SyscallObject<BPFError> for SyscallEcrecover {
|
||||
+ fn call(
|
||||
+ &mut self,
|
||||
+ input: u64,
|
||||
+ output: u64,
|
||||
+ _arg3: u64,
|
||||
+ _arg4: u64,
|
||||
+ _arg5: u64,
|
||||
+ ro_regions: &[MemoryRegion],
|
||||
+ _rw_regions: &[MemoryRegion],
|
||||
+ ) -> Result<u64, EbpfError<BPFError>> {
|
||||
+ let input = translate_type!(
|
||||
+ EcrecoverInput,
|
||||
+ input,
|
||||
+ ro_regions
|
||||
+ )?;
|
||||
+
|
||||
+ let mut output = translate_type_mut!(
|
||||
+ EcrecoverOutput,
|
||||
+ output,
|
||||
+ ro_regions
|
||||
+ )?;
|
||||
+
|
||||
+ let signature = match secp256k1::Signature::parse_slice(&input.signature[..64]) {
|
||||
+ Ok(v) => v,
|
||||
+ Err(_) => {
|
||||
+ return Ok(0);
|
||||
+ }
|
||||
+ };
|
||||
+
|
||||
+ let recovery_id = match secp256k1::RecoveryId::parse(input.signature[64]) {
|
||||
+ Ok(v) => v,
|
||||
+ Err(_) => { return Ok(0); }
|
||||
+ };
|
||||
+
|
||||
+ match secp256k1::recover(&secp256k1::Message::parse(&input.message), &signature,
|
||||
+ &recovery_id) {
|
||||
+ Ok(v) => {
|
||||
+ let mut addr = [0u8; 20];
|
||||
+ addr.copy_from_slice(&sha3::Keccak256::digest(&v.serialize()[1..])[12..]);
|
||||
+ output.address = addr;
|
||||
+ }
|
||||
+ Err(_) => {
|
||||
+ return Ok(0);
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ Ok(1)
|
||||
+ }
|
||||
+}
|
||||
+
|
||||
+/// Verify a ETH optimized Schnorr Signature
|
||||
+pub struct SyscallSchorrify {
|
||||
+}
|
||||
+
|
||||
+impl SyscallObject<BPFError> for SyscallSchorrify {
|
||||
+ fn call(
|
||||
+ &mut self,
|
||||
+ addr: u64,
|
||||
+ _arg2: u64,
|
||||
+ _arg3: u64,
|
||||
+ _arg4: u64,
|
||||
+ _arg5: u64,
|
||||
+ ro_regions: &[MemoryRegion],
|
||||
+ _rw_regions: &[MemoryRegion],
|
||||
+ ) -> Result<u64, EbpfError<BPFError>> {
|
||||
+ let input = translate_type!(
|
||||
+ SchnorrifyInput,
|
||||
+ addr,
|
||||
+ ro_regions
|
||||
+ )?;
|
||||
+ let res = input.verify();
|
||||
+
|
||||
+ Ok(res as u64)
|
||||
+ }
|
||||
+}
|
||||
+
|
||||
// Cross-program invocation syscalls
|
||||
|
||||
struct AccountReferences<'a> {
|
||||
@@ -422,6 +522,7 @@
|
||||
callers_keyed_accounts: &'a [KeyedAccount<'a>],
|
||||
invoke_context: Rc<RefCell<&'a mut dyn InvokeContext>>,
|
||||
}
|
||||
+
|
||||
impl<'a> SyscallProcessInstruction<'a> for SyscallProcessInstructionRust<'a> {
|
||||
fn get_context_mut(&self) -> Result<RefMut<&'a mut dyn InvokeContext>, EbpfError<BPFError>> {
|
||||
self.invoke_context
|
||||
@@ -443,7 +544,7 @@
|
||||
ix.accounts.len(),
|
||||
ro_regions
|
||||
)?
|
||||
- .to_vec();
|
||||
+ .to_vec();
|
||||
let data = translate_slice!(u8, ix.data.as_ptr(), ix.data.len(), ro_regions)?.to_vec();
|
||||
Ok(Instruction {
|
||||
program_id: ix.program_id,
|
||||
@@ -558,6 +659,7 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
+
|
||||
impl<'a> SyscallObject<BPFError> for SyscallProcessInstructionRust<'a> {
|
||||
fn call(
|
||||
&mut self,
|
||||
@@ -633,6 +735,7 @@
|
||||
callers_keyed_accounts: &'a [KeyedAccount<'a>],
|
||||
invoke_context: Rc<RefCell<&'a mut dyn InvokeContext>>,
|
||||
}
|
||||
+
|
||||
impl<'a> SyscallProcessInstruction<'a> for SyscallProcessSolInstructionC<'a> {
|
||||
fn get_context_mut(&self) -> Result<RefMut<&'a mut dyn InvokeContext>, EbpfError<BPFError>> {
|
||||
self.invoke_context
|
||||
@@ -770,6 +873,7 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
+
|
||||
impl<'a> SyscallObject<BPFError> for SyscallProcessSolInstructionC<'a> {
|
||||
fn call(
|
||||
&mut self,
|
||||
@@ -821,10 +925,10 @@
|
||||
}
|
||||
|
||||
if account.is_signer && // If message indicates account is signed
|
||||
- !( // one of the following needs to be true:
|
||||
- keyed_account.signer_key().is_some() // Signed in the parent instruction
|
||||
- || signers.contains(&account.pubkey) // Signed by the program
|
||||
- ) {
|
||||
+ !( // one of the following needs to be true:
|
||||
+ keyed_account.signer_key().is_some() // Signed in the parent instruction
|
||||
+ || signers.contains(&account.pubkey) // Signed by the program
|
||||
+ ) {
|
||||
return Err(SyscallError::PrivilegeEscalation.into());
|
||||
}
|
||||
}
|
||||
@@ -1042,7 +1146,7 @@
|
||||
assert_eq!(string, "Gaggablaghblagh!");
|
||||
Ok(42)
|
||||
})
|
||||
- .unwrap()
|
||||
+ .unwrap()
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1074,7 +1178,7 @@
|
||||
&[ro_region],
|
||||
&[rw_region],
|
||||
)
|
||||
- .unwrap();
|
||||
+ .unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
Index: fetch-spl.sh
|
||||
IDEA additional info:
|
||||
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
|
||||
|
@ -754,3 +35,64 @@ Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
|
|||
Ok(TokenAccountType::Account(UiTokenAccount {
|
||||
mint: account.mint.to_string(),
|
||||
owner: account.owner.to_string(),
|
||||
Index: programs/bpf_loader/src/bpf_verifier.rs
|
||||
IDEA additional info:
|
||||
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
|
||||
<+>UTF-8
|
||||
===================================================================
|
||||
--- programs/bpf_loader/src/bpf_verifier.rs (revision 6563726f227414164c32a0373fa32b0f87fbd4e8)
|
||||
+++ programs/bpf_loader/src/bpf_verifier.rs (date 1600862128332)
|
||||
@@ -58,7 +58,7 @@
|
||||
if prog.len() % ebpf::INSN_SIZE != 0 {
|
||||
return Err(VerifierError::ProgramLengthNotMultiple.into());
|
||||
}
|
||||
- if prog.len() > ebpf::PROG_MAX_SIZE {
|
||||
+ if prog.len() > ebpf::PROG_MAX_SIZE * 2 {
|
||||
return Err(VerifierError::ProgramTooLarge(prog.len() / ebpf::INSN_SIZE).into());
|
||||
}
|
||||
|
||||
|
||||
--- core/src/rpc.rs
|
||||
+++ core/src/rpc.rs
|
||||
@@ -2210,6 +2210,10 @@ impl RpcSol for RpcSolImpl {
|
||||
return Err(RpcCustomError::TransactionSignatureVerificationFailure.into());
|
||||
}
|
||||
|
||||
+ if let Err(e) = transaction.verify_precompiles() {
|
||||
+ return Err(RpcCustomError::TransactionPrecompileVerificationFailure(e).into());
|
||||
+ }
|
||||
+
|
||||
if meta.health.check() != RpcHealthStatus::Ok {
|
||||
return Err(RpcCustomError::RpcNodeUnhealthy.into());
|
||||
}
|
||||
|
||||
--- core/src/rpc_error.rs
|
||||
+++ core/src/rpc_error.rs
|
||||
@@ -7,6 +7,7 @@ const JSON_RPC_SERVER_ERROR_2: i64 = -32002;
|
||||
const JSON_RPC_SERVER_ERROR_3: i64 = -32003;
|
||||
const JSON_RPC_SERVER_ERROR_4: i64 = -32004;
|
||||
const JSON_RPC_SERVER_ERROR_5: i64 = -32005;
|
||||
+const JSON_RPC_SERVER_ERROR_6: i64 = -32006;
|
||||
|
||||
pub enum RpcCustomError {
|
||||
BlockCleanedUp {
|
||||
@@ -22,6 +23,7 @@ pub enum RpcCustomError {
|
||||
slot: Slot,
|
||||
},
|
||||
RpcNodeUnhealthy,
|
||||
+ TransactionPrecompileVerificationFailure(solana_sdk::transaction::TransactionError),
|
||||
}
|
||||
|
||||
impl From<RpcCustomError> for Error {
|
||||
@@ -58,6 +60,11 @@ impl From<RpcCustomError> for Error {
|
||||
message: "RPC node is unhealthy".to_string(),
|
||||
data: None,
|
||||
},
|
||||
+ RpcCustomError::TransactionPrecompileVerificationFailure(e) => Self {
|
||||
+ code: ErrorCode::ServerError(JSON_RPC_SERVER_ERROR_6),
|
||||
+ message: format!("Transaction precompile verification failure {:?}", e),
|
||||
+ data: None,
|
||||
+ },
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,7 +11,7 @@ RUN rustup component add rustfmt
|
|||
WORKDIR /usr/src/solana
|
||||
|
||||
RUN git clone https://github.com/solana-labs/solana --branch master && \
|
||||
cd solana && git checkout e2d66cf7
|
||||
cd solana && git checkout 5dcf3480986a87cc9c80788c1d8ccd8f0cb44a8d
|
||||
|
||||
ADD *.patch .
|
||||
|
||||
|
|
|
@ -76,13 +76,24 @@ function TransferProposals() {
|
|||
|
||||
let executeVAA = async (v: LockupWithStatus) => {
|
||||
let wh = WormholeFactory.connect(BRIDGE_ADDRESS, signer)
|
||||
let vaa = v.vaa;
|
||||
let vaa = new Buffer(v.vaa);
|
||||
for (let i = vaa.length; i > 0; i--) {
|
||||
if (vaa[i] == 0xff) {
|
||||
vaa = vaa.slice(0, i)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
let signatures = await b.fetchSignatureStatus(v.signatureAccount);
|
||||
let sigData = Buffer.of(...signatures.reduce((previousValue, currentValue) => {
|
||||
previousValue.push(currentValue.index)
|
||||
previousValue.push(...currentValue.signature)
|
||||
|
||||
return previousValue
|
||||
}, new Array<number>()))
|
||||
|
||||
vaa = Buffer.concat([vaa.slice(0, 5), Buffer.of(signatures.length), sigData, vaa.slice(6)])
|
||||
|
||||
message.loading({content: "Signing transaction...", key: "eth_tx", duration: 1000},)
|
||||
let tx = await wh.submitVAA(vaa)
|
||||
message.loading({content: "Waiting for transaction to be mined...", key: "eth_tx", duration: 1000})
|
||||
|
|
|
@ -5,12 +5,14 @@ const WRAPPED_MASTER = "e78A0F7E598Cc8b0Bb87894B0F60dD2a88d6a8Ab"
|
|||
|
||||
|
||||
const SOLANA_BRIDGE_PROGRAM = new PublicKey("Bridge1p5gheXUvJ6jGWGeCsgPKgnE3YgdGKRVCMY9o");
|
||||
const TOKEN_PROGRAM = new PublicKey("TokenSVp5gheXUvJ6jGWGeCsgPKgnE3YgdGKRVCMY9o");
|
||||
const TOKEN_PROGRAM = new PublicKey("TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA");
|
||||
|
||||
const SOLANA_HOST = "http://localhost:8899";
|
||||
|
||||
export {
|
||||
BRIDGE_ADDRESS,
|
||||
TOKEN_PROGRAM,
|
||||
WRAPPED_MASTER,
|
||||
SOLANA_BRIDGE_PROGRAM
|
||||
SOLANA_BRIDGE_PROGRAM,
|
||||
SOLANA_HOST
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import React from 'react'
|
||||
import * as solanaWeb3 from '@solana/web3.js';
|
||||
import {SOLANA_HOST} from "../config";
|
||||
|
||||
const ClientContext = React.createContext<solanaWeb3.Connection>(new solanaWeb3.Connection("http://localhost:8899"));
|
||||
const ClientContext = React.createContext<solanaWeb3.Connection>(new solanaWeb3.Connection(SOLANA_HOST));
|
||||
export default ClientContext
|
||||
|
|
|
@ -5,7 +5,7 @@ import assert from "assert";
|
|||
// @ts-ignore
|
||||
import * as BufferLayout from 'buffer-layout'
|
||||
import {Token} from "@solana/spl-token";
|
||||
import {TOKEN_PROGRAM} from "../config";
|
||||
import {SOLANA_HOST, TOKEN_PROGRAM} from "../config";
|
||||
import * as bs58 from "bs58";
|
||||
|
||||
export interface AssetMeta {
|
||||
|
@ -26,9 +26,15 @@ export interface Lockup {
|
|||
vaa: Uint8Array,
|
||||
vaaTime: number,
|
||||
pokeCounter: number,
|
||||
signatureAccount: PublicKey,
|
||||
initialized: boolean,
|
||||
}
|
||||
|
||||
export interface Signature {
|
||||
signature: number[],
|
||||
index: number,
|
||||
}
|
||||
|
||||
export const CHAIN_ID_SOLANA = 1;
|
||||
|
||||
class SolanaBridge {
|
||||
|
@ -95,6 +101,7 @@ class SolanaBridge {
|
|||
{pubkey: this.programID, isSigner: false, isWritable: false},
|
||||
{pubkey: solanaWeb3.SystemProgram.programId, isSigner: false, isWritable: false},
|
||||
{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: tokenAccount, isSigner: false, isWritable: true},
|
||||
{pubkey: configKey, isSigner: false, isWritable: false},
|
||||
|
@ -171,11 +178,47 @@ class SolanaBridge {
|
|||
}
|
||||
}
|
||||
|
||||
// fetchSignatureStatus fetches the signatures for a VAA
|
||||
async fetchSignatureStatus(
|
||||
signatureStatus: PublicKey,
|
||||
): Promise<Signature[]> {
|
||||
let signatureInfo = await this.connection.getAccountInfo(signatureStatus);
|
||||
console.log(signatureStatus.toBase58())
|
||||
if (signatureInfo == null || signatureInfo.lamports == 0) {
|
||||
throw new Error("not found")
|
||||
} else {
|
||||
const dataLayout = BufferLayout.struct([
|
||||
BufferLayout.blob(20 * 65, 'signaturesRaw'),
|
||||
]);
|
||||
let rawSignatureInfo = dataLayout.decode(signatureInfo?.data);
|
||||
|
||||
let signatures: Signature[] = [];
|
||||
for (let i = 0; i < 20; i++) {
|
||||
let data = rawSignatureInfo.signaturesRaw.slice(65 * i, 65 * (i + 1));
|
||||
let empty = true;
|
||||
for (let v of data) {
|
||||
if (v != 0) {
|
||||
empty = false;
|
||||
break
|
||||
}
|
||||
}
|
||||
if (empty) continue;
|
||||
|
||||
signatures.push({
|
||||
signature: data,
|
||||
index: i,
|
||||
})
|
||||
}
|
||||
|
||||
return signatures;
|
||||
}
|
||||
}
|
||||
|
||||
// fetchAssetMeta fetches the AssetMeta for an SPL token
|
||||
async fetchTransferProposals(
|
||||
tokenAccount: PublicKey,
|
||||
): Promise<Lockup[]> {
|
||||
let accountRes = await fetch("http://localhost:8899", {
|
||||
let accountRes = await fetch(SOLANA_HOST, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
|
@ -186,7 +229,7 @@ class SolanaBridge {
|
|||
"method": "getProgramAccounts",
|
||||
"params": [this.programID.toString(), {
|
||||
"commitment": "single",
|
||||
"filters": [{"dataSize": 1152}, {
|
||||
"filters": [{"dataSize": 1184}, {
|
||||
"memcmp": {
|
||||
"offset": 33,
|
||||
"bytes": tokenAccount.toString()
|
||||
|
@ -210,7 +253,9 @@ class SolanaBridge {
|
|||
BufferLayout.blob(1001, 'vaa'),
|
||||
BufferLayout.seq(BufferLayout.u8(), 3), // 4 byte alignment because a u32 is following
|
||||
BufferLayout.u32('vaaTime'),
|
||||
BufferLayout.u32('lockupTime'),
|
||||
BufferLayout.u8('pokeCounter'),
|
||||
BufferLayout.blob(32, 'signatureAccount'),
|
||||
BufferLayout.u8('initialized'),
|
||||
]);
|
||||
|
||||
|
@ -230,6 +275,7 @@ class SolanaBridge {
|
|||
toChain: parsedAccount.toChain,
|
||||
vaa: parsedAccount.vaa,
|
||||
vaaTime: parsedAccount.vaaTime,
|
||||
signatureAccount: new PublicKey(parsedAccount.signatureAccount),
|
||||
pokeCounter: parsedAccount.pokeCounter
|
||||
})
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue