diff --git a/src/main/java/org/stellar/sdk/AbstractTransaction.java b/src/main/java/org/stellar/sdk/AbstractTransaction.java index c18110f8d..0cef65a2c 100644 --- a/src/main/java/org/stellar/sdk/AbstractTransaction.java +++ b/src/main/java/org/stellar/sdk/AbstractTransaction.java @@ -85,6 +85,15 @@ public Network getNetwork() { return mNetwork; } + /** + * Gets the {@link AccountConverter} for this transaction. + * + * @return the {@link AccountConverter} object + */ + public AccountConverter getAccountConverter() { + return accountConverter; + } + /** * Gets read only list(immutable) of the signatures on transaction. * diff --git a/src/main/java/org/stellar/sdk/AccountNotFoundException.java b/src/main/java/org/stellar/sdk/AccountNotFoundException.java new file mode 100644 index 000000000..fd028d2a8 --- /dev/null +++ b/src/main/java/org/stellar/sdk/AccountNotFoundException.java @@ -0,0 +1,15 @@ +package org.stellar.sdk; + +import lombok.Getter; + +/** Exception thrown when trying to load an account that doesn't exist on the Stellar network. */ +@Getter +public class AccountNotFoundException extends Exception { + // The account that was not found. + private final String accountId; + + public AccountNotFoundException(String accountId) { + super("Account not found, accountId: " + accountId); + this.accountId = accountId; + } +} diff --git a/src/main/java/org/stellar/sdk/Network.java b/src/main/java/org/stellar/sdk/Network.java index f1a4e50dc..d82eaad71 100644 --- a/src/main/java/org/stellar/sdk/Network.java +++ b/src/main/java/org/stellar/sdk/Network.java @@ -13,6 +13,10 @@ public class Network { public static final Network PUBLIC = new Network("Public Global Stellar Network ; September 2015"); public static final Network TESTNET = new Network("Test SDF Network ; September 2015"); + public static final Network FUTURENET = new Network("Test SDF Future Network ; October 2022"); + public static final Network STANDALONE = new Network("Standalone Network ; February 2017"); + public static final Network SANDBOX = + new Network("Local Sandbox Stellar Network ; September 2022"); private final String networkPassphrase; diff --git a/src/main/java/org/stellar/sdk/PrepareTransactionException.java b/src/main/java/org/stellar/sdk/PrepareTransactionException.java new file mode 100644 index 000000000..63871a11e --- /dev/null +++ b/src/main/java/org/stellar/sdk/PrepareTransactionException.java @@ -0,0 +1,17 @@ +package org.stellar.sdk; + +import lombok.Getter; +import org.stellar.sdk.responses.sorobanrpc.SimulateTransactionResponse; + +/** Exception thrown when preparing a transaction failed. */ +@Getter +public class PrepareTransactionException extends Exception { + // The response returned by the Soroban-RPC instance when simulating the transaction. + private final SimulateTransactionResponse simulateTransactionResponse; + + public PrepareTransactionException( + String message, SimulateTransactionResponse simulateTransactionResponse) { + super(message); + this.simulateTransactionResponse = simulateTransactionResponse; + } +} diff --git a/src/main/java/org/stellar/sdk/SorobanServer.java b/src/main/java/org/stellar/sdk/SorobanServer.java new file mode 100644 index 000000000..f1b81fe99 --- /dev/null +++ b/src/main/java/org/stellar/sdk/SorobanServer.java @@ -0,0 +1,526 @@ +package org.stellar.sdk; + +import com.google.common.io.BaseEncoding; +import com.google.gson.Gson; +import com.google.gson.reflect.TypeToken; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.Closeable; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Optional; +import java.util.UUID; +import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; +import javax.annotation.Nullable; +import okhttp3.HttpUrl; +import okhttp3.MediaType; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.RequestBody; +import okhttp3.Response; +import org.stellar.sdk.requests.ClientIdentificationInterceptor; +import org.stellar.sdk.requests.ResponseHandler; +import org.stellar.sdk.requests.sorobanrpc.GetEventsRequest; +import org.stellar.sdk.requests.sorobanrpc.GetLedgerEntriesRequest; +import org.stellar.sdk.requests.sorobanrpc.GetTransactionRequest; +import org.stellar.sdk.requests.sorobanrpc.SendTransactionRequest; +import org.stellar.sdk.requests.sorobanrpc.SimulateTransactionRequest; +import org.stellar.sdk.requests.sorobanrpc.SorobanRpcErrorResponse; +import org.stellar.sdk.requests.sorobanrpc.SorobanRpcRequest; +import org.stellar.sdk.responses.sorobanrpc.GetEventsResponse; +import org.stellar.sdk.responses.sorobanrpc.GetHealthResponse; +import org.stellar.sdk.responses.sorobanrpc.GetLatestLedgerResponse; +import org.stellar.sdk.responses.sorobanrpc.GetLedgerEntriesResponse; +import org.stellar.sdk.responses.sorobanrpc.GetNetworkResponse; +import org.stellar.sdk.responses.sorobanrpc.GetTransactionResponse; +import org.stellar.sdk.responses.sorobanrpc.SendTransactionResponse; +import org.stellar.sdk.responses.sorobanrpc.SimulateTransactionResponse; +import org.stellar.sdk.responses.sorobanrpc.SorobanRpcResponse; +import org.stellar.sdk.xdr.ContractDataDurability; +import org.stellar.sdk.xdr.ContractEntryBodyType; +import org.stellar.sdk.xdr.LedgerEntry; +import org.stellar.sdk.xdr.LedgerEntryType; +import org.stellar.sdk.xdr.LedgerKey; +import org.stellar.sdk.xdr.SCVal; +import org.stellar.sdk.xdr.SorobanAuthorizationEntry; +import org.stellar.sdk.xdr.SorobanTransactionData; +import org.stellar.sdk.xdr.XdrDataInputStream; +import org.stellar.sdk.xdr.XdrDataOutputStream; + +/** + * Main class used to connect to the Soroban-RPC instance and exposes an interface for requests to + * that instance. + */ +public class SorobanServer implements Closeable { + private static final int SUBMIT_TRANSACTION_TIMEOUT = 60; // seconds + private static final int CONNECT_TIMEOUT = 10; // seconds + private final HttpUrl serverURI; + private final OkHttpClient httpClient; + private final Gson gson = new Gson(); + + /** + * Creates a new SorobanServer instance. + * + * @param serverURI The URI of the Soroban-RPC instance to connect to. + */ + public SorobanServer(String serverURI) { + this( + serverURI, + new OkHttpClient.Builder() + .addInterceptor(new ClientIdentificationInterceptor()) + .connectTimeout(CONNECT_TIMEOUT, TimeUnit.SECONDS) + .readTimeout(SUBMIT_TRANSACTION_TIMEOUT, TimeUnit.SECONDS) + .retryOnConnectionFailure(true) + .build()); + } + + /** + * Creates a new SorobanServer instance. + * + * @param serverURI The URI of the Soroban-RPC instance to connect to. + * @param httpClient The {@link OkHttpClient} instance to use for requests. + */ + public SorobanServer(String serverURI, OkHttpClient httpClient) { + this.serverURI = HttpUrl.parse(serverURI); + this.httpClient = httpClient; + } + + /** + * Fetch a minimal set of current info about a Stellar account. Needed to get the current sequence + * number for the account, so you can build a successful transaction with {@link + * TransactionBuilder}. + * + * @param accountId The public address of the account to load. + * @return An {@link Account} object containing the sequence number and current state of the + * account. + * @throws IOException If the request could not be executed due to cancellation, a connectivity + * problem or timeout. Because networks can fail during an exchange, it is possible that the + * remote server accepted the request before the failure. + * @throws AccountNotFoundException If the account does not exist on the network. You may need to + * fund it first. + * @throws SorobanRpcErrorResponse If the Soroban-RPC instance returns an error response. + */ + public TransactionBuilderAccount getAccount(String accountId) + throws IOException, AccountNotFoundException, SorobanRpcErrorResponse { + LedgerKey.LedgerKeyAccount ledgerKeyAccount = + new LedgerKey.LedgerKeyAccount.Builder() + .accountID(KeyPair.fromAccountId(accountId).getXdrAccountId()) + .build(); + LedgerKey ledgerKey = + new LedgerKey.Builder() + .account(ledgerKeyAccount) + .discriminant(LedgerEntryType.ACCOUNT) + .build(); + GetLedgerEntriesResponse getLedgerEntriesResponse = + this.getLedgerEntries(Collections.singleton(ledgerKey)); + List entries = + getLedgerEntriesResponse.getEntries(); + if (entries == null || entries.isEmpty()) { + throw new AccountNotFoundException(accountId); + } + LedgerEntry.LedgerEntryData ledgerEntryData = + ledgerEntryDataFromXdrBase64(entries.get(0).getXdr()); + long sequence = ledgerEntryData.getAccount().getSeqNum().getSequenceNumber().getInt64(); + return new Account(accountId, sequence); + } + + /** + * General node health check. + * + * @see getHealth + * documentation + * @return A {@link GetHealthResponse} object containing the health check result. + * @throws IOException If the request could not be executed due to cancellation, a connectivity + * problem or timeout. Because networks can fail during an exchange, it is possible that the + * remote server accepted the request before the failure. + * @throws SorobanRpcErrorResponse If the Soroban-RPC instance returns an error response. + */ + public GetHealthResponse getHealth() throws IOException, SorobanRpcErrorResponse { + return this.sendRequest( + "getHealth", null, new TypeToken>() {}); + } + + /** + * Reads the current value of contract data ledger entries directly. + * + * @param contractId The contract ID containing the data to load. Encoded as Stellar Contract + * Address. e.g. "CCJZ5DGASBWQXR5MPFCJXMBI333XE5U3FSJTNQU7RIKE3P5GN2K2WYD5" + * @param key The key of the contract data to load. + * @param durability The "durability keyspace" that this ledger key belongs to, which is either + * {@link Durability#TEMPORARY} or {@link Durability#PERSISTENT}. + * @return A {@link GetLedgerEntriesResponse.LedgerEntryResult} object containing the ledger entry + * result. + * @throws IOException If the request could not be executed due to cancellation, a connectivity + * problem or timeout. Because networks can fail during an exchange, it is possible that the + * remote server accepted the request before the failure. + * @throws SorobanRpcErrorResponse If the Soroban-RPC instance returns an error response. + */ + public Optional getContractData( + String contractId, SCVal key, Durability durability) + throws IOException, SorobanRpcErrorResponse { + + ContractDataDurability contractDataDurability; + switch (durability) { + case TEMPORARY: + contractDataDurability = ContractDataDurability.TEMPORARY; + break; + case PERSISTENT: + contractDataDurability = ContractDataDurability.PERSISTENT; + break; + default: + throw new IllegalArgumentException("Invalid durability: " + durability); + } + + Address address = new Address(contractId); + LedgerKey.LedgerKeyContractData ledgerKeyContractData = + new LedgerKey.LedgerKeyContractData.Builder() + .contract(address.toSCAddress()) + .key(key) + .durability(contractDataDurability) + .bodyType(ContractEntryBodyType.DATA_ENTRY) + .build(); + LedgerKey ledgerKey = + new LedgerKey.Builder() + .discriminant(LedgerEntryType.CONTRACT_DATA) + .contractData(ledgerKeyContractData) + .build(); + GetLedgerEntriesResponse getLedgerEntriesResponse = + this.getLedgerEntries(Collections.singleton(ledgerKey)); + List entries = + getLedgerEntriesResponse.getEntries(); + if (entries == null || entries.isEmpty()) { + return Optional.empty(); + } + GetLedgerEntriesResponse.LedgerEntryResult result = entries.get(0); + return Optional.of(result); + } + + /** + * Reads the current value of ledger entries directly. + * + *

Allows you to directly inspect the current state of contracts, contract's code, or any other + * ledger entries. + * + * @see getLedgerEntries documentation + * @param keys The key of the contract data to load, at least one key must be provided. + * @return A {@link GetLedgerEntriesResponse} object containing the current values. + * @throws IOException If the request could not be executed due to cancellation, a connectivity + * problem or timeout. Because networks can fail during an exchange, it is possible that the + * remote server accepted the request before the failure. + * @throws SorobanRpcErrorResponse If the Soroban-RPC instance returns an error response. + */ + public GetLedgerEntriesResponse getLedgerEntries(Collection keys) + throws IOException, SorobanRpcErrorResponse { + if (keys.isEmpty()) { + throw new IllegalArgumentException("At least one key must be provided."); + } + + List xdrKeys = + keys.stream().map(SorobanServer::ledgerKeyToXdrBase64).collect(Collectors.toList()); + GetLedgerEntriesRequest params = new GetLedgerEntriesRequest(xdrKeys); + return this.sendRequest( + "getLedgerEntries", + params, + new TypeToken>() {}); + } + + /** + * Fetch the details of a submitted transaction. + * + *

When submitting a transaction, client should poll this to tell when the transaction has + * completed. + * + * @see getTransaction documentation + * @param hash The hash of the transaction to check. Encoded as a hex string. + * @return A {@link GetTransactionResponse} object containing the transaction status, result, and + * other details. + * @throws IOException If the request could not be executed due to cancellation, a connectivity + * problem or timeout. Because networks can fail during an exchange, it is possible that the + * remote server accepted the request before the failure. + * @throws SorobanRpcErrorResponse If the Soroban-RPC instance returns an error response. + */ + public GetTransactionResponse getTransaction(String hash) + throws IOException, SorobanRpcErrorResponse { + GetTransactionRequest params = new GetTransactionRequest(hash); + return this.sendRequest( + "getTransaction", params, new TypeToken>() {}); + } + + /** + * Fetches all events that match the given {@link GetEventsRequest}. + * + * @see getEvents + * documentation + * @param getEventsRequest The {@link GetEventsRequest} to use for the request. + * @return A {@link GetEventsResponse} object containing the events that match the request. + * @throws IOException If the request could not be executed due to cancellation, a connectivity + * problem or timeout. Because networks can fail during an exchange, it is possible that the + * remote server accepted the request before the failure. + * @throws SorobanRpcErrorResponse If the Soroban-RPC instance returns an error response. + */ + public GetEventsResponse getEvents(GetEventsRequest getEventsRequest) + throws IOException, SorobanRpcErrorResponse { + return this.sendRequest( + "getEvents", getEventsRequest, new TypeToken>() {}); + } + + /** + * Fetches metadata about the network which Soroban-RPC is connected to. + * + * @see getNetwork + * documentation + * @return A {@link GetNetworkResponse} object containing the network metadata. + * @throws IOException If the request could not be executed due to cancellation, a connectivity + * problem or timeout. Because networks can fail during an exchange, it is possible that the + * remote server accepted the request before the failure. + * @throws SorobanRpcErrorResponse If the Soroban-RPC instance returns an error response. + */ + public GetNetworkResponse getNetwork() throws IOException, SorobanRpcErrorResponse { + return this.sendRequest( + "getNetwork", null, new TypeToken>() {}); + } + + /** + * Fetches the latest ledger meta info from network which Soroban-RPC is connected to. + * + * @see getLatestLedger documentation + * @return A {@link GetLatestLedgerResponse} object containing the latest ledger meta info. + * @throws IOException If the request could not be executed due to cancellation, a connectivity + * problem or timeout. Because networks can fail during an exchange, it is possible that the + * remote server accepted the request before the failure. + * @throws SorobanRpcErrorResponse If the Soroban-RPC instance returns an error response. + */ + public GetLatestLedgerResponse getLatestLedger() throws IOException, SorobanRpcErrorResponse { + return this.sendRequest( + "getLatestLedger", null, new TypeToken>() {}); + } + + /** + * Submit a trial contract invocation to get back return values, expected ledger footprint, + * expected authorizations, and expected costs. + * + * @see simulateTransaction documentation + * @param transaction The transaction to simulate. It should include exactly one operation, which + * must be one of {@link InvokeHostFunctionOperation}, {@link + * BumpFootprintExpirationOperation}, or {@link RestoreFootprintOperation}. Any provided + * footprint will be ignored. + * @return A {@link SimulateTransactionResponse} object containing the cost, footprint, + * result/auth requirements (if applicable), and error of the transaction. + * @throws IOException If the request could not be executed due to cancellation, a connectivity + * problem or timeout. Because networks can fail during an exchange, it is possible that the + * remote server accepted the request before the failure. + * @throws SorobanRpcErrorResponse If the Soroban-RPC instance returns an error response. + */ + public SimulateTransactionResponse simulateTransaction(Transaction transaction) + throws IOException, SorobanRpcErrorResponse { + // TODO: In the future, it may be necessary to consider FeeBumpTransaction. + SimulateTransactionRequest params = + new SimulateTransactionRequest(transaction.toEnvelopeXdrBase64()); + return this.sendRequest( + "simulateTransaction", + params, + new TypeToken>() {}); + } + + /** + * Submit a trial contract invocation, first run a simulation of the contract invocation as + * defined on the incoming transaction, and apply the results to a new copy of the transaction + * which is then returned. Setting the ledger footprint and authorization, so the resulting + * transaction is ready for signing & sending. + * + *

The returned transaction will also have an updated fee that is the sum of fee set on + * incoming transaction with the contract resource fees estimated from simulation. It is advisable + * to check the fee on returned transaction and validate or take appropriate measures for + * interaction with user to confirm it is acceptable. + * + *

You can call the {@link SorobanServer#simulateTransaction} method directly first if you want + * to inspect estimated fees for a given transaction in detail first, if that is of importance. + * + * @param transaction The transaction to prepare. It should include exactly one operation, which + * must be one of {@link InvokeHostFunctionOperation}, {@link + * BumpFootprintExpirationOperation}, or {@link RestoreFootprintOperation}. Any provided + * footprint will be ignored. You can use {@link Transaction#isSorobanTransaction()} to check + * if a transaction is a Soroban transaction. + * @return Returns a copy of the {@link Transaction}, with the expected authorizations (in the + * case of invocation) and ledger footprint added. The transaction fee will also automatically + * be padded with the contract's minimum resource fees discovered from the simulation. + * @throws PrepareTransactionException If preparing the transaction fails. + * @throws IOException If the request could not be executed due to cancellation, a connectivity + * problem or timeout. Because networks can fail during an exchange, it is possible that the + * remote server accepted the request before the failure. + * @throws SorobanRpcErrorResponse If the Soroban-RPC instance returns an error response. + */ + public Transaction prepareTransaction(Transaction transaction) + throws IOException, SorobanRpcErrorResponse, PrepareTransactionException { + SimulateTransactionResponse simulateTransactionResponse = this.simulateTransaction(transaction); + if (simulateTransactionResponse.getError() != null) { + throw new PrepareTransactionException( + "simulation transaction failed, the response contains error information.", + simulateTransactionResponse); + } + if (simulateTransactionResponse.getResults() == null + || simulateTransactionResponse.getResults().size() != 1) { + throw new PrepareTransactionException( + "simulation transaction failed, the \"results\" field contains multiple records, but it should only contain one.", + simulateTransactionResponse); + } + return assembleTransaction(transaction, simulateTransactionResponse); + } + + /** + * Submit a real transaction to the Stellar network. This is the only way to make changes + * "on-chain". Unlike Horizon, Soroban-RPC does not wait for transaction completion. It simply + * validates the transaction and enqueues it. Clients should call {@link + * SorobanServer#getTransaction} to learn about transaction's status. + * + * @see sendTransaction documentation + * @param transaction The transaction to submit. + * @return A {@link SendTransactionResponse} object containing some details about the transaction + * that was submitted. + * @throws IOException If the request could not be executed due to cancellation, a connectivity + * problem or timeout. Because networks can fail during an exchange, it is possible that the + * remote server accepted the request before the failure. + * @throws SorobanRpcErrorResponse If the Soroban-RPC instance returns an error response. + */ + public SendTransactionResponse sendTransaction(Transaction transaction) + throws IOException, SorobanRpcErrorResponse { + // TODO: In the future, it may be necessary to consider FeeBumpTransaction. + SendTransactionRequest params = new SendTransactionRequest(transaction.toEnvelopeXdrBase64()); + return this.sendRequest( + "sendTransaction", params, new TypeToken>() {}); + } + + private Transaction assembleTransaction( + Transaction transaction, SimulateTransactionResponse simulateTransactionResponse) { + if (!transaction.isSorobanTransaction()) { + throw new IllegalArgumentException( + "unsupported transaction: must contain exactly one InvokeHostFunctionOperation, BumpSequenceOperation, or RestoreFootprintOperation"); + } + + SimulateTransactionResponse.SimulateHostFunctionResult simulateHostFunctionResult = + simulateTransactionResponse.getResults().get(0); + + long classicFeeNum = transaction.getFee(); + long minResourceFeeNum = + Optional.ofNullable(simulateTransactionResponse.getMinResourceFee()).orElse(0L); + long fee = classicFeeNum + minResourceFeeNum; + Operation operation = transaction.getOperations()[0]; + + if (operation instanceof InvokeHostFunctionOperation) { + Collection originalEntries = + ((InvokeHostFunctionOperation) operation).getAuth(); + List newEntries = new ArrayList<>(originalEntries); + if (simulateHostFunctionResult.getAuth() != null) { + for (String auth : simulateHostFunctionResult.getAuth()) { + newEntries.add(sorobanAuthorizationEntryFromXdrBase64(auth)); + } + } + operation = + InvokeHostFunctionOperation.builder() + .hostFunction(((InvokeHostFunctionOperation) operation).getHostFunction()) + .sourceAccount(operation.getSourceAccount()) + .auth(newEntries) + .build(); + } + + SorobanTransactionData sorobanData = + Util.sorobanTransactionDataToXDR(simulateTransactionResponse.getTransactionData()); + + return new Transaction( + transaction.getAccountConverter(), + transaction.getSourceAccount(), + fee, + transaction.getSequenceNumber(), + new Operation[] {operation}, + transaction.getMemo(), + transaction.getPreconditions(), + sorobanData, + transaction.getNetwork()); + } + + private R sendRequest( + String method, @Nullable T params, TypeToken> responseType) + throws IOException, SorobanRpcErrorResponse { + String requestId = generateRequestId(); + ResponseHandler> responseHandler = new ResponseHandler<>(responseType); + SorobanRpcRequest sorobanRpcRequest = new SorobanRpcRequest<>(requestId, method, params); + MediaType mediaType = MediaType.parse("application/json"); + RequestBody requestBody = + RequestBody.create(gson.toJson(sorobanRpcRequest).getBytes(), mediaType); + + Request request = new Request.Builder().url(this.serverURI).post(requestBody).build(); + try (Response response = this.httpClient.newCall(request).execute()) { + SorobanRpcResponse sorobanRpcResponse = responseHandler.handleResponse(response); + if (sorobanRpcResponse.getError() != null) { + SorobanRpcResponse.Error error = sorobanRpcResponse.getError(); + throw new SorobanRpcErrorResponse(error.getCode(), error.getMessage(), error.getData()); + } + return sorobanRpcResponse.getResult(); + } + } + + @Override + public void close() throws IOException { + this.httpClient.connectionPool().evictAll(); + } + + private static String generateRequestId() { + return UUID.randomUUID().toString(); + } + + private static String ledgerKeyToXdrBase64(LedgerKey ledgerKey) { + ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); + XdrDataOutputStream xdrDataOutputStream = new XdrDataOutputStream(byteArrayOutputStream); + try { + ledgerKey.encode(xdrDataOutputStream); + } catch (IOException e) { + throw new IllegalArgumentException("invalid ledgerKey.", e); + } + BaseEncoding base64Encoding = BaseEncoding.base64(); + return base64Encoding.encode(byteArrayOutputStream.toByteArray()); + } + + private static LedgerEntry.LedgerEntryData ledgerEntryDataFromXdrBase64(String ledgerEntryData) { + BaseEncoding base64Encoding = BaseEncoding.base64(); + byte[] bytes = base64Encoding.decode(ledgerEntryData); + ByteArrayInputStream inputStream = new ByteArrayInputStream(bytes); + XdrDataInputStream xdrInputStream = new XdrDataInputStream(inputStream); + try { + return LedgerEntry.LedgerEntryData.decode(xdrInputStream); + } catch (IOException e) { + throw new IllegalArgumentException("invalid ledgerEntryData: " + ledgerEntryData, e); + } + } + + private static SorobanAuthorizationEntry sorobanAuthorizationEntryFromXdrBase64( + String sorobanAuthorizationEntry) { + BaseEncoding base64Encoding = BaseEncoding.base64(); + byte[] bytes = base64Encoding.decode(sorobanAuthorizationEntry); + ByteArrayInputStream inputStream = new ByteArrayInputStream(bytes); + XdrDataInputStream xdrInputStream = new XdrDataInputStream(inputStream); + try { + return SorobanAuthorizationEntry.decode(xdrInputStream); + } catch (IOException e) { + throw new IllegalArgumentException( + "invalid ledgerEntryData: " + sorobanAuthorizationEntry, e); + } + } + + /** + * Represents the "durability keyspace" that this ledger key belongs to, check {@link + * SorobanServer#getContractData} for more details. + */ + public enum Durability { + TEMPORARY, + PERSISTENT + } +} diff --git a/src/main/java/org/stellar/sdk/Transaction.java b/src/main/java/org/stellar/sdk/Transaction.java index fe3409013..430bef521 100644 --- a/src/main/java/org/stellar/sdk/Transaction.java +++ b/src/main/java/org/stellar/sdk/Transaction.java @@ -388,4 +388,20 @@ public Builder(TransactionBuilderAccount sourceAccount, Network network) { super(sourceAccount, network); } } + + /** + * Returns true if this transaction is a Soroban transaction. + * + * @return true if this transaction is a Soroban transaction. + */ + public boolean isSorobanTransaction() { + if (mOperations.length != 1) { + return false; + } + + Operation op = mOperations[0]; + return op instanceof InvokeHostFunctionOperation + || op instanceof BumpSequenceOperation + || op instanceof RestoreFootprintOperation; + } } diff --git a/src/main/java/org/stellar/sdk/requests/sorobanrpc/EventFilterType.java b/src/main/java/org/stellar/sdk/requests/sorobanrpc/EventFilterType.java new file mode 100644 index 000000000..bb50bccf8 --- /dev/null +++ b/src/main/java/org/stellar/sdk/requests/sorobanrpc/EventFilterType.java @@ -0,0 +1,13 @@ +package org.stellar.sdk.requests.sorobanrpc; + +import com.google.gson.annotations.SerializedName; + +/** Represents the type of event. */ +public enum EventFilterType { + @SerializedName("system") + SYSTEM, + @SerializedName("contract") + CONTRACT, + @SerializedName("diagnostic") + DIAGNOSTIC +} diff --git a/src/main/java/org/stellar/sdk/requests/sorobanrpc/GetEventsRequest.java b/src/main/java/org/stellar/sdk/requests/sorobanrpc/GetEventsRequest.java new file mode 100644 index 000000000..d72c4039f --- /dev/null +++ b/src/main/java/org/stellar/sdk/requests/sorobanrpc/GetEventsRequest.java @@ -0,0 +1,43 @@ +package org.stellar.sdk.requests.sorobanrpc; + +import java.util.Collection; +import lombok.Builder; +import lombok.NonNull; +import lombok.Singular; +import lombok.Value; + +/** + * Request for JSON-RPC method getEvents. + * + * @see getEvents documentation + */ +@Value +@Builder(toBuilder = true) +public class GetEventsRequest { + @NonNull String startLedger; + + @Singular("filter") + Collection filters; + + PaginationOptions pagination; + + @Value + @Builder(toBuilder = true) + public static class PaginationOptions { + Long limit; + + String cursor; + } + + @Builder(toBuilder = true) + @Value + public static class EventFilter { + EventFilterType type; + + Collection contractIds; + + @Singular("topic") + Collection> topics; + } +} diff --git a/src/main/java/org/stellar/sdk/requests/sorobanrpc/GetLedgerEntriesRequest.java b/src/main/java/org/stellar/sdk/requests/sorobanrpc/GetLedgerEntriesRequest.java new file mode 100644 index 000000000..5620c174c --- /dev/null +++ b/src/main/java/org/stellar/sdk/requests/sorobanrpc/GetLedgerEntriesRequest.java @@ -0,0 +1,17 @@ +package org.stellar.sdk.requests.sorobanrpc; + +import java.util.Collection; +import lombok.AllArgsConstructor; +import lombok.Value; + +/** + * Request for JSON-RPC method getLedgerEntries. + * + * @see getLedgerEntries documentation + */ +@AllArgsConstructor +@Value +public class GetLedgerEntriesRequest { + Collection keys; +} diff --git a/src/main/java/org/stellar/sdk/requests/sorobanrpc/GetTransactionRequest.java b/src/main/java/org/stellar/sdk/requests/sorobanrpc/GetTransactionRequest.java new file mode 100644 index 000000000..980802249 --- /dev/null +++ b/src/main/java/org/stellar/sdk/requests/sorobanrpc/GetTransactionRequest.java @@ -0,0 +1,16 @@ +package org.stellar.sdk.requests.sorobanrpc; + +import lombok.AllArgsConstructor; +import lombok.Value; + +/** + * Request for JSON-RPC method getTransaction. + * + * @see getTransaction documentation + */ +@Value +@AllArgsConstructor +public class GetTransactionRequest { + String hash; +} diff --git a/src/main/java/org/stellar/sdk/requests/sorobanrpc/SendTransactionRequest.java b/src/main/java/org/stellar/sdk/requests/sorobanrpc/SendTransactionRequest.java new file mode 100644 index 000000000..4ebbecc55 --- /dev/null +++ b/src/main/java/org/stellar/sdk/requests/sorobanrpc/SendTransactionRequest.java @@ -0,0 +1,16 @@ +package org.stellar.sdk.requests.sorobanrpc; + +import lombok.AllArgsConstructor; +import lombok.Value; + +/** + * Request for JSON-RPC method sendTransaction. + * + * @see sendTransaction documentation + */ +@AllArgsConstructor +@Value +public class SendTransactionRequest { + String transaction; +} diff --git a/src/main/java/org/stellar/sdk/requests/sorobanrpc/SimulateTransactionRequest.java b/src/main/java/org/stellar/sdk/requests/sorobanrpc/SimulateTransactionRequest.java new file mode 100644 index 000000000..ebf4d631d --- /dev/null +++ b/src/main/java/org/stellar/sdk/requests/sorobanrpc/SimulateTransactionRequest.java @@ -0,0 +1,16 @@ +package org.stellar.sdk.requests.sorobanrpc; + +import lombok.AllArgsConstructor; +import lombok.Value; + +/** + * Request for JSON-RPC method simulateTransaction. + * + * @see simulateTransaction documentation + */ +@AllArgsConstructor +@Value +public class SimulateTransactionRequest { + String transaction; +} diff --git a/src/main/java/org/stellar/sdk/requests/sorobanrpc/SorobanRpcErrorResponse.java b/src/main/java/org/stellar/sdk/requests/sorobanrpc/SorobanRpcErrorResponse.java new file mode 100644 index 000000000..9ad3c7248 --- /dev/null +++ b/src/main/java/org/stellar/sdk/requests/sorobanrpc/SorobanRpcErrorResponse.java @@ -0,0 +1,25 @@ +package org.stellar.sdk.requests.sorobanrpc; + +import lombok.Getter; + +/** + * Throws when Soroban-RPC instance responds with error. + * + * @see JSON-RPC 2.0 + * Specification - Error object + */ +@Getter +public class SorobanRpcErrorResponse extends Exception { + private final Integer code; + + private final String message; + + private final String data; + + public SorobanRpcErrorResponse(Integer code, String message, String data) { + super(message); + this.code = code; + this.message = message; + this.data = data; + } +} diff --git a/src/main/java/org/stellar/sdk/requests/sorobanrpc/SorobanRpcRequest.java b/src/main/java/org/stellar/sdk/requests/sorobanrpc/SorobanRpcRequest.java new file mode 100644 index 000000000..d3aa2e508 --- /dev/null +++ b/src/main/java/org/stellar/sdk/requests/sorobanrpc/SorobanRpcRequest.java @@ -0,0 +1,24 @@ +package org.stellar.sdk.requests.sorobanrpc; + +import com.google.gson.annotations.SerializedName; +import lombok.RequiredArgsConstructor; +import lombok.Value; + +/** + * Represent the request sent to Soroban-RPC. + * + * @see JSON-RPC 2.0 + * Specification - Request object + */ +@RequiredArgsConstructor +@Value +public class SorobanRpcRequest { + @SerializedName("jsonrpc") + String jsonRpc = "2.0"; + + String id; + + String method; + + T params; +} diff --git a/src/main/java/org/stellar/sdk/responses/GsonSingleton.java b/src/main/java/org/stellar/sdk/responses/GsonSingleton.java index 2a717c2d3..c357e0bab 100644 --- a/src/main/java/org/stellar/sdk/responses/GsonSingleton.java +++ b/src/main/java/org/stellar/sdk/responses/GsonSingleton.java @@ -1,5 +1,6 @@ package org.stellar.sdk.responses; +import com.google.common.collect.ImmutableList; import com.google.gson.Gson; import com.google.gson.GsonBuilder; import com.google.gson.reflect.TypeToken; @@ -68,6 +69,7 @@ public static Gson getInstance() { .registerTypeAdapter( claimableBalancePageType.getType(), new PageDeserializer(claimableBalancePageType)) + .registerTypeAdapter(ImmutableList.class, new ImmutableListDeserializer()) .create(); } return instance; diff --git a/src/main/java/org/stellar/sdk/responses/sorobanrpc/GetEventsResponse.java b/src/main/java/org/stellar/sdk/responses/sorobanrpc/GetEventsResponse.java new file mode 100644 index 000000000..890b94b65 --- /dev/null +++ b/src/main/java/org/stellar/sdk/responses/sorobanrpc/GetEventsResponse.java @@ -0,0 +1,48 @@ +package org.stellar.sdk.responses.sorobanrpc; + +import com.google.common.collect.ImmutableList; +import lombok.AllArgsConstructor; +import lombok.Value; +import org.stellar.sdk.requests.sorobanrpc.EventFilterType; + +/** + * Response for JSON-RPC method getEvents. + * + * @see getEvents documentation + */ +@AllArgsConstructor +@Value +public class GetEventsResponse { + ImmutableList events; + + Long latestLedger; + + @AllArgsConstructor + @Value + public static class EventInfo { + EventFilterType type; + + Integer ledger; + + String ledgerClosedAt; + + String contractId; + + String id; + + String pagingToken; + + ImmutableList topic; + + EventInfoValue value; + + Boolean inSuccessfulContractCall; + } + + @AllArgsConstructor + @Value + public static class EventInfoValue { + String xdr; + } +} diff --git a/src/main/java/org/stellar/sdk/responses/sorobanrpc/GetHealthResponse.java b/src/main/java/org/stellar/sdk/responses/sorobanrpc/GetHealthResponse.java new file mode 100644 index 000000000..ea59076ab --- /dev/null +++ b/src/main/java/org/stellar/sdk/responses/sorobanrpc/GetHealthResponse.java @@ -0,0 +1,16 @@ +package org.stellar.sdk.responses.sorobanrpc; + +import lombok.AllArgsConstructor; +import lombok.Value; + +/** + * Response for JSON-RPC method getHealth. + * + * @see getHealth documentation + */ +@AllArgsConstructor +@Value +public class GetHealthResponse { + String status; +} diff --git a/src/main/java/org/stellar/sdk/responses/sorobanrpc/GetLatestLedgerResponse.java b/src/main/java/org/stellar/sdk/responses/sorobanrpc/GetLatestLedgerResponse.java new file mode 100644 index 000000000..abca3d775 --- /dev/null +++ b/src/main/java/org/stellar/sdk/responses/sorobanrpc/GetLatestLedgerResponse.java @@ -0,0 +1,20 @@ +package org.stellar.sdk.responses.sorobanrpc; + +import lombok.AllArgsConstructor; +import lombok.Value; + +/** + * Response for JSON-RPC method getLatestLedger. + * + * @see getLatestLedger documentation + */ +@AllArgsConstructor +@Value +public class GetLatestLedgerResponse { + String id; + + Integer protocolVersion; + + Integer sequence; +} diff --git a/src/main/java/org/stellar/sdk/responses/sorobanrpc/GetLedgerEntriesResponse.java b/src/main/java/org/stellar/sdk/responses/sorobanrpc/GetLedgerEntriesResponse.java new file mode 100644 index 000000000..96015fdd5 --- /dev/null +++ b/src/main/java/org/stellar/sdk/responses/sorobanrpc/GetLedgerEntriesResponse.java @@ -0,0 +1,31 @@ +package org.stellar.sdk.responses.sorobanrpc; + +import com.google.common.collect.ImmutableList; +import com.google.gson.annotations.SerializedName; +import lombok.AllArgsConstructor; +import lombok.Value; + +/** + * Response for JSON-RPC method getLedgerEntries. + * + * @see getLedgerEntries documentation + */ +@AllArgsConstructor +@Value +public class GetLedgerEntriesResponse { + ImmutableList entries; + + Long latestLedger; + + @AllArgsConstructor + @Value + public static class LedgerEntryResult { + String key; + + String xdr; + + @SerializedName("lastModifiedLedgerSeq") + Long lastModifiedLedger; + } +} diff --git a/src/main/java/org/stellar/sdk/responses/sorobanrpc/GetNetworkResponse.java b/src/main/java/org/stellar/sdk/responses/sorobanrpc/GetNetworkResponse.java new file mode 100644 index 000000000..a32cfb754 --- /dev/null +++ b/src/main/java/org/stellar/sdk/responses/sorobanrpc/GetNetworkResponse.java @@ -0,0 +1,20 @@ +package org.stellar.sdk.responses.sorobanrpc; + +import lombok.AllArgsConstructor; +import lombok.Value; + +/** + * Response for JSON-RPC method getNetwork. + * + * @see getNetwork documentation + */ +@AllArgsConstructor +@Value +public class GetNetworkResponse { + String friendbotUrl; + + String passphrase; + + Integer protocolVersion; +} diff --git a/src/main/java/org/stellar/sdk/responses/sorobanrpc/GetTransactionResponse.java b/src/main/java/org/stellar/sdk/responses/sorobanrpc/GetTransactionResponse.java new file mode 100644 index 000000000..6e099baf1 --- /dev/null +++ b/src/main/java/org/stellar/sdk/responses/sorobanrpc/GetTransactionResponse.java @@ -0,0 +1,44 @@ +package org.stellar.sdk.responses.sorobanrpc; + +import lombok.AllArgsConstructor; +import lombok.Value; + +/** + * Response for JSON-RPC method getTransaction. + * + * @see getTransaction documentation + */ +@AllArgsConstructor +@Value +public class GetTransactionResponse { + GetTransactionStatus status; + + Long latestLedger; + + Long latestLedgerCloseTime; + + Long oldestLedger; + + Long oldestLedgerCloseTime; + + Integer applicationOrder; + + Boolean feeBump; + + String envelopeXdr; + + String resultXdr; + + String resultMetaXdr; + + Long ledger; + + Long createdAt; + + public enum GetTransactionStatus { + NOT_FOUND, + SUCCESS, + FAILED + } +} diff --git a/src/main/java/org/stellar/sdk/responses/sorobanrpc/SendTransactionResponse.java b/src/main/java/org/stellar/sdk/responses/sorobanrpc/SendTransactionResponse.java new file mode 100644 index 000000000..99ffeeb86 --- /dev/null +++ b/src/main/java/org/stellar/sdk/responses/sorobanrpc/SendTransactionResponse.java @@ -0,0 +1,31 @@ +package org.stellar.sdk.responses.sorobanrpc; + +import lombok.AllArgsConstructor; +import lombok.Value; + +/** + * Response for JSON-RPC method sendTransaction. + * + * @see sendTransaction documentation + */ +@AllArgsConstructor +@Value +public class SendTransactionResponse { + SendTransactionStatus status; + + String errorResultXdr; + + String hash; + + Long latestLedger; + + Long latestLedgerCloseTime; + + public enum SendTransactionStatus { + PENDING, + DUPLICATE, + TRY_AGAIN_LATER, + ERROR + } +} diff --git a/src/main/java/org/stellar/sdk/responses/sorobanrpc/SimulateTransactionResponse.java b/src/main/java/org/stellar/sdk/responses/sorobanrpc/SimulateTransactionResponse.java new file mode 100644 index 000000000..0fb1b80f8 --- /dev/null +++ b/src/main/java/org/stellar/sdk/responses/sorobanrpc/SimulateTransactionResponse.java @@ -0,0 +1,49 @@ +package org.stellar.sdk.responses.sorobanrpc; + +import com.google.common.collect.ImmutableList; +import com.google.gson.annotations.SerializedName; +import java.math.BigInteger; +import lombok.AllArgsConstructor; +import lombok.Value; + +/** + * Response for JSON-RPC method simulateTransaction. + * + * @see simulateTransaction documentation + */ +@AllArgsConstructor +@Value +public class SimulateTransactionResponse { + String error; + + String transactionData; + + ImmutableList events; + + Long minResourceFee; + + ImmutableList results; + + SimulateTransactionCost cost; + + Long latestLedger; + + @AllArgsConstructor + @Value + public static class SimulateHostFunctionResult { + ImmutableList auth; + + String xdr; + } + + @AllArgsConstructor + @Value + public static class SimulateTransactionCost { + @SerializedName("cpuInsns") + BigInteger cpuInstructions; + + @SerializedName("memBytes") + BigInteger memoryBytes; + } +} diff --git a/src/main/java/org/stellar/sdk/responses/sorobanrpc/SorobanRpcResponse.java b/src/main/java/org/stellar/sdk/responses/sorobanrpc/SorobanRpcResponse.java new file mode 100644 index 000000000..f0f46bb09 --- /dev/null +++ b/src/main/java/org/stellar/sdk/responses/sorobanrpc/SorobanRpcResponse.java @@ -0,0 +1,36 @@ +package org.stellar.sdk.responses.sorobanrpc; + +import com.google.gson.annotations.SerializedName; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.Value; +import org.stellar.sdk.responses.Response; + +/** + * Represent the response returned by Soroban-RPC. + * + * @see JSON-RPC 2.0 + * Specification - Response object + */ +@AllArgsConstructor +@Getter +public class SorobanRpcResponse extends Response { + @SerializedName("jsonrpc") + private final String jsonRpc; + + private final String id; + + private final T result; + + private final Error error; + + @AllArgsConstructor + @Value + public static class Error { + Integer code; + + String message; + + String data; + } +} diff --git a/src/test/java/org/stellar/sdk/SorobanServerTest.java b/src/test/java/org/stellar/sdk/SorobanServerTest.java new file mode 100644 index 000000000..cd576f103 --- /dev/null +++ b/src/test/java/org/stellar/sdk/SorobanServerTest.java @@ -0,0 +1,1420 @@ +package org.stellar.sdk; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; +import static org.stellar.sdk.xdr.SCValType.SCV_LEDGER_KEY_CONTRACT_INSTANCE; + +import com.google.common.io.BaseEncoding; +import com.google.gson.Gson; +import com.google.gson.reflect.TypeToken; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.Optional; +import okhttp3.HttpUrl; +import okhttp3.mockwebserver.Dispatcher; +import okhttp3.mockwebserver.MockResponse; +import okhttp3.mockwebserver.MockWebServer; +import okhttp3.mockwebserver.RecordedRequest; +import org.jetbrains.annotations.NotNull; +import org.junit.Test; +import org.stellar.sdk.requests.sorobanrpc.EventFilterType; +import org.stellar.sdk.requests.sorobanrpc.GetEventsRequest; +import org.stellar.sdk.requests.sorobanrpc.GetLedgerEntriesRequest; +import org.stellar.sdk.requests.sorobanrpc.GetTransactionRequest; +import org.stellar.sdk.requests.sorobanrpc.SendTransactionRequest; +import org.stellar.sdk.requests.sorobanrpc.SimulateTransactionRequest; +import org.stellar.sdk.requests.sorobanrpc.SorobanRpcErrorResponse; +import org.stellar.sdk.requests.sorobanrpc.SorobanRpcRequest; +import org.stellar.sdk.responses.sorobanrpc.GetEventsResponse; +import org.stellar.sdk.responses.sorobanrpc.GetHealthResponse; +import org.stellar.sdk.responses.sorobanrpc.GetLatestLedgerResponse; +import org.stellar.sdk.responses.sorobanrpc.GetLedgerEntriesResponse; +import org.stellar.sdk.responses.sorobanrpc.GetNetworkResponse; +import org.stellar.sdk.responses.sorobanrpc.SendTransactionResponse; +import org.stellar.sdk.responses.sorobanrpc.SimulateTransactionResponse; +import org.stellar.sdk.xdr.ContractDataDurability; +import org.stellar.sdk.xdr.ContractEntryBodyType; +import org.stellar.sdk.xdr.ContractExecutable; +import org.stellar.sdk.xdr.ContractExecutableType; +import org.stellar.sdk.xdr.ContractIDPreimage; +import org.stellar.sdk.xdr.ContractIDPreimageType; +import org.stellar.sdk.xdr.CreateContractArgs; +import org.stellar.sdk.xdr.ExtensionPoint; +import org.stellar.sdk.xdr.HostFunction; +import org.stellar.sdk.xdr.HostFunctionType; +import org.stellar.sdk.xdr.Int64; +import org.stellar.sdk.xdr.LedgerEntryType; +import org.stellar.sdk.xdr.LedgerFootprint; +import org.stellar.sdk.xdr.LedgerKey; +import org.stellar.sdk.xdr.SCSymbol; +import org.stellar.sdk.xdr.SCVal; +import org.stellar.sdk.xdr.SCValType; +import org.stellar.sdk.xdr.SCVec; +import org.stellar.sdk.xdr.SorobanAuthorizationEntry; +import org.stellar.sdk.xdr.SorobanAuthorizedFunction; +import org.stellar.sdk.xdr.SorobanAuthorizedFunctionType; +import org.stellar.sdk.xdr.SorobanAuthorizedInvocation; +import org.stellar.sdk.xdr.SorobanCredentials; +import org.stellar.sdk.xdr.SorobanCredentialsType; +import org.stellar.sdk.xdr.SorobanResources; +import org.stellar.sdk.xdr.SorobanTransactionData; +import org.stellar.sdk.xdr.Uint256; +import org.stellar.sdk.xdr.Uint32; +import org.stellar.sdk.xdr.XdrDataInputStream; +import org.stellar.sdk.xdr.XdrDataOutputStream; +import org.stellar.sdk.xdr.XdrString; + +public class SorobanServerTest { + private final Gson gson = new Gson(); + + @Test + public void testGetAccount() + throws IOException, SorobanRpcErrorResponse, AccountNotFoundException { + String accountId = "GDAT5HWTGIU4TSSZ4752OUC4SABDLTLZFRPZUJ3D6LKBNEPA7V2CIG54"; + String json = + "{\n" + + " \"jsonrpc\": \"2.0\",\n" + + " \"id\": \"ecb18f82ec12484190673502d0486b98\",\n" + + " \"result\": {\n" + + " \"entries\": [\n" + + " {\n" + + " \"key\": \"AAAAAAAAAADBPp7TMinJylnn+6dQXJACNc15LF+aJ2Py1BaR4P10JA==\",\n" + + " \"xdr\": \"AAAAAAAAAADBPp7TMinJylnn+6dQXJACNc15LF+aJ2Py1BaR4P10JAAAABdIcDhpAAADHAAAAAwAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAAAAAAAAAAAAAAAAAADAAAAAAABfI8AAAAAZMK3qQ==\",\n" + + " \"lastModifiedLedgerSeq\": \"97423\"\n" + + " }\n" + + " ],\n" + + " \"latestLedger\": \"108023\"\n" + + " }\n" + + "}"; + + MockWebServer mockWebServer = new MockWebServer(); + Dispatcher dispatcher = + new Dispatcher() { + @NotNull + @Override + public MockResponse dispatch(@NotNull RecordedRequest recordedRequest) + throws InterruptedException { + GetLedgerEntriesRequest expectedRequest = + new GetLedgerEntriesRequest( + Collections.singletonList( + "AAAAAAAAAADBPp7TMinJylnn+6dQXJACNc15LF+aJ2Py1BaR4P10JA==")); + SorobanRpcRequest sorobanRpcRequest = + gson.fromJson( + recordedRequest.getBody().readUtf8(), + new TypeToken>() {}.getType()); + if ("POST".equals(recordedRequest.getMethod()) + && sorobanRpcRequest.getMethod().equals("getLedgerEntries") + && sorobanRpcRequest.getParams().equals(expectedRequest)) { + return new MockResponse().setResponseCode(200).setBody(json); + } + return new MockResponse().setResponseCode(404); + } + }; + mockWebServer.setDispatcher(dispatcher); + mockWebServer.start(); + + HttpUrl baseUrl = mockWebServer.url(""); + SorobanServer server = new SorobanServer(baseUrl.toString()); + TransactionBuilderAccount resp = server.getAccount(accountId); + assertEquals(resp.getAccountId(), accountId); + assertEquals(resp.getSequenceNumber().longValue(), 3418793967628L); + server.close(); + mockWebServer.close(); + } + + @Test(expected = AccountNotFoundException.class) + public void testGetAccountNotFoundThrows() + throws IOException, SorobanRpcErrorResponse, AccountNotFoundException { + String accountId = "GBG6OSICP2YJ5ROY4HBGNSVRQDCQ4RYPFFUH6I6BI7LHYNW2CM7AJVBE"; + String json = + "{\n" + + " \"jsonrpc\": \"2.0\",\n" + + " \"id\": \"0376b51e6a744dd2abb3b83be4c2e6dd\",\n" + + " \"result\": {\n" + + " \"entries\": null,\n" + + " \"latestLedger\": \"109048\"\n" + + " }\n" + + "}"; + + MockWebServer mockWebServer = new MockWebServer(); + Dispatcher dispatcher = + new Dispatcher() { + @NotNull + @Override + public MockResponse dispatch(@NotNull RecordedRequest recordedRequest) + throws InterruptedException { + SorobanRpcRequest sorobanRpcRequest = + gson.fromJson( + recordedRequest.getBody().readUtf8(), + new TypeToken>() {}.getType()); + if ("POST".equals(recordedRequest.getMethod()) + && sorobanRpcRequest.getMethod().equals("getLedgerEntries")) { + return new MockResponse().setResponseCode(200).setBody(json); + } + return new MockResponse().setResponseCode(404); + } + }; + mockWebServer.setDispatcher(dispatcher); + mockWebServer.start(); + + HttpUrl baseUrl = mockWebServer.url(""); + SorobanServer server = new SorobanServer(baseUrl.toString()); + server.getAccount(accountId); + server.close(); + mockWebServer.close(); + } + + @Test + public void testGetHealth() throws IOException, SorobanRpcErrorResponse { + String json = + "{\n" + + " \"jsonrpc\": \"2.0\",\n" + + " \"id\": \"198cb1a8-9104-4446-a269-88bf000c2721\",\n" + + " \"result\": {\n" + + " \"status\": \"healthy\"\n" + + " }\n" + + "}"; + + MockWebServer mockWebServer = new MockWebServer(); + Dispatcher dispatcher = + new Dispatcher() { + @NotNull + @Override + public MockResponse dispatch(@NotNull RecordedRequest recordedRequest) + throws InterruptedException { + SorobanRpcRequest sorobanRpcRequest = + gson.fromJson( + recordedRequest.getBody().readUtf8(), + new TypeToken>() {}.getType()); + if ("POST".equals(recordedRequest.getMethod()) + && sorobanRpcRequest.getMethod().equals("getHealth")) { + return new MockResponse().setResponseCode(200).setBody(json); + } + return new MockResponse().setResponseCode(404); + } + }; + mockWebServer.setDispatcher(dispatcher); + mockWebServer.start(); + + HttpUrl baseUrl = mockWebServer.url(""); + SorobanServer server = new SorobanServer(baseUrl.toString()); + GetHealthResponse resp = server.getHealth(); + assertEquals(resp.getStatus(), "healthy"); + server.close(); + mockWebServer.close(); + } + + @Test + public void testGetContractData() throws IOException, SorobanRpcErrorResponse { + String json = + "{\n" + + " \"jsonrpc\": \"2.0\",\n" + + " \"id\": \"839c6c921d40456db5ba8a1c4e1a0e70\",\n" + + " \"result\": {\n" + + " \"entries\": [\n" + + " {\n" + + " \"key\": \"AAAABgAAAAFgdoLyR3pr6M3w/fMr4T1fJaaGzAlP2T1ao9e2gjLQwAAAABQAAAABAAAAAA==\",\n" + + " \"xdr\": \"AAAABgAAAAFgdoLyR3pr6M3w/fMr4T1fJaaGzAlP2T1ao9e2gjLQwAAAABQAAAABAAAAAAAAAAAAAAATAAAAALnBupvoT7RHZ+oTeaPHSiSufpac3O3mc0u663Kqbko/AAAAAQAAAAEAAAAPAAAAB0NPVU5URVIAAAAAAwAAAAEAABD1\",\n" + + " \"lastModifiedLedgerSeq\": \"290\"\n" + + " }\n" + + " ],\n" + + " \"latestLedger\": \"296\"\n" + + " }\n" + + "}"; + + String contractId = "CBQHNAXSI55GX2GN6D67GK7BHVPSLJUGZQEU7WJ5LKR5PNUCGLIMAO4K"; + SCVal key = new SCVal.Builder().discriminant(SCV_LEDGER_KEY_CONTRACT_INSTANCE).build(); + + MockWebServer mockWebServer = new MockWebServer(); + Dispatcher dispatcher = + new Dispatcher() { + @NotNull + @Override + public MockResponse dispatch(@NotNull RecordedRequest recordedRequest) + throws InterruptedException { + + Address address = new Address(contractId); + LedgerKey.LedgerKeyContractData ledgerKeyContractData = + new LedgerKey.LedgerKeyContractData.Builder() + .contract(address.toSCAddress()) + .key(key) + .durability(ContractDataDurability.PERSISTENT) + .bodyType(ContractEntryBodyType.DATA_ENTRY) + .build(); + LedgerKey ledgerKey = + new LedgerKey.Builder() + .discriminant(LedgerEntryType.CONTRACT_DATA) + .contractData(ledgerKeyContractData) + .build(); + + GetLedgerEntriesRequest expectedRequest = + new GetLedgerEntriesRequest( + Collections.singletonList(ledgerKeyToXdrBase64(ledgerKey))); + SorobanRpcRequest sorobanRpcRequest = + gson.fromJson( + recordedRequest.getBody().readUtf8(), + new TypeToken>() {}.getType()); + if ("POST".equals(recordedRequest.getMethod()) + && sorobanRpcRequest.getMethod().equals("getLedgerEntries") + && sorobanRpcRequest.getParams().equals(expectedRequest)) { + return new MockResponse().setResponseCode(200).setBody(json); + } + return new MockResponse().setResponseCode(404); + } + }; + mockWebServer.setDispatcher(dispatcher); + mockWebServer.start(); + + HttpUrl baseUrl = mockWebServer.url(""); + SorobanServer server = new SorobanServer(baseUrl.toString()); + Optional resp = + server.getContractData(contractId, key, SorobanServer.Durability.PERSISTENT); + assertTrue(resp.isPresent()); + assertEquals(resp.get().getLastModifiedLedger().longValue(), 290L); + assertEquals( + resp.get().getKey(), + "AAAABgAAAAFgdoLyR3pr6M3w/fMr4T1fJaaGzAlP2T1ao9e2gjLQwAAAABQAAAABAAAAAA=="); + assertEquals( + resp.get().getXdr(), + "AAAABgAAAAFgdoLyR3pr6M3w/fMr4T1fJaaGzAlP2T1ao9e2gjLQwAAAABQAAAABAAAAAAAAAAAAAAATAAAAALnBupvoT7RHZ+oTeaPHSiSufpac3O3mc0u663Kqbko/AAAAAQAAAAEAAAAPAAAAB0NPVU5URVIAAAAAAwAAAAEAABD1"); + server.close(); + mockWebServer.close(); + } + + @Test + public void testGetContractDataReturnNull() throws IOException, SorobanRpcErrorResponse { + String json = + "{\n" + + " \"jsonrpc\": \"2.0\",\n" + + " \"id\": \"7d61ef6b1f974ba886b323f4266b4211\",\n" + + " \"result\": {\n" + + " \"entries\": null,\n" + + " \"latestLedger\": \"191\"\n" + + " }\n" + + "}"; + String contractId = "CBQHNAXSI55GX2GN6D67GK7BHVPSLJUGZQEU7WJ5LKR5PNUCGLIMAO4K"; + SCVal key = new SCVal.Builder().discriminant(SCV_LEDGER_KEY_CONTRACT_INSTANCE).build(); + + MockWebServer mockWebServer = new MockWebServer(); + Dispatcher dispatcher = + new Dispatcher() { + @NotNull + @Override + public MockResponse dispatch(@NotNull RecordedRequest recordedRequest) + throws InterruptedException { + + Address address = new Address(contractId); + LedgerKey.LedgerKeyContractData ledgerKeyContractData = + new LedgerKey.LedgerKeyContractData.Builder() + .contract(address.toSCAddress()) + .key(key) + .durability(ContractDataDurability.PERSISTENT) + .bodyType(ContractEntryBodyType.DATA_ENTRY) + .build(); + LedgerKey ledgerKey = + new LedgerKey.Builder() + .discriminant(LedgerEntryType.CONTRACT_DATA) + .contractData(ledgerKeyContractData) + .build(); + + GetLedgerEntriesRequest expectedRequest = + new GetLedgerEntriesRequest( + Collections.singletonList(ledgerKeyToXdrBase64(ledgerKey))); + SorobanRpcRequest sorobanRpcRequest = + gson.fromJson( + recordedRequest.getBody().readUtf8(), + new TypeToken>() {}.getType()); + if ("POST".equals(recordedRequest.getMethod()) + && sorobanRpcRequest.getMethod().equals("getLedgerEntries") + && sorobanRpcRequest.getParams().equals(expectedRequest)) { + return new MockResponse().setResponseCode(200).setBody(json); + } + return new MockResponse().setResponseCode(404); + } + }; + mockWebServer.setDispatcher(dispatcher); + mockWebServer.start(); + + HttpUrl baseUrl = mockWebServer.url(""); + SorobanServer server = new SorobanServer(baseUrl.toString()); + Optional resp = + server.getContractData(contractId, key, SorobanServer.Durability.PERSISTENT); + assertFalse(resp.isPresent()); + server.close(); + mockWebServer.close(); + } + + @Test + public void testGetLedgerEntries() throws IOException, SorobanRpcErrorResponse { + String json = + "{\n" + + " \"jsonrpc\": \"2.0\",\n" + + " \"id\": \"0ce70038b1804b3c93ca7abc137f3061\",\n" + + " \"result\": {\n" + + " \"entries\": [\n" + + " {\n" + + " \"key\": \"AAAAAAAAAACynni6I2ACEzWuORVM1b2y0k1ZDni0W6JlC/Ad/mfCSg==\",\n" + + " \"xdr\": \"AAAAAAAAAACynni6I2ACEzWuORVM1b2y0k1ZDni0W6JlC/Ad/mfCSgAAABdIdugAAAAAnwAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAA\",\n" + + " \"lastModifiedLedgerSeq\": \"159\"\n" + + " },\n" + + " {\n" + + " \"key\": \"AAAAAAAAAADBPp7TMinJylnn+6dQXJACNc15LF+aJ2Py1BaR4P10JA==\",\n" + + " \"xdr\": \"AAAAAAAAAADBPp7TMinJylnn+6dQXJACNc15LF+aJ2Py1BaR4P10JAAAABdIcmH6AAAAoQAAAAgAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAAAAAAAAAAAAAAAAAADAAAAAAAAHAkAAAAAZMPQ0g==\",\n" + + " \"lastModifiedLedgerSeq\": \"7177\"\n" + + " }\n" + + " ],\n" + + " \"latestLedger\": \"7943\"\n" + + " }\n" + + "}"; + + String accountId0 = "GCZJ46F2ENQAEEZVVY4RKTGVXWZNETKZBZ4LIW5CMUF7AHP6M7BEV464"; + LedgerKey.LedgerKeyAccount ledgerKeyAccount0 = + new LedgerKey.LedgerKeyAccount.Builder() + .accountID(KeyPair.fromAccountId(accountId0).getXdrAccountId()) + .build(); + LedgerKey ledgerKey0 = + new LedgerKey.Builder() + .account(ledgerKeyAccount0) + .discriminant(LedgerEntryType.ACCOUNT) + .build(); + String ledgerKey0Xdr = ledgerKeyToXdrBase64(ledgerKey0); + + String accountId1 = "GDAT5HWTGIU4TSSZ4752OUC4SABDLTLZFRPZUJ3D6LKBNEPA7V2CIG54"; + LedgerKey.LedgerKeyAccount ledgerKeyAccount1 = + new LedgerKey.LedgerKeyAccount.Builder() + .accountID(KeyPair.fromAccountId(accountId1).getXdrAccountId()) + .build(); + LedgerKey ledgerKey1 = + new LedgerKey.Builder() + .account(ledgerKeyAccount1) + .discriminant(LedgerEntryType.ACCOUNT) + .build(); + String ledgerKey1Xdr = ledgerKeyToXdrBase64(ledgerKey1); + + MockWebServer mockWebServer = new MockWebServer(); + Dispatcher dispatcher = + new Dispatcher() { + @NotNull + @Override + public MockResponse dispatch(@NotNull RecordedRequest recordedRequest) + throws InterruptedException { + + // add ledgerKey0Xdr and ledgerKey1Xdr + GetLedgerEntriesRequest expectedRequest = + new GetLedgerEntriesRequest(Arrays.asList(ledgerKey0Xdr, ledgerKey1Xdr)); + SorobanRpcRequest sorobanRpcRequest = + gson.fromJson( + recordedRequest.getBody().readUtf8(), + new TypeToken>() {}.getType()); + if ("POST".equals(recordedRequest.getMethod()) + && sorobanRpcRequest.getMethod().equals("getLedgerEntries") + && sorobanRpcRequest.getParams().equals(expectedRequest)) { + return new MockResponse().setResponseCode(200).setBody(json); + } + return new MockResponse().setResponseCode(404); + } + }; + mockWebServer.setDispatcher(dispatcher); + mockWebServer.start(); + + HttpUrl baseUrl = mockWebServer.url(""); + SorobanServer server = new SorobanServer(baseUrl.toString()); + GetLedgerEntriesResponse resp = server.getLedgerEntries(Arrays.asList(ledgerKey0, ledgerKey1)); + assertEquals(resp.getLatestLedger().longValue(), 7943L); + assertEquals(resp.getEntries().size(), 2); + assertEquals( + resp.getEntries().asList().get(0).getKey(), + "AAAAAAAAAACynni6I2ACEzWuORVM1b2y0k1ZDni0W6JlC/Ad/mfCSg=="); + assertEquals( + resp.getEntries().asList().get(0).getXdr(), + "AAAAAAAAAACynni6I2ACEzWuORVM1b2y0k1ZDni0W6JlC/Ad/mfCSgAAABdIdugAAAAAnwAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAA"); + assertEquals( + resp.getEntries().asList().get(1).getKey(), + "AAAAAAAAAADBPp7TMinJylnn+6dQXJACNc15LF+aJ2Py1BaR4P10JA=="); + assertEquals( + resp.getEntries().asList().get(1).getXdr(), + "AAAAAAAAAADBPp7TMinJylnn+6dQXJACNc15LF+aJ2Py1BaR4P10JAAAABdIcmH6AAAAoQAAAAgAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAAAAAAAAAAAAAAAAAADAAAAAAAAHAkAAAAAZMPQ0g=="); + server.close(); + mockWebServer.close(); + } + + @Test + public void testGetTransaction() throws IOException, SorobanRpcErrorResponse { + String hash = "06dd9ee70bf93bbfe219e2b31363ab5a0361cc6285328592e4d3d1fed4c9025c"; + String json = + "{\n" + + " \"jsonrpc\": \"2.0\",\n" + + " \"id\": \"198cb1a8-9104-4446-a269-88bf000c2721\",\n" + + " \"result\": {\n" + + " \"status\": \"SUCCESS\",\n" + + " \"latestLedger\": \"79289\",\n" + + " \"latestLedgerCloseTime\": \"1690387240\",\n" + + " \"oldestLedger\": \"77850\",\n" + + " \"oldestLedgerCloseTime\": \"1690379694\",\n" + + " \"applicationOrder\": 1,\n" + + " \"envelopeXdr\": \"AAAAAgAAAADTYKIzfa0ubKp7qjOcF+ZO8sjQutzo1iHuDh8esi9q+wABNjQAATW1AAAAAQAAAAAAAAAAAAAAAQAAAAAAAAAYAAAAAAAAAAIAAAASAAAAAb3H+V1yFoNDBpje4rchxeaR7/hRNS2CAT2Dh6A8z6xrAAAADwAAAARuYW1lAAAAAAAAAAEAAAAAAAAAAwAAAAYAAAABvcf5XXIWg0MGmN7ityHF5pHv+FE1LYIBPYOHoDzPrGsAAAAPAAAACE1FVEFEQVRBAAAAAQAAAAAAAAAGAAAAAb3H+V1yFoNDBpje4rchxeaR7/hRNS2CAT2Dh6A8z6xrAAAAFAAAAAEAAAAAAAAAB++FkDTZODW0rvolF6AuIZf4w7+GQd8RofaH8u2pM+UPAAAAAAAAAAAAUrutAAAiqAAAAAAAAADIAAAAAAAAACgAAAABsi9q+wAAAEDgHR/5rU+bsXD/oPUFodyEgXFNbDm5T2+M1GarZXy+d+tZ757PBL9ysK41F1hUYz3p5CA7Urlpe3fnNjYcu1EM\",\n" + + " \"resultXdr\": \"AAAAAAABNCwAAAAAAAAAAQAAAAAAAAAYAAAAAJhEDjNV0Jj46jrxCj87qJ6JaYKJN4c+p5tvapkLwrn8AAAAAA==\",\n" + + " \"resultMetaXdr\": \"AAAAAwAAAAAAAAACAAAAAwABNbYAAAAAAAAAANNgojN9rS5sqnuqM5wX5k7yyNC63OjWIe4OHx6yL2r7AAAAF0h1s9QAATW1AAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAQABNbYAAAAAAAAAANNgojN9rS5sqnuqM5wX5k7yyNC63OjWIe4OHx6yL2r7AAAAF0h1s9QAATW1AAAAAQAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAAAAAAAAAAAAAAAAAMAAAAAAAE1tgAAAABkwUMZAAAAAAAAAAEAAAAAAAAAAgAAAAMAATW2AAAAAAAAAADTYKIzfa0ubKp7qjOcF+ZO8sjQutzo1iHuDh8esi9q+wAAABdIdbPUAAE1tQAAAAEAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAAAAAAAAAAAAAAAAAADAAAAAAABNbYAAAAAZMFDGQAAAAAAAAABAAE1tgAAAAAAAAAA02CiM32tLmyqe6oznBfmTvLI0Lrc6NYh7g4fHrIvavsAAAAXSHWz/AABNbUAAAABAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAACAAAAAAAAAAAAAAAAAAAAAwAAAAAAATW2AAAAAGTBQxkAAAAAAAAAAQAAAAAAAAAAAAAADgAAAAZUb2tlbkEAAAAAAAIAAAABAAAAAAAAAAAAAAACAAAAAAAAAAMAAAAPAAAAB2ZuX2NhbGwAAAAADQAAACC9x/ldchaDQwaY3uK3IcXmke/4UTUtggE9g4egPM+sawAAAA8AAAAEbmFtZQAAAAEAAAABAAAAAAAAAAG9x/ldchaDQwaY3uK3IcXmke/4UTUtggE9g4egPM+sawAAAAIAAAAAAAAAAgAAAA8AAAAJZm5fcmV0dXJuAAAAAAAADwAAAARuYW1lAAAADgAAAAZUb2tlbkEAAA==\",\n" + + " \"ledger\": \"79286\",\n" + + " \"createdAt\": \"1690387225\"\n" + + " }\n" + + "}"; + + MockWebServer mockWebServer = new MockWebServer(); + Dispatcher dispatcher = + new Dispatcher() { + @NotNull + @Override + public MockResponse dispatch(@NotNull RecordedRequest recordedRequest) + throws InterruptedException { + GetTransactionRequest expectedRequest = new GetTransactionRequest(hash); + SorobanRpcRequest sorobanRpcRequest = + gson.fromJson( + recordedRequest.getBody().readUtf8(), + new TypeToken>() {}.getType()); + if ("POST".equals(recordedRequest.getMethod()) + && sorobanRpcRequest.getMethod().equals("getTransaction") + && sorobanRpcRequest.getParams().equals(expectedRequest)) { + return new MockResponse().setResponseCode(200).setBody(json); + } + return new MockResponse().setResponseCode(404); + } + }; + mockWebServer.setDispatcher(dispatcher); + mockWebServer.start(); + + HttpUrl baseUrl = mockWebServer.url(""); + SorobanServer server = new SorobanServer(baseUrl.toString()); + server.getTransaction(hash); + + server.close(); + mockWebServer.close(); + } + + @Test + public void testGetEvents() throws IOException, SorobanRpcErrorResponse { + String json = + "{\n" + + " \"jsonrpc\": \"2.0\",\n" + + " \"id\": \"198cb1a8-9104-4446-a269-88bf000c2721\",\n" + + " \"result\": {\n" + + " \"events\": [\n" + + " {\n" + + " \"type\": \"contract\",\n" + + " \"ledger\": \"107\",\n" + + " \"ledgerClosedAt\": \"2023-07-28T14:57:02Z\",\n" + + " \"contractId\": \"607682f2477a6be8cdf0fdf32be13d5f25a686cc094fd93d5aa3d7b68232d0c0\",\n" + + " \"id\": \"0000000459561504768-0000000000\",\n" + + " \"pagingToken\": \"0000000459561504768-0000000000\",\n" + + " \"topic\": [\n" + + " \"AAAADwAAAAdDT1VOVEVSAA==\",\n" + + " \"AAAADwAAAAlpbmNyZW1lbnQAAAA=\"\n" + + " ],\n" + + " \"value\": {\n" + + " \"xdr\": \"AAAAAwAAAAQ=\"\n" + + " },\n" + + " \"inSuccessfulContractCall\": true\n" + + " },\n" + + " {\n" + + " \"type\": \"contract\",\n" + + " \"ledger\": \"109\",\n" + + " \"ledgerClosedAt\": \"2023-07-28T14:57:04Z\",\n" + + " \"contractId\": \"607682f2477a6be8cdf0fdf32be13d5f25a686cc094fd93d5aa3d7b68232d0c0\",\n" + + " \"id\": \"0000000468151439360-0000000000\",\n" + + " \"pagingToken\": \"0000000468151439360-0000000000\",\n" + + " \"topic\": [\n" + + " \"AAAADwAAAAdDT1VOVEVSAA==\",\n" + + " \"AAAADwAAAAlpbmNyZW1lbnQAAAA=\"\n" + + " ],\n" + + " \"value\": {\n" + + " \"xdr\": \"AAAAAwAAAAU=\"\n" + + " },\n" + + " \"inSuccessfulContractCall\": true\n" + + " }\n" + + " ],\n" + + " \"latestLedger\": \"187\"\n" + + " }\n" + + "}"; + + GetEventsRequest.EventFilter eventFilter = + GetEventsRequest.EventFilter.builder() + .contractIds( + Collections.singletonList( + "607682f2477a6be8cdf0fdf32be13d5f25a686cc094fd93d5aa3d7b68232d0c0")) + .type(EventFilterType.CONTRACT) + .topic(Arrays.asList("AAAADwAAAAdDT1VOVEVSAA==", "AAAADwAAAAlpbmNyZW1lbnQAAAA=")) + .build(); + GetEventsRequest.PaginationOptions paginationOptions = + GetEventsRequest.PaginationOptions.builder() + .limit(10L) + .cursor("0000007799660613632-0000000000") + .build(); + GetEventsRequest getEventsRequest = + GetEventsRequest.builder() + .startLedger("100") + .filter(eventFilter) + .pagination(paginationOptions) + .build(); + + MockWebServer mockWebServer = new MockWebServer(); + Dispatcher dispatcher = + new Dispatcher() { + @NotNull + @Override + public MockResponse dispatch(@NotNull RecordedRequest recordedRequest) + throws InterruptedException { + SorobanRpcRequest sorobanRpcRequest = + gson.fromJson( + recordedRequest.getBody().readUtf8(), + new TypeToken>() {}.getType()); + if ("POST".equals(recordedRequest.getMethod()) + && sorobanRpcRequest.getMethod().equals("getEvents") + && sorobanRpcRequest.getParams().equals(getEventsRequest)) { + return new MockResponse().setResponseCode(200).setBody(json); + } + return new MockResponse().setResponseCode(404); + } + }; + mockWebServer.setDispatcher(dispatcher); + mockWebServer.start(); + + HttpUrl baseUrl = mockWebServer.url(""); + SorobanServer server = new SorobanServer(baseUrl.toString()); + GetEventsResponse resp = server.getEvents(getEventsRequest); + assertEquals(resp.getLatestLedger().longValue(), 187L); + assertEquals(resp.getEvents().size(), 2); + assertEquals(resp.getEvents().get(0).getType(), EventFilterType.CONTRACT); + assertEquals(resp.getEvents().get(0).getLedger().longValue(), 107L); + assertEquals(resp.getEvents().get(0).getLedgerClosedAt(), "2023-07-28T14:57:02Z"); + assertEquals( + resp.getEvents().get(0).getContractId(), + "607682f2477a6be8cdf0fdf32be13d5f25a686cc094fd93d5aa3d7b68232d0c0"); + assertEquals(resp.getEvents().get(0).getId(), "0000000459561504768-0000000000"); + assertEquals(resp.getEvents().get(0).getPagingToken(), "0000000459561504768-0000000000"); + assertEquals(resp.getEvents().get(0).getTopic().size(), 2); + assertEquals(resp.getEvents().get(0).getTopic().get(0), "AAAADwAAAAdDT1VOVEVSAA=="); + assertEquals(resp.getEvents().get(0).getTopic().get(1), "AAAADwAAAAlpbmNyZW1lbnQAAAA="); + assertEquals(resp.getEvents().get(0).getValue().getXdr(), "AAAAAwAAAAQ="); + assertEquals(resp.getEvents().get(0).getInSuccessfulContractCall(), true); + + server.close(); + mockWebServer.close(); + } + + @Test + public void testGetNetwork() throws IOException, SorobanRpcErrorResponse { + String json = + "{\n" + + " \"jsonrpc\": \"2.0\",\n" + + " \"id\": \"198cb1a8-9104-4446-a269-88bf000c2721\",\n" + + " \"result\": {\n" + + " \"friendbotUrl\": \"http://localhost:8000/friendbot\",\n" + + " \"passphrase\": \"Standalone Network ; February 2017\",\n" + + " \"protocolVersion\": \"20\"\n" + + " }\n" + + "}"; + + MockWebServer mockWebServer = new MockWebServer(); + Dispatcher dispatcher = + new Dispatcher() { + @NotNull + @Override + public MockResponse dispatch(@NotNull RecordedRequest recordedRequest) + throws InterruptedException { + SorobanRpcRequest sorobanRpcRequest = + gson.fromJson( + recordedRequest.getBody().readUtf8(), + new TypeToken>() {}.getType()); + if ("POST".equals(recordedRequest.getMethod()) + && sorobanRpcRequest.getMethod().equals("getNetwork")) { + return new MockResponse().setResponseCode(200).setBody(json); + } + return new MockResponse().setResponseCode(404); + } + }; + mockWebServer.setDispatcher(dispatcher); + mockWebServer.start(); + + HttpUrl baseUrl = mockWebServer.url(""); + SorobanServer server = new SorobanServer(baseUrl.toString()); + GetNetworkResponse resp = server.getNetwork(); + assertEquals(resp.getFriendbotUrl(), "http://localhost:8000/friendbot"); + assertEquals(resp.getPassphrase(), "Standalone Network ; February 2017"); + assertEquals(resp.getProtocolVersion().longValue(), 20L); + server.close(); + mockWebServer.close(); + } + + @Test + public void testGetLatestLedger() throws IOException, SorobanRpcErrorResponse { + String json = + "{\n" + + " \"jsonrpc\": \"2.0\",\n" + + " \"id\": \"198cb1a8-9104-4446-a269-88bf000c2721\",\n" + + " \"result\": {\n" + + " \"id\": \"e73d7654b72daa637f396669182c6072549736a9e3b6fcb8e685adb61f8c910a\",\n" + + " \"protocolVersion\": \"20\",\n" + + " \"sequence\": 24170\n" + + " }\n" + + "}"; + + MockWebServer mockWebServer = new MockWebServer(); + Dispatcher dispatcher = + new Dispatcher() { + @NotNull + @Override + public MockResponse dispatch(@NotNull RecordedRequest recordedRequest) + throws InterruptedException { + SorobanRpcRequest sorobanRpcRequest = + gson.fromJson( + recordedRequest.getBody().readUtf8(), + new TypeToken>() {}.getType()); + if ("POST".equals(recordedRequest.getMethod()) + && sorobanRpcRequest.getMethod().equals("getLatestLedger")) { + return new MockResponse().setResponseCode(200).setBody(json); + } + return new MockResponse().setResponseCode(404); + } + }; + mockWebServer.setDispatcher(dispatcher); + mockWebServer.start(); + + HttpUrl baseUrl = mockWebServer.url(""); + SorobanServer server = new SorobanServer(baseUrl.toString()); + GetLatestLedgerResponse resp = server.getLatestLedger(); + assertEquals(resp.getId(), "e73d7654b72daa637f396669182c6072549736a9e3b6fcb8e685adb61f8c910a"); + assertEquals(resp.getProtocolVersion().intValue(), 20); + assertEquals(resp.getSequence().intValue(), 24170); + + server.close(); + mockWebServer.close(); + } + + @Test + public void testSimulateTransaction() throws IOException, SorobanRpcErrorResponse { + String json = + "{\n" + + " \"jsonrpc\": \"2.0\",\n" + + " \"id\": \"e1fabdcdf0244a2a9adfab94d7748b6c\",\n" + + " \"result\": {\n" + + " \"transactionData\": \"AAAAAAAAAAIAAAAGAAAAAcWLK/vE8FTnMk9r8gytPgJuQbutGm0gw9fUkY3tFlQRAAAAFAAAAAEAAAAAAAAAB300Hyg0HZG+Qie3zvsxLvugrNtFqd3AIntWy9bg2YvZAAAAAAAAAAEAAAAGAAAAAcWLK/vE8FTnMk9r8gytPgJuQbutGm0gw9fUkY3tFlQRAAAAEAAAAAEAAAACAAAADwAAAAdDb3VudGVyAAAAABIAAAAAAAAAAFi3xKLI8peqjz0kcSgf38zsr+SOVmMxPsGOEqc+ypihAAAAAQAAAAAAFcLDAAAF8AAAAQgAAAMcAAAAAAAAAJw=\",\n" + + " \"events\": [\n" + + " \"AAAAAQAAAAAAAAAAAAAAAgAAAAAAAAADAAAADwAAAAdmbl9jYWxsAAAAAA0AAAAgxYsr+8TwVOcyT2vyDK0+Am5Bu60abSDD19SRje0WVBEAAAAPAAAACWluY3JlbWVudAAAAAAAABAAAAABAAAAAgAAABIAAAAAAAAAAFi3xKLI8peqjz0kcSgf38zsr+SOVmMxPsGOEqc+ypihAAAAAwAAAAo=\",\n" + + " \"AAAAAQAAAAAAAAABxYsr+8TwVOcyT2vyDK0+Am5Bu60abSDD19SRje0WVBEAAAACAAAAAAAAAAIAAAAPAAAACWZuX3JldHVybgAAAAAAAA8AAAAJaW5jcmVtZW50AAAAAAAAAwAAABQ=\"\n" + + " ],\n" + + " \"minResourceFee\": \"58595\",\n" + + " \"results\": [\n" + + " {\n" + + " \"auth\": [\n" + + " \"AAAAAAAAAAAAAAABxYsr+8TwVOcyT2vyDK0+Am5Bu60abSDD19SRje0WVBEAAAAJaW5jcmVtZW50AAAAAAAAAgAAABIAAAAAAAAAAFi3xKLI8peqjz0kcSgf38zsr+SOVmMxPsGOEqc+ypihAAAAAwAAAAoAAAAA\"\n" + + " ],\n" + + " \"xdr\": \"AAAAAwAAABQ=\"\n" + + " }\n" + + " ],\n" + + " \"cost\": {\n" + + " \"cpuInsns\": \"1240100\",\n" + + " \"memBytes\": \"161637\"\n" + + " },\n" + + " \"latestLedger\": \"1479\"\n" + + " }\n" + + "}"; + + Transaction transaction = buildSorobanTransaction(null, null); + + MockWebServer mockWebServer = new MockWebServer(); + Dispatcher dispatcher = + new Dispatcher() { + @NotNull + @Override + public MockResponse dispatch(@NotNull RecordedRequest recordedRequest) + throws InterruptedException { + SorobanRpcRequest sorobanRpcRequest = + gson.fromJson( + recordedRequest.getBody().readUtf8(), + new TypeToken>() {}.getType()); + if ("POST".equals(recordedRequest.getMethod()) + && sorobanRpcRequest.getMethod().equals("simulateTransaction") + && sorobanRpcRequest + .getParams() + .getTransaction() + .equals(transaction.toEnvelopeXdrBase64())) { + return new MockResponse().setResponseCode(200).setBody(json); + } + return new MockResponse().setResponseCode(404); + } + }; + mockWebServer.setDispatcher(dispatcher); + mockWebServer.start(); + + HttpUrl baseUrl = mockWebServer.url(""); + SorobanServer server = new SorobanServer(baseUrl.toString()); + SimulateTransactionResponse resp = server.simulateTransaction(transaction); + assertEquals(resp.getLatestLedger().longValue(), 1479L); + assertEquals( + resp.getTransactionData(), + "AAAAAAAAAAIAAAAGAAAAAcWLK/vE8FTnMk9r8gytPgJuQbutGm0gw9fUkY3tFlQRAAAAFAAAAAEAAAAAAAAAB300Hyg0HZG+Qie3zvsxLvugrNtFqd3AIntWy9bg2YvZAAAAAAAAAAEAAAAGAAAAAcWLK/vE8FTnMk9r8gytPgJuQbutGm0gw9fUkY3tFlQRAAAAEAAAAAEAAAACAAAADwAAAAdDb3VudGVyAAAAABIAAAAAAAAAAFi3xKLI8peqjz0kcSgf38zsr+SOVmMxPsGOEqc+ypihAAAAAQAAAAAAFcLDAAAF8AAAAQgAAAMcAAAAAAAAAJw="); + assertEquals(resp.getEvents().size(), 2); + assertEquals( + resp.getEvents().get(0), + "AAAAAQAAAAAAAAAAAAAAAgAAAAAAAAADAAAADwAAAAdmbl9jYWxsAAAAAA0AAAAgxYsr+8TwVOcyT2vyDK0+Am5Bu60abSDD19SRje0WVBEAAAAPAAAACWluY3JlbWVudAAAAAAAABAAAAABAAAAAgAAABIAAAAAAAAAAFi3xKLI8peqjz0kcSgf38zsr+SOVmMxPsGOEqc+ypihAAAAAwAAAAo="); + assertEquals( + resp.getEvents().get(1), + "AAAAAQAAAAAAAAABxYsr+8TwVOcyT2vyDK0+Am5Bu60abSDD19SRje0WVBEAAAACAAAAAAAAAAIAAAAPAAAACWZuX3JldHVybgAAAAAAAA8AAAAJaW5jcmVtZW50AAAAAAAAAwAAABQ="); + assertEquals(resp.getMinResourceFee().longValue(), 58595L); + assertEquals(resp.getResults().size(), 1); + assertEquals(resp.getResults().get(0).getAuth().size(), 1); + assertEquals( + resp.getResults().get(0).getAuth().get(0), + "AAAAAAAAAAAAAAABxYsr+8TwVOcyT2vyDK0+Am5Bu60abSDD19SRje0WVBEAAAAJaW5jcmVtZW50AAAAAAAAAgAAABIAAAAAAAAAAFi3xKLI8peqjz0kcSgf38zsr+SOVmMxPsGOEqc+ypihAAAAAwAAAAoAAAAA"); + assertEquals(resp.getResults().get(0).getXdr(), "AAAAAwAAABQ="); + assertEquals(resp.getCost().getCpuInstructions().longValue(), 1240100L); + assertEquals(resp.getCost().getMemoryBytes().longValue(), 161637L); + server.close(); + mockWebServer.close(); + } + + @Test + public void testPrepareTransaction() + throws IOException, SorobanRpcErrorResponse, PrepareTransactionException { + String json = + "{\n" + + " \"jsonrpc\": \"2.0\",\n" + + " \"id\": \"e1fabdcdf0244a2a9adfab94d7748b6c\",\n" + + " \"result\": {\n" + + " \"transactionData\": \"AAAAAAAAAAIAAAAGAAAAAcWLK/vE8FTnMk9r8gytPgJuQbutGm0gw9fUkY3tFlQRAAAAFAAAAAEAAAAAAAAAB300Hyg0HZG+Qie3zvsxLvugrNtFqd3AIntWy9bg2YvZAAAAAAAAAAEAAAAGAAAAAcWLK/vE8FTnMk9r8gytPgJuQbutGm0gw9fUkY3tFlQRAAAAEAAAAAEAAAACAAAADwAAAAdDb3VudGVyAAAAABIAAAAAAAAAAFi3xKLI8peqjz0kcSgf38zsr+SOVmMxPsGOEqc+ypihAAAAAQAAAAAAFcLDAAAF8AAAAQgAAAMcAAAAAAAAAJw=\",\n" + + " \"events\": [\n" + + " \"AAAAAQAAAAAAAAAAAAAAAgAAAAAAAAADAAAADwAAAAdmbl9jYWxsAAAAAA0AAAAgxYsr+8TwVOcyT2vyDK0+Am5Bu60abSDD19SRje0WVBEAAAAPAAAACWluY3JlbWVudAAAAAAAABAAAAABAAAAAgAAABIAAAAAAAAAAFi3xKLI8peqjz0kcSgf38zsr+SOVmMxPsGOEqc+ypihAAAAAwAAAAo=\",\n" + + " \"AAAAAQAAAAAAAAABxYsr+8TwVOcyT2vyDK0+Am5Bu60abSDD19SRje0WVBEAAAACAAAAAAAAAAIAAAAPAAAACWZuX3JldHVybgAAAAAAAA8AAAAJaW5jcmVtZW50AAAAAAAAAwAAABQ=\"\n" + + " ],\n" + + " \"minResourceFee\": \"58595\",\n" + + " \"results\": [\n" + + " {\n" + + " \"auth\": [\n" + + " \"AAAAAAAAAAAAAAABxYsr+8TwVOcyT2vyDK0+Am5Bu60abSDD19SRje0WVBEAAAAJaW5jcmVtZW50AAAAAAAAAgAAABIAAAAAAAAAAFi3xKLI8peqjz0kcSgf38zsr+SOVmMxPsGOEqc+ypihAAAAAwAAAAoAAAAA\"\n" + + " ],\n" + + " \"xdr\": \"AAAAAwAAABQ=\"\n" + + " }\n" + + " ],\n" + + " \"cost\": {\n" + + " \"cpuInsns\": \"1240100\",\n" + + " \"memBytes\": \"161637\"\n" + + " },\n" + + " \"latestLedger\": \"1479\"\n" + + " }\n" + + "}"; + + Transaction transaction = buildSorobanTransaction(null, null); + + MockWebServer mockWebServer = new MockWebServer(); + Dispatcher dispatcher = + new Dispatcher() { + @NotNull + @Override + public MockResponse dispatch(@NotNull RecordedRequest recordedRequest) + throws InterruptedException { + SorobanRpcRequest sorobanRpcRequest = + gson.fromJson( + recordedRequest.getBody().readUtf8(), + new TypeToken>() {}.getType()); + if ("POST".equals(recordedRequest.getMethod()) + && sorobanRpcRequest.getMethod().equals("simulateTransaction") + && sorobanRpcRequest + .getParams() + .getTransaction() + .equals(transaction.toEnvelopeXdrBase64())) { + return new MockResponse().setResponseCode(200).setBody(json); + } + return new MockResponse().setResponseCode(404); + } + }; + mockWebServer.setDispatcher(dispatcher); + mockWebServer.start(); + + HttpUrl baseUrl = mockWebServer.url(""); + SorobanServer server = new SorobanServer(baseUrl.toString()); + Transaction newTx = server.prepareTransaction(transaction); + + SorobanTransactionData sorobanData = + Util.sorobanTransactionDataToXDR( + "AAAAAAAAAAIAAAAGAAAAAcWLK/vE8FTnMk9r8gytPgJuQbutGm0gw9fUkY3tFlQRAAAAFAAAAAEAAAAAAAAAB300Hyg0HZG+Qie3zvsxLvugrNtFqd3AIntWy9bg2YvZAAAAAAAAAAEAAAAGAAAAAcWLK/vE8FTnMk9r8gytPgJuQbutGm0gw9fUkY3tFlQRAAAAEAAAAAEAAAACAAAADwAAAAdDb3VudGVyAAAAABIAAAAAAAAAAFi3xKLI8peqjz0kcSgf38zsr+SOVmMxPsGOEqc+ypihAAAAAQAAAAAAFcLDAAAF8AAAAQgAAAMcAAAAAAAAAJw="); + InvokeHostFunctionOperation operation = + InvokeHostFunctionOperation.builder() + .hostFunction( + ((InvokeHostFunctionOperation) transaction.getOperations()[0]).getHostFunction()) + .sourceAccount(transaction.getOperations()[0].getSourceAccount()) + .auth( + Collections.singletonList( + sorobanAuthorizationEntryFromXdrBase64( + "AAAAAAAAAAAAAAABxYsr+8TwVOcyT2vyDK0+Am5Bu60abSDD19SRje0WVBEAAAAJaW5jcmVtZW50AAAAAAAAAgAAABIAAAAAAAAAAFi3xKLI8peqjz0kcSgf38zsr+SOVmMxPsGOEqc+ypihAAAAAwAAAAoAAAAA"))) + .build(); + Transaction expectedTx = + new Transaction( + transaction.getAccountConverter(), + transaction.getSourceAccount(), + transaction.getFee() + 58595L, + transaction.getSequenceNumber(), + new Operation[] {operation}, + transaction.getMemo(), + transaction.getPreconditions(), + sorobanData, + transaction.getNetwork()); + assertEquals(expectedTx, newTx); + + server.close(); + mockWebServer.close(); + } + + @Test + public void testPrepareTransactionWithSorobanData() + throws IOException, SorobanRpcErrorResponse, PrepareTransactionException { + // soroban data will be overwritten + String json = + "{\n" + + " \"jsonrpc\": \"2.0\",\n" + + " \"id\": \"e1fabdcdf0244a2a9adfab94d7748b6c\",\n" + + " \"result\": {\n" + + " \"transactionData\": \"AAAAAAAAAAIAAAAGAAAAAcWLK/vE8FTnMk9r8gytPgJuQbutGm0gw9fUkY3tFlQRAAAAFAAAAAEAAAAAAAAAB300Hyg0HZG+Qie3zvsxLvugrNtFqd3AIntWy9bg2YvZAAAAAAAAAAEAAAAGAAAAAcWLK/vE8FTnMk9r8gytPgJuQbutGm0gw9fUkY3tFlQRAAAAEAAAAAEAAAACAAAADwAAAAdDb3VudGVyAAAAABIAAAAAAAAAAFi3xKLI8peqjz0kcSgf38zsr+SOVmMxPsGOEqc+ypihAAAAAQAAAAAAFcLDAAAF8AAAAQgAAAMcAAAAAAAAAJw=\",\n" + + " \"events\": [\n" + + " \"AAAAAQAAAAAAAAAAAAAAAgAAAAAAAAADAAAADwAAAAdmbl9jYWxsAAAAAA0AAAAgxYsr+8TwVOcyT2vyDK0+Am5Bu60abSDD19SRje0WVBEAAAAPAAAACWluY3JlbWVudAAAAAAAABAAAAABAAAAAgAAABIAAAAAAAAAAFi3xKLI8peqjz0kcSgf38zsr+SOVmMxPsGOEqc+ypihAAAAAwAAAAo=\",\n" + + " \"AAAAAQAAAAAAAAABxYsr+8TwVOcyT2vyDK0+Am5Bu60abSDD19SRje0WVBEAAAACAAAAAAAAAAIAAAAPAAAACWZuX3JldHVybgAAAAAAAA8AAAAJaW5jcmVtZW50AAAAAAAAAwAAABQ=\"\n" + + " ],\n" + + " \"minResourceFee\": \"58595\",\n" + + " \"results\": [\n" + + " {\n" + + " \"auth\": [\n" + + " \"AAAAAAAAAAAAAAABxYsr+8TwVOcyT2vyDK0+Am5Bu60abSDD19SRje0WVBEAAAAJaW5jcmVtZW50AAAAAAAAAgAAABIAAAAAAAAAAFi3xKLI8peqjz0kcSgf38zsr+SOVmMxPsGOEqc+ypihAAAAAwAAAAoAAAAA\"\n" + + " ],\n" + + " \"xdr\": \"AAAAAwAAABQ=\"\n" + + " }\n" + + " ],\n" + + " \"cost\": {\n" + + " \"cpuInsns\": \"1240100\",\n" + + " \"memBytes\": \"161637\"\n" + + " },\n" + + " \"latestLedger\": \"1479\"\n" + + " }\n" + + "}"; + LedgerKey ledgerKey = + new LedgerKey.Builder() + .discriminant(LedgerEntryType.ACCOUNT) + .account( + new LedgerKey.LedgerKeyAccount.Builder() + .accountID( + KeyPair.fromAccountId( + "GB7TAYRUZGE6TVT7NHP5SMIZRNQA6PLM423EYISAOAP3MKYIQMVYP2JO") + .getXdrAccountId()) + .build()) + .build(); + SorobanTransactionData originSorobanData = + new SorobanTransactionData.Builder() + .resources( + new SorobanResources.Builder() + .footprint( + new LedgerFootprint.Builder() + .readOnly(new LedgerKey[] {ledgerKey}) + .readWrite(new LedgerKey[] {}) + .build()) + .extendedMetaDataSizeBytes(new Uint32(216)) + .readBytes(new Uint32(699)) + .writeBytes(new Uint32(0)) + .instructions(new Uint32(34567)) + .build()) + .refundableFee(new Int64(100L)) + .ext(new ExtensionPoint.Builder().discriminant(0).build()) + .build(); + Transaction transaction = buildSorobanTransaction(originSorobanData, null); + + MockWebServer mockWebServer = new MockWebServer(); + Dispatcher dispatcher = + new Dispatcher() { + @NotNull + @Override + public MockResponse dispatch(@NotNull RecordedRequest recordedRequest) + throws InterruptedException { + SorobanRpcRequest sorobanRpcRequest = + gson.fromJson( + recordedRequest.getBody().readUtf8(), + new TypeToken>() {}.getType()); + if ("POST".equals(recordedRequest.getMethod()) + && sorobanRpcRequest.getMethod().equals("simulateTransaction") + && sorobanRpcRequest + .getParams() + .getTransaction() + .equals(transaction.toEnvelopeXdrBase64())) { + return new MockResponse().setResponseCode(200).setBody(json); + } + return new MockResponse().setResponseCode(404); + } + }; + mockWebServer.setDispatcher(dispatcher); + mockWebServer.start(); + + HttpUrl baseUrl = mockWebServer.url(""); + SorobanServer server = new SorobanServer(baseUrl.toString()); + Transaction newTx = server.prepareTransaction(transaction); + + SorobanTransactionData sorobanData = + Util.sorobanTransactionDataToXDR( + "AAAAAAAAAAIAAAAGAAAAAcWLK/vE8FTnMk9r8gytPgJuQbutGm0gw9fUkY3tFlQRAAAAFAAAAAEAAAAAAAAAB300Hyg0HZG+Qie3zvsxLvugrNtFqd3AIntWy9bg2YvZAAAAAAAAAAEAAAAGAAAAAcWLK/vE8FTnMk9r8gytPgJuQbutGm0gw9fUkY3tFlQRAAAAEAAAAAEAAAACAAAADwAAAAdDb3VudGVyAAAAABIAAAAAAAAAAFi3xKLI8peqjz0kcSgf38zsr+SOVmMxPsGOEqc+ypihAAAAAQAAAAAAFcLDAAAF8AAAAQgAAAMcAAAAAAAAAJw="); + InvokeHostFunctionOperation operation = + InvokeHostFunctionOperation.builder() + .hostFunction( + ((InvokeHostFunctionOperation) transaction.getOperations()[0]).getHostFunction()) + .sourceAccount(transaction.getOperations()[0].getSourceAccount()) + .auth( + Collections.singletonList( + sorobanAuthorizationEntryFromXdrBase64( + "AAAAAAAAAAAAAAABxYsr+8TwVOcyT2vyDK0+Am5Bu60abSDD19SRje0WVBEAAAAJaW5jcmVtZW50AAAAAAAAAgAAABIAAAAAAAAAAFi3xKLI8peqjz0kcSgf38zsr+SOVmMxPsGOEqc+ypihAAAAAwAAAAoAAAAA"))) + .build(); + Transaction expectedTx = + new Transaction( + transaction.getAccountConverter(), + transaction.getSourceAccount(), + transaction.getFee() + 58595L, + transaction.getSequenceNumber(), + new Operation[] {operation}, + transaction.getMemo(), + transaction.getPreconditions(), + sorobanData, + transaction.getNetwork()); + assertEquals(expectedTx, newTx); + + server.close(); + mockWebServer.close(); + } + + @Test + public void testPrepareTransactionWithAuth() + throws IOException, SorobanRpcErrorResponse, PrepareTransactionException { + // origin auth will not be overwritten + String json = + "{\n" + + " \"jsonrpc\": \"2.0\",\n" + + " \"id\": \"e1fabdcdf0244a2a9adfab94d7748b6c\",\n" + + " \"result\": {\n" + + " \"transactionData\": \"AAAAAAAAAAIAAAAGAAAAAcWLK/vE8FTnMk9r8gytPgJuQbutGm0gw9fUkY3tFlQRAAAAFAAAAAEAAAAAAAAAB300Hyg0HZG+Qie3zvsxLvugrNtFqd3AIntWy9bg2YvZAAAAAAAAAAEAAAAGAAAAAcWLK/vE8FTnMk9r8gytPgJuQbutGm0gw9fUkY3tFlQRAAAAEAAAAAEAAAACAAAADwAAAAdDb3VudGVyAAAAABIAAAAAAAAAAFi3xKLI8peqjz0kcSgf38zsr+SOVmMxPsGOEqc+ypihAAAAAQAAAAAAFcLDAAAF8AAAAQgAAAMcAAAAAAAAAJw=\",\n" + + " \"events\": [\n" + + " \"AAAAAQAAAAAAAAAAAAAAAgAAAAAAAAADAAAADwAAAAdmbl9jYWxsAAAAAA0AAAAgxYsr+8TwVOcyT2vyDK0+Am5Bu60abSDD19SRje0WVBEAAAAPAAAACWluY3JlbWVudAAAAAAAABAAAAABAAAAAgAAABIAAAAAAAAAAFi3xKLI8peqjz0kcSgf38zsr+SOVmMxPsGOEqc+ypihAAAAAwAAAAo=\",\n" + + " \"AAAAAQAAAAAAAAABxYsr+8TwVOcyT2vyDK0+Am5Bu60abSDD19SRje0WVBEAAAACAAAAAAAAAAIAAAAPAAAACWZuX3JldHVybgAAAAAAAA8AAAAJaW5jcmVtZW50AAAAAAAAAwAAABQ=\"\n" + + " ],\n" + + " \"minResourceFee\": \"58595\",\n" + + " \"results\": [\n" + + " {\n" + + " \"auth\": [\n" + + " \"AAAAAAAAAAAAAAABxYsr+8TwVOcyT2vyDK0+Am5Bu60abSDD19SRje0WVBEAAAAJaW5jcmVtZW50AAAAAAAAAgAAABIAAAAAAAAAAFi3xKLI8peqjz0kcSgf38zsr+SOVmMxPsGOEqc+ypihAAAAAwAAAAoAAAAA\"\n" + + " ],\n" + + " \"xdr\": \"AAAAAwAAABQ=\"\n" + + " }\n" + + " ],\n" + + " \"cost\": {\n" + + " \"cpuInsns\": \"1240100\",\n" + + " \"memBytes\": \"161637\"\n" + + " },\n" + + " \"latestLedger\": \"1479\"\n" + + " }\n" + + "}"; + CreateContractArgs createContractArgs = + new CreateContractArgs.Builder() + .contractIDPreimage( + new ContractIDPreimage.Builder() + .discriminant(ContractIDPreimageType.CONTRACT_ID_PREIMAGE_FROM_ADDRESS) + .fromAddress( + new ContractIDPreimage.ContractIDPreimageFromAddress.Builder() + .address( + new Address( + "GB7TAYRUZGE6TVT7NHP5SMIZRNQA6PLM423EYISAOAP3MKYIQMVYP2JO") + .toSCAddress()) + .salt(new Uint256(new byte[32])) + .build()) + .build()) + .executable( + new ContractExecutable.Builder() + .discriminant(ContractExecutableType.CONTRACT_EXECUTABLE_TOKEN) + .build()) + .build(); + SorobanAuthorizationEntry auth = + new SorobanAuthorizationEntry.Builder() + .credentials( + new SorobanCredentials.Builder() + .discriminant(SorobanCredentialsType.SOROBAN_CREDENTIALS_SOURCE_ACCOUNT) + .build()) + .rootInvocation( + new SorobanAuthorizedInvocation.Builder() + .subInvocations(new SorobanAuthorizedInvocation[] {}) + .function( + new SorobanAuthorizedFunction.Builder() + .discriminant( + SorobanAuthorizedFunctionType + .SOROBAN_AUTHORIZED_FUNCTION_TYPE_CREATE_CONTRACT_HOST_FN) + .createContractHostFn(createContractArgs) + .build()) + .build()) + .build(); + + Transaction transaction = buildSorobanTransaction(null, Collections.singletonList(auth)); + + MockWebServer mockWebServer = new MockWebServer(); + Dispatcher dispatcher = + new Dispatcher() { + @NotNull + @Override + public MockResponse dispatch(@NotNull RecordedRequest recordedRequest) + throws InterruptedException { + SorobanRpcRequest sorobanRpcRequest = + gson.fromJson( + recordedRequest.getBody().readUtf8(), + new TypeToken>() {}.getType()); + if ("POST".equals(recordedRequest.getMethod()) + && sorobanRpcRequest.getMethod().equals("simulateTransaction") + && sorobanRpcRequest + .getParams() + .getTransaction() + .equals(transaction.toEnvelopeXdrBase64())) { + return new MockResponse().setResponseCode(200).setBody(json); + } + return new MockResponse().setResponseCode(404); + } + }; + mockWebServer.setDispatcher(dispatcher); + mockWebServer.start(); + + HttpUrl baseUrl = mockWebServer.url(""); + SorobanServer server = new SorobanServer(baseUrl.toString()); + Transaction newTx = server.prepareTransaction(transaction); + + SorobanTransactionData sorobanData = + Util.sorobanTransactionDataToXDR( + "AAAAAAAAAAIAAAAGAAAAAcWLK/vE8FTnMk9r8gytPgJuQbutGm0gw9fUkY3tFlQRAAAAFAAAAAEAAAAAAAAAB300Hyg0HZG+Qie3zvsxLvugrNtFqd3AIntWy9bg2YvZAAAAAAAAAAEAAAAGAAAAAcWLK/vE8FTnMk9r8gytPgJuQbutGm0gw9fUkY3tFlQRAAAAEAAAAAEAAAACAAAADwAAAAdDb3VudGVyAAAAABIAAAAAAAAAAFi3xKLI8peqjz0kcSgf38zsr+SOVmMxPsGOEqc+ypihAAAAAQAAAAAAFcLDAAAF8AAAAQgAAAMcAAAAAAAAAJw="); + InvokeHostFunctionOperation operation = + InvokeHostFunctionOperation.builder() + .hostFunction( + ((InvokeHostFunctionOperation) transaction.getOperations()[0]).getHostFunction()) + .sourceAccount(transaction.getOperations()[0].getSourceAccount()) + .auth( + Arrays.asList( + auth, + sorobanAuthorizationEntryFromXdrBase64( + "AAAAAAAAAAAAAAABxYsr+8TwVOcyT2vyDK0+Am5Bu60abSDD19SRje0WVBEAAAAJaW5jcmVtZW50AAAAAAAAAgAAABIAAAAAAAAAAFi3xKLI8peqjz0kcSgf38zsr+SOVmMxPsGOEqc+ypihAAAAAwAAAAoAAAAA"))) + .build(); + Transaction expectedTx = + new Transaction( + transaction.getAccountConverter(), + transaction.getSourceAccount(), + transaction.getFee() + 58595L, + transaction.getSequenceNumber(), + new Operation[] {operation}, + transaction.getMemo(), + transaction.getPreconditions(), + sorobanData, + transaction.getNetwork()); + assertEquals(expectedTx, newTx); + + server.close(); + mockWebServer.close(); + } + + @Test(expected = PrepareTransactionException.class) + public void testPrepareTransactionWithPrepareTransactionExceptionThrowsErrorResponse() + throws IOException, SorobanRpcErrorResponse, PrepareTransactionException { + String json = + "{\n" + + " \"jsonrpc\": \"2.0\",\n" + + " \"id\": \"7b6ada2bdec04ee28147d1557aadc3cf\",\n" + + " \"result\": {\n" + + " \"error\": \"HostError: Error(WasmVm, MissingValue)\\n\\nEvent log (newest first):\\n 0: [Diagnostic Event] contract:607682f2477a6be8cdf0fdf32be13d5f25a686cc094fd93d5aa3d7b68232d0c0, topics:[error, Error(WasmVm, MissingValue)], data:[\\\"invoking unknown export\\\", increment]\\n 1: [Diagnostic Event] topics:[fn_call, Bytes(607682f2477a6be8cdf0fdf32be13d5f25a686cc094fd93d5aa3d7b68232d0c0), increment], data:[Address(Account(58b7c4a2c8f297aa8f3d2471281fdfccecafe48e5663313ec18e12a73eca98a1)), 10]\\n\\nBacktrace (newest first):\\n 0: soroban_env_host::vm::Vm::invoke_function_raw\\n 1: soroban_env_host::host::frame::::call_n_internal\\n 2: soroban_env_host::host::frame::::invoke_function\\n 3: preflight::preflight_invoke_hf_op::{{closure}}\\n 4: preflight::catch_preflight_panic\\n 5: _cgo_a3255893d7fd_Cfunc_preflight_invoke_hf_op\\n at /tmp/go-build/cgo-gcc-prolog:99:11\\n 6: runtime.asmcgocall\\n at ./runtime/asm_amd64.s:848\\n\\n\",\n" + + " \"transactionData\": \"\",\n" + + " \"events\": null,\n" + + " \"minResourceFee\": \"0\",\n" + + " \"cost\": {\n" + + " \"cpuInsns\": \"0\",\n" + + " \"memBytes\": \"0\"\n" + + " },\n" + + " \"latestLedger\": \"898\"\n" + + " }\n" + + "}"; + + Transaction transaction = buildSorobanTransaction(null, null); + + MockWebServer mockWebServer = new MockWebServer(); + Dispatcher dispatcher = + new Dispatcher() { + @NotNull + @Override + public MockResponse dispatch(@NotNull RecordedRequest recordedRequest) + throws InterruptedException { + SorobanRpcRequest sorobanRpcRequest = + gson.fromJson( + recordedRequest.getBody().readUtf8(), + new TypeToken>() {}.getType()); + if ("POST".equals(recordedRequest.getMethod()) + && sorobanRpcRequest.getMethod().equals("simulateTransaction") + && sorobanRpcRequest + .getParams() + .getTransaction() + .equals(transaction.toEnvelopeXdrBase64())) { + return new MockResponse().setResponseCode(200).setBody(json); + } + return new MockResponse().setResponseCode(404); + } + }; + mockWebServer.setDispatcher(dispatcher); + mockWebServer.start(); + + HttpUrl baseUrl = mockWebServer.url(""); + SorobanServer server = new SorobanServer(baseUrl.toString()); + server.prepareTransaction(transaction); + server.close(); + mockWebServer.close(); + } + + @Test(expected = PrepareTransactionException.class) + public void testPrepareTransactionWithPrepareTransactionExceptionThrowsErrorInvalidResults() + throws IOException, SorobanRpcErrorResponse, PrepareTransactionException { + String json = + "{\n" + + " \"jsonrpc\": \"2.0\",\n" + + " \"id\": \"e1fabdcdf0244a2a9adfab94d7748b6c\",\n" + + " \"result\": {\n" + + " \"transactionData\": \"AAAAAAAAAAIAAAAGAAAAAcWLK/vE8FTnMk9r8gytPgJuQbutGm0gw9fUkY3tFlQRAAAAFAAAAAEAAAAAAAAAB300Hyg0HZG+Qie3zvsxLvugrNtFqd3AIntWy9bg2YvZAAAAAAAAAAEAAAAGAAAAAcWLK/vE8FTnMk9r8gytPgJuQbutGm0gw9fUkY3tFlQRAAAAEAAAAAEAAAACAAAADwAAAAdDb3VudGVyAAAAABIAAAAAAAAAAFi3xKLI8peqjz0kcSgf38zsr+SOVmMxPsGOEqc+ypihAAAAAQAAAAAAFcLDAAAF8AAAAQgAAAMcAAAAAAAAAJw=\",\n" + + " \"events\": [\n" + + " \"AAAAAQAAAAAAAAAAAAAAAgAAAAAAAAADAAAADwAAAAdmbl9jYWxsAAAAAA0AAAAgxYsr+8TwVOcyT2vyDK0+Am5Bu60abSDD19SRje0WVBEAAAAPAAAACWluY3JlbWVudAAAAAAAABAAAAABAAAAAgAAABIAAAAAAAAAAFi3xKLI8peqjz0kcSgf38zsr+SOVmMxPsGOEqc+ypihAAAAAwAAAAo=\",\n" + + " \"AAAAAQAAAAAAAAABxYsr+8TwVOcyT2vyDK0+Am5Bu60abSDD19SRje0WVBEAAAACAAAAAAAAAAIAAAAPAAAACWZuX3JldHVybgAAAAAAAA8AAAAJaW5jcmVtZW50AAAAAAAAAwAAABQ=\"\n" + + " ],\n" + + " \"minResourceFee\": \"58595\",\n" + + " \"results\": [\n" + + " {\n" + + " \"auth\": [\n" + + " \"AAAAAAAAAAAAAAABxYsr+8TwVOcyT2vyDK0+Am5Bu60abSDD19SRje0WVBEAAAAJaW5jcmVtZW50AAAAAAAAAgAAABIAAAAAAAAAAFi3xKLI8peqjz0kcSgf38zsr+SOVmMxPsGOEqc+ypihAAAAAwAAAAoAAAAA\"\n" + + " ],\n" + + " \"xdr\": \"AAAAAwAAABQ=\"\n" + + " },\n" + + " {\n" + + " \"auth\": [\n" + + " \"AAAAAAAAAAAAAAABxYsr+8TwVOcyT2vyDK0+Am5Bu60abSDD19SRje0WVBEAAAAJaW5jcmVtZW50AAAAAAAAAgAAABIAAAAAAAAAAFi3xKLI8peqjz0kcSgf38zsr+SOVmMxPsGOEqc+ypihAAAAAwAAAAoAAAAA\"\n" + + " ],\n" + + " \"xdr\": \"AAAAAwAAABQ=\"\n" + + " }\n" + + " ],\n" + + " \"cost\": {\n" + + " \"cpuInsns\": \"1240100\",\n" + + " \"memBytes\": \"161637\"\n" + + " },\n" + + " \"latestLedger\": \"1479\"\n" + + " }\n" + + "}"; + + Transaction transaction = buildSorobanTransaction(null, null); + + MockWebServer mockWebServer = new MockWebServer(); + Dispatcher dispatcher = + new Dispatcher() { + @NotNull + @Override + public MockResponse dispatch(@NotNull RecordedRequest recordedRequest) + throws InterruptedException { + SorobanRpcRequest sorobanRpcRequest = + gson.fromJson( + recordedRequest.getBody().readUtf8(), + new TypeToken>() {}.getType()); + if ("POST".equals(recordedRequest.getMethod()) + && sorobanRpcRequest.getMethod().equals("simulateTransaction") + && sorobanRpcRequest + .getParams() + .getTransaction() + .equals(transaction.toEnvelopeXdrBase64())) { + return new MockResponse().setResponseCode(200).setBody(json); + } + return new MockResponse().setResponseCode(404); + } + }; + mockWebServer.setDispatcher(dispatcher); + mockWebServer.start(); + + HttpUrl baseUrl = mockWebServer.url(""); + SorobanServer server = new SorobanServer(baseUrl.toString()); + server.prepareTransaction(transaction); + server.close(); + mockWebServer.close(); + } + + @Test + public void testSendTransaction() + throws IOException, SorobanRpcErrorResponse, PrepareTransactionException { + String json = + "{\n" + + " \"jsonrpc\": \"2.0\",\n" + + " \"id\": \"688dfcf3bcd04f52af4866e98dffe387\",\n" + + " \"result\": {\n" + + " \"status\": \"PENDING\",\n" + + " \"hash\": \"f59636c3bb27ad958c599632405ed657c3d7d55c717dbfd2644a68625e9d9e7e\",\n" + + " \"latestLedger\": \"1479\",\n" + + " \"latestLedgerCloseTime\": \"1690594566\"\n" + + " }\n" + + "}"; + + Transaction transaction = buildSorobanTransaction(null, null); + + MockWebServer mockWebServer = new MockWebServer(); + Dispatcher dispatcher = + new Dispatcher() { + @NotNull + @Override + public MockResponse dispatch(@NotNull RecordedRequest recordedRequest) + throws InterruptedException { + SorobanRpcRequest sorobanRpcRequest = + gson.fromJson( + recordedRequest.getBody().readUtf8(), + new TypeToken>() {}.getType()); + if ("POST".equals(recordedRequest.getMethod()) + && sorobanRpcRequest.getMethod().equals("sendTransaction") + && sorobanRpcRequest + .getParams() + .getTransaction() + .equals(transaction.toEnvelopeXdrBase64())) { + return new MockResponse().setResponseCode(200).setBody(json); + } + return new MockResponse().setResponseCode(404); + } + }; + mockWebServer.setDispatcher(dispatcher); + mockWebServer.start(); + + HttpUrl baseUrl = mockWebServer.url(""); + SorobanServer server = new SorobanServer(baseUrl.toString()); + SendTransactionResponse response = server.sendTransaction(transaction); + assertEquals(response.getStatus(), SendTransactionResponse.SendTransactionStatus.PENDING); + assertEquals(response.getHash(), transaction.hashHex()); + assertEquals(response.getLatestLedger().longValue(), 1479L); + assertEquals(response.getLatestLedgerCloseTime().longValue(), 1690594566L); + + server.close(); + mockWebServer.close(); + } + + @Test + public void testSorobanRpcErrorResponseThrows() throws IOException { + String json = + "{\n" + + " \"jsonrpc\": \"2.0\",\n" + + " \"id\": \"198cb1a8-9104-4446-a269-88bf000c2721\",\n" + + " \"error\": {\n" + + " \"code\": -32601,\n" + + " \"message\": \"method not found\",\n" + + " \"data\": \"mockTest\"\n" + + " }\n" + + "}"; + + MockWebServer mockWebServer = new MockWebServer(); + Dispatcher dispatcher = + new Dispatcher() { + @NotNull + @Override + public MockResponse dispatch(@NotNull RecordedRequest recordedRequest) + throws InterruptedException { + SorobanRpcRequest sorobanRpcRequest = + gson.fromJson( + recordedRequest.getBody().readUtf8(), + new TypeToken>() {}.getType()); + if ("POST".equals(recordedRequest.getMethod()) + && sorobanRpcRequest.getMethod().equals("getNetwork")) { + return new MockResponse().setResponseCode(200).setBody(json); + } + return new MockResponse().setResponseCode(404); + } + }; + mockWebServer.setDispatcher(dispatcher); + mockWebServer.start(); + + HttpUrl baseUrl = mockWebServer.url(""); + SorobanServer server = new SorobanServer(baseUrl.toString()); + try { + server.getNetwork(); + fail(); + } catch (SorobanRpcErrorResponse e) { + assertEquals(e.getCode().longValue(), -32601L); + assertEquals(e.getMessage(), "method not found"); + assertEquals(e.getData(), "mockTest"); + } + + server.close(); + mockWebServer.close(); + } + + private Transaction buildSorobanTransaction( + SorobanTransactionData sorobanData, Collection auth) { + String contractId = "CDCYWK73YTYFJZZSJ5V7EDFNHYBG4QN3VUNG2IGD27KJDDPNCZKBCBXK"; + KeyPair txSubmitterKp = + KeyPair.fromSecretSeed("SAAPYAPTTRZMCUZFPG3G66V4ZMHTK4TWA6NS7U4F7Z3IMUD52EK4DDEV"); + KeyPair opInvokerKp = + KeyPair.fromSecretSeed("SAEZSI6DY7AXJFIYA4PM6SIBNEYYXIEM2MSOTHFGKHDW32MBQ7KVO6EN"); + + TransactionBuilderAccount source = new Account(txSubmitterKp.getAccountId(), 6171868004355L); + + if (auth == null) { + auth = new ArrayList<>(); + } + + return new TransactionBuilder(AccountConverter.enableMuxed(), source, Network.STANDALONE) + .setBaseFee(50000) + .addPreconditions( + TransactionPreconditions.builder().timeBounds(new TimeBounds(0, 0)).build()) + .addOperation( + InvokeHostFunctionOperation.builder() + .sourceAccount(opInvokerKp.getAccountId()) + .hostFunction( + new HostFunction.Builder() + .discriminant(HostFunctionType.HOST_FUNCTION_TYPE_INVOKE_CONTRACT) + .invokeContract( + new SCVec( + new SCVal[] { + new Address(contractId).toSCVal(), + new SCVal.Builder() + .discriminant(SCValType.SCV_SYMBOL) + .sym(new SCSymbol(new XdrString("increment"))) + .build(), + new Address(opInvokerKp.getAccountId()).toSCVal(), + new SCVal.Builder() + .discriminant(SCValType.SCV_U32) + .u32(new Uint32(10)) + .build() + })) + .build()) + .auth(auth) + .build()) + .setSorobanData(sorobanData) + .build(); + } + + private static SorobanAuthorizationEntry sorobanAuthorizationEntryFromXdrBase64( + String sorobanAuthorizationEntry) { + BaseEncoding base64Encoding = BaseEncoding.base64(); + byte[] bytes = base64Encoding.decode(sorobanAuthorizationEntry); + ByteArrayInputStream inputStream = new ByteArrayInputStream(bytes); + XdrDataInputStream xdrInputStream = new XdrDataInputStream(inputStream); + try { + return SorobanAuthorizationEntry.decode(xdrInputStream); + } catch (IOException e) { + throw new IllegalArgumentException( + "invalid ledgerEntryData: " + sorobanAuthorizationEntry, e); + } + } + + private static String ledgerKeyToXdrBase64(LedgerKey ledgerKey) { + ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); + XdrDataOutputStream xdrDataOutputStream = new XdrDataOutputStream(byteArrayOutputStream); + try { + ledgerKey.encode(xdrDataOutputStream); + } catch (IOException e) { + throw new IllegalArgumentException("invalid ledgerKey.", e); + } + BaseEncoding base64Encoding = BaseEncoding.base64(); + return base64Encoding.encode(byteArrayOutputStream.toByteArray()); + } +} diff --git a/src/test/java/org/stellar/sdk/responses/sorobanrpc/GetEventsDeserializerTest.java b/src/test/java/org/stellar/sdk/responses/sorobanrpc/GetEventsDeserializerTest.java new file mode 100644 index 000000000..0f162dd80 --- /dev/null +++ b/src/test/java/org/stellar/sdk/responses/sorobanrpc/GetEventsDeserializerTest.java @@ -0,0 +1,98 @@ +package org.stellar.sdk.responses.sorobanrpc; + +import static org.junit.Assert.assertEquals; + +import com.google.gson.reflect.TypeToken; +import org.junit.Test; +import org.stellar.sdk.requests.sorobanrpc.EventFilterType; +import org.stellar.sdk.responses.GsonSingleton; + +public class GetEventsDeserializerTest { + @Test + public void testDeserialize() { + SorobanRpcResponse getEventsResponse = + GsonSingleton.getInstance() + .fromJson(json, new TypeToken>() {}.getType()); + assertEquals(getEventsResponse.getResult().getLatestLedger().longValue(), 169L); + assertEquals(getEventsResponse.getResult().getEvents().size(), 3); + GetEventsResponse.EventInfo eventInfo0 = getEventsResponse.getResult().getEvents().get(0); + assertEquals(eventInfo0.getType(), EventFilterType.CONTRACT); + assertEquals(eventInfo0.getLedger().intValue(), 108); + assertEquals(eventInfo0.getLedgerClosedAt(), "2023-07-23T14:47:01Z"); + assertEquals( + eventInfo0.getContractId(), + "607682f2477a6be8cdf0fdf32be13d5f25a686cc094fd93d5aa3d7b68232d0c0"); + assertEquals(eventInfo0.getId(), "0000000463856472064-0000000000"); + assertEquals(eventInfo0.getPagingToken(), "0000000463856472064-0000000000"); + assertEquals(eventInfo0.getTopic().size(), 2); + assertEquals(eventInfo0.getTopic().get(0), "AAAADwAAAAdDT1VOVEVSAA=="); + assertEquals(eventInfo0.getTopic().get(1), "AAAADwAAAAlpbmNyZW1lbnQAAAA="); + assertEquals(eventInfo0.getValue().getXdr(), "AAAAAwAAAAE="); + assertEquals(eventInfo0.getInSuccessfulContractCall(), true); + + GetEventsResponse.EventInfo eventInfo1 = getEventsResponse.getResult().getEvents().get(1); + assertEquals(eventInfo1.getType(), EventFilterType.SYSTEM); + + GetEventsResponse.EventInfo eventInfo2 = getEventsResponse.getResult().getEvents().get(2); + assertEquals(eventInfo2.getType(), EventFilterType.DIAGNOSTIC); + } + + String json = + "{\n" + + " \"jsonrpc\": \"2.0\",\n" + + " \"id\": \"198cb1a8-9104-4446-a269-88bf000c2721\",\n" + + " \"result\": {\n" + + " \"events\": [\n" + + " {\n" + + " \"type\": \"contract\",\n" + + " \"ledger\": \"108\",\n" + + " \"ledgerClosedAt\": \"2023-07-23T14:47:01Z\",\n" + + " \"contractId\": \"607682f2477a6be8cdf0fdf32be13d5f25a686cc094fd93d5aa3d7b68232d0c0\",\n" + + " \"id\": \"0000000463856472064-0000000000\",\n" + + " \"pagingToken\": \"0000000463856472064-0000000000\",\n" + + " \"topic\": [\n" + + " \"AAAADwAAAAdDT1VOVEVSAA==\",\n" + + " \"AAAADwAAAAlpbmNyZW1lbnQAAAA=\"\n" + + " ],\n" + + " \"value\": {\n" + + " \"xdr\": \"AAAAAwAAAAE=\"\n" + + " },\n" + + " \"inSuccessfulContractCall\": true\n" + + " },\n" + + " {\n" + + " \"type\": \"system\",\n" + + " \"ledger\": \"111\",\n" + + " \"ledgerClosedAt\": \"2023-07-23T14:47:04Z\",\n" + + " \"contractId\": \"607682f2477a6be8cdf0fdf32be13d5f25a686cc094fd93d5aa3d7b68232d0c0\",\n" + + " \"id\": \"0000000476741373952-0000000000\",\n" + + " \"pagingToken\": \"0000000476741373952-0000000000\",\n" + + " \"topic\": [\n" + + " \"AAAADwAAAAdDT1VOVEVSAA==\",\n" + + " \"AAAADwAAAAlpbmNyZW1lbnQAAAA=\"\n" + + " ],\n" + + " \"value\": {\n" + + " \"xdr\": \"AAAAAwAAAAI=\"\n" + + " },\n" + + " \"inSuccessfulContractCall\": true\n" + + " },\n" + + " {\n" + + " \"type\": \"diagnostic\",\n" + + " \"ledger\": \"114\",\n" + + " \"ledgerClosedAt\": \"2023-07-23T14:47:07Z\",\n" + + " \"contractId\": \"607682f2477a6be8cdf0fdf32be13d5f25a686cc094fd93d5aa3d7b68232d0c0\",\n" + + " \"id\": \"0000000489626275840-0000000000\",\n" + + " \"pagingToken\": \"0000000489626275840-0000000000\",\n" + + " \"topic\": [\n" + + " \"AAAADwAAAAdDT1VOVEVSAA==\",\n" + + " \"AAAADwAAAAlpbmNyZW1lbnQAAAA=\"\n" + + " ],\n" + + " \"value\": {\n" + + " \"xdr\": \"AAAAAwAAAAM=\"\n" + + " },\n" + + " \"inSuccessfulContractCall\": true\n" + + " }\n" + + " ],\n" + + " \"latestLedger\": \"169\"\n" + + " }\n" + + "}"; +} diff --git a/src/test/java/org/stellar/sdk/responses/sorobanrpc/GetHealthDeserializerTest.java b/src/test/java/org/stellar/sdk/responses/sorobanrpc/GetHealthDeserializerTest.java new file mode 100644 index 000000000..fc3e33712 --- /dev/null +++ b/src/test/java/org/stellar/sdk/responses/sorobanrpc/GetHealthDeserializerTest.java @@ -0,0 +1,27 @@ +package org.stellar.sdk.responses.sorobanrpc; + +import static org.junit.Assert.assertEquals; + +import com.google.gson.reflect.TypeToken; +import org.junit.Test; +import org.stellar.sdk.responses.GsonSingleton; + +public class GetHealthDeserializerTest { + + @Test + public void testDeserialize() { + SorobanRpcResponse getHealthResponse = + GsonSingleton.getInstance() + .fromJson(json, new TypeToken>() {}.getType()); + assertEquals(getHealthResponse.getResult().getStatus(), "healthy"); + } + + String json = + "{\n" + + " \"jsonrpc\": \"2.0\",\n" + + " \"id\": \"198cb1a8-9104-4446-a269-88bf000c2721\",\n" + + " \"result\": {\n" + + " \"status\": \"healthy\"\n" + + " }\n" + + "}"; +} diff --git a/src/test/java/org/stellar/sdk/responses/sorobanrpc/GetLatestLedgerDeserializerTest.java b/src/test/java/org/stellar/sdk/responses/sorobanrpc/GetLatestLedgerDeserializerTest.java new file mode 100644 index 000000000..360384272 --- /dev/null +++ b/src/test/java/org/stellar/sdk/responses/sorobanrpc/GetLatestLedgerDeserializerTest.java @@ -0,0 +1,33 @@ +package org.stellar.sdk.responses.sorobanrpc; + +import static org.junit.Assert.assertEquals; + +import com.google.gson.reflect.TypeToken; +import org.junit.Test; +import org.stellar.sdk.responses.GsonSingleton; + +public class GetLatestLedgerDeserializerTest { + @Test + public void testDeserialize() { + SorobanRpcResponse getLatestLedgerResponse = + GsonSingleton.getInstance() + .fromJson( + json, new TypeToken>() {}.getType()); + assertEquals( + getLatestLedgerResponse.getResult().getId(), + "e73d7654b72daa637f396669182c6072549736a9e3b6fcb8e685adb61f8c910a"); + assertEquals(getLatestLedgerResponse.getResult().getProtocolVersion().intValue(), 20); + assertEquals(getLatestLedgerResponse.getResult().getSequence().intValue(), 24170); + } + + String json = + "{\n" + + " \"jsonrpc\": \"2.0\",\n" + + " \"id\": \"198cb1a8-9104-4446-a269-88bf000c2721\",\n" + + " \"result\": {\n" + + " \"id\": \"e73d7654b72daa637f396669182c6072549736a9e3b6fcb8e685adb61f8c910a\",\n" + + " \"protocolVersion\": \"20\",\n" + + " \"sequence\": 24170\n" + + " }\n" + + "}"; +} diff --git a/src/test/java/org/stellar/sdk/responses/sorobanrpc/GetLedgerEntriesDeserializerTest.java b/src/test/java/org/stellar/sdk/responses/sorobanrpc/GetLedgerEntriesDeserializerTest.java new file mode 100644 index 000000000..279e15e2f --- /dev/null +++ b/src/test/java/org/stellar/sdk/responses/sorobanrpc/GetLedgerEntriesDeserializerTest.java @@ -0,0 +1,73 @@ +package org.stellar.sdk.responses.sorobanrpc; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; + +import com.google.gson.reflect.TypeToken; +import org.junit.Test; +import org.stellar.sdk.responses.GsonSingleton; + +public class GetLedgerEntriesDeserializerTest { + + @Test + public void testDeserializeEntriesNotNull() { + SorobanRpcResponse getLedgerEntriesResponse = + GsonSingleton.getInstance() + .fromJson( + getJsonEntriesNotNull, + new TypeToken>() {}.getType()); + assertEquals(getLedgerEntriesResponse.getResult().getLatestLedger().longValue(), 1457); + assertEquals(getLedgerEntriesResponse.getResult().getEntries().size(), 1); + assertEquals( + getLedgerEntriesResponse.getResult().getEntries().get(0).getKey(), + "AAAAAAAAAADBPp7TMinJylnn+6dQXJACNc15LF+aJ2Py1BaR4P10JA=="); + assertEquals( + getLedgerEntriesResponse.getResult().getEntries().get(0).getXdr(), + "AAAAAAAAAADBPp7TMinJylnn+6dQXJACNc15LF+aJ2Py1BaR4P10JAAAABdIcjmeAAAAfgAAAAgAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAAAAAAAAAAAAAAAAAADAAAAAAAAArcAAAAAZMIW+A=="); + assertEquals( + getLedgerEntriesResponse + .getResult() + .getEntries() + .get(0) + .getLastModifiedLedger() + .longValue(), + 695); + } + + @Test + public void testDeserializeEntriesNull() { + SorobanRpcResponse getLedgerEntriesResponse = + GsonSingleton.getInstance() + .fromJson( + jsonEntriesNull, + new TypeToken>() {}.getType()); + assertNull(getLedgerEntriesResponse.getResult().getEntries()); + assertEquals(getLedgerEntriesResponse.getResult().getLatestLedger().longValue(), 1009L); + } + + String getJsonEntriesNotNull = + "{\n" + + " \"jsonrpc\": \"2.0\",\n" + + " \"id\": \"44dc79a1b2734eb79d28fe8806345b39\",\n" + + " \"result\": {\n" + + " \"entries\": [\n" + + " {\n" + + " \"key\": \"AAAAAAAAAADBPp7TMinJylnn+6dQXJACNc15LF+aJ2Py1BaR4P10JA==\",\n" + + " \"xdr\": \"AAAAAAAAAADBPp7TMinJylnn+6dQXJACNc15LF+aJ2Py1BaR4P10JAAAABdIcjmeAAAAfgAAAAgAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAAAAAAAAAAAAAAAAAADAAAAAAAAArcAAAAAZMIW+A==\",\n" + + " \"lastModifiedLedgerSeq\": \"695\"\n" + + " }\n" + + " ],\n" + + " \"latestLedger\": \"1457\"\n" + + " }\n" + + "}"; + + String jsonEntriesNull = + "{\n" + + " \"jsonrpc\": \"2.0\",\n" + + " \"id\": 8675309,\n" + + " \"result\": {\n" + + " \"entries\": null,\n" + + " \"latestLedger\": \"1009\"\n" + + " }\n" + + "}"; +} diff --git a/src/test/java/org/stellar/sdk/responses/sorobanrpc/GetNetworkDeserializerTest.java b/src/test/java/org/stellar/sdk/responses/sorobanrpc/GetNetworkDeserializerTest.java new file mode 100644 index 000000000..5148a9534 --- /dev/null +++ b/src/test/java/org/stellar/sdk/responses/sorobanrpc/GetNetworkDeserializerTest.java @@ -0,0 +1,32 @@ +package org.stellar.sdk.responses.sorobanrpc; + +import static org.junit.Assert.assertEquals; + +import com.google.gson.reflect.TypeToken; +import org.junit.Test; +import org.stellar.sdk.responses.GsonSingleton; + +public class GetNetworkDeserializerTest { + @Test + public void testDeserialize() { + SorobanRpcResponse getLatestLedgerResponse = + GsonSingleton.getInstance() + .fromJson(json, new TypeToken>() {}.getType()); + assertEquals( + getLatestLedgerResponse.getResult().getFriendbotUrl(), "http://localhost:8000/friendbot"); + assertEquals( + getLatestLedgerResponse.getResult().getPassphrase(), "Standalone Network ; February 2017"); + assertEquals(getLatestLedgerResponse.getResult().getProtocolVersion().intValue(), 20); + } + + String json = + "{\n" + + " \"jsonrpc\": \"2.0\",\n" + + " \"id\": \"198cb1a8-9104-4446-a269-88bf000c2721\",\n" + + " \"result\": {\n" + + " \"friendbotUrl\": \"http://localhost:8000/friendbot\",\n" + + " \"passphrase\": \"Standalone Network ; February 2017\",\n" + + " \"protocolVersion\": \"20\"\n" + + " }\n" + + "}"; +} diff --git a/src/test/java/org/stellar/sdk/responses/sorobanrpc/GetTransactionDeserializerTest.java b/src/test/java/org/stellar/sdk/responses/sorobanrpc/GetTransactionDeserializerTest.java new file mode 100644 index 000000000..053f01300 --- /dev/null +++ b/src/test/java/org/stellar/sdk/responses/sorobanrpc/GetTransactionDeserializerTest.java @@ -0,0 +1,149 @@ +package org.stellar.sdk.responses.sorobanrpc; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; + +import com.google.gson.reflect.TypeToken; +import org.junit.Test; +import org.stellar.sdk.responses.GsonSingleton; + +public class GetTransactionDeserializerTest { + @Test + public void testDeserializeSuccess() { + SorobanRpcResponse getTransactionResponse = + GsonSingleton.getInstance() + .fromJson( + jsonSuccess, + new TypeToken>() {}.getType()); + assertEquals( + getTransactionResponse.getResult().getStatus(), + GetTransactionResponse.GetTransactionStatus.SUCCESS); + assertEquals(getTransactionResponse.getResult().getLatestLedger().longValue(), 79289L); + assertEquals( + getTransactionResponse.getResult().getLatestLedgerCloseTime().longValue(), 1690387240L); + assertEquals(getTransactionResponse.getResult().getOldestLedger().longValue(), 77850L); + assertEquals( + getTransactionResponse.getResult().getOldestLedgerCloseTime().longValue(), 1690379694L); + assertEquals(getTransactionResponse.getResult().getApplicationOrder().intValue(), 1); + assertEquals( + getTransactionResponse.getResult().getEnvelopeXdr(), + "AAAAAgAAAADTYKIzfa0ubKp7qjOcF+ZO8sjQutzo1iHuDh8esi9q+wABNjQAATW1AAAAAQAAAAAAAAAAAAAAAQAAAAAAAAAYAAAAAAAAAAIAAAASAAAAAb3H+V1yFoNDBpje4rchxeaR7/hRNS2CAT2Dh6A8z6xrAAAADwAAAARuYW1lAAAAAAAAAAEAAAAAAAAAAwAAAAYAAAABvcf5XXIWg0MGmN7ityHF5pHv+FE1LYIBPYOHoDzPrGsAAAAPAAAACE1FVEFEQVRBAAAAAQAAAAAAAAAGAAAAAb3H+V1yFoNDBpje4rchxeaR7/hRNS2CAT2Dh6A8z6xrAAAAFAAAAAEAAAAAAAAAB++FkDTZODW0rvolF6AuIZf4w7+GQd8RofaH8u2pM+UPAAAAAAAAAAAAUrutAAAiqAAAAAAAAADIAAAAAAAAACgAAAABsi9q+wAAAEDgHR/5rU+bsXD/oPUFodyEgXFNbDm5T2+M1GarZXy+d+tZ757PBL9ysK41F1hUYz3p5CA7Urlpe3fnNjYcu1EM"); + assertEquals( + getTransactionResponse.getResult().getResultXdr(), + "AAAAAAABNCwAAAAAAAAAAQAAAAAAAAAYAAAAAJhEDjNV0Jj46jrxCj87qJ6JaYKJN4c+p5tvapkLwrn8AAAAAA=="); + assertEquals( + getTransactionResponse.getResult().getResultMetaXdr(), + "AAAAAwAAAAAAAAACAAAAAwABNbYAAAAAAAAAANNgojN9rS5sqnuqM5wX5k7yyNC63OjWIe4OHx6yL2r7AAAAF0h1s9QAATW1AAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAQABNbYAAAAAAAAAANNgojN9rS5sqnuqM5wX5k7yyNC63OjWIe4OHx6yL2r7AAAAF0h1s9QAATW1AAAAAQAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAAAAAAAAAAAAAAAAAMAAAAAAAE1tgAAAABkwUMZAAAAAAAAAAEAAAAAAAAAAgAAAAMAATW2AAAAAAAAAADTYKIzfa0ubKp7qjOcF+ZO8sjQutzo1iHuDh8esi9q+wAAABdIdbPUAAE1tQAAAAEAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAAAAAAAAAAAAAAAAAADAAAAAAABNbYAAAAAZMFDGQAAAAAAAAABAAE1tgAAAAAAAAAA02CiM32tLmyqe6oznBfmTvLI0Lrc6NYh7g4fHrIvavsAAAAXSHWz/AABNbUAAAABAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAACAAAAAAAAAAAAAAAAAAAAAwAAAAAAATW2AAAAAGTBQxkAAAAAAAAAAQAAAAAAAAAAAAAADgAAAAZUb2tlbkEAAAAAAAIAAAABAAAAAAAAAAAAAAACAAAAAAAAAAMAAAAPAAAAB2ZuX2NhbGwAAAAADQAAACC9x/ldchaDQwaY3uK3IcXmke/4UTUtggE9g4egPM+sawAAAA8AAAAEbmFtZQAAAAEAAAABAAAAAAAAAAG9x/ldchaDQwaY3uK3IcXmke/4UTUtggE9g4egPM+sawAAAAIAAAAAAAAAAgAAAA8AAAAJZm5fcmV0dXJuAAAAAAAADwAAAARuYW1lAAAADgAAAAZUb2tlbkEAAA=="); + assertEquals(getTransactionResponse.getResult().getLedger().longValue(), 79286L); + assertEquals(getTransactionResponse.getResult().getCreatedAt().longValue(), 1690387225L); + assertNull(getTransactionResponse.getResult().getFeeBump()); + } + + @Test + public void testDeserializeFailed() { + SorobanRpcResponse getTransactionResponse = + GsonSingleton.getInstance() + .fromJson( + jsonFailed, + new TypeToken>() {}.getType()); + assertEquals( + getTransactionResponse.getResult().getStatus(), + GetTransactionResponse.GetTransactionStatus.FAILED); + assertEquals(getTransactionResponse.getResult().getLatestLedger().longValue(), 79289L); + assertEquals( + getTransactionResponse.getResult().getLatestLedgerCloseTime().longValue(), 1690387240L); + assertEquals(getTransactionResponse.getResult().getOldestLedger().longValue(), 77850L); + assertEquals( + getTransactionResponse.getResult().getOldestLedgerCloseTime().longValue(), 1690379694L); + assertEquals(getTransactionResponse.getResult().getApplicationOrder().intValue(), 1); + assertEquals( + getTransactionResponse.getResult().getEnvelopeXdr(), + "AAAAAgAAAADTYKIzfa0ubKp7qjOcF+ZO8sjQutzo1iHuDh8esi9q+wABNjQAATW1AAAAAQAAAAAAAAAAAAAAAQAAAAAAAAAYAAAAAAAAAAIAAAASAAAAAb3H+V1yFoNDBpje4rchxeaR7/hRNS2CAT2Dh6A8z6xrAAAADwAAAARuYW1lAAAAAAAAAAEAAAAAAAAAAwAAAAYAAAABvcf5XXIWg0MGmN7ityHF5pHv+FE1LYIBPYOHoDzPrGsAAAAPAAAACE1FVEFEQVRBAAAAAQAAAAAAAAAGAAAAAb3H+V1yFoNDBpje4rchxeaR7/hRNS2CAT2Dh6A8z6xrAAAAFAAAAAEAAAAAAAAAB++FkDTZODW0rvolF6AuIZf4w7+GQd8RofaH8u2pM+UPAAAAAAAAAAAAUrutAAAiqAAAAAAAAADIAAAAAAAAACgAAAABsi9q+wAAAEDgHR/5rU+bsXD/oPUFodyEgXFNbDm5T2+M1GarZXy+d+tZ757PBL9ysK41F1hUYz3p5CA7Urlpe3fnNjYcu1EM"); + assertEquals( + getTransactionResponse.getResult().getResultXdr(), + "AAAAAAABNCwAAAAAAAAAAQAAAAAAAAAYAAAAAJhEDjNV0Jj46jrxCj87qJ6JaYKJN4c+p5tvapkLwrn8AAAAAA=="); + assertEquals( + getTransactionResponse.getResult().getResultMetaXdr(), + "AAAAAwAAAAAAAAACAAAAAwABNbYAAAAAAAAAANNgojN9rS5sqnuqM5wX5k7yyNC63OjWIe4OHx6yL2r7AAAAF0h1s9QAATW1AAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAQABNbYAAAAAAAAAANNgojN9rS5sqnuqM5wX5k7yyNC63OjWIe4OHx6yL2r7AAAAF0h1s9QAATW1AAAAAQAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAAAAAAAAAAAAAAAAAMAAAAAAAE1tgAAAABkwUMZAAAAAAAAAAEAAAAAAAAAAgAAAAMAATW2AAAAAAAAAADTYKIzfa0ubKp7qjOcF+ZO8sjQutzo1iHuDh8esi9q+wAAABdIdbPUAAE1tQAAAAEAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAAAAAAAAAAAAAAAAAADAAAAAAABNbYAAAAAZMFDGQAAAAAAAAABAAE1tgAAAAAAAAAA02CiM32tLmyqe6oznBfmTvLI0Lrc6NYh7g4fHrIvavsAAAAXSHWz/AABNbUAAAABAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAACAAAAAAAAAAAAAAAAAAAAAwAAAAAAATW2AAAAAGTBQxkAAAAAAAAAAQAAAAAAAAAAAAAADgAAAAZUb2tlbkEAAAAAAAIAAAABAAAAAAAAAAAAAAACAAAAAAAAAAMAAAAPAAAAB2ZuX2NhbGwAAAAADQAAACC9x/ldchaDQwaY3uK3IcXmke/4UTUtggE9g4egPM+sawAAAA8AAAAEbmFtZQAAAAEAAAABAAAAAAAAAAG9x/ldchaDQwaY3uK3IcXmke/4UTUtggE9g4egPM+sawAAAAIAAAAAAAAAAgAAAA8AAAAJZm5fcmV0dXJuAAAAAAAADwAAAARuYW1lAAAADgAAAAZUb2tlbkEAAA=="); + assertEquals(getTransactionResponse.getResult().getLedger().longValue(), 79286L); + assertEquals(getTransactionResponse.getResult().getCreatedAt().longValue(), 1690387225L); + assertNull(getTransactionResponse.getResult().getFeeBump()); + } + + @Test + public void testDeserializeNotFound() { + SorobanRpcResponse getTransactionResponse = + GsonSingleton.getInstance() + .fromJson( + jsonNotFound, + new TypeToken>() {}.getType()); + assertEquals( + getTransactionResponse.getResult().getStatus(), + GetTransactionResponse.GetTransactionStatus.NOT_FOUND); + assertEquals(getTransactionResponse.getResult().getLatestLedger().longValue(), 79285L); + assertEquals( + getTransactionResponse.getResult().getLatestLedgerCloseTime().longValue(), 1690387220L); + assertEquals(getTransactionResponse.getResult().getOldestLedger().longValue(), 77846L); + assertEquals( + getTransactionResponse.getResult().getOldestLedgerCloseTime().longValue(), 1690379672L); + + assertNull(getTransactionResponse.getResult().getApplicationOrder()); + assertNull(getTransactionResponse.getResult().getEnvelopeXdr()); + assertNull(getTransactionResponse.getResult().getResultXdr()); + assertNull(getTransactionResponse.getResult().getResultMetaXdr()); + assertNull(getTransactionResponse.getResult().getLedger()); + assertNull(getTransactionResponse.getResult().getCreatedAt()); + assertNull(getTransactionResponse.getResult().getFeeBump()); + } + + String jsonSuccess = + "{\n" + + " \"jsonrpc\": \"2.0\",\n" + + " \"id\": \"198cb1a8-9104-4446-a269-88bf000c2721\",\n" + + " \"result\": {\n" + + " \"status\": \"SUCCESS\",\n" + + " \"latestLedger\": \"79289\",\n" + + " \"latestLedgerCloseTime\": \"1690387240\",\n" + + " \"oldestLedger\": \"77850\",\n" + + " \"oldestLedgerCloseTime\": \"1690379694\",\n" + + " \"applicationOrder\": 1,\n" + + " \"envelopeXdr\": \"AAAAAgAAAADTYKIzfa0ubKp7qjOcF+ZO8sjQutzo1iHuDh8esi9q+wABNjQAATW1AAAAAQAAAAAAAAAAAAAAAQAAAAAAAAAYAAAAAAAAAAIAAAASAAAAAb3H+V1yFoNDBpje4rchxeaR7/hRNS2CAT2Dh6A8z6xrAAAADwAAAARuYW1lAAAAAAAAAAEAAAAAAAAAAwAAAAYAAAABvcf5XXIWg0MGmN7ityHF5pHv+FE1LYIBPYOHoDzPrGsAAAAPAAAACE1FVEFEQVRBAAAAAQAAAAAAAAAGAAAAAb3H+V1yFoNDBpje4rchxeaR7/hRNS2CAT2Dh6A8z6xrAAAAFAAAAAEAAAAAAAAAB++FkDTZODW0rvolF6AuIZf4w7+GQd8RofaH8u2pM+UPAAAAAAAAAAAAUrutAAAiqAAAAAAAAADIAAAAAAAAACgAAAABsi9q+wAAAEDgHR/5rU+bsXD/oPUFodyEgXFNbDm5T2+M1GarZXy+d+tZ757PBL9ysK41F1hUYz3p5CA7Urlpe3fnNjYcu1EM\",\n" + + " \"resultXdr\": \"AAAAAAABNCwAAAAAAAAAAQAAAAAAAAAYAAAAAJhEDjNV0Jj46jrxCj87qJ6JaYKJN4c+p5tvapkLwrn8AAAAAA==\",\n" + + " \"resultMetaXdr\": \"AAAAAwAAAAAAAAACAAAAAwABNbYAAAAAAAAAANNgojN9rS5sqnuqM5wX5k7yyNC63OjWIe4OHx6yL2r7AAAAF0h1s9QAATW1AAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAQABNbYAAAAAAAAAANNgojN9rS5sqnuqM5wX5k7yyNC63OjWIe4OHx6yL2r7AAAAF0h1s9QAATW1AAAAAQAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAAAAAAAAAAAAAAAAAMAAAAAAAE1tgAAAABkwUMZAAAAAAAAAAEAAAAAAAAAAgAAAAMAATW2AAAAAAAAAADTYKIzfa0ubKp7qjOcF+ZO8sjQutzo1iHuDh8esi9q+wAAABdIdbPUAAE1tQAAAAEAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAAAAAAAAAAAAAAAAAADAAAAAAABNbYAAAAAZMFDGQAAAAAAAAABAAE1tgAAAAAAAAAA02CiM32tLmyqe6oznBfmTvLI0Lrc6NYh7g4fHrIvavsAAAAXSHWz/AABNbUAAAABAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAACAAAAAAAAAAAAAAAAAAAAAwAAAAAAATW2AAAAAGTBQxkAAAAAAAAAAQAAAAAAAAAAAAAADgAAAAZUb2tlbkEAAAAAAAIAAAABAAAAAAAAAAAAAAACAAAAAAAAAAMAAAAPAAAAB2ZuX2NhbGwAAAAADQAAACC9x/ldchaDQwaY3uK3IcXmke/4UTUtggE9g4egPM+sawAAAA8AAAAEbmFtZQAAAAEAAAABAAAAAAAAAAG9x/ldchaDQwaY3uK3IcXmke/4UTUtggE9g4egPM+sawAAAAIAAAAAAAAAAgAAAA8AAAAJZm5fcmV0dXJuAAAAAAAADwAAAARuYW1lAAAADgAAAAZUb2tlbkEAAA==\",\n" + + " \"ledger\": \"79286\",\n" + + " \"createdAt\": \"1690387225\"\n" + + " }\n" + + "}"; + + String jsonNotFound = + "{\n" + + " \"jsonrpc\": \"2.0\",\n" + + " \"id\": \"198cb1a8-9104-4446-a269-88bf000c2721\",\n" + + " \"result\": {\n" + + " \"status\": \"NOT_FOUND\",\n" + + " \"latestLedger\": \"79285\",\n" + + " \"latestLedgerCloseTime\": \"1690387220\",\n" + + " \"oldestLedger\": \"77846\",\n" + + " \"oldestLedgerCloseTime\": \"1690379672\"\n" + + " }\n" + + "}"; + + String jsonFailed = + "{\n" + + " \"jsonrpc\": \"2.0\",\n" + + " \"id\": \"198cb1a8-9104-4446-a269-88bf000c2721\",\n" + + " \"result\": {\n" + + " \"status\": \"FAILED\",\n" + + " \"latestLedger\": \"79289\",\n" + + " \"latestLedgerCloseTime\": \"1690387240\",\n" + + " \"oldestLedger\": \"77850\",\n" + + " \"oldestLedgerCloseTime\": \"1690379694\",\n" + + " \"applicationOrder\": 1,\n" + + " \"envelopeXdr\": \"AAAAAgAAAADTYKIzfa0ubKp7qjOcF+ZO8sjQutzo1iHuDh8esi9q+wABNjQAATW1AAAAAQAAAAAAAAAAAAAAAQAAAAAAAAAYAAAAAAAAAAIAAAASAAAAAb3H+V1yFoNDBpje4rchxeaR7/hRNS2CAT2Dh6A8z6xrAAAADwAAAARuYW1lAAAAAAAAAAEAAAAAAAAAAwAAAAYAAAABvcf5XXIWg0MGmN7ityHF5pHv+FE1LYIBPYOHoDzPrGsAAAAPAAAACE1FVEFEQVRBAAAAAQAAAAAAAAAGAAAAAb3H+V1yFoNDBpje4rchxeaR7/hRNS2CAT2Dh6A8z6xrAAAAFAAAAAEAAAAAAAAAB++FkDTZODW0rvolF6AuIZf4w7+GQd8RofaH8u2pM+UPAAAAAAAAAAAAUrutAAAiqAAAAAAAAADIAAAAAAAAACgAAAABsi9q+wAAAEDgHR/5rU+bsXD/oPUFodyEgXFNbDm5T2+M1GarZXy+d+tZ757PBL9ysK41F1hUYz3p5CA7Urlpe3fnNjYcu1EM\",\n" + + " \"resultXdr\": \"AAAAAAABNCwAAAAAAAAAAQAAAAAAAAAYAAAAAJhEDjNV0Jj46jrxCj87qJ6JaYKJN4c+p5tvapkLwrn8AAAAAA==\",\n" + + " \"resultMetaXdr\": \"AAAAAwAAAAAAAAACAAAAAwABNbYAAAAAAAAAANNgojN9rS5sqnuqM5wX5k7yyNC63OjWIe4OHx6yL2r7AAAAF0h1s9QAATW1AAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAQABNbYAAAAAAAAAANNgojN9rS5sqnuqM5wX5k7yyNC63OjWIe4OHx6yL2r7AAAAF0h1s9QAATW1AAAAAQAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAAAAAAAAAAAAAAAAAMAAAAAAAE1tgAAAABkwUMZAAAAAAAAAAEAAAAAAAAAAgAAAAMAATW2AAAAAAAAAADTYKIzfa0ubKp7qjOcF+ZO8sjQutzo1iHuDh8esi9q+wAAABdIdbPUAAE1tQAAAAEAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAAAAAAAAAAAAAAAAAADAAAAAAABNbYAAAAAZMFDGQAAAAAAAAABAAE1tgAAAAAAAAAA02CiM32tLmyqe6oznBfmTvLI0Lrc6NYh7g4fHrIvavsAAAAXSHWz/AABNbUAAAABAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAACAAAAAAAAAAAAAAAAAAAAAwAAAAAAATW2AAAAAGTBQxkAAAAAAAAAAQAAAAAAAAAAAAAADgAAAAZUb2tlbkEAAAAAAAIAAAABAAAAAAAAAAAAAAACAAAAAAAAAAMAAAAPAAAAB2ZuX2NhbGwAAAAADQAAACC9x/ldchaDQwaY3uK3IcXmke/4UTUtggE9g4egPM+sawAAAA8AAAAEbmFtZQAAAAEAAAABAAAAAAAAAAG9x/ldchaDQwaY3uK3IcXmke/4UTUtggE9g4egPM+sawAAAAIAAAAAAAAAAgAAAA8AAAAJZm5fcmV0dXJuAAAAAAAADwAAAARuYW1lAAAADgAAAAZUb2tlbkEAAA==\",\n" + + " \"ledger\": \"79286\",\n" + + " \"createdAt\": \"1690387225\"\n" + + " }\n" + + "}"; +} diff --git a/src/test/java/org/stellar/sdk/responses/sorobanrpc/SendTransactionDeserializerTest.java b/src/test/java/org/stellar/sdk/responses/sorobanrpc/SendTransactionDeserializerTest.java new file mode 100644 index 000000000..decdeed5d --- /dev/null +++ b/src/test/java/org/stellar/sdk/responses/sorobanrpc/SendTransactionDeserializerTest.java @@ -0,0 +1,123 @@ +package org.stellar.sdk.responses.sorobanrpc; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; + +import com.google.gson.reflect.TypeToken; +import org.junit.Test; +import org.stellar.sdk.responses.GsonSingleton; + +public class SendTransactionDeserializerTest { + @Test + public void testDeserializeError() { + SorobanRpcResponse getTransactionResponse = + GsonSingleton.getInstance() + .fromJson( + jsonError, + new TypeToken>() {}.getType()); + SendTransactionResponse data = getTransactionResponse.getResult(); + assertEquals(data.getErrorResultXdr(), "AAAAAAAAf67////6AAAAAA=="); + assertEquals(data.getStatus(), SendTransactionResponse.SendTransactionStatus.ERROR); + assertEquals( + data.getHash(), "3b0c982bb8245be869d34ec822f999deb68f3a8480cf6e663643cf2f6e397e64"); + assertEquals(data.getLatestLedger().longValue(), 62L); + assertEquals(data.getLatestLedgerCloseTime().longValue(), 1690447331L); + } + + @Test + public void testDeserializePending() { + SorobanRpcResponse getTransactionResponse = + GsonSingleton.getInstance() + .fromJson( + jsonPending, + new TypeToken>() {}.getType()); + SendTransactionResponse data = getTransactionResponse.getResult(); + assertNull(data.getErrorResultXdr()); + assertEquals(data.getStatus(), SendTransactionResponse.SendTransactionStatus.PENDING); + assertEquals( + data.getHash(), "5e58bb3530cf4ff852805ad1a5077b181b227e541301bdfa17f5a66991910d13"); + assertEquals(data.getLatestLedger().longValue(), 3449L); + assertEquals(data.getLatestLedgerCloseTime().longValue(), 1690444223L); + } + + @Test + public void testDeserializeDuplicate() { + SorobanRpcResponse getTransactionResponse = + GsonSingleton.getInstance() + .fromJson( + jsonDuplicate, + new TypeToken>() {}.getType()); + SendTransactionResponse data = getTransactionResponse.getResult(); + assertNull(data.getErrorResultXdr()); + assertEquals(data.getStatus(), SendTransactionResponse.SendTransactionStatus.DUPLICATE); + assertEquals( + data.getHash(), "5e58bb3530cf4ff852805ad1a5077b181b227e541301bdfa17f5a66991910d13"); + assertEquals(data.getLatestLedger().longValue(), 3449L); + assertEquals(data.getLatestLedgerCloseTime().longValue(), 1690444223L); + } + + @Test + public void testDeserializeTryAgainLater() { + SorobanRpcResponse getTransactionResponse = + GsonSingleton.getInstance() + .fromJson( + jsonTryAgainLater, + new TypeToken>() {}.getType()); + SendTransactionResponse data = getTransactionResponse.getResult(); + assertNull(data.getErrorResultXdr()); + assertEquals(data.getStatus(), SendTransactionResponse.SendTransactionStatus.TRY_AGAIN_LATER); + assertEquals( + data.getHash(), "5e58bb3530cf4ff852805ad1a5077b181b227e541301bdfa17f5a66991910d13"); + assertEquals(data.getLatestLedger().longValue(), 3449L); + assertEquals(data.getLatestLedgerCloseTime().longValue(), 1690444223L); + } + + String jsonError = + "{\n" + + " \"jsonrpc\": \"2.0\",\n" + + " \"id\": \"b96311af98d54d7cbb8736dbb0ed7730\",\n" + + " \"result\": {\n" + + " \"errorResultXdr\": \"AAAAAAAAf67////6AAAAAA==\",\n" + + " \"status\": \"ERROR\",\n" + + " \"hash\": \"3b0c982bb8245be869d34ec822f999deb68f3a8480cf6e663643cf2f6e397e64\",\n" + + " \"latestLedger\": \"62\",\n" + + " \"latestLedgerCloseTime\": \"1690447331\"\n" + + " }\n" + + "}"; + + String jsonPending = + "{\n" + + " \"jsonrpc\": \"2.0\",\n" + + " \"id\": \"ce651a0633e8407e9f377127bd649476\",\n" + + " \"result\": {\n" + + " \"status\": \"PENDING\",\n" + + " \"hash\": \"5e58bb3530cf4ff852805ad1a5077b181b227e541301bdfa17f5a66991910d13\",\n" + + " \"latestLedger\": \"3449\",\n" + + " \"latestLedgerCloseTime\": \"1690444223\"\n" + + " }\n" + + "}"; + + String jsonDuplicate = + "{\n" + + " \"jsonrpc\": \"2.0\",\n" + + " \"id\": \"ce651a0633e8407e9f377127bd649476\",\n" + + " \"result\": {\n" + + " \"status\": \"DUPLICATE\",\n" + + " \"hash\": \"5e58bb3530cf4ff852805ad1a5077b181b227e541301bdfa17f5a66991910d13\",\n" + + " \"latestLedger\": \"3449\",\n" + + " \"latestLedgerCloseTime\": \"1690444223\"\n" + + " }\n" + + "}"; + + String jsonTryAgainLater = + "{\n" + + " \"jsonrpc\": \"2.0\",\n" + + " \"id\": \"ce651a0633e8407e9f377127bd649476\",\n" + + " \"result\": {\n" + + " \"status\": \"TRY_AGAIN_LATER\",\n" + + " \"hash\": \"5e58bb3530cf4ff852805ad1a5077b181b227e541301bdfa17f5a66991910d13\",\n" + + " \"latestLedger\": \"3449\",\n" + + " \"latestLedgerCloseTime\": \"1690444223\"\n" + + " }\n" + + "}"; +} diff --git a/src/test/java/org/stellar/sdk/responses/sorobanrpc/SimulateTransactionDeserializerTest.java b/src/test/java/org/stellar/sdk/responses/sorobanrpc/SimulateTransactionDeserializerTest.java new file mode 100644 index 000000000..2da5249e1 --- /dev/null +++ b/src/test/java/org/stellar/sdk/responses/sorobanrpc/SimulateTransactionDeserializerTest.java @@ -0,0 +1,104 @@ +package org.stellar.sdk.responses.sorobanrpc; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; + +import com.google.gson.reflect.TypeToken; +import org.junit.Test; +import org.stellar.sdk.responses.GsonSingleton; + +public class SimulateTransactionDeserializerTest { + @Test + public void testDeserializeSuccess() { + SorobanRpcResponse simulateTransactionResponse = + GsonSingleton.getInstance() + .fromJson( + jsonSuccess, + new TypeToken>() {}.getType()); + SimulateTransactionResponse data = simulateTransactionResponse.getResult(); + assertEquals( + data.getTransactionData(), + "AAAAAAAAAAMAAAAAAAAAAFi3xKLI8peqjz0kcSgf38zsr+SOVmMxPsGOEqc+ypihAAAABgAAAAHFiyv7xPBU5zJPa/IMrT4CbkG7rRptIMPX1JGN7RZUEQAAABQAAAABAAAAAAAAAAd9NB8oNB2RvkInt877MS77oKzbRandwCJ7VsvW4NmL2QAAAAAAAAACAAAABgAAAAAAAAAAWLfEosjyl6qPPSRxKB/fzOyv5I5WYzE+wY4Spz7KmKEAAAAVWqB4d0kcwg0AAAAAAAAAAAAAAAYAAAABxYsr+8TwVOcyT2vyDK0+Am5Bu60abSDD19SRje0WVBEAAAAQAAAAAQAAAAIAAAAPAAAAB0NvdW50ZXIAAAAAEgAAAAAAAAAAWLfEosjyl6qPPSRxKB/fzOyv5I5WYzE+wY4Spz7KmKEAAAABAAAAAAAWW9sAAAYkAAABnAAAA2AAAAAAAAAAqQ=="); + assertEquals(data.getEvents().size(), 2); + assertEquals( + data.getEvents().get(0), + "AAAAAQAAAAAAAAAAAAAAAgAAAAAAAAADAAAADwAAAAdmbl9jYWxsAAAAAA0AAAAgxYsr+8TwVOcyT2vyDK0+Am5Bu60abSDD19SRje0WVBEAAAAPAAAACWluY3JlbWVudAAAAAAAABAAAAABAAAAAgAAABIAAAAAAAAAAFi3xKLI8peqjz0kcSgf38zsr+SOVmMxPsGOEqc+ypihAAAAAwAAAAo="); + assertEquals( + data.getEvents().get(1), + "AAAAAQAAAAAAAAABxYsr+8TwVOcyT2vyDK0+Am5Bu60abSDD19SRje0WVBEAAAACAAAAAAAAAAIAAAAPAAAACWZuX3JldHVybgAAAAAAAA8AAAAJaW5jcmVtZW50AAAAAAAAAwAAAAo="); + assertEquals(data.getMinResourceFee().longValue(), 94876L); + assertEquals(data.getResults().size(), 1); + assertEquals(data.getResults().get(0).getAuth().size(), 1); + assertEquals( + data.getResults().get(0).getAuth().get(0), + "AAAAAQAAAAAAAAAAWLfEosjyl6qPPSRxKB/fzOyv5I5WYzE+wY4Spz7KmKFaoHh3SRzCDQAAAAAAAAAAAAAAAAAAAAHFiyv7xPBU5zJPa/IMrT4CbkG7rRptIMPX1JGN7RZUEQAAAAlpbmNyZW1lbnQAAAAAAAACAAAAEgAAAAAAAAAAWLfEosjyl6qPPSRxKB/fzOyv5I5WYzE+wY4Spz7KmKEAAAADAAAACgAAAAA="); + assertEquals(data.getResults().get(0).getXdr(), "AAAAAwAAAAo="); + assertEquals(data.getCost().getCpuInstructions().longValue(), 1274180L); + assertEquals(data.getCost().getMemoryBytes().longValue(), 162857L); + assertEquals(data.getLatestLedger().longValue(), 694L); + assertNull(data.getError()); + } + + @Test + public void testDeserializeFailed() { + SorobanRpcResponse simulateTransactionResponse = + GsonSingleton.getInstance() + .fromJson( + jsonFailed, + new TypeToken>() {}.getType()); + SimulateTransactionResponse data = simulateTransactionResponse.getResult(); + assertEquals( + data.getError(), + "HostError: Error(WasmVm, MissingValue)\n\nEvent log (newest first):\n 0: [Diagnostic Event] contract:607682f2477a6be8cdf0fdf32be13d5f25a686cc094fd93d5aa3d7b68232d0c0, topics:[error, Error(WasmVm, MissingValue)], data:[\"invoking unknown export\", increment]\n 1: [Diagnostic Event] topics:[fn_call, Bytes(607682f2477a6be8cdf0fdf32be13d5f25a686cc094fd93d5aa3d7b68232d0c0), increment], data:[Address(Account(58b7c4a2c8f297aa8f3d2471281fdfccecafe48e5663313ec18e12a73eca98a1)), 10]\n\nBacktrace (newest first):\n 0: soroban_env_host::vm::Vm::invoke_function_raw\n 1: soroban_env_host::host::frame::::call_n_internal\n 2: soroban_env_host::host::frame::::invoke_function\n 3: preflight::preflight_invoke_hf_op::{{closure}}\n 4: preflight::catch_preflight_panic\n 5: _cgo_a3255893d7fd_Cfunc_preflight_invoke_hf_op\n at /tmp/go-build/cgo-gcc-prolog:99:11\n 6: runtime.asmcgocall\n at ./runtime/asm_amd64.s:848\n\n"); + assertEquals(data.getTransactionData(), ""); + assertNull(data.getEvents()); + assertEquals(data.getMinResourceFee().longValue(), 0L); + assertEquals(data.getCost().getCpuInstructions().longValue(), 0L); + assertEquals(data.getCost().getMemoryBytes().longValue(), 0L); + assertEquals(data.getLatestLedger().longValue(), 898L); + } + + String jsonSuccess = + "{\n" + + " \"jsonrpc\": \"2.0\",\n" + + " \"id\": \"4a69486e5a83404bbf9772f3f02c21e5\",\n" + + " \"result\": {\n" + + " \"transactionData\": \"AAAAAAAAAAMAAAAAAAAAAFi3xKLI8peqjz0kcSgf38zsr+SOVmMxPsGOEqc+ypihAAAABgAAAAHFiyv7xPBU5zJPa/IMrT4CbkG7rRptIMPX1JGN7RZUEQAAABQAAAABAAAAAAAAAAd9NB8oNB2RvkInt877MS77oKzbRandwCJ7VsvW4NmL2QAAAAAAAAACAAAABgAAAAAAAAAAWLfEosjyl6qPPSRxKB/fzOyv5I5WYzE+wY4Spz7KmKEAAAAVWqB4d0kcwg0AAAAAAAAAAAAAAAYAAAABxYsr+8TwVOcyT2vyDK0+Am5Bu60abSDD19SRje0WVBEAAAAQAAAAAQAAAAIAAAAPAAAAB0NvdW50ZXIAAAAAEgAAAAAAAAAAWLfEosjyl6qPPSRxKB/fzOyv5I5WYzE+wY4Spz7KmKEAAAABAAAAAAAWW9sAAAYkAAABnAAAA2AAAAAAAAAAqQ==\",\n" + + " \"events\": [\n" + + " \"AAAAAQAAAAAAAAAAAAAAAgAAAAAAAAADAAAADwAAAAdmbl9jYWxsAAAAAA0AAAAgxYsr+8TwVOcyT2vyDK0+Am5Bu60abSDD19SRje0WVBEAAAAPAAAACWluY3JlbWVudAAAAAAAABAAAAABAAAAAgAAABIAAAAAAAAAAFi3xKLI8peqjz0kcSgf38zsr+SOVmMxPsGOEqc+ypihAAAAAwAAAAo=\",\n" + + " \"AAAAAQAAAAAAAAABxYsr+8TwVOcyT2vyDK0+Am5Bu60abSDD19SRje0WVBEAAAACAAAAAAAAAAIAAAAPAAAACWZuX3JldHVybgAAAAAAAA8AAAAJaW5jcmVtZW50AAAAAAAAAwAAAAo=\"\n" + + " ],\n" + + " \"minResourceFee\": \"94876\",\n" + + " \"results\": [\n" + + " {\n" + + " \"auth\": [\n" + + " \"AAAAAQAAAAAAAAAAWLfEosjyl6qPPSRxKB/fzOyv5I5WYzE+wY4Spz7KmKFaoHh3SRzCDQAAAAAAAAAAAAAAAAAAAAHFiyv7xPBU5zJPa/IMrT4CbkG7rRptIMPX1JGN7RZUEQAAAAlpbmNyZW1lbnQAAAAAAAACAAAAEgAAAAAAAAAAWLfEosjyl6qPPSRxKB/fzOyv5I5WYzE+wY4Spz7KmKEAAAADAAAACgAAAAA=\"\n" + + " ],\n" + + " \"xdr\": \"AAAAAwAAAAo=\"\n" + + " }\n" + + " ],\n" + + " \"cost\": {\n" + + " \"cpuInsns\": \"1274180\",\n" + + " \"memBytes\": \"162857\"\n" + + " },\n" + + " \"latestLedger\": \"694\"\n" + + " }\n" + + "}"; + + String jsonFailed = + "{\n" + + " \"jsonrpc\": \"2.0\",\n" + + " \"id\": \"7b6ada2bdec04ee28147d1557aadc3cf\",\n" + + " \"result\": {\n" + + " \"error\": \"HostError: Error(WasmVm, MissingValue)\\n\\nEvent log (newest first):\\n 0: [Diagnostic Event] contract:607682f2477a6be8cdf0fdf32be13d5f25a686cc094fd93d5aa3d7b68232d0c0, topics:[error, Error(WasmVm, MissingValue)], data:[\\\"invoking unknown export\\\", increment]\\n 1: [Diagnostic Event] topics:[fn_call, Bytes(607682f2477a6be8cdf0fdf32be13d5f25a686cc094fd93d5aa3d7b68232d0c0), increment], data:[Address(Account(58b7c4a2c8f297aa8f3d2471281fdfccecafe48e5663313ec18e12a73eca98a1)), 10]\\n\\nBacktrace (newest first):\\n 0: soroban_env_host::vm::Vm::invoke_function_raw\\n 1: soroban_env_host::host::frame::::call_n_internal\\n 2: soroban_env_host::host::frame::::invoke_function\\n 3: preflight::preflight_invoke_hf_op::{{closure}}\\n 4: preflight::catch_preflight_panic\\n 5: _cgo_a3255893d7fd_Cfunc_preflight_invoke_hf_op\\n at /tmp/go-build/cgo-gcc-prolog:99:11\\n 6: runtime.asmcgocall\\n at ./runtime/asm_amd64.s:848\\n\\n\",\n" + + " \"transactionData\": \"\",\n" + + " \"events\": null,\n" + + " \"minResourceFee\": \"0\",\n" + + " \"cost\": {\n" + + " \"cpuInsns\": \"0\",\n" + + " \"memBytes\": \"0\"\n" + + " },\n" + + " \"latestLedger\": \"898\"\n" + + " }\n" + + "}"; +} diff --git a/src/test/java/org/stellar/sdk/responses/sorobanrpc/SorobanRpcDeserializerTest.java b/src/test/java/org/stellar/sdk/responses/sorobanrpc/SorobanRpcDeserializerTest.java new file mode 100644 index 000000000..cd85ec4b0 --- /dev/null +++ b/src/test/java/org/stellar/sdk/responses/sorobanrpc/SorobanRpcDeserializerTest.java @@ -0,0 +1,56 @@ +package org.stellar.sdk.responses.sorobanrpc; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; + +import com.google.gson.reflect.TypeToken; +import org.junit.Test; +import org.stellar.sdk.responses.GsonSingleton; + +public class SorobanRpcDeserializerTest { + @Test + public void testDeserializeSuccess() { + SorobanRpcResponse sorobanRpcResponse = + GsonSingleton.getInstance() + .fromJson( + jsonSuccess, new TypeToken>() {}.getType()); + assertEquals(sorobanRpcResponse.getJsonRpc(), "2.0"); + assertEquals(sorobanRpcResponse.getId(), "198cb1a8-9104-4446-a269-88bf000c2721"); + assertNotNull(sorobanRpcResponse.getResult()); + assertNull(sorobanRpcResponse.getError()); + } + + @Test + public void testDeserializeError() { + SorobanRpcResponse sorobanRpcResponse = + GsonSingleton.getInstance() + .fromJson( + jsonError, new TypeToken>() {}.getType()); + assertEquals(sorobanRpcResponse.getJsonRpc(), "2.0"); + assertEquals(sorobanRpcResponse.getId(), "e860b82a-1738-4646-a999-b97ea3e117eb"); + assertNull(sorobanRpcResponse.getResult()); + assertNotNull(sorobanRpcResponse.getError()); + assertEquals(sorobanRpcResponse.getError().getCode().longValue(), -32602); + assertEquals(sorobanRpcResponse.getError().getMessage(), "startLedger must be positive"); + } + + String jsonSuccess = + "{\n" + + " \"jsonrpc\": \"2.0\",\n" + + " \"id\": \"198cb1a8-9104-4446-a269-88bf000c2721\",\n" + + " \"result\": {\n" + + " \"status\": \"healthy\"\n" + + " }\n" + + "}"; + + String jsonError = + "{\n" + + " \"jsonrpc\": \"2.0\",\n" + + " \"id\": \"e860b82a-1738-4646-a999-b97ea3e117eb\",\n" + + " \"error\": {\n" + + " \"code\": -32602,\n" + + " \"message\": \"startLedger must be positive\"\n" + + " }\n" + + "}"; +}