diff --git a/core-rust/mesh-api-server/src/mesh_api/conversions/operations.rs b/core-rust/mesh-api-server/src/mesh_api/conversions/operations.rs index 0fa498721a..464b9d8d91 100644 --- a/core-rust/mesh-api-server/src/mesh_api/conversions/operations.rs +++ b/core-rust/mesh-api-server/src/mesh_api/conversions/operations.rs @@ -1,7 +1,8 @@ use crate::engine_prelude::*; use crate::prelude::*; use radix_engine_interface::blueprints::account::{ - AccountTryDepositOrAbortManifestInput, AccountWithdrawManifestInput, + AccountTryDepositBatchOrAbortManifestInput, AccountTryDepositOrAbortManifestInput, + AccountWithdrawManifestInput, }; use radix_transactions::manifest::{CallMethod, TakeFromWorktop}; @@ -196,6 +197,8 @@ pub fn to_mesh_api_operations_from_instructions_v1( ) -> Result, ResponseError> { let mut operations = Vec::new(); let mut next_index = 0; + let mut withdraw_input: Option<(ResourceAddress, Decimal)> = None; + while next_index < instructions.len() { let mut instruction = &instructions[next_index]; next_index = next_index + 1; @@ -204,35 +207,58 @@ pub fn to_mesh_api_operations_from_instructions_v1( address: DynamicGlobalAddress::Static(global_address), method_name, args, - }) if global_address.is_account() => { + }) => { let args_bytes = manifest_encode(&args).unwrap(); match method_name.as_str() { "lock_fee" => (), - "withdraw" => { + "withdraw" if global_address.is_account() => { let input = manifest_decode::(&args_bytes) .map_err(|_| { - ResponseError::from(ApiError::InvalidWithdrawInstruction) + ResponseError::from(ApiError::InvalidManifestInstruction) .with_details("Invalid withdraw instruction") })?; + let resource_adddress = &match input.resource_address { + ManifestResourceAddress::Static(resource_address) => resource_address, + ManifestResourceAddress::Named(_) => { + return Err(ResponseError::from(ApiError::NamedAddressNotSupported) + .with_details("Named address is not supported")) + } + }; + operations.push(to_mesh_api_operation_no_fee( mapping_context, database, operations.len() as i64, None, global_address, - &match input.resource_address { - ManifestResourceAddress::Static(resource_address) => { - resource_address - } - ManifestResourceAddress::Named(_) => { - return Err(ResponseError::from( - ApiError::NamedAddressNotSupported, - ) - .with_details("Named address is not supported")) - } - }, + resource_adddress, -input.amount.clone(), )?); + withdraw_input = Some((*resource_adddress, input.amount)); + } + // Below assumes that previous operation was Withdraw and whole withdraw amount + // shall be deposited to the global address + "try_deposit_batch_or_abort" + if global_address.is_account() && withdraw_input.is_some() => + { + let (resource_address, amount) = withdraw_input.unwrap(); + if let Ok(_input) = manifest_decode::< + AccountTryDepositBatchOrAbortManifestInput, + >(&args_bytes) + { + operations.push(to_mesh_api_operation_no_fee( + mapping_context, + database, + operations.len() as i64, + None, + global_address, + &resource_address, + amount, + )?); + } else { + return Err(ResponseError::from(ApiError::InvalidManifestInstruction) + .with_details("Invalid try_deposit_batch_or_abort instruction")); + } } _ => { return Err(ResponseError::from(ApiError::UnrecognizedInstruction) @@ -265,6 +291,9 @@ pub fn to_mesh_api_operations_from_instructions_v1( resource_address, *amount, )?); + } else { + return Err(ResponseError::from(ApiError::InvalidManifestInstruction) + .with_details("Invalid try_deposit_or_abort instruction")); } } _ => { @@ -272,6 +301,7 @@ pub fn to_mesh_api_operations_from_instructions_v1( .with_details(format!("Unrecognized instruction: {:?}", instruction))); } } + withdraw_input = None; } _ => { return Err(ResponseError::from(ApiError::UnrecognizedInstruction) diff --git a/core-rust/mesh-api-server/src/mesh_api/errors.rs b/core-rust/mesh-api-server/src/mesh_api/errors.rs index 6bcd168b2d..9ee6292684 100644 --- a/core-rust/mesh-api-server/src/mesh_api/errors.rs +++ b/core-rust/mesh-api-server/src/mesh_api/errors.rs @@ -31,8 +31,8 @@ pub(crate) enum ApiError { InvalidNumberOfSignatures, #[strum(serialize = "Invalid transaction")] InvalidTransaction, - #[strum(serialize = "Invalid Withdraw instruction")] - InvalidWithdrawInstruction, + #[strum(serialize = "Invalid manifest instruction")] + InvalidManifestInstruction, #[strum(serialize = "Named address not supported")] NamedAddressNotSupported, #[strum(serialize = "Instruction is not recognized")] diff --git a/core/src/test/java/com/radixdlt/api/DeterministicMeshApiTestBase.java b/core/src/test/java/com/radixdlt/api/DeterministicMeshApiTestBase.java index d4ede19ffb..275630b80d 100644 --- a/core/src/test/java/com/radixdlt/api/DeterministicMeshApiTestBase.java +++ b/core/src/test/java/com/radixdlt/api/DeterministicMeshApiTestBase.java @@ -145,6 +145,10 @@ public Response assertErrorResponseOfType( return coreApiHelper.assertErrorResponseOfType(apiCall, responseClass); } + protected CoreApiHelper getCoreApiHelper() { + return coreApiHelper; + } + protected TransactionApi getCoreTransactionApi() { return coreApiHelper.transactionApi(); } diff --git a/core/src/test/java/com/radixdlt/api/mesh_api/MeshApiMempoolEndpointsTest.java b/core/src/test/java/com/radixdlt/api/mesh_api/MeshApiMempoolEndpointsTest.java index f2cbebe438..8a9954cf16 100644 --- a/core/src/test/java/com/radixdlt/api/mesh_api/MeshApiMempoolEndpointsTest.java +++ b/core/src/test/java/com/radixdlt/api/mesh_api/MeshApiMempoolEndpointsTest.java @@ -69,8 +69,13 @@ import com.radixdlt.api.DeterministicMeshApiTestBase; import com.radixdlt.api.core.generated.models.TransactionSubmitRequest; import com.radixdlt.api.mesh.generated.models.*; +import com.radixdlt.identifiers.Address; +import com.radixdlt.rev2.Decimal; +import com.radixdlt.rev2.Manifest; +import com.radixdlt.rev2.ScryptoConstants; import com.radixdlt.rev2.TransactionBuilder; import java.util.HashSet; +import java.util.List; import org.junit.Test; public class MeshApiMempoolEndpointsTest extends DeterministicMeshApiTestBase { @@ -81,12 +86,12 @@ public void test_mempool_endpoint() throws Exception { test.suppressUnusedWarning(); // Arrange - var expected_transaction_identifiers = new HashSet(); + var expectedTransactionIdentifiers = new HashSet(); for (int i = 0; i < 2; i++) { var transaction = TransactionBuilder.forTests().prepare(); - expected_transaction_identifiers.add( + expectedTransactionIdentifiers.add( new TransactionIdentifier() .hash(addressing.encode(transaction.transactionIntentHash()))); @@ -103,14 +108,14 @@ public void test_mempool_endpoint() throws Exception { // Act // Get mempool from the MeshAPI - var mempool_response = + var mempoolResponse = new HashSet<>( getMempoolApi() .mempool(new NetworkRequest().networkIdentifier(getNetworkIdentifier())) .getTransactionIdentifiers()); // Assert that both transactions are in the mempool list - assertThat(mempool_response.equals(expected_transaction_identifiers)); + assertThat(mempoolResponse.equals(expectedTransactionIdentifiers)); } } @@ -120,8 +125,30 @@ public void test_mempool_transaction_endpoint() throws Exception { test.suppressUnusedWarning(); // Arrange - var transaction = TransactionBuilder.forTests().prepare(); - var transaction_identifier = + var senderKeyPair = TransactionBuilder.generateKeyPair(2); + var senderAddress = Address.virtualAccountAddress(senderKeyPair.getPublicKey()); + var senderAddressStr = senderAddress.encode(networkDefinition); + + var receiverKeyPair = TransactionBuilder.generateKeyPair(3); + var receiverAddress = Address.virtualAccountAddress(receiverKeyPair.getPublicKey()); + var receiverAddressStr = receiverAddress.encode(networkDefinition); + + // Prefund sender account + getCoreApiHelper() + .submitAndWaitForSuccess(test, Manifest.depositFromFaucet(senderAddress), List.of()); + + var transaction = + TransactionBuilder.forTests() + .manifest( + Manifest.transferBetweenAccountsFeeFromSender( + senderAddress, + ScryptoConstants.XRD_RESOURCE_ADDRESS, + Decimal.ofNonNegative(1000), + receiverAddress)) + .signatories(List.of(senderKeyPair)) + .prepare(); + + var transactionIdentifier = new TransactionIdentifier().hash(addressing.encode(transaction.transactionIntentHash())); // Submit transaction to the CoreAPI @@ -136,17 +163,37 @@ public void test_mempool_transaction_endpoint() throws Exception { // Act // Get mempool transaction from the MeshAPI - var mempool_transaction_response = + var mempoolTransactionResponse = getMempoolApi() .mempoolTransaction( new MempoolTransactionRequest() .networkIdentifier(getNetworkIdentifier()) - .transactionIdentifier(transaction_identifier)) + .transactionIdentifier(transactionIdentifier)) .getTransaction(); - // Assert that transaction1 is in the mempool transaction list - assertThat(mempool_transaction_response) - .isEqualTo(new Transaction().transactionIdentifier(transaction_identifier)); + var xrdCurrency = + new Currency() + .symbol(ScryptoConstants.XRD_RESOURCE_ADDRESS.encode(networkDefinition)) + .decimals(18); + var withdrawOperation = + new Operation() + .operationIdentifier(new OperationIdentifier().index(0L)) + .type("Withdraw") + .account(new AccountIdentifier().address(senderAddressStr)) + .amount(new Amount().value("-1000000000000000000000").currency(xrdCurrency)); + var depositOperation = + new Operation() + .operationIdentifier(new OperationIdentifier().index(1L)) + .type("Deposit") + .account(new AccountIdentifier().address(receiverAddressStr)) + .amount(new Amount().value("1000000000000000000000").currency(xrdCurrency)); + var expectedTransaction = + new Transaction() + .transactionIdentifier(transactionIdentifier) + .operations(List.of(withdrawOperation, depositOperation)); + + // Assert that expected transaction is in the mempool transaction list + assertThat(mempoolTransactionResponse).isEqualTo(expectedTransaction); } } }