diff --git a/api-schema/src/main/java/org/stellar/anchor/api/sep/sep38/Sep38Context.java b/api-schema/src/main/java/org/stellar/anchor/api/sep/sep38/Sep38Context.java index 586dadc419..4b6e549248 100644 --- a/api-schema/src/main/java/org/stellar/anchor/api/sep/sep38/Sep38Context.java +++ b/api-schema/src/main/java/org/stellar/anchor/api/sep/sep38/Sep38Context.java @@ -6,6 +6,9 @@ public enum Sep38Context { @SerializedName("sep6") SEP6("sep6"), + @SerializedName("sep24") + SEP24("sep24"), + @SerializedName("sep31") SEP31("sep31"); diff --git a/core/src/main/java/org/stellar/anchor/sep24/Sep24Service.java b/core/src/main/java/org/stellar/anchor/sep24/Sep24Service.java index 75bad28e91..1592e23a3c 100644 --- a/core/src/main/java/org/stellar/anchor/sep24/Sep24Service.java +++ b/core/src/main/java/org/stellar/anchor/sep24/Sep24Service.java @@ -7,7 +7,7 @@ import static org.stellar.anchor.api.sep.sep24.InfoResponse.FeeResponse; import static org.stellar.anchor.event.EventService.EventQueue.TRANSACTION; import static org.stellar.anchor.sep24.Sep24Helper.fromTxn; -import static org.stellar.anchor.sep24.Sep24Transaction.Kind.WITHDRAWAL; +import static org.stellar.anchor.sep24.Sep24Transaction.Kind.*; import static org.stellar.anchor.util.Log.debug; import static org.stellar.anchor.util.Log.debugF; import static org.stellar.anchor.util.Log.info; @@ -27,18 +27,9 @@ import java.net.MalformedURLException; import java.net.URISyntaxException; import java.time.Instant; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Locale; -import java.util.Map; -import java.util.UUID; +import java.util.*; import org.stellar.anchor.api.event.AnchorEvent; -import org.stellar.anchor.api.exception.AnchorException; -import org.stellar.anchor.api.exception.SepException; -import org.stellar.anchor.api.exception.SepNotAuthorizedException; -import org.stellar.anchor.api.exception.SepNotFoundException; -import org.stellar.anchor.api.exception.SepValidationException; +import org.stellar.anchor.api.exception.*; import org.stellar.anchor.api.sep.AssetInfo; import org.stellar.anchor.api.sep.sep24.GetTransactionRequest; import org.stellar.anchor.api.sep.sep24.GetTransactionsRequest; @@ -55,6 +46,8 @@ import org.stellar.anchor.config.CustodyConfig; import org.stellar.anchor.config.Sep24Config; import org.stellar.anchor.event.EventService; +import org.stellar.anchor.sep38.Sep38Quote; +import org.stellar.anchor.sep6.ExchangeAmountsCalculator; import org.stellar.anchor.util.ConfigHelper; import org.stellar.anchor.util.CustodyUtils; import org.stellar.anchor.util.MetricConstants; @@ -74,6 +67,7 @@ public class Sep24Service { final InteractiveUrlConstructor interactiveUrlConstructor; final MoreInfoUrlConstructor moreInfoUrlConstructor; final CustodyConfig custodyConfig; + final ExchangeAmountsCalculator exchangeAmountsCalculator; final Counter sep24TransactionRequestedCounter = counter(MetricConstants.SEP24_TRANSACTION_REQUESTED); @@ -103,7 +97,8 @@ public Sep24Service( EventService eventService, InteractiveUrlConstructor interactiveUrlConstructor, MoreInfoUrlConstructor moreInfoUrlConstructor, - CustodyConfig custodyConfig) { + CustodyConfig custodyConfig, + ExchangeAmountsCalculator exchangeAmountsCalculator) { debug("appConfig:", appConfig); debug("sep24Config:", sep24Config); this.appConfig = appConfig; @@ -116,6 +111,7 @@ public Sep24Service( this.interactiveUrlConstructor = interactiveUrlConstructor; this.moreInfoUrlConstructor = moreInfoUrlConstructor; this.custodyConfig = custodyConfig; + this.exchangeAmountsCalculator = exchangeAmountsCalculator; info("Sep24Service initialized."); } @@ -250,6 +246,14 @@ public InteractiveTransactionResponse withdraw( builder.refundMemoType(memoTypeString(memoType(refundMemo))); } + String quoteId = withdrawRequest.get("quote_id"); + if (quoteId != null) { + System.out.println(asset.getSep38AssetName() + 1231231); + AssetInfo buyAsset = assetService.getAssetByName(withdrawRequest.get("destination_asset")); + this.validatedAndPopulateQuote( + quoteId, asset, buyAsset, strAmount, builder, WITHDRAWAL.toString(), txnId); + } + Sep24Transaction txn = builder.build(); txnStore.save(txn); @@ -340,7 +344,7 @@ public InteractiveTransactionResponse deposit(Sep10Jwt token, Map() + every { txnStore.save(capture(slotTxn)) } returns null + every { sep38QuoteStore.findByQuoteId(any()) } returns withdrawQuote + sep24Service.withdraw( + createTestSep10JwtWithMemo(), + createTestTransactionRequest(withdrawQuote.id) + ) + assertEquals(withdrawQuote.id, slotTxn.captured.quoteId) + assertEquals(withdrawQuote.buyAsset, slotTxn.captured.destinationAsset) + } + @Test fun `test withdrawal with no token and no request failure`() { assertThrows { @@ -245,6 +307,13 @@ internal class Sep24ServiceTest { token.sub = "G1234" sep24Service.withdraw(token, request) } + + assertThrows { + val request = createTestTransactionRequest("bad-quote-id") + val token = createTestSep10JwtToken() + every { sep38QuoteStore.findByQuoteId(any()) } returns null + sep24Service.withdraw(token, request) + } } @ParameterizedTest @@ -310,6 +379,19 @@ internal class Sep24ServiceTest { assertEquals(TEST_CLIENT_DOMAIN, slotTxn.captured.clientDomain) } + @Test + fun `test deposit with quote_id`() { + val slotTxn = slot() + every { txnStore.save(capture(slotTxn)) } returns null + every { sep38QuoteStore.findByQuoteId(any()) } returns depositQuote + sep24Service.deposit( + createTestSep10JwtWithMemo(), + createTestTransactionRequest(depositQuote.id) + ) + assertEquals(depositQuote.id, slotTxn.captured.quoteId) + assertEquals(depositQuote.sellAsset, slotTxn.captured.sourceAsset) + } + @Test fun `test deposit with no token and no request`() { assertThrows { @@ -401,6 +483,13 @@ internal class Sep24ServiceTest { token.sub = "G1234" sep24Service.deposit(token, request) } + + assertThrows { + val request = createTestTransactionRequest("bad-quote-id") + val token = createTestSep10JwtToken() + every { sep38QuoteStore.findByQuoteId(any()) } returns null + sep24Service.deposit(token, request) + } } @ParameterizedTest diff --git a/core/src/test/kotlin/org/stellar/anchor/sep24/Sep24ServiceTestHelper.kt b/core/src/test/kotlin/org/stellar/anchor/sep24/Sep24ServiceTestHelper.kt index fef1b9aa24..97ee312f13 100644 --- a/core/src/test/kotlin/org/stellar/anchor/sep24/Sep24ServiceTestHelper.kt +++ b/core/src/test/kotlin/org/stellar/anchor/sep24/Sep24ServiceTestHelper.kt @@ -2,17 +2,24 @@ package org.stellar.anchor.sep24 import org.stellar.anchor.TestConstants -fun createTestTransactionRequest(): MutableMap { - return mutableMapOf( - "lang" to "en", - "asset_code" to TestConstants.TEST_ASSET, - "asset_issuer" to TestConstants.TEST_ASSET_ISSUER_ACCOUNT_ID, - "account" to TestConstants.TEST_ACCOUNT, - "amount" to "123.4", - "email_address" to "jamie@stellar.org", - "first_name" to "Jamie", - "last_name" to "Li" - ) +fun createTestTransactionRequest( + quoteID: String? = null, +): MutableMap { + val request = + mutableMapOf( + "lang" to "en", + "asset_code" to TestConstants.TEST_ASSET, + "asset_issuer" to TestConstants.TEST_ASSET_ISSUER_ACCOUNT_ID, + "account" to TestConstants.TEST_ACCOUNT, + "amount" to "542", + "email_address" to "jamie@stellar.org", + "first_name" to "Jamie", + "last_name" to "Li", + ) + if (quoteID != null) { + request["quote_id"] = quoteID + } + return request } fun createTestTransaction(kind: String): Sep24Transaction { diff --git a/core/src/test/kotlin/org/stellar/anchor/sep38/Sep38ServiceTest.kt b/core/src/test/kotlin/org/stellar/anchor/sep38/Sep38ServiceTest.kt index 47ceba852a..0ce4d2a284 100644 --- a/core/src/test/kotlin/org/stellar/anchor/sep38/Sep38ServiceTest.kt +++ b/core/src/test/kotlin/org/stellar/anchor/sep38/Sep38ServiceTest.kt @@ -819,7 +819,7 @@ class Sep38ServiceTest { ) } assertInstanceOf(BadRequestException::class.java, ex) - assertEquals("Unsupported context. Should be one of [sep6, sep31].", ex.message) + assertEquals("Unsupported context. Should be one of [sep6, sep24, sep31].", ex.message) // sell_amount should be within limit ex = assertThrows { diff --git a/core/src/test/kotlin/org/stellar/anchor/sep6/ExchangeAmountsCalculatorTest.kt b/core/src/test/kotlin/org/stellar/anchor/sep6/ExchangeAmountsCalculatorTest.kt index 151b4bc285..e887d1543d 100644 --- a/core/src/test/kotlin/org/stellar/anchor/sep6/ExchangeAmountsCalculatorTest.kt +++ b/core/src/test/kotlin/org/stellar/anchor/sep6/ExchangeAmountsCalculatorTest.kt @@ -103,4 +103,18 @@ class ExchangeAmountsCalculatorTest { calculator.calculateFromQuote(quoteId, assetService.getAsset("USDC"), "100") } } + + @Test + fun `test validateQuoteAgainstRequestInfo with mismatched buy asset`() { + val quoteId = "id" + every { sep38QuoteStore.findByQuoteId(quoteId) } returns usdcQuote + assertThrows { + calculator.validateQuoteAgainstRequestInfo( + quoteId, + assetService.getAsset("USDC"), + assetService.getAsset("JPYC"), + "100" + ) + } + } } diff --git a/platform/src/main/java/org/stellar/anchor/platform/component/sep/SepBeans.java b/platform/src/main/java/org/stellar/anchor/platform/component/sep/SepBeans.java index 23c10c280f..6b93be61ac 100644 --- a/platform/src/main/java/org/stellar/anchor/platform/component/sep/SepBeans.java +++ b/platform/src/main/java/org/stellar/anchor/platform/component/sep/SepBeans.java @@ -180,7 +180,10 @@ Sep24Service sep24Service( EventService eventService, InteractiveUrlConstructor interactiveUrlConstructor, MoreInfoUrlConstructor moreInfoUrlConstructor, - CustodyConfig custodyConfig) { + CustodyConfig custodyConfig, + Sep38QuoteStore sep38QuoteStore) { + ExchangeAmountsCalculator exchangeAmountsCalculator = + new ExchangeAmountsCalculator(sep38QuoteStore); return new Sep24Service( appConfig, sep24Config, @@ -191,7 +194,8 @@ Sep24Service sep24Service( eventService, interactiveUrlConstructor, moreInfoUrlConstructor, - custodyConfig); + custodyConfig, + exchangeAmountsCalculator); } @Bean diff --git a/platform/src/main/java/org/stellar/anchor/platform/data/JdbcSep24Transaction.java b/platform/src/main/java/org/stellar/anchor/platform/data/JdbcSep24Transaction.java index 3f74f80e70..1699279120 100644 --- a/platform/src/main/java/org/stellar/anchor/platform/data/JdbcSep24Transaction.java +++ b/platform/src/main/java/org/stellar/anchor/platform/data/JdbcSep24Transaction.java @@ -135,4 +135,16 @@ public void setRefunds(Sep24Refunds refunds) { @SerializedName("refund_memo_type") @Column(name = "refund_memo_type") String refundMemoType; + + @SerializedName("quote_id") + @Column(name = "quote_id") + String quoteId; + + @SerializedName("source_asset") + @Column(name = "source_asset") + String sourceAsset; + + @SerializedName("destination_asset") + @Column(name = "destination_asset") + String destinationAsset; } diff --git a/platform/src/main/resources/db/migration/V11__sep24_field_updates.sql b/platform/src/main/resources/db/migration/V11__sep24_field_updates.sql new file mode 100644 index 0000000000..ea5faf4342 --- /dev/null +++ b/platform/src/main/resources/db/migration/V11__sep24_field_updates.sql @@ -0,0 +1,5 @@ +ALTER TABLE sep24_transaction ADD quote_id VARCHAR(255); + +ALTER TABLE sep24_transaction ADD source_asset VARCHAR(255); + +ALTER TABLE sep24_transaction ADD destination_asset VARCHAR(255); \ No newline at end of file