Skip to content
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
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import com.getcode.opencode.controllers.TransactionOperations
import com.getcode.opencode.model.accounts.AccountCluster
import com.getcode.opencode.model.accounts.AccountFilter
import com.getcode.opencode.model.accounts.AccountType
import com.getcode.opencode.model.financial.Fiat
import com.getcode.solana.keys.Mint
import com.getcode.solana.keys.base58
import com.getcode.utils.TraceType
Expand All @@ -23,21 +24,28 @@ class UsdcDepositSweep(
private val transactionOperations: TransactionOperations,
private val accountController: AccountController,
private val tokenCoordinator: TokenCoordinator,
private val balancePoller: BalancePoller,
private val maxRetries: Int = MAX_RETRIES,
private val initialDelay: Duration = INITIAL_DELAY,
private val backoffFactor: Double = BACKOFF_FACTOR,
private val pollInterval: Duration = POLL_INTERVAL,
private val pollMaxAttempts: Int = POLL_MAX_ATTEMPTS,
) {
@Inject constructor(
transactionOperations: TransactionOperations,
accountController: AccountController,
tokenCoordinator: TokenCoordinator,
balancePoller: BalancePoller,
) : this(
transactionOperations = transactionOperations,
accountController = accountController,
tokenCoordinator = tokenCoordinator,
balancePoller = balancePoller,
maxRetries = MAX_RETRIES,
initialDelay = INITIAL_DELAY,
backoffFactor = BACKOFF_FACTOR
backoffFactor = BACKOFF_FACTOR,
pollInterval = POLL_INTERVAL,
pollMaxAttempts = POLL_MAX_ATTEMPTS,
)

private val scope = CoroutineScope(Dispatchers.IO + SupervisorJob())
Expand Down Expand Up @@ -75,7 +83,19 @@ class UsdcDepositSweep(
amount = amount,
).onSuccess {
trace(tag = TAG, message = "USDC→USDF sweep completed")
tokenCoordinator.update()
balancePoller.awaitBalanceChange(
mint = Mint.usdf,
baseline = Fiat.Zero,
predicate = { _, current -> current.hasDisplayableValue },
interval = pollInterval,
maxAttempts = pollMaxAttempts,
).onFailure { error ->
trace(
tag = TAG,
message = "USDF balance poll timed out: ${error.message}",
type = TraceType.Log,
)
}
}.onFailure { error ->
trace(tag = TAG, message = "USDC→USDF sweep failed: ${error.message}", error = error)
}
Expand All @@ -92,5 +112,7 @@ class UsdcDepositSweep(
private const val MAX_RETRIES = 5
private val INITIAL_DELAY = 5.seconds
private const val BACKOFF_FACTOR = 2.0
internal val POLL_INTERVAL = 5.seconds
internal const val POLL_MAX_ATTEMPTS = 12
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import com.getcode.opencode.controllers.TransactionOperations
import com.getcode.opencode.model.accounts.AccountCluster
import com.getcode.opencode.model.accounts.AccountInfo
import com.getcode.opencode.model.accounts.AccountType
import com.getcode.opencode.model.financial.Fiat
import com.getcode.solana.keys.Mint
import com.getcode.solana.keys.PublicKey
import io.mockk.coEvery
import io.mockk.coVerify
Expand All @@ -24,20 +26,28 @@ class UsdcDepositSweepTest {
private val transactionOperations: TransactionOperations = mockk(relaxed = true)
private val accountController: AccountController = mockk(relaxed = true)
private val tokenCoordinator: TokenCoordinator = mockk(relaxed = true)
private val balancePoller: BalancePoller = mockk(relaxed = true)

private val owner: AccountCluster = mockk(relaxed = true)

private lateinit var sweep: UsdcDepositSweep

@Before
fun setUp() {
coEvery {
balancePoller.awaitBalanceChange(any(), any(), any(), any(), any())
} returns Result.success(Fiat.Zero)

sweep = UsdcDepositSweep(
transactionOperations = transactionOperations,
accountController = accountController,
tokenCoordinator = tokenCoordinator,
balancePoller = balancePoller,
maxRetries = 3,
initialDelay = 10.milliseconds,
backoffFactor = 1.0,
pollInterval = 10.milliseconds,
pollMaxAttempts = 2,
)
}

Expand Down Expand Up @@ -144,7 +154,7 @@ class UsdcDepositSweepTest {
}

@Test
fun `calls tokenCoordinator update on successful swap`() = runTest {
fun `polls for USDF balance on successful swap`() = runTest {
stubUsdcAccount(balance = 1_000_000L)
coEvery { transactionOperations.swapUsdc(any(), any()) } returns Result.success(Unit)

Expand All @@ -153,12 +163,18 @@ class UsdcDepositSweepTest {
Thread.sleep(200)

coVerify {
tokenCoordinator.update()
balancePoller.awaitBalanceChange(
mint = Mint.usdf,
baseline = Fiat.Zero,
predicate = any(),
interval = 10.milliseconds,
maxAttempts = 2,
)
}
}

@Test
fun `does not call tokenCoordinator update on failed swap`() = runTest {
fun `does not poll for USDF balance when swap fails`() = runTest {
stubUsdcAccount(balance = 1_000_000L)
coEvery {
transactionOperations.swapUsdc(any(), any())
Expand All @@ -169,7 +185,24 @@ class UsdcDepositSweepTest {
Thread.sleep(200)

coVerify(exactly = 0) {
tokenCoordinator.update()
balancePoller.awaitBalanceChange(any(), any(), any(), any(), any())
}
}

@Test
fun `completes gracefully when USDF balance poll times out`() = runTest {
stubUsdcAccount(balance = 1_000_000L)
coEvery { transactionOperations.swapUsdc(any(), any()) } returns Result.success(Unit)
coEvery {
balancePoller.awaitBalanceChange(any(), any(), any(), any(), any())
} returns Result.failure(BalancePollError.Timeout(Mint.usdf, 2))

sweep.execute(owner)
advanceUntilIdle()
Thread.sleep(200)

coVerify {
balancePoller.awaitBalanceChange(any(), any(), any(), any(), any())
}
}

Expand Down
Loading