Skip to content

Commit

Permalink
feat: add hooks for preparing Morpho transactions (#1871)
Browse files Browse the repository at this point in the history
  • Loading branch information
dschlabach authored Jan 23, 2025
1 parent 2f48a95 commit 46415c9
Show file tree
Hide file tree
Showing 12 changed files with 1,795 additions and 34 deletions.
1,400 changes: 1,400 additions & 0 deletions src/earn/abis/morpho.ts

Large diffs are not rendered by default.

24 changes: 0 additions & 24 deletions src/earn/constants.ts
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;
59 changes: 59 additions & 0 deletions src/earn/hooks/useBuildMorphoDepositTx.test.ts
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);
});
});
46 changes: 46 additions & 0 deletions src/earn/hooks/useBuildMorphoDepositTx.ts
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,
};
}
71 changes: 71 additions & 0 deletions src/earn/hooks/useBuildMorphoWithdrawTx.test.ts
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);
});
});
53 changes: 53 additions & 0 deletions src/earn/hooks/useBuildMorphoWithdrawTx.ts
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,
};
}
90 changes: 90 additions & 0 deletions src/earn/hooks/useMorphoVault.test.ts
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();
});
});
Loading

0 comments on commit 46415c9

Please sign in to comment.