Skip to content

Commit

Permalink
Exports solana setFeePayer function to kotlin/swift code
Browse files Browse the repository at this point in the history
  • Loading branch information
10gic committed Dec 11, 2024
1 parent d3a5a75 commit fb60df5
Show file tree
Hide file tree
Showing 9 changed files with 160 additions and 18 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -118,4 +118,19 @@ class TestSolanaTransaction {
val expectedString = "AVUye82Mv+/aWeU2G+B6Nes365mUU2m8iqcGZn/8kFJvw4wY6AgKGG+vJHaknHlCDwE1yi1SIMVUUtNCOm3kHg8BAAIEODI+iWe7g68B9iwCy8bFkJKvsIEj350oSOpcv4gNnv/st+6qmqipl9lwMK6toB9TiL7LrJVfij+pKwr+pUKxfwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwZGb+UhFzL/7K26csOb57yM5bvF9xJrLEObOkAAAAAboZ09wD3Y5HNUl7aN8bwpCqowev1hMu7fSgziLswUSgMDAAUCECcAAAICAAEMAgAAAOgDAAAAAAAAAwAJA+gDAAAAAAAA"
assertEquals(output.encoded, expectedString)
}

@Test
fun testSetFeePayer() {
val originalTx = "AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAQABA2uEKrOPvZNBtdUtSFXcg8+kj4O/Z1Ht/hwvnaqq5s6mTXd3KtwUyJFfRs2PBfeQW8xCEZvNr/5J/Tx8ltbn0pwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACo+QRbvXWNKoOfaOL4cSpfYrmn/2TV+dBmct+HsmmwdAQICAAEMAgAAAACcnwYAAAAAAA=="

// Step 1 - Add fee payer to the transaction.
val updatedTx = SolanaTransaction.setFeePayer(originalTx, "Eg5jqooyG6ySaXKbQUu4Lpvu2SqUPZrNkM4zXs9iUDLJ")
assertEquals(updatedTx, "AgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAIAAQTLKvCJtWpVdze8Fxjgy/Iyz1sC4U7gqnxmdSM/X2+bV2uEKrOPvZNBtdUtSFXcg8+kj4O/Z1Ht/hwvnaqq5s6mTXd3KtwUyJFfRs2PBfeQW8xCEZvNr/5J/Tx8ltbn0pwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACo+QRbvXWNKoOfaOL4cSpfYrmn/2TV+dBmct+HsmmwdAQMCAQIMAgAAAACcnwYAAAAAAA==")

// This case originates from a test case in C++. Here, only the most critical function is verified for correctness,
// while the remaining steps have been omitted.
// Step 2 - Decode transaction into a `RawMessage` Protobuf.
// Step 3 - Obtain preimage hash.
// Step 4 - Compile transaction info.
}
}
12 changes: 10 additions & 2 deletions include/TrustWalletCore/TWSolanaTransaction.h
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ TWString *_Nullable TWSolanaTransactionGetComputeUnitLimit(TWString *_Nonnull en
/// and returns the updated transaction.
///
/// \param encodedTx base64 encoded Solana transaction.
/// \price Unit Price as a decimal string.
/// \param 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);
Expand All @@ -59,9 +59,17 @@ TWString *_Nullable TWSolanaTransactionSetComputeUnitPrice(TWString *_Nonnull en
/// and returns the updated transaction.
///
/// \param encodedTx base64 encoded Solana transaction.
/// \limit Unit Limit as a decimal string.
/// \param 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);

/// Adds fee payer to the given transaction and returns the updated transaction.
///
/// \param encodedTx base64 encoded Solana transaction.
/// \param feePayer fee payer account address. Must be a base58 encoded public key. It must NOT be in the account list yet.
/// \return base64 encoded Solana transaction. Null if an error occurred.
TW_EXPORT_STATIC_METHOD
TWString *_Nullable TWSolanaTransactionSetFeePayer(TWString *_Nonnull encodedTx, TWString *_Nonnull feePayer);

TW_EXTERN_C_END
2 changes: 1 addition & 1 deletion rust/chains/tw_solana/src/modules/insert_instruction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ pub trait InsertInstruction {

/// Adds a fee payer account to the message.
/// Note: The fee payer must NOT be in the account list yet.
fn add_fee_payer(&mut self, account: SolanaAddress) -> SigningResult<()> {
fn set_fee_payer(&mut self, account: SolanaAddress) -> SigningResult<()> {
if self.account_keys_mut().contains(&account) {
// For security reasons, we don't allow adding a fee payer if it's already in the account list.
//
Expand Down
4 changes: 2 additions & 2 deletions rust/chains/tw_solana/src/modules/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -160,12 +160,12 @@ impl SolanaTransaction {
tx.to_base64().tw_err(|_| SigningErrorType::Error_internal)
}

pub fn add_fee_payer(encoded_tx: &str, fee_payer: SolanaAddress) -> SigningResult<String> {
pub fn set_fee_payer(encoded_tx: &str, fee_payer: SolanaAddress) -> SigningResult<String> {
let tx_bytes = base64::decode(encoded_tx, STANDARD)?;
let mut tx: VersionedTransaction =
bincode::deserialize(&tx_bytes).map_err(|_| SigningErrorType::Error_input_parse)?;

tx.message.add_fee_payer(fee_payer)?;
tx.message.set_fee_payer(fee_payer)?;

// Set the correct number of zero signatures
let unsigned_tx = VersionedTransaction::unsigned(tx.message);
Expand Down
20 changes: 10 additions & 10 deletions rust/tw_tests/tests/chains/solana/solana_transaction_ffi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,9 @@ use tw_proto::Common::Proto::SigningError;
use tw_proto::Solana::Proto::{self};
use tw_solana::SOLANA_ALPHABET;
use wallet_core_rs::ffi::solana::transaction::{
tw_solana_transaction_add_fee_payer, tw_solana_transaction_get_compute_unit_limit,
tw_solana_transaction_get_compute_unit_price, tw_solana_transaction_set_compute_unit_limit,
tw_solana_transaction_set_compute_unit_price, tw_solana_transaction_update_blockhash_and_sign,
tw_solana_transaction_get_compute_unit_limit, tw_solana_transaction_get_compute_unit_price,
tw_solana_transaction_set_compute_unit_limit, tw_solana_transaction_set_compute_unit_price,
tw_solana_transaction_set_fee_payer, tw_solana_transaction_update_blockhash_and_sign,
};

#[test]
Expand Down Expand Up @@ -285,7 +285,7 @@ fn test_solana_transaction_set_priority_fee_transfer_with_address_lookup() {
}

#[test]
fn test_solana_transaction_add_fee_payer() {
fn test_solana_transaction_set_fee_payer() {
let encoded_tx_str = "AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAQABA2uEKrOPvZNBtdUtSFXcg8+kj4O/Z1Ht/hwvnaqq5s6mTXd3KtwUyJFfRs2PBfeQW8xCEZvNr/5J/Tx8ltbn0pwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACo+QRbvXWNKoOfaOL4cSpfYrmn/2TV+dBmct+HsmmwdAQICAAEMAgAAAACcnwYAAAAAAA==";
let encoded_tx = TWStringHelper::create(encoded_tx_str);

Expand All @@ -294,7 +294,7 @@ fn test_solana_transaction_add_fee_payer() {

// Step 1 - Add fee payer to the transaction.
let updated_tx = TWStringHelper::wrap(unsafe {
tw_solana_transaction_add_fee_payer(encoded_tx.ptr(), fee_payer.ptr())
tw_solana_transaction_set_fee_payer(encoded_tx.ptr(), fee_payer.ptr())
});
let updated_tx = updated_tx.to_string().unwrap();
assert_eq!(updated_tx, "AgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAIAAQTLKvCJtWpVdze8Fxjgy/Iyz1sC4U7gqnxmdSM/X2+bV2uEKrOPvZNBtdUtSFXcg8+kj4O/Z1Ht/hwvnaqq5s6mTXd3KtwUyJFfRs2PBfeQW8xCEZvNr/5J/Tx8ltbn0pwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACo+QRbvXWNKoOfaOL4cSpfYrmn/2TV+dBmct+HsmmwdAQMCAQIMAgAAAACcnwYAAAAAAA==");
Expand All @@ -313,7 +313,7 @@ fn test_solana_transaction_add_fee_payer() {
..Proto::SigningInput::default()
};

// Step 3: Obtain preimage hash
// Step 3 - Obtain preimage hash.
let mut pre_imager = PreImageHelper::<Proto::PreSigningOutput>::default();
let preimage_output = pre_imager.pre_image_hashes(CoinType::Solana, &signing_input);

Expand All @@ -323,7 +323,7 @@ fn test_solana_transaction_add_fee_payer() {
"8002000104cb2af089b56a557737bc1718e0cbf232cf5b02e14ee0aa7c6675233f5f6f9b576b842ab38fbd9341b5d52d4855dc83cfa48f83bf6751edfe1c2f9daaaae6cea64d77772adc14c8915f46cd8f05f7905bcc42119bcdaffe49fd3c7c96d6e7d29c00000000000000000000000000000000000000000000000000000000000000002a3e4116ef5d634aa0e7da38be1c4a97d8ae69ffd9357e74199cb7e1ec9a6c1d01030201020c02000000009c9f060000000000"
);

// Step 4: Compile transaction info
// Step 4 - Compile transaction info.
// Simulate signature, normally obtained from signature server.
let fee_payer_signature = "feb9f15cc345fa156450676100033860edbe80a6f61dab8199e94fdc47678ecfdb95e3bc10ec0a7f863ab8ef5c38edae72db7e5d72855db225fd935fd59b700a".decode_hex().unwrap();
let fee_payer_public_key = base58::decode(fee_payer_str, SOLANA_ALPHABET).unwrap();
Expand All @@ -349,18 +349,18 @@ fn test_solana_transaction_add_fee_payer() {
}

#[test]
fn test_solana_transaction_add_fee_payer_already_exists() {
fn test_solana_transaction_set_fee_payer_already_exists() {
let encoded_tx_str = "AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAQABA2uEKrOPvZNBtdUtSFXcg8+kj4O/Z1Ht/hwvnaqq5s6mTXd3KtwUyJFfRs2PBfeQW8xCEZvNr/5J/Tx8ltbn0pwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACo+QRbvXWNKoOfaOL4cSpfYrmn/2TV+dBmct+HsmmwdAQICAAEMAgAAAACcnwYAAAAAAA==";
let encoded_tx = TWStringHelper::create(encoded_tx_str);

let fee_payer_str = "8EhWjZGEt58UKzeiburZVx6QQF3rbayScpDjPNqCx62q";
let fee_payer = TWStringHelper::create(fee_payer_str);

let updated_tx = TWStringHelper::wrap(unsafe {
tw_solana_transaction_add_fee_payer(encoded_tx.ptr(), fee_payer.ptr())
tw_solana_transaction_set_fee_payer(encoded_tx.ptr(), fee_payer.ptr())
});

// The fee payer is already in the transaction.
// We expect tw_solana_transaction_add_fee_payer to return null.
// We expect tw_solana_transaction_set_fee_payer to return null.
assert_eq!(updated_tx.to_string(), None);
}
6 changes: 3 additions & 3 deletions rust/wallet_core_rs/src/ffi/solana/transaction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -137,10 +137,10 @@ pub unsafe extern "C" fn tw_solana_transaction_set_compute_unit_limit(
/// Adds fee payer to the given transaction, and returns the updated transaction.
///
/// \param encoded_tx base64 encoded Solana transaction.
/// \price fee_payer fee payer account address. Must be a base58 encoded public key.
/// \param fee_payer fee payer account address. Must be a base58 encoded public key. It must NOT be in the account list yet.
/// \return base64 encoded Solana transaction. Null if an error occurred.
#[no_mangle]
pub unsafe extern "C" fn tw_solana_transaction_add_fee_payer(
pub unsafe extern "C" fn tw_solana_transaction_set_fee_payer(
encoded_tx: *const TWString,
fee_payer: *const TWString,
) -> *mut TWString {
Expand All @@ -151,7 +151,7 @@ pub unsafe extern "C" fn tw_solana_transaction_add_fee_payer(
let fee_payer = try_or_else!(fee_payer.as_str(), std::ptr::null_mut);
let fee_payer = try_or_else!(fee_payer.parse(), std::ptr::null_mut);

match SolanaTransaction::add_fee_payer(encoded_tx, fee_payer) {
match SolanaTransaction::set_fee_payer(encoded_tx, fee_payer) {
Ok(updated_tx) => TWString::from(updated_tx).into_ptr(),
_ => std::ptr::null_mut(),
}
Expand Down
17 changes: 17 additions & 0 deletions src/interface/TWSolanaTransaction.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -87,3 +87,20 @@ TWString *_Nullable TWSolanaTransactionSetComputeUnitLimit(TWString *_Nonnull en
auto updatedTx = updatedTxStr.toStringOrDefault();
return TWStringCreateWithUTF8Bytes(updatedTx.c_str());
}

TWString *_Nullable TWSolanaTransactionSetFeePayer(TWString *_Nonnull encodedTx, TWString *_Nonnull feePayer) {
auto& encodedTxRef = *reinterpret_cast<const std::string*>(encodedTx);
auto& feePayerRef = *reinterpret_cast<const std::string*>(feePayer);

const Rust::TWStringWrapper encodedTxStr = encodedTxRef;
const Rust::TWStringWrapper feePayerStr = feePayerRef;

Rust::TWStringWrapper updatedTxStr = Rust::tw_solana_transaction_set_fee_payer(encodedTxStr.get(), feePayerStr.get());

if (!updatedTxStr) {
return nullptr;
}

const auto updatedTx = updatedTxStr.toStringOrDefault();
return TWStringCreateWithUTF8Bytes(updatedTx.c_str());
}
48 changes: 48 additions & 0 deletions swift/Tests/Blockchains/SolanaTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -329,6 +329,54 @@ class SolanaTests: XCTestCase {
// https://explorer.solana.com/tx/2ho7wZUXbDNz12xGfsXg2kcNMqkBAQjv7YNXNcVcuCmbC4p9FZe9ELeM2gMjq9MKQPpmE3nBW5pbdgwVCfNLr1h8
XCTAssertEqual(output.encoded, "AVUye82Mv+/aWeU2G+B6Nes365mUU2m8iqcGZn/8kFJvw4wY6AgKGG+vJHaknHlCDwE1yi1SIMVUUtNCOm3kHg8BAAIEODI+iWe7g68B9iwCy8bFkJKvsIEj350oSOpcv4gNnv/st+6qmqipl9lwMK6toB9TiL7LrJVfij+pKwr+pUKxfwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwZGb+UhFzL/7K26csOb57yM5bvF9xJrLEObOkAAAAAboZ09wD3Y5HNUl7aN8bwpCqowev1hMu7fSgziLswUSgMDAAUCECcAAAICAAEMAgAAAOgDAAAAAAAAAwAJA+gDAAAAAAAA")
}

func testSetFeePayer() throws {
// base64 encoded
let originalTx = "AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAQABA2uEKrOPvZNBtdUtSFXcg8+kj4O/Z1Ht/hwvnaqq5s6mTXd3KtwUyJFfRs2PBfeQW8xCEZvNr/5J/Tx8ltbn0pwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACo+QRbvXWNKoOfaOL4cSpfYrmn/2TV+dBmct+HsmmwdAQICAAEMAgAAAACcnwYAAAAAAA=="

// Step 1 - Add fee payer to the transaction.
let updatedTx = SolanaTransaction.setFeePayer(encodedTx: originalTx, feePayer: "Eg5jqooyG6ySaXKbQUu4Lpvu2SqUPZrNkM4zXs9iUDLJ")!

XCTAssertEqual(updatedTx, "AgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAIAAQTLKvCJtWpVdze8Fxjgy/Iyz1sC4U7gqnxmdSM/X2+bV2uEKrOPvZNBtdUtSFXcg8+kj4O/Z1Ht/hwvnaqq5s6mTXd3KtwUyJFfRs2PBfeQW8xCEZvNr/5J/Tx8ltbn0pwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACo+QRbvXWNKoOfaOL4cSpfYrmn/2TV+dBmct+HsmmwdAQMCAQIMAgAAAACcnwYAAAAAAA==")

// Step 2 - 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 3 - Obtain preimage hash.
let signingInput = SolanaSigningInput.with {
$0.rawMessage = decodeOutput.transaction
$0.txEncoding = .base64
}
let txInputData = try signingInput.serializedData() // Serialize input
let preImageHashes = TransactionCompiler.preImageHashes(coinType: .solana, txInputData: txInputData)
let preSigningOutput: BitcoinPreSigningOutput = try SolanaPreSigningOutput(serializedData: preImageHashes)
XCTAssertEqual(preSigningOutput.error, CommonSigningError.ok)
XCTAssertEqual(preSigningOutput.data.hexString, "8002000104cb2af089b56a557737bc1718e0cbf232cf5b02e14ee0aa7c6675233f5f6f9b576b842ab38fbd9341b5d52d4855dc83cfa48f83bf6751edfe1c2f9daaaae6cea64d77772adc14c8915f46cd8f05f7905bcc42119bcdaffe49fd3c7c96d6e7d29c00000000000000000000000000000000000000000000000000000000000000002a3e4116ef5d634aa0e7da38be1c4a97d8ae69ffd9357e74199cb7e1ec9a6c1d01030201020c02000000009c9f060000000000")

// Step 4 - Compile transaction info.
// Simulate signature, normally obtained from signature server.
let signatureVec = DataVector()
let pubkeyVec = DataVector()
let feePayerSignature = Data(hexString: "feb9f15cc345fa156450676100033860edbe80a6f61dab8199e94fdc47678ecfdb95e3bc10ec0a7f863ab8ef5c38edae72db7e5d72855db225fd935fd59b700a")!
let feePayerPublicKey = Data(hexString: "cb2af089b56a557737bc1718e0cbf232cf5b02e14ee0aa7c6675233f5f6f9b57")!
signatureVec.add(data: feePayerSignature)
pubkeyVec.add(data: feePayerPublicKey)
let solSenderSignature = Data(hexString: "936cd6d176e701d1f748031925b2f029f6f1ab4b99aec76e24ccf05649ec269569a08ec0bd80f5fee1cb8d13ecd420bf50c5f64ae74c7afa267458cabb4e5804")!
let solSenderPublicKey = Data(hexString: "6b842ab38fbd9341b5d52d4855dc83cfa48f83bf6751edfe1c2f9daaaae6cea6")!
signatureVec.add(data: solSenderSignature)
pubkeyVec.add(data: solSenderPublicKey)

let compileWithSignatures = TransactionCompiler.compileWithSignatures(coinType: coin, txInputData: txInputData, signatures: signatureVec, publicKeys: pubkeyVec)
let expectedTx = "Av658VzDRfoVZFBnYQADOGDtvoCm9h2rgZnpT9xHZ47P25XjvBDsCn+GOrjvXDjtrnLbfl1yhV2yJf2TX9WbcAqTbNbRducB0fdIAxklsvAp9vGrS5mux24kzPBWSewmlWmgjsC9gPX+4cuNE+zUIL9QxfZK50x6+iZ0WMq7TlgEgAIAAQTLKvCJtWpVdze8Fxjgy/Iyz1sC4U7gqnxmdSM/X2+bV2uEKrOPvZNBtdUtSFXcg8+kj4O/Z1Ht/hwvnaqq5s6mTXd3KtwUyJFfRs2PBfeQW8xCEZvNr/5J/Tx8ltbn0pwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACo+QRbvXWNKoOfaOL4cSpfYrmn/2TV+dBmct+HsmmwdAQMCAQIMAgAAAACcnwYAAAAAAA=="
let output: SolanaSigningOutput = try SolanaSigningOutput(serializedData: compileWithSignatures)
XCTAssertEqual(output.encoded.hexString, expectedTx)
// Successfully broadcasted tx:
// https://explorer.solana.com/tx/66PAVjxFVGP4ctrkXmyNRhp6BdFT7gDe1k356DZzCRaBDTmJZF1ewGsbujWRjDTrt5utnz8oHZw3mg8qBNyct41w?cluster=devnet
}

func testSignUserMessage() throws {
let privateKey = Data(hexString: "44f480ca27711895586074a14c552e58cc52e66a58edb6c58cf9b9b7295d4a2d")!
Expand Down
Loading

0 comments on commit fb60df5

Please sign in to comment.