-
Notifications
You must be signed in to change notification settings - Fork 228
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: add hooks for preparing Morpho transactions (#1871)
- Loading branch information
1 parent
2f48a95
commit 46415c9
Showing
12 changed files
with
1,795 additions
and
34 deletions.
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,26 +1,2 @@ | ||
export const MORPHO_VAULT_ABI = [ | ||
{ | ||
inputs: [ | ||
{ internalType: 'uint256', name: 'assets', type: 'uint256' }, | ||
{ internalType: 'address', name: 'receiver', type: 'address' }, | ||
], | ||
name: 'deposit', | ||
outputs: [{ internalType: 'uint256', name: 'shares', type: 'uint256' }], | ||
stateMutability: 'nonpayable', | ||
type: 'function', | ||
}, | ||
{ | ||
inputs: [ | ||
{ internalType: 'uint256', name: 'assets', type: 'uint256' }, | ||
{ internalType: 'address', name: 'receiver', type: 'address' }, | ||
{ internalType: 'address', name: 'owner', type: 'address' }, | ||
], | ||
name: 'withdraw', | ||
outputs: [{ internalType: 'uint256', name: 'shares', type: 'uint256' }], | ||
stateMutability: 'nonpayable', | ||
type: 'function', | ||
}, | ||
] as const; | ||
|
||
export const USDC_ADDRESS = '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48'; | ||
export const USDC_DECIMALS = 6; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
import { renderHook } from '@testing-library/react'; | ||
import { describe, expect, it, vi } from 'vitest'; | ||
import { | ||
type UseBuildMorphoDepositTxParams, | ||
useBuildMorphoDepositTx, | ||
} from './useBuildMorphoDepositTx'; | ||
import { useMorphoVault } from './useMorphoVault'; | ||
|
||
const DUMMY_ADDRESS = '0x9E95f497a7663B70404496dB6481c890C4825fe1' as const; | ||
|
||
// Mock dependencies | ||
vi.mock('./useMorphoVault'); | ||
vi.mock('@/earn/utils/buildDepositToMorphoTx', () => ({ | ||
buildDepositToMorphoTx: vi | ||
.fn() | ||
.mockReturnValue([{ to: '0x123', data: '0x456' }]), | ||
})); | ||
|
||
describe('useBuildMorphoDepositTx', () => { | ||
const mockParams: UseBuildMorphoDepositTxParams = { | ||
vaultAddress: DUMMY_ADDRESS, | ||
receiverAddress: DUMMY_ADDRESS, | ||
amount: 100, | ||
}; | ||
|
||
it('returns empty calls when vault data is not available', () => { | ||
vi.mocked(useMorphoVault).mockReturnValue({ | ||
status: 'pending', | ||
asset: undefined, | ||
balance: undefined, | ||
assetDecimals: undefined, | ||
vaultDecimals: undefined, | ||
name: undefined, | ||
}); | ||
|
||
const { result } = renderHook(() => useBuildMorphoDepositTx(mockParams)); | ||
|
||
expect(result.current.calls).toEqual([]); | ||
}); | ||
|
||
it('builds deposit transaction when vault data is available', () => { | ||
const mockAsset = DUMMY_ADDRESS; | ||
const mockDecimals = 18; | ||
|
||
vi.mocked(useMorphoVault).mockReturnValue({ | ||
status: 'success', | ||
asset: mockAsset, | ||
balance: '1000', | ||
assetDecimals: mockDecimals, | ||
vaultDecimals: mockDecimals, | ||
name: 'Mock Name', | ||
}); | ||
|
||
const { result } = renderHook(() => useBuildMorphoDepositTx(mockParams)); | ||
|
||
expect(result.current.calls).toEqual([{ to: '0x123', data: '0x456' }]); | ||
expect(result.current.calls).toHaveLength(1); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
import { useMorphoVault } from '@/earn/hooks/useMorphoVault'; | ||
import { buildDepositToMorphoTx } from '@/earn/utils/buildDepositToMorphoTx'; | ||
import type { Call } from '@/transaction/types'; | ||
import { type Address, parseUnits } from 'viem'; | ||
|
||
export type UseBuildMorphoDepositTxParams = { | ||
vaultAddress: Address; | ||
receiverAddress: Address; | ||
amount: number; | ||
}; | ||
|
||
/** | ||
* Generates Call[] for a Morpho deposit transaction | ||
* to be used with <Transaction /> | ||
*/ | ||
export function useBuildMorphoDepositTx({ | ||
vaultAddress, | ||
receiverAddress, | ||
amount, | ||
}: UseBuildMorphoDepositTxParams): { | ||
calls: Call[]; | ||
} { | ||
const { asset, balance, assetDecimals } = useMorphoVault({ | ||
vaultAddress, | ||
address: receiverAddress, | ||
}); | ||
|
||
if (!asset || balance === undefined || !assetDecimals) { | ||
return { | ||
calls: [], | ||
}; | ||
} | ||
|
||
const parsedAmount = parseUnits(amount.toString(), assetDecimals); | ||
|
||
const calls = buildDepositToMorphoTx({ | ||
receiverAddress, | ||
vaultAddress, | ||
tokenAddress: asset, | ||
amount: parsedAmount, | ||
}); | ||
|
||
return { | ||
calls, | ||
}; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,71 @@ | ||
import { renderHook } from '@testing-library/react'; | ||
import { describe, expect, it, vi } from 'vitest'; | ||
import { | ||
type UseBuildMorphoWithdrawTxParams, | ||
useBuildMorphoWithdrawTx, | ||
} from './useBuildMorphoWithdrawTx'; | ||
import { useMorphoVault } from './useMorphoVault'; | ||
|
||
const DUMMY_ADDRESS = '0x9E95f497a7663B70404496dB6481c890C4825fe1' as const; | ||
|
||
// Mock dependencies | ||
vi.mock('./useMorphoVault'); | ||
vi.mock('@/earn/utils/buildWithdrawFromMorphoTx', () => ({ | ||
buildWithdrawFromMorphoTx: vi | ||
.fn() | ||
.mockReturnValue([{ to: '0x123', data: '0x456' }]), | ||
})); | ||
|
||
describe('useBuildMorphoWithdrawTx', () => { | ||
const mockParams: UseBuildMorphoWithdrawTxParams = { | ||
vaultAddress: DUMMY_ADDRESS, | ||
receiverAddress: DUMMY_ADDRESS, | ||
amount: 100, | ||
}; | ||
|
||
it('returns empty calls when vault data is not available', () => { | ||
vi.mocked(useMorphoVault).mockReturnValue({ | ||
status: 'pending', | ||
asset: undefined, | ||
balance: undefined, | ||
assetDecimals: undefined, | ||
vaultDecimals: undefined, | ||
name: undefined, | ||
}); | ||
|
||
const { result } = renderHook(() => useBuildMorphoWithdrawTx(mockParams)); | ||
|
||
expect(result.current.calls).toEqual([]); | ||
}); | ||
|
||
it('returns empty calls when amount is greater than balance', () => { | ||
vi.mocked(useMorphoVault).mockReturnValue({ | ||
status: 'success', | ||
asset: DUMMY_ADDRESS, | ||
balance: '50', | ||
assetDecimals: 18, | ||
vaultDecimals: 18, | ||
name: 'Mock Name', | ||
}); | ||
|
||
const { result } = renderHook(() => useBuildMorphoWithdrawTx(mockParams)); | ||
|
||
expect(result.current.calls).toEqual([]); | ||
}); | ||
|
||
it('builds withdraw transaction when vault data is available and amount is valid', () => { | ||
vi.mocked(useMorphoVault).mockReturnValue({ | ||
status: 'success', | ||
asset: DUMMY_ADDRESS, | ||
balance: '1000', | ||
assetDecimals: 18, | ||
vaultDecimals: 18, | ||
name: 'Mock Name', | ||
}); | ||
|
||
const { result } = renderHook(() => useBuildMorphoWithdrawTx(mockParams)); | ||
|
||
expect(result.current.calls).toEqual([{ to: '0x123', data: '0x456' }]); | ||
expect(result.current.calls).toHaveLength(1); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
import { useMorphoVault } from '@/earn/hooks/useMorphoVault'; | ||
import { buildWithdrawFromMorphoTx } from '@/earn/utils/buildWithdrawFromMorphoTx'; | ||
import type { Call } from '@/transaction/types'; | ||
import { type Address, parseUnits } from 'viem'; | ||
|
||
export type UseBuildMorphoWithdrawTxParams = { | ||
vaultAddress: Address; | ||
receiverAddress: Address; | ||
amount: number; | ||
}; | ||
|
||
/** | ||
* Generates Call[] for a Morpho withdraw transaction | ||
* to be used with <Transaction /> | ||
*/ | ||
export function useBuildMorphoWithdrawTx({ | ||
vaultAddress, | ||
amount, | ||
receiverAddress, | ||
}: UseBuildMorphoWithdrawTxParams): { | ||
calls: Call[]; | ||
} { | ||
const { asset, balance, assetDecimals, vaultDecimals } = useMorphoVault({ | ||
vaultAddress, | ||
address: receiverAddress, | ||
}); | ||
|
||
const amountIsGreaterThanBalance = amount > Number(balance); | ||
|
||
if ( | ||
!asset || | ||
balance === undefined || | ||
!assetDecimals || | ||
!vaultDecimals || | ||
amountIsGreaterThanBalance | ||
) { | ||
return { | ||
calls: [], | ||
}; | ||
} | ||
|
||
const parsedAmount = parseUnits(amount.toString(), assetDecimals); | ||
|
||
const calls = buildWithdrawFromMorphoTx({ | ||
receiverAddress, | ||
vaultAddress, | ||
amount: parsedAmount, | ||
}); | ||
|
||
return { | ||
calls, | ||
}; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,90 @@ | ||
import { renderHook } from '@testing-library/react'; | ||
import { describe, expect, it, vi } from 'vitest'; | ||
import { | ||
type UseReadContractReturnType, | ||
type UseReadContractsReturnType, | ||
useReadContract, | ||
useReadContracts, | ||
} from 'wagmi'; | ||
import { useMorphoVault } from './useMorphoVault'; | ||
|
||
const DUMMY_ADDRESS = '0x9E95f497a7663B70404496dB6481c890C4825fe1' as const; | ||
|
||
// Mock dependencies | ||
vi.mock('wagmi', () => ({ | ||
useReadContract: vi.fn(), | ||
useReadContracts: vi.fn(), | ||
})); | ||
|
||
describe('useMorphoVault', () => { | ||
const mockParams = { | ||
vaultAddress: DUMMY_ADDRESS, | ||
address: DUMMY_ADDRESS, | ||
}; | ||
|
||
it('returns undefined values when contract reads are pending', () => { | ||
vi.mocked(useReadContracts).mockReturnValue({ | ||
data: undefined, | ||
status: 'pending', | ||
} as UseReadContractsReturnType<unknown[], boolean, unknown>); // for brevity | ||
vi.mocked(useReadContract).mockReturnValue({ | ||
data: undefined, | ||
} as UseReadContractReturnType<unknown[], string, unknown[], unknown>); // for brevity | ||
|
||
const { result } = renderHook(() => useMorphoVault(mockParams)); | ||
|
||
expect(result.current).toEqual({ | ||
status: 'pending', | ||
asset: undefined, | ||
assetDecimals: undefined, | ||
vaultDecimals: undefined, | ||
name: undefined, | ||
balance: undefined, | ||
}); | ||
}); | ||
|
||
it('returns formatted data when contract reads are successful', () => { | ||
vi.mocked(useReadContracts).mockReturnValue({ | ||
data: [ | ||
{ result: DUMMY_ADDRESS }, // asset | ||
{ result: 'Morpho Vault' }, // name | ||
{ result: 1000000000000000000n }, // balanceOf | ||
{ result: 18 }, // decimals | ||
], | ||
status: 'success', | ||
} as UseReadContractsReturnType<unknown[], boolean, unknown>); // for brevity | ||
vi.mocked(useReadContract).mockReturnValue({ | ||
data: 18, | ||
} as UseReadContractReturnType<unknown[], string, unknown[], unknown>); // for brevity | ||
|
||
const { result } = renderHook(() => useMorphoVault(mockParams)); | ||
|
||
expect(result.current).toEqual({ | ||
status: 'success', | ||
asset: DUMMY_ADDRESS, | ||
assetDecimals: 18, | ||
vaultDecimals: 18, | ||
name: 'Morpho Vault', | ||
balance: '1', | ||
}); | ||
}); | ||
|
||
it('handles missing balance data', () => { | ||
vi.mocked(useReadContracts).mockReturnValue({ | ||
data: [ | ||
{ result: DUMMY_ADDRESS }, | ||
{ result: 'Morpho Vault' }, | ||
{ result: undefined }, // missing balance | ||
{ result: 18 }, | ||
], | ||
status: 'success', | ||
} as UseReadContractsReturnType<unknown[], boolean, unknown>); // for brevity | ||
vi.mocked(useReadContract).mockReturnValue({ | ||
data: 18, | ||
} as UseReadContractReturnType<unknown[], string, unknown[], unknown>); // for brevity | ||
|
||
const { result } = renderHook(() => useMorphoVault(mockParams)); | ||
|
||
expect(result.current.balance).toBeUndefined(); | ||
}); | ||
}); |
Oops, something went wrong.