diff --git a/core/bin/zksync_api/src/api_server/tx_sender.rs b/core/bin/zksync_api/src/api_server/tx_sender.rs index c6fe0d2fe6..a97d35b67e 100644 --- a/core/bin/zksync_api/src/api_server/tx_sender.rs +++ b/core/bin/zksync_api/src/api_server/tx_sender.rs @@ -295,36 +295,17 @@ impl TxSender { let mut verified_txs = Vec::new(); let mut verified_signature = None; - let mut messages_to_sign = vec![]; - for tx in &txs { - messages_to_sign.push(self.tx_message_to_sign(&tx.0).await?); - } - if let Some(signature) = eth_signature { // User provided the signature for the whole batch. - let (verified_batch, sign_data) = verify_txs_batch_signature( - txs, - signature, - messages_to_sign, - self.sign_verify_requests.clone(), - ) - .await? - .unwrap_batch(); + let (verified_batch, sign_data) = + self.glm_verify_txs_batch_info(txs, signature).await?; verified_signature = Some(sign_data.signature); verified_txs.extend(verified_batch.into_iter()); } else { // Otherwise, we process every transaction in turn. - for (tx, msg_to_sign) in txs.into_iter().zip(messages_to_sign.into_iter()) { - let verified_tx = verify_tx_info_message_signature( - &tx.0, - tx.1.clone(), - msg_to_sign, - self.sign_verify_requests.clone(), - ) - .await? - .unwrap_tx(); - + for (tx, signature) in txs { + let verified_tx = self.glm_verify_tx_info(&tx, signature).await?; verified_txs.push(verified_tx); } } @@ -382,6 +363,7 @@ impl TxSender { /// Returns a message that user has to sign to send the transaction. /// If the transaction doesn't need a message signature, returns `None`. /// If any error is encountered during the message generation, returns `jsonrpc_core::Error`. + #[allow(dead_code)] async fn tx_message_to_sign(&self, tx: &ZkSyncTx) -> Result>, SubmitError> { Ok(match tx { ZkSyncTx::Transfer(tx) => { @@ -504,6 +486,55 @@ impl TxSender { }) } + fn glm_force_tx_message_to_sign(&self, tx: &ZkSyncTx, decimals: u8) -> Option> { + match tx { + ZkSyncTx::Transfer(tx) => { + let msg = tx.get_ethereum_sign_message("tGLM", decimals).into_bytes(); + Some(msg) + } + + ZkSyncTx::Withdraw(tx) => { + let msg = tx.get_ethereum_sign_message("tGLM", decimals).into_bytes(); + Some(msg) + } + + _ => None, + } + } + + fn glm_force_txs_batch_message_to_sign( + &self, + txs: &[(ZkSyncTx, Option)], + decimals: u8, + ) -> Vec>> { + txs.iter() + .map(|(tx, _)| self.glm_force_tx_message_to_sign(tx, decimals)) + .collect() + } + + async fn glm_txs_batch_message_to_sign( + &self, + txs: &[(ZkSyncTx, Option)], + ) -> Result<(Option, Vec>>), SubmitError> { + let mut messages_to_sign = vec![]; + let mut batch_token = None; + + for tx in txs { + let msg = match self.glm_tx_message_to_sign(&tx.0).await? { + Some((token, msg)) => { + if batch_token.is_none() { + batch_token = Some(token.clone()); + } + Some(msg) + } + None => None, + }; + messages_to_sign.push(msg); + } + + Ok((batch_token, messages_to_sign)) + } + async fn glm_verify_tx_info( &self, tx: &ZkSyncTx, @@ -522,23 +553,7 @@ impl TxSender { Ok(tx) => Ok(tx.unwrap_tx()), Err(err) => { if token.symbol == "GNT" { - let msg_to_sign = match tx { - ZkSyncTx::Transfer(tx) => { - let msg = tx - .get_ethereum_sign_message("tGLM", token.decimals) - .into_bytes(); - Some(msg) - } - - ZkSyncTx::Withdraw(tx) => { - let msg = tx - .get_ethereum_sign_message("tGLM", token.decimals) - .into_bytes(); - Some(msg) - } - - _ => unreachable!(), - }; + let msg_to_sign = self.glm_force_tx_message_to_sign(tx, token.decimals); let verified_tx = verify_tx_info_message_signature( &tx, @@ -568,6 +583,39 @@ impl TxSender { Ok(verified_tx) } } + + async fn glm_verify_txs_batch_info( + &self, + batch: Vec<(ZkSyncTx, Option)>, + signature: TxEthSignature, + ) -> Result<(Vec, EthSignData), SubmitError> { + let (batch_token, messages_to_sign) = self.glm_txs_batch_message_to_sign(&batch).await?; + + let verify_result = verify_txs_batch_signature( + batch.clone(), + signature.clone(), + messages_to_sign, + self.sign_verify_requests.clone(), + ) + .await; + + match (verify_result, batch_token) { + (Ok(tx), _) => Ok(tx.unwrap_batch()), + (Err(_), Some(token)) if token.symbol == "GNT" => { + let messages_to_sign = + self.glm_force_txs_batch_message_to_sign(&batch, token.decimals); + Ok(verify_txs_batch_signature( + batch, + signature, + messages_to_sign, + self.sign_verify_requests.clone(), + ) + .await? + .unwrap_batch()) + } + (Err(e), _) => Err(e), + } + } } async fn send_verify_request_and_recv( diff --git a/sdk/zksync-rs/tests/integration.rs b/sdk/zksync-rs/tests/integration.rs index 6d3ac88db0..f5506989b9 100644 --- a/sdk/zksync-rs/tests/integration.rs +++ b/sdk/zksync-rs/tests/integration.rs @@ -686,6 +686,42 @@ async fn comprehensive_test() -> Result<(), anyhow::Error> { ) .await?; + // Test batch with single transaction + let total_fee = alice_wallet2 + .provider + .get_tx_fee(TxFeeTypes::Transfer, bob_wallet1.address(), "tGLM") + .await? + .total_fee; + + let mut nonce = alice_wallet2.account_info().await?.committed.nonce; + let (transfer, signature) = alice_wallet2 + .signer + .sign_transfer( + token_tglm.clone(), + 20_000_000_000_000_000_000u128.into(), + total_fee, + bob_wallet1.address(), + nonce, + ) + .await?; + *nonce += 1; + + let signed_transfers = vec![( + ZkSyncTx::Transfer(Box::new(transfer.clone())), + signature.clone(), + )]; + + let tx_hashes = alice_wallet2 + .provider + .send_txs_batch(signed_transfers, None) + .await?; + let batch_handle = SyncTransactionHandle::new(tx_hashes[0], alice_wallet2.provider.clone()); + + batch_handle + .commit_timeout(Duration::from_secs(180)) + .wait_for_commit() + .await?; + Ok(()) }