Skip to content
This repository has been archived by the owner on May 31, 2023. It is now read-only.

feat: add vesting resolver #173

Merged
merged 2 commits into from
Apr 5, 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
1 change: 0 additions & 1 deletion src/constants.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,4 @@ export default {
nativeAssetId: '0',
basiliskAddressPrefix: 10041, // prefix for the ss58 address formatting of substrate addresses
basiliskWeb3ProviderName: 'basilisk-ui',
defaultValue: '0',
};
1 change: 1 addition & 0 deletions src/errors.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,5 @@ export default {
noArgumentsProvidedBalanceQuery:
'No Arguments have been provided to the balance query',
invalidTransferVariables: 'Invalid transfer parameters provided',
vestingScheduleIncomplete: 'Vesting schedule has at least one undefined property'
};
4 changes: 2 additions & 2 deletions src/hooks/apollo/useApollo.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import { usePoolsMutationResolvers } from '../pools/resolvers/usePoolsMutationRe
import { useExtensionResolvers } from '../extension/resolvers/useExtensionResolvers';
import { usePersistentConfig } from '../config/usePersistentConfig';
import { useFaucetResolvers } from '../faucet/resolvers/useFaucetResolvers';
import { useVestingScheduleQueryResolvers } from '../vesting/useVestingScheduleQueryResolvers';
import { useVestingQueryResolvers } from '../vesting/useVestingQueryResolvers';

/**
* Add all local gql resolvers here
Expand Down Expand Up @@ -48,7 +48,7 @@ export const useResolvers: () => Resolvers = () => {
LBPPool,
Account: {
...useBalanceQueryResolvers(),
...useVestingScheduleQueryResolvers()
...useVestingQueryResolvers()
}
};
};
Expand Down
47 changes: 0 additions & 47 deletions src/hooks/vesting/calculateClaimableAmount.test.tsx

This file was deleted.

90 changes: 46 additions & 44 deletions src/hooks/vesting/calculateClaimableAmount.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
import { ApiPromise } from '@polkadot/api';
import { BalanceLock } from '@polkadot/types/interfaces';
import { Codec } from '@polkadot/types/types';
import BigNumber from 'bignumber.js';
import { find } from 'lodash';
import constants from '../../constants';
import { VestingSchedule } from './useGetVestingScheduleByAddress';
import errors from '../../errors';
import { VestingSchedule } from './useGetVestingByAddress';

export const balanceLockDataType = 'Vec<BalanceLock>';
export const tokensLockDataType = balanceLockDataType;
Expand Down Expand Up @@ -61,58 +60,61 @@ export const getLockedBalanceByAddressAndLockId = async (
};

/**
* This function casts a number in string representation
* to a BigNumber. If the input is undefined, it returns
* a default value.
* Calculates original and future lock for given VestingSchedule.
* https://gist.github.com/maht0rz/53466af0aefba004d5a4baad23f8ce26
*/
export const toBN = (numberAsString: string | undefined) => {
// TODO: check if it is any good to use default values
// on undefined VestingSchedule properties!
if (!numberAsString) return new BigNumber(constants.defaultValue);
return new BigNumber(numberAsString);
};

// https://gist.github.com/maht0rz/53466af0aefba004d5a4baad23f8ce26
// TODO: check if calc makes sense for undefined VestingSchedule properties
export const calculateFutureLock = (
export const calculateLock = (
vestingSchedule: VestingSchedule,
currentBlockNumber: BigNumber
) => {
const startPeriod = toBN(vestingSchedule.start);
const period = toBN(vestingSchedule.period);
const numberOfPeriods = currentBlockNumber
currentBlockNumber: string
): [BigNumber, BigNumber] => {
// check for undefined property in vestingSchedule
if (
!vestingSchedule.perPeriod ||
!vestingSchedule.period ||
!vestingSchedule.periodCount ||
!vestingSchedule.start
)
throw Error(errors.vestingScheduleIncomplete);

const startPeriod = new BigNumber(vestingSchedule.start);
const period = new BigNumber(vestingSchedule.period);
const numberOfPeriods = new BigNumber(currentBlockNumber)
.minus(startPeriod)
.dividedBy(period);

const perPeriod = toBN(vestingSchedule.perPeriod);
const perPeriod = new BigNumber(vestingSchedule.perPeriod);
const vestedOverPeriods = numberOfPeriods.multipliedBy(perPeriod);

const periodCount = toBN(vestingSchedule.periodCount);
const periodCount = new BigNumber(vestingSchedule.periodCount);
const originalLock = periodCount.multipliedBy(perPeriod);

const futureLock = originalLock.minus(vestedOverPeriods);
return futureLock;

return [originalLock, futureLock];
};

// get lockedVestingAmount from function getLockedBalanceByAddressAndLockId
export const calculateClaimableAmount = (
export const calculateTotalLocks = (
vestingSchedules: VestingSchedule[],
lockedVestingAmount: string,
currentBlockNumber: BigNumber
): BigNumber => {
// calculate futureLock for every vesting schedule and sum to total
const totalFutureLocks = vestingSchedules.reduce(function (
total,
vestingSchedule
) {
const futureLock = calculateFutureLock(vestingSchedule, currentBlockNumber);
return total.plus(futureLock);
},
new BigNumber(0));

// calculate claimable amount
const remainingVestingAmount = toBN(lockedVestingAmount);
const claimableAmount = remainingVestingAmount.minus(totalFutureLocks);

return claimableAmount;
currentBlockNumber: string
) => {
// calculate originalLock and futureLock for every vesting schedule and sum to total
const total = vestingSchedules.reduce(
function (total, vestingSchedule) {
const [originalLock, futureLock] = calculateLock(
vestingSchedule,
currentBlockNumber
);

total.original.plus(originalLock);
total.future.plus(futureLock);

return total;
},
{ original: new BigNumber('0'), future: new BigNumber('0') }
);

return {
original: total.original.toString(),
future: total.future.toString(),
};
};
Original file line number Diff line number Diff line change
@@ -1,42 +1,46 @@
import { useCallback, useMemo } from 'react';
import { useMemo } from 'react';
import { usePolkadotJsContext } from '../polkadotJs/usePolkadotJs';
import { Vec } from '@polkadot/types';
import { VestingScheduleOf } from '@open-web3/orml-types/interfaces';
import { ApiPromise } from '@polkadot/api';
import { calculateClaimableAmount, getLockedBalanceByAddressAndLockId, vestingBalanceLockId } from './calculateClaimableAmount';
import {
calculateTotalLocks,
getLockedBalanceByAddressAndLockId,
vestingBalanceLockId,
} from './calculateClaimableAmount';
import { readLastBlock } from '../lastBlock/readLastBlock';
import { ApolloCache, ApolloClient } from '@apollo/client';
import { ApolloClient } from '@apollo/client';
import BigNumber from 'bignumber.js';
import { fromPrecision12 } from '../math/useFromPrecision';

export const vestingScheduleDataType = 'Vec<VestingScheduleOf>';

// TODO: use type from graphql in VestingSchedule.graphql
// TODO: use type from graphql in VestingSchedule.graphql
// without remainingVestingAmount property - currently yarn run codegen is broken
export type VestingSchedule = {
start: string | undefined;
period: string| undefined;
periodCount: string| undefined;
perPeriod: string| undefined;
period: string | undefined;
periodCount: string | undefined;
perPeriod: string | undefined;
};

export const getVestingSchedulesByAddressFactory =
(apiInstance?: ApiPromise) => async (client: ApolloClient<object>, address?: string) => {
export const getVestingByAddressFactory =
(apiInstance?: ApiPromise) =>
async (client: ApolloClient<object>, address?: string) => {
if (!apiInstance || !address) return;

const currentBlockNumber =
readLastBlock(client)?.lastBlock?.relaychainBlockNumber;
if (!currentBlockNumber)
throw Error(`Can't calculate locks without current block number.`);

// TODO: instead of multiple .createType calls, use the following
// https://github.com/AcalaNetwork/acala.js/blob/9634e2291f1723a84980b3087c55573763c8e82e/packages/sdk-core/src/functions/getSubscribeOrAtQuery.ts#L4
const vestingSchedulesData = apiInstance.createType(
vestingScheduleDataType,
await apiInstance.query.vesting.vestingSchedules(address)
) as Vec<VestingScheduleOf>;

const lockedVestingBalance = (await getLockedBalanceByAddressAndLockId(
apiInstance,
address,
vestingBalanceLockId
))?.amount?.toString();

const vestingSchedules = vestingSchedulesData.map((vestingSchedule) => {
// remap to object with string properties
return {
Expand All @@ -47,32 +51,42 @@ export const getVestingSchedulesByAddressFactory =
} as VestingSchedule;
});

const currentBlockNumber = readLastBlock(client)?.lastBlock?.relaychainBlockNumber;
const totalLocks = calculateTotalLocks(
vestingSchedules,
currentBlockNumber!
);

const lockedVestingBalance = (
await getLockedBalanceByAddressAndLockId(
apiInstance,
address,
vestingBalanceLockId
)
)?.amount?.toString();

const shouldCalculate = lockedVestingBalance && currentBlockNumber;
if (!lockedVestingBalance)
throw Error(`Can't fetch remaining vesting balance`);

const claimableAmount = shouldCalculate ? calculateClaimableAmount(
vestingSchedules,
lockedVestingBalance,
new BigNumber(currentBlockNumber)
) : '0'
const totalRemainingVesting = new BigNumber(lockedVestingBalance!);
// claimable = remainingVesting - all future locks
const claimableAmount = totalRemainingVesting.minus(
new BigNumber(totalLocks.future)
);

console.log('claimableAmount', {
return {
claimableAmount: fromPrecision12(claimableAmount.toString()),
lockedVestingBalance: fromPrecision12(lockedVestingBalance)
})

return vestingSchedules;
originalLockBalance: fromPrecision12(totalLocks.original),
lockedVestingBalance: fromPrecision12(totalRemainingVesting.toString()),
};
};

// TODO: change to plural "Schedules"
export const useGetVestingScheduleByAddress = () => {
export const useGetVestingByAddress = () => {
const { apiInstance } = usePolkadotJsContext();

const getVestingScheduleByAddress = useMemo(
() => getVestingSchedulesByAddressFactory(apiInstance),
const getVestingByAddress = useMemo(
() => getVestingByAddressFactory(apiInstance),
[apiInstance]
);

return getVestingScheduleByAddress;
return getVestingByAddress;
};
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
import { ApolloCache, ApolloClient } from '@apollo/client';
import { ApolloClient } from '@apollo/client';
import { useCallback } from 'react';
import { Account } from '../../generated/graphql';
import { withErrorHandler } from '../apollo/withErrorHandler';
import { useGetVestingScheduleByAddress } from './useGetVestingScheduleByAddress';
import { useGetVestingByAddress } from './useGetVestingByAddress';

export const useVestingScheduleQueryResolvers = () => {
const getVestingScheduleByAddress = useGetVestingScheduleByAddress();
export const useVestingQueryResolvers = () => {
const getVestingByAddress = useGetVestingByAddress();
const vestingSchedule = withErrorHandler(
useCallback(
async (
account: Account,
_args: any,
{ client }: { client: ApolloClient<any> }
) => await getVestingScheduleByAddress(client, account.id),
[getVestingScheduleByAddress]
) => await getVestingByAddress(client, account.id),
[getVestingByAddress]
),
'vestingSchedule'
);
Expand Down