diff --git a/build.gradle.kts b/build.gradle.kts index fa309dcf3..4fb39184d 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -52,7 +52,7 @@ allprojects { } group = "exchange.dydx.abacus" -version = "1.11.12" +version = "1.11.13" repositories { google() diff --git a/src/commonMain/kotlin/exchange.dydx.abacus/functional/vault/VaultDepositWithdrawForm.kt b/src/commonMain/kotlin/exchange.dydx.abacus/functional/vault/VaultDepositWithdrawForm.kt index d3e342e09..c4bcdb407 100644 --- a/src/commonMain/kotlin/exchange.dydx.abacus/functional/vault/VaultDepositWithdrawForm.kt +++ b/src/commonMain/kotlin/exchange.dydx.abacus/functional/vault/VaultDepositWithdrawForm.kt @@ -1,12 +1,19 @@ package exchange.dydx.abacus.functional.vault +import exchange.dydx.abacus.output.input.ErrorFormat +import exchange.dydx.abacus.output.input.ErrorParam +import exchange.dydx.abacus.output.input.ErrorResources +import exchange.dydx.abacus.output.input.ErrorString import exchange.dydx.abacus.output.input.ErrorType +import exchange.dydx.abacus.output.input.ValidationError import exchange.dydx.abacus.protocols.asTypedObject import exchange.dydx.abacus.utils.IList import exchange.dydx.abacus.utils.Parser +import exchange.dydx.abacus.utils.format import kollections.toIList import kotlinx.serialization.Serializable import kotlin.js.JsExport +import kotlin.math.floor @JsExport @Serializable @@ -28,7 +35,8 @@ enum class VaultFormAction { @Serializable data class VaultFormAccountData( val marginUsage: Double?, - val freeCollateral: Double? + val freeCollateral: Double?, + val canViewAccount: Boolean?, ) @JsExport @@ -38,26 +46,112 @@ data class VaultDepositWithdrawSlippageResponse( val expectedAmount: Double, ) -@JsExport -@Serializable -data class VaultFormValidationError( - val severity: ErrorType, - val type: VaultFormValidationErrorType -) +object VaultFormValidationErrors { + private fun createError( + code: String, + type: ErrorType, + fields: List? = null, + titleKey: String? = null, + textKey: String? = null, + textKeyParams: List? = null + ): ValidationError { + return ValidationError( + code = code, + type = type, + fields = fields?.toIList(), + action = null, + link = null, + linkText = null, + resources = ErrorResources( + title = titleKey?.let { ErrorString(stringKey = it, params = null, localized = null) }, + text = textKey?.let { + ErrorString( + stringKey = it, + params = textKeyParams?.toIList(), + localized = null, + ) + }, + action = null, + ), + ) + } -@JsExport -@Serializable -enum class VaultFormValidationErrorType { - AMOUNT_EMPTY, - DEPOSIT_TOO_HIGH, - WITHDRAW_TOO_HIGH, - WITHDRAWING_LOCKED_BALANCE, - SLIPPAGE_TOO_HIGH, - MUST_ACK_SLIPPAGE, - VAULT_ACCOUNT_MISSING, - ACCOUNT_DATA_MISSING, - SLIPPAGE_RESPONSE_MISSING, - SLIPPAGE_RESPONSE_WRONG_SHARES, + fun amountEmpty(operation: VaultFormAction) = createError( + code = "AMOUNT_EMPTY", + type = ErrorType.error, + fields = listOf("amount"), + titleKey = if (operation == VaultFormAction.DEPOSIT) { + "APP.VAULTS.ENTER_AMOUNT_TO_DEPOSIT" + } else { + "APP.VAULTS.ENTER_AMOUNT_TO_WITHDRAW" + }, + ) + + fun accountDataMissing(canViewAccount: Boolean?) = createError( + code = "ACCOUNT_DATA_MISSING", + type = ErrorType.error, + titleKey = if (canViewAccount != null && canViewAccount) { + "APP.GENERAL.NOT_ALLOWED" + } else { + "APP.GENERAL.CONNECT_WALLET" + }, + ) + + fun depositTooHigh() = createError( + code = "DEPOSIT_TOO_HIGH", + type = ErrorType.error, + fields = listOf("amount"), + titleKey = "APP.TRADE.MODIFY_SIZE_FIELD", + textKey = "APP.VAULTS.DEPOSIT_TOO_HIGH", + ) + + fun withdrawTooHigh() = createError( + code = "WITHDRAW_TOO_HIGH", + type = ErrorType.error, + fields = listOf("amount"), + titleKey = "APP.TRADE.MODIFY_SIZE_FIELD", + textKey = "APP.VAULTS.WITHDRAW_TOO_HIGH", + ) + + fun withdrawingLockedBalance() = createError( + code = "WITHDRAWING_LOCKED_BALANCE", + type = ErrorType.error, + fields = listOf("amount"), + titleKey = "APP.TRADE.MODIFY_SIZE_FIELD", + textKey = "APP.VAULTS.WITHDRAW_TOO_HIGH", + ) + + fun slippageTooHigh(slippagePercent: Double) = createError( + code = "SLIPPAGE_TOO_HIGH", + type = ErrorType.warning, + textKey = "APP.VAULTS.SLIPPAGE_WARNING", + textKeyParams = listOf( + ErrorParam(key = "AMOUNT", value = slippagePercent.format(4), format = ErrorFormat.Percent), + ErrorParam(key = "LINK", value = "", format = null), + ), + ) + + fun mustAckSlippage() = createError( + code = "MUST_ACK_SLIPPAGE", + type = ErrorType.error, + fields = listOf("acknowledgeSlippage"), + titleKey = "APP.VAULTS.ACKNOWLEDGE_HIGH_SLIPPAGE", + ) + + fun vaultAccountMissing() = createError( + code = "VAULT_ACCOUNT_MISSING", + type = ErrorType.error, + ) + + fun slippageResponseMissing() = createError( + code = "SLIPPAGE_RESPONSE_MISSING", + type = ErrorType.error, + ) + + fun slippageResponseWrongShares() = createError( + code = "SLIPPAGE_RESPONSE_WRONG_SHARES", + type = ErrorType.error, + ) } @JsExport @@ -96,7 +190,7 @@ data class VaultFormSummaryData( @JsExport @Serializable data class VaultFormValidationResult( - val errors: IList, + val errors: IList, val submissionData: VaultDepositWithdrawSubmissionData?, val summaryData: VaultFormSummaryData ) @@ -119,7 +213,7 @@ object VaultDepositWithdrawFormValidator { vaultAccount: VaultAccount?, slippageResponse: VaultDepositWithdrawSlippageResponse? ): VaultFormValidationResult { - val errors = mutableListOf() + val errors = mutableListOf() var submissionData: VaultDepositWithdrawSubmissionData? = null // Calculate post-operation values and slippage @@ -131,7 +225,8 @@ object VaultDepositWithdrawFormValidator { null } val sharesToAttemptWithdraw = if (amount > 0 && shareValue != null && shareValue > 0) { - amount / shareValue + // shares must be whole numbers + floor(amount / shareValue) } else { null } @@ -180,61 +275,58 @@ object VaultDepositWithdrawFormValidator { // Perform validation checks and populate errors list if (accountData == null) { - errors.add(VaultFormValidationError(ErrorType.error, VaultFormValidationErrorType.ACCOUNT_DATA_MISSING)) + errors.add(VaultFormValidationErrors.accountDataMissing(accountData?.canViewAccount)) } if (amount == 0.0) { - errors.add(VaultFormValidationError(ErrorType.error, VaultFormValidationErrorType.AMOUNT_EMPTY)) + errors.add(VaultFormValidationErrors.amountEmpty(formData.action)) } // can't actually submit if we are missing key validation information if (formData.inConfirmationStep && formData.action === VaultFormAction.WITHDRAW) { if (vaultAccount == null) { - errors.add(VaultFormValidationError(ErrorType.error, VaultFormValidationErrorType.VAULT_ACCOUNT_MISSING)) + errors.add(VaultFormValidationErrors.vaultAccountMissing()) } if (slippageResponse == null || sharesToAttemptWithdraw == null) { - errors.add(VaultFormValidationError(ErrorType.error, VaultFormValidationErrorType.SLIPPAGE_RESPONSE_MISSING)) + errors.add(VaultFormValidationErrors.slippageResponseMissing()) } } if (formData.inConfirmationStep && formData.action === VaultFormAction.DEPOSIT) { if (accountData?.marginUsage == null || accountData.freeCollateral == null) { - errors.add(VaultFormValidationError(ErrorType.error, VaultFormValidationErrorType.ACCOUNT_DATA_MISSING)) + errors.add(VaultFormValidationErrors.accountDataMissing(accountData?.canViewAccount)) } } when (formData.action) { VaultFormAction.DEPOSIT -> { if (postOpFreeCollateral != null && postOpFreeCollateral < 0) { - errors.add(VaultFormValidationError(ErrorType.error, VaultFormValidationErrorType.DEPOSIT_TOO_HIGH)) + errors.add(VaultFormValidationErrors.depositTooHigh()) } } VaultFormAction.WITHDRAW -> { if (postOpVaultBalance != null && postOpVaultBalance < 0) { - errors.add(VaultFormValidationError(ErrorType.error, VaultFormValidationErrorType.WITHDRAW_TOO_HIGH)) + errors.add(VaultFormValidationErrors.withdrawTooHigh()) } if (postOpVaultBalance != null && postOpVaultBalance >= 0 && amount > 0 && vaultAccount?.withdrawableUsdc != null && amount > vaultAccount.withdrawableUsdc) { - errors.add(VaultFormValidationError(ErrorType.error, VaultFormValidationErrorType.WITHDRAWING_LOCKED_BALANCE)) + errors.add(VaultFormValidationErrors.withdrawingLockedBalance()) } if (sharesToAttemptWithdraw != null && slippageResponse != null && sharesToAttemptWithdraw != slippageResponse.shares) { errors.add( - VaultFormValidationError( - ErrorType.error, - VaultFormValidationErrorType.SLIPPAGE_RESPONSE_WRONG_SHARES, - ), + VaultFormValidationErrors.slippageResponseWrongShares(), ) } if (needSlippageAck) { - errors.add(VaultFormValidationError(ErrorType.warning, VaultFormValidationErrorType.SLIPPAGE_TOO_HIGH)) + errors.add(VaultFormValidationErrors.slippageTooHigh(slippagePercent)) if (slippagePercent >= SLIPPAGE_PERCENT_ACK && !formData.acknowledgedSlippage && formData.inConfirmationStep) { - errors.add(VaultFormValidationError(ErrorType.error, VaultFormValidationErrorType.MUST_ACK_SLIPPAGE)) + errors.add(VaultFormValidationErrors.mustAckSlippage()) } } } } // Prepare submission data if no errors - if (errors.none { it.severity === ErrorType.error }) { + if (errors.none { it.type === ErrorType.error }) { submissionData = when (formData.action) { VaultFormAction.DEPOSIT -> VaultDepositWithdrawSubmissionData( deposit = VaultDepositData( diff --git a/src/commonTest/kotlin/exchange.dydx.abacus/functional/vault/VaultFormTests.kt b/src/commonTest/kotlin/exchange.dydx.abacus/functional/vault/VaultFormTests.kt index 49abd686d..6380c2694 100644 --- a/src/commonTest/kotlin/exchange.dydx.abacus/functional/vault/VaultFormTests.kt +++ b/src/commonTest/kotlin/exchange.dydx.abacus/functional/vault/VaultFormTests.kt @@ -1,6 +1,6 @@ package exchange.dydx.abacus.functional.vault -import exchange.dydx.abacus.output.input.ErrorType +import exchange.dydx.abacus.output.input.ValidationError import kollections.iListOf import kollections.toIList import kotlin.test.Test @@ -32,6 +32,7 @@ class VaultFormTests { accountData = VaultFormAccountData( marginUsage = 0.5, freeCollateral = 1000.0, + canViewAccount = true, ), vaultAccount = makeVaultAccount( balanceUsdc = 500.0, @@ -43,7 +44,7 @@ class VaultFormTests { assertEquals( VaultFormValidationResult( - errors = listOf().toIList(), + errors = listOf().toIList(), submissionData = VaultDepositWithdrawSubmissionData( deposit = VaultDepositData( subaccountFrom = "0", @@ -76,6 +77,7 @@ class VaultFormTests { accountData = VaultFormAccountData( marginUsage = 0.5, freeCollateral = 1000.0, + canViewAccount = true, ), vaultAccount = makeVaultAccount( balanceUsdc = 500.0, @@ -91,7 +93,7 @@ class VaultFormTests { assertEquals( VaultFormValidationResult( errors = iListOf( - VaultFormValidationError(ErrorType.warning, VaultFormValidationErrorType.SLIPPAGE_TOO_HIGH), + VaultFormValidationErrors.slippageTooHigh(0.02), ), submissionData = VaultDepositWithdrawSubmissionData( deposit = null, @@ -127,6 +129,7 @@ class VaultFormTests { accountData = VaultFormAccountData( marginUsage = 0.5, freeCollateral = 1000.0, + canViewAccount = true, ), vaultAccount = makeVaultAccount( balanceUsdc = 500.0, @@ -142,8 +145,8 @@ class VaultFormTests { assertEquals( VaultFormValidationResult( errors = iListOf( - VaultFormValidationError(ErrorType.error, VaultFormValidationErrorType.SLIPPAGE_RESPONSE_WRONG_SHARES), - VaultFormValidationError(ErrorType.warning, VaultFormValidationErrorType.SLIPPAGE_TOO_HIGH), + VaultFormValidationErrors.slippageResponseWrongShares(), + VaultFormValidationErrors.slippageTooHigh(0.02), ), submissionData = null, summaryData = VaultFormSummaryData( @@ -171,6 +174,7 @@ class VaultFormTests { accountData = VaultFormAccountData( marginUsage = 0.5, freeCollateral = 1000.0, + canViewAccount = true, ), vaultAccount = makeVaultAccount( balanceUsdc = 500.0, @@ -186,9 +190,9 @@ class VaultFormTests { assertEquals( VaultFormValidationResult( errors = iListOf( - VaultFormValidationError(ErrorType.error, VaultFormValidationErrorType.WITHDRAW_TOO_HIGH), - VaultFormValidationError(ErrorType.warning, VaultFormValidationErrorType.SLIPPAGE_TOO_HIGH), - VaultFormValidationError(ErrorType.error, VaultFormValidationErrorType.MUST_ACK_SLIPPAGE), + VaultFormValidationErrors.withdrawTooHigh(), + VaultFormValidationErrors.slippageTooHigh(0.166666), + VaultFormValidationErrors.mustAckSlippage(), ), submissionData = null, summaryData = VaultFormSummaryData( diff --git a/v4_abacus.podspec b/v4_abacus.podspec index eb6fb10d1..7c5b99946 100644 --- a/v4_abacus.podspec +++ b/v4_abacus.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |spec| spec.name = 'v4_abacus' - spec.version = '1.11.12' + spec.version = '1.11.13' spec.homepage = 'https://github.com/dydxprotocol/v4-abacus' spec.source = { :http=> ''} spec.authors = ''