Skip to content

Commit

Permalink
feat: better vault errors (#665)
Browse files Browse the repository at this point in the history
Co-authored-by: Rui <[email protected]>
  • Loading branch information
tyleroooo and ruixhuang authored Sep 19, 2024
1 parent 5dd1ce8 commit 0871947
Show file tree
Hide file tree
Showing 4 changed files with 144 additions and 48 deletions.
2 changes: 1 addition & 1 deletion build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ allprojects {
}

group = "exchange.dydx.abacus"
version = "1.11.12"
version = "1.11.13"

repositories {
google()
Expand Down
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -28,7 +35,8 @@ enum class VaultFormAction {
@Serializable
data class VaultFormAccountData(
val marginUsage: Double?,
val freeCollateral: Double?
val freeCollateral: Double?,
val canViewAccount: Boolean?,
)

@JsExport
Expand All @@ -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<String>? = null,
titleKey: String? = null,
textKey: String? = null,
textKeyParams: List<ErrorParam>? = 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
Expand Down Expand Up @@ -96,7 +190,7 @@ data class VaultFormSummaryData(
@JsExport
@Serializable
data class VaultFormValidationResult(
val errors: IList<VaultFormValidationError>,
val errors: IList<ValidationError>,
val submissionData: VaultDepositWithdrawSubmissionData?,
val summaryData: VaultFormSummaryData
)
Expand All @@ -119,7 +213,7 @@ object VaultDepositWithdrawFormValidator {
vaultAccount: VaultAccount?,
slippageResponse: VaultDepositWithdrawSlippageResponse?
): VaultFormValidationResult {
val errors = mutableListOf<VaultFormValidationError>()
val errors = mutableListOf<ValidationError>()
var submissionData: VaultDepositWithdrawSubmissionData? = null

// Calculate post-operation values and slippage
Expand All @@ -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
}
Expand Down Expand Up @@ -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(
Expand Down
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -32,6 +32,7 @@ class VaultFormTests {
accountData = VaultFormAccountData(
marginUsage = 0.5,
freeCollateral = 1000.0,
canViewAccount = true,
),
vaultAccount = makeVaultAccount(
balanceUsdc = 500.0,
Expand All @@ -43,7 +44,7 @@ class VaultFormTests {

assertEquals(
VaultFormValidationResult(
errors = listOf<VaultFormValidationError>().toIList(),
errors = listOf<ValidationError>().toIList(),
submissionData = VaultDepositWithdrawSubmissionData(
deposit = VaultDepositData(
subaccountFrom = "0",
Expand Down Expand Up @@ -76,6 +77,7 @@ class VaultFormTests {
accountData = VaultFormAccountData(
marginUsage = 0.5,
freeCollateral = 1000.0,
canViewAccount = true,
),
vaultAccount = makeVaultAccount(
balanceUsdc = 500.0,
Expand All @@ -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,
Expand Down Expand Up @@ -127,6 +129,7 @@ class VaultFormTests {
accountData = VaultFormAccountData(
marginUsage = 0.5,
freeCollateral = 1000.0,
canViewAccount = true,
),
vaultAccount = makeVaultAccount(
balanceUsdc = 500.0,
Expand All @@ -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(
Expand Down Expand Up @@ -171,6 +174,7 @@ class VaultFormTests {
accountData = VaultFormAccountData(
marginUsage = 0.5,
freeCollateral = 1000.0,
canViewAccount = true,
),
vaultAccount = makeVaultAccount(
balanceUsdc = 500.0,
Expand All @@ -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(
Expand Down
2 changes: 1 addition & 1 deletion v4_abacus.podspec
Original file line number Diff line number Diff line change
@@ -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 = ''
Expand Down

0 comments on commit 0871947

Please sign in to comment.