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
|
#### Multiple signatures - MultiSig
|
||||||
|
|
||||||
The most simple solution is by using a *MultiSig* mechanism. This means that each guardian would sign a message
|
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).
|
and submit it via a P2P gossip network.
|
||||||
Since a transaction itself is already signed, we can simplify this to using the transaction itself as proof.
|
|
||||||
|
|
||||||
Said smart contract will count the number of guardians that have submitted a transaction for a *decision*.
|
Once the consensus threshold has been reached, a guardian will aggregate all signatures into a VAA and execute/submit it
|
||||||
Once the consensus threshold has been reached, the contract will execute the action the guardians have agreed on.
|
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
|
The downside here is that gas costs increase with larger guardian sets bringing verification costs to
|
||||||
example one such transaction would cost `21k+20k+x` gas (base + `SSTORE` \[to track the tx] + additional compute).
|
`(5k+5k)*n` (`ECRECOVER+GTXDATANONZERO*72`).
|
||||||
With `n` txs and 20 guardians threshold (`2/3m+1`) the cost would be `n*(41k+x)` which is `820k+20x`.
|
|
||||||
|
|
||||||
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
|
To prevent lagging and complex gas price handling by validators or relayers, we always submit VAAs to Solana where txs
|
||||||
means costs of `12.3$`.
|
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
|
||||||
These prices will require the guardians to charge significant fees. If these fees are not covered by the user, bridge
|
check issued by the guardians (a VAA) which can be used on another chain to claim assets.
|
||||||
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.
|
|
||||||
|
|
||||||
#### Threshold signatures
|
#### Threshold signatures
|
||||||
|
|
||||||
|
@ -114,7 +90,7 @@ A great overview can be found [here](https://github.com/Turing-Chain/TSSKit-Thre
|
||||||
|
|
||||||
#### Design choices
|
#### 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
|
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).
|
between chains. We call this structure: **VAA** (Verifiable Action Approval).
|
||||||
|
|
||||||
|
@ -159,8 +135,6 @@ set.
|
||||||
|
|
||||||
ID: `0x01`
|
ID: `0x01`
|
||||||
|
|
||||||
Size: `32 byte`
|
|
||||||
|
|
||||||
Payload:
|
Payload:
|
||||||
|
|
||||||
```
|
```
|
||||||
|
@ -176,8 +150,6 @@ desynchronization between the any of the chains in the system.
|
||||||
|
|
||||||
ID: `0x10`
|
ID: `0x10`
|
||||||
|
|
||||||
Size: `75 byte`
|
|
||||||
|
|
||||||
Payload:
|
Payload:
|
||||||
|
|
||||||
```
|
```
|
||||||
|
@ -195,9 +167,6 @@ uint256 amount
|
||||||
|
|
||||||
#### Transfer of assets Foreign Chain -> Root Chain
|
#### 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 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.
|
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
|
produced VAA (`Transfer`) testifying that they have seen a foreign lockup. They will post this VAA on the root chain
|
||||||
using the `SubmitVAA` instruction.
|
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
|
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.
|
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
|
full confirmation (i.e. the max lockup, currently 32 slots), but can be changed to a different commitment levels
|
||||||
on each guardian's discretion.
|
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
|
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`.
|
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
|
This VAA will be posted on Solana by one of the guardians using the `SubmitVAA` instruction and will be stored in the
|
||||||
`LockProposal`.
|
`LockProposal`.
|
||||||
|
|
||||||
Depending on whether the fees are sufficient for **guardians** or **relayers** to cover the foreign chain fees, they
|
The user can then get the VAA from the `LockProposal` and submit it on the foreign chain.
|
||||||
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`.
|
|
||||||
|
|
||||||
### Fees
|
### Fees
|
||||||
|
|
||||||
|
@ -254,11 +218,6 @@ TODO \o/
|
||||||
### Config changes
|
### Config changes
|
||||||
#### Guardian set 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.
|
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
|
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.
|
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
|
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
|
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 | | ✅ | ️ | ✅ |
|
| 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
|
#### TransferOut
|
||||||
|
|
||||||
Burns a wrapped asset `token` from `sender` on the Solana chain.
|
Burns a wrapped asset `token` from `sender` on the Solana chain.
|
||||||
|
@ -37,12 +48,13 @@ Parameters:
|
||||||
| 0 | bridge_p | BridgeProgram | | | ️ | |
|
| 0 | bridge_p | BridgeProgram | | | ️ | |
|
||||||
| 1 | sys | SystemProgram | | | ️ | |
|
| 1 | sys | SystemProgram | | | ️ | |
|
||||||
| 2 | token_program | SplToken | | | ️ | |
|
| 2 | token_program | SplToken | | | ️ | |
|
||||||
| 3 | clock | Sysvar | | | ️ | ✅ |
|
| 3 | rent | Sysvar | | | ️ | ✅ |
|
||||||
| 4 | token_account | TokenAccount | | ✅ | | |
|
| 4 | clock | Sysvar | | | ️ | ✅ |
|
||||||
| 5 | bridge | BridgeConfig | | | | |
|
| 5 | token_account | TokenAccount | | ✅ | | |
|
||||||
| 6 | proposal | TransferOutProposal | | ✅ | ✅ | ✅ |
|
| 6 | bridge | BridgeConfig | | | | |
|
||||||
| 7 | token | WrappedAsset | | ✅ | | ✅ |
|
| 7 | proposal | TransferOutProposal | | ✅ | ✅ | ✅ |
|
||||||
| 8 | payer | Account | ✅ | | | |
|
| 8 | token | WrappedAsset | | ✅ | | ✅ |
|
||||||
|
| 9 | payer | Account | ✅ | | | |
|
||||||
|
|
||||||
#### TransferOutNative
|
#### TransferOutNative
|
||||||
|
|
||||||
|
@ -56,13 +68,14 @@ The transfer proposal will be tracked at a new account `proposal` where a VAA wi
|
||||||
| 0 | bridge_p | BridgeProgram | | | ️ | |
|
| 0 | bridge_p | BridgeProgram | | | ️ | |
|
||||||
| 1 | sys | SystemProgram | | | ️ | |
|
| 1 | sys | SystemProgram | | | ️ | |
|
||||||
| 2 | token_program | SplToken | | | ️ | |
|
| 2 | token_program | SplToken | | | ️ | |
|
||||||
| 3 | clock | Sysvar | | | ️ | ✅ |
|
| 3 | rent | Sysvar | | | ️ | ✅ |
|
||||||
| 4 | token_account | TokenAccount | | ✅ | | |
|
| 4 | clock | Sysvar | | | ️ | ✅ |
|
||||||
| 5 | bridge | BridgeConfig | | | | |
|
| 5 | token_account | TokenAccount | | ✅ | | |
|
||||||
| 6 | proposal | TransferOutProposal | | ✅ | ✅ | ✅ |
|
| 6 | bridge | BridgeConfig | | | | |
|
||||||
| 7 | token | Mint | | ✅ | | |
|
| 7 | proposal | TransferOutProposal | | ✅ | ✅ | ✅ |
|
||||||
| 8 | payer | Account | ✅ | | | |
|
| 8 | token | Mint | | ✅ | | |
|
||||||
| 9 | custody_account | TokenAccount | | ✅ | opt | ✅ |
|
| 9 | payer | Account | ✅ | | | |
|
||||||
|
| 10 | custody_account | TokenAccount | | ✅ | opt | ✅ |
|
||||||
|
|
||||||
#### EvictTransferOut
|
#### EvictTransferOut
|
||||||
|
|
||||||
|
@ -88,20 +101,6 @@ Deletes a `ClaimedVAA` after the `VAA_EXPIRATION_TIME` to free up space on chain
|
||||||
| 3 | bridge | BridgeConfig | | | | |
|
| 3 | bridge | BridgeConfig | | | | |
|
||||||
| 4 | claim | ClaimedVAA | | ✅ | | ✅ |
|
| 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
|
#### SubmitVAA
|
||||||
|
|
||||||
Submits a VAA signed by the guardians to perform an action.
|
Submits a VAA signed by the guardians to perform an action.
|
||||||
|
@ -114,11 +113,13 @@ All require:
|
||||||
| ----- | ------------ | ------------ | ------ | --------- | ----- | ------- |
|
| ----- | ------------ | ------------ | ------ | --------- | ----- | ------- |
|
||||||
| 0 | bridge_p | BridgeProgram | | | ️ | |
|
| 0 | bridge_p | BridgeProgram | | | ️ | |
|
||||||
| 1 | sys | SystemProgram | | | ️ | |
|
| 1 | sys | SystemProgram | | | ️ | |
|
||||||
| 2 | clock | Sysvar | | | ️ | ✅ |
|
| 2 | rent | Sysvar | | | ️ | ✅ |
|
||||||
| 3 | bridge | BridgeConfig | | | | |
|
| 3 | clock | Sysvar | | | ️ | ✅ |
|
||||||
| 4 | guardian_set | GuardianSet | | | | |
|
| 4 | bridge | BridgeConfig | | | | |
|
||||||
| 5 | claim | ExecutedVAA | | ✅ | ✅ | ✅ |
|
| 5 | guardian_set | GuardianSet | | | | |
|
||||||
| 6 | payer | Account | ✅ | | | |
|
| 6 | claim | ExecutedVAA | | ✅ | ✅ | ✅ |
|
||||||
|
| 7 | sig_info | SigState | | | ✅ | |
|
||||||
|
| 8 | payer | Account | ✅ | | | |
|
||||||
|
|
||||||
followed by:
|
followed by:
|
||||||
|
|
||||||
|
@ -126,31 +127,31 @@ followed by:
|
||||||
|
|
||||||
| Index | Name | Type | signer | writeable | empty | derived |
|
| Index | Name | Type | signer | writeable | empty | derived |
|
||||||
| ----- | ------------ | ------------------- | ------ | --------- | ----- | ------- |
|
| ----- | ------------ | ------------------- | ------ | --------- | ----- | ------- |
|
||||||
| 7 | guardian_set_new | GuardianSet | | ✅ | ✅ | ✅ |
|
| 9 | guardian_set_new | GuardianSet | | ✅ | ✅ | ✅ |
|
||||||
|
|
||||||
##### Transfer: Ethereum (native) -> Solana (wrapped)
|
##### Transfer: Ethereum (native) -> Solana (wrapped)
|
||||||
|
|
||||||
| Index | Name | Type | signer | writeable | empty | derived |
|
| Index | Name | Type | signer | writeable | empty | derived |
|
||||||
| ----- | ------------ | ------------ | ------ | --------- | ----- | ------- |
|
| ----- | ------------ | ------------ | ------ | --------- | ----- | ------- |
|
||||||
| 7 | token_program | SplToken | | | ️ | |
|
| 9 | token_program | SplToken | | | ️ | |
|
||||||
| 8 | token | WrappedAsset | | | | ✅ |
|
| 10 | token | WrappedAsset | | | | ✅ |
|
||||||
| 9 | destination | TokenAccount | | ✅ | | |
|
| 11 | destination | TokenAccount | | ✅ | | |
|
||||||
| 10 | wrapped_meta | WrappedMeta | | ✅ | opt | ✅ |
|
| 12 | wrapped_meta | WrappedMeta | | ✅ | opt | ✅ |
|
||||||
|
|
||||||
##### Transfer: Ethereum (wrapped) -> Solana (native)
|
##### Transfer: Ethereum (wrapped) -> Solana (native)
|
||||||
|
|
||||||
| Index | Name | Type | signer | writeable | empty | derived |
|
| Index | Name | Type | signer | writeable | empty | derived |
|
||||||
| ----- | ------------ | ------------ | ------ | --------- | ----- | ------- |
|
| ----- | ------------ | ------------ | ------ | --------- | ----- | ------- |
|
||||||
| 7 | token_program | SplToken | | | ️ | |
|
| 9 | token_program | SplToken | | | ️ | |
|
||||||
| 8 | token | Mint | | | | ✅ |
|
| 10 | token | Mint | | | | ✅ |
|
||||||
| 9 | destination | TokenAccount | | ✅ | opt | |
|
| 11 | destination | TokenAccount | | ✅ | opt | |
|
||||||
| 10 | custody_src | TokenAccount | | ✅ | | ✅ |
|
| 12 | custody_src | TokenAccount | | ✅ | | ✅ |
|
||||||
|
|
||||||
##### Transfer: Solana (any) -> Ethereum (any)
|
##### Transfer: Solana (any) -> Ethereum (any)
|
||||||
|
|
||||||
| Index | Name | Type | signer | writeable | empty | derived |
|
| Index | Name | Type | signer | writeable | empty | derived |
|
||||||
| ----- | ------------ | ------------------- | ------ | --------- | ----- | ------- |
|
| ----- | ------------ | ------------------- | ------ | --------- | ----- | ------- |
|
||||||
| 7 | out_proposal | TransferOutProposal | | ✅ | | ✅ |
|
| 9 | out_proposal | TransferOutProposal | | ✅ | | ✅ |
|
||||||
|
|
||||||
## Accounts
|
## Accounts
|
||||||
|
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -3,3 +3,5 @@ members = ["agent", "bridge", "cli"]
|
||||||
|
|
||||||
[patch.crates-io]
|
[patch.crates-io]
|
||||||
solana-sdk = { git="https://github.com/solana-labs/solana", branch="master" }
|
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,11 +9,10 @@ tonic = "0.3.0"
|
||||||
tokio = { version = "0.2", features = ["rt-threaded", "time", "stream", "fs", "macros", "uds"] }
|
tokio = { version = "0.2", features = ["rt-threaded", "time", "stream", "fs", "macros", "uds"] }
|
||||||
prost = "0.6"
|
prost = "0.6"
|
||||||
prost-types = "0.6"
|
prost-types = "0.6"
|
||||||
solana-sdk = { version = "1.3.3" }
|
solana-sdk = { version = "1.3.11" }
|
||||||
solana-client = { version = "1.3.3" }
|
solana-client = { version = "1.3.11" }
|
||||||
solana-faucet = "1.3.3"
|
solana-faucet = "1.3.11"
|
||||||
solana-transaction-status = "1.3.3"
|
spl-token = "=2.0.3"
|
||||||
spl-token = { package = "spl-token", git = "https://github.com/solana-labs/solana-program-library" }
|
|
||||||
wormhole-bridge = { path = "../bridge" }
|
wormhole-bridge = { path = "../bridge" }
|
||||||
primitive-types = { version = "0.7.2" }
|
primitive-types = { version = "0.7.2" }
|
||||||
hex = "0.4.2"
|
hex = "0.4.2"
|
||||||
|
@ -26,6 +25,7 @@ log ="0.4.8"
|
||||||
serde_derive = "1.0.103"
|
serde_derive = "1.0.103"
|
||||||
serde_json = "1.0.57"
|
serde_json = "1.0.57"
|
||||||
bs58 = "0.3.1"
|
bs58 = "0.3.1"
|
||||||
|
byteorder = "1.3.4"
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
tonic-build = { version = "0.3.0", features = ["prost"] }
|
tonic-build = { version = "0.3.0", features = ["prost"] }
|
||||||
|
|
|
@ -1,37 +1,41 @@
|
||||||
use std::env;
|
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 std::{io::Write, mem::size_of};
|
||||||
use solana_client::rpc_client::RpcClient;
|
|
||||||
use solana_client::rpc_config::RpcSendTransactionConfig;
|
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::commitment_config::{CommitmentConfig, CommitmentLevel};
|
||||||
use solana_sdk::fee_calculator::FeeCalculator;
|
|
||||||
use solana_sdk::instruction::Instruction;
|
use solana_sdk::instruction::Instruction;
|
||||||
use solana_sdk::program_error::ProgramError;
|
|
||||||
use solana_sdk::pubkey::Pubkey;
|
use solana_sdk::{
|
||||||
use solana_sdk::signature::{read_keypair_file, write_keypair_file, Keypair, Signer};
|
pubkey::Pubkey,
|
||||||
use solana_sdk::transaction::Transaction;
|
signature::{read_keypair_file, write_keypair_file, Keypair, Signature, Signer},
|
||||||
use solana_transaction_status::UiTransactionEncoding;
|
system_instruction::create_account,
|
||||||
use spl_token::state::Account;
|
transaction::Transaction,
|
||||||
use tokio::stream::Stream;
|
};
|
||||||
|
|
||||||
use tokio::sync::mpsc;
|
use tokio::sync::mpsc;
|
||||||
use tokio::time::Duration;
|
|
||||||
use tonic::{transport::Server, Code, Request, Response, Status};
|
use tonic::{transport::Server, Code, Request, Response, Status};
|
||||||
|
|
||||||
use service::agent_server::{Agent, AgentServer};
|
|
||||||
use service::{
|
use service::{
|
||||||
lockup_event::Event, Empty, LockupEvent, LockupEventNew, LockupEventVaaPosted,
|
agent_server::{Agent, AgentServer},
|
||||||
SubmitVaaRequest, SubmitVaaResponse, WatchLockupsRequest,
|
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;
|
mod monitor;
|
||||||
|
|
||||||
|
@ -47,6 +51,12 @@ pub struct AgentImpl {
|
||||||
key: Keypair,
|
key: Keypair,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct SignatureItem {
|
||||||
|
signature: [u8; 64 + 1],
|
||||||
|
key: [u8; 20],
|
||||||
|
index: u8,
|
||||||
|
}
|
||||||
|
|
||||||
#[tonic::async_trait]
|
#[tonic::async_trait]
|
||||||
impl Agent for AgentImpl {
|
impl Agent for AgentImpl {
|
||||||
async fn submit_vaa(
|
async fn submit_vaa(
|
||||||
|
@ -56,42 +66,58 @@ impl Agent for AgentImpl {
|
||||||
// Hack to clone keypair
|
// Hack to clone keypair
|
||||||
let b = self.key.to_bytes();
|
let b = self.key.to_bytes();
|
||||||
let key = Keypair::from_bytes(&b).unwrap();
|
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();
|
let rpc_url = self.rpc_url.clone();
|
||||||
|
|
||||||
// we need to spawn an extra thread because tokio does not allow nested runtimes
|
// we need to spawn an extra thread because tokio does not allow nested runtimes
|
||||||
std::thread::spawn(move || {
|
std::thread::spawn(move || {
|
||||||
let rpc = RpcClient::new(rpc_url);
|
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,
|
Ok(v) => v,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
return Err(Status::new(
|
return Err(Status::new(
|
||||||
Code::Unavailable,
|
Code::InvalidArgument,
|
||||||
format!("could not fetch recent blockhash: {}", e),
|
format!("could not parse VAA: {}", e),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
transaction.sign(&[&key], recent_blockhash);
|
let verify_txs = pack_sig_verification_txs(&rpc, &bridge, &vaa, &key, &sig_key)?;
|
||||||
match rpc.send_and_confirm_transaction_with_spinner_and_config(
|
|
||||||
&transaction,
|
// Strip signatures
|
||||||
CommitmentConfig {
|
vaa.signatures = Vec::new();
|
||||||
commitment: CommitmentLevel::Single,
|
let ix = match post_vaa(
|
||||||
},
|
&bridge,
|
||||||
RpcSendTransactionConfig {
|
&key.pubkey(),
|
||||||
skip_preflight: true,
|
&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 {
|
Ok(s) => Ok(Response::new(SubmitVaaResponse {
|
||||||
signature: s.to_string(),
|
signature: s.to_string(),
|
||||||
})),
|
})),
|
||||||
|
@ -115,15 +141,15 @@ impl Agent for AgentImpl {
|
||||||
|
|
||||||
async fn watch_lockups(
|
async fn watch_lockups(
|
||||||
&self,
|
&self,
|
||||||
req: Request<WatchLockupsRequest>,
|
_req: Request<WatchLockupsRequest>,
|
||||||
) -> Result<Response<Self::WatchLockupsStream>, Status> {
|
) -> 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 url = self.url.clone();
|
||||||
let bridge = self.bridge.clone();
|
let bridge = self.bridge.clone();
|
||||||
let rpc_url = self.rpc_url.clone();
|
let rpc_url = self.rpc_url.clone();
|
||||||
|
|
||||||
tokio::spawn(async move {
|
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();
|
let sub = PubsubClient::program_subscribe(&url, &bridge).unwrap();
|
||||||
// looping and sending our response using stream
|
// looping and sending our response using stream
|
||||||
loop {
|
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]
|
#[tokio::main]
|
||||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
let args: Vec<String> = env::args().collect();
|
let args: Vec<String> = env::args().collect();
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use std::str::FromStr;
|
|
||||||
use std::{
|
use std::{
|
||||||
marker::PhantomData,
|
marker::PhantomData,
|
||||||
|
str::FromStr,
|
||||||
sync::{
|
sync::{
|
||||||
atomic::{AtomicBool, Ordering},
|
atomic::{AtomicBool, Ordering},
|
||||||
mpsc::{channel, Receiver},
|
mpsc::{channel, Receiver},
|
||||||
|
@ -11,14 +11,16 @@ use std::{
|
||||||
|
|
||||||
use bs58;
|
use bs58;
|
||||||
use log::*;
|
use log::*;
|
||||||
use serde::{de::DeserializeOwned, de::Error, Deserialize, Deserializer, Serialize};
|
use serde::{
|
||||||
|
de::{DeserializeOwned, Error},
|
||||||
|
Deserialize, Deserializer, Serialize,
|
||||||
|
};
|
||||||
use serde_json::{
|
use serde_json::{
|
||||||
json,
|
json,
|
||||||
value::Value::{Number, Object},
|
value::Value::{Number, Object},
|
||||||
Map, Value,
|
Map, Value,
|
||||||
};
|
};
|
||||||
use solana_sdk::account::Account;
|
|
||||||
use solana_sdk::account_info::AccountInfo;
|
|
||||||
use solana_sdk::pubkey::Pubkey;
|
use solana_sdk::pubkey::Pubkey;
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
use tungstenite::{client::AutoStream, connect, Message, WebSocket};
|
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-derive = "0.2"
|
||||||
num-traits = "0.2"
|
num-traits = "0.2"
|
||||||
remove_dir_all = "=0.5.0"
|
remove_dir_all = "=0.5.0"
|
||||||
solana-sdk = { version = "1.3.3", default-features = false, optional = true }
|
solana-sdk = { version = "1.3.11", default-features = false, optional = true }
|
||||||
spl-token = { package = "spl-token", git = "https://github.com/solana-labs/solana-program-library", default-features = false, optional = true }
|
spl-token = { version = "=2.0.3", default-features = false, optional = true }
|
||||||
thiserror = "1.0"
|
thiserror = "1.0"
|
||||||
byteorder = "1.3.4"
|
byteorder = "1.3.4"
|
||||||
zerocopy = "0.3.0"
|
zerocopy = "0.3.0"
|
||||||
|
|
|
@ -100,6 +100,9 @@ pub enum Error {
|
||||||
/// VAA for this transfer has already been submitted
|
/// VAA for this transfer has already been submitted
|
||||||
#[error("VAAAlreadySubmitted")]
|
#[error("VAAAlreadySubmitted")]
|
||||||
VAAAlreadySubmitted,
|
VAAAlreadySubmitted,
|
||||||
|
/// Mismatching guardian set
|
||||||
|
#[error("GuardianSetMismatch")]
|
||||||
|
GuardianSetMismatch,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<Error> for ProgramError {
|
impl From<Error> for ProgramError {
|
||||||
|
|
|
@ -38,6 +38,7 @@ impl PrintProgramError for Error {
|
||||||
Error::VAATooLong => info!("Error: VAATooLong"),
|
Error::VAATooLong => info!("Error: VAATooLong"),
|
||||||
Error::CannotWrapNative => info!("Error: CannotWrapNative"),
|
Error::CannotWrapNative => info!("Error: CannotWrapNative"),
|
||||||
Error::VAAAlreadySubmitted => info!("Error: VAAAlreadySubmitted"),
|
Error::VAAAlreadySubmitted => info!("Error: VAAAlreadySubmitted"),
|
||||||
|
Error::GuardianSetMismatch => info!("Error: GuardianSetMismatch"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,8 @@
|
||||||
#![allow(clippy::too_many_arguments)]
|
#![allow(clippy::too_many_arguments)]
|
||||||
//! Instruction types
|
//! Instruction types
|
||||||
|
|
||||||
use std::io::{Cursor, Read, Write};
|
|
||||||
use std::mem::size_of;
|
use std::mem::size_of;
|
||||||
|
|
||||||
use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
|
|
||||||
use primitive_types::U256;
|
use primitive_types::U256;
|
||||||
use solana_sdk::{
|
use solana_sdk::{
|
||||||
instruction::{AccountMeta, Instruction},
|
instruction::{AccountMeta, Instruction},
|
||||||
|
@ -12,11 +10,13 @@ use solana_sdk::{
|
||||||
pubkey::Pubkey,
|
pubkey::Pubkey,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::error::Error;
|
use crate::{
|
||||||
use crate::error::Error::VAATooLong;
|
instruction::BridgeInstruction::{
|
||||||
use crate::instruction::BridgeInstruction::{Initialize, PokeProposal, PostVAA, TransferOut};
|
Initialize, PokeProposal, PostVAA, TransferOut, VerifySignatures,
|
||||||
use crate::state::{AssetMeta, Bridge, BridgeConfig};
|
},
|
||||||
use crate::vaa::{VAABody, VAA};
|
state::{AssetMeta, Bridge, BridgeConfig},
|
||||||
|
vaa::{VAABody, VAA},
|
||||||
|
};
|
||||||
|
|
||||||
/// chain id of this chain
|
/// chain id of this chain
|
||||||
pub const CHAIN_ID_SOLANA: u8 = 1;
|
pub const CHAIN_ID_SOLANA: u8 = 1;
|
||||||
|
@ -75,6 +75,14 @@ pub struct TransferOutPayloadRaw {
|
||||||
pub nonce: u32,
|
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.
|
/// Instructions supported by the SwapInfo program.
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
pub enum BridgeInstruction {
|
pub enum BridgeInstruction {
|
||||||
|
@ -126,6 +134,9 @@ pub enum BridgeInstruction {
|
||||||
|
|
||||||
/// Pokes a proposal with no valid VAAs attached so guardians reprocess it.
|
/// Pokes a proposal with no valid VAAs attached so guardians reprocess it.
|
||||||
PokeProposal(),
|
PokeProposal(),
|
||||||
|
|
||||||
|
/// Verifies signature instructions
|
||||||
|
VerifySignatures(VerifySigPayload),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl BridgeInstruction {
|
impl BridgeInstruction {
|
||||||
|
@ -157,16 +168,22 @@ impl BridgeInstruction {
|
||||||
PostVAA(payload)
|
PostVAA(payload)
|
||||||
}
|
}
|
||||||
5 => PokeProposal(),
|
5 => PokeProposal(),
|
||||||
|
6 => {
|
||||||
|
let payload: &VerifySigPayload = unpack(input)?;
|
||||||
|
|
||||||
|
VerifySignatures(*payload)
|
||||||
|
}
|
||||||
_ => return Err(ProgramError::InvalidInstructionData),
|
_ => return Err(ProgramError::InvalidInstructionData),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Serializes a BridgeInstruction into a byte buffer.
|
/// Serializes a BridgeInstruction into a byte buffer.
|
||||||
pub fn serialize(self: Self) -> Result<Vec<u8>, ProgramError> {
|
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 {
|
match self {
|
||||||
Self::Initialize(payload) => {
|
Self::Initialize(payload) => {
|
||||||
|
output.resize(size_of::<InitializePayload>() + 1, 0);
|
||||||
output[0] = 0;
|
output[0] = 0;
|
||||||
#[allow(clippy::cast_ptr_alignment)]
|
#[allow(clippy::cast_ptr_alignment)]
|
||||||
let value = unsafe {
|
let value = unsafe {
|
||||||
|
@ -175,6 +192,7 @@ impl BridgeInstruction {
|
||||||
*value = payload;
|
*value = payload;
|
||||||
}
|
}
|
||||||
Self::TransferOut(payload) => {
|
Self::TransferOut(payload) => {
|
||||||
|
output.resize(size_of::<TransferOutPayloadRaw>() + 1, 0);
|
||||||
output[0] = 1;
|
output[0] = 1;
|
||||||
#[allow(clippy::cast_ptr_alignment)]
|
#[allow(clippy::cast_ptr_alignment)]
|
||||||
let value = unsafe {
|
let value = unsafe {
|
||||||
|
@ -193,21 +211,32 @@ impl BridgeInstruction {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
Self::PostVAA(payload) => {
|
Self::PostVAA(payload) => {
|
||||||
|
output.resize(1, 0);
|
||||||
output[0] = 2;
|
output[0] = 2;
|
||||||
#[allow(clippy::cast_ptr_alignment)]
|
#[allow(clippy::cast_ptr_alignment)]
|
||||||
let value =
|
output.extend_from_slice(&payload);
|
||||||
unsafe { &mut *(&mut output[size_of::<u8>()] as *mut u8 as *mut VAAData) };
|
|
||||||
*value = payload;
|
|
||||||
}
|
}
|
||||||
Self::EvictTransferOut() => {
|
Self::EvictTransferOut() => {
|
||||||
|
output.resize(1, 0);
|
||||||
output[0] = 3;
|
output[0] = 3;
|
||||||
}
|
}
|
||||||
Self::EvictClaimedVAA() => {
|
Self::EvictClaimedVAA() => {
|
||||||
|
output.resize(1, 0);
|
||||||
output[0] = 4;
|
output[0] = 4;
|
||||||
}
|
}
|
||||||
Self::PokeProposal() => {
|
Self::PokeProposal() => {
|
||||||
|
output.resize(1, 0);
|
||||||
output[0] = 5;
|
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)
|
Ok(output)
|
||||||
}
|
}
|
||||||
|
@ -280,6 +309,7 @@ pub fn transfer_out(
|
||||||
AccountMeta::new_readonly(*program_id, false),
|
AccountMeta::new_readonly(*program_id, false),
|
||||||
AccountMeta::new_readonly(solana_sdk::system_program::id(), false),
|
AccountMeta::new_readonly(solana_sdk::system_program::id(), false),
|
||||||
AccountMeta::new_readonly(spl_token::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_readonly(solana_sdk::sysvar::clock::id(), false),
|
||||||
AccountMeta::new(*token_account, false),
|
AccountMeta::new(*token_account, false),
|
||||||
AccountMeta::new(bridge_key, 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.
|
/// Creates a 'PostVAA' instruction.
|
||||||
#[cfg(not(target_arch = "bpf"))]
|
#[cfg(not(target_arch = "bpf"))]
|
||||||
pub fn post_vaa(
|
pub fn post_vaa(
|
||||||
program_id: &Pubkey,
|
program_id: &Pubkey,
|
||||||
payer: &Pubkey,
|
payer: &Pubkey,
|
||||||
|
signature_key: &Pubkey,
|
||||||
v: VAAData,
|
v: VAAData,
|
||||||
) -> Result<Instruction, ProgramError> {
|
) -> Result<Instruction, ProgramError> {
|
||||||
let mut data = v.clone();
|
let mut data = v.clone();
|
||||||
|
@ -322,10 +381,12 @@ pub fn post_vaa(
|
||||||
let mut accounts = vec![
|
let mut accounts = vec![
|
||||||
AccountMeta::new_readonly(*program_id, false),
|
AccountMeta::new_readonly(*program_id, false),
|
||||||
AccountMeta::new_readonly(solana_sdk::system_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_readonly(solana_sdk::sysvar::clock::id(), false),
|
||||||
AccountMeta::new(bridge_key, false),
|
AccountMeta::new(bridge_key, false),
|
||||||
AccountMeta::new(guardian_set_key, false),
|
AccountMeta::new(guardian_set_key, false),
|
||||||
AccountMeta::new(claim_key, false),
|
AccountMeta::new(claim_key, false),
|
||||||
|
AccountMeta::new(*signature_key, false),
|
||||||
AccountMeta::new(*payer, true),
|
AccountMeta::new(*payer, true),
|
||||||
];
|
];
|
||||||
|
|
||||||
|
@ -390,7 +451,7 @@ pub fn poke_proposal(
|
||||||
) -> Result<Instruction, ProgramError> {
|
) -> Result<Instruction, ProgramError> {
|
||||||
let data = BridgeInstruction::PokeProposal().serialize()?;
|
let data = BridgeInstruction::PokeProposal().serialize()?;
|
||||||
|
|
||||||
let mut accounts = vec![AccountMeta::new(*transfer_proposal, false)];
|
let accounts = vec![AccountMeta::new(*transfer_proposal, false)];
|
||||||
|
|
||||||
Ok(Instruction {
|
Ok(Instruction {
|
||||||
program_id: *program_id,
|
program_id: *program_id,
|
||||||
|
|
|
@ -7,5 +7,4 @@ pub mod error_program;
|
||||||
pub mod instruction;
|
pub mod instruction;
|
||||||
pub mod processor;
|
pub mod processor;
|
||||||
pub mod state;
|
pub mod state;
|
||||||
pub mod syscalls;
|
|
||||||
pub mod vaa;
|
pub mod vaa;
|
||||||
|
|
|
@ -1,32 +1,53 @@
|
||||||
//! Program instruction processing logic
|
//! Program instruction processing logic
|
||||||
#![cfg(feature = "program")]
|
#![cfg(feature = "program")]
|
||||||
|
|
||||||
use std::borrow::Borrow;
|
use std::{borrow::Borrow, cell::RefCell, io::Write, mem::size_of, slice::Iter};
|
||||||
use std::cell::RefCell;
|
|
||||||
use std::mem::size_of;
|
|
||||||
use std::slice::Iter;
|
|
||||||
|
|
||||||
|
use byteorder::ByteOrder;
|
||||||
use num_traits::AsPrimitive;
|
use num_traits::AsPrimitive;
|
||||||
use primitive_types::U256;
|
use primitive_types::U256;
|
||||||
use solana_sdk::clock::Clock;
|
use sha3::Digest;
|
||||||
use solana_sdk::hash::Hasher;
|
|
||||||
#[cfg(target_arch = "bpf")]
|
#[cfg(target_arch = "bpf")]
|
||||||
use solana_sdk::program::invoke_signed;
|
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::{
|
use solana_sdk::{
|
||||||
account_info::next_account_info, account_info::AccountInfo, entrypoint::ProgramResult, info,
|
account_info::{next_account_info, AccountInfo},
|
||||||
instruction::Instruction, program_error::ProgramError, pubkey::Pubkey,
|
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::{
|
||||||
use crate::instruction::BridgeInstruction::*;
|
error::Error,
|
||||||
use crate::instruction::{BridgeInstruction, TransferOutPayload, VAAData, CHAIN_ID_SOLANA};
|
instruction::{
|
||||||
use crate::instruction::{MAX_LEN_GUARDIAN_KEYS, MAX_VAA_SIZE};
|
BridgeInstruction, BridgeInstruction::*, TransferOutPayload, VAAData, VerifySigPayload,
|
||||||
use crate::state::*;
|
CHAIN_ID_SOLANA, MAX_LEN_GUARDIAN_KEYS, MAX_VAA_SIZE,
|
||||||
use crate::vaa::{BodyTransfer, BodyUpdateGuardianSet, VAABody, VAA};
|
},
|
||||||
|
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
|
/// Instruction processing logic
|
||||||
impl Bridge {
|
impl Bridge {
|
||||||
|
@ -64,6 +85,11 @@ impl Bridge {
|
||||||
|
|
||||||
Self::process_poke(program_id, accounts)
|
Self::process_poke(program_id, accounts)
|
||||||
}
|
}
|
||||||
|
VerifySignatures(p) => {
|
||||||
|
info!("Instruction: VerifySignatures");
|
||||||
|
|
||||||
|
Self::process_verify_signatures(program_id, accounts, &p)
|
||||||
|
}
|
||||||
_ => panic!(""),
|
_ => panic!(""),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -155,6 +181,158 @@ impl Bridge {
|
||||||
Ok(())
|
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
|
/// Transfers a wrapped asset out
|
||||||
pub fn process_transfer_out(
|
pub fn process_transfer_out(
|
||||||
program_id: &Pubkey,
|
program_id: &Pubkey,
|
||||||
|
@ -166,6 +344,7 @@ impl Bridge {
|
||||||
next_account_info(account_info_iter)?; // Bridge program
|
next_account_info(account_info_iter)?; // Bridge program
|
||||||
next_account_info(account_info_iter)?; // System program
|
next_account_info(account_info_iter)?; // System program
|
||||||
next_account_info(account_info_iter)?; // Token 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 clock_info = next_account_info(account_info_iter)?;
|
||||||
let sender_account_info = next_account_info(account_info_iter)?;
|
let sender_account_info = next_account_info(account_info_iter)?;
|
||||||
let bridge_info = next_account_info(account_info_iter)?;
|
let bridge_info = next_account_info(account_info_iter)?;
|
||||||
|
@ -223,6 +402,7 @@ impl Bridge {
|
||||||
accounts,
|
accounts,
|
||||||
&bridge.config.token_program,
|
&bridge.config.token_program,
|
||||||
sender_account_info.key,
|
sender_account_info.key,
|
||||||
|
mint_info.key,
|
||||||
t.amount,
|
t.amount,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
|
@ -256,6 +436,7 @@ impl Bridge {
|
||||||
next_account_info(account_info_iter)?; // Bridge program
|
next_account_info(account_info_iter)?; // Bridge program
|
||||||
next_account_info(account_info_iter)?; // System program
|
next_account_info(account_info_iter)?; // System program
|
||||||
next_account_info(account_info_iter)?; // Token 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 clock_info = next_account_info(account_info_iter)?;
|
||||||
let sender_account_info = next_account_info(account_info_iter)?;
|
let sender_account_info = next_account_info(account_info_iter)?;
|
||||||
let bridge_info = next_account_info(account_info_iter)?;
|
let bridge_info = next_account_info(account_info_iter)?;
|
||||||
|
@ -368,10 +549,12 @@ impl Bridge {
|
||||||
// Load VAA processing default accounts
|
// Load VAA processing default accounts
|
||||||
next_account_info(account_info_iter)?; // Bridge program
|
next_account_info(account_info_iter)?; // Bridge program
|
||||||
next_account_info(account_info_iter)?; // System program
|
next_account_info(account_info_iter)?; // System program
|
||||||
|
next_account_info(account_info_iter)?; // Rent sysvar
|
||||||
let clock_info = next_account_info(account_info_iter)?;
|
let clock_info = next_account_info(account_info_iter)?;
|
||||||
let bridge_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 guardian_set_info = next_account_info(account_info_iter)?;
|
||||||
let claim_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 payer_info = next_account_info(account_info_iter)?;
|
||||||
|
|
||||||
let mut bridge = Bridge::bridge_deserialize(bridge_info)?;
|
let mut bridge = Bridge::bridge_deserialize(bridge_info)?;
|
||||||
|
@ -401,9 +584,29 @@ impl Bridge {
|
||||||
return Err(Error::GuardianSetExpired.into());
|
return Err(Error::GuardianSetExpired.into());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verify VAA signature
|
// Verify sig state
|
||||||
if !vaa.verify(&guardian_set.keys[..guardian_set.len_keys as usize]) {
|
let mut sig_state_data = sig_info.data.borrow_mut();
|
||||||
return Err(Error::InvalidVAASignature.into());
|
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)?;
|
let payload = vaa.payload.as_ref().ok_or(Error::InvalidVAAAction)?;
|
||||||
|
@ -428,6 +631,7 @@ impl Bridge {
|
||||||
vaa,
|
vaa,
|
||||||
&v,
|
&v,
|
||||||
vaa_data,
|
vaa_data,
|
||||||
|
sig_info.key,
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
Self::process_vaa_transfer(
|
Self::process_vaa_transfer(
|
||||||
|
@ -635,6 +839,7 @@ impl Bridge {
|
||||||
vaa: &VAA,
|
vaa: &VAA,
|
||||||
b: &BodyTransfer,
|
b: &BodyTransfer,
|
||||||
vaa_data: VAAData,
|
vaa_data: VAAData,
|
||||||
|
sig_account: &Pubkey,
|
||||||
) -> ProgramResult {
|
) -> ProgramResult {
|
||||||
info!("posting VAA");
|
info!("posting VAA");
|
||||||
let proposal_info = next_account_info(account_info_iter)?;
|
let proposal_info = next_account_info(account_info_iter)?;
|
||||||
|
@ -673,6 +878,7 @@ impl Bridge {
|
||||||
// Stop byte
|
// Stop byte
|
||||||
proposal.vaa[vaa_data.len()] = 0xff;
|
proposal.vaa[vaa_data.len()] = 0xff;
|
||||||
proposal.vaa_time = vaa.timestamp;
|
proposal.vaa_time = vaa.timestamp;
|
||||||
|
proposal.signature_account = *sig_account;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -686,11 +892,13 @@ impl Bridge {
|
||||||
accounts: &[AccountInfo],
|
accounts: &[AccountInfo],
|
||||||
token_program_id: &Pubkey,
|
token_program_id: &Pubkey,
|
||||||
token_account: &Pubkey,
|
token_account: &Pubkey,
|
||||||
|
mint_account: &Pubkey,
|
||||||
amount: U256,
|
amount: U256,
|
||||||
) -> Result<(), ProgramError> {
|
) -> Result<(), ProgramError> {
|
||||||
let ix = spl_token::instruction::burn(
|
let ix = spl_token::instruction::burn(
|
||||||
token_program_id,
|
token_program_id,
|
||||||
token_account,
|
token_account,
|
||||||
|
mint_account,
|
||||||
&Self::derive_bridge_id(program_id)?,
|
&Self::derive_bridge_id(program_id)?,
|
||||||
&[],
|
&[],
|
||||||
amount.as_u64(),
|
amount.as_u64(),
|
||||||
|
@ -769,7 +977,7 @@ impl Bridge {
|
||||||
mint: &Pubkey,
|
mint: &Pubkey,
|
||||||
payer: &Pubkey,
|
payer: &Pubkey,
|
||||||
) -> Result<(), ProgramError> {
|
) -> Result<(), ProgramError> {
|
||||||
Self::check_and_create_account::<spl_token::state::Account>(
|
Self::check_and_create_account::<[u8; spl_token::state::Account::LEN]>(
|
||||||
program_id,
|
program_id,
|
||||||
accounts,
|
accounts,
|
||||||
account,
|
account,
|
||||||
|
@ -777,7 +985,6 @@ impl Bridge {
|
||||||
token_program,
|
token_program,
|
||||||
&Self::derive_custody_seeds(bridge, mint),
|
&Self::derive_custody_seeds(bridge, mint),
|
||||||
)?;
|
)?;
|
||||||
info!("bababu");
|
|
||||||
info!(token_program.to_string().as_str());
|
info!(token_program.to_string().as_str());
|
||||||
let ix = spl_token::instruction::initialize_account(
|
let ix = spl_token::instruction::initialize_account(
|
||||||
token_program,
|
token_program,
|
||||||
|
@ -799,7 +1006,7 @@ impl Bridge {
|
||||||
asset: &AssetMeta,
|
asset: &AssetMeta,
|
||||||
decimals: u8,
|
decimals: u8,
|
||||||
) -> Result<(), ProgramError> {
|
) -> Result<(), ProgramError> {
|
||||||
Self::check_and_create_account::<Mint>(
|
Self::check_and_create_account::<[u8; spl_token::state::Mint::LEN]>(
|
||||||
program_id,
|
program_id,
|
||||||
accounts,
|
accounts,
|
||||||
mint,
|
mint,
|
||||||
|
@ -810,9 +1017,8 @@ impl Bridge {
|
||||||
let ix = spl_token::instruction::initialize_mint(
|
let ix = spl_token::instruction::initialize_mint(
|
||||||
token_program,
|
token_program,
|
||||||
mint,
|
mint,
|
||||||
|
&Self::derive_bridge_id(program_id)?,
|
||||||
None,
|
None,
|
||||||
Some(&Self::derive_bridge_id(program_id)?),
|
|
||||||
0,
|
|
||||||
decimals,
|
decimals,
|
||||||
)?;
|
)?;
|
||||||
invoke_signed(&ix, accounts, &[])
|
invoke_signed(&ix, accounts, &[])
|
||||||
|
|
|
@ -1,18 +1,16 @@
|
||||||
//! Bridge transition types
|
//! Bridge transition types
|
||||||
|
|
||||||
use std::io::{Cursor, Read, Write};
|
|
||||||
use std::mem::size_of;
|
use std::mem::size_of;
|
||||||
use std::ops::Deref;
|
|
||||||
|
|
||||||
use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
|
|
||||||
use primitive_types::U256;
|
use primitive_types::U256;
|
||||||
use solana_sdk::pubkey::{PubkeyError, MAX_SEED_LEN};
|
|
||||||
use solana_sdk::{account_info::AccountInfo, program_error::ProgramError, pubkey::Pubkey};
|
use solana_sdk::{account_info::AccountInfo, program_error::ProgramError, pubkey::Pubkey};
|
||||||
use zerocopy::AsBytes;
|
use zerocopy::AsBytes;
|
||||||
|
|
||||||
use crate::error::Error;
|
use crate::{
|
||||||
use crate::instruction::{ForeignAddress, VAAData, MAX_LEN_GUARDIAN_KEYS, MAX_VAA_SIZE};
|
error::Error,
|
||||||
use crate::vaa::BodyTransfer;
|
instruction::{ForeignAddress, MAX_LEN_GUARDIAN_KEYS, MAX_VAA_SIZE},
|
||||||
|
vaa::BodyTransfer,
|
||||||
|
};
|
||||||
|
|
||||||
/// fee rate as a ratio
|
/// fee rate as a ratio
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
|
@ -73,6 +71,8 @@ pub struct TransferOutProposal {
|
||||||
pub lockup_time: u32,
|
pub lockup_time: u32,
|
||||||
/// times the proposal has been poked
|
/// times the proposal has been poked
|
||||||
pub poke_counter: u8,
|
pub poke_counter: u8,
|
||||||
|
/// Account where signatures are stored
|
||||||
|
pub signature_account: Pubkey,
|
||||||
|
|
||||||
/// Is `true` if this structure has been initialized.
|
/// Is `true` if this structure has been initialized.
|
||||||
pub is_initialized: bool,
|
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
|
/// Implementation of serialization functions
|
||||||
impl Bridge {
|
impl Bridge {
|
||||||
/// Deserializes a spl_token `Account`.
|
/// Deserializes a spl_token `Account`.
|
||||||
pub fn token_account_deserialize(
|
pub fn token_account_deserialize(
|
||||||
info: &AccountInfo,
|
info: &AccountInfo,
|
||||||
) -> Result<spl_token::state::Account, Error> {
|
) -> 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)?)
|
.map_err(|_| Error::ExpectedAccount)?)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Deserializes a spl_token `Mint`.
|
/// Deserializes a spl_token `Mint`.
|
||||||
pub fn mint_deserialize(info: &AccountInfo) -> Result<spl_token::state::Mint, Error> {
|
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)?)
|
.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 byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
|
||||||
use primitive_types::U256;
|
use primitive_types::U256;
|
||||||
use sha3::Digest;
|
use sha3::Digest;
|
||||||
|
use solana_sdk::program_error::ProgramError;
|
||||||
|
|
||||||
use crate::error::Error;
|
use crate::{error::Error, state::AssetMeta};
|
||||||
use crate::error::Error::InvalidVAAFormat;
|
|
||||||
use crate::state::AssetMeta;
|
|
||||||
use crate::syscalls::{sol_syscall_ecrecover, EcrecoverInput, EcrecoverOutput};
|
|
||||||
|
|
||||||
pub type ForeignAddress = [u8; 32];
|
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() {
|
let body = match self.signature_body() {
|
||||||
Ok(v) => v,
|
Ok(v) => v,
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
return false;
|
return Err(ProgramError::InvalidArgument);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut h = sha3::Keccak256::default();
|
let mut h = sha3::Keccak256::default();
|
||||||
if let Err(_) = h.write(body.as_slice()) {
|
if let Err(_) = h.write(body.as_slice()) {
|
||||||
return false;
|
return Err(ProgramError::InvalidArgument);
|
||||||
};
|
};
|
||||||
let hash = h.finalize().into();
|
Ok(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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn serialize(&self) -> Result<Vec<u8>, Error> {
|
pub fn serialize(&self) -> Result<Vec<u8>, Error> {
|
||||||
|
@ -136,7 +103,7 @@ impl VAA {
|
||||||
|
|
||||||
let len_sig = rdr.read_u8()?;
|
let len_sig = rdr.read_u8()?;
|
||||||
let mut sigs: Vec<Signature> = Vec::with_capacity(len_sig as usize);
|
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();
|
let mut sig = Signature::default();
|
||||||
|
|
||||||
sig.index = rdr.read_u8()?;
|
sig.index = rdr.read_u8()?;
|
||||||
|
@ -302,8 +269,10 @@ mod tests {
|
||||||
use hex;
|
use hex;
|
||||||
use primitive_types::U256;
|
use primitive_types::U256;
|
||||||
|
|
||||||
use crate::state::AssetMeta;
|
use crate::{
|
||||||
use crate::vaa::{BodyTransfer, BodyUpdateGuardianSet, Signature, VAABody, VAA};
|
state::AssetMeta,
|
||||||
|
vaa::{BodyTransfer, BodyUpdateGuardianSet, Signature, VAABody, VAA},
|
||||||
|
};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn serialize_deserialize_vaa_transfer() {
|
fn serialize_deserialize_vaa_transfer() {
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -8,15 +8,14 @@ edition = "2018"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
clap = "2.33.0"
|
clap = "2.33.0"
|
||||||
solana-clap-utils = { version = "1.3.3"}
|
solana-clap-utils = { version = "1.3.11"}
|
||||||
solana-cli-config = { version = "1.3.3" }
|
solana-cli-config = { version = "1.3.11" }
|
||||||
solana-logger = { version = "1.3.3" }
|
solana-logger = { version = "1.3.11" }
|
||||||
solana-sdk = { version = "1.3.3" }
|
solana-sdk = { version = "1.3.11" }
|
||||||
solana-client = { version = "=1.3.3" }
|
solana-client = { version = "1.3.11" }
|
||||||
solana-faucet = "1.3.3"
|
solana-faucet = "1.3.11"
|
||||||
solana-transaction-status = "1.3.3"
|
solana-account-decoder = { version = "1.3.11" }
|
||||||
solana-account-decoder = { version = "1.3.3" }
|
spl-token = "=2.0.3"
|
||||||
spl-token = { package = "spl-token", git = "https://github.com/solana-labs/solana-program-library" }
|
|
||||||
wormhole-bridge = { path = "../bridge" }
|
wormhole-bridge = { path = "../bridge" }
|
||||||
primitive-types = {version ="0.7.2"}
|
primitive-types = {version ="0.7.2"}
|
||||||
hex = "0.4.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_client::rpc_client::RpcClient;
|
||||||
use solana_faucet::faucet::request_airdrop_transaction;
|
use solana_faucet::faucet::request_airdrop_transaction;
|
||||||
use solana_sdk::hash::Hash;
|
use solana_sdk::{
|
||||||
use solana_sdk::pubkey::Pubkey;
|
hash::Hash,
|
||||||
use solana_sdk::signature::{Signature, Signer, SignerError};
|
pubkey::Pubkey,
|
||||||
use solana_sdk::transaction::Transaction;
|
signature::{Signature, Signer, SignerError},
|
||||||
use std::error;
|
transaction::Transaction,
|
||||||
use std::net::SocketAddr;
|
};
|
||||||
use std::thread::sleep;
|
|
||||||
use std::time::Duration;
|
use crate::CommmandResult;
|
||||||
|
|
||||||
// Quick and dirty Keypair that assumes the client will do retries but not update the
|
// 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.
|
// blockhash. If the client updates the blockhash, the signature will be invalid.
|
||||||
|
|
|
@ -1,7 +1,4 @@
|
||||||
use std::fmt::Display;
|
use std::{fmt::Display, mem::size_of, net::ToSocketAddrs, process::exit};
|
||||||
use std::net::{SocketAddr, ToSocketAddrs};
|
|
||||||
use std::str::FromStr;
|
|
||||||
use std::{mem::size_of, process::exit};
|
|
||||||
|
|
||||||
use clap::{
|
use clap::{
|
||||||
crate_description, crate_name, crate_version, value_t, value_t_or_exit, App, AppSettings, Arg,
|
crate_description, crate_name, crate_version, value_t, value_t_or_exit, App, AppSettings, Arg,
|
||||||
|
@ -10,16 +7,13 @@ use clap::{
|
||||||
use hex;
|
use hex;
|
||||||
use primitive_types::U256;
|
use primitive_types::U256;
|
||||||
use solana_account_decoder::{parse_token::TokenAccountType, UiAccountData};
|
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::{
|
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},
|
input_validators::{is_amount, is_keypair, is_pubkey_or_keypair, is_url},
|
||||||
};
|
};
|
||||||
use solana_client::client_error::ClientError;
|
use solana_client::{
|
||||||
use solana_client::rpc_config::RpcSendTransactionConfig;
|
rpc_client::RpcClient, rpc_config::RpcSendTransactionConfig, rpc_request::TokenAccountsFilter,
|
||||||
use solana_client::{rpc_client::RpcClient, rpc_request::TokenAccountsFilter};
|
};
|
||||||
use solana_sdk::system_instruction::create_account;
|
|
||||||
use solana_sdk::{
|
use solana_sdk::{
|
||||||
commitment_config::CommitmentConfig,
|
commitment_config::CommitmentConfig,
|
||||||
native_token::*,
|
native_token::*,
|
||||||
|
@ -32,11 +26,11 @@ use spl_token::{
|
||||||
self,
|
self,
|
||||||
instruction::*,
|
instruction::*,
|
||||||
native_mint,
|
native_mint,
|
||||||
|
pack::Pack,
|
||||||
state::{Account, Mint},
|
state::{Account, Mint},
|
||||||
};
|
};
|
||||||
|
|
||||||
use spl_bridge::instruction::*;
|
use spl_bridge::{instruction::*, state::*};
|
||||||
use spl_bridge::state::*;
|
|
||||||
|
|
||||||
use crate::faucet::request_and_confirm_airdrop;
|
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()?;
|
let (recent_blockhash, fee_calculator) = config.rpc_client.get_recent_blockhash()?;
|
||||||
check_fee_payer_balance(
|
check_fee_payer_balance(
|
||||||
config,
|
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);
|
transaction.sign(&[&config.fee_payer, &config.owner], recent_blockhash);
|
||||||
Ok(Some(transaction))
|
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 mut transaction = Transaction::new_with_payer(&[ix], Some(&config.fee_payer.pubkey()));
|
||||||
|
|
||||||
let (recent_blockhash, fee_calculator) = config.rpc_client.get_recent_blockhash()?;
|
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);
|
transaction.sign(&[&config.fee_payer, &config.owner], recent_blockhash);
|
||||||
Ok(Some(transaction))
|
Ok(Some(transaction))
|
||||||
}
|
}
|
||||||
|
@ -127,14 +125,14 @@ fn command_lock_tokens(
|
||||||
decimals: 0,
|
decimals: 0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(e) => AssetMeta {
|
Err(_e) => AssetMeta {
|
||||||
address: token.to_bytes(),
|
address: token.to_bytes(),
|
||||||
chain: CHAIN_ID_SOLANA,
|
chain: CHAIN_ID_SOLANA,
|
||||||
decimals: 0,
|
decimals: 0,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut instructions = vec![
|
let instructions = vec![
|
||||||
approve(
|
approve(
|
||||||
&spl_token::id(),
|
&spl_token::id(),
|
||||||
&account,
|
&account,
|
||||||
|
@ -169,27 +167,8 @@ fn command_lock_tokens(
|
||||||
let (recent_blockhash, fee_calculator) = config.rpc_client.get_recent_blockhash()?;
|
let (recent_blockhash, fee_calculator) = config.rpc_client.get_recent_blockhash()?;
|
||||||
check_fee_payer_balance(
|
check_fee_payer_balance(
|
||||||
config,
|
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))
|
|
||||||
}
|
|
||||||
|
|
||||||
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()),
|
|
||||||
)?;
|
)?;
|
||||||
transaction.sign(&[&config.fee_payer, &config.owner], recent_blockhash);
|
transaction.sign(&[&config.fee_payer, &config.owner], recent_blockhash);
|
||||||
Ok(Some(transaction))
|
Ok(Some(transaction))
|
||||||
|
@ -231,7 +210,7 @@ fn command_create_token(config: &Config, decimals: u8) -> CommmandResult {
|
||||||
|
|
||||||
let minimum_balance_for_rent_exemption = config
|
let minimum_balance_for_rent_exemption = config
|
||||||
.rpc_client
|
.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(
|
let mut transaction = Transaction::new_with_payer(
|
||||||
&[
|
&[
|
||||||
|
@ -239,15 +218,14 @@ fn command_create_token(config: &Config, decimals: u8) -> CommmandResult {
|
||||||
&config.fee_payer.pubkey(),
|
&config.fee_payer.pubkey(),
|
||||||
&token.pubkey(),
|
&token.pubkey(),
|
||||||
minimum_balance_for_rent_exemption,
|
minimum_balance_for_rent_exemption,
|
||||||
size_of::<Mint>() as u64,
|
Mint::LEN as u64,
|
||||||
&spl_token::id(),
|
&spl_token::id(),
|
||||||
),
|
),
|
||||||
initialize_mint(
|
initialize_mint(
|
||||||
&spl_token::id(),
|
&spl_token::id(),
|
||||||
&token.pubkey(),
|
&token.pubkey(),
|
||||||
|
&config.owner.pubkey(),
|
||||||
None,
|
None,
|
||||||
Some(&config.owner.pubkey()),
|
|
||||||
0,
|
|
||||||
decimals,
|
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()?;
|
let (recent_blockhash, fee_calculator) = config.rpc_client.get_recent_blockhash()?;
|
||||||
check_fee_payer_balance(
|
check_fee_payer_balance(
|
||||||
config,
|
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(
|
transaction.sign(
|
||||||
&[&config.fee_payer, &config.owner, &token],
|
&[&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
|
let minimum_balance_for_rent_exemption = config
|
||||||
.rpc_client
|
.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(
|
let mut transaction = Transaction::new_with_payer(
|
||||||
&[
|
&[
|
||||||
|
@ -280,7 +259,7 @@ fn command_create_account(config: &Config, token: Pubkey) -> CommmandResult {
|
||||||
&config.fee_payer.pubkey(),
|
&config.fee_payer.pubkey(),
|
||||||
&account.pubkey(),
|
&account.pubkey(),
|
||||||
minimum_balance_for_rent_exemption,
|
minimum_balance_for_rent_exemption,
|
||||||
size_of::<Account>() as u64,
|
Account::LEN as u64,
|
||||||
&spl_token::id(),
|
&spl_token::id(),
|
||||||
),
|
),
|
||||||
initialize_account(
|
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()?;
|
let (recent_blockhash, fee_calculator) = config.rpc_client.get_recent_blockhash()?;
|
||||||
check_fee_payer_balance(
|
check_fee_payer_balance(
|
||||||
config,
|
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(
|
transaction.sign(
|
||||||
&[&config.fee_payer, &config.owner, &account],
|
&[&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(
|
let mut transaction = Transaction::new_with_payer(
|
||||||
&[set_owner(
|
&[spl_token::instruction::set_authority(
|
||||||
&spl_token::id(),
|
&spl_token::id(),
|
||||||
&account,
|
&account,
|
||||||
&new_owner,
|
Some(&new_owner),
|
||||||
|
AuthorityType::AccountOwner,
|
||||||
&config.owner.pubkey(),
|
&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()?;
|
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);
|
transaction.sign(&[&config.fee_payer, &config.owner], recent_blockhash);
|
||||||
Ok(Some(transaction))
|
Ok(Some(transaction))
|
||||||
}
|
}
|
||||||
|
@ -361,7 +345,10 @@ fn command_transfer(
|
||||||
);
|
);
|
||||||
|
|
||||||
let (recent_blockhash, fee_calculator) = config.rpc_client.get_recent_blockhash()?;
|
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);
|
transaction.sign(&[&config.fee_payer, &config.owner], recent_blockhash);
|
||||||
Ok(Some(transaction))
|
Ok(Some(transaction))
|
||||||
}
|
}
|
||||||
|
@ -373,12 +360,19 @@ fn command_burn(config: &Config, source: Pubkey, ui_amount: f64) -> CommmandResu
|
||||||
.rpc_client
|
.rpc_client
|
||||||
.get_token_account_balance_with_commitment(&source, config.commitment_config)?
|
.get_token_account_balance_with_commitment(&source, config.commitment_config)?
|
||||||
.value;
|
.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 amount = spl_token::ui_amount_to_amount(ui_amount, source_token_balance.decimals);
|
||||||
let mut transaction = Transaction::new_with_payer(
|
let mut transaction = Transaction::new_with_payer(
|
||||||
&[burn(
|
&[burn(
|
||||||
&spl_token::id(),
|
&spl_token::id(),
|
||||||
&source,
|
&source,
|
||||||
|
&mint_pubkey,
|
||||||
&config.owner.pubkey(),
|
&config.owner.pubkey(),
|
||||||
&[],
|
&[],
|
||||||
amount,
|
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()?;
|
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);
|
transaction.sign(&[&config.fee_payer, &config.owner], recent_blockhash);
|
||||||
Ok(Some(transaction))
|
Ok(Some(transaction))
|
||||||
}
|
}
|
||||||
|
@ -422,7 +419,10 @@ fn command_mint(
|
||||||
);
|
);
|
||||||
|
|
||||||
let (recent_blockhash, fee_calculator) = config.rpc_client.get_recent_blockhash()?;
|
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);
|
transaction.sign(&[&config.fee_payer, &config.owner], recent_blockhash);
|
||||||
Ok(Some(transaction))
|
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()?;
|
let (recent_blockhash, fee_calculator) = config.rpc_client.get_recent_blockhash()?;
|
||||||
check_owner_balance(config, lamports)?;
|
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(
|
transaction.sign(
|
||||||
&[&config.fee_payer, &config.owner, &account],
|
&[&config.fee_payer, &config.owner, &account],
|
||||||
recent_blockhash,
|
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()?;
|
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);
|
transaction.sign(&[&config.fee_payer, &config.owner], recent_blockhash);
|
||||||
Ok(Some(transaction))
|
Ok(Some(transaction))
|
||||||
}
|
}
|
||||||
|
@ -1148,12 +1154,6 @@ fn main() {
|
||||||
&config, &bridge, account, token, amount, chain, recipient, nonce,
|
&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)) => {
|
("poke", Some(arg_matches)) => {
|
||||||
let bridge = pubkey_of(arg_matches, "bridge").unwrap();
|
let bridge = pubkey_of(arg_matches, "bridge").unwrap();
|
||||||
let proposal = pubkey_of(arg_matches, "proposal").unwrap();
|
let proposal = pubkey_of(arg_matches, "proposal").unwrap();
|
||||||
|
@ -1189,6 +1189,7 @@ fn main() {
|
||||||
RpcSendTransactionConfig {
|
RpcSendTransactionConfig {
|
||||||
// TODO: move to https://github.com/solana-labs/solana/pull/11792
|
// TODO: move to https://github.com/solana-labs/solana/pull/11792
|
||||||
skip_preflight: true,
|
skip_preflight: true,
|
||||||
|
preflight_commitment: None,
|
||||||
},
|
},
|
||||||
)?;
|
)?;
|
||||||
println!("Signature: {}", signature);
|
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
|
Index: fetch-spl.sh
|
||||||
IDEA additional info:
|
IDEA additional info:
|
||||||
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
|
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
|
||||||
|
@ -754,3 +35,64 @@ Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
|
||||||
Ok(TokenAccountType::Account(UiTokenAccount {
|
Ok(TokenAccountType::Account(UiTokenAccount {
|
||||||
mint: account.mint.to_string(),
|
mint: account.mint.to_string(),
|
||||||
owner: account.owner.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
|
WORKDIR /usr/src/solana
|
||||||
|
|
||||||
RUN git clone https://github.com/solana-labs/solana --branch master && \
|
RUN git clone https://github.com/solana-labs/solana --branch master && \
|
||||||
cd solana && git checkout e2d66cf7
|
cd solana && git checkout 5dcf3480986a87cc9c80788c1d8ccd8f0cb44a8d
|
||||||
|
|
||||||
ADD *.patch .
|
ADD *.patch .
|
||||||
|
|
||||||
|
|
|
@ -76,13 +76,24 @@ function TransferProposals() {
|
||||||
|
|
||||||
let executeVAA = async (v: LockupWithStatus) => {
|
let executeVAA = async (v: LockupWithStatus) => {
|
||||||
let wh = WormholeFactory.connect(BRIDGE_ADDRESS, signer)
|
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--) {
|
for (let i = vaa.length; i > 0; i--) {
|
||||||
if (vaa[i] == 0xff) {
|
if (vaa[i] == 0xff) {
|
||||||
vaa = vaa.slice(0, i)
|
vaa = vaa.slice(0, i)
|
||||||
break
|
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},)
|
message.loading({content: "Signing transaction...", key: "eth_tx", duration: 1000},)
|
||||||
let tx = await wh.submitVAA(vaa)
|
let tx = await wh.submitVAA(vaa)
|
||||||
message.loading({content: "Waiting for transaction to be mined...", key: "eth_tx", duration: 1000})
|
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 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 {
|
export {
|
||||||
BRIDGE_ADDRESS,
|
BRIDGE_ADDRESS,
|
||||||
TOKEN_PROGRAM,
|
TOKEN_PROGRAM,
|
||||||
WRAPPED_MASTER,
|
WRAPPED_MASTER,
|
||||||
SOLANA_BRIDGE_PROGRAM
|
SOLANA_BRIDGE_PROGRAM,
|
||||||
|
SOLANA_HOST
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import * as solanaWeb3 from '@solana/web3.js';
|
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
|
export default ClientContext
|
||||||
|
|
|
@ -5,7 +5,7 @@ import assert from "assert";
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
import * as BufferLayout from 'buffer-layout'
|
import * as BufferLayout from 'buffer-layout'
|
||||||
import {Token} from "@solana/spl-token";
|
import {Token} from "@solana/spl-token";
|
||||||
import {TOKEN_PROGRAM} from "../config";
|
import {SOLANA_HOST, TOKEN_PROGRAM} from "../config";
|
||||||
import * as bs58 from "bs58";
|
import * as bs58 from "bs58";
|
||||||
|
|
||||||
export interface AssetMeta {
|
export interface AssetMeta {
|
||||||
|
@ -26,9 +26,15 @@ export interface Lockup {
|
||||||
vaa: Uint8Array,
|
vaa: Uint8Array,
|
||||||
vaaTime: number,
|
vaaTime: number,
|
||||||
pokeCounter: number,
|
pokeCounter: number,
|
||||||
|
signatureAccount: PublicKey,
|
||||||
initialized: boolean,
|
initialized: boolean,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface Signature {
|
||||||
|
signature: number[],
|
||||||
|
index: number,
|
||||||
|
}
|
||||||
|
|
||||||
export const CHAIN_ID_SOLANA = 1;
|
export const CHAIN_ID_SOLANA = 1;
|
||||||
|
|
||||||
class SolanaBridge {
|
class SolanaBridge {
|
||||||
|
@ -95,6 +101,7 @@ class SolanaBridge {
|
||||||
{pubkey: this.programID, isSigner: false, isWritable: false},
|
{pubkey: this.programID, isSigner: false, isWritable: false},
|
||||||
{pubkey: solanaWeb3.SystemProgram.programId, isSigner: false, isWritable: false},
|
{pubkey: solanaWeb3.SystemProgram.programId, isSigner: false, isWritable: false},
|
||||||
{pubkey: this.tokenProgram, 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: solanaWeb3.SYSVAR_CLOCK_PUBKEY, isSigner: false, isWritable: false},
|
||||||
{pubkey: tokenAccount, isSigner: false, isWritable: true},
|
{pubkey: tokenAccount, isSigner: false, isWritable: true},
|
||||||
{pubkey: configKey, isSigner: false, isWritable: false},
|
{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
|
// fetchAssetMeta fetches the AssetMeta for an SPL token
|
||||||
async fetchTransferProposals(
|
async fetchTransferProposals(
|
||||||
tokenAccount: PublicKey,
|
tokenAccount: PublicKey,
|
||||||
): Promise<Lockup[]> {
|
): Promise<Lockup[]> {
|
||||||
let accountRes = await fetch("http://localhost:8899", {
|
let accountRes = await fetch(SOLANA_HOST, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json'
|
'Content-Type': 'application/json'
|
||||||
|
@ -186,7 +229,7 @@ class SolanaBridge {
|
||||||
"method": "getProgramAccounts",
|
"method": "getProgramAccounts",
|
||||||
"params": [this.programID.toString(), {
|
"params": [this.programID.toString(), {
|
||||||
"commitment": "single",
|
"commitment": "single",
|
||||||
"filters": [{"dataSize": 1152}, {
|
"filters": [{"dataSize": 1184}, {
|
||||||
"memcmp": {
|
"memcmp": {
|
||||||
"offset": 33,
|
"offset": 33,
|
||||||
"bytes": tokenAccount.toString()
|
"bytes": tokenAccount.toString()
|
||||||
|
@ -210,7 +253,9 @@ class SolanaBridge {
|
||||||
BufferLayout.blob(1001, 'vaa'),
|
BufferLayout.blob(1001, 'vaa'),
|
||||||
BufferLayout.seq(BufferLayout.u8(), 3), // 4 byte alignment because a u32 is following
|
BufferLayout.seq(BufferLayout.u8(), 3), // 4 byte alignment because a u32 is following
|
||||||
BufferLayout.u32('vaaTime'),
|
BufferLayout.u32('vaaTime'),
|
||||||
|
BufferLayout.u32('lockupTime'),
|
||||||
BufferLayout.u8('pokeCounter'),
|
BufferLayout.u8('pokeCounter'),
|
||||||
|
BufferLayout.blob(32, 'signatureAccount'),
|
||||||
BufferLayout.u8('initialized'),
|
BufferLayout.u8('initialized'),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
@ -230,6 +275,7 @@ class SolanaBridge {
|
||||||
toChain: parsedAccount.toChain,
|
toChain: parsedAccount.toChain,
|
||||||
vaa: parsedAccount.vaa,
|
vaa: parsedAccount.vaa,
|
||||||
vaaTime: parsedAccount.vaaTime,
|
vaaTime: parsedAccount.vaaTime,
|
||||||
|
signatureAccount: new PublicKey(parsedAccount.signatureAccount),
|
||||||
pokeCounter: parsedAccount.pokeCounter
|
pokeCounter: parsedAccount.pokeCounter
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue