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 {