Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix exception when closing a position of > 100% margin usage #761

Merged
merged 1 commit into from
Dec 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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.13.35"
version = "1.13.36"

repositories {
google()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,12 @@ internal class TradeInputMarketOrderCalculator() {
val tradeSize = trade.size
val freeCollateral = subaccount?.calculated?.get(CalculationPeriod.current)?.freeCollateral

if (tradeSize != null && tradeSide != null && freeCollateral != null && freeCollateral > Numeric.double.ZERO) {
val freeCollateralCondition = if (trade.reduceOnly) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't need to check freeCollateral > 0 if the trade is reduceOnly.

true
} else {
freeCollateral != null && freeCollateral > Numeric.double.ZERO
}
if (tradeSize != null && tradeSide != null && freeCollateral != null && freeCollateralCondition) {
val maxMarketLeverage = market?.perpetualMarket?.configs?.maxMarketLeverage ?: Numeric.double.ONE
val targetLeverage = trade.targetLeverage
val marginMode = trade.marginMode ?: MarginMode.Cross
Expand Down Expand Up @@ -269,7 +274,14 @@ internal class TradeInputMarketOrderCalculator() {
if (marginMode == MarginMode.Isolated && !isTradeSameSide) {
// For isolated margin orders where the user is trading on the opposite side of their currentPosition, the balancePercent represents a percentage of their current position rather than freeCollateral
val desiredSize = existingPositionSize.abs() * balancePercent
return createMarketOrderFromSize(size = desiredSize, existingPositionNotionalSize = existingPositionNotionalSize, isTradeSameSide = isTradeSameSide, freeCollateral = freeCollateral, tradeLeverage = tradeLeverage, orderbook = orderbook)
return createMarketOrderFromSize(
size = desiredSize,
existingPositionNotionalSize = existingPositionNotionalSize,
isTradeSameSide = isTradeSameSide,
freeCollateral = freeCollateral,
tradeLeverage = tradeLeverage,
orderbook = orderbook,
)
}

val cappedPercent = min(balancePercent, MAX_FREE_COLLATERAL_BUFFER_PERCENT)
Expand Down Expand Up @@ -398,7 +410,13 @@ internal class TradeInputMarketOrderCalculator() {
break@orderbookLoop
}
}
val balancePercentTotal = calculateBalancePercentFromUsdcSize(usdcSize = usdcSizeTotal, freeCollateral = freeCollateral, positionSize = existingPositionNotionalSize, tradeLeverage = tradeLeverage, isTradeSameSide = isTradeSameSide)
val balancePercentTotal = calculateBalancePercentFromUsdcSize(
usdcSize = usdcSizeTotal,
freeCollateral = freeCollateral,
positionSize = existingPositionNotionalSize,
tradeLeverage = tradeLeverage,
isTradeSameSide = isTradeSameSide,
)
createMarketOrderWith(
orderbook = marketOrderOrderBook,
size = sizeTotal,
Expand Down Expand Up @@ -471,7 +489,13 @@ internal class TradeInputMarketOrderCalculator() {
}
}
}
val balancePercentTotal = calculateBalancePercentFromUsdcSize(usdcSize = usdcSizeTotal, freeCollateral = freeCollateral, positionSize = existingPositionNotionalSize, tradeLeverage = tradeLeverage, isTradeSameSide = isTradeSameSide)
val balancePercentTotal = calculateBalancePercentFromUsdcSize(
usdcSize = usdcSizeTotal,
freeCollateral = freeCollateral,
positionSize = existingPositionNotionalSize,
tradeLeverage = tradeLeverage,
isTradeSameSide = isTradeSameSide,
)
createMarketOrderWith(
orderbook = marketOrderOrderBook,
size = sizeTotal,
Expand Down Expand Up @@ -657,7 +681,13 @@ internal class TradeInputMarketOrderCalculator() {
break@orderbookLoop
}
}
val balancePercentTotal = calculateBalancePercentFromUsdcSize(usdcSize = usdcSizeTotal, freeCollateral = freeCollateral, positionSize = positionSizeNotional, tradeLeverage = tradeLeverage, isTradeSameSide = isTradeSameSide)
val balancePercentTotal = calculateBalancePercentFromUsdcSize(
usdcSize = usdcSizeTotal,
freeCollateral = freeCollateral,
positionSize = positionSizeNotional,
tradeLeverage = tradeLeverage,
isTradeSameSide = isTradeSameSide,
)
return createMarketOrderWith(
orderbook = marketOrderOrderBook,
size = sizeTotal,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,43 +13,51 @@ class V4TransactionErrors {
return if (code != null) {
if (code != 0 && codespace != null) {
ParsingError(
ParsingErrorType.BackendError,
message ?: "Unknown error",
"ERRORS.BROADCAST_ERROR_${codespace.uppercase()}_$code",
type = ParsingErrorType.BackendError,
message = message ?: "Unknown error",
stringKey = "ERRORS.BROADCAST_ERROR_${codespace.uppercase()}_$code",
)
} else {
null
}
} else if (message?.startsWith(QUERY_RESULT_ERROR_PREFIX) == true) {
parseQueryResultErrorFromMessage(message)
} else {
ParsingError(ParsingErrorType.BackendError, message ?: "Unknown error", null)
ParsingError(
type = ParsingErrorType.BackendError,
message = message ?: "Unknown error",
stringKey = null,
)
}
}

private fun parseQueryResultErrorFromMessage(message: String): ParsingError {
// Workaround: Regex match different query results until protocol can return codespace/code
parseSubaccountUpdateError(message)?.let { return it }
return ParsingError(ParsingErrorType.BackendError, "Unknown query result error", null)
return ParsingError(
type = ParsingErrorType.BackendError,
message = "Unknown query result error",
stringKey = null,
)
}

private fun parseSubaccountUpdateError(message: String): ParsingError? {
val matchResult = FAILED_SUBACCOUNT_UPDATE_RESULT_PATTERN.find(message)
return matchResult?.groups?.get(1)?.value?.let {
val matchedUpdateResult = SubaccountUpdateFailedResult.invoke(it)
ParsingError(
ParsingErrorType.BackendError,
"Subaccount update error: $it",
if (matchedUpdateResult != null) "ERRORS.QUERY_ERROR_SUBACCOUNTS_${matchedUpdateResult.toString().uppercase()}" else null,
type = ParsingErrorType.BackendError,
message = "Subaccount update error: $it",
stringKey = if (matchedUpdateResult != null) "ERRORS.QUERY_ERROR_SUBACCOUNTS_${matchedUpdateResult.toString().uppercase()}" else null,
)
}
}

fun parseErrorFromRawLog(rawLog: String): ParsingError? {
return if (rawLog.startsWith(OUT_OF_GAS_ERROR_RAW_LOG_PREFIX)) {
return ParsingError(
ParsingErrorType.BackendError,
"Out of gas: inaccurate gas estimation for transaction",
type = ParsingErrorType.BackendError,
message = "Out of gas: inaccurate gas estimation for transaction",
)
} else {
null
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import exchange.dydx.abacus.protocols.ThreadingType
import exchange.dydx.abacus.protocols.TransactionCallback
import exchange.dydx.abacus.protocols.asTypedObject
import exchange.dydx.abacus.protocols.readCachedTextFile
import exchange.dydx.abacus.responses.ParsingError
import exchange.dydx.abacus.state.app.adaptors.V4TransactionErrors
import exchange.dydx.abacus.state.app.helper.DynamicLocalizer
import exchange.dydx.abacus.state.manager.ApiData
Expand Down Expand Up @@ -49,6 +50,7 @@ import exchange.dydx.abacus.utils.DummyFormatter
import exchange.dydx.abacus.utils.DummyLocalizer
import exchange.dydx.abacus.utils.IList
import exchange.dydx.abacus.utils.IOImplementations
import exchange.dydx.abacus.utils.JsonEncoder
import exchange.dydx.abacus.utils.Logger
import exchange.dydx.abacus.utils.Parser
import exchange.dydx.abacus.utils.ProtocolNativeImpFactory
Expand Down Expand Up @@ -506,50 +508,111 @@ class AsyncAbacusStateManagerV2(
}

override fun placeOrderPayload(): HumanReadablePlaceOrderPayload? {
return adaptor?.placeOrderPayload()
try {
return adaptor?.placeOrderPayload()
} catch (e: Exception) {
val error = V4TransactionErrors.error(null, e.toString())
trackTransactionError("placeOrderPayload", error)
throw e
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All those "..Payload" functions are called by the web FE.. I think web code catches the exception to display the error.

Here we just catch the exception for logging and re-throw it.

}
}

override fun closePositionPayload(): HumanReadablePlaceOrderPayload? {
return adaptor?.closePositionPayload()
try {
return adaptor?.closePositionPayload()
} catch (e: Exception) {
val error = V4TransactionErrors.error(null, e.toString())
trackTransactionError("closePositionPayload", error)
throw e
}
}

override fun cancelOrderPayload(orderId: String): HumanReadableCancelOrderPayload? {
return adaptor?.cancelOrderPayload(orderId)
try {
return adaptor?.cancelOrderPayload(orderId)
} catch (e: Exception) {
val error = V4TransactionErrors.error(null, e.toString())
trackTransactionError("cancelOrderPayload", error)
throw e
}
}

override fun cancelAllOrdersPayload(marketId: String?): HumanReadableCancelAllOrdersPayload? {
return adaptor?.cancelAllOrdersPayload(marketId)
try {
return adaptor?.cancelAllOrdersPayload(marketId)
} catch (e: Exception) {
val error = V4TransactionErrors.error(null, e.toString())
trackTransactionError("cancelAllOrdersPayload", error)
throw e
}
}

override fun closeAllPositionsPayload(): HumanReadableCloseAllPositionsPayload? {
return adaptor?.closeAllPositionsPayload()
try {
return adaptor?.closeAllPositionsPayload()
} catch (e: Exception) {
val error = V4TransactionErrors.error(null, e.toString())
trackTransactionError("closeAllPositionsPayload", error)
throw e
}
}

override fun triggerOrdersPayload(): HumanReadableTriggerOrdersPayload? {
return adaptor?.triggerOrdersPayload()
try {
return adaptor?.triggerOrdersPayload()
} catch (e: Exception) {
val error = V4TransactionErrors.error(null, e.toString())
trackTransactionError("triggerOrdersPayload", error)
throw e
}
}

override fun adjustIsolatedMarginPayload(): HumanReadableSubaccountTransferPayload? {
return adaptor?.adjustIsolatedMarginPayload()
try {
return adaptor?.adjustIsolatedMarginPayload()
} catch (e: Exception) {
val error = V4TransactionErrors.error(null, e.toString())
trackTransactionError("adjustIsolatedMarginPayload", error)
throw e
}
}

override fun depositPayload(): HumanReadableDepositPayload? {
return adaptor?.depositPayload()
try {
return adaptor?.depositPayload()
} catch (e: Exception) {
val error = V4TransactionErrors.error(null, e.toString())
trackTransactionError("depositPayload", error)
throw e
}
}

override fun withdrawPayload(): HumanReadableWithdrawPayload? {
return adaptor?.withdrawPayload()
try {
return adaptor?.withdrawPayload()
} catch (e: Exception) {
val error = V4TransactionErrors.error(null, e.toString())
trackTransactionError("withdrawPayload", error)
throw e
}
}

override fun subaccountTransferPayload(): HumanReadableSubaccountTransferPayload? {
return adaptor?.subaccountTransferPayload()
try {
return adaptor?.subaccountTransferPayload()
} catch (e: Exception) {
val error = V4TransactionErrors.error(null, e.toString())
trackTransactionError("subaccountTransferPayload", error)
throw e
}
}

override fun commitPlaceOrder(callback: TransactionCallback): HumanReadablePlaceOrderPayload? {
return try {
adaptor?.commitPlaceOrder(callback)
} catch (e: Exception) {
val error = V4TransactionErrors.error(null, e.toString())
trackTransactionError("commitPlaceOrder", error)
callback(false, error, null)
null
}
Expand All @@ -560,6 +623,7 @@ class AsyncAbacusStateManagerV2(
adaptor?.commitTriggerOrders(callback)
} catch (e: Exception) {
val error = V4TransactionErrors.error(null, e.toString())
trackTransactionError("commitTriggerOrders", error)
callback(false, error, null)
null
}
Expand All @@ -570,6 +634,7 @@ class AsyncAbacusStateManagerV2(
adaptor?.commitAdjustIsolatedMargin(callback)
} catch (e: Exception) {
val error = V4TransactionErrors.error(null, e.toString())
trackTransactionError("commitAdjustIsolatedMargin", error)
callback(false, error, null)
null
}
Expand All @@ -580,6 +645,7 @@ class AsyncAbacusStateManagerV2(
adaptor?.commitClosePosition(callback)
} catch (e: Exception) {
val error = V4TransactionErrors.error(null, e.toString())
trackTransactionError("commitClosePosition", error)
callback(false, error, null)
null
}
Expand All @@ -594,6 +660,7 @@ class AsyncAbacusStateManagerV2(
adaptor?.commitTransfer(callback)
} catch (e: Exception) {
val error = V4TransactionErrors.error(null, e.toString())
trackTransactionError("commitTransfer", error)
callback(false, error, null)
}
}
Expand All @@ -603,6 +670,7 @@ class AsyncAbacusStateManagerV2(
adaptor?.commitCCTPWithdraw(callback)
} catch (e: Exception) {
val error = V4TransactionErrors.error(null, e.toString())
trackTransactionError("commitCCTPWithdraw", error)
callback(false, error, null)
}
}
Expand All @@ -612,6 +680,7 @@ class AsyncAbacusStateManagerV2(
adaptor?.faucet(amount, callback)
} catch (e: Exception) {
val error = V4TransactionErrors.error(null, e.toString())
trackTransactionError("faucet", error)
callback(false, error, null)
}
}
Expand All @@ -621,6 +690,7 @@ class AsyncAbacusStateManagerV2(
adaptor?.cancelOrder(orderId, callback)
} catch (e: Exception) {
val error = V4TransactionErrors.error(null, e.toString())
trackTransactionError("cancelOrder", error)
callback(false, error, null)
}
}
Expand All @@ -630,6 +700,7 @@ class AsyncAbacusStateManagerV2(
adaptor?.cancelAllOrders(marketId, callback)
} catch (e: Exception) {
val error = V4TransactionErrors.error(null, e.toString())
trackTransactionError("cancelAllOrders", error)
callback(false, error, null)
}
}
Expand All @@ -639,6 +710,7 @@ class AsyncAbacusStateManagerV2(
adaptor?.closeAllPositions(callback)
} catch (e: Exception) {
val error = V4TransactionErrors.error(null, e.toString())
trackTransactionError("closeAllPositions", error)
callback(false, error, null)
null
}
Expand All @@ -649,10 +721,30 @@ class AsyncAbacusStateManagerV2(
adaptor?.triggerCompliance(action, callback)
} catch (e: Exception) {
val error = V4TransactionErrors.error(null, e.toString())
trackTransactionError("triggerCompliance", error)
callback(false, error, null)
}
}

private fun trackTransactionError(functionName: String, error: ParsingError?) {
if (error == null) {
return
}

val params = mapOf(
"functionName" to functionName,
"errorType" to error.type.rawValue,
"errorMessage" to error.message,
"stackTrace" to error.stackTrace,
)
ioImplementations.threading?.async(ThreadingType.main) {
ioImplementations.tracking?.log(
event = "ClientTransactionError",
data = JsonEncoder().encode(params),
)
}
}

// Bridge functions.
// If client is not using cancelOrder function, it should call orderCanceled function with
// payload from v4-client to process state
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.13.35'
spec.version = '1.13.36'
spec.homepage = 'https://github.com/dydxprotocol/v4-abacus'
spec.source = { :http=> ''}
spec.authors = ''
Expand Down