From 9eff1b749bf709df331e20b33a8fcfc35c6cd02a Mon Sep 17 00:00:00 2001 From: Claudiu Lataretu <claudiu.lataretu@gmail.com> Date: Mon, 26 Aug 2024 18:01:08 +0300 Subject: [PATCH 1/7] MEX-514: added compute methods for user staking boosted APRs Signed-off-by: Claudiu Lataretu <claudiu.lataretu@gmail.com> --- .../services/staking.compute.service.ts | 294 ++++++++++-------- 1 file changed, 160 insertions(+), 134 deletions(-) diff --git a/src/modules/staking/services/staking.compute.service.ts b/src/modules/staking/services/staking.compute.service.ts index 2da8bbcfd..b0895cca9 100644 --- a/src/modules/staking/services/staking.compute.service.ts +++ b/src/modules/staking/services/staking.compute.service.ts @@ -10,7 +10,6 @@ import { GetOrSetCache } from 'src/helpers/decorators/caching.decorator'; import { CacheTtlInfo } from 'src/services/caching/cache.ttl.info'; import { computeValueUSD, denominateAmount } from 'src/utils/token.converters'; import { OptimalCompoundModel } from '../models/staking.model'; -import { TokenService } from 'src/modules/tokens/services/token.service'; import { TokenComputeService } from 'src/modules/tokens/services/token.compute.service'; import { TokenDistributionModel } from 'src/submodules/weekly-rewards-splitting/models/weekly-rewards-splitting.model'; import { WeeklyRewardsSplittingComputeService } from 'src/submodules/weekly-rewards-splitting/services/weekly-rewards-splitting.compute.service'; @@ -18,6 +17,7 @@ import { WeekTimekeepingComputeService } from 'src/submodules/week-timekeeping/s import { WeeklyRewardsSplittingAbiService } from 'src/submodules/weekly-rewards-splitting/services/weekly-rewards-splitting.abi.service'; import { EsdtTokenPayment } from 'src/models/esdtTokenPayment.model'; import { MXApiService } from 'src/services/multiversx-communication/mx.api.service'; +import { WeekTimekeepingAbiService } from 'src/submodules/week-timekeeping/services/week-timekeeping.abi.service'; @Injectable() export class StakingComputeService { @@ -26,9 +26,9 @@ export class StakingComputeService { @Inject(forwardRef(() => StakingService)) private readonly stakingService: StakingService, private readonly contextGetter: ContextGetterService, - private readonly tokenService: TokenService, private readonly tokenCompute: TokenComputeService, private readonly weekTimekeepingCompute: WeekTimekeepingComputeService, + private readonly weekTimeKeepingAbi: WeekTimekeepingAbiService, private readonly weeklyRewardsSplittingAbi: WeeklyRewardsSplittingAbiService, private readonly weeklyRewardsSplittingCompute: WeeklyRewardsSplittingComputeService, private readonly apiService: MXApiService, @@ -476,81 +476,101 @@ export class StakingComputeService { userAddress: string, week: number, ): Promise<string> { - return this.computeUserAccumulatedRewards(scAddress, userAddress, week); + const rewards = await this.computeUserRewardsForWeek( + scAddress, + userAddress, + week, + ); + + return rewards[0] ? rewards[0].amount : '0'; } - async computeUserAccumulatedRewards( + async computeUserRewardsForWeek( scAddress: string, userAddress: string, week: number, - ): Promise<string> { - const [ - boostedYieldsFactors, - boostedYieldsRewardsPercenatage, - userEnergy, - totalRewards, - rewardsPerBlock, - farmTokenSupply, - totalEnergy, - blocksInWeek, - liquidity, - ] = await Promise.all([ - this.stakingAbi.boostedYieldsFactors(scAddress), - this.stakingAbi.boostedYieldsRewardsPercenatage(scAddress), - this.weeklyRewardsSplittingAbi.userEnergyForWeek( + rewardsPerWeek?: EsdtTokenPayment[], + ): Promise<EsdtTokenPayment[]> { + const userRewardsForWeek = []; + + const [currentWeek, userEnergyForWeek, totalEnergyForWeek, liquidity] = + await Promise.all([ + this.weekTimeKeepingAbi.currentWeek(scAddress), + this.weeklyRewardsSplittingAbi.userEnergyForWeek( + scAddress, + userAddress, + week, + ), + this.weeklyRewardsSplittingAbi.totalEnergyForWeek( + scAddress, + week, + ), + + this.stakingAbi.userTotalStakePosition(scAddress, userAddress), + ]); + + const rewardsForWeek = + rewardsPerWeek ?? + (await this.weeklyRewardsSplittingAbi.totalRewardsForWeek( scAddress, - userAddress, week, - ), - this.stakingAbi.accumulatedRewardsForWeek(scAddress, week), - this.stakingAbi.perBlockRewardsAmount(scAddress), - this.stakingAbi.farmTokenSupply(scAddress), - this.weeklyRewardsSplittingAbi.totalEnergyForWeek(scAddress, week), - this.computeBlocksInWeek(scAddress, week), - this.stakingAbi.userTotalStakePosition(scAddress, userAddress), - ]); + )); - const energyAmount = userEnergy.amount; + if (rewardsForWeek.length === 0) { + return userRewardsForWeek; + } + + if (rewardsForWeek.length !== 1) { + throw new Error('Invalid boosted yields rewards'); + } - const userHasMinEnergy = new BigNumber(energyAmount).isGreaterThan( - boostedYieldsFactors.minEnergyAmount, + const boostedYieldsFactors = await this.stakingAbi.boostedYieldsFactors( + scAddress, ); + + const userHasMinEnergy = new BigNumber( + userEnergyForWeek.amount, + ).isGreaterThan(boostedYieldsFactors.minEnergyAmount); if (!userHasMinEnergy) { - return '0'; + return userRewardsForWeek; } const userMinFarmAmount = new BigNumber(liquidity).isGreaterThan( boostedYieldsFactors.minFarmAmount, ); if (!userMinFarmAmount) { - return '0'; + return userRewardsForWeek; } - if (totalRewards.length === 0) { - return '0'; + const farmTokenSupply = + week === currentWeek + ? await this.stakingAbi.farmTokenSupply(scAddress) + : await this.stakingAbi.farmSupplyForWeek(scAddress, week); + + const rewardForWeek = rewardsForWeek[0]; + + const weeklyRewardsAmount = new BigNumber(rewardForWeek.amount); + if (weeklyRewardsAmount.isZero()) { + return userRewardsForWeek; } - const userMaxBoostedRewardsPerBlock = new BigNumber(rewardsPerBlock) - .multipliedBy(boostedYieldsRewardsPercenatage) - .dividedBy(constantsConfig.MAX_PERCENT) + const userMaxRewards = weeklyRewardsAmount .multipliedBy(liquidity) - .dividedBy(farmTokenSupply); + .multipliedBy(boostedYieldsFactors.maxRewardsFactor) + .dividedBy(farmTokenSupply) + .integerValue(); - const userRewardsForWeek = new BigNumber( - boostedYieldsFactors.maxRewardsFactor, - ) - .multipliedBy(userMaxBoostedRewardsPerBlock) - .multipliedBy(blocksInWeek); - - const boostedRewardsByEnergy = new BigNumber(totalRewards) + const boostedRewardsByEnergy = weeklyRewardsAmount .multipliedBy(boostedYieldsFactors.userRewardsEnergy) - .multipliedBy(userEnergy.amount) - .dividedBy(totalEnergy); + .multipliedBy(userEnergyForWeek.amount) + .dividedBy(totalEnergyForWeek) + .integerValue(); - const boostedRewardsByTokens = new BigNumber(totalRewards) + const boostedRewardsByTokens = weeklyRewardsAmount .multipliedBy(boostedYieldsFactors.userRewardsFarm) .multipliedBy(liquidity) - .dividedBy(farmTokenSupply); + .dividedBy(farmTokenSupply) + .integerValue(); const constantsBase = new BigNumber( boostedYieldsFactors.userRewardsEnergy, @@ -558,116 +578,122 @@ export class StakingComputeService { const boostedRewardAmount = boostedRewardsByEnergy .plus(boostedRewardsByTokens) - .dividedBy(constantsBase); + .dividedBy(constantsBase) + .integerValue(); - const paymentAmount = - boostedRewardAmount.comparedTo(userRewardsForWeek) < 1 + const userRewardForWeek = + boostedRewardAmount.comparedTo(userMaxRewards) < 1 ? boostedRewardAmount - : userRewardsForWeek; + : userMaxRewards; + + if (userRewardForWeek.isPositive()) { + userRewardsForWeek.push( + new EsdtTokenPayment({ + tokenID: rewardForWeek.tokenID, + nonce: rewardForWeek.nonce, + amount: userRewardForWeek.toFixed(), + }), + ); + } - return paymentAmount.integerValue().toFixed(); + return userRewardsForWeek; } - async computeUserRewardsForWeek( + async computeUserCurentBoostedAPR( scAddress: string, userAddress: string, - week: number, - ): Promise<EsdtTokenPayment[]> { - const payments: EsdtTokenPayment[] = []; - const [ - totalRewardsForWeek, - userEnergyForWeek, - totalEnergyForWeek, - liquidity, - ] = await Promise.all([ - this.weeklyRewardsSplittingAbi.totalRewardsForWeek(scAddress, week), - this.weeklyRewardsSplittingAbi.userEnergyForWeek( - scAddress, - userAddress, - week, - ), - this.weeklyRewardsSplittingAbi.totalEnergyForWeek(scAddress, week), - this.stakingAbi.userTotalStakePosition(scAddress, userAddress), - ]); + ): Promise<number> { + const [currentWeek, boostedRewardsPerWeek, userTotalStakePosition] = + await Promise.all([ + this.weekTimeKeepingAbi.currentWeek(scAddress), + this.computeBoostedRewardsPerWeek(scAddress), + this.stakingAbi.userTotalStakePosition(scAddress, userAddress), + ]); - const boostedYieldsFactors = await this.stakingAbi.boostedYieldsFactors( + const userRewardsPerWeek = await this.computeUserRewardsForWeek( scAddress, + userAddress, + currentWeek, + boostedRewardsPerWeek, ); - const userHasMinEnergy = new BigNumber( - userEnergyForWeek.amount, - ).isGreaterThan(boostedYieldsFactors.minEnergyAmount); - if (!userHasMinEnergy) { - return payments; - } + return new BigNumber(userRewardsPerWeek[0].amount) + .multipliedBy(52) + .dividedBy(userTotalStakePosition) + .toNumber(); + } - const userMinFarmAmount = new BigNumber(liquidity).isGreaterThan( - boostedYieldsFactors.minFarmAmount, - ); - if (!userMinFarmAmount) { - return payments; - } + async computeUserMaxBoostedAPR( + scAddress: string, + userAddress: string, + ): Promise<number> { + const [ + boostedRewardsPerWeek, + boostedYieldsFactors, + farmTokenSupply, + userTotalStakePosition, + ] = await Promise.all([ + this.computeBoostedRewardsPerWeek(scAddress), + this.stakingAbi.boostedYieldsFactors(scAddress), + this.stakingAbi.farmTokenSupply(scAddress), + this.stakingAbi.userTotalStakePosition(scAddress, userAddress), + ]); - if (totalRewardsForWeek.length === 0) { - return payments; - } + const userMaxRewardsPerWeek = new BigNumber( + boostedRewardsPerWeek[0].amount, + ) + .multipliedBy(boostedYieldsFactors.maxRewardsFactor) + .multipliedBy(userTotalStakePosition) + .dividedBy(farmTokenSupply); + return userMaxRewardsPerWeek + .multipliedBy(52) + .dividedBy(userTotalStakePosition) + .toNumber(); + } + + async computeBoostedRewardsPerWeek( + scAddress: string, + ): Promise<EsdtTokenPayment[]> { const [ + rewardTokenID, rewardsPerBlock, + annualPercentageRewards, farmTokenSupply, - boostedYieldsRewardsPercenatage, + boostedYieldsRewardsPercentage, ] = await Promise.all([ + this.stakingAbi.rewardTokenID(scAddress), this.stakingAbi.perBlockRewardsAmount(scAddress), + this.stakingAbi.annualPercentageRewards(scAddress), this.stakingAbi.farmTokenSupply(scAddress), this.stakingAbi.boostedYieldsRewardsPercenatage(scAddress), ]); - const userMaxBoostedRewardsPerBlock = new BigNumber(rewardsPerBlock) - .multipliedBy(boostedYieldsRewardsPercenatage) + const rewardsPerBlockAPRBound = new BigNumber(farmTokenSupply) + .multipliedBy(annualPercentageRewards) .dividedBy(constantsConfig.MAX_PERCENT) - .multipliedBy(liquidity) - .dividedBy(farmTokenSupply); + .dividedBy(constantsConfig.BLOCKS_IN_YEAR); - const userRewardsForWeek = new BigNumber( - boostedYieldsFactors.maxRewardsFactor, + const actualRewardsPerBlock = new BigNumber(rewardsPerBlock).isLessThan( + rewardsPerBlockAPRBound, ) - .multipliedBy(userMaxBoostedRewardsPerBlock) - .multipliedBy(constantsConfig.BLOCKS_PER_WEEK); - - for (const weeklyRewards of totalRewardsForWeek) { - const boostedRewardsByEnergy = new BigNumber(weeklyRewards.amount) - .multipliedBy(boostedYieldsFactors.userRewardsEnergy) - .multipliedBy(userEnergyForWeek.amount) - .dividedBy(totalEnergyForWeek); - - const boostedRewardsByTokens = new BigNumber(weeklyRewards.amount) - .multipliedBy(boostedYieldsFactors.userRewardsFarm) - .multipliedBy(liquidity) - .dividedBy(farmTokenSupply); - - const constantsBase = new BigNumber( - boostedYieldsFactors.userRewardsEnergy, - ).plus(boostedYieldsFactors.userRewardsFarm); - - const boostedRewardAmount = boostedRewardsByEnergy - .plus(boostedRewardsByTokens) - .dividedBy(constantsBase); - - const paymentAmount = - boostedRewardAmount.comparedTo(userRewardsForWeek) < 1 - ? boostedRewardAmount - : userRewardsForWeek; - if (paymentAmount.isPositive()) { - const payment = new EsdtTokenPayment(); - payment.amount = paymentAmount.integerValue().toFixed(); - payment.nonce = 0; - payment.tokenID = weeklyRewards.tokenID; - payment.tokenType = weeklyRewards.tokenType; - payments.push(payment); - } - } - - return payments; + ? new BigNumber(rewardsPerBlock) + : rewardsPerBlockAPRBound; + const blocksInWeek = 14440 * 7; + const totalRewardsPerWeek = + actualRewardsPerBlock.multipliedBy(blocksInWeek); + + return [ + new EsdtTokenPayment({ + tokenID: rewardTokenID, + nonce: 0, + amount: totalRewardsPerWeek + .multipliedBy(boostedYieldsRewardsPercentage) + .dividedBy(constantsConfig.MAX_PERCENT) + .integerValue() + .toFixed(), + }), + ]; } @ErrorLoggerAsync({ From 80ae381a77d677a81be48f50b68c8740b7d7dbad Mon Sep 17 00:00:00 2001 From: Claudiu Lataretu <claudiu.lataretu@gmail.com> Date: Mon, 26 Aug 2024 18:01:45 +0300 Subject: [PATCH 2/7] MEX-514: Added user boosted APRs to BoostedRewardsModel Signed-off-by: Claudiu Lataretu <claudiu.lataretu@gmail.com> --- src/modules/farm/models/farm.model.ts | 6 ++++ src/modules/staking/models/staking.model.ts | 8 +++++ .../staking/services/staking.service.ts | 14 +++++--- src/modules/staking/staking.module.ts | 8 +++-- src/modules/staking/staking.resolver.ts | 35 +++++++++++++++---- 5 files changed, 58 insertions(+), 13 deletions(-) diff --git a/src/modules/farm/models/farm.model.ts b/src/modules/farm/models/farm.model.ts index 84b23d351..72bbb521e 100644 --- a/src/modules/farm/models/farm.model.ts +++ b/src/modules/farm/models/farm.model.ts @@ -47,12 +47,18 @@ export class RewardsModel { export class BoostedRewardsModel { @Field() farmAddress: string; + @Field() + userAddress: string; @Field(() => [UserInfoByWeekModel], { nullable: true }) boostedRewardsWeeklyInfo: UserInfoByWeekModel[]; @Field(() => ClaimProgress, { nullable: true }) claimProgress: ClaimProgress; @Field({ nullable: true }) accumulatedRewards: string; + @Field({ nullable: true }) + curentBoostedAPR: number; + @Field({ nullable: true }) + maximumBoostedAPR: number; constructor(init?: Partial<BoostedRewardsModel>) { Object.assign(this, init); diff --git a/src/modules/staking/models/staking.model.ts b/src/modules/staking/models/staking.model.ts index 436c83eba..20a08545a 100644 --- a/src/modules/staking/models/staking.model.ts +++ b/src/modules/staking/models/staking.model.ts @@ -9,6 +9,7 @@ import { UserInfoByWeekModel, } from 'src/submodules/weekly-rewards-splitting/models/weekly-rewards-splitting.model'; import { BoostedYieldsFactors } from 'src/modules/farm/models/farm.v2.model'; +import { BoostedRewardsModel } from 'src/modules/farm/models/farm.model'; @ObjectType() export class StakingModel { @@ -109,6 +110,13 @@ export class StakingRewardsModel { } } +@ObjectType() +export class StakingBoostedRewardsModel extends BoostedRewardsModel { + constructor(init?: Partial<StakingBoostedRewardsModel>) { + super(init); + } +} + @ObjectType() export class OptimalCompoundModel { @Field(() => Int, { diff --git a/src/modules/staking/services/staking.service.ts b/src/modules/staking/services/staking.service.ts index dbba2b6a2..4d147ae56 100644 --- a/src/modules/staking/services/staking.service.ts +++ b/src/modules/staking/services/staking.service.ts @@ -9,7 +9,11 @@ import { DecodeAttributesArgs } from 'src/modules/proxy/models/proxy.args'; import { RemoteConfigGetterService } from 'src/modules/remote-config/remote-config.getter.service'; import { ContextGetterService } from 'src/services/context/context.getter.service'; import { MXApiService } from 'src/services/multiversx-communication/mx.api.service'; -import { StakingModel, StakingRewardsModel } from '../models/staking.model'; +import { + StakingBoostedRewardsModel, + StakingModel, + StakingRewardsModel, +} from '../models/staking.model'; import { StakingTokenAttributesModel, UnbondTokenAttributesModel, @@ -26,7 +30,6 @@ import { import { WeekTimekeepingAbiService } from 'src/submodules/week-timekeeping/services/week-timekeeping.abi.service'; import { WeeklyRewardsSplittingAbiService } from 'src/submodules/weekly-rewards-splitting/services/weekly-rewards-splitting.abi.service'; import { constantsConfig } from 'src/config'; -import { BoostedRewardsModel } from 'src/modules/farm/models/farm.model'; import { CollectionType } from 'src/modules/common/collection.type'; import { PaginationArgs } from 'src/modules/dex.model'; import { @@ -255,7 +258,7 @@ export class StakingService { async getStakingBoostedRewardsBatch( stakingAddresses: string[], userAddress: string, - ): Promise<BoostedRewardsModel[]> { + ): Promise<StakingBoostedRewardsModel[]> { const promises = stakingAddresses.map(async (address) => { return await this.getStakingBoostedRewards(address, userAddress); }); @@ -265,7 +268,7 @@ export class StakingService { async getStakingBoostedRewards( stakingAddress: string, userAddress: string, - ): Promise<BoostedRewardsModel> { + ): Promise<StakingBoostedRewardsModel> { const currentWeek = await this.weekTimekeepingAbi.currentWeek( stakingAddress, ); @@ -309,8 +312,9 @@ export class StakingService { currentWeek, ); - return new BoostedRewardsModel({ + return new StakingBoostedRewardsModel({ farmAddress: stakingAddress, + userAddress: userAddress, boostedRewardsWeeklyInfo: modelsList, claimProgress: currentClaimProgress, accumulatedRewards: userAccumulatedRewards, diff --git a/src/modules/staking/staking.module.ts b/src/modules/staking/staking.module.ts index a610aff6d..d45122bc7 100644 --- a/src/modules/staking/staking.module.ts +++ b/src/modules/staking/staking.module.ts @@ -9,7 +9,10 @@ import { StakingComputeService } from './services/staking.compute.service'; import { StakingService } from './services/staking.service'; import { StakingSetterService } from './services/staking.setter.service'; import { StakingTransactionService } from './services/staking.transactions.service'; -import { StakingResolver } from './staking.resolver'; +import { + StakingBoostedRewardsResolver, + StakingResolver, +} from './staking.resolver'; import { WeekTimekeepingModule } from 'src/submodules/week-timekeeping/week-timekeeping.module'; import { WeeklyRewardsSplittingModule } from 'src/submodules/weekly-rewards-splitting/weekly-rewards-splitting.module'; import { StakingFilteringService } from './services/staking.filtering.service'; @@ -30,8 +33,9 @@ import { StakingFilteringService } from './services/staking.filtering.service'; StakingSetterService, StakingComputeService, StakingTransactionService, - StakingResolver, StakingFilteringService, + StakingResolver, + StakingBoostedRewardsResolver, ], exports: [ StakingAbiService, diff --git a/src/modules/staking/staking.resolver.ts b/src/modules/staking/staking.resolver.ts index a0c07619a..64099386f 100644 --- a/src/modules/staking/staking.resolver.ts +++ b/src/modules/staking/staking.resolver.ts @@ -15,6 +15,7 @@ import { } from './models/staking.args'; import { OptimalCompoundModel, + StakingBoostedRewardsModel, StakingModel, StakingRewardsModel, } from './models/staking.model'; @@ -34,16 +35,38 @@ import { constantsConfig } from 'src/config'; import { WeeklyRewardsSplittingAbiService } from 'src/submodules/weekly-rewards-splitting/services/weekly-rewards-splitting.abi.service'; import { StakeAddressValidationPipe } from './validators/stake.address.validator'; import { BoostedYieldsFactors } from '../farm/models/farm.v2.model'; -import { - BoostedRewardsModel, - UserTotalBoostedPosition, -} from '../farm/models/farm.model'; +import { UserTotalBoostedPosition } from '../farm/models/farm.model'; import { StakingFarmsResponse } from './models/staking.farms.response'; import ConnectionArgs, { getPagingParameters, } from '../common/filters/connection.args'; import PageResponse from '../common/page.response'; +@Resolver(() => StakingBoostedRewardsModel) +export class StakingBoostedRewardsResolver { + constructor(private readonly stakingCompute: StakingComputeService) {} + + @ResolveField() + async curentBoostedAPR( + @Parent() parent: StakingBoostedRewardsModel, + ): Promise<number> { + return this.stakingCompute.computeUserCurentBoostedAPR( + parent.farmAddress, + parent.userAddress, + ); + } + + @ResolveField() + async maximumBoostedAPR( + @Parent() parent: StakingBoostedRewardsModel, + ): Promise<number> { + return this.stakingCompute.computeUserMaxBoostedAPR( + parent.farmAddress, + parent.userAddress, + ); + } +} + @Resolver(() => StakingModel) export class StakingResolver { constructor( @@ -315,14 +338,14 @@ export class StakingResolver { } @UseGuards(JwtOrNativeAuthGuard) - @Query(() => [BoostedRewardsModel], { + @Query(() => [StakingBoostedRewardsModel], { description: 'Returns staking boosted rewards for the user', }) async getStakingBoostedRewardsBatch( @Args('stakingAddresses', { type: () => [String] }) stakingAddresses: string[], @AuthUser() user: UserAuthResult, - ): Promise<BoostedRewardsModel[]> { + ): Promise<StakingBoostedRewardsModel[]> { return this.stakingService.getStakingBoostedRewardsBatch( stakingAddresses, user.address, From d92785b2f1ee3371ace5f3af3983e5c3c82b891f Mon Sep 17 00:00:00 2001 From: Claudiu Lataretu <claudiu.lataretu@gmail.com> Date: Mon, 26 Aug 2024 18:27:52 +0300 Subject: [PATCH 3/7] MEX-514: add compute for user farm boosted APRs Signed-off-by: Claudiu Lataretu <claudiu.lataretu@gmail.com> --- .../v2/services/farm.v2.compute.service.ts | 316 ++++++++++-------- 1 file changed, 185 insertions(+), 131 deletions(-) diff --git a/src/modules/farm/v2/services/farm.v2.compute.service.ts b/src/modules/farm/v2/services/farm.v2.compute.service.ts index 70558ed5c..f4db18eaa 100644 --- a/src/modules/farm/v2/services/farm.v2.compute.service.ts +++ b/src/modules/farm/v2/services/farm.v2.compute.service.ts @@ -19,6 +19,8 @@ import { CacheService } from '@multiversx/sdk-nestjs-cache'; import { TokenDistributionModel } from 'src/submodules/weekly-rewards-splitting/models/weekly-rewards-splitting.model'; import { WeeklyRewardsSplittingComputeService } from 'src/submodules/weekly-rewards-splitting/services/weekly-rewards-splitting.compute.service'; import { IFarmComputeServiceV2 } from './interfaces'; +import { WeekTimekeepingAbiService } from 'src/submodules/week-timekeeping/services/week-timekeeping.abi.service'; +import { computeValueUSD } from 'src/utils/token.converters'; @Injectable() export class FarmComputeServiceV2 @@ -36,6 +38,7 @@ export class FarmComputeServiceV2 protected readonly contextGetter: ContextGetterService, protected readonly tokenCompute: TokenComputeService, protected readonly cachingService: CacheService, + private readonly weekTimeKeepingAbi: WeekTimekeepingAbiService, private readonly weekTimekeepingCompute: WeekTimekeepingComputeService, private readonly weeklyRewardsSplittingAbi: WeeklyRewardsSplittingAbiService, private readonly weeklyRewardsSplittingCompute: WeeklyRewardsSplittingComputeService, @@ -164,81 +167,101 @@ export class FarmComputeServiceV2 userAddress: string, week: number, ): Promise<string> { - return this.computeUserAccumulatedRewards(scAddress, userAddress, week); + const rewards = await this.computeUserRewardsForWeek( + scAddress, + userAddress, + week, + ); + + return rewards[0] ? rewards[0].amount : '0'; } - async computeUserAccumulatedRewards( + async computeUserRewardsForWeek( scAddress: string, userAddress: string, week: number, - ): Promise<string> { - const [ - boostedYieldsFactors, - boostedYieldsRewardsPercenatage, - userEnergy, - totalRewards, - rewardsPerBlock, - farmTokenSupply, - totalEnergy, - blocksInWeek, - liquidity, - ] = await Promise.all([ - this.farmAbi.boostedYieldsFactors(scAddress), - this.farmAbi.boostedYieldsRewardsPercenatage(scAddress), - this.weeklyRewardsSplittingAbi.userEnergyForWeek( + rewardsPerWeek?: EsdtTokenPayment[], + ): Promise<EsdtTokenPayment[]> { + const userRewardsForWeek = []; + + const [currentWeek, userEnergyForWeek, totalEnergyForWeek, liquidity] = + await Promise.all([ + this.weekTimeKeepingAbi.currentWeek(scAddress), + this.weeklyRewardsSplittingAbi.userEnergyForWeek( + scAddress, + userAddress, + week, + ), + this.weeklyRewardsSplittingAbi.totalEnergyForWeek( + scAddress, + week, + ), + + this.farmAbi.userTotalFarmPosition(scAddress, userAddress), + ]); + + const rewardsForWeek = + rewardsPerWeek ?? + (await this.weeklyRewardsSplittingAbi.totalRewardsForWeek( scAddress, - userAddress, week, - ), - this.farmAbi.accumulatedRewardsForWeek(scAddress, week), - this.farmAbi.rewardsPerBlock(scAddress), - this.farmAbi.farmTokenSupply(scAddress), - this.weeklyRewardsSplittingAbi.totalEnergyForWeek(scAddress, week), - this.computeBlocksInWeek(scAddress, week), - this.farmAbi.userTotalFarmPosition(scAddress, userAddress), - ]); + )); - const energyAmount = userEnergy.amount; + if (rewardsForWeek.length === 0) { + return userRewardsForWeek; + } + + if (rewardsForWeek.length !== 1) { + throw new Error('Invalid boosted yields rewards'); + } - const userHasMinEnergy = new BigNumber(energyAmount).isGreaterThan( - boostedYieldsFactors.minEnergyAmount, + const boostedYieldsFactors = await this.farmAbi.boostedYieldsFactors( + scAddress, ); + + const userHasMinEnergy = new BigNumber( + userEnergyForWeek.amount, + ).isGreaterThan(boostedYieldsFactors.minEnergyAmount); if (!userHasMinEnergy) { - return '0'; + return userRewardsForWeek; } const userMinFarmAmount = new BigNumber(liquidity).isGreaterThan( boostedYieldsFactors.minFarmAmount, ); if (!userMinFarmAmount) { - return '0'; + return userRewardsForWeek; } - if (totalRewards.length === 0) { - return '0'; + const farmTokenSupply = + week === currentWeek + ? await this.farmAbi.farmTokenSupply(scAddress) + : await this.farmAbi.farmSupplyForWeek(scAddress, week); + + const rewardForWeek = rewardsForWeek[0]; + + const weeklyRewardsAmount = new BigNumber(rewardForWeek.amount); + if (weeklyRewardsAmount.isZero()) { + return userRewardsForWeek; } - const userMaxBoostedRewardsPerBlock = new BigNumber(rewardsPerBlock) - .multipliedBy(boostedYieldsRewardsPercenatage) - .dividedBy(constantsConfig.MAX_PERCENT) + const userMaxRewards = weeklyRewardsAmount .multipliedBy(liquidity) - .dividedBy(farmTokenSupply); - - const userRewardsForWeek = new BigNumber( - boostedYieldsFactors.maxRewardsFactor, - ) - .multipliedBy(userMaxBoostedRewardsPerBlock) - .multipliedBy(blocksInWeek); + .multipliedBy(boostedYieldsFactors.maxRewardsFactor) + .dividedBy(farmTokenSupply) + .integerValue(); - const boostedRewardsByEnergy = new BigNumber(totalRewards) + const boostedRewardsByEnergy = weeklyRewardsAmount .multipliedBy(boostedYieldsFactors.userRewardsEnergy) - .multipliedBy(userEnergy.amount) - .dividedBy(totalEnergy); + .multipliedBy(userEnergyForWeek.amount) + .dividedBy(totalEnergyForWeek) + .integerValue(); - const boostedRewardsByTokens = new BigNumber(totalRewards) + const boostedRewardsByTokens = weeklyRewardsAmount .multipliedBy(boostedYieldsFactors.userRewardsFarm) .multipliedBy(liquidity) - .dividedBy(farmTokenSupply); + .dividedBy(farmTokenSupply) + .integerValue(); const constantsBase = new BigNumber( boostedYieldsFactors.userRewardsEnergy, @@ -246,116 +269,147 @@ export class FarmComputeServiceV2 const boostedRewardAmount = boostedRewardsByEnergy .plus(boostedRewardsByTokens) - .dividedBy(constantsBase); + .dividedBy(constantsBase) + .integerValue(); - const paymentAmount = - boostedRewardAmount.comparedTo(userRewardsForWeek) < 1 + const userRewardForWeek = + boostedRewardAmount.comparedTo(userMaxRewards) < 1 ? boostedRewardAmount - : userRewardsForWeek; + : userMaxRewards; + + if (userRewardForWeek.isPositive()) { + userRewardsForWeek.push( + new EsdtTokenPayment({ + tokenID: rewardForWeek.tokenID, + nonce: rewardForWeek.nonce, + amount: userRewardForWeek.toFixed(), + }), + ); + } - return paymentAmount.integerValue().toFixed(); + return userRewardsForWeek; } - async computeUserRewardsForWeek( + async computeUserCurentBoostedAPR( scAddress: string, userAddress: string, - week: number, - ): Promise<EsdtTokenPayment[]> { - const payments: EsdtTokenPayment[] = []; + ): Promise<number> { const [ - totalRewardsForWeek, - userEnergyForWeek, - totalEnergyForWeek, - liquidity, + currentWeek, + boostedRewardsPerWeek, + userTotalFarmPosition, + farmToken, + farmedToken, + farmingTokenPriceUSD, + farmedTokenPriceUSD, ] = await Promise.all([ - this.weeklyRewardsSplittingAbi.totalRewardsForWeek(scAddress, week), - this.weeklyRewardsSplittingAbi.userEnergyForWeek( - scAddress, - userAddress, - week, - ), - this.weeklyRewardsSplittingAbi.totalEnergyForWeek(scAddress, week), + this.weekTimeKeepingAbi.currentWeek(scAddress), + this.computeBoostedRewardsPerWeek(scAddress), this.farmAbi.userTotalFarmPosition(scAddress, userAddress), + this.farmService.getFarmToken(scAddress), + this.farmService.getFarmedToken(scAddress), + this.farmingTokenPriceUSD(scAddress), + this.farmedTokenPriceUSD(scAddress), ]); - const boostedYieldsFactors = await this.farmAbi.boostedYieldsFactors( + const userRewardsPerWeek = await this.computeUserRewardsForWeek( scAddress, + userAddress, + currentWeek, + boostedRewardsPerWeek, ); - const userHasMinEnergy = new BigNumber( - userEnergyForWeek.amount, - ).isGreaterThan(boostedYieldsFactors.minEnergyAmount); - if (!userHasMinEnergy) { - return payments; - } - - const userMinFarmAmount = new BigNumber(liquidity).isGreaterThan( - boostedYieldsFactors.minFarmAmount, + const userTotalFarmPositionUSD = computeValueUSD( + userTotalFarmPosition, + farmToken.decimals, + farmingTokenPriceUSD, + ); + const userRewardsPerWeekUSD = computeValueUSD( + userRewardsPerWeek[0].amount, + farmedToken.decimals, + farmedTokenPriceUSD, ); - if (!userMinFarmAmount) { - return payments; - } - if (totalRewardsForWeek.length === 0) { - return payments; - } + return new BigNumber(userRewardsPerWeekUSD) + .multipliedBy(52) + .dividedBy(userTotalFarmPositionUSD) + .toNumber(); + } + async computeUserMaxBoostedAPR( + scAddress: string, + userAddress: string, + ): Promise<number> { const [ - rewardsPerBlock, + boostedRewardsPerWeek, + boostedYieldsFactors, farmTokenSupply, - boostedYieldsRewardsPercenatage, + userTotalFarmPosition, + farmToken, + farmedToken, + farmingTokenPriceUSD, + farmedTokenPriceUSD, ] = await Promise.all([ - this.farmAbi.rewardsPerBlock(scAddress), + this.computeBoostedRewardsPerWeek(scAddress), + this.farmAbi.boostedYieldsFactors(scAddress), this.farmAbi.farmTokenSupply(scAddress), - this.farmAbi.boostedYieldsRewardsPercenatage(scAddress), + this.farmAbi.userTotalFarmPosition(scAddress, userAddress), + this.farmService.getFarmToken(scAddress), + this.farmService.getFarmedToken(scAddress), + this.farmingTokenPriceUSD(scAddress), + this.farmedTokenPriceUSD(scAddress), ]); - const userMaxBoostedRewardsPerBlock = new BigNumber(rewardsPerBlock) - .multipliedBy(boostedYieldsRewardsPercenatage) - .dividedBy(constantsConfig.MAX_PERCENT) - .multipliedBy(liquidity) + const userMaxRewardsPerWeek = new BigNumber( + boostedRewardsPerWeek[0].amount, + ) + .multipliedBy(boostedYieldsFactors.maxRewardsFactor) + .multipliedBy(userTotalFarmPosition) .dividedBy(farmTokenSupply); - const userRewardsForWeek = new BigNumber( - boostedYieldsFactors.maxRewardsFactor, - ) - .multipliedBy(userMaxBoostedRewardsPerBlock) - .multipliedBy(constantsConfig.BLOCKS_PER_WEEK); - - for (const weeklyRewards of totalRewardsForWeek) { - const boostedRewardsByEnergy = new BigNumber(weeklyRewards.amount) - .multipliedBy(boostedYieldsFactors.userRewardsEnergy) - .multipliedBy(userEnergyForWeek.amount) - .dividedBy(totalEnergyForWeek); - - const boostedRewardsByTokens = new BigNumber(weeklyRewards.amount) - .multipliedBy(boostedYieldsFactors.userRewardsFarm) - .multipliedBy(liquidity) - .dividedBy(farmTokenSupply); - - const constantsBase = new BigNumber( - boostedYieldsFactors.userRewardsEnergy, - ).plus(boostedYieldsFactors.userRewardsFarm); - - const boostedRewardAmount = boostedRewardsByEnergy - .plus(boostedRewardsByTokens) - .dividedBy(constantsBase); - - const paymentAmount = - boostedRewardAmount.comparedTo(userRewardsForWeek) < 1 - ? boostedRewardAmount - : userRewardsForWeek; - if (paymentAmount.isPositive()) { - const payment = new EsdtTokenPayment(); - payment.amount = paymentAmount.integerValue().toFixed(); - payment.nonce = 0; - payment.tokenID = weeklyRewards.tokenID; - payment.tokenType = weeklyRewards.tokenType; - payments.push(payment); - } - } + const userTotalFarmPositionUSD = computeValueUSD( + userTotalFarmPosition, + farmToken.decimals, + farmingTokenPriceUSD, + ); + const userMaxRewardsPerWeekUSD = computeValueUSD( + userMaxRewardsPerWeek.toFixed(), + farmedToken.decimals, + farmedTokenPriceUSD, + ); + + return userMaxRewardsPerWeekUSD + .multipliedBy(52) + .dividedBy(userTotalFarmPositionUSD) + .toNumber(); + } + + async computeBoostedRewardsPerWeek( + scAddress: string, + ): Promise<EsdtTokenPayment[]> { + const [rewardTokenID, rewardsPerBlock, boostedYieldsRewardsPercentage] = + await Promise.all([ + this.farmAbi.farmedTokenID(scAddress), + this.farmAbi.rewardsPerBlock(scAddress), + this.farmAbi.boostedYieldsRewardsPercenatage(scAddress), + ]); + + const blocksInWeek = 14440 * 7; + const totalRewardsPerWeek = new BigNumber(rewardsPerBlock).multipliedBy( + blocksInWeek, + ); - return payments; + return [ + new EsdtTokenPayment({ + tokenID: rewardTokenID, + nonce: 0, + amount: totalRewardsPerWeek + .multipliedBy(boostedYieldsRewardsPercentage) + .dividedBy(constantsConfig.MAX_PERCENT) + .integerValue() + .toFixed(), + }), + ]; } @ErrorLoggerAsync({ From 363c3a86ea9bef822bcb0fbc6089175777891e0f Mon Sep 17 00:00:00 2001 From: Claudiu Lataretu <claudiu.lataretu@gmail.com> Date: Mon, 26 Aug 2024 18:30:12 +0300 Subject: [PATCH 4/7] MEX-514: added resolver for farm boosted rewards model Signed-off-by: Claudiu Lataretu <claudiu.lataretu@gmail.com> --- src/modules/farm/v2/farm.v2.module.ts | 3 ++- src/modules/farm/v2/farm.v2.resolver.ts | 25 +++++++++++++++++++ .../farm/v2/services/farm.v2.service.ts | 1 + 3 files changed, 28 insertions(+), 1 deletion(-) diff --git a/src/modules/farm/v2/farm.v2.module.ts b/src/modules/farm/v2/farm.v2.module.ts index 48e182872..60db256c5 100644 --- a/src/modules/farm/v2/farm.v2.module.ts +++ b/src/modules/farm/v2/farm.v2.module.ts @@ -3,7 +3,7 @@ import { TokenModule } from 'src/modules/tokens/token.module'; import { ContextModule } from 'src/services/context/context.module'; import { MXCommunicationModule } from 'src/services/multiversx-communication/mx.communication.module'; import { FarmAbiServiceV2 } from './services/farm.v2.abi.service'; -import { FarmResolverV2 } from './farm.v2.resolver'; +import { FarmBoostedRewardsResolver, FarmResolverV2 } from './farm.v2.resolver'; import { FarmServiceV2 } from './services/farm.v2.service'; import { FarmComputeServiceV2 } from './services/farm.v2.compute.service'; import { PairModule } from 'src/modules/pair/pair.module'; @@ -36,6 +36,7 @@ import { FarmComputeLoaderV2 } from './services/farm.v2.compute.loader'; FarmTransactionServiceV2, FarmResolverV2, FarmTransactionResolverV2, + FarmBoostedRewardsResolver, ], exports: [ FarmServiceV2, diff --git a/src/modules/farm/v2/farm.v2.resolver.ts b/src/modules/farm/v2/farm.v2.resolver.ts index 78dce345c..0b4d926e2 100644 --- a/src/modules/farm/v2/farm.v2.resolver.ts +++ b/src/modules/farm/v2/farm.v2.resolver.ts @@ -26,6 +26,31 @@ import { WINSTON_MODULE_PROVIDER } from 'nest-winston'; import { FarmAbiLoaderV2 } from './services/farm.v2.abi.loader'; import { FarmComputeLoaderV2 } from './services/farm.v2.compute.loader'; +@Resolver(() => BoostedRewardsModel) +export class FarmBoostedRewardsResolver { + constructor(private readonly farmCompute: FarmComputeServiceV2) {} + + @ResolveField() + async curentBoostedAPR( + @Parent() parent: BoostedRewardsModel, + ): Promise<number> { + return this.farmCompute.computeUserCurentBoostedAPR( + parent.farmAddress, + parent.userAddress, + ); + } + + @ResolveField() + async maximumBoostedAPR( + @Parent() parent: BoostedRewardsModel, + ): Promise<number> { + return this.farmCompute.computeUserMaxBoostedAPR( + parent.farmAddress, + parent.userAddress, + ); + } +} + @Resolver(() => FarmModelV2) export class FarmResolverV2 extends FarmResolver { constructor( diff --git a/src/modules/farm/v2/services/farm.v2.service.ts b/src/modules/farm/v2/services/farm.v2.service.ts index 8dda3aa8c..bd4183548 100644 --- a/src/modules/farm/v2/services/farm.v2.service.ts +++ b/src/modules/farm/v2/services/farm.v2.service.ts @@ -207,6 +207,7 @@ export class FarmServiceV2 extends FarmServiceBase { return new BoostedRewardsModel({ farmAddress, + userAddress, boostedRewardsWeeklyInfo: modelsList, claimProgress: currentClaimProgress, accumulatedRewards: userAccumulatedRewards, From 31ce12f99c349942161ac65af844c06134725fea Mon Sep 17 00:00:00 2001 From: Claudiu Lataretu <claudiu.lataretu@gmail.com> Date: Tue, 10 Sep 2024 17:40:32 +0300 Subject: [PATCH 5/7] MEX-514: update compute farm boosted rewards with custom user information Signed-off-by: Claudiu Lataretu <claudiu.lataretu@gmail.com> --- src/modules/farm/v2/farm.v2.resolver.ts | 21 +++++ .../v2/services/farm.v2.compute.service.ts | 85 ++++++++++++------- 2 files changed, 76 insertions(+), 30 deletions(-) diff --git a/src/modules/farm/v2/farm.v2.resolver.ts b/src/modules/farm/v2/farm.v2.resolver.ts index 0b4d926e2..da230239b 100644 --- a/src/modules/farm/v2/farm.v2.resolver.ts +++ b/src/modules/farm/v2/farm.v2.resolver.ts @@ -33,20 +33,41 @@ export class FarmBoostedRewardsResolver { @ResolveField() async curentBoostedAPR( @Parent() parent: BoostedRewardsModel, + @Args('additionalUserFarmAmount', { + type: () => String, + nullable: true, + defaultValue: '0', + }) + additionalUserFarmAmount: string, + @Args('additionalUserEnergy', { + type: () => String, + nullable: true, + defaultValue: '0', + }) + additionalUserEnergy: string, ): Promise<number> { return this.farmCompute.computeUserCurentBoostedAPR( parent.farmAddress, parent.userAddress, + additionalUserFarmAmount, + additionalUserEnergy, ); } @ResolveField() async maximumBoostedAPR( @Parent() parent: BoostedRewardsModel, + @Args('additionalUserFarmAmount', { + type: () => String, + nullable: true, + defaultValue: '0', + }) + additionalUserFarmAmount: string, ): Promise<number> { return this.farmCompute.computeUserMaxBoostedAPR( parent.farmAddress, parent.userAddress, + additionalUserFarmAmount, ); } } diff --git a/src/modules/farm/v2/services/farm.v2.compute.service.ts b/src/modules/farm/v2/services/farm.v2.compute.service.ts index f4db18eaa..a6cd0f786 100644 --- a/src/modules/farm/v2/services/farm.v2.compute.service.ts +++ b/src/modules/farm/v2/services/farm.v2.compute.service.ts @@ -180,26 +180,12 @@ export class FarmComputeServiceV2 scAddress: string, userAddress: string, week: number, + additionalUserFarmAmount = '0', + additionalUserEnergyAmount = '0', rewardsPerWeek?: EsdtTokenPayment[], ): Promise<EsdtTokenPayment[]> { const userRewardsForWeek = []; - const [currentWeek, userEnergyForWeek, totalEnergyForWeek, liquidity] = - await Promise.all([ - this.weekTimeKeepingAbi.currentWeek(scAddress), - this.weeklyRewardsSplittingAbi.userEnergyForWeek( - scAddress, - userAddress, - week, - ), - this.weeklyRewardsSplittingAbi.totalEnergyForWeek( - scAddress, - week, - ), - - this.farmAbi.userTotalFarmPosition(scAddress, userAddress), - ]); - const rewardsForWeek = rewardsPerWeek ?? (await this.weeklyRewardsSplittingAbi.totalRewardsForWeek( @@ -215,9 +201,35 @@ export class FarmComputeServiceV2 throw new Error('Invalid boosted yields rewards'); } - const boostedYieldsFactors = await this.farmAbi.boostedYieldsFactors( - scAddress, - ); + const [currentWeek, boostedYieldsFactors, userEnergyForWeek] = + await Promise.all([ + this.weekTimeKeepingAbi.currentWeek(scAddress), + this.farmAbi.boostedYieldsFactors(scAddress), + this.weeklyRewardsSplittingAbi.userEnergyForWeek( + scAddress, + userAddress, + week, + ), + ]); + + let [totalEnergyForWeek, liquidity] = await Promise.all([ + this.weeklyRewardsSplittingAbi.totalEnergyForWeek(scAddress, week), + this.farmAbi.userTotalFarmPosition(scAddress, userAddress), + ]); + let farmTokenSupply = + week === currentWeek + ? await this.farmAbi.farmTokenSupply(scAddress) + : await this.farmAbi.farmSupplyForWeek(scAddress, week); + + totalEnergyForWeek = new BigNumber(totalEnergyForWeek) + .plus(additionalUserEnergyAmount) + .toFixed(); + liquidity = new BigNumber(liquidity) + .plus(additionalUserFarmAmount) + .toFixed(); + farmTokenSupply = new BigNumber(farmTokenSupply) + .plus(additionalUserFarmAmount) + .toFixed(); const userHasMinEnergy = new BigNumber( userEnergyForWeek.amount, @@ -233,11 +245,6 @@ export class FarmComputeServiceV2 return userRewardsForWeek; } - const farmTokenSupply = - week === currentWeek - ? await this.farmAbi.farmTokenSupply(scAddress) - : await this.farmAbi.farmSupplyForWeek(scAddress, week); - const rewardForWeek = rewardsForWeek[0]; const weeklyRewardsAmount = new BigNumber(rewardForWeek.amount); @@ -293,11 +300,12 @@ export class FarmComputeServiceV2 async computeUserCurentBoostedAPR( scAddress: string, userAddress: string, + additionalUserFarmAmount = '0', + additionalUserEnergy = '0', ): Promise<number> { const [ currentWeek, boostedRewardsPerWeek, - userTotalFarmPosition, farmToken, farmedToken, farmingTokenPriceUSD, @@ -305,17 +313,26 @@ export class FarmComputeServiceV2 ] = await Promise.all([ this.weekTimeKeepingAbi.currentWeek(scAddress), this.computeBoostedRewardsPerWeek(scAddress), - this.farmAbi.userTotalFarmPosition(scAddress, userAddress), this.farmService.getFarmToken(scAddress), this.farmService.getFarmedToken(scAddress), this.farmingTokenPriceUSD(scAddress), this.farmedTokenPriceUSD(scAddress), ]); + let userTotalFarmPosition = await this.farmAbi.userTotalFarmPosition( + scAddress, + userAddress, + ); + userTotalFarmPosition = new BigNumber(userTotalFarmPosition) + .plus(additionalUserFarmAmount) + .toFixed(); + const userRewardsPerWeek = await this.computeUserRewardsForWeek( scAddress, userAddress, currentWeek, + additionalUserFarmAmount, + additionalUserEnergy, boostedRewardsPerWeek, ); @@ -339,12 +356,11 @@ export class FarmComputeServiceV2 async computeUserMaxBoostedAPR( scAddress: string, userAddress: string, + additionalUserFarmAmount = '0', ): Promise<number> { const [ boostedRewardsPerWeek, boostedYieldsFactors, - farmTokenSupply, - userTotalFarmPosition, farmToken, farmedToken, farmingTokenPriceUSD, @@ -352,14 +368,23 @@ export class FarmComputeServiceV2 ] = await Promise.all([ this.computeBoostedRewardsPerWeek(scAddress), this.farmAbi.boostedYieldsFactors(scAddress), - this.farmAbi.farmTokenSupply(scAddress), - this.farmAbi.userTotalFarmPosition(scAddress, userAddress), this.farmService.getFarmToken(scAddress), this.farmService.getFarmedToken(scAddress), this.farmingTokenPriceUSD(scAddress), this.farmedTokenPriceUSD(scAddress), ]); + let [farmTokenSupply, userTotalFarmPosition] = await Promise.all([ + this.farmAbi.farmTokenSupply(scAddress), + this.farmAbi.userTotalFarmPosition(scAddress, userAddress), + ]); + farmTokenSupply = new BigNumber(farmTokenSupply) + .plus(additionalUserFarmAmount) + .toFixed(); + userTotalFarmPosition = new BigNumber(userTotalFarmPosition) + .plus(additionalUserFarmAmount) + .toFixed(); + const userMaxRewardsPerWeek = new BigNumber( boostedRewardsPerWeek[0].amount, ) From 585cdd84ad291e65e0425ce82224ca4952a117a3 Mon Sep 17 00:00:00 2001 From: Claudiu Lataretu <claudiu.lataretu@gmail.com> Date: Tue, 10 Sep 2024 17:41:32 +0300 Subject: [PATCH 6/7] MEX-514: update compute staking boosted rewards with custom user information Signed-off-by: Claudiu Lataretu <claudiu.lataretu@gmail.com> --- .../services/staking.compute.service.ts | 118 ++++++++++++------ src/modules/staking/staking.resolver.ts | 21 ++++ 2 files changed, 99 insertions(+), 40 deletions(-) diff --git a/src/modules/staking/services/staking.compute.service.ts b/src/modules/staking/services/staking.compute.service.ts index b0895cca9..c23b33118 100644 --- a/src/modules/staking/services/staking.compute.service.ts +++ b/src/modules/staking/services/staking.compute.service.ts @@ -489,26 +489,12 @@ export class StakingComputeService { scAddress: string, userAddress: string, week: number, + additionalUserStakeAmount = '0', + additionalUserEnergy = '0', rewardsPerWeek?: EsdtTokenPayment[], ): Promise<EsdtTokenPayment[]> { const userRewardsForWeek = []; - const [currentWeek, userEnergyForWeek, totalEnergyForWeek, liquidity] = - await Promise.all([ - this.weekTimeKeepingAbi.currentWeek(scAddress), - this.weeklyRewardsSplittingAbi.userEnergyForWeek( - scAddress, - userAddress, - week, - ), - this.weeklyRewardsSplittingAbi.totalEnergyForWeek( - scAddress, - week, - ), - - this.stakingAbi.userTotalStakePosition(scAddress, userAddress), - ]); - const rewardsForWeek = rewardsPerWeek ?? (await this.weeklyRewardsSplittingAbi.totalRewardsForWeek( @@ -524,9 +510,39 @@ export class StakingComputeService { throw new Error('Invalid boosted yields rewards'); } - const boostedYieldsFactors = await this.stakingAbi.boostedYieldsFactors( - scAddress, - ); + const [currentWeek, boostedYieldsFactors, userEnergyForWeek] = + await Promise.all([ + this.weekTimeKeepingAbi.currentWeek(scAddress), + this.stakingAbi.boostedYieldsFactors(scAddress), + this.weeklyRewardsSplittingAbi.userEnergyForWeek( + scAddress, + userAddress, + week, + ), + ]); + + let [totalEnergyForWeek, liquidity] = await Promise.all([ + this.weeklyRewardsSplittingAbi.totalEnergyForWeek(scAddress, week), + + this.stakingAbi.userTotalStakePosition(scAddress, userAddress), + ]); + let farmTokenSupply = + week === currentWeek + ? await this.stakingAbi.farmTokenSupply(scAddress) + : await this.stakingAbi.farmSupplyForWeek(scAddress, week); + + userEnergyForWeek.amount = new BigNumber(userEnergyForWeek.amount) + .plus(additionalUserEnergy) + .toFixed(); + totalEnergyForWeek = new BigNumber(totalEnergyForWeek) + .plus(additionalUserEnergy) + .toFixed(); + liquidity = new BigNumber(liquidity) + .plus(additionalUserStakeAmount) + .toFixed(); + farmTokenSupply = new BigNumber(farmTokenSupply) + .plus(additionalUserStakeAmount) + .toFixed(); const userHasMinEnergy = new BigNumber( userEnergyForWeek.amount, @@ -542,11 +558,6 @@ export class StakingComputeService { return userRewardsForWeek; } - const farmTokenSupply = - week === currentWeek - ? await this.stakingAbi.farmTokenSupply(scAddress) - : await this.stakingAbi.farmSupplyForWeek(scAddress, week); - const rewardForWeek = rewardsForWeek[0]; const weeklyRewardsAmount = new BigNumber(rewardForWeek.amount); @@ -602,18 +613,31 @@ export class StakingComputeService { async computeUserCurentBoostedAPR( scAddress: string, userAddress: string, + additionalUserStakeAmount = '0', + additionalUserEnergy = '0', ): Promise<number> { - const [currentWeek, boostedRewardsPerWeek, userTotalStakePosition] = - await Promise.all([ - this.weekTimeKeepingAbi.currentWeek(scAddress), - this.computeBoostedRewardsPerWeek(scAddress), - this.stakingAbi.userTotalStakePosition(scAddress, userAddress), - ]); + const [currentWeek, boostedRewardsPerWeek] = await Promise.all([ + this.weekTimeKeepingAbi.currentWeek(scAddress), + this.computeBoostedRewardsPerWeek( + scAddress, + additionalUserStakeAmount, + ), + ]); + let userTotalStakePosition = + await this.stakingAbi.userTotalStakePosition( + scAddress, + userAddress, + ); + userTotalStakePosition = new BigNumber(userTotalStakePosition) + .plus(additionalUserStakeAmount) + .toFixed(); const userRewardsPerWeek = await this.computeUserRewardsForWeek( scAddress, userAddress, currentWeek, + additionalUserStakeAmount, + additionalUserEnergy, boostedRewardsPerWeek, ); @@ -626,18 +650,28 @@ export class StakingComputeService { async computeUserMaxBoostedAPR( scAddress: string, userAddress: string, + additionalUserStakeAmount = '0', ): Promise<number> { - const [ - boostedRewardsPerWeek, - boostedYieldsFactors, - farmTokenSupply, - userTotalStakePosition, - ] = await Promise.all([ - this.computeBoostedRewardsPerWeek(scAddress), - this.stakingAbi.boostedYieldsFactors(scAddress), + const [boostedRewardsPerWeek, boostedYieldsFactors] = await Promise.all( + [ + this.computeBoostedRewardsPerWeek( + scAddress, + additionalUserStakeAmount, + ), + this.stakingAbi.boostedYieldsFactors(scAddress), + ], + ); + + let [farmTokenSupply, userTotalStakePosition] = await Promise.all([ this.stakingAbi.farmTokenSupply(scAddress), this.stakingAbi.userTotalStakePosition(scAddress, userAddress), ]); + farmTokenSupply = new BigNumber(farmTokenSupply) + .plus(additionalUserStakeAmount) + .toFixed(); + userTotalStakePosition = new BigNumber(userTotalStakePosition) + .plus(additionalUserStakeAmount) + .toFixed(); const userMaxRewardsPerWeek = new BigNumber( boostedRewardsPerWeek[0].amount, @@ -654,21 +688,25 @@ export class StakingComputeService { async computeBoostedRewardsPerWeek( scAddress: string, + additionalUserStakeAmount = '0', ): Promise<EsdtTokenPayment[]> { const [ rewardTokenID, rewardsPerBlock, annualPercentageRewards, - farmTokenSupply, boostedYieldsRewardsPercentage, ] = await Promise.all([ this.stakingAbi.rewardTokenID(scAddress), this.stakingAbi.perBlockRewardsAmount(scAddress), this.stakingAbi.annualPercentageRewards(scAddress), - this.stakingAbi.farmTokenSupply(scAddress), this.stakingAbi.boostedYieldsRewardsPercenatage(scAddress), ]); + let farmTokenSupply = await this.stakingAbi.farmTokenSupply(scAddress); + farmTokenSupply = new BigNumber(farmTokenSupply) + .plus(additionalUserStakeAmount) + .toFixed(); + const rewardsPerBlockAPRBound = new BigNumber(farmTokenSupply) .multipliedBy(annualPercentageRewards) .dividedBy(constantsConfig.MAX_PERCENT) diff --git a/src/modules/staking/staking.resolver.ts b/src/modules/staking/staking.resolver.ts index 64099386f..e4907bee6 100644 --- a/src/modules/staking/staking.resolver.ts +++ b/src/modules/staking/staking.resolver.ts @@ -49,20 +49,41 @@ export class StakingBoostedRewardsResolver { @ResolveField() async curentBoostedAPR( @Parent() parent: StakingBoostedRewardsModel, + @Args('additionalUserFarmAmount', { + type: () => String, + nullable: true, + defaultValue: '0', + }) + additionalUserFarmAmount: string, + @Args('additionalUserEnergy', { + type: () => String, + nullable: true, + defaultValue: '0', + }) + additionalUserEnergy: string, ): Promise<number> { return this.stakingCompute.computeUserCurentBoostedAPR( parent.farmAddress, parent.userAddress, + additionalUserFarmAmount, + additionalUserEnergy, ); } @ResolveField() async maximumBoostedAPR( @Parent() parent: StakingBoostedRewardsModel, + @Args('additionalUserFarmAmount', { + type: () => String, + nullable: true, + defaultValue: '0', + }) + additionalUserFarmAmount: string, ): Promise<number> { return this.stakingCompute.computeUserMaxBoostedAPR( parent.farmAddress, parent.userAddress, + additionalUserFarmAmount, ); } } From 6d99a57ebb888eea7b558ff79182a022d9da7701 Mon Sep 17 00:00:00 2001 From: Claudiu Lataretu <claudiu.lataretu@gmail.com> Date: Tue, 10 Sep 2024 18:40:56 +0300 Subject: [PATCH 7/7] MEX-514: fix farm v2 compute service unit tests Signed-off-by: Claudiu Lataretu <claudiu.lataretu@gmail.com> --- src/modules/farm/mocks/farm.v2.abi.service.mock.ts | 7 +++++++ .../farm/specs/farm.v2.compute.service.spec.ts | 14 ++++++++++++-- src/modules/farm/v2/services/interfaces.ts | 2 +- 3 files changed, 20 insertions(+), 3 deletions(-) diff --git a/src/modules/farm/mocks/farm.v2.abi.service.mock.ts b/src/modules/farm/mocks/farm.v2.abi.service.mock.ts index 559085e87..5796882bb 100644 --- a/src/modules/farm/mocks/farm.v2.abi.service.mock.ts +++ b/src/modules/farm/mocks/farm.v2.abi.service.mock.ts @@ -73,6 +73,13 @@ export class FarmAbiServiceMockV2 async farmPositionMigrationNonce(farmAddress: string): Promise<number> { return 10; } + + async farmSupplyForWeek( + farmAddress: string, + week: number, + ): Promise<string> { + return '2'; + } } export const FarmAbiServiceProviderV2 = { diff --git a/src/modules/farm/specs/farm.v2.compute.service.spec.ts b/src/modules/farm/specs/farm.v2.compute.service.spec.ts index 9b3d45d2d..b0fbef5be 100644 --- a/src/modules/farm/specs/farm.v2.compute.service.spec.ts +++ b/src/modules/farm/specs/farm.v2.compute.service.spec.ts @@ -29,6 +29,7 @@ import { FarmAbiServiceV2 } from '../v2/services/farm.v2.abi.service'; import { Address } from '@multiversx/sdk-core/out'; import { ContextGetterService } from 'src/services/context/context.getter.service'; import { ElasticSearchModule } from 'src/services/elastic-search/elastic.search.module'; +import { EsdtTokenPayment } from 'src/models/esdtTokenPayment.model'; describe('FarmServiceV2', () => { let module: TestingModule; @@ -131,13 +132,22 @@ describe('FarmServiceV2', () => { totalLockedTokens: '1', lastUpdateEpoch: 256, }); + jest.spyOn( + weeklyRewardsSplittingAbi, + 'totalRewardsForWeek', + ).mockResolvedValue([ + new EsdtTokenPayment({ + amount: '60480000000000000000000', + tokenID: 'MEX-123456', + }), + ]); jest.spyOn(farmAbi, 'farmTokenSupply').mockResolvedValue('2'); jest.spyOn(farmAbi, 'rewardsPerBlock').mockResolvedValue( '1000000000000000000', ); jest.spyOn(farmAbi, 'userTotalFarmPosition').mockResolvedValue('2'); - const accumulatedRewards = await service.computeUserAccumulatedRewards( + const accumulatedRewards = await service.computeUserRewardsForWeek( Address.fromBech32( 'erd1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqpqsdtp6mh', ).bech32(), @@ -145,6 +155,6 @@ describe('FarmServiceV2', () => { 1, ); - expect(accumulatedRewards).toEqual('60480000000000000000000'); + expect(accumulatedRewards[0].amount).toEqual('60480000000000000000000'); }); }); diff --git a/src/modules/farm/v2/services/interfaces.ts b/src/modules/farm/v2/services/interfaces.ts index 69a9f7fe7..0436e3da4 100644 --- a/src/modules/farm/v2/services/interfaces.ts +++ b/src/modules/farm/v2/services/interfaces.ts @@ -1,4 +1,3 @@ -import { TokenDistributionModel } from 'src/submodules/weekly-rewards-splitting/models/weekly-rewards-splitting.model'; import { IFarmAbiService, IFarmComputeService, @@ -25,6 +24,7 @@ export interface IFarmAbiServiceV2 extends IFarmAbiService { userAddress: string, ): Promise<string>; farmPositionMigrationNonce(farmAddress: string): Promise<number>; + farmSupplyForWeek(farmAddress: string, week: number): Promise<string>; } export interface IFarmComputeServiceV2 extends IFarmComputeService {