From 8fb357e169d82bd4d338d9fffd9ad28ed7bc45da Mon Sep 17 00:00:00 2001 From: jeremy lee Date: Wed, 12 Jun 2024 21:09:24 +0200 Subject: [PATCH 1/5] fix: qa route endpoint feat: enable withdrawals. also small fixes Bump version cleanup suppression remove squid integrator id check from skip method address detekt complaint cleanup --- .../state/v2/supervisor/AccountSupervisor.kt | 57 ++++++++++++ .../router/skip/SkipRouteProcessorTests.kt | 90 +++++++++++++++++++ 2 files changed, 147 insertions(+) diff --git a/src/commonMain/kotlin/exchange.dydx.abacus/state/v2/supervisor/AccountSupervisor.kt b/src/commonMain/kotlin/exchange.dydx.abacus/state/v2/supervisor/AccountSupervisor.kt index 13096d860..3a106c6f8 100644 --- a/src/commonMain/kotlin/exchange.dydx.abacus/state/v2/supervisor/AccountSupervisor.kt +++ b/src/commonMain/kotlin/exchange.dydx.abacus/state/v2/supervisor/AccountSupervisor.kt @@ -654,6 +654,63 @@ internal open class AccountSupervisor( } } } + private fun transferNobleBalanceSkip(amount: BigDecimal) { + val url = helper.configs.skipV2MsgsDirect() + val fromChain = helper.configs.nobleChainId() + val fromToken = helper.configs.nobleDenom() + val nobleAddress = accountAddress.toNobleAddress() + val chainId = helper.environment.dydxChainId + val dydxTokenDemon = helper.environment.tokens["usdc"]?.denom + if ( + fromChain != null && + fromToken != null && + nobleAddress != null && + chainId != null && + dydxTokenDemon != null + ) { + val body: Map = mapOf( + "amount_in" to amount.toPlainString(), +// from noble denom and chain + "source_asset_denom" to fromToken, + "source_asset_chain_id" to fromChain, +// to dydx denom and chain + "dest_asset_denom" to dydxTokenDemon, + "dest_asset_chain_id" to chainId, + "chain_ids_to_addresses" to mapOf( + fromChain to nobleAddress, + chainId to accountAddress.toString(), + ), + "slippage_tolerance_percent" to SLIPPAGE_PERCENT, + ) + val header = + iMapOf( + "Content-Type" to "application/json", + ) + helper.post(url, header, body.toJsonPrettyPrint()) { _, response, code, _ -> + if (response == null) { + val json = helper.parser.decodeJsonObject(response) + if (json != null) { + val skipRoutePayloadProcessor = SkipRoutePayloadProcessor(parser = helper.parser) + val processedPayload = skipRoutePayloadProcessor.received(existing = mapOf(), payload = json) + val ibcPayload = + helper.parser.asString( + processedPayload.get("data"), + ) + if (ibcPayload != null) { + helper.transaction(TransactionType.SendNobleIBC, ibcPayload) { + val error = helper.parseTransactionResponse(it) + if (error != null) { + Logger.e { "transferNobleBalanceSkip error: $error" } + } + } + } + } + } else { + Logger.e { "transferNobleBalanceSkip error, code: $code" } + } + } + } + } private fun transferNobleBalanceSkip(amount: BigDecimal) { val url = helper.configs.skipV2MsgsDirect() diff --git a/src/commonTest/kotlin/exchange.dydx.abacus/processor/router/skip/SkipRouteProcessorTests.kt b/src/commonTest/kotlin/exchange.dydx.abacus/processor/router/skip/SkipRouteProcessorTests.kt index e44bf143c..bc603b1bf 100644 --- a/src/commonTest/kotlin/exchange.dydx.abacus/processor/router/skip/SkipRouteProcessorTests.kt +++ b/src/commonTest/kotlin/exchange.dydx.abacus/processor/router/skip/SkipRouteProcessorTests.kt @@ -174,6 +174,96 @@ class SkipRouteProcessorTests { assertEquals(expected, result) } + /** + * Tests a CCTP withdrawal initiated from the cctpToNobleSkip method + * This payload is used by the chain transaction method WithdrawToNobleIBC + * This processes a Dydx -> Noble CCTP transaction + */ + @Test + fun testReceivedCCTPDydxToNoble() { + val payload = skipRouteMock.payloadCCTPDydxToNoble + val result = skipRouteProcessor.received(existing = mapOf(), payload = templateToJson(payload), decimals = 6.0) + val jsonEncoder = JsonEncoder() + val expectedMsg = mapOf( + "sourcePort" to "transfer", + "sourceChannel" to "channel-0", + "token" to mapOf( + "denom" to "ibc/8E27BA2D5493AF5636760E354E46004562C46AB7EC0CC4C1CA14E9E20E2545B5", + "amount" to "10996029", + ), + "sender" to "dydx1nhzuazjhyfu474er6v4ey8zn6wa5fy6g2dgp7s", + "receiver" to "noble1nhzuazjhyfu474er6v4ey8zn6wa5fy6gthndxf", + "timeoutHeight" to mapOf(), + "timeoutTimestamp" to 1718308711061386287, + ) + val expectedData = jsonEncoder.encode( + mapOf( + "msg" to expectedMsg, + "value" to expectedMsg, + "msgTypeUrl" to "/ibc.applications.transfer.v1.MsgTransfer", + "typeUrl" to "/ibc.applications.transfer.v1.MsgTransfer", + ), + ) + val expected = mapOf( + "toAmountUSD" to 11.01, + "toAmount" to 10.996029, + "slippage" to "1", + "requestPayload" to mapOf( + "fromChainId" to "dydx-mainnet-1", + "fromAddress" to "ibc/8E27BA2D5493AF5636760E354E46004562C46AB7EC0CC4C1CA14E9E20E2545B5", + "toChainId" to "noble-1", + "toAddress" to "uusdc", + "data" to expectedData, + ), + ) + assertEquals(expected, result) + } + + /** + * Tests a CCTP autosweep from the transferNobleBalance method + * This payload is used by the chain transaction method sendNobleIBC + * This processes a Noble -> Dydx CCTP transaction + */ + @Test + fun testReceivedCCTPNobleToDydx() { + val payload = skipRouteMock.payloadCCTPNobleToDydx + val result = skipRouteProcessor.received(existing = mapOf(), payload = templateToJson(payload), decimals = 6.0) + val jsonEncoder = JsonEncoder() + val expectedMsg = mapOf( + "sourcePort" to "transfer", + "sourceChannel" to "channel-33", + "token" to mapOf( + "denom" to "uusdc", + "amount" to "5884", + ), + "sender" to "noble1nhzuazjhyfu474er6v4ey8zn6wa5fy6gthndxf", + "receiver" to "dydx1nhzuazjhyfu474er6v4ey8zn6wa5fy6g2dgp7s", + "timeoutHeight" to mapOf(), + "timeoutTimestamp" to 1718318348813666048, + ) + val expectedData = jsonEncoder.encode( + mapOf( + "msg" to expectedMsg, + "value" to expectedMsg, + "msgTypeUrl" to "/ibc.applications.transfer.v1.MsgTransfer", + "typeUrl" to "/ibc.applications.transfer.v1.MsgTransfer", + ), + ) + val expected = mapOf( + "toAmountUSD" to 0.01, + "toAmount" to 0.005884, + "slippage" to "1", + "requestPayload" to mapOf( + "fromChainId" to "noble-1", + "fromAddress" to "uusdc", + "toChainId" to "dydx-mainnet-1", + "toAddress" to "ibc/8E27BA2D5493AF5636760E354E46004562C46AB7EC0CC4C1CA14E9E20E2545B5", + "data" to expectedData, + ), + ) + assertEquals(expected, result) + } + @Test fun testReceivedError() { val payload = skipRouteMock.payloadError From 496049415d60b894ecd418556d1ac255ea887fa1 Mon Sep 17 00:00:00 2001 From: jeremy lee Date: Fri, 14 Jun 2024 13:56:27 -0400 Subject: [PATCH 2/5] cleanup --- .../state/v2/supervisor/AccountSupervisor.kt | 79 +++++++++---------- 1 file changed, 36 insertions(+), 43 deletions(-) diff --git a/src/commonMain/kotlin/exchange.dydx.abacus/state/v2/supervisor/AccountSupervisor.kt b/src/commonMain/kotlin/exchange.dydx.abacus/state/v2/supervisor/AccountSupervisor.kt index 3a106c6f8..73ecc69cf 100644 --- a/src/commonMain/kotlin/exchange.dydx.abacus/state/v2/supervisor/AccountSupervisor.kt +++ b/src/commonMain/kotlin/exchange.dydx.abacus/state/v2/supervisor/AccountSupervisor.kt @@ -654,60 +654,53 @@ internal open class AccountSupervisor( } } } + private fun transferNobleBalanceSkip(amount: BigDecimal) { val url = helper.configs.skipV2MsgsDirect() val fromChain = helper.configs.nobleChainId() val fromToken = helper.configs.nobleDenom() - val nobleAddress = accountAddress.toNobleAddress() - val chainId = helper.environment.dydxChainId - val dydxTokenDemon = helper.environment.tokens["usdc"]?.denom - if ( - fromChain != null && - fromToken != null && - nobleAddress != null && - chainId != null && - dydxTokenDemon != null - ) { - val body: Map = mapOf( - "amount_in" to amount.toPlainString(), + val nobleAddress = accountAddress.toNobleAddress() ?: return + val chainId = helper.environment.dydxChainId ?: return + val dydxTokenDemon = helper.environment.tokens["usdc"]?.denom ?: return + val body: Map = mapOf( + "amount_in" to amount.toPlainString(), // from noble denom and chain - "source_asset_denom" to fromToken, - "source_asset_chain_id" to fromChain, + "source_asset_denom" to fromToken, + "source_asset_chain_id" to fromChain, // to dydx denom and chain - "dest_asset_denom" to dydxTokenDemon, - "dest_asset_chain_id" to chainId, - "chain_ids_to_addresses" to mapOf( - fromChain to nobleAddress, - chainId to accountAddress.toString(), - ), - "slippage_tolerance_percent" to SLIPPAGE_PERCENT, + "dest_asset_denom" to dydxTokenDemon, + "dest_asset_chain_id" to chainId, + "chain_ids_to_addresses" to mapOf( + fromChain to nobleAddress, + chainId to accountAddress, + ), + "slippage_tolerance_percent" to SLIPPAGE_PERCENT, + ) + val header = + iMapOf( + "Content-Type" to "application/json", ) - val header = - iMapOf( - "Content-Type" to "application/json", - ) - helper.post(url, header, body.toJsonPrettyPrint()) { _, response, code, _ -> - if (response == null) { - val json = helper.parser.decodeJsonObject(response) - if (json != null) { - val skipRoutePayloadProcessor = SkipRoutePayloadProcessor(parser = helper.parser) - val processedPayload = skipRoutePayloadProcessor.received(existing = mapOf(), payload = json) - val ibcPayload = - helper.parser.asString( - processedPayload.get("data"), - ) - if (ibcPayload != null) { - helper.transaction(TransactionType.SendNobleIBC, ibcPayload) { - val error = helper.parseTransactionResponse(it) - if (error != null) { - Logger.e { "transferNobleBalanceSkip error: $error" } - } + helper.post(url, header, body.toJsonPrettyPrint()) { _, response, code, _ -> + if (response == null) { + val json = helper.parser.decodeJsonObject(response) + if (json != null) { + val skipRoutePayloadProcessor = SkipRoutePayloadProcessor(parser = helper.parser) + val processedPayload = skipRoutePayloadProcessor.received(existing = mapOf(), payload = json) + val ibcPayload = + helper.parser.asString( + processedPayload.get("data"), + ) + if (ibcPayload != null) { + helper.transaction(TransactionType.SendNobleIBC, ibcPayload) { + val error = helper.parseTransactionResponse(it) + if (error != null) { + Logger.e { "transferNobleBalanceSkip error: $error" } } } } - } else { - Logger.e { "transferNobleBalanceSkip error, code: $code" } } + } else { + Logger.e { "transferNobleBalanceSkip error, code: $code" } } } } From c7f84a27a284f3f9d01b3657b2be367a50ff2724 Mon Sep 17 00:00:00 2001 From: jeremy lee Date: Fri, 14 Jun 2024 14:13:25 -0400 Subject: [PATCH 3/5] address detekt complaint about noble denom --- .../state/v2/supervisor/AccountSupervisor.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/commonMain/kotlin/exchange.dydx.abacus/state/v2/supervisor/AccountSupervisor.kt b/src/commonMain/kotlin/exchange.dydx.abacus/state/v2/supervisor/AccountSupervisor.kt index 73ecc69cf..2c2ee9a06 100644 --- a/src/commonMain/kotlin/exchange.dydx.abacus/state/v2/supervisor/AccountSupervisor.kt +++ b/src/commonMain/kotlin/exchange.dydx.abacus/state/v2/supervisor/AccountSupervisor.kt @@ -658,7 +658,7 @@ internal open class AccountSupervisor( private fun transferNobleBalanceSkip(amount: BigDecimal) { val url = helper.configs.skipV2MsgsDirect() val fromChain = helper.configs.nobleChainId() - val fromToken = helper.configs.nobleDenom() + val fromToken = helper.configs.nobleDenom val nobleAddress = accountAddress.toNobleAddress() ?: return val chainId = helper.environment.dydxChainId ?: return val dydxTokenDemon = helper.environment.tokens["usdc"]?.denom ?: return From a5899cb83ae2e693805061af65cc91efa5d0856d Mon Sep 17 00:00:00 2001 From: jeremy lee Date: Mon, 17 Jun 2024 11:54:06 -0400 Subject: [PATCH 4/5] feat: enable testnet --- .../processor/router/IRouterProcessor.kt | 1 + .../processor/router/skip/SkipProcessor.kt | 22 ++++++++++++++----- .../manager/configs/V4StateManagerConfigs.kt | 17 ++++++++++---- .../v2/supervisor/OnboardingSupervisor.kt | 11 +++++----- .../exchange.dydx.abacus/utils/Constants.kt | 3 +++ 5 files changed, 38 insertions(+), 16 deletions(-) diff --git a/src/commonMain/kotlin/exchange.dydx.abacus/processor/router/IRouterProcessor.kt b/src/commonMain/kotlin/exchange.dydx.abacus/processor/router/IRouterProcessor.kt index 3bfe6c026..ae3d12265 100644 --- a/src/commonMain/kotlin/exchange.dydx.abacus/processor/router/IRouterProcessor.kt +++ b/src/commonMain/kotlin/exchange.dydx.abacus/processor/router/IRouterProcessor.kt @@ -8,6 +8,7 @@ interface IRouterProcessor { var tokens: List? var chains: List? var exchangeDestinationChainId: String? + fun receivedChains( existing: Map?, payload: Map diff --git a/src/commonMain/kotlin/exchange.dydx.abacus/processor/router/skip/SkipProcessor.kt b/src/commonMain/kotlin/exchange.dydx.abacus/processor/router/skip/SkipProcessor.kt index 857074614..0903d6474 100644 --- a/src/commonMain/kotlin/exchange.dydx.abacus/processor/router/skip/SkipProcessor.kt +++ b/src/commonMain/kotlin/exchange.dydx.abacus/processor/router/skip/SkipProcessor.kt @@ -10,6 +10,7 @@ import exchange.dydx.abacus.processor.router.squid.SquidStatusProcessor import exchange.dydx.abacus.protocols.ParserProtocol import exchange.dydx.abacus.state.internalstate.InternalTransferInputState import exchange.dydx.abacus.state.manager.CctpConfig.cctpChainIds +import exchange.dydx.abacus.utils.NATIVE_TOKEN_DEFAULT_ADDRESS import exchange.dydx.abacus.utils.mutable import exchange.dydx.abacus.utils.safeSet @@ -47,9 +48,12 @@ internal class SkipProcessor( modified = it.mutable() } val chainOptions = chainOptions() - internalState.chains = chainOptions val selectedChainId = defaultChainId() +// We diff based on map values in order to determine whether to return new state +// Until we diff on `internalState` changes we need to update old map state as well + modified.safeSet("transfer.depositOptions.chains", chainOptions) + modified.safeSet("transfer.withdrawOptions.chains", chainOptions) modified.safeSet("transfer.chain", selectedChainId) selectedChainId?.let { internalState.chainResources = chainResources(chainId = selectedChainId) @@ -118,7 +122,6 @@ internal class SkipProcessor( return receivedRoute(existing, payload, requestId) } -// TODO: deduplicate this from squid override fun usdcAmount(data: Map): Double? { var toAmountUSD = parser.asString(parser.value(data, "transfer.route.toAmountUSD")) toAmountUSD = toAmountUSD?.replace(",", "") @@ -141,12 +144,19 @@ internal class SkipProcessor( override fun updateTokensDefaults(modified: MutableMap, selectedChainId: String?) { val tokenOptions = tokenOptions(selectedChainId) internalState.tokens = tokenOptions - modified.safeSet("transfer.token", defaultTokenAddress(selectedChainId)) + modified.safeSet("transfer.token ", defaultTokenAddress(selectedChainId)) + modified.safeSet("transfer.depositOptions.tokens", tokenOptions) + modified.safeSet("transfer.withdrawalOptions.tokens", tokenOptions) internalState.tokenResources = tokenResources(selectedChainId) } + private fun getChainById(chainId: String): Map? { + return parser.asNativeMap(this.chains?.find { parser.asString(parser.asNativeMap(it)?.get("chain_id")) == chainId }) + } + override fun defaultChainId(): String? { - val selectedChain = parser.asNativeMap(this.chains?.find { parser.asString(parser.asNativeMap(it)?.get("chain_id")) == "1" }) +// eth mainnet chainId is 1 + val selectedChain = getChainById("1") ?: parser.asNativeMap(this.chains?.firstOrNull()) return parser.asString(selectedChain?.get("chain_id")) } @@ -181,7 +191,7 @@ internal class SkipProcessor( val assetsMapForChainId = parser.asNativeMap(this.skipTokens?.get(chainIdToUse)) val assetsForChainId = parser.asNativeList(assetsMapForChainId?.get("assets")) // coinbase exchange chainId is noble-1. we only allow usdc withdrawals from it - if (chainId === "noble-1") { + if (chainId === exchangeDestinationChainId) { return assetsForChainId?.filter { parser.asString(parser.asNativeMap(it)?.get("denom")) == "uusdc" } @@ -198,7 +208,7 @@ internal class SkipProcessor( val denom = parser.asString(token["denom"]) if (denom?.endsWith("native") == true) { token["skipDenom"] = denom - token["denom"] = "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE" + token["denom"] = NATIVE_TOKEN_DEFAULT_ADDRESS } filteredTokens.add(token.toMap()) } diff --git a/src/commonMain/kotlin/exchange.dydx.abacus/state/manager/configs/V4StateManagerConfigs.kt b/src/commonMain/kotlin/exchange.dydx.abacus/state/manager/configs/V4StateManagerConfigs.kt index 9936755c0..7489cb3bc 100644 --- a/src/commonMain/kotlin/exchange.dydx.abacus/state/manager/configs/V4StateManagerConfigs.kt +++ b/src/commonMain/kotlin/exchange.dydx.abacus/state/manager/configs/V4StateManagerConfigs.kt @@ -103,16 +103,20 @@ class V4StateManagerConfigs( return if (environment.isMainNet) "noble-1" else "grand-1" } - fun osmosisChainId(): String? { - return if (environment.isMainNet) "osmosis-1" else "osmosis-5" + fun osmosisChainId(): String { + return if (environment.isMainNet) "osmosis-1" else "osmo-test-5" + } + + fun neutronChainId(): String { + return if (environment.isMainNet) "neutron-1" else "pion-1" } fun skipV1Chains(): String { - return "$skipHost/v1/info/chains?include_evm=true" + return "$skipHost/v1/info/chains?include_evm=true$onlyTestnets" } fun skipV1Assets(): String { - return "$skipHost/v1/fungible/assets?include_evm_assets=true" + return "$skipHost/v1/fungible/assets?include_evm_assets=true$onlyTestnets" } fun skipV2MsgsDirect(): String { @@ -121,6 +125,11 @@ class V4StateManagerConfigs( val nobleDenom = "uusdc" + private val onlyTestnets: String + get() { + return if (environment.isMainNet) "" else "&only_testnets=true" + } + private val skipHost: String get() { return "https://api.skip.money" diff --git a/src/commonMain/kotlin/exchange.dydx.abacus/state/v2/supervisor/OnboardingSupervisor.kt b/src/commonMain/kotlin/exchange.dydx.abacus/state/v2/supervisor/OnboardingSupervisor.kt index a7e9735d6..0afa24c9f 100644 --- a/src/commonMain/kotlin/exchange.dydx.abacus/state/v2/supervisor/OnboardingSupervisor.kt +++ b/src/commonMain/kotlin/exchange.dydx.abacus/state/v2/supervisor/OnboardingSupervisor.kt @@ -1018,10 +1018,9 @@ internal class OnboardingSupervisor( return } if (fromAmount <= 0) return - val osmosisAddress = accountAddress.toOsmosisAddress() ?: return - val nobleAddress = accountAddress.toNobleAddress() ?: return - val osmosisChainId = helper.configs.osmosisChainId() ?: return + val osmosisChainId = helper.configs.osmosisChainId() val nobleChainId = helper.configs.nobleChainId() + val neutronChainId = helper.configs.neutronChainId() val fromChain = helper.environment.dydxChainId ?: return val fromToken = helper.environment.tokens["usdc"]?.denom ?: return val fromAmountString = helper.parser.asString(fromAmount) ?: return @@ -1034,9 +1033,9 @@ internal class OnboardingSupervisor( "dest_asset_chain_id" to toChain, "chain_ids_to_addresses" to mapOf( fromChain to accountAddress, - osmosisChainId to osmosisAddress, - nobleChainId to nobleAddress, - "neutron-1" to accountAddress.toNeutronAddress(), + osmosisChainId to accountAddress.toOsmosisAddress(), + nobleChainId to accountAddress.toNobleAddress(), + neutronChainId to accountAddress.toNeutronAddress(), toChain to toAddress, ), "allow_multi_tx" to true, diff --git a/src/commonMain/kotlin/exchange.dydx.abacus/utils/Constants.kt b/src/commonMain/kotlin/exchange.dydx.abacus/utils/Constants.kt index c455791de..c4c0864b1 100644 --- a/src/commonMain/kotlin/exchange.dydx.abacus/utils/Constants.kt +++ b/src/commonMain/kotlin/exchange.dydx.abacus/utils/Constants.kt @@ -17,3 +17,6 @@ internal const val MAX_LEVERAGE_BUFFER_PERCENT = 0.98 // Order flags internal const val SHORT_TERM_ORDER_FLAGS = 0 internal const val CONDITIONAL_ORDER_FLAGS = 32 + +// Asset Constants +internal const val NATIVE_TOKEN_DEFAULT_ADDRESS = "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE" From 33daf476c026dd8fb2191e8e22fc484274d7ad8b Mon Sep 17 00:00:00 2001 From: jeremy lee Date: Mon, 17 Jun 2024 11:57:28 -0400 Subject: [PATCH 5/5] cleanup --- .../exchange.dydx.abacus/processor/router/skip/SkipProcessor.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/commonMain/kotlin/exchange.dydx.abacus/processor/router/skip/SkipProcessor.kt b/src/commonMain/kotlin/exchange.dydx.abacus/processor/router/skip/SkipProcessor.kt index 0903d6474..29a75043d 100644 --- a/src/commonMain/kotlin/exchange.dydx.abacus/processor/router/skip/SkipProcessor.kt +++ b/src/commonMain/kotlin/exchange.dydx.abacus/processor/router/skip/SkipProcessor.kt @@ -144,7 +144,7 @@ internal class SkipProcessor( override fun updateTokensDefaults(modified: MutableMap, selectedChainId: String?) { val tokenOptions = tokenOptions(selectedChainId) internalState.tokens = tokenOptions - modified.safeSet("transfer.token ", defaultTokenAddress(selectedChainId)) + modified.safeSet("transfer.token", defaultTokenAddress(selectedChainId)) modified.safeSet("transfer.depositOptions.tokens", tokenOptions) modified.safeSet("transfer.withdrawalOptions.tokens", tokenOptions) internalState.tokenResources = tokenResources(selectedChainId)