diff --git a/Package.swift b/Package.swift index 7e933efe93d..68de23747c4 100644 --- a/Package.swift +++ b/Package.swift @@ -12,13 +12,13 @@ let package = Package( targets: [ .binaryTarget( name: "WalletCore", - url: "https://github.com/trustwallet/wallet-core/releases/download/4.1.12/WalletCore.xcframework.zip", - checksum: "1632bbbab1c6a588689eec77a24e1468d9a6746968652cf0a7e669e996c3d24d" + url: "https://github.com/trustwallet/wallet-core/releases/download/4.1.21/WalletCore.xcframework.zip", + checksum: "503937e1013bb7c1f610a8a4ec74a6ccdedb06bdec9fa9126ac47e25a90da06a" ), .binaryTarget( name: "SwiftProtobuf", - url: "https://github.com/trustwallet/wallet-core/releases/download/4.1.12/SwiftProtobuf.xcframework.zip", - checksum: "33d80c20428c9db4fcf99d1272ba19655f7c6ee7e5b1809fa8a7e7d4aa1b222b" + url: "https://github.com/trustwallet/wallet-core/releases/download/4.1.21/SwiftProtobuf.xcframework.zip", + checksum: "f6da2b8fafdce5e8d46ea305972f1ad942cc796f63026a32c883331dd3813285" ) ] ) 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 a7f1ca53348..535970c9957 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 @@ -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. + } } \ No newline at end of file diff --git a/docs/registry.md b/docs/registry.md index a5045889d19..8a109c526d2 100644 --- a/docs/registry.md +++ b/docs/registry.md @@ -131,7 +131,7 @@ This list is generated from [./registry.json](../registry.json) | 10004689 | IoTeX EVM | IOTX | | | | 10007000 | NativeZetaChain | ZETA | | | | 10007700 | NativeCanto | CANTO | | | -| 10008217 | Kaia | KLAY | | | +| 10008217 | Kaia | KAIA | | | | 10009000 | Avalanche C-Chain | AVAX | | | | 10009001 | Evmos | EVMOS | | | | 10042170 | Arbitrum Nova | ETH | | | diff --git a/include/TrustWalletCore/TWSolanaTransaction.h b/include/TrustWalletCore/TWSolanaTransaction.h index 871393fc840..420a84ca7b6 100644 --- a/include/TrustWalletCore/TWSolanaTransaction.h +++ b/include/TrustWalletCore/TWSolanaTransaction.h @@ -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); @@ -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 diff --git a/registry.json b/registry.json index 2934d0c651c..5e43fe7ae90 100644 --- a/registry.json +++ b/registry.json @@ -4066,7 +4066,7 @@ "id": "kaia", "name": "Kaia", "coinId": 10008217, - "symbol": "KLAY", + "symbol": "KAIA", "decimals": 18, "blockchain": "Ethereum", "derivation": [ diff --git a/rust/chains/tw_solana/src/modules/insert_instruction.rs b/rust/chains/tw_solana/src/modules/insert_instruction.rs index 22643e349ce..36c1c583de3 100644 --- a/rust/chains/tw_solana/src/modules/insert_instruction.rs +++ b/rust/chains/tw_solana/src/modules/insert_instruction.rs @@ -91,6 +91,37 @@ pub trait InsertInstruction { Ok(account_added_at) } + /// Adds a fee payer account to the message. + /// Note: The fee payer must NOT be in the account list yet. + 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. + // + // If the fee payer is already in the transaction and there is a malicious instruction to + // transfer tokens from the fee payer to another account, The fee payer may have inadvertently + // signed off on such transactions, which is not what they would expect. + // + // Such examples may be difficult to exploit, but we still took precautionary measures to prohibit + // the new fee payer from appearing in the account list of the transaction out of caution + return SigningError::err(SigningErrorType::Error_internal) + .context("Fee payer account is already in the account list"); + } + + // Insert the fee payer account at the beginning of the account list. + self.account_keys_mut().insert(0, account); + self.message_header_mut().num_required_signatures += 1; + + // Update `program id indexes` and `account id indexes` in every instruction as we inserted the account at the beginning of the list. + self.instructions_mut().iter_mut().for_each(|ix| { + ix.program_id_index += 1; // Update `program id indexes` + ix.accounts + .iter_mut() + .for_each(|account_id| *account_id += 1); // Update `account id indexes` + }); + + Ok(()) + } + /// Returns ALT (Address Lookup Tables) if supported by the message version. fn address_table_lookups(&self) -> Option<&[MessageAddressTableLookup]>; diff --git a/rust/chains/tw_solana/src/modules/tx_signer.rs b/rust/chains/tw_solana/src/modules/tx_signer.rs index 5fb421e6876..754fa372efe 100644 --- a/rust/chains/tw_solana/src/modules/tx_signer.rs +++ b/rust/chains/tw_solana/src/modules/tx_signer.rs @@ -44,13 +44,6 @@ impl TxSigner { ) -> SigningResult { let mut tx = versioned::VersionedTransaction::unsigned(unsigned_msg); - let actual_signatures = key_signs.len(); - let expected_signatures = tx.message.num_required_signatures(); - if actual_signatures != expected_signatures { - return SigningError::err(SigningErrorType::Error_signatures_count) - .with_context(|| format!("Expected '{expected_signatures}' signatures, provided '{actual_signatures}'")); - } - for (signing_pubkey, ed25519_signature) in key_signs { // Find an index of the corresponding account. let account_index = tx diff --git a/rust/chains/tw_solana/src/modules/utils.rs b/rust/chains/tw_solana/src/modules/utils.rs index da19b9d17d8..0d7f61dd8a3 100644 --- a/rust/chains/tw_solana/src/modules/utils.rs +++ b/rust/chains/tw_solana/src/modules/utils.rs @@ -2,6 +2,7 @@ // // Copyright © 2017 Trust Wallet. +use crate::address::SolanaAddress; use crate::defined_addresses::{COMPUTE_BUDGET_ADDRESS, SYSTEM_PROGRAM_ID_ADDRESS}; use crate::modules::insert_instruction::InsertInstruction; use crate::modules::instruction_builder::compute_budget_instruction::{ @@ -158,6 +159,20 @@ impl SolanaTransaction { tx.to_base64().tw_err(|_| SigningErrorType::Error_internal) } + + pub fn set_fee_payer(encoded_tx: &str, fee_payer: SolanaAddress) -> SigningResult { + let tx_bytes = base64::decode(encoded_tx, STANDARD)?; + let mut tx: VersionedTransaction = + bincode::deserialize(&tx_bytes).map_err(|_| SigningErrorType::Error_input_parse)?; + + tx.message.set_fee_payer(fee_payer)?; + + // Set the correct number of zero signatures + let unsigned_tx = VersionedTransaction::unsigned(tx.message); + unsigned_tx + .to_base64() + .tw_err(|_| SigningErrorType::Error_internal) + } } fn try_instruction_as_compute_budget( diff --git a/rust/frameworks/tw_utxo/src/modules/sighash_computer.rs b/rust/frameworks/tw_utxo/src/modules/sighash_computer.rs index 0bcfb626960..f2ee8880590 100644 --- a/rust/frameworks/tw_utxo/src/modules/sighash_computer.rs +++ b/rust/frameworks/tw_utxo/src/modules/sighash_computer.rs @@ -60,11 +60,11 @@ where .input_args() .iter() .enumerate() - .map(|(input_index, utxo)| { + .map(|(signing_input_index, utxo)| { let signing_method = utxo.signing_method; let utxo_args = UtxoPreimageArgs { - input_index, + input_index: signing_input_index, script_pubkey: utxo.script_pubkey.clone(), amount: utxo.amount, // TODO move `leaf_hash_code_separator` to `UtxoTaprootPreimageArgs`. @@ -90,12 +90,14 @@ where let tr_spent_script_pubkeys: Vec