diff --git a/build.gradle.kts b/build.gradle.kts index 74c007148..753067110 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -51,7 +51,7 @@ allprojects { } group = "exchange.dydx.abacus" -version = "1.7.21" +version = "1.7.22" repositories { google() diff --git a/src/commonMain/kotlin/exchange.dydx.abacus/calculator/AdjustIsolatedMarginInputCalculator.kt b/src/commonMain/kotlin/exchange.dydx.abacus/calculator/AdjustIsolatedMarginInputCalculator.kt index 113feec6a..ac4ddd73a 100644 --- a/src/commonMain/kotlin/exchange.dydx.abacus/calculator/AdjustIsolatedMarginInputCalculator.kt +++ b/src/commonMain/kotlin/exchange.dydx.abacus/calculator/AdjustIsolatedMarginInputCalculator.kt @@ -21,7 +21,7 @@ internal class AdjustIsolatedMarginInputCalculator(val parser: ParserProtocol) { IsolatedMarginAdjustmentType.valueOf(it) } ?: IsolatedMarginAdjustmentType.Add - return if (wallet != null && isolatedMarginAdjustment != null && type != null) { + return if (wallet != null && isolatedMarginAdjustment != null) { val modified = state.mutable() val parentTransferDelta = getModifiedTransferDelta(isolatedMarginAdjustment, true) val childTransferDelta = getModifiedTransferDelta(isolatedMarginAdjustment, false) @@ -44,8 +44,8 @@ internal class AdjustIsolatedMarginInputCalculator(val parser: ParserProtocol) { "postOrder", ) - val modifiedParentSubaccount = parser.asNativeMap(parser.value(walletPostChildSubaccountTransfer, "accounts.subaccounts.$parentSubaccountNumber")) - val modifiedChildSubaccount = parser.asNativeMap(parser.value(walletPostChildSubaccountTransfer, "accounts.subaccounts.$childSubaccountNumber")) + val modifiedParentSubaccount = parser.asNativeMap(parser.value(walletPostChildSubaccountTransfer, "account.subaccounts.$parentSubaccountNumber")) + val modifiedChildSubaccount = parser.asNativeMap(parser.value(walletPostChildSubaccountTransfer, "account.subaccounts.$childSubaccountNumber")) val modifiedIsolatedMarginAdjustment = finalize(isolatedMarginAdjustment, modifiedParentSubaccount, modifiedChildSubaccount, type) modified["adjustIsolatedMargin"] = modifiedIsolatedMarginAdjustment @@ -94,13 +94,13 @@ internal class AdjustIsolatedMarginInputCalculator(val parser: ParserProtocol) { type: IsolatedMarginAdjustmentType, ): Map { val summary = mutableMapOf() - val crossCollateral = parser.asDouble(parser.value(parentSubaccount, "freeCollateral.postOrder")) - val crossMarginUsage = parser.asDouble(parser.value(parentSubaccount, "marginUsage.postOrder")) + val crossCollateral = parentSubaccount?.get("freeCollateral") + val crossMarginUsage = parentSubaccount?.get("marginUsage") val openPositions = parser.asNativeMap(childSubaccount?.get("openPositions")) val marketId = openPositions?.keys?.firstOrNull() - val positionMargin = parser.asDouble(parser.value(childSubaccount, "freeCollateral.postOrder")) - val positionLeverage = parser.asDouble(parser.value(childSubaccount, "openPositions.$marketId.leverage.postOrder")) - val liquidationPrice = parser.asDouble(parser.value(childSubaccount, "openPositions.$marketId.liquidationPrice.postOrder")) + val positionMargin = childSubaccount?.get("freeCollateral") + val positionLeverage = parser.value(childSubaccount, "openPositions.$marketId.leverage") + val liquidationPrice = parser.value(childSubaccount, "openPositions.$marketId.liquidationPrice") when (type) { IsolatedMarginAdjustmentType.Add -> { @@ -123,6 +123,41 @@ internal class AdjustIsolatedMarginInputCalculator(val parser: ParserProtocol) { return summary } + private fun amountField(): Map { + return mapOf( + "field" to "amount", + "type" to "double", + ) + } + + private fun requiredFields(): List { + return listOf( + amountField(), + ) + } + + private fun calculatedOptionsFromField(fields: List?): Map? { + fields?.let { + val options = mutableMapOf( + "needsSize" to false, + ) + + for (item in fields) { + parser.asNativeMap(item)?.let { field -> + when (parser.asString(field["field"])) { + "amount" -> { + options["needsSize"] = true + } + } + } + } + + return options + } + + return null + } + private fun finalize( isolatedMarginAdjustment: Map, parentSubaccount: Map?, @@ -130,6 +165,9 @@ internal class AdjustIsolatedMarginInputCalculator(val parser: ParserProtocol) { type: IsolatedMarginAdjustmentType, ): Map { val modified = isolatedMarginAdjustment.mutable() + val fields = requiredFields() + modified.safeSet("fields", fields) + modified.safeSet("options", calculatedOptionsFromField(fields)) modified.safeSet("summary", summaryForType(parentSubaccount, childSubaccount, type)) return modified } diff --git a/src/commonMain/kotlin/exchange.dydx.abacus/output/input/AdjustIsolatedMarginInput.kt b/src/commonMain/kotlin/exchange.dydx.abacus/output/input/AdjustIsolatedMarginInput.kt index c6dd2941a..6b3b7f7c9 100644 --- a/src/commonMain/kotlin/exchange.dydx.abacus/output/input/AdjustIsolatedMarginInput.kt +++ b/src/commonMain/kotlin/exchange.dydx.abacus/output/input/AdjustIsolatedMarginInput.kt @@ -38,10 +38,15 @@ data class AdjustIsolatedMarginInputOptions( @Serializable data class AdjustIsolatedMarginInputSummary( val crossFreeCollateral: Double?, + val crossFreeCollateralUpdated: Double?, val crossMarginUsage: Double?, + val crossMarginUsageUpdated: Double?, val positionMargin: Double?, + val positionMarginUpdated: Double?, val positionLeverage: Double?, + val positionLeverageUpdated: Double?, val liquidationPrice: Double?, + val liquidationPriceUpdated: Double?, ) { companion object { internal fun create( @@ -52,25 +57,40 @@ data class AdjustIsolatedMarginInputSummary( Logger.d { "creating Adjust Isolated Margin Input Summary\n" } data?.let { - val crossFreeCollateral = parser.asDouble(data["crossFreeCollateral"]) - val crossMarginUsage = parser.asDouble(data["crossMarginUsage"]) - val positionMargin = parser.asDouble(data["positionMargin"]) - val positionLeverage = parser.asDouble(data["positionLeverage"]) - val liquidationPrice = parser.asDouble(data["liquidationPrice"]) + val crossFreeCollateral = parser.asDouble(parser.value(data, "crossFreeCollateral.current")) + val crossFreeCollateralUpdated = parser.asDouble(parser.value(data, "crossFreeCollateral.postOrder")) + val crossMarginUsage = parser.asDouble(parser.value(data, "crossMarginUsage.current")) + val crossMarginUsageUpdated = parser.asDouble(parser.value(data, "crossMarginUsage.postOrder")) + val positionMargin = parser.asDouble(parser.value(data, "positionMargin.current")) + val positionMarginUpdated = parser.asDouble(parser.value(data, "positionMargin.postOrder")) + val positionLeverage = parser.asDouble(parser.value(data, "positionLeverage.current")) + val positionLeverageUpdated = parser.asDouble(parser.value(data, "positionLeverage.postOrder")) + val liquidationPrice = parser.asDouble(parser.value(data, "liquidationPrice.current")) + val liquidationPriceUpdated = parser.asDouble(parser.value(data, "liquidationPrice.postOrder")) return if ( existing?.crossFreeCollateral != crossFreeCollateral || + existing?.crossFreeCollateralUpdated != crossFreeCollateralUpdated || existing?.crossMarginUsage != crossMarginUsage || + existing?.crossMarginUsageUpdated != crossMarginUsageUpdated || existing?.positionMargin != positionMargin || + existing?.positionMarginUpdated != positionMarginUpdated || existing?.positionLeverage != positionLeverage || - existing?.liquidationPrice != liquidationPrice + existing?.positionLeverageUpdated != positionLeverageUpdated || + existing?.liquidationPrice != liquidationPrice || + existing?.liquidationPriceUpdated != liquidationPriceUpdated ) { AdjustIsolatedMarginInputSummary( crossFreeCollateral, + crossFreeCollateralUpdated, crossMarginUsage, + crossMarginUsageUpdated, positionMargin, + positionMarginUpdated, positionLeverage, + positionLeverageUpdated, liquidationPrice, + liquidationPriceUpdated, ) } else { existing @@ -94,12 +114,10 @@ enum class IsolatedMarginAdjustmentType { data class AdjustIsolatedMarginInput( val type: IsolatedMarginAdjustmentType, val amount: String?, + val amountPercent: String?, val childSubaccountNumber: Int?, val adjustIsolatedMarginInputOptions: AdjustIsolatedMarginInputOptions?, - val summary: AdjustIsolatedMarginInputSummary?, - val errors: String?, - val errorMessage: String?, - val fee: Double?, + val summary: AdjustIsolatedMarginInputSummary? ) { companion object { internal fun create( @@ -116,11 +134,12 @@ data class AdjustIsolatedMarginInput( val childSubaccountNumber = parser.asInt(data["ChildSubaccountNumber"]) val amount = parser.asString(data["Amount"]) - val fee = parser.asDouble(data["fee"]) + val amountPercent = parser.asString(data["AmountPercent"]) + val adjustIsolatedMarginInputOptions = AdjustIsolatedMarginInputOptions.create( existing?.adjustIsolatedMarginInputOptions, parser, - parser.asMap(data["adjustIsolatedMarginInputOptions"]), + parser.asMap(data["options"]), ) val summary = AdjustIsolatedMarginInputSummary.create( existing?.summary, @@ -128,36 +147,21 @@ data class AdjustIsolatedMarginInput( parser.asMap(data["summary"]), ) - val errors = parser.asString(data["errors"]) - - val errorMessage: String? = - if (errors != null) { - val errorArray = parser.decodeJsonArray(errors) - val firstError = parser.asMap(errorArray?.first()) - parser.asString(firstError?.get("message")) - } else { - null - } - return if ( existing?.type != type || existing.amount != amount || + existing.amountPercent != amountPercent || existing.childSubaccountNumber != childSubaccountNumber || existing.adjustIsolatedMarginInputOptions != adjustIsolatedMarginInputOptions || - existing.summary !== summary || - existing.errors !== errors || - existing.errorMessage != errorMessage || - existing.fee != fee + existing.summary !== summary ) { AdjustIsolatedMarginInput( type, amount, + amountPercent, childSubaccountNumber, adjustIsolatedMarginInputOptions, summary, - errors, - errorMessage, - fee, ) } else { existing diff --git a/src/commonMain/kotlin/exchange.dydx.abacus/state/manager/StateManagerAdaptor.kt b/src/commonMain/kotlin/exchange.dydx.abacus/state/manager/StateManagerAdaptor.kt index ef78ed483..f8b8f934d 100644 --- a/src/commonMain/kotlin/exchange.dydx.abacus/state/manager/StateManagerAdaptor.kt +++ b/src/commonMain/kotlin/exchange.dydx.abacus/state/manager/StateManagerAdaptor.kt @@ -2244,7 +2244,7 @@ open class StateManagerAdaptor( val walletAddress = wallet.walletAddress ?: error("walletAddress is null") val isolatedMarginAdjustment = stateMachine.state?.input?.adjustIsolatedMargin ?: error("isolatedMarginAdjustment is null") - val amount = isolatedMarginAdjustment.amount ?: error("amount is null") + val amount = parser.asString(isolatedMarginAdjustment.amount) ?: error("amount is null") val childSubaccountNumber = isolatedMarginAdjustment.childSubaccountNumber ?: error("childSubaccountNumber is null") diff --git a/src/commonMain/kotlin/exchange.dydx.abacus/state/model/TradingStateMachine+AdjustIsolatedMarginInput.kt b/src/commonMain/kotlin/exchange.dydx.abacus/state/model/TradingStateMachine+AdjustIsolatedMarginInput.kt index a902e6358..780d26763 100644 --- a/src/commonMain/kotlin/exchange.dydx.abacus/state/model/TradingStateMachine+AdjustIsolatedMarginInput.kt +++ b/src/commonMain/kotlin/exchange.dydx.abacus/state/model/TradingStateMachine+AdjustIsolatedMarginInput.kt @@ -6,6 +6,7 @@ import exchange.dydx.abacus.responses.ParsingError import exchange.dydx.abacus.responses.StateResponse import exchange.dydx.abacus.state.changes.Changes import exchange.dydx.abacus.state.changes.StateChanges +import exchange.dydx.abacus.utils.IList import exchange.dydx.abacus.utils.NUM_PARENT_SUBACCOUNTS import exchange.dydx.abacus.utils.mutable import exchange.dydx.abacus.utils.mutableMapOf @@ -19,6 +20,7 @@ import kotlinx.serialization.Serializable enum class AdjustIsolatedMarginInputField { Type, Amount, + AmountPercent, ChildSubaccountNumber, } @@ -64,37 +66,59 @@ fun TradingStateMachine.adjustIsolatedMargin( if (adjustIsolatedMargin["Type"] != parser.asString(data)) { adjustIsolatedMargin.safeSet(type.name, parser.asString(data)) adjustIsolatedMargin.safeSet("Amount", null) + adjustIsolatedMargin.safeSet("AmountPercent", null) } - changes = StateChanges( - iListOf(Changes.wallet, Changes.subaccount, Changes.input), - null, - subaccountNumbers, - ) + changes = getStateChanges(subaccountNumbers) } + AdjustIsolatedMarginInputField.AmountPercent, AdjustIsolatedMarginInputField.Amount -> { - val amount = parser.asString(data) - adjustIsolatedMargin.safeSet(type.name, amount) - changes = StateChanges( - iListOf(Changes.wallet, Changes.subaccount, Changes.input), - null, - subaccountNumbers, + val isolatedMarginAdjustmentType = adjustIsolatedMargin["Type"] ?: IsolatedMarginAdjustmentType.Add.name + val subaccountNumber = if (isolatedMarginAdjustmentType == IsolatedMarginAdjustmentType.Add.name) { + parentSubaccountNumber + } else { + childSubaccountNumber + } + val subaccount = parser.asNativeMap( + parser.value(this.account, "subaccounts.$subaccountNumber"), ) + + val freeCollateral = parser.asDouble(parser.value(subaccount, "freeCollateral.current")) + val amountValue = parser.asDouble(data) + + if (amountValue == null) { + adjustIsolatedMargin.safeSet("Amount", null) + adjustIsolatedMargin.safeSet("AmountPercent", null) + } else if (type == AdjustIsolatedMarginInputField.Amount) { + adjustIsolatedMargin.safeSet(type.name, amountValue.toString()) + + if (freeCollateral != null) { + val amountPercent = amountValue / freeCollateral + adjustIsolatedMargin.safeSet("AmountPercent", amountPercent.toString()) + } else { + adjustIsolatedMargin.safeSet("AmountPercent", null) + } + } else if (type == AdjustIsolatedMarginInputField.AmountPercent) { + adjustIsolatedMargin.safeSet(type.name, amountValue.toString()) + + if (freeCollateral != null) { + val amount = amountValue * freeCollateral + adjustIsolatedMargin.safeSet("Amount", amount.toString()) + } else { + adjustIsolatedMargin.safeSet("Amount", null) + } + } + + changes = getStateChanges(subaccountNumbers) } AdjustIsolatedMarginInputField.ChildSubaccountNumber -> { - val childSubaccountNumber = parser.asInt(data) - adjustIsolatedMargin.safeSet(type.name, childSubaccountNumber) - val subaccountNumbers = if (childSubaccountNumber != null) { - iListOf(parentSubaccountNumber, childSubaccountNumber) - } else { - iListOf(parentSubaccountNumber) + var updatedSubaccountNumbers = iListOf(parentSubaccountNumber) + val updatedChildSubaccountNumber = parser.asInt(data) + adjustIsolatedMargin.safeSet(type.name, updatedChildSubaccountNumber) + if (updatedChildSubaccountNumber != null) { + updatedSubaccountNumbers = iListOf(parentSubaccountNumber, updatedChildSubaccountNumber) } - changes = StateChanges( - iListOf(Changes.wallet, Changes.subaccount, Changes.input), - null, - subaccountNumbers, - ) + changes = getStateChanges(updatedSubaccountNumbers) } - else -> {} } } else { error = cannotModify(type.name) @@ -113,6 +137,16 @@ fun TradingStateMachine.adjustIsolatedMargin( return StateResponse(state, changes, if (error != null) iListOf(error) else null) } +fun getStateChanges( + subaccountNumbers: IList, +): StateChanges { + return StateChanges( + iListOf(Changes.wallet, Changes.subaccount, Changes.input), + null, + subaccountNumbers, + ) +} + fun TradingStateMachine.validAdjustIsolatedMarginInput( adjustIsolatedMargin: Map, parentSubaccountNumber: Int?, @@ -130,6 +164,10 @@ fun TradingStateMachine.validAdjustIsolatedMarginInput( val amount = parser.asDouble(adjustIsolatedMargin["Amount"]) return amount == null || amount > 0 } + AdjustIsolatedMarginInputField.AmountPercent.name -> { + val amountPercent = parser.asDouble(adjustIsolatedMargin["AmountPercent"]) + return amountPercent == null || amountPercent > 0 + } AdjustIsolatedMarginInputField.ChildSubaccountNumber.name -> { val childSubaccountNumber = parser.asInt(adjustIsolatedMargin["ChildSubaccountNumber"]) return childSubaccountNumber == null || childSubaccountNumber % NUM_PARENT_SUBACCOUNTS == parentSubaccountNumber diff --git a/src/commonMain/kotlin/exchange.dydx.abacus/state/v2/supervisor/SubaccountSupervisor.kt b/src/commonMain/kotlin/exchange.dydx.abacus/state/v2/supervisor/SubaccountSupervisor.kt index 5e2fc8f40..10ae94c96 100644 --- a/src/commonMain/kotlin/exchange.dydx.abacus/state/v2/supervisor/SubaccountSupervisor.kt +++ b/src/commonMain/kotlin/exchange.dydx.abacus/state/v2/supervisor/SubaccountSupervisor.kt @@ -4,6 +4,7 @@ import exchange.dydx.abacus.calculator.TriggerOrdersConstants.TRIGGER_ORDER_DEFA import exchange.dydx.abacus.output.Notification import exchange.dydx.abacus.output.SubaccountOrder import exchange.dydx.abacus.output.TransferRecordType +import exchange.dydx.abacus.output.input.IsolatedMarginAdjustmentType import exchange.dydx.abacus.output.input.OrderStatus import exchange.dydx.abacus.output.input.OrderType import exchange.dydx.abacus.output.input.TradeInputGoodUntil @@ -1277,12 +1278,25 @@ internal class SubaccountSupervisor( val isolatedMarginAdjustment = stateMachine.state?.input?.adjustIsolatedMargin ?: error("AdjustIsolatedMarginInput is null") val amount = isolatedMarginAdjustment.amount ?: error("amount is null") val childSubaccountNumber = isolatedMarginAdjustment.childSubaccountNumber ?: error("childSubaccountNumber is null") + val type = isolatedMarginAdjustment.type + + val recipientSubaccountNumber = if (type == IsolatedMarginAdjustmentType.Add) { + childSubaccountNumber + } else { + subaccountNumber + } + + val sourceSubaccountNumber = if (type == IsolatedMarginAdjustmentType.Add) { + subaccountNumber + } else { + childSubaccountNumber + } return HumanReadableSubaccountTransferPayload( - subaccountNumber, + sourceSubaccountNumber, amount, accountAddress, - childSubaccountNumber, + recipientSubaccountNumber, ) } diff --git a/src/commonTest/kotlin/exchange.dydx.abacus/payload/AdjustIsolatedMarginInputTests.kt b/src/commonTest/kotlin/exchange.dydx.abacus/payload/AdjustIsolatedMarginInputTests.kt index e60d945bc..6a328a935 100644 --- a/src/commonTest/kotlin/exchange.dydx.abacus/payload/AdjustIsolatedMarginInputTests.kt +++ b/src/commonTest/kotlin/exchange.dydx.abacus/payload/AdjustIsolatedMarginInputTests.kt @@ -45,6 +45,7 @@ class AdjustIsolatedMarginInputTests : V4BaseTests() { testMarginAddition() testMarginRemoval() testZeroAmount() + testMarginAmountPercent() } private fun testChildSubaccountNumberInput() { @@ -203,4 +204,82 @@ class AdjustIsolatedMarginInputTests : V4BaseTests() { """.trimIndent(), ) } + + private fun testMarginAmountPercent() { + test( + { + perp.adjustIsolatedMargin(IsolatedMarginAdjustmentType.Add.name, AdjustIsolatedMarginInputField.Type, 0) + }, + """ + { + "input": { + "current": "adjustIsolatedMargin", + "adjustIsolatedMargin": { + "Type": "Add", + "Amount": null, + "AmountPercent": null + } + } + } + """.trimIndent(), + ) + + test( + { + perp.adjustIsolatedMargin("0.1", AdjustIsolatedMarginInputField.AmountPercent, 0) + }, + """ + { + "input": { + "current": "adjustIsolatedMargin", + "adjustIsolatedMargin": { + "Type": "Add", + "AmountPercent": "0.1", + "Amount": "8882.656169898173" + } + } + } + """.trimIndent(), + ) + + test( + { + perp.adjustIsolatedMargin( + IsolatedMarginAdjustmentType.Remove.name, + AdjustIsolatedMarginInputField.Type, + 0, + ) + }, + """ + { + "input": { + "current": "adjustIsolatedMargin", + "adjustIsolatedMargin": { + "Type": "Remove", + "Amount": null, + "AmountPercent": null + } + } + } + """.trimIndent(), + ) + + test( + { + perp.adjustIsolatedMargin("0.1", AdjustIsolatedMarginInputField.AmountPercent, 0) + }, + """ + { + "input": { + "current": "adjustIsolatedMargin", + "adjustIsolatedMargin": { + "Type": "Remove", + "AmountPercent": "0.1", + "Amount": "79.62439999999999" + } + } + } + """.trimIndent(), + ) + } } diff --git a/v4_abacus.podspec b/v4_abacus.podspec index cbe4baa63..f478286ef 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.7.21' + spec.version = '1.7.22' spec.homepage = 'https://github.com/dydxprotocol/v4-abacus' spec.source = { :http=> ''} spec.authors = ''