Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

test: Add spec for EarningsInterface#accountAssetsData #227

Merged
merged 1 commit into from
Feb 25, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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(
jstashh marked this conversation as resolved.
Show resolved Hide resolved
({ 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