diff --git a/.changeset/mighty-phones-raise.md b/.changeset/mighty-phones-raise.md new file mode 100644 index 00000000..a845151c --- /dev/null +++ b/.changeset/mighty-phones-raise.md @@ -0,0 +1,2 @@ +--- +--- diff --git a/packages/client/src/testing.ts b/packages/client/src/testing.ts index cf46013a..71e1e36a 100644 --- a/packages/client/src/testing.ts +++ b/packages/client/src/testing.ts @@ -66,6 +66,9 @@ export const ETHEREUM_WSTETH_ADDRESS = evmAddress( export const ETHEREUM_1INCH_ADDRESS = evmAddress( '0x111111111117dC0aa78b770fA6A738034120C302', ); +export const ETHEREUM_AAVE_ADDRESS = evmAddress( + '0x7Fc66500c84A76Ad7e9c93437bFc5Ac33E2DDaE9', +); // Spoke addresses and ids export const ETHEREUM_SPOKE_CORE_ADDRESS = evmAddress( @@ -123,6 +126,13 @@ const devnetChain = await chain(client, { chainId: ETHEREUM_FORK_ID }) () => never('No devnet chain found'), ); +export async function createForkPublicClient() { + return createPublicClient({ + chain: devnetChain, + transport: http(ETHEREUM_FORK_RPC_URL), + }); +} + export async function createNewWallet( privateKey?: `0x${string}`, ): Promise> { diff --git a/packages/spec/borrow/business.spec.ts b/packages/spec/borrow/business.spec.ts index c51bb0fa..2c11dd06 100644 --- a/packages/spec/borrow/business.spec.ts +++ b/packages/spec/borrow/business.spec.ts @@ -23,18 +23,12 @@ describe('Borrowing Assets on Aave V4', () => { beforeAll(async () => { const amountToSupply = bigDecimal('100'); - const setup = await fundErc20Address(evmAddress(user.account.address), { - address: ETHEREUM_USDC_ADDRESS, + const setup = await findReserveAndSupply(client, user, { + token: ETHEREUM_USDC_ADDRESS, + spoke: ETHEREUM_SPOKE_CORE_ID, amount: amountToSupply, - decimals: 6, - }).andThen(() => - findReserveAndSupply(client, user, { - token: ETHEREUM_USDC_ADDRESS, - spoke: ETHEREUM_SPOKE_CORE_ID, - amount: amountToSupply, - asCollateral: true, - }), - ); + asCollateral: true, + }); assertOk(setup); }); @@ -105,6 +99,7 @@ describe('Borrowing Assets on Aave V4', () => { assertSingleElementArray(result.value); expect(result.value[0].debt.amount.value).toBeBigDecimalCloseTo( amountToBorrow, + 2, ); expect(result.value[0].debt.token.isWrappedNativeToken).toBe(false); }); @@ -189,6 +184,7 @@ describe('Borrowing Assets on Aave V4', () => { ); expect(balanceAfter).toBeBigDecimalCloseTo( balanceBefore.add(amountToBorrow), + 2, ); expect(position?.debt.amount.value).toBeBigDecimalCloseTo( diff --git a/packages/spec/borrow/multipleBorrows.spec.ts b/packages/spec/borrow/multipleBorrows.spec.ts index 5012e7ef..75e4f42d 100644 --- a/packages/spec/borrow/multipleBorrows.spec.ts +++ b/packages/spec/borrow/multipleBorrows.spec.ts @@ -5,7 +5,6 @@ import { createNewWallet, ETHEREUM_SPOKE_CORE_ID, ETHEREUM_USDC_ADDRESS, - fundErc20Address, } from '@aave/client/testing'; import { sendWith } from '@aave/client/viem'; import { beforeAll, describe, expect, it } from 'vitest'; @@ -21,18 +20,12 @@ describe('Borrowing from Multiple Reserves on Aave V4', () => { beforeAll(async () => { const amountToSupply = bigDecimal('100'); - const setup = await fundErc20Address(evmAddress(user.account.address), { - address: ETHEREUM_USDC_ADDRESS, + const setup = await findReserveAndSupply(client, user, { + token: ETHEREUM_USDC_ADDRESS, + spoke: ETHEREUM_SPOKE_CORE_ID, amount: amountToSupply, - decimals: 6, - }).andThen(() => - findReserveAndSupply(client, user, { - token: ETHEREUM_USDC_ADDRESS, - spoke: ETHEREUM_SPOKE_CORE_ID, - amount: amountToSupply, - asCollateral: true, - }), - ); + asCollateral: true, + }); assertOk(setup); }, 120_000); @@ -107,6 +100,7 @@ describe('Borrowing from Multiple Reserves on Aave V4', () => { reservesToBorrow.value[0]!.userState!.borrowable.amount.value.times( 0.1, ), + 2, ); // Verify second borrow position (USDS) @@ -127,6 +121,7 @@ describe('Borrowing from Multiple Reserves on Aave V4', () => { reservesToBorrow.value[1]!.userState!.borrowable.amount.value.times( 0.1, ), + 2, ); }); }); diff --git a/packages/spec/helpers/supplyBorrow.ts b/packages/spec/helpers/supplyBorrow.ts index 63b0fb87..806edac0 100644 --- a/packages/spec/helpers/supplyBorrow.ts +++ b/packages/spec/helpers/supplyBorrow.ts @@ -73,23 +73,49 @@ export function findReserveAndSupply( spoke, asCollateral, }: { - token: EvmAddress; - amount: BigDecimal; + token?: EvmAddress; + amount?: BigDecimal; spoke?: SpokeId; asCollateral?: boolean; }, -): ResultAsync { +): ResultAsync<{ reserveInfo: Reserve; amountSupplied: BigDecimal }, Error> { return findReservesToSupply(client, user, { token: token, spoke: spoke, asCollateral: asCollateral, - }).andThen((reserves) => - supplyToReserve(client, user, { - reserve: reserves[0].id, - amount: { erc20: { value: amount } }, - sender: evmAddress(user.account.address), - }).map(() => reserves[0]), - ); + }).andThen((reserves) => { + return fundErc20Address(evmAddress(user.account.address), { + address: token ?? reserves[0]!.asset.underlying.address, + amount: + amount ?? + reserves[0]!.supplyCap + .minus(reserves[0]!.summary.supplied.amount.value) + .div(100000), + decimals: reserves[0]!.asset.underlying.info.decimals, + }).andThen(() => + supplyToReserve(client, user, { + reserve: reserves[0]!.id, + amount: { + erc20: { + value: + amount ?? + reserves[0]!.supplyCap + .minus(reserves[0]!.summary.supplied.amount.value) + .div(100000), + }, + }, + sender: evmAddress(user.account.address), + enableCollateral: asCollateral ?? true, + }).map(() => ({ + reserveInfo: reserves[0]!, + amountSupplied: + amount ?? + reserves[0]!.supplyCap + .minus(reserves[0]!.summary.supplied.amount.value) + .div(100000), + })), + ); + }); } export function supplyAndBorrow( @@ -141,6 +167,33 @@ export function supplyAndBorrow( ); } +export function borrowFromRandomReserve( + client: AaveClient, + user: WalletClient, + params: { + spoke?: SpokeId; + token?: EvmAddress; + ratioToBorrow?: number; + }, +): ResultAsync { + return findReservesToBorrow(client, user, { + spoke: params.spoke, + token: params.token, + }).andThen((reserves) => { + return borrowFromReserve(client, user, { + reserve: reserves[0].id, + amount: { + erc20: { + value: reserves[0].userState!.borrowable.amount.value.times( + params.ratioToBorrow ?? 0.1, + ), + }, + }, + sender: evmAddress(user.account.address), + }).map(() => reserves[0]); + }); +} + export function supplyAndBorrowNativeToken( client: AaveClient, user: WalletClient, diff --git a/packages/spec/positions/business.spec.ts b/packages/spec/positions/business.spec.ts index 7d12a169..0e0e10cb 100644 --- a/packages/spec/positions/business.spec.ts +++ b/packages/spec/positions/business.spec.ts @@ -40,18 +40,12 @@ describe('Health Factor Scenarios on Aave V4', () => { beforeAll(async () => { const amountToSupply = bigDecimal('100'); - const setup = await fundErc20Address(evmAddress(user.account.address), { - address: ETHEREUM_USDC_ADDRESS, + const setup = await findReserveAndSupply(client, user, { + token: ETHEREUM_USDC_ADDRESS, + spoke: ETHEREUM_SPOKE_CORE_ID, + asCollateral: true, amount: amountToSupply, - decimals: 6, - }).andThen(() => - findReserveAndSupply(client, user, { - token: ETHEREUM_USDC_ADDRESS, - spoke: ETHEREUM_SPOKE_CORE_ID, - asCollateral: true, - amount: amountToSupply, - }), - ); + }); assertOk(setup); }); diff --git a/packages/spec/positions/helper.ts b/packages/spec/positions/helper.ts index 36aa343e..a37b59cf 100644 --- a/packages/spec/positions/helper.ts +++ b/packages/spec/positions/helper.ts @@ -17,15 +17,14 @@ import { } from '@aave/client/actions'; import { ETHEREUM_FORK_ID, - ETHEREUM_GHO_ADDRESS, ETHEREUM_SPOKE_CORE_ID, ETHEREUM_SPOKE_ETHENA_ID, ETHEREUM_WETH_ADDRESS, - ETHEREUM_WSTETH_ADDRESS, fundErc20Address, } from '@aave/client/testing'; import { sendWith } from '@aave/client/viem'; import type { Account, Chain, Transport, WalletClient } from 'viem'; + import { findReservesToBorrow, findReservesToSupply, @@ -33,7 +32,6 @@ import { import { borrowFromReserve, findReserveAndSupply, - supplyAndBorrowNativeToken, supplyToReserve, } from '../helpers/supplyBorrow'; import { @@ -203,41 +201,6 @@ export const recreateUserActivities = async ( } }; -export const recreateUserSummary = async ( - client: AaveClient, - user: WalletClient, -) => { - const setup = await fundErc20Address(evmAddress(user.account.address), { - address: ETHEREUM_WETH_ADDRESS, - amount: bigDecimal('0.5'), - }) - .andThen(() => - fundErc20Address(evmAddress(user.account.address), { - address: ETHEREUM_WSTETH_ADDRESS, - amount: bigDecimal('0.5'), - }), - ) - .andThen(() => - fundErc20Address(evmAddress(user.account.address), { - address: ETHEREUM_GHO_ADDRESS, - amount: bigDecimal('100'), - }), - ) - .andThen(() => - findReserveAndSupply(client, user, { - token: ETHEREUM_GHO_ADDRESS, - amount: bigDecimal('100'), - }), - ) - .andThen(() => - supplyAndBorrowNativeToken(client, user, { - spoke: ETHEREUM_SPOKE_CORE_ID, - ratioToBorrow: 0.4, - }), - ); - assertOk(setup); -}; - export const recreateUserBorrows = async ( client: AaveClient, user: WalletClient, diff --git a/packages/spec/positions/math/helper.ts b/packages/spec/positions/math/helper.ts new file mode 100644 index 00000000..6161dd08 --- /dev/null +++ b/packages/spec/positions/math/helper.ts @@ -0,0 +1,80 @@ +import { type BigDecimal, bigDecimal } from '@aave/client'; +import { createForkPublicClient } from '@aave/client/testing'; +import type { Address } from 'viem'; + +// Constants +const WAD = 10n ** 18n; // 1e18 = 1.0 in WAD format +const userAccountDataABI = [ + { + inputs: [{ name: 'user', type: 'address' }], + name: 'getUserAccountData', + outputs: [ + { + components: [ + { name: 'riskPremium', type: 'uint256' }, + { name: 'avgCollateralFactor', type: 'uint256' }, + { name: 'healthFactor', type: 'uint256' }, + { name: 'totalCollateralValue', type: 'uint256' }, + { name: 'totalDebtValue', type: 'uint256' }, + { name: 'activeCollateralCount', type: 'uint256' }, + { name: 'borrowedCount', type: 'uint256' }, + ], + name: '', + type: 'tuple', + }, + ], + stateMutability: 'view', + type: 'function', + }, +] as const; + +export interface UserAccountData { + riskPremium: BigDecimal; + avgCollateralFactor: BigDecimal; + healthFactor: BigDecimal; + totalCollateralValue: BigDecimal; + totalDebtValue: BigDecimal; + activeCollateralCount: number; + borrowedCount: number; +} + +function formatWAD(value: bigint): BigDecimal { + return bigDecimal(value).div(WAD); +} + +function formatUSD(value: bigint): BigDecimal { + return bigDecimal(value).div(bigDecimal('1e26')); +} + +function formatBPS(value: bigint): BigDecimal { + return bigDecimal(value).div(bigDecimal('100')); +} + +/** + * Get user account data from the Spoke contract + * @param address - The user address to query + * @param spoke - The Spoke contract address + * @returns User account data + */ +export async function getAccountData( + address: Address, + spoke: Address, +): Promise { + const forkPublicClient = await createForkPublicClient(); + const result = await forkPublicClient.readContract({ + address: spoke, + abi: userAccountDataABI, + functionName: 'getUserAccountData', + args: [address], + }); + + return { + riskPremium: formatBPS(result.riskPremium), + avgCollateralFactor: formatWAD(result.avgCollateralFactor), + healthFactor: formatWAD(result.healthFactor), + totalCollateralValue: formatUSD(result.totalCollateralValue), + totalDebtValue: formatUSD(result.totalDebtValue), + activeCollateralCount: Number(result.activeCollateralCount), + borrowedCount: Number(result.borrowedCount), + }; +} diff --git a/packages/spec/positions/math/userPositions.spec.ts b/packages/spec/positions/math/userPositions.spec.ts new file mode 100644 index 00000000..2c723cbf --- /dev/null +++ b/packages/spec/positions/math/userPositions.spec.ts @@ -0,0 +1,256 @@ +import { + assertOk, + bigDecimal, + evmAddress, + type UserBorrowItem, + type UserPosition, + type UserSupplyItem, +} from '@aave/client'; +import { userBorrows, userPositions, userSupplies } from '@aave/client/actions'; +import { + client, + createNewWallet, + ETHEREUM_AAVE_ADDRESS, + ETHEREUM_FORK_ID, + ETHEREUM_GHO_ADDRESS, + ETHEREUM_SPOKE_CORE_ADDRESS, + ETHEREUM_SPOKE_CORE_ID, + ETHEREUM_USDC_ADDRESS, + ETHEREUM_WETH_ADDRESS, +} from '@aave/client/testing'; +import { beforeAll, describe, expect, it } from 'vitest'; +import { + borrowFromRandomReserve, + findReserveAndSupply, +} from '../../helpers/supplyBorrow'; +import { + assertNonEmptyArray, + assertSingleElementArray, +} from '../../test-utils'; +import { getAccountData, type UserAccountData } from './helper'; + +const user = await createNewWallet( + '0xbae6035617e696766fc0a0739508200144f6e785600cc155496ddfc1d78a6a14', +); + +describe('Given a user with a User Position on a Spoke', () => { + describe('With 3 supply positions, 2 of which set as collateral', () => { + let suppliesPositions: UserSupplyItem[]; + + beforeAll(async () => { + const resultSupplies = await userSupplies(client, { + query: { + userSpoke: { + spoke: ETHEREUM_SPOKE_CORE_ID, + user: evmAddress(user.account.address), + }, + }, + }); + assertOk(resultSupplies); + suppliesPositions = resultSupplies.value; + + if (suppliesPositions.length < 3) { + const supplyGHOCollateral = await findReserveAndSupply(client, user, { + spoke: ETHEREUM_SPOKE_CORE_ID, + token: ETHEREUM_GHO_ADDRESS, + asCollateral: true, + amount: bigDecimal('100'), + }); + assertOk(supplyGHOCollateral); + + const supplyUSDCDNoCollateral = await findReserveAndSupply( + client, + user, + { + spoke: ETHEREUM_SPOKE_CORE_ID, + token: ETHEREUM_USDC_ADDRESS, + asCollateral: true, + amount: bigDecimal('100'), + }, + ); + assertOk(supplyUSDCDNoCollateral); + + const supplyWETHCollateral = await findReserveAndSupply(client, user, { + spoke: ETHEREUM_SPOKE_CORE_ID, + token: ETHEREUM_AAVE_ADDRESS, + asCollateral: false, + amount: bigDecimal('0.5'), + }); + assertOk(supplyWETHCollateral); + } + }, 100_000); + + describe('And 2 borrow positions', () => { + let borrowPositions: UserBorrowItem[]; + + beforeAll(async () => { + const borrows = await userBorrows(client, { + query: { + userSpoke: { + spoke: ETHEREUM_SPOKE_CORE_ID, + user: evmAddress(user.account.address), + }, + }, + }); + assertOk(borrows); + borrowPositions = borrows.value; + + if (borrows.value.length < 2) { + const borrowAAVE = await borrowFromRandomReserve(client, user, { + spoke: ETHEREUM_SPOKE_CORE_ID, + token: ETHEREUM_AAVE_ADDRESS, + ratioToBorrow: 0.1, + }); + assertOk(borrowAAVE); + + const borrowWETH = await borrowFromRandomReserve(client, user, { + spoke: ETHEREUM_SPOKE_CORE_ID, + token: ETHEREUM_WETH_ADDRESS, + ratioToBorrow: 0.1, + }); + assertOk(borrowWETH); + } + }, 180_000); + + describe('When fetching the User Position data', () => { + let position: UserPosition; + let accountDataOnChain: UserAccountData; + + beforeAll(async () => { + const [positionsResult, accountDataResult] = await Promise.all([ + userPositions(client, { + user: evmAddress(user.account.address), + filter: { + chainIds: [ETHEREUM_FORK_ID], + }, + }), + getAccountData( + evmAddress(user.account.address), + ETHEREUM_SPOKE_CORE_ADDRESS, + ), + ]); + + assertOk(positionsResult); + assertNonEmptyArray(positionsResult.value); + // We only operate on one spoke, so we expect a single element array + assertSingleElementArray(positionsResult.value); + position = positionsResult.value[0]; + + accountDataOnChain = accountDataResult; + }, 180_000); + + it('Then it should return the correct totalSupplied value', async () => { + // total supplied is the sum of the principal and interest for all positions in the spoke + const totalSupplied = suppliesPositions.reduce( + (acc, supply) => + acc.plus( + supply.principal.exchange.value.plus( + supply.interest.exchange.value, + ), + ), + bigDecimal('0'), + ); + expect(totalSupplied).toBeBigDecimalCloseTo( + position.totalSupplied.current.value, + 2, + ); + }); + + it('Then it should return the correct totalCollateral value', async () => { + expect(position.totalCollateral.current.value).toBeBigDecimalCloseTo( + accountDataOnChain.totalCollateralValue, + 1, + ); + }); + + it('Then it should return the correct netCollateral value', async () => { + // net collateral is the sum of the total collateral minus the total debt + const totalCollateral = suppliesPositions + .filter((supply) => supply.isCollateral) + .reduce( + (acc, supply) => + acc.plus( + supply.principal.exchange.value.plus( + supply.interest.exchange.value, + ), + ), + bigDecimal('0'), + ); + const totalDebt = borrowPositions.reduce( + (acc, borrow) => + acc.plus( + borrow.debt.exchange.value.plus(borrow.interest.exchange.value), + ), + bigDecimal('0'), + ); + + expect(totalCollateral.minus(totalDebt)).toBeBigDecimalCloseTo( + position.netCollateral.current.value, + 1, + ); + }); + + it('Then it should return the correct totalDebt value', async () => { + // total debt is the sum of the principal and interest for all positions in the spoke + expect(position.totalDebt.current.value).toBeBigDecimalCloseTo( + accountDataOnChain.totalDebtValue, + 1, + ); + }); + + it('Then it should return the correct netBalance value', async () => { + // net balance is the sum of the total supplied minus the borrows (debt) + const totalSupplied = suppliesPositions.reduce( + (acc, supply) => + acc.plus( + supply.principal.exchange.value.plus( + supply.interest.exchange.value, + ), + ), + bigDecimal('0'), + ); + + const totalDebt = borrowPositions.reduce( + (acc, borrow) => + acc.plus( + borrow.debt.exchange.value.plus(borrow.interest.exchange.value), + ), + bigDecimal('0'), + ); + + expect(totalSupplied.minus(totalDebt)).toBeBigDecimalCloseTo( + position.netBalance.current.value, + 1, + ); + }); + + it('Then it should return the correct healthFactor', async () => { + expect(position.healthFactor.current).toBeBigDecimalCloseTo( + accountDataOnChain.healthFactor, + 2, + ); + }); + + it('Then it should return the correct averageCollateralFactor value', async () => { + expect(position.averageCollateralFactor.value).toBeBigDecimalCloseTo( + accountDataOnChain.avgCollateralFactor, + 2, + ); + }); + + it('Then it should return the correct riskPremium value', async () => { + expect(position.riskPremium?.current.value).toBeBigDecimalCloseTo( + accountDataOnChain.riskPremium, + 2, + ); + }); + + it.todo('Then it should return the correct netApy value'); + it.todo('Then it should return the correct netSupplyApy value'); + it.todo('Then it should return the correct netBorrowApy value'); + it.todo('Then it should return the correct borrowingPower value'); + it.todo('Then it should return the correct liquidationPrice value'); + }); + }); + }); +}); diff --git a/packages/spec/repay/business.spec.ts b/packages/spec/repay/business.spec.ts index e05a3480..7e3d780b 100644 --- a/packages/spec/repay/business.spec.ts +++ b/packages/spec/repay/business.spec.ts @@ -17,14 +17,11 @@ import { sendWith, signERC20PermitWith } from '@aave/client/viem'; import type { Reserve } from '@aave/graphql'; import { beforeAll, beforeEach, describe, expect, it } from 'vitest'; -import { - findReservesToBorrow, - findReservesToSupply, -} from '../helpers/reserves'; +import { findReservesToBorrow } from '../helpers/reserves'; import { borrowFromReserve, + findReserveAndSupply, supplyAndBorrowNativeToken, - supplyToReserve, } from '../helpers/supplyBorrow'; const user = await createNewWallet(); @@ -34,29 +31,13 @@ describe('Repaying Loans on Aave V4', () => { let reserve: Reserve; beforeEach(async () => { - const supplySetup = await findReservesToSupply(client, user, { + const supplySetup = await findReserveAndSupply(client, user, { token: ETHEREUM_USDC_ADDRESS, spoke: ETHEREUM_SPOKE_CORE_ID, asCollateral: true, - }).andThen((supplyReserves) => { - const amountToSupply = supplyReserves[0].supplyCap - .minus(supplyReserves[0].summary.supplied.amount.value) - .div(10000); - - return fundErc20Address(evmAddress(user.account.address), { - address: supplyReserves[0].asset.underlying.address, - amount: amountToSupply, - decimals: supplyReserves[0].asset.underlying.info.decimals, - }).andThen(() => - supplyToReserve(client, user, { - reserve: supplyReserves[0].id, - amount: { erc20: { value: amountToSupply } }, - sender: evmAddress(user.account.address), - enableCollateral: true, - }), - ); }); assertOk(supplySetup); + const borrowSetup = await findReservesToBorrow(client, user, { spoke: ETHEREUM_SPOKE_CORE_ID, }).andThen((borrowReserves) => { @@ -295,6 +276,7 @@ describe('Repaying Loans on Aave V4', () => { invariant(positionAfter, 'No position found'); expect(positionAfter.debt.amount.value).toBeBigDecimalCloseTo( positionBefore.debt.amount.value.minus(amountToRepay), + 2, ); }); }); @@ -368,6 +350,7 @@ describe('Repaying Loans on Aave V4', () => { invariant(positionAfter, 'No position found'); expect(positionAfter.debt.amount.value).toBeBigDecimalCloseTo( amountToRepay, + 2, ); const balanceAfter = await getNativeBalance( diff --git a/packages/spec/supply/business.spec.ts b/packages/spec/supply/business.spec.ts index 8cc9c5f0..36619137 100644 --- a/packages/spec/supply/business.spec.ts +++ b/packages/spec/supply/business.spec.ts @@ -82,6 +82,7 @@ describe('Supplying Assets on Aave V4', () => { expect(supplyPosition.isCollateral).toEqual(true); expect(supplyPosition.principal.amount.value).toBeBigDecimalCloseTo( amountToSupply, + 2, ); // Check the other reserves were not affected @@ -295,6 +296,7 @@ describe('Supplying Assets on Aave V4', () => { supplyPositionBefore.value?.withdrawable.amount.value.plus( amountToSupply, ), + 2, ); }); }); diff --git a/packages/spec/supply/setCollateral.spec.ts b/packages/spec/supply/setCollateral.spec.ts index 544bcb67..73648af6 100644 --- a/packages/spec/supply/setCollateral.spec.ts +++ b/packages/spec/supply/setCollateral.spec.ts @@ -123,7 +123,10 @@ describe('Setting Supply as Collateral on Aave V4', () => { // netBalance should be the same expect( previewResult.value.netBalance.after.value, - ).toBeBigDecimalCloseTo(previewResult.value.netBalance.current.value); + ).toBeBigDecimalCloseTo( + previewResult.value.netBalance.current.value, + 2, + ); if (!positions.value[0].isCollateral) { expect( previewResult.value.netCollateral.after.value, diff --git a/packages/spec/tools/balances.spec.ts b/packages/spec/tools/balances.spec.ts index 3018bd04..d5243dba 100644 --- a/packages/spec/tools/balances.spec.ts +++ b/packages/spec/tools/balances.spec.ts @@ -8,7 +8,6 @@ import { ETHEREUM_HUB_CORE_ADDRESS, ETHEREUM_SPOKE_CORE_ADDRESS, ETHEREUM_USDC_ADDRESS, - fundErc20Address, } from '@aave/client/testing'; import { beforeAll, describe, expect, it } from 'vitest'; import { findReserveAndSupply } from '../helpers/supplyBorrow'; @@ -33,21 +32,12 @@ describe('Querying User Balances on Aave V4', () => { assertOk(balances); if (balances.value.length < 3) { for (const token of [ETHEREUM_USDC_ADDRESS, ETHEREUM_1INCH_ADDRESS]) { - const result = await fundErc20Address( - evmAddress(user.account.address), - { - address: token, - amount: bigDecimal('100'), - decimals: token === ETHEREUM_1INCH_ADDRESS ? 18 : 6, - }, - ).andThen(() => - findReserveAndSupply(client, user, { - token: token, - amount: bigDecimal('50'), - asCollateral: true, - }), - ); - assertOk(result); + const setup = await findReserveAndSupply(client, user, { + token: token, + amount: bigDecimal('100'), + asCollateral: true, + }); + assertOk(setup); } } }, 60_000); diff --git a/packages/spec/withdraw/business.spec.ts b/packages/spec/withdraw/business.spec.ts index e44556cb..85f5a12e 100644 --- a/packages/spec/withdraw/business.spec.ts +++ b/packages/spec/withdraw/business.spec.ts @@ -9,18 +9,15 @@ import { client, createNewWallet, ETHEREUM_SPOKE_CORE_ID, - fundErc20Address, getBalance, getNativeBalance, } from '@aave/client/testing'; import { sendWith } from '@aave/client/viem'; import type { Reserve } from '@aave/graphql'; import { beforeEach, describe, expect, it } from 'vitest'; - -import { findReservesToSupply } from '../helpers/reserves'; import { + findReserveAndSupply, supplyNativeTokenToReserve, - supplyToReserve, } from '../helpers/supplyBorrow'; import { assertSingleElementArray } from '../test-utils'; @@ -32,31 +29,12 @@ describe('Withdrawing Assets on Aave V4', () => { let amountToSupply: BigDecimal; beforeEach(async () => { - const setup = await findReservesToSupply(client, user, { + const setup = await findReserveAndSupply(client, user, { spoke: ETHEREUM_SPOKE_CORE_ID, - }).andThen((listReserves) => { - amountToSupply = listReserves[0].supplyCap - .minus(listReserves[0].summary.supplied.amount.value) - .div(10000); - - return fundErc20Address(evmAddress(user.account!.address), { - address: listReserves[0].asset.underlying.address, - amount: amountToSupply, - decimals: listReserves[0].asset.underlying.info.decimals, - }).andThen(() => - supplyToReserve(client, user, { - reserve: listReserves[0].id, - amount: { - erc20: { - value: amountToSupply, - }, - }, - sender: evmAddress(user.account.address), - }).map(() => listReserves[0]), - ); }); assertOk(setup); - reserve = setup.value; + reserve = setup.value.reserveInfo; + amountToSupply = setup.value.amountSupplied; }, 40_000); describe('When the user withdraws part of their supplied tokens', () => { @@ -90,7 +68,7 @@ describe('Withdrawing Assets on Aave V4', () => { assertSingleElementArray(withdrawResult.value); expect( withdrawResult.value[0].withdrawable.amount.value, - ).toBeBigDecimalCloseTo(amountToSupply.minus(amountToWithdraw)); + ).toBeBigDecimalCloseTo(amountToSupply.minus(amountToWithdraw), 2); const balanceAfter = await getBalance( evmAddress(user.account.address), diff --git a/vitest.d.ts b/vitest.d.ts index 53ce0245..810d5d86 100644 --- a/vitest.d.ts +++ b/vitest.d.ts @@ -2,7 +2,7 @@ import 'vitest'; declare module 'vitest' { interface AsymmetricMatchersContaining extends JestExtendedMatchers { - toBeBigDecimalCloseTo: (expected: number | string, precision?: number) => R; + toBeBigDecimalCloseTo: (expected: number | string, precision: number) => R; toBeBigDecimalGreaterThan: (expected: number | string) => R; toBeBigDecimalLessThan: (expected: number | string) => R; toBeBetweenDates: (start: Date, end: Date) => R; diff --git a/vitest.setup.ts b/vitest.setup.ts index a2006fcf..4b777a1a 100644 --- a/vitest.setup.ts +++ b/vitest.setup.ts @@ -8,7 +8,7 @@ expect.extend({ toBeBigDecimalCloseTo( received: BigDecimal, expected: BigDecimal, - precision = 2, + precision: number, ) { const pass = received.round(precision).eq(expected.round(precision));