diff --git a/rust/chains/tw_solana/src/modules/message_builder.rs b/rust/chains/tw_solana/src/modules/message_builder.rs index b9e43cde4e5..7b8f6eaee04 100644 --- a/rust/chains/tw_solana/src/modules/message_builder.rs +++ b/rust/chains/tw_solana/src/modules/message_builder.rs @@ -168,9 +168,9 @@ impl<'a> MessageBuilder<'a> { let mut builder = InstructionBuilder::default(); builder + .maybe_advance_nonce(self.nonce_account()?, from) .maybe_priority_fee_price(self.priority_fee_price()) .maybe_priority_fee_limit(self.priority_fee_limit()) - .maybe_advance_nonce(self.nonce_account()?, from) .maybe_memo(transfer.memo.as_ref()) .add_instruction(transfer_ix); Ok(builder.output()) @@ -201,9 +201,9 @@ impl<'a> MessageBuilder<'a> { let mut builder = InstructionBuilder::default(); builder + .maybe_advance_nonce(self.nonce_account()?, sender) .maybe_priority_fee_price(self.priority_fee_price()) .maybe_priority_fee_limit(self.priority_fee_limit()) - .maybe_advance_nonce(self.nonce_account()?, sender) .add_instructions(deposit_ixs); Ok(builder.output()) } @@ -218,9 +218,9 @@ impl<'a> MessageBuilder<'a> { let mut builder = InstructionBuilder::default(); builder + .maybe_advance_nonce(self.nonce_account()?, sender) .maybe_priority_fee_price(self.priority_fee_price()) .maybe_priority_fee_limit(self.priority_fee_limit()) - .maybe_advance_nonce(self.nonce_account()?, sender) .add_instruction(deactivate_ix); Ok(builder.output()) } @@ -241,9 +241,9 @@ impl<'a> MessageBuilder<'a> { let mut builder = InstructionBuilder::default(); builder + .maybe_advance_nonce(self.nonce_account()?, sender) .maybe_priority_fee_price(self.priority_fee_price()) .maybe_priority_fee_limit(self.priority_fee_limit()) - .maybe_advance_nonce(self.nonce_account()?, sender) .add_instructions(deactivate_ixs); Ok(builder.output()) } @@ -266,9 +266,9 @@ impl<'a> MessageBuilder<'a> { let mut builder = InstructionBuilder::default(); builder + .maybe_advance_nonce(self.nonce_account()?, sender) .maybe_priority_fee_price(self.priority_fee_price()) .maybe_priority_fee_limit(self.priority_fee_limit()) - .maybe_advance_nonce(self.nonce_account()?, sender) .add_instruction(withdraw_ix); Ok(builder.output()) } @@ -297,9 +297,9 @@ impl<'a> MessageBuilder<'a> { let mut builder = InstructionBuilder::default(); builder + .maybe_advance_nonce(self.nonce_account()?, sender) .maybe_priority_fee_price(self.priority_fee_price()) .maybe_priority_fee_limit(self.priority_fee_limit()) - .maybe_advance_nonce(self.nonce_account()?, sender) .add_instructions(withdraw_ixs); Ok(builder.output()) } @@ -322,9 +322,9 @@ impl<'a> MessageBuilder<'a> { ); let mut builder = InstructionBuilder::default(); builder + .maybe_advance_nonce(self.nonce_account()?, funding_account) .maybe_priority_fee_price(self.priority_fee_price()) .maybe_priority_fee_limit(self.priority_fee_limit()) - .maybe_advance_nonce(self.nonce_account()?, funding_account) .add_instruction(instruction); Ok(builder.output()) } @@ -359,9 +359,9 @@ impl<'a> MessageBuilder<'a> { let mut builder = InstructionBuilder::default(); builder + .maybe_advance_nonce(self.nonce_account()?, signer) .maybe_priority_fee_price(self.priority_fee_price()) .maybe_priority_fee_limit(self.priority_fee_limit()) - .maybe_advance_nonce(self.nonce_account()?, signer) .maybe_memo(token_transfer.memo.as_ref()) .add_instruction(transfer_instruction); Ok(builder.output()) @@ -407,9 +407,9 @@ impl<'a> MessageBuilder<'a> { let mut builder = InstructionBuilder::default(); builder + .maybe_advance_nonce(self.nonce_account()?, signer) .maybe_priority_fee_price(self.priority_fee_price()) .maybe_priority_fee_limit(self.priority_fee_limit()) - .maybe_advance_nonce(self.nonce_account()?, signer) .add_instruction(create_account_instruction) // Optional memo. Order: before transfer, as per documentation. .maybe_memo(create_and_transfer.memo.as_ref()) @@ -435,9 +435,9 @@ impl<'a> MessageBuilder<'a> { let mut builder = InstructionBuilder::default(); builder + .maybe_advance_nonce(prev_nonce_account, signer) .maybe_priority_fee_price(self.priority_fee_price()) .maybe_priority_fee_limit(self.priority_fee_limit()) - .maybe_advance_nonce(prev_nonce_account, signer) .add_instructions(SystemInstructionBuilder::create_nonce_account( signer, new_nonce_account, @@ -457,9 +457,9 @@ impl<'a> MessageBuilder<'a> { let mut builder = InstructionBuilder::default(); builder + .maybe_advance_nonce(self.nonce_account()?, signer) .maybe_priority_fee_price(self.priority_fee_price()) .maybe_priority_fee_limit(self.priority_fee_limit()) - .maybe_advance_nonce(self.nonce_account()?, signer) .add_instruction(SystemInstructionBuilder::withdraw_nonce_account( withdraw_from_nonce, signer, @@ -478,9 +478,9 @@ impl<'a> MessageBuilder<'a> { let mut builder = InstructionBuilder::default(); builder + .maybe_advance_nonce(Some(nonce_account), signer) .maybe_priority_fee_price(self.priority_fee_price()) - .maybe_priority_fee_limit(self.priority_fee_limit()) - .maybe_advance_nonce(Some(nonce_account), signer); + .maybe_priority_fee_limit(self.priority_fee_limit()); Ok(builder.output()) } diff --git a/tests/chains/Solana/TransactionCompilerTests.cpp b/tests/chains/Solana/TransactionCompilerTests.cpp index 53e3567486e..7130966e3b8 100644 --- a/tests/chains/Solana/TransactionCompilerTests.cpp +++ b/tests/chains/Solana/TransactionCompilerTests.cpp @@ -83,4 +83,52 @@ TEST(SolanaCompiler, CompileTransferWithSignatures) { } } +TEST(SolanaCompiler, CompileTransferWithPriorityFee) { + // tx on mainnet + // https://explorer.solana.com/tx/5asW13PSGvbZAeiGe8YFo7jt3UTqb8KUfFhXh5DXpDVfpVup1ZP41tp7PmBJH43gK5xT9U4VDVChDynmC7PJp9fa + + const auto coin = TWCoinTypeSolana; + /// Step 1: Prepare transaction input (protobuf) + auto input = TW::Solana::Proto::SigningInput(); + auto& message = *input.mutable_transfer_transaction(); + auto recipient = std::string("6NdFCrugyZVRhFbHvJT3dFBrGE9ZYFbfc8dBS2q4d2a9"); + auto sender = std::string("EQ37VYUVcqUSzYgvaguDB4yVRg5m3Xg7qQoKF8zFiJxe"); + input.set_sender(sender); + input.set_recent_blockhash(std::string("8oFmGuWUpsy8h8WP8AXTp3yhcLt4gQJRG5GxNcK7jfhX")); + input.set_nonce_account("ubKTCz9avQt3twiC9TbRjfoCiJnggH1abjgj9FjZJJm"); + input.mutable_priority_fee_price()->set_price(40000); + input.mutable_priority_fee_limit()->set_limit(480000); + message.set_recipient(recipient); + message.set_value((uint64_t)10000); + auto inputString = input.SerializeAsString(); + auto inputStrData = TW::Data(inputString.begin(), inputString.end()); + + /// Step 2: Obtain preimage hash + const auto preImageHashesData = TransactionCompiler::preImageHashes(coin, inputStrData); + auto preSigningOutput = TW::Solana::Proto::PreSigningOutput(); + preSigningOutput.ParseFromArray(preImageHashesData.data(), (int)preImageHashesData.size()); + ASSERT_EQ(preSigningOutput.signers_size(), 1); + auto signer = preSigningOutput.signers(0); + EXPECT_EQ(signer, sender); + auto preImageHash = preSigningOutput.data(); + EXPECT_EQ(hex(preImageHash), "01000306c70ead37125b5e142838eb59a6883ef915474f9b0a52494f698a4d23f4827ca70d79017647148829ddee52e687a840b42ce55a808367ff3cb0dc67444f5361ca4fd49d2664e7ac3016160e0b943a9222a21bee6510248dc2ae341f58195a2a3606a7d517192c568ee08a845f73d29788cf035c3145b21ab344d8062ea940000000000000000000000000000000000000000000000000000000000000000000000306466fe5211732ffecadba72c39be7bc8ce5bbc5f7126b2c439b3a4000000073db37fafe5d370e4605395dfc4661d886170c751ecfd7d76a744ea43c9f16f6040403010300040400000005000903409c0000000000000500050200530700040200020c020000001027000000000000"); + + // Simulate signature, normally obtained from signature server + const Data publicKeyData = parse_hex("c70ead37125b5e142838eb59a6883ef915474f9b0a52494f698a4d23f4827ca7"); + const PublicKey publicKey = PublicKey(publicKeyData, TWPublicKeyTypeED25519); + const auto signature = parse_hex("e546dbc2b896ff53abe5d3f090abdea84fb3862c6dcab4a6878e4d0dc803a53a2b077ef14014737e049815ff4df5daa92dc3e11b55770466d5feab6bdfccf005"); + // Verify signature (pubkey & hash & signature) + EXPECT_TRUE(publicKey.verify(signature, TW::data(preImageHash))); + + /// Step 3: Compile transaction info + auto outputData = TransactionCompiler::compileWithSignatures(coin, inputStrData, {signature}, {publicKeyData}); + const auto ExpectedTx = "84RnGAbza5DstiCPgBwtyuGnB2TPYsRGFr4L9tvzc71tBjyPS5aK9xGfMPdKA16gm8dJSxdAwMQX22zF3bQAHtgNHSApaL9Hs2B4Rz1HjazPmbNYLJKkSZ4gq2MWbY6DSKkg3NUf4L9HpZVFbrUw7TNgFYbEYiA1wTJ4aVwVAh9NQLDaQBgANnMvjFTYy2rDjgHL8nZU6omjK2uvDaLdqp2L4hXjGTKQ1mMFVRLXnrMUX8dijBPty3NDcgJ3G442ccGguez7DwYooirYuZ5ajiJwkKtqu8tW4namRdvAC7YmL1tCqZpWXyDBhqMapoyf1bVCvUbnuz64RZwhd7nj6DyULtiMXCUXFayeShe2nvJGTkzWZxEEeHPyvLtTmSNrmRWqE2ZEDCFGH3bopBKG2QJVeEomh7rKFSZ6WTDmG2V7L6zPFexAhuen9ynEBu8JJWRM3nx5dj4BSDeQf"; + { + Solana::Proto::SigningOutput output; + ASSERT_TRUE(output.ParseFromArray(outputData.data(), (int)outputData.size())); + + EXPECT_EQ(output.encoded(), ExpectedTx); + } +} + } // namespace TW::Solana::tests