Skip to content

Commit

Permalink
test: add spec for EarningsInterface#accountAssetsData (#227)
Browse files Browse the repository at this point in the history
  • Loading branch information
karelianpie authored and jstashh committed May 31, 2022
1 parent 6f32db9 commit db88cbe
Show file tree
Hide file tree
Showing 5 changed files with 145 additions and 26 deletions.
72 changes: 66 additions & 6 deletions src/interfaces/earnings.spec.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
import { getAddress } from "@ethersproject/address";
import BigNumber from "bignumber.js";

import { Address, AssetHistoricEarnings, ChainId, EarningsInterface, SdkError, Usdc, VaultStatic } from "..";
import { Address, ApyMap, AssetHistoricEarnings, ChainId, EarningsInterface, SdkError, Usdc, VaultStatic } from "..";
import { Context } from "../context";
import { createMockAssetHistoricEarnings, createMockAssetStaticVaultV2 } from "../test-utils/factories";
import { createMockAccountEarningsResponse } from "../test-utils/factories/accountEarningsResponse.factory";
import { createMockApy } from "../test-utils/factories/apy.factory";
import { Yearn } from "../yearn";

const getAddressMock = jest.fn();
const subgraphFetchQueryMock = jest.fn();
const visionApyMock: jest.Mock<Promise<ApyMap<string>>> = jest.fn();
const oracleGetPriceUsdcMock: jest.Mock<Promise<Usdc>> = jest.fn();
const lensAdaptersVaultsV2AssetsStaticMock: jest.Mock<Promise<VaultStatic[]>> = jest.fn();
const getBlockNumberMock: jest.Mock<Promise<number>> = jest.fn();
Expand All @@ -18,7 +21,9 @@ jest.mock("../yearn", () => ({
subgraph: {
fetchQuery: subgraphFetchQueryMock
},
vision: {},
vision: {
apy: visionApyMock
},
lens: {
adapters: { vaults: { v2: { assetsStatic: lensAdaptersVaultsV2AssetsStaticMock } } }
},
Expand All @@ -30,7 +35,7 @@ jest.mock("../yearn", () => ({
}));

jest.mock("@ethersproject/address", () => ({
getAddress: jest.fn().mockImplementation(() => getAddressMock)
getAddress: jest.fn(() => getAddressMock())
}));

jest.mock("../context", () => ({
Expand Down Expand Up @@ -146,8 +151,8 @@ describe("EarningsInterface", () => {
expect(actualAssetEarnings).toEqual({
amount: "2000000000000000000",
amountUsdc: "7",
assetAddress: getAddressMock,
tokenAddress: getAddressMock
assetAddress: "0x001",
tokenAddress: "0x001"
});
expect(oracleGetPriceUsdcMock).toHaveBeenCalledTimes(1);
expect(oracleGetPriceUsdcMock).toHaveBeenCalledWith("vaultTokenId");
Expand All @@ -160,7 +165,62 @@ describe("EarningsInterface", () => {
});

describe("accountAssetsData", () => {
it.todo("todo");
describe("when there is no account", () => {
beforeEach(() => {
subgraphFetchQueryMock.mockResolvedValue(undefined);
});

it("should return an empty EarningsUserData", async () => {
const actual = await earningsInterface.accountAssetsData("0x001");

expect(actual).toEqual({
earnings: "0",
holdings: "0",
earningsAssetData: [],
grossApy: 0,
estimatedYearlyYield: "0"
});
});
});

describe("when there is an account", () => {
const accountAddress: Address = "0x001";
const apyMock = createMockApy({
net_apy: 42
});
const accountEarningsResponse = createMockAccountEarningsResponse();
const tokensValueInUsdcMock: jest.Mock<Promise<BigNumber>> = jest.fn();

beforeEach(() => {
subgraphFetchQueryMock.mockResolvedValue(accountEarningsResponse);
visionApyMock.mockResolvedValue({
[accountAddress]: apyMock
});
getAddressMock.mockReturnValue(accountAddress);
(earningsInterface as any).tokensValueInUsdc = tokensValueInUsdcMock;
tokensValueInUsdcMock.mockResolvedValue(new BigNumber(10));
});

it("should return an empty EarningsUserData", async () => {
const actual = await earningsInterface.accountAssetsData(accountAddress);

expect(visionApyMock).toHaveBeenCalledTimes(1);
expect(visionApyMock).toHaveBeenCalledWith([accountAddress]);
expect(getAddressMock).toHaveBeenCalledTimes(1);
expect(actual).toEqual({
earnings: "10",
earningsAssetData: [
{
assetAddress: accountAddress,
earned: "10"
}
],
estimatedYearlyYield: "420",
grossApy: 42,
holdings: "10"
});
});
});
});

describe("assetsHistoricEarnings", () => {
Expand Down
40 changes: 20 additions & 20 deletions src/interfaces/earnings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,33 +78,32 @@ export class EarningsInterface<C extends ChainId> extends ServiceInterface<C> {
buildAccountEarningsVariables(accountAddress)
);

const account = response.data.account;
const account = response?.data?.account;

if (!account) {
return { earnings: "0", holdings: "0", earningsAssetData: [], grossApy: 0, estimatedYearlyYield: "0" };
}

const assetAddresses = account.vaultPositions.map(position => getAddress(position.vault.id));
const apys = await this.yearn.services.vision.apy(assetAddresses);

const assetsData = await Promise.all(
account.vaultPositions.map(async assetPosition => {
const balanceTokens = new BigNumber(assetPosition.balanceShares)
.multipliedBy(new BigNumber(assetPosition.vault.latestUpdate?.pricePerShare || 0))
.div(10 ** assetPosition.token.decimals);

const deposits = assetPosition.updates
.map(update => new BigNumber(update.deposits))
.reduce((sum, value) => sum.plus(value));
const withdrawals = assetPosition.updates
.map(update => new BigNumber(update.withdrawals))
.reduce((sum, value) => sum.plus(value));
const tokensReceived = assetPosition.updates
.map(update => new BigNumber(update.tokensReceived))
.reduce((sum, value) => sum.plus(value));
const tokensSent = assetPosition.updates
.map(update => new BigNumber(update.tokensSent))
.reduce((sum, value) => sum.plus(value));
const { deposits, withdrawals, tokensReceived, tokensSent } = assetPosition.updates.reduce(
({ deposits, withdrawals, tokensReceived, tokensSent }, current) => ({
deposits: deposits.plus(new BigNumber(current.deposits)),
withdrawals: withdrawals.plus(new BigNumber(current.withdrawals)),
tokensReceived: tokensReceived.plus(new BigNumber(current.tokensReceived)),
tokensSent: tokensSent.plus(new BigNumber(current.tokensSent))
}),
{
deposits: BigZero,
withdrawals: BigZero,
tokensReceived: BigZero,
tokensSent: BigZero
}
);

const positiveTokens = balanceTokens.plus(withdrawals).plus(tokensSent);
const negativeTokens = deposits.plus(tokensReceived);
Expand All @@ -122,16 +121,17 @@ export class EarningsInterface<C extends ChainId> extends ServiceInterface<C> {
assetPosition.token.decimals
);

const assetAddress = getAddress(assetPosition.vault.id);

return {
assetAddress: assetAddress,
balanceUsdc: balanceUsdc,
assetAddress: getAddress(assetPosition.vault.id),
balanceUsdc,
earned: earningsUsdc.toFixed(0)
};
})
);

const assetAddresses = assetsData.map(assetData => assetData.assetAddress);
const apys = await this.yearn.services.vision.apy(assetAddresses);

const totalEarnings = assetsData.map(datum => new BigNumber(datum.earned)).reduce((sum, value) => sum.plus(value));
const holdings = assetsData.map(datum => new BigNumber(datum.balanceUsdc)).reduce((sum, value) => sum.plus(value));

Expand Down
36 changes: 36 additions & 0 deletions src/test-utils/factories/accountEarningsResponse.factory.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { AccountEarningsResponse } from "../../services/subgraph/responses";

export const DEFAULT_ACCOUNT_EARNINGS_RESPONSE: AccountEarningsResponse = {
data: {
account: {
vaultPositions: [
{
balanceShares: "1",
token: {
id: "AERTokenId",
decimals: 18
},
shareToken: {
symbol: "AER"
},
updates: [
{
deposits: "1",
withdrawals: "2",
tokensReceived: "3",
tokensSent: "4"
}
],
vault: {
id: "AERVaultId"
}
}
]
}
}
};

export const createMockAccountEarningsResponse = (overwrites: Partial<AccountEarningsResponse> = {}) => ({
...DEFAULT_ACCOUNT_EARNINGS_RESPONSE,
...overwrites
});
21 changes: 21 additions & 0 deletions src/test-utils/factories/apy.factory.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { Apy } from "../..";

export const DEFAULT_APY: Apy = {
type: "apyType",
gross_apr: 2,
net_apy: 3,
fees: {
performance: null,
withdrawal: null,
management: null,
keep_crv: null,
cvx_keep_crv: null
},
points: null,
composite: null
};

export const createMockApy = (overwrites: Partial<Apy> = {}) => ({
...DEFAULT_APY,
...overwrites
});
2 changes: 2 additions & 0 deletions src/test-utils/factories/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
export * from "./accountEarningsResponse.factory";
export * from "./apy.factory";
export * from "./asset.factory";
export * from "./assetHistoricEarnings.factory";
export * from "./balance.factory";
Expand Down

0 comments on commit db88cbe

Please sign in to comment.