Skip to content

Commit c0cf03f

Browse files
[Solana]: Move Solana blockchain to Rust (#3691)
* [Solana]: Add `MessageRaw` to `SigningInput` * Replace `Pubkey` with `SolanaAddress` * [Solana]: Add instruction builders * [Solana]: WIP Signer * [Solana]: Move instruction builders to dedicated modules * [Bitcoin]: Fix the order of compiled account keys * [Solana]: Fix delegate staking * [Solana]: Add deactivate staking transaction * [Solana]: Add deactivate all stake transaction * [Solana]: Add withdraw stake transaction * [Solana]: Add withdraw all stake transaction * [Solana]: Add create token account transaction * [Solana]: Add TokenTransfer transaction * Refactor `InstructionBuilder` to allow easily append references, add memo and nonce instructions * [Solana]: Minor changes * [Solana]: Add CreateAndTransferToken transaction * Add support for fee payer * [Solana]: Add CreateNonceAccount and WithdrawNonceAccount transactions * [Solana]: Add AdvanceNonceAccount transaction * [Solana]: Add RawMessage transaction * [Solana]: Add support for preimage hashing and compiling * [Solana]: Replace C++ implementation * [Solana]: Add more compile tests * [Solana]: Add tw_solana_address_default_token_address * [Solana]: Add "WithDurableNonce" tests * [Solana]: Remove C++ implementation * Identify a bug in `Address::findProgramAddress` function * [CI] Trigger CI * [Solana]: Remove extra C++ tests * [Solana]: Fix clippy warnings * [Solana]: Fix `tw_solana_address_default_token_address` * Replace C++'s implementation with `tw_solana_address_default_token_address` * [Solana]: Remove fix Boost path at CMakeLists.txt * [Solana]: Add `TransactionDecoder` module (#3698) * [Solana]: Add external signatures to the `RawMessage` * [Solana]: Add `ProtoBuilder` to convert `VersionedTransaction` to a `Proto::RawMessage` * [Solana]: Add `TransactionDecoder` module * [Solana]: Fix signature overriding, add more tests * [Solana]: Refactor tests * [Solana]: Add and implement `TWTransactionDecoderDecode` FFI in C++ * [Solana]: Add Kotlin test * [Solana]: Remove fix Boost path at CMakeLists.txt * [Solana]: Add Swift test * [WalletConnect/Solana]: Add support for WalletConnect signing requests (#3705) * [Solana]: Add SolanaWalletConnector * [Solana]: Add pubkey signatures to the `Proto::SigningOutput` * [Solana]: Add `TWSolanaWalletConnect` test * [Solana]: Add Kotlin, Swift tests * [Solana]: Fix C++ test * [Solana]: Fix iOS test * [Solana]: Add support for priority fee (#3710) * [Solana]: Add priority fee price and limit * [Solana]: Add priority fee tests * [CI]: Split linux-ci-rust.yml into two jobs * Move `new-blockchain-rust` step into codegen-v2.yml * [CI]: Fix linux-ci-rust.yml
1 parent 7c71119 commit c0cf03f

File tree

116 files changed

+5311
-4379
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

116 files changed

+5311
-4379
lines changed

.github/workflows/codegen-v2.yml

Lines changed: 34 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,16 +10,42 @@ on:
1010
paths:
1111
- 'codegen-v2/**'
1212

13+
env:
14+
SCCACHE_GHA_ENABLED: "true"
15+
RUSTC_WRAPPER: "sccache"
16+
1317
jobs:
1418
test:
1519
runs-on: ubuntu-latest
1620
if: github.event.pull_request.draft == false
1721
steps:
18-
- uses: actions/checkout@v2
19-
- uses: actions-rs/toolchain@v1
20-
with:
21-
toolchain: stable
22-
- uses: actions-rs/cargo@v1
23-
with:
24-
command: test
25-
args: --manifest-path codegen-v2/Cargo.toml
22+
- uses: actions/checkout@v3
23+
- name: Install system dependencies
24+
run: |
25+
tools/install-sys-dependencies-linux
26+
27+
- name: Run sccache-cache
28+
uses: mozilla-actions/sccache-action@v0.0.3
29+
30+
- name: Install Rust dependencies
31+
run: |
32+
tools/install-rust-dependencies
33+
34+
- name: Run codegen-v2 tests
35+
run: |
36+
cargo test --all
37+
working-directory: codegen-v2
38+
39+
# Generate files for a blockchain.
40+
# Please note the blockchain should not be implemented in Rust at the moment of running this step,
41+
# otherwise consider either generating files for another blockchain or removing this step at all.
42+
- name: Test codegen-v2 new-blockchain-rust
43+
run: |
44+
cargo run -- new-blockchain-rust iotex
45+
working-directory: codegen-v2
46+
47+
# Check if `new-blockchain-rust` command has generated files that do not break project compilation.
48+
- name: Check Rust compiles
49+
run: |
50+
cargo check --tests
51+
working-directory: rust

.github/workflows/linux-ci-rust.yml

Lines changed: 28 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@ concurrency:
1515
cancel-in-progress: true
1616

1717
jobs:
18-
build:
18+
# Check formatting, clippy warnings, run tests and check code coverage.
19+
build-and-test:
1920
permissions:
2021
contents: read
2122
checks: write
@@ -33,16 +34,14 @@ jobs:
3334
- name: Cache Rust
3435
uses: Swatinem/rust-cache@v2
3536
with:
37+
key: "build-and-test"
3638
workspaces: |
3739
rust
3840
3941
- name: Install Rust dependencies
4042
run: |
4143
tools/install-rust-dependencies dev
4244
43-
- name: Install emsdk
44-
run: tools/install-wasm-dependencies
45-
4645
- name: Check code formatting
4746
run: |
4847
cargo fmt --check
@@ -58,9 +57,6 @@ jobs:
5857
cargo llvm-cov nextest --profile ci --no-fail-fast --lcov --output-path coverage.info
5958
working-directory: rust
6059

61-
- name: Run tests in WASM
62-
run: tools/rust-test wasm
63-
6460
- name: Rust Test Report
6561
uses: dorny/test-reporter@v1
6662
if: success() || failure()
@@ -75,17 +71,32 @@ jobs:
7571
run: |
7672
tools/check-coverage rust/coverage.stats rust/coverage.info
7773
78-
# Generate files for a blockchain in the end of the pipeline.
79-
# Please note the blockchain should not be implemented in Rust at the moment of running this step,
80-
# otherwise consider either generate files for another blockchain or remove this step at all.
81-
- name: Test codegen-v2 new-blockchain-rust
74+
# Run Rust tests in WASM.
75+
test-wasm:
76+
runs-on: ubuntu-latest
77+
if: github.event.pull_request.draft == false
78+
steps:
79+
- uses: actions/checkout@v3
80+
- name: Install system dependencies
8281
run: |
83-
cargo run -- new-blockchain-rust iotex
84-
working-directory: codegen-v2
82+
tools/install-sys-dependencies-linux
83+
84+
- name: Run sccache-cache
85+
uses: mozilla-actions/sccache-action@v0.0.3
86+
87+
- name: Cache Rust
88+
uses: Swatinem/rust-cache@v2
89+
with:
90+
key: "test-wasm"
91+
workspaces: |
92+
rust
8593
86-
# Check if `new-blockchain-rust` command has generated files that do not break project compilation.
87-
- name: Check Rust compiles
94+
- name: Install Rust dependencies
8895
run: |
89-
cargo check --tests
90-
working-directory: rust
96+
tools/install-rust-dependencies
9197
98+
- name: Install emsdk
99+
run: tools/install-wasm-dependencies
100+
101+
- name: Run tests in WASM
102+
run: tools/rust-test wasm

android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/solana/TestSolanaTransaction.kt

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,16 @@ import org.junit.Assert.assertEquals
77
import org.junit.Test
88
import wallet.core.jni.Base58
99
import wallet.core.java.AnySigner
10+
import wallet.core.jni.Base64
11+
import wallet.core.jni.CoinType
1012
import wallet.core.jni.CoinType.SOLANA
1113
import wallet.core.jni.SolanaTransaction
1214
import wallet.core.jni.DataVector
15+
import wallet.core.jni.TransactionDecoder
1316
import wallet.core.jni.proto.Common.SigningError
1417
import wallet.core.jni.proto.Solana
18+
import wallet.core.jni.proto.Solana.DecodingTransactionOutput
19+
import wallet.core.jni.proto.Solana.SigningInput
1520
import wallet.core.jni.proto.Solana.SigningOutput
1621

1722
class TestSolanaTransaction {
@@ -39,4 +44,41 @@ class TestSolanaTransaction {
3944
val expectedString = "Ajzc/Tke0CG8Cew5qFa6xZI/7Ya3DN0M8Ige6tKPsGzhg8Bw9DqL18KUrEZZ1F4YqZBo4Rv+FsDT8A7Nss7p4A6BNVZzzGprCJqYQeNg0EVIbmPc6mDitNniHXGeKgPZ6QZbM4FElw9O7IOFTpOBPvQFeqy0vZf/aayncL8EK/UEAgACBssq8Im1alV3N7wXGODL8jLPWwLhTuCqfGZ1Iz9fb5tXlMOJD6jUvASrKmdtLK/qXNyJns2Vqcvlk+nfJYdZaFpIWiT/tAcEYbttfxyLdYxrLckAKdVRtf1OrNgtZeMCII4SAn6SYaaidrX/AN3s/aVn/zrlEKW0cEUIatHVDKtXO0Qss5EhV/E6kz0BNCgtAytf/s0Botvxt3kGCN8ALqcG3fbh12Whk9nL4UbO63msHLSF7V9bN5E6jPWFfv8AqbHiki6ThNH3auuyZPQpJntnN0mA//56nMpK/6HIuu8xAQUEAgQDAQoMoA8AAAAAAAAG"
4045
assertEquals(output.encoded, expectedString)
4146
}
47+
48+
@Test
49+
fun testDecodeUpdateBlockhashAndSign() {
50+
// https://explorer.solana.com/tx/3KbvREZUat76wgWMtnJfWbJL74Vzh4U2eabVJa3Z3bb2fPtW8AREP5pbmRwUrxZCESbTomWpL41PeKDcPGbojsej?cluster=devnet
51+
val encodedTx = Base64.decode("AnQTYwZpkm3fs4SdLxnV6gQj3hSLsyacpxDdLMALYWObm722f79IfYFTbZeFK9xHtMumiDOWAM2hHQP4r/GtbARpncaXgOVFv7OgbRLMbuCEJHO1qwcdCbtH72VzyzU8yw9sqqHIAaCUE8xaQTgT6Z5IyZfeyMe2QGJIfOjz65UPAgACBssq8Im1alV3N7wXGODL8jLPWwLhTuCqfGZ1Iz9fb5tXlMOJD6jUvASrKmdtLK/qXNyJns2Vqcvlk+nfJYdZaFpIWiT/tAcEYbttfxyLdYxrLckAKdVRtf1OrNgtZeMCII4SAn6SYaaidrX/AN3s/aVn/zrlEKW0cEUIatHVDKtXO0Qss5EhV/E6kz0BNCgtAytf/s0Botvxt3kGCN8ALqcG3fbh12Whk9nL4UbO63msHLSF7V9bN5E6jPWFfv8Aqe6sdLXiXSDILEtzckCjkjchiSf6zVGpMYiAE5BE2IqHAQUEAgQDAQoMoA8AAAAAAAAG")
52+
val newBlockhash = "CyPYVsYWrsJNfVpi8aazu7WsrswNFuDd385z6GNoBGUg"
53+
54+
val senderPrivateKeyData = "7f0932159226ddec9e1a4b0b8fe7cdc135049f9e549a867d722aa720dd64f32e".toHexByteArray()
55+
val feePayerPrivateKeyData = "4b9d6f57d28b06cbfa1d4cc710953e62d653caf853415c56ffd9d150acdeb7f7".toHexByteArray()
56+
57+
// Step 1: Decode the transaction.
58+
59+
val decodedData = TransactionDecoder.decode(SOLANA, encodedTx)
60+
val decodedOutput = DecodingTransactionOutput.parseFrom(decodedData)
61+
62+
assertEquals(decodedOutput.error, SigningError.OK)
63+
assert(decodedOutput.transaction.hasLegacy())
64+
65+
// Step 2: Update recent blockhash.
66+
67+
val rawTx = decodedOutput.transaction.toBuilder().apply {
68+
legacy = decodedOutput.transaction.legacy.toBuilder().setRecentBlockhash(newBlockhash).build()
69+
}.build()
70+
71+
// Step 3: Re-sign the updated transaction.
72+
73+
val signingInput = SigningInput.newBuilder().apply {
74+
rawMessage = rawTx
75+
privateKey = ByteString.copyFrom(senderPrivateKeyData)
76+
feePayerPrivateKey = ByteString.copyFrom(feePayerPrivateKeyData)
77+
txEncoding = Solana.Encoding.Base64
78+
}.build()
79+
80+
val output = AnySigner.sign(signingInput, SOLANA, SigningOutput.parser())
81+
val expectedString = "Ajzc/Tke0CG8Cew5qFa6xZI/7Ya3DN0M8Ige6tKPsGzhg8Bw9DqL18KUrEZZ1F4YqZBo4Rv+FsDT8A7Nss7p4A6BNVZzzGprCJqYQeNg0EVIbmPc6mDitNniHXGeKgPZ6QZbM4FElw9O7IOFTpOBPvQFeqy0vZf/aayncL8EK/UEAgACBssq8Im1alV3N7wXGODL8jLPWwLhTuCqfGZ1Iz9fb5tXlMOJD6jUvASrKmdtLK/qXNyJns2Vqcvlk+nfJYdZaFpIWiT/tAcEYbttfxyLdYxrLckAKdVRtf1OrNgtZeMCII4SAn6SYaaidrX/AN3s/aVn/zrlEKW0cEUIatHVDKtXO0Qss5EhV/E6kz0BNCgtAytf/s0Botvxt3kGCN8ALqcG3fbh12Whk9nL4UbO63msHLSF7V9bN5E6jPWFfv8AqbHiki6ThNH3auuyZPQpJntnN0mA//56nMpK/6HIuu8xAQUEAgQDAQoMoA8AAAAAAAAG"
82+
assertEquals(output.encoded, expectedString)
83+
}
4284
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
package com.trustwallet.core.app.blockchains.solana
2+
3+
import com.google.protobuf.ByteString
4+
import org.junit.Assert.assertEquals
5+
import org.junit.Test
6+
import wallet.core.java.AnySigner
7+
import wallet.core.jni.Base58
8+
import wallet.core.jni.CoinType.SOLANA
9+
import wallet.core.jni.WalletConnectRequest
10+
import wallet.core.jni.proto.Common.SigningError
11+
import wallet.core.jni.proto.Solana.Encoding
12+
import wallet.core.jni.proto.Solana.SigningOutput
13+
import wallet.core.jni.proto.WalletConnect
14+
15+
class TestSolanaWalletConnectSigning {
16+
17+
init {
18+
System.loadLibrary("TrustWalletCore")
19+
}
20+
21+
@Test
22+
fun testSignSolanaTransactionFromWalletConnectRequest() {
23+
// Step 1: Parse a signing request received through WalletConnect.
24+
25+
val parsingInput = WalletConnect.ParseRequestInput.newBuilder().apply {
26+
method = WalletConnect.Method.SolanaSignTransaction
27+
payload = "{\"transaction\":\"AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAEDZsL1CMnFVcrMn7JtiOiN1U4hC7WovOVof2DX51xM0H/GizyJTHgrBanCf8bGbrFNTn0x3pCGq30hKbywSTr6AgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAgIAAQwCAAAAKgAAAAAAAAA=\"}"
28+
}.build()
29+
30+
val parsingOutputBytes = WalletConnectRequest.parse(SOLANA, parsingInput.toByteArray())
31+
val parsingOutput = WalletConnect.ParseRequestOutput.parseFrom(parsingOutputBytes)
32+
33+
assertEquals(parsingOutput.error, SigningError.OK)
34+
35+
// Step 2: Set missing fields.
36+
37+
val signingInput = parsingOutput.solana.toBuilder().apply {
38+
privateKey = ByteString.copyFrom(Base58.decodeNoCheck("A7psj2GW7ZMdY4E5hJq14KMeYg7HFjULSsWSrTXZLvYr"))
39+
txEncoding = Encoding.Base64
40+
}.build()
41+
42+
// Step 3: Sign the transaction.
43+
44+
val output = AnySigner.sign(signingInput, SOLANA, SigningOutput.parser())
45+
46+
assertEquals(output.error, SigningError.OK)
47+
assertEquals(output.encoded, "AQPWaOi7dMdmQpXi8HyQQKwiqIftrg1igGQxGtZeT50ksn4wAnyH4DtDrkkuE0fqgx80LTp4LwNN9a440SrmoA8BAAEDZsL1CMnFVcrMn7JtiOiN1U4hC7WovOVof2DX51xM0H/GizyJTHgrBanCf8bGbrFNTn0x3pCGq30hKbywSTr6AgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAgIAAQwCAAAAKgAAAAAAAAA=")
48+
49+
assertEquals(output.getSignatures(0).pubkey, "7v91N7iZ9mNicL8WfG6cgSCKyRXydQjLh6UYBWwm6y1Q")
50+
assertEquals(output.getSignatures(0).signature, "5T6uZBHnHFd8uWErDBTFRVkbKuhbcm94K5MJ2beTYDruzqv4FjS7EMKvC94ZfxNAiWUXZ6bZxS3WXUbhJwYNPWn")
51+
}
52+
}

codegen-v2/src/codegen/rust/templates/blockchain_crate/entry.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ use tw_coin_entry::error::AddressResult;
1313
use tw_coin_entry::modules::json_signer::NoJsonSigner;
1414
use tw_coin_entry::modules::message_signer::NoMessageSigner;
1515
use tw_coin_entry::modules::plan_builder::NoPlanBuilder;
16+
use tw_coin_entry::modules::transaction_decoder::NoTransactionDecoder;
1617
use tw_coin_entry::modules::wallet_connector::NoWalletConnector;
1718
use tw_coin_entry::prefix::NoPrefix;
1819
use tw_keypair::tw::PublicKey;
@@ -33,6 +34,7 @@ impl CoinEntry for {BLOCKCHAIN}Entry {
3334
type PlanBuilder = NoPlanBuilder;
3435
type MessageSigner = NoMessageSigner;
3536
type WalletConnector = NoWalletConnector;
37+
type TransactionDecoder = NoTransactionDecoder;
3638

3739
#[inline]
3840
fn parse_address(
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
// SPDX-License-Identifier: Apache-2.0
2+
//
3+
// Copyright © 2017 Trust Wallet.
4+
5+
#pragma once
6+
7+
#include "TWBase.h"
8+
#include "TWCoinType.h"
9+
#include "TWData.h"
10+
#include "TWString.h"
11+
12+
TW_EXTERN_C_BEGIN
13+
14+
TW_EXPORT_STRUCT
15+
struct TWTransactionDecoder;
16+
17+
/// Decodes a transaction from a binary representation.
18+
///
19+
/// \param coin coin type.
20+
/// \param encodedTx encoded transaction data.
21+
/// \return serialized protobuf message specific for the given coin.
22+
TW_EXPORT_STATIC_METHOD
23+
TWData *_Nonnull TWTransactionDecoderDecode(enum TWCoinType coinType, TWData *_Nonnull encodedTx);
24+
25+
TW_EXTERN_C_END

0 commit comments

Comments
 (0)