From 75802b9cad5a4d85c9395ebbae4a9cb8c1477cb6 Mon Sep 17 00:00:00 2001 From: Satoshi Otomakan Date: Thu, 31 Oct 2024 10:57:49 +0100 Subject: [PATCH] [Solana]: Add Android, iOS tests --- .../solana/TestSolanaTransaction.kt | 43 +++++++++++++++++-- include/TrustWalletCore/TWSolanaTransaction.h | 4 ++ swift/Tests/Blockchains/SolanaTests.swift | 39 +++++++++++++++++ 3 files changed, 83 insertions(+), 3 deletions(-) diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/solana/TestSolanaTransaction.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/solana/TestSolanaTransaction.kt index c2f8a3d461c..a7f1ca53348 100644 --- a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/solana/TestSolanaTransaction.kt +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/solana/TestSolanaTransaction.kt @@ -1,14 +1,11 @@ package com.trustwallet.core.app.blockchains.solana import com.google.protobuf.ByteString -import com.trustwallet.core.app.utils.toHex import com.trustwallet.core.app.utils.toHexByteArray import org.junit.Assert.assertEquals import org.junit.Test -import wallet.core.jni.Base58 import wallet.core.java.AnySigner import wallet.core.jni.Base64 -import wallet.core.jni.CoinType import wallet.core.jni.CoinType.SOLANA import wallet.core.jni.SolanaTransaction import wallet.core.jni.DataVector @@ -18,6 +15,7 @@ import wallet.core.jni.proto.Solana import wallet.core.jni.proto.Solana.DecodingTransactionOutput import wallet.core.jni.proto.Solana.SigningInput import wallet.core.jni.proto.Solana.SigningOutput +import wallet.core.jni.proto.Solana.Encoding class TestSolanaTransaction { @@ -81,4 +79,43 @@ class TestSolanaTransaction { val expectedString = "Ajzc/Tke0CG8Cew5qFa6xZI/7Ya3DN0M8Ige6tKPsGzhg8Bw9DqL18KUrEZZ1F4YqZBo4Rv+FsDT8A7Nss7p4A6BNVZzzGprCJqYQeNg0EVIbmPc6mDitNniHXGeKgPZ6QZbM4FElw9O7IOFTpOBPvQFeqy0vZf/aayncL8EK/UEAgACBssq8Im1alV3N7wXGODL8jLPWwLhTuCqfGZ1Iz9fb5tXlMOJD6jUvASrKmdtLK/qXNyJns2Vqcvlk+nfJYdZaFpIWiT/tAcEYbttfxyLdYxrLckAKdVRtf1OrNgtZeMCII4SAn6SYaaidrX/AN3s/aVn/zrlEKW0cEUIatHVDKtXO0Qss5EhV/E6kz0BNCgtAytf/s0Botvxt3kGCN8ALqcG3fbh12Whk9nL4UbO63msHLSF7V9bN5E6jPWFfv8AqbHiki6ThNH3auuyZPQpJntnN0mA//56nMpK/6HIuu8xAQUEAgQDAQoMoA8AAAAAAAAG" assertEquals(output.encoded, expectedString) } + + @Test + fun testSetPriorityFee() { + val privateKey = ByteString.copyFrom("baf2b2dbbbad7ca96c1fa199c686f3d8fbd2c7b352f307e37e04f33df6741f18".toHexByteArray()) + val originalTx = "AX43+Ir2EDqf2zLEvgzFrCZKRjdr3wCdp8CnvYh6N0G/s86IueX9BbiNUl16iLRGvwREDfi2Srb0hmLNBFw1BwABAAEDODI+iWe7g68B9iwCy8bFkJKvsIEj350oSOpcv4gNnv/st+6qmqipl9lwMK6toB9TiL7LrJVfij+pKwr+pUKxfwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAG6GdPcA92ORzVJe2jfG8KQqqMHr9YTLu30oM4i7MFEoBAgIAAQwCAAAA6AMAAAAAAAA=" + + // Step 1 - Check if there are no price and limit instructions in the original transaction. + assertEquals(SolanaTransaction.getComputeUnitPrice(originalTx), null) + assertEquals(SolanaTransaction.getComputeUnitLimit(originalTx), null) + + // Step 2 - Set price and limit instructions. + val txWithPrice = SolanaTransaction.setComputeUnitPrice(originalTx, "1000") + val updatedTx = SolanaTransaction.setComputeUnitLimit(txWithPrice, "10000") + + assertEquals(updatedTx, "AX43+Ir2EDqf2zLEvgzFrCZKRjdr3wCdp8CnvYh6N0G/s86IueX9BbiNUl16iLRGvwREDfi2Srb0hmLNBFw1BwABAAIEODI+iWe7g68B9iwCy8bFkJKvsIEj350oSOpcv4gNnv/st+6qmqipl9lwMK6toB9TiL7LrJVfij+pKwr+pUKxfwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwZGb+UhFzL/7K26csOb57yM5bvF9xJrLEObOkAAAAAboZ09wD3Y5HNUl7aN8bwpCqowev1hMu7fSgziLswUSgMDAAUCECcAAAICAAEMAgAAAOgDAAAAAAAAAwAJA+gDAAAAAAAA") + + // Step 3 - Check if price and limit instructions are set successfully. + assertEquals(SolanaTransaction.getComputeUnitPrice(updatedTx), "1000") + assertEquals(SolanaTransaction.getComputeUnitLimit(updatedTx), "10000") + + // Step 4 - Decode transaction into a `RawMessage` Protobuf. + val updatedTxData = Base64.decode(updatedTx) + val decodedData = TransactionDecoder.decode(SOLANA, updatedTxData) + val decodedOutput = DecodingTransactionOutput.parseFrom(decodedData) + + assertEquals(decodedOutput.error, SigningError.OK) + + // Step 5 - Sign the decoded `RawMessage` transaction. + val signingInput = SigningInput.newBuilder() + .setPrivateKey(privateKey) + .setRawMessage(decodedOutput.transaction) + .setTxEncoding(Encoding.Base64) + .build() + val output = AnySigner.sign(signingInput, SOLANA, SigningOutput.parser()) + // Successfully broadcasted tx: + // https://explorer.solana.com/tx/2ho7wZUXbDNz12xGfsXg2kcNMqkBAQjv7YNXNcVcuCmbC4p9FZe9ELeM2gMjq9MKQPpmE3nBW5pbdgwVCfNLr1h8 + val expectedString = "AVUye82Mv+/aWeU2G+B6Nes365mUU2m8iqcGZn/8kFJvw4wY6AgKGG+vJHaknHlCDwE1yi1SIMVUUtNCOm3kHg8BAAIEODI+iWe7g68B9iwCy8bFkJKvsIEj350oSOpcv4gNnv/st+6qmqipl9lwMK6toB9TiL7LrJVfij+pKwr+pUKxfwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwZGb+UhFzL/7K26csOb57yM5bvF9xJrLEObOkAAAAAboZ09wD3Y5HNUl7aN8bwpCqowev1hMu7fSgziLswUSgMDAAUCECcAAAICAAEMAgAAAOgDAAAAAAAAAwAJA+gDAAAAAAAA" + assertEquals(output.encoded, expectedString) + } } \ No newline at end of file diff --git a/include/TrustWalletCore/TWSolanaTransaction.h b/include/TrustWalletCore/TWSolanaTransaction.h index 5e01465970d..871393fc840 100644 --- a/include/TrustWalletCore/TWSolanaTransaction.h +++ b/include/TrustWalletCore/TWSolanaTransaction.h @@ -35,6 +35,7 @@ TWData *_Nonnull TWSolanaTransactionUpdateBlockhashAndSign(TWString *_Nonnull en /// /// \param encodedTx base64 encoded Solana transaction. /// \return nullable Unit Price as a decimal string. Null if no instruction found. +TW_EXPORT_STATIC_METHOD TWString *_Nullable TWSolanaTransactionGetComputeUnitPrice(TWString *_Nonnull encodedTx); /// Try to find a `ComputeBudgetInstruction::SetComputeUnitLimit` instruction in the given transaction, @@ -42,6 +43,7 @@ TWString *_Nullable TWSolanaTransactionGetComputeUnitPrice(TWString *_Nonnull en /// /// \param encodedTx base64 encoded Solana transaction. /// \return nullable Unit Limit as a decimal string. Null if no instruction found. +TW_EXPORT_STATIC_METHOD TWString *_Nullable TWSolanaTransactionGetComputeUnitLimit(TWString *_Nonnull encodedTx); /// Adds or updates a `ComputeBudgetInstruction::SetComputeUnitPrice` instruction of the given transaction, @@ -50,6 +52,7 @@ TWString *_Nullable TWSolanaTransactionGetComputeUnitLimit(TWString *_Nonnull en /// \param encodedTx base64 encoded Solana transaction. /// \price Unit Price as a decimal string. /// \return base64 encoded Solana transaction. Null if an error occurred. +TW_EXPORT_STATIC_METHOD TWString *_Nullable TWSolanaTransactionSetComputeUnitPrice(TWString *_Nonnull encodedTx, TWString *_Nonnull price); /// Adds or updates a `ComputeBudgetInstruction::SetComputeUnitLimit` instruction of the given transaction, @@ -58,6 +61,7 @@ TWString *_Nullable TWSolanaTransactionSetComputeUnitPrice(TWString *_Nonnull en /// \param encodedTx base64 encoded Solana transaction. /// \limit Unit Limit as a decimal string. /// \return base64 encoded Solana transaction. Null if an error occurred. +TW_EXPORT_STATIC_METHOD TWString *_Nullable TWSolanaTransactionSetComputeUnitLimit(TWString *_Nonnull encodedTx, TWString *_Nonnull limit); TW_EXTERN_C_END diff --git a/swift/Tests/Blockchains/SolanaTests.swift b/swift/Tests/Blockchains/SolanaTests.swift index e813fe6c245..9c6287ddef3 100644 --- a/swift/Tests/Blockchains/SolanaTests.swift +++ b/swift/Tests/Blockchains/SolanaTests.swift @@ -290,4 +290,43 @@ class SolanaTests: XCTestCase { let expected = "AQPWaOi7dMdmQpXi8HyQQKwiqIftrg1igGQxGtZeT50ksn4wAnyH4DtDrkkuE0fqgx80LTp4LwNN9a440SrmoA8BAAEDZsL1CMnFVcrMn7JtiOiN1U4hC7WovOVof2DX51xM0H/GizyJTHgrBanCf8bGbrFNTn0x3pCGq30hKbywSTr6AgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAgIAAQwCAAAAKgAAAAAAAAA=" XCTAssertEqual(output.encoded, expected) } + + func testSetPriorityFee() throws { + let privateKey = Data(hexString: "baf2b2dbbbad7ca96c1fa199c686f3d8fbd2c7b352f307e37e04f33df6741f18")! + let originalTx = "AX43+Ir2EDqf2zLEvgzFrCZKRjdr3wCdp8CnvYh6N0G/s86IueX9BbiNUl16iLRGvwREDfi2Srb0hmLNBFw1BwABAAEDODI+iWe7g68B9iwCy8bFkJKvsIEj350oSOpcv4gNnv/st+6qmqipl9lwMK6toB9TiL7LrJVfij+pKwr+pUKxfwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAG6GdPcA92ORzVJe2jfG8KQqqMHr9YTLu30oM4i7MFEoBAgIAAQwCAAAA6AMAAAAAAAA=" + + // Step 1 - Check if there are no price and limit instructions in the original transaction. + XCTAssertEqual(SolanaTransaction.getComputeUnitPrice(encodedTx: originalTx), nil) + XCTAssertEqual(SolanaTransaction.getComputeUnitLimit(encodedTx: originalTx), nil) + + // Step 2 - Set price and limit instructions. + let txWithPrice = SolanaTransaction.setComputeUnitPrice(encodedTx: originalTx, price: "1000")! + let updatedTx = SolanaTransaction.setComputeUnitLimit(encodedTx: txWithPrice, limit: "10000")! + + XCTAssertEqual(updatedTx, "AX43+Ir2EDqf2zLEvgzFrCZKRjdr3wCdp8CnvYh6N0G/s86IueX9BbiNUl16iLRGvwREDfi2Srb0hmLNBFw1BwABAAIEODI+iWe7g68B9iwCy8bFkJKvsIEj350oSOpcv4gNnv/st+6qmqipl9lwMK6toB9TiL7LrJVfij+pKwr+pUKxfwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwZGb+UhFzL/7K26csOb57yM5bvF9xJrLEObOkAAAAAboZ09wD3Y5HNUl7aN8bwpCqowev1hMu7fSgziLswUSgMDAAUCECcAAAICAAEMAgAAAOgDAAAAAAAAAwAJA+gDAAAAAAAA") + + // Step 3 - Check if price and limit instructions are set successfully. + XCTAssertEqual(SolanaTransaction.getComputeUnitPrice(encodedTx: updatedTx), "1000") + XCTAssertEqual(SolanaTransaction.getComputeUnitLimit(encodedTx: updatedTx), "10000") + + // Step 4 - Decode transaction into a `RawMessage` Protobuf. + let updatedTxData = Base64.decode(string: updatedTx)! + let decodeOutputData = TransactionDecoder.decode(coinType: .solana, encodedTx: updatedTxData) + var decodeOutput = try SolanaDecodingTransactionOutput(serializedData: decodeOutputData) + + XCTAssertEqual(decodeOutput.error, .ok) + + // Step 5 - Sign the decoded `RawMessage` transaction. + let signingInput = SolanaSigningInput.with { + $0.privateKey = privateKey + $0.rawMessage = decodeOutput.transaction + $0.txEncoding = .base64 + } + + let output: SolanaSigningOutput = AnySigner.sign(input: signingInput, coin: .solana) + XCTAssertEqual(output.error, .ok) + // Successfully broadcasted tx: + // https://explorer.solana.com/tx/2ho7wZUXbDNz12xGfsXg2kcNMqkBAQjv7YNXNcVcuCmbC4p9FZe9ELeM2gMjq9MKQPpmE3nBW5pbdgwVCfNLr1h8 + XCTAssertEqual(output.encoded, "AVUye82Mv+/aWeU2G+B6Nes365mUU2m8iqcGZn/8kFJvw4wY6AgKGG+vJHaknHlCDwE1yi1SIMVUUtNCOm3kHg8BAAIEODI+iWe7g68B9iwCy8bFkJKvsIEj350oSOpcv4gNnv/st+6qmqipl9lwMK6toB9TiL7LrJVfij+pKwr+pUKxfwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwZGb+UhFzL/7K26csOb57yM5bvF9xJrLEObOkAAAAAboZ09wD3Y5HNUl7aN8bwpCqowev1hMu7fSgziLswUSgMDAAUCECcAAAICAAEMAgAAAOgDAAAAAAAAAwAJA+gDAAAAAAAA") + } }