From 94fea25dfa12cf36e6b7cd524ec1d2ca2f6d4aa7 Mon Sep 17 00:00:00 2001 From: Oskar <43062492+oskarvu@users.noreply.github.com> Date: Tue, 18 Jun 2024 09:03:46 +0200 Subject: [PATCH] Add hooks for gnosis sexy dai interactions (#104) --- .../useSexyDaiDeposit.test.ts | 79 +++++++++++++++++++ .../useSexyDaiDeposit.ts | 51 ++++++++++++ .../useSexyDaiRedeemAll.test.ts | 62 +++++++++++++++ .../useSexyDaiRedeemAll.ts | 46 +++++++++++ .../useSexyDaiWithdraw.test.ts | 78 ++++++++++++++++++ .../useSexyDaiWithdraw.ts | 51 ++++++++++++ .../useVaultRedeem.ts | 2 +- 7 files changed, 368 insertions(+), 1 deletion(-) create mode 100644 packages/app/src/domain/tokenized-vault-operations/useSexyDaiDeposit.test.ts create mode 100644 packages/app/src/domain/tokenized-vault-operations/useSexyDaiDeposit.ts create mode 100644 packages/app/src/domain/tokenized-vault-operations/useSexyDaiRedeemAll.test.ts create mode 100644 packages/app/src/domain/tokenized-vault-operations/useSexyDaiRedeemAll.ts create mode 100644 packages/app/src/domain/tokenized-vault-operations/useSexyDaiWithdraw.test.ts create mode 100644 packages/app/src/domain/tokenized-vault-operations/useSexyDaiWithdraw.ts diff --git a/packages/app/src/domain/tokenized-vault-operations/useSexyDaiDeposit.test.ts b/packages/app/src/domain/tokenized-vault-operations/useSexyDaiDeposit.test.ts new file mode 100644 index 000000000..ec1b0896f --- /dev/null +++ b/packages/app/src/domain/tokenized-vault-operations/useSexyDaiDeposit.test.ts @@ -0,0 +1,79 @@ +import { savingsXDaiAdapterAbi, savingsXDaiAdapterAddress } from '@/config/contracts-generated' +import { BaseUnitNumber } from '@/domain/types/NumericValues' +import { testAddresses } from '@/test/integration/constants' +import { handlers } from '@/test/integration/mockTransport' +import { setupHookRenderer } from '@/test/integration/setupHookRenderer' +import { toBigInt } from '@/utils/bigNumber' +import { waitFor } from '@testing-library/react' +import { gnosis } from 'viem/chains' +import { useSexyDaiDeposit } from './useSexyDaiDeposit' + +const account = testAddresses.alice +const value = BaseUnitNumber(1) + +const hookRenderer = setupHookRenderer({ + hook: useSexyDaiDeposit, + account, + chain: gnosis, + handlers: [handlers.chainIdCall({ chainId: gnosis.id }), handlers.balanceCall({ balance: 0n, address: account })], + args: { + value, + }, +}) + +describe(useSexyDaiDeposit.name, () => { + it('is not enabled for guest ', async () => { + const { result } = hookRenderer({ account: undefined }) + + await waitFor(() => { + expect(result.current.status.kind).toBe('disabled') + }) + }) + + it('is not enabled for 0 value', async () => { + const { result } = hookRenderer({ args: { value: BaseUnitNumber(0) } }) + + await waitFor(() => { + expect(result.current.status.kind).toBe('disabled') + }) + }) + + it('is not enabled when explicitly disabled', async () => { + const { result } = hookRenderer({ args: { enabled: false, value } }) + + await waitFor(() => { + expect(result.current.status.kind).toBe('disabled') + }) + }) + + it('deposits xDAI', async () => { + const { result } = hookRenderer({ + args: { + value, + }, + extraHandlers: [ + handlers.contractCall({ + to: savingsXDaiAdapterAddress[gnosis.id], + abi: savingsXDaiAdapterAbi, + functionName: 'depositXDAI', + args: [account], + value: toBigInt(value), + from: account, + result: 1n, + }), + handlers.mineTransaction(), + ], + }) + + await waitFor(() => { + expect(result.current.status.kind).toBe('ready') + }) + expect((result.current as any).error).toBeUndefined() + + result.current.write() + + await waitFor(() => { + expect(result.current.status.kind).toBe('success') + }) + }) +}) diff --git a/packages/app/src/domain/tokenized-vault-operations/useSexyDaiDeposit.ts b/packages/app/src/domain/tokenized-vault-operations/useSexyDaiDeposit.ts new file mode 100644 index 000000000..156822622 --- /dev/null +++ b/packages/app/src/domain/tokenized-vault-operations/useSexyDaiDeposit.ts @@ -0,0 +1,51 @@ +import { savingsXDaiAdapterAbi, savingsXDaiAdapterAddress } from '@/config/contracts-generated' +import { toBigInt } from '@/utils/bigNumber' +import { useQueryClient } from '@tanstack/react-query' +import { gnosis } from 'viem/chains' +import { useAccount, useConfig } from 'wagmi' +import { ensureConfigTypes, useWrite } from '../hooks/useWrite' +import { BaseUnitNumber } from '../types/NumericValues' +import { balances } from '../wallet/balances' + +export interface UseSexyDaiDepositArgs { + value: BaseUnitNumber + onTransactionSettled?: () => void + enabled?: boolean +} + +export function useSexyDaiDeposit({ + value: _value, + onTransactionSettled, + enabled: _enabled = true, +}: UseSexyDaiDepositArgs): ReturnType { + const client = useQueryClient() + const wagmiConfig = useConfig() + + const { address: receiver } = useAccount() + const value = toBigInt(_value) + + const config = ensureConfigTypes({ + address: savingsXDaiAdapterAddress[gnosis.id], + abi: savingsXDaiAdapterAbi, + functionName: 'depositXDAI', + args: [receiver!], + value, + }) + const enabled = _enabled && value > 0 && !!receiver + + return useWrite( + { + ...config, + enabled, + }, + { + onTransactionSettled: async () => { + void client.invalidateQueries({ + queryKey: balances({ wagmiConfig, chainId: gnosis.id, account: receiver }).queryKey, + }) + + onTransactionSettled?.() + }, + }, + ) +} diff --git a/packages/app/src/domain/tokenized-vault-operations/useSexyDaiRedeemAll.test.ts b/packages/app/src/domain/tokenized-vault-operations/useSexyDaiRedeemAll.test.ts new file mode 100644 index 000000000..549106c0a --- /dev/null +++ b/packages/app/src/domain/tokenized-vault-operations/useSexyDaiRedeemAll.test.ts @@ -0,0 +1,62 @@ +import { savingsXDaiAdapterAbi, savingsXDaiAdapterAddress } from '@/config/contracts-generated' +import { testAddresses } from '@/test/integration/constants' +import { handlers } from '@/test/integration/mockTransport' +import { setupHookRenderer } from '@/test/integration/setupHookRenderer' +import { waitFor } from '@testing-library/react' +import { gnosis } from 'viem/chains' +import { useSexyDaiRedeemAll } from './useSexyDaiRedeemAll' + +const account = testAddresses.alice + +const hookRenderer = setupHookRenderer({ + hook: useSexyDaiRedeemAll, + account, + chain: gnosis, + handlers: [handlers.chainIdCall({ chainId: gnosis.id }), handlers.balanceCall({ balance: 0n, address: account })], + args: {}, +}) + +describe(useSexyDaiRedeemAll.name, () => { + it('is not enabled for guest ', async () => { + const { result } = hookRenderer({ account: undefined }) + + await waitFor(() => { + expect(result.current.status.kind).toBe('disabled') + }) + }) + + it('is not enabled when explicitly disabled', async () => { + const { result } = hookRenderer({ args: { enabled: false } }) + + await waitFor(() => { + expect(result.current.status.kind).toBe('disabled') + }) + }) + + it('redeems all xDAI', async () => { + const { result } = hookRenderer({ + extraHandlers: [ + handlers.contractCall({ + to: savingsXDaiAdapterAddress[gnosis.id], + abi: savingsXDaiAdapterAbi, + functionName: 'redeemAllXDAI', + args: [account], + from: account, + result: 1n, + }), + handlers.mineTransaction(), + ], + }) + + await waitFor(() => { + expect(result.current.status.kind).toBe('ready') + }) + expect((result.current as any).error).toBeUndefined() + + result.current.write() + + await waitFor(() => { + expect(result.current.status.kind).toBe('success') + }) + }) +}) diff --git a/packages/app/src/domain/tokenized-vault-operations/useSexyDaiRedeemAll.ts b/packages/app/src/domain/tokenized-vault-operations/useSexyDaiRedeemAll.ts new file mode 100644 index 000000000..c4ab63cea --- /dev/null +++ b/packages/app/src/domain/tokenized-vault-operations/useSexyDaiRedeemAll.ts @@ -0,0 +1,46 @@ +import { savingsXDaiAdapterAbi, savingsXDaiAdapterAddress } from '@/config/contracts-generated' +import { useQueryClient } from '@tanstack/react-query' +import { gnosis } from 'viem/chains' +import { useAccount, useConfig } from 'wagmi' +import { ensureConfigTypes, useWrite } from '../hooks/useWrite' +import { balances } from '../wallet/balances' + +export interface UseSexyDaiRedeemAllArgs { + onTransactionSettled?: () => void + enabled?: boolean +} + +// @note: 'redeemAllXDAI' function allows user to redeem all xDAI in exchange for all sDAI. +export function useSexyDaiRedeemAll({ + onTransactionSettled, + enabled: _enabled = true, +}: UseSexyDaiRedeemAllArgs): ReturnType { + const client = useQueryClient() + const wagmiConfig = useConfig() + + const { address: receiver } = useAccount() + + const config = ensureConfigTypes({ + address: savingsXDaiAdapterAddress[gnosis.id], + abi: savingsXDaiAdapterAbi, + functionName: 'redeemAllXDAI', + args: [receiver!], + }) + const enabled = _enabled && !!receiver + + return useWrite( + { + ...config, + enabled, + }, + { + onTransactionSettled: async () => { + void client.invalidateQueries({ + queryKey: balances({ wagmiConfig, chainId: gnosis.id, account: receiver }).queryKey, + }) + + onTransactionSettled?.() + }, + }, + ) +} diff --git a/packages/app/src/domain/tokenized-vault-operations/useSexyDaiWithdraw.test.ts b/packages/app/src/domain/tokenized-vault-operations/useSexyDaiWithdraw.test.ts new file mode 100644 index 000000000..67340ddef --- /dev/null +++ b/packages/app/src/domain/tokenized-vault-operations/useSexyDaiWithdraw.test.ts @@ -0,0 +1,78 @@ +import { savingsXDaiAdapterAbi, savingsXDaiAdapterAddress } from '@/config/contracts-generated' +import { BaseUnitNumber } from '@/domain/types/NumericValues' +import { testAddresses } from '@/test/integration/constants' +import { handlers } from '@/test/integration/mockTransport' +import { setupHookRenderer } from '@/test/integration/setupHookRenderer' +import { toBigInt } from '@/utils/bigNumber' +import { waitFor } from '@testing-library/react' +import { gnosis } from 'viem/chains' +import { useSexyDaiWithdraw } from './useSexyDaiWithdraw' + +const account = testAddresses.alice +const assetsAmount = BaseUnitNumber(1) + +const hookRenderer = setupHookRenderer({ + hook: useSexyDaiWithdraw, + account, + chain: gnosis, + handlers: [handlers.chainIdCall({ chainId: gnosis.id }), handlers.balanceCall({ balance: 0n, address: account })], + args: { + assetsAmount, + }, +}) + +describe(useSexyDaiWithdraw.name, () => { + it('is not enabled for guest ', async () => { + const { result } = hookRenderer({ account: undefined }) + + await waitFor(() => { + expect(result.current.status.kind).toBe('disabled') + }) + }) + + it('is not enabled for 0 assets amount', async () => { + const { result } = hookRenderer({ args: { assetsAmount: BaseUnitNumber(0) } }) + + await waitFor(() => { + expect(result.current.status.kind).toBe('disabled') + }) + }) + + it('is not enabled when explicitly disabled', async () => { + const { result } = hookRenderer({ args: { enabled: false, assetsAmount } }) + + await waitFor(() => { + expect(result.current.status.kind).toBe('disabled') + }) + }) + + it('withdraws xDAI', async () => { + const { result } = hookRenderer({ + args: { + assetsAmount, + }, + extraHandlers: [ + handlers.contractCall({ + to: savingsXDaiAdapterAddress[gnosis.id], + abi: savingsXDaiAdapterAbi, + functionName: 'withdrawXDAI', + args: [toBigInt(assetsAmount), account], + from: account, + result: 1n, + }), + handlers.mineTransaction(), + ], + }) + + await waitFor(() => { + expect(result.current.status.kind).toBe('ready') + }) + expect((result.current as any).error).toBeUndefined() + + result.current.write() + + await waitFor(() => { + expect(result.current.status.kind).toBe('success') + }) + }) +}) diff --git a/packages/app/src/domain/tokenized-vault-operations/useSexyDaiWithdraw.ts b/packages/app/src/domain/tokenized-vault-operations/useSexyDaiWithdraw.ts new file mode 100644 index 000000000..1eeed1365 --- /dev/null +++ b/packages/app/src/domain/tokenized-vault-operations/useSexyDaiWithdraw.ts @@ -0,0 +1,51 @@ +import { savingsXDaiAdapterAbi, savingsXDaiAdapterAddress } from '@/config/contracts-generated' +import { toBigInt } from '@/utils/bigNumber' +import { useQueryClient } from '@tanstack/react-query' +import { gnosis } from 'viem/chains' +import { useAccount, useConfig } from 'wagmi' +import { ensureConfigTypes, useWrite } from '../hooks/useWrite' +import { BaseUnitNumber } from '../types/NumericValues' +import { balances } from '../wallet/balances' + +interface UseSexyDaiWithdrawArgs { + assetsAmount: BaseUnitNumber + onTransactionSettled?: () => void + enabled?: boolean +} + +// @note: 'withdrawXDAI' function allows user to withdraw a specified amount +// of xDAI from the vault by burning the corresponding sDAI amount. +// Example: Withdraw X xDAI by burning Y sDAI (useful is one wants to withdraw exact number of xDAI) +export function useSexyDaiWithdraw({ + assetsAmount, + onTransactionSettled, + enabled = true, +}: UseSexyDaiWithdrawArgs): ReturnType { + const client = useQueryClient() + const wagmiConfig = useConfig() + + const { address: receiver } = useAccount() + + const config = ensureConfigTypes({ + address: savingsXDaiAdapterAddress[gnosis.id], + abi: savingsXDaiAdapterAbi, + functionName: 'withdrawXDAI', + args: [toBigInt(assetsAmount), receiver!], + }) + + return useWrite( + { + ...config, + enabled: enabled && assetsAmount.gt(0) && !!receiver, + }, + { + onTransactionSettled: async () => { + void client.invalidateQueries({ + queryKey: balances({ wagmiConfig, chainId: gnosis.id, account: receiver }).queryKey, + }) + + onTransactionSettled?.() + }, + }, + ) +} diff --git a/packages/app/src/domain/tokenized-vault-operations/useVaultRedeem.ts b/packages/app/src/domain/tokenized-vault-operations/useVaultRedeem.ts index 39ec88107..17c4c2062 100644 --- a/packages/app/src/domain/tokenized-vault-operations/useVaultRedeem.ts +++ b/packages/app/src/domain/tokenized-vault-operations/useVaultRedeem.ts @@ -16,7 +16,7 @@ interface UseVaultRedeemArgs { // @note: 'redeem' vault function allows user to redeem a specified // amount of shares in exchange for the underlying asset. -// Example: Redeem X sDAI to get Y DAI (useful if one want to withdraw all DAI) +// Example: Redeem X sDAI to get Y DAI (useful if one wants to withdraw all DAI) export function useVaultRedeem({ vault, sharesAmount,