From 4537c218436a108a6f4fd5e7477652397bfce861 Mon Sep 17 00:00:00 2001 From: Claudiu Lataretu Date: Wed, 4 Oct 2023 12:07:32 +0300 Subject: [PATCH 01/11] MEX-376: add method to get farm position migration nonce Signed-off-by: Claudiu Lataretu --- .../farm/mocks/farm.v2.abi.service.mock.ts | 4 ++++ .../farm/v2/services/farm.v2.abi.service.ts | 22 +++++++++++++++++++ src/modules/farm/v2/services/interfaces.ts | 1 + 3 files changed, 27 insertions(+) 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 d0e73969c..848590e74 100644 --- a/src/modules/farm/mocks/farm.v2.abi.service.mock.ts +++ b/src/modules/farm/mocks/farm.v2.abi.service.mock.ts @@ -55,6 +55,10 @@ export class FarmAbiServiceMockV2 ): Promise { throw new Error('Method not implemented.'); } + + async farmPositionMigrationNonce(farmAddress: string): Promise { + return 10; + } } export const FarmAbiServiceProviderV2 = { diff --git a/src/modules/farm/v2/services/farm.v2.abi.service.ts b/src/modules/farm/v2/services/farm.v2.abi.service.ts index 2a8af0016..255bffefe 100644 --- a/src/modules/farm/v2/services/farm.v2.abi.service.ts +++ b/src/modules/farm/v2/services/farm.v2.abi.service.ts @@ -416,4 +416,26 @@ export class FarmAbiServiceV2 return response.firstValue.valueOf().toFixed(); } + + @ErrorLoggerAsync({ + logArgs: true, + }) + @GetOrSetCache({ + baseKey: 'farm', + remoteTtl: CacheTtlInfo.ContractInfo.remoteTtl, + localTtl: CacheTtlInfo.ContractInfo.localTtl, + }) + async farmPositionMigrationNonce(farmAddress: string): Promise { + return this.getFarmPositionMigrationNonceRaw(farmAddress); + } + + async getFarmPositionMigrationNonceRaw( + farmAddress: string, + ): Promise { + const contract = await this.mxProxy.getFarmSmartContract(farmAddress); + const interaction: Interaction = + contract.methodsExplicit.getFarmPositionMigrationNonce(); + const response = await this.getGenericData(interaction); + return response.firstValue.valueOf().toNumber(); + } } diff --git a/src/modules/farm/v2/services/interfaces.ts b/src/modules/farm/v2/services/interfaces.ts index 8c9af6cff..bc49dabc2 100644 --- a/src/modules/farm/v2/services/interfaces.ts +++ b/src/modules/farm/v2/services/interfaces.ts @@ -25,6 +25,7 @@ export interface IFarmAbiServiceV2 extends IFarmAbiService { farmAddress: string, userAddress: string, ): Promise; + farmPositionMigrationNonce(farmAddress: string): Promise; } export interface IFarmComputeServiceV2 extends IFarmComputeService { From 5833375fcfc80fd31087e90cf08f5b94b6a708cb Mon Sep 17 00:00:00 2001 From: Claudiu Lataretu Date: Wed, 4 Oct 2023 12:09:34 +0300 Subject: [PATCH 02/11] MEX-376: add method to generate migration transactions to initialize user total farm position Signed-off-by: Claudiu Lataretu --- src/modules/farm/v2/farm.v2.module.ts | 2 + .../farm/v2/farm.v2.transaction.resolver.ts | 24 +++++++++++ .../services/farm.v2.transaction.service.ts | 42 +++++++++++++++++++ 3 files changed, 68 insertions(+) create mode 100644 src/modules/farm/v2/farm.v2.transaction.resolver.ts diff --git a/src/modules/farm/v2/farm.v2.module.ts b/src/modules/farm/v2/farm.v2.module.ts index a52e86abd..783feaa30 100644 --- a/src/modules/farm/v2/farm.v2.module.ts +++ b/src/modules/farm/v2/farm.v2.module.ts @@ -12,6 +12,7 @@ import { FarmSetterServiceV2 } from './services/farm.v2.setter.service'; import { WeekTimekeepingModule } from '../../../submodules/week-timekeeping/week-timekeeping.module'; import { WeeklyRewardsSplittingModule } from '../../../submodules/weekly-rewards-splitting/weekly-rewards-splitting.module'; import { EnergyModule } from '../../energy/energy.module'; +import { FarmTransactionResolverV2 } from './farm.v2.transaction.resolver'; @Module({ imports: [ @@ -30,6 +31,7 @@ import { EnergyModule } from '../../energy/energy.module'; FarmComputeServiceV2, FarmTransactionServiceV2, FarmResolverV2, + FarmTransactionResolverV2, ], exports: [ FarmServiceV2, diff --git a/src/modules/farm/v2/farm.v2.transaction.resolver.ts b/src/modules/farm/v2/farm.v2.transaction.resolver.ts new file mode 100644 index 000000000..9cb5d1fb5 --- /dev/null +++ b/src/modules/farm/v2/farm.v2.transaction.resolver.ts @@ -0,0 +1,24 @@ +import { Args, Query, Resolver } from '@nestjs/graphql'; +import { FarmTransactionServiceV2 } from './services/farm.v2.transaction.service'; +import { UseGuards } from '@nestjs/common'; +import { JwtOrNativeAuthGuard } from 'src/modules/auth/jwt.or.native.auth.guard'; +import { TransactionModel } from 'src/models/transaction.model'; +import { AuthUser } from 'src/modules/auth/auth.user'; +import { UserAuthResult } from 'src/modules/auth/user.auth.result'; + +@Resolver() +export class FarmTransactionResolverV2 { + constructor(private readonly farmTransaction: FarmTransactionServiceV2) {} + + @UseGuards(JwtOrNativeAuthGuard) + @Query(() => [TransactionModel]) + async migrateTotalFarmPositions( + @Args('farmAddress') farmAddress: string, + @AuthUser() user: UserAuthResult, + ): Promise { + return this.farmTransaction.migrateTotalFarmPosition( + farmAddress, + user.address, + ); + } +} diff --git a/src/modules/farm/v2/services/farm.v2.transaction.service.ts b/src/modules/farm/v2/services/farm.v2.transaction.service.ts index 527699290..7a879affd 100644 --- a/src/modules/farm/v2/services/farm.v2.transaction.service.ts +++ b/src/modules/farm/v2/services/farm.v2.transaction.service.ts @@ -17,6 +17,7 @@ import { MXProxyService } from 'src/services/multiversx-communication/mx.proxy.s import { FarmAbiServiceV2 } from './farm.v2.abi.service'; import { PairService } from 'src/modules/pair/services/pair.service'; import { PairAbiService } from 'src/modules/pair/services/pair.abi.service'; +import { MXApiService } from 'src/services/multiversx-communication/mx.api.service'; @Injectable() export class FarmTransactionServiceV2 extends TransactionsFarmService { @@ -25,6 +26,7 @@ export class FarmTransactionServiceV2 extends TransactionsFarmService { protected readonly farmAbi: FarmAbiServiceV2, protected readonly pairService: PairService, protected readonly pairAbi: PairAbiService, + private readonly mxApi: MXApiService, ) { super(mxProxy, farmAbi, pairService, pairAbi); } @@ -136,4 +138,44 @@ export class FarmTransactionServiceV2 extends TransactionsFarmService { ): Promise { throw new Error('Method not implemented.'); } + + async migrateTotalFarmPosition( + farmAddress: string, + userAddress: string, + ): Promise { + const [farmTokenID, migrationNonce, userNftsCount] = await Promise.all([ + this.farmAbi.farmTokenID(farmAddress), + this.farmAbi.farmPositionMigrationNonce(farmAddress), + this.mxApi.getNftsCountForUser(userAddress), + ]); + + if (userNftsCount === 0) { + return []; + } + + const userNfts = await this.mxApi.getNftsForUser( + userAddress, + 0, + userNftsCount, + 'MetaESDT', + [farmTokenID], + ); + + const promises: Promise[] = []; + userNfts.forEach((nft) => { + if (nft.nonce < migrationNonce) { + promises.push( + this.claimRewards(userAddress, { + farmAddress, + farmTokenID: nft.collection, + farmTokenNonce: nft.nonce, + amount: nft.balance, + lockRewards: true, + }), + ); + } + }); + + return Promise.all(promises); + } } From 8efd440be2a31f0bc247ef9622254452ac0c20bb Mon Sep 17 00:00:00 2001 From: Claudiu Lataretu Date: Wed, 4 Oct 2023 12:10:56 +0300 Subject: [PATCH 03/11] MEX-376: proxy dex generate user total farm position migration transactions Signed-off-by: Claudiu Lataretu --- .../proxy/proxy.transaction.resolver.ts | 12 ++++ .../services/proxy-farm/proxy.farm.module.ts | 2 + .../proxy.farm.transactions.service.ts | 66 +++++++++++++++++++ 3 files changed, 80 insertions(+) diff --git a/src/modules/proxy/proxy.transaction.resolver.ts b/src/modules/proxy/proxy.transaction.resolver.ts index 7bca6af60..f64a67662 100644 --- a/src/modules/proxy/proxy.transaction.resolver.ts +++ b/src/modules/proxy/proxy.transaction.resolver.ts @@ -195,4 +195,16 @@ export class ProxyTransactionResolver { args, ); } + + @UseGuards(JwtOrNativeAuthGuard) + @Query(() => [TransactionModel]) + async migrateTotalFarmPositionsProxy( + @Args('proxyAddress') proxyAddress: string, + @AuthUser() user: UserAuthResult, + ): Promise { + return this.transactionsProxyFarmService.migrateTotalFarmPosition( + user.address, + proxyAddress, + ); + } } diff --git a/src/modules/proxy/services/proxy-farm/proxy.farm.module.ts b/src/modules/proxy/services/proxy-farm/proxy.farm.module.ts index 68363eddb..48c7fea04 100644 --- a/src/modules/proxy/services/proxy-farm/proxy.farm.module.ts +++ b/src/modules/proxy/services/proxy-farm/proxy.farm.module.ts @@ -8,6 +8,7 @@ import { ProxyModule } from '../../proxy.module'; import { PairModule } from 'src/modules/pair/pair.module'; import { TokenModule } from 'src/modules/tokens/token.module'; import { FarmModule } from 'src/modules/farm/farm.module'; +import { FarmModuleV2 } from 'src/modules/farm/v2/farm.v2.module'; @Module({ imports: [ @@ -16,6 +17,7 @@ import { FarmModule } from 'src/modules/farm/farm.module'; ProxyPairModule, forwardRef(() => ProxyModule), FarmModule, + FarmModuleV2, PairModule, TokenModule, ], diff --git a/src/modules/proxy/services/proxy-farm/proxy.farm.transactions.service.ts b/src/modules/proxy/services/proxy-farm/proxy.farm.transactions.service.ts index 4165f8f4f..a69e78324 100644 --- a/src/modules/proxy/services/proxy-farm/proxy.farm.transactions.service.ts +++ b/src/modules/proxy/services/proxy-farm/proxy.farm.transactions.service.ts @@ -26,14 +26,24 @@ import { PairService } from 'src/modules/pair/services/pair.service'; import { proxyVersion } from 'src/utils/proxy.utils'; import { PairAbiService } from 'src/modules/pair/services/pair.abi.service'; import { FarmAbiFactory } from 'src/modules/farm/farm.abi.factory'; +import { ProxyFarmAbiService } from './proxy.farm.abi.service'; +import { MXApiService } from 'src/services/multiversx-communication/mx.api.service'; +import { + WrappedFarmTokenAttributes, + WrappedFarmTokenAttributesV2, +} from '@multiversx/sdk-exchange'; +import { FarmAbiServiceV2 } from 'src/modules/farm/v2/services/farm.v2.abi.service'; @Injectable() export class ProxyFarmTransactionsService { constructor( private readonly mxProxy: MXProxyService, + private readonly mxApi: MXApiService, private readonly farmAbi: FarmAbiFactory, + private readonly farmAbiV2: FarmAbiServiceV2, private readonly pairService: PairService, private readonly pairAbi: PairAbiService, + private readonly proxyFarmAbi: ProxyFarmAbiService, ) {} async enterFarmProxy( @@ -264,6 +274,62 @@ export class ProxyFarmTransactionsService { .toPlainObject(); } + async migrateTotalFarmPosition( + sender: string, + proxyAddress: string, + ): Promise { + const wrappedFarmTokenID = await this.proxyFarmAbi.wrappedFarmTokenID( + proxyAddress, + ); + const userNftsCount = await this.mxApi.getNftsCountForUser(sender); + const userNfts = await this.mxApi.getNftsForUser( + sender, + 0, + userNftsCount, + 'MetaESDT', + [wrappedFarmTokenID], + ); + const promises: Promise[] = []; + for (const nft of userNfts) { + const version = proxyVersion(proxyAddress); + + let farmTokenID: string; + let farmTokenNonce: number; + if (version === 'v2') { + const decodedAttributes = + WrappedFarmTokenAttributesV2.fromAttributes(nft.attributes); + farmTokenID = decodedAttributes.farmToken.tokenIdentifier; + farmTokenNonce = decodedAttributes.farmToken.tokenNonce; + } else { + const decodedAttributes = + WrappedFarmTokenAttributes.fromAttributes(nft.attributes); + farmTokenID = decodedAttributes.farmTokenID; + farmTokenNonce = decodedAttributes.farmTokenNonce; + } + + const farmAddress = await this.farmAbi.getFarmAddressByFarmTokenID( + farmTokenID, + ); + + const migrationNonce = + await this.farmAbiV2.farmPositionMigrationNonce(farmAddress); + + if (farmTokenNonce < migrationNonce) { + promises.push( + this.claimFarmRewardsProxy(sender, proxyAddress, { + farmAddress, + wrappedFarmTokenID: nft.collection, + wrappedFarmTokenNonce: nft.nonce, + amount: nft.balance, + lockRewards: true, + }), + ); + } + } + + return Promise.all(promises); + } + private async getExitFarmProxyGasLimit( args: ExitFarmProxyArgs, ): Promise { From 8eed3adbeb626649fa0c28aede8cdd11b9acfc5d Mon Sep 17 00:00:00 2001 From: Claudiu Lataretu Date: Wed, 4 Oct 2023 12:12:25 +0300 Subject: [PATCH 04/11] MEX-376: unit tests for farm v2 transactions service - add unit tests for generate migration transactions for user total farm position Signed-off-by: Claudiu Lataretu --- src/config/test.json | 8 +- src/modules/farm/mocks/farm.constants.ts | 12 ++ .../farm.v2.transactions.service.spec.ts | 127 ++++++++++++++++++ .../mx.api.service.mock.ts | 4 + 4 files changed, 150 insertions(+), 1 deletion(-) create mode 100644 src/modules/farm/specs/farm.v2.transactions.service.spec.ts diff --git a/src/config/test.json b/src/config/test.json index 066923fe8..e02ba68d1 100644 --- a/src/config/test.json +++ b/src/config/test.json @@ -4,7 +4,8 @@ "MEX-123456": "erd1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqfqktvqkr", "WEGLD_USDC": "erd1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqfsr2ycrs", "proxyDexAddress": { - "v1": "erd1qqqqqqqqqqqqqpgqrc4pg2xarca9z34njcxeur622qmfjp8w2jps89fxnl" + "v1": "erd1qqqqqqqqqqqqqpgqrc4pg2xarca9z34njcxeur622qmfjp8w2jps89fxnl", + "v2": "erd1qqqqqqqqqqqqqpgqt6ltx52ukss9d2qag2k67at28a36xc9lkp2sr06394" } }, "farms": { @@ -17,6 +18,11 @@ "customRewards": [ "erd1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqes9lzxht" ] + }, + "v2": { + "lockedRewards": [ + "erd1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqpqsdtp6mh" + ] } }, "tokenProviderUSD": "WEGLD-123456", diff --git a/src/modules/farm/mocks/farm.constants.ts b/src/modules/farm/mocks/farm.constants.ts index 0c00a43b9..e54a42958 100644 --- a/src/modules/farm/mocks/farm.constants.ts +++ b/src/modules/farm/mocks/farm.constants.ts @@ -49,4 +49,16 @@ export const farms = [ rewardsPerBlock: '2000000000000000000', rewardPerShare: '0', }, + { + address: Address.fromHex( + '0000000000000000000000000000000000000000000000000000000000000041', + ).bech32(), + farmedTokenID: 'MEX-123456', + farmTokenID: 'EGLDMEXFL-ghijkl', + farmingTokenID: 'EGLDMEXLP-abcdef', + farmTotalSupply: '1000000000000000000', + farmingTokenReserve: '1000000000000000000', + rewardsPerBlock: '2000000000000000000', + rewardPerShare: '0', + }, ]; diff --git a/src/modules/farm/specs/farm.v2.transactions.service.spec.ts b/src/modules/farm/specs/farm.v2.transactions.service.spec.ts new file mode 100644 index 000000000..bc6266891 --- /dev/null +++ b/src/modules/farm/specs/farm.v2.transactions.service.spec.ts @@ -0,0 +1,127 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { FarmTransactionServiceV2 } from '../v2/services/farm.v2.transaction.service'; +import { ConfigModule } from '@nestjs/config'; +import { WinstonModule } from 'nest-winston'; +import winston from 'winston'; +import { DynamicModuleUtils } from 'src/utils/dynamic.module.utils'; +import { MXProxyServiceProvider } from 'src/services/multiversx-communication/mx.proxy.service.mock'; +import { FarmAbiServiceProviderV2 } from '../mocks/farm.v2.abi.service.mock'; +import { PairService } from 'src/modules/pair/services/pair.service'; +import { PairAbiServiceProvider } from 'src/modules/pair/mocks/pair.abi.service.mock'; +import { PairComputeServiceProvider } from 'src/modules/pair/mocks/pair.compute.service.mock'; +import { RouterAbiServiceProvider } from 'src/modules/router/mocks/router.abi.service.mock'; +import { MXApiServiceProvider } from 'src/services/multiversx-communication/mx.api.service.mock'; +import { WrapAbiServiceProvider } from 'src/modules/wrapping/mocks/wrap.abi.service.mock'; +import { ApiConfigService } from 'src/helpers/api.config.service'; +import { TokenServiceProvider } from 'src/modules/tokens/mocks/token.service.mock'; +import { ContextGetterServiceProvider } from 'src/services/context/mocks/context.getter.service.mock'; +import { MXApiService } from 'src/services/multiversx-communication/mx.api.service'; +import { Address } from '@multiversx/sdk-core/out'; +import { encodeTransactionData } from 'src/helpers/helpers'; + +describe('FarmTransactionsServiceV2', () => { + let module: TestingModule; + + beforeAll(async () => { + module = await Test.createTestingModule({ + imports: [ + ConfigModule.forRoot({}), + WinstonModule.forRoot({ + transports: [new winston.transports.Console({})], + }), + DynamicModuleUtils.getCacheModule(), + ], + providers: [ + ApiConfigService, + MXProxyServiceProvider, + MXApiServiceProvider, + FarmAbiServiceProviderV2, + PairService, + PairAbiServiceProvider, + PairComputeServiceProvider, + RouterAbiServiceProvider, + WrapAbiServiceProvider, + TokenServiceProvider, + ContextGetterServiceProvider, + FarmTransactionServiceV2, + ], + }).compile(); + }); + + it('should be defined', () => { + const service = module.get( + FarmTransactionServiceV2, + ); + expect(service).toBeDefined(); + }); + + it('should NOT get migrate total farm position transaction', async () => { + const service = module.get( + FarmTransactionServiceV2, + ); + const mxApi = module.get(MXApiService); + jest.spyOn(mxApi, 'getNftsForUser').mockResolvedValue([ + { + identifier: 'EGLDMEXFL-ghijkl-0a', + collection: 'EGLDMEXFL-ghijkl', + attributes: + 'AAAACBEpiw/pcSUIAAAAAAAADNIAAAAAAAAABysPQGpo9rtgu2ARp4Hri1PWHXkEdFCtqkaXfiAjkxKVEmLBBX0QkA==', + nonce: 10, + type: 'MetaESDT', + name: 'EGLDMEXLPStakedLK', + creator: + 'erd1qqqqqqqqqqqqqpgqna8cqqzdtdg849hr0pd5cmft8ujaguhv295qgchtqa', + balance: '12120193336145595', + decimals: 18, + ticker: 'EGLDMEXFL-2d2bba', + }, + ]); + + const transactions = await service.migrateTotalFarmPosition( + Address.fromHex( + '0000000000000000000000000000000000000000000000000000000000000041', + ).bech32(), + Address.Zero().bech32(), + ); + + expect(transactions.length).toEqual(0); + }); + + it('should NOT get migrate total farm position transaction', async () => { + const service = module.get( + FarmTransactionServiceV2, + ); + const mxApi = module.get(MXApiService); + jest.spyOn(mxApi, 'getNftsForUser').mockResolvedValue([ + { + identifier: 'EGLDMEXFL-ghijkl-01', + collection: 'EGLDMEXFL-ghijkl', + attributes: + 'AAAACBEpiw/pcSUIAAAAAAAADNIAAAAAAAAABysPQGpo9rtgu2ARp4Hri1PWHXkEdFCtqkaXfiAjkxKVEmLBBX0QkA==', + nonce: 1, + type: 'MetaESDT', + name: 'EGLDMEXLPStakedLK', + creator: + 'erd1qqqqqqqqqqqqqpgqna8cqqzdtdg849hr0pd5cmft8ujaguhv295qgchtqa', + balance: '12120193336145595', + decimals: 18, + ticker: 'EGLDMEXFL-2d2bba', + }, + ]); + + const transactions = await service.migrateTotalFarmPosition( + Address.fromHex( + '0000000000000000000000000000000000000000000000000000000000000041', + ).bech32(), + Address.Zero().bech32(), + ); + console.log(transactions); + expect(transactions[0].data).toEqual( + encodeTransactionData( + `ESDTNFTTransfer@EGLDMEXFL-ghijkl@01@12120193336145595@${Address.fromHex( + '0000000000000000000000000000000000000000000000000000000000000041', + ).bech32()}@claimRewards`, + ), + ); + }); +}); diff --git a/src/services/multiversx-communication/mx.api.service.mock.ts b/src/services/multiversx-communication/mx.api.service.mock.ts index d7e87c87f..888a003bb 100644 --- a/src/services/multiversx-communication/mx.api.service.mock.ts +++ b/src/services/multiversx-communication/mx.api.service.mock.ts @@ -33,6 +33,10 @@ export class MXApiServiceMock { ]; } + async getNftsCountForUser(address: string): Promise { + return 1; + } + async getNftsForUser(address: string): Promise { return [ { From 2781ffdce99ac3b54caceb513a031cd0a05557bc Mon Sep 17 00:00:00 2001 From: Claudiu Lataretu Date: Wed, 4 Oct 2023 12:13:51 +0300 Subject: [PATCH 05/11] MEX-376: unit tests for proxy farm transactions service - add unit tests for generate migration transactions for user total farm positions Signed-off-by: Claudiu Lataretu --- .../proxy/mocks/proxy.abi.service.mock.ts | 6 +- .../proxy-pair-transactions.service.spec.ts | 8 +- .../proxy.farm.transactions.service.spec.ts | 134 ++++++++++++++++++ 3 files changed, 141 insertions(+), 7 deletions(-) create mode 100644 src/modules/proxy/specs/proxy.farm.transactions.service.spec.ts diff --git a/src/modules/proxy/mocks/proxy.abi.service.mock.ts b/src/modules/proxy/mocks/proxy.abi.service.mock.ts index c54984dd6..8ab85f045 100644 --- a/src/modules/proxy/mocks/proxy.abi.service.mock.ts +++ b/src/modules/proxy/mocks/proxy.abi.service.mock.ts @@ -9,13 +9,13 @@ import { ProxyFarmAbiService } from '../services/proxy-farm/proxy.farm.abi.servi export class ProxyAbiServiceMock implements IProxyAbiService { async lockedAssetTokenID(proxyAddress: string): Promise { - return ['LKMEX-1234']; + return ['LKMEX-123456']; } } export class ProxyPairAbiServiceMock implements IProxyPairAbiService { async wrappedLpTokenID(proxyAddress: string): Promise { - return 'LKLP-abcd'; + return 'LKLP-abcdef'; } intermediatedPairs(proxyAddress: string): Promise { throw new Error('Method not implemented.'); @@ -24,7 +24,7 @@ export class ProxyPairAbiServiceMock implements IProxyPairAbiService { export class ProxyFarmAbiServiceMock implements IProxyFarmAbiService { async wrappedFarmTokenID(proxyAddress: string): Promise { - return 'LKFARM-1234'; + return 'LKFARM-123456'; } intermediatedFarms(proxyAddress: string): Promise { throw new Error('Method not implemented.'); diff --git a/src/modules/proxy/specs/proxy-pair-transactions.service.spec.ts b/src/modules/proxy/specs/proxy-pair-transactions.service.spec.ts index a2423ab50..6286835a6 100644 --- a/src/modules/proxy/specs/proxy-pair-transactions.service.spec.ts +++ b/src/modules/proxy/specs/proxy-pair-transactions.service.spec.ts @@ -102,7 +102,7 @@ describe('TransactionProxyPairService', () => { amount: firstTokenAmount, }, { - tokenID: 'LKMEX-1234', + tokenID: 'LKMEX-123456', nonce: 1, amount: secondTokenAmount, }, @@ -116,7 +116,7 @@ describe('TransactionProxyPairService', () => { expect(wrapEgldTransaction.value).toEqual(firstTokenAmount); expect(addLiquidityProxy.data).toEqual( encodeTransactionData( - 'MultiESDTNFTTransfer@000000000000000005001e2a1428dd1e3a5146b3960d9e0f4a50369904ee5483@02@WEGLD-123456@@10@LKMEX-1234@01@09@addLiquidityProxy@0000000000000000000000000000000000000000000000000000000000000000@09@08', + 'MultiESDTNFTTransfer@000000000000000005001e2a1428dd1e3a5146b3960d9e0f4a50369904ee5483@02@WEGLD-123456@@10@LKMEX-123456@01@09@addLiquidityProxy@0000000000000000000000000000000000000000000000000000000000000000@09@08', ), ); }); @@ -148,7 +148,7 @@ describe('TransactionProxyPairService', () => { pairAddress: Address.Zero().bech32(), tokens: [ { - tokenID: 'LKMEX-1234', + tokenID: 'LKMEX-123456', nonce: 1, amount: firstTokenAmount, }, @@ -167,7 +167,7 @@ describe('TransactionProxyPairService', () => { expect(wrapEgldTransaction.value).toEqual(secondTokenAmount); expect(addLiquidityProxy.data).toEqual( encodeTransactionData( - 'MultiESDTNFTTransfer@000000000000000005001e2a1428dd1e3a5146b3960d9e0f4a50369904ee5483@02@WEGLD-123456@@09@LKMEX-1234@01@10@addLiquidityProxy@0000000000000000000000000000000000000000000000000000000000000000@08@09', + 'MultiESDTNFTTransfer@000000000000000005001e2a1428dd1e3a5146b3960d9e0f4a50369904ee5483@02@WEGLD-123456@@09@LKMEX-123456@01@10@addLiquidityProxy@0000000000000000000000000000000000000000000000000000000000000000@08@09', ), ); }); diff --git a/src/modules/proxy/specs/proxy.farm.transactions.service.spec.ts b/src/modules/proxy/specs/proxy.farm.transactions.service.spec.ts new file mode 100644 index 000000000..107e7f575 --- /dev/null +++ b/src/modules/proxy/specs/proxy.farm.transactions.service.spec.ts @@ -0,0 +1,134 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { ProxyFarmTransactionsService } from '../services/proxy-farm/proxy.farm.transactions.service'; +import { WinstonModule } from 'nest-winston'; +import winston from 'winston'; +import { ConfigModule } from '@nestjs/config'; +import { DynamicModuleUtils } from 'src/utils/dynamic.module.utils'; +import { MXProxyServiceProvider } from 'src/services/multiversx-communication/mx.proxy.service.mock'; +import { MXApiServiceProvider } from 'src/services/multiversx-communication/mx.api.service.mock'; +import { FarmAbiServiceProviderV1_2 } from 'src/modules/farm/mocks/farm.v1.2.abi.service.mock'; +import { FarmAbiServiceProviderV1_3 } from 'src/modules/farm/mocks/farm.v1.3.abi.service.mock'; +import { FarmAbiServiceProviderV2 } from 'src/modules/farm/mocks/farm.v2.abi.service.mock'; +import { PairService } from 'src/modules/pair/services/pair.service'; +import { PairAbiServiceProvider } from 'src/modules/pair/mocks/pair.abi.service.mock'; +import { ProxyFarmAbiServiceProvider } from '../mocks/proxy.abi.service.mock'; +import { PairComputeServiceProvider } from 'src/modules/pair/mocks/pair.compute.service.mock'; +import { RouterAbiServiceProvider } from 'src/modules/router/mocks/router.abi.service.mock'; +import { WrapAbiServiceProvider } from 'src/modules/wrapping/mocks/wrap.abi.service.mock'; +import { TokenServiceProvider } from 'src/modules/tokens/mocks/token.service.mock'; +import { ContextGetterServiceProvider } from 'src/services/context/mocks/context.getter.service.mock'; +import { FarmAbiFactory } from 'src/modules/farm/farm.abi.factory'; +import { ApiConfigService } from 'src/helpers/api.config.service'; +import { Address } from '@multiversx/sdk-core/out'; +import { MXApiService } from 'src/services/multiversx-communication/mx.api.service'; +import { encodeTransactionData } from 'src/helpers/helpers'; + +describe('ProxyFarmTransactionsService', () => { + let module: TestingModule; + + beforeAll(async () => { + module = await Test.createTestingModule({ + imports: [ + WinstonModule.forRoot({ + transports: [new winston.transports.Console({})], + }), + ConfigModule.forRoot({}), + DynamicModuleUtils.getCacheModule(), + ], + providers: [ + ProxyFarmTransactionsService, + MXProxyServiceProvider, + MXApiServiceProvider, + FarmAbiFactory, + FarmAbiServiceProviderV1_2, + FarmAbiServiceProviderV1_3, + FarmAbiServiceProviderV2, + PairService, + PairAbiServiceProvider, + PairComputeServiceProvider, + RouterAbiServiceProvider, + WrapAbiServiceProvider, + TokenServiceProvider, + ContextGetterServiceProvider, + ApiConfigService, + ProxyFarmAbiServiceProvider, + ], + }).compile(); + }); + + it('should be defined', () => { + const service: ProxyFarmTransactionsService = + module.get( + ProxyFarmTransactionsService, + ); + expect(service).toBeDefined(); + }); + + it('should NOT get migrate to total farm position transaction', async () => { + const service: ProxyFarmTransactionsService = + module.get( + ProxyFarmTransactionsService, + ); + const mxApi = module.get(MXApiService); + jest.spyOn(mxApi, 'getNftsCountForUser').mockResolvedValue(1); + jest.spyOn(mxApi, 'getNftsForUser').mockResolvedValue([ + { + identifier: 'LKFARM-123456-0a', + collection: 'LKFARM-123456', + attributes: + 'AAAAEEVHTERNRVhGTC1naGlqa2wAAAAAAAAACgAAAAcrKmP2fki2AAAAC0xLTFAtYWJjZGVmAAAAAAAAAAEAAAAHKypj9n5Itg==', + nonce: 10, + type: 'MetaESDT', + name: 'xMEXLPStaked', + creator: + 'erd1qqqqqqqqqqqqqpgqat0auzsgk9x0g9gm8n6tq6qa9xtmwu4h295qaalzvq', + balance: '12150032824158390', + decimals: 18, + ticker: 'LKFARM-123456', + }, + ]); + + const transactions = await service.migrateTotalFarmPosition( + Address.Zero().bech32(), + 'erd1qqqqqqqqqqqqqpgqt6ltx52ukss9d2qag2k67at28a36xc9lkp2sr06394', + ); + + expect(transactions.length).toEqual(0); + }); + + it('should get migrate to total farm position transaction', async () => { + const service: ProxyFarmTransactionsService = + module.get( + ProxyFarmTransactionsService, + ); + const mxApi = module.get(MXApiService); + jest.spyOn(mxApi, 'getNftsCountForUser').mockResolvedValue(1); + jest.spyOn(mxApi, 'getNftsForUser').mockResolvedValue([ + { + identifier: 'LKFARM-123456-05', + collection: 'LKFARM-123456', + attributes: + 'AAAAEEVHTERNRVhGTC1naGlqa2wAAAAAAAAAAQAAAAcrKmP2fki2AAAAC0xLTFAtYWJjZGVmAAAAAAAAAAEAAAAHKypj9n5Itg==', + nonce: 5, + type: 'MetaESDT', + name: 'xMEXLPStaked', + creator: + 'erd1qqqqqqqqqqqqqpgqat0auzsgk9x0g9gm8n6tq6qa9xtmwu4h295qaalzvq', + balance: '12150032824158390', + decimals: 18, + ticker: 'LKFARM-123456', + }, + ]); + + const transactions = await service.migrateTotalFarmPosition( + Address.Zero().bech32(), + 'erd1qqqqqqqqqqqqqpgqt6ltx52ukss9d2qag2k67at28a36xc9lkp2sr06394', + ); + + expect(transactions[0].data).toEqual( + encodeTransactionData( + 'ESDTNFTTransfer@LKFARM-123456@05@12150032824158390@erd1qqqqqqqqqqqqqpgqt6ltx52ukss9d2qag2k67at28a36xc9lkp2sr06394@claimRewardsProxy@erd1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqpqsdtp6mh', + ), + ); + }); +}); From 2afe239fb3774a9b41f9b794a8ab0b799d78c719 Mon Sep 17 00:00:00 2001 From: Claudiu Lataretu Date: Wed, 4 Oct 2023 14:14:42 +0300 Subject: [PATCH 06/11] MEX-376: add description for migration query Signed-off-by: Claudiu Lataretu --- .../farm/v2/farm.v2.transaction.resolver.ts | 16 +++++++++++++++- src/modules/proxy/proxy.transaction.resolver.ts | 5 ++++- 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/src/modules/farm/v2/farm.v2.transaction.resolver.ts b/src/modules/farm/v2/farm.v2.transaction.resolver.ts index 9cb5d1fb5..84f58a55f 100644 --- a/src/modules/farm/v2/farm.v2.transaction.resolver.ts +++ b/src/modules/farm/v2/farm.v2.transaction.resolver.ts @@ -5,17 +5,31 @@ import { JwtOrNativeAuthGuard } from 'src/modules/auth/jwt.or.native.auth.guard' import { TransactionModel } from 'src/models/transaction.model'; import { AuthUser } from 'src/modules/auth/auth.user'; import { UserAuthResult } from 'src/modules/auth/user.auth.result'; +import { farmVersion } from 'src/utils/farm.utils'; +import { FarmVersion } from '../models/farm.model'; +import { GraphQLError } from 'graphql'; +import { ApolloServerErrorCode } from '@apollo/server/errors'; @Resolver() export class FarmTransactionResolverV2 { constructor(private readonly farmTransaction: FarmTransactionServiceV2) {} @UseGuards(JwtOrNativeAuthGuard) - @Query(() => [TransactionModel]) + @Query(() => [TransactionModel], { + description: + 'Generate transactions to initialize the total farm positions for a user', + }) async migrateTotalFarmPositions( @Args('farmAddress') farmAddress: string, @AuthUser() user: UserAuthResult, ): Promise { + if (farmVersion(farmAddress) !== FarmVersion.V2) { + throw new GraphQLError('Farm version is not supported', { + extensions: { + code: ApolloServerErrorCode.BAD_USER_INPUT, + }, + }); + } return this.farmTransaction.migrateTotalFarmPosition( farmAddress, user.address, diff --git a/src/modules/proxy/proxy.transaction.resolver.ts b/src/modules/proxy/proxy.transaction.resolver.ts index f64a67662..6f0fe81af 100644 --- a/src/modules/proxy/proxy.transaction.resolver.ts +++ b/src/modules/proxy/proxy.transaction.resolver.ts @@ -197,7 +197,10 @@ export class ProxyTransactionResolver { } @UseGuards(JwtOrNativeAuthGuard) - @Query(() => [TransactionModel]) + @Query(() => [TransactionModel], { + description: + 'Generate transactions to initialize the total farm positions for a user', + }) async migrateTotalFarmPositionsProxy( @Args('proxyAddress') proxyAddress: string, @AuthUser() user: UserAuthResult, From 729cf5a371e1fac191c68df0d7ddb753b214606a Mon Sep 17 00:00:00 2001 From: Claudiu Lataretu Date: Wed, 4 Oct 2023 17:16:57 +0300 Subject: [PATCH 07/11] MEX-376: fix user total farm position query Signed-off-by: Claudiu Lataretu --- src/modules/farm/v2/services/farm.v2.abi.service.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/modules/farm/v2/services/farm.v2.abi.service.ts b/src/modules/farm/v2/services/farm.v2.abi.service.ts index 255bffefe..3fd1d9424 100644 --- a/src/modules/farm/v2/services/farm.v2.abi.service.ts +++ b/src/modules/farm/v2/services/farm.v2.abi.service.ts @@ -410,11 +410,14 @@ export class FarmAbiServiceV2 ]); const response = await this.getGenericData(interaction); - if (response.returnCode.equals(ReturnCode.FunctionNotFound)) { + if ( + response.returnCode.equals(ReturnCode.FunctionNotFound) || + response.returnCode.equals(ReturnCode.UserError) + ) { return '0'; } - return response.firstValue.valueOf().toFixed(); + return response.firstValue.valueOf().total_farm_position.toFixed(); } @ErrorLoggerAsync({ From 6cde88d93ffc575fcd8b8d0594c93be524a94fd2 Mon Sep 17 00:00:00 2001 From: Claudiu Lataretu Date: Wed, 4 Oct 2023 17:32:54 +0300 Subject: [PATCH 08/11] MEX-376: update analytics unit test Signed-off-by: Claudiu Lataretu --- src/modules/analytics/specs/analytics.service.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/modules/analytics/specs/analytics.service.spec.ts b/src/modules/analytics/specs/analytics.service.spec.ts index 94274ded5..6490887e2 100644 --- a/src/modules/analytics/specs/analytics.service.spec.ts +++ b/src/modules/analytics/specs/analytics.service.spec.ts @@ -122,6 +122,6 @@ describe('AnalyticsService', () => { const totalLockedValueUSDFarms = await service.computeLockedValueUSDFarms(); - expect(totalLockedValueUSDFarms.toString()).toEqual('90'); + expect(totalLockedValueUSDFarms.toString()).toEqual('110'); }); }); From c17da00569d63d0fe8e1ccd7a90a54e3f309e859 Mon Sep 17 00:00:00 2001 From: Claudiu Lataretu Date: Wed, 4 Oct 2023 17:59:35 +0300 Subject: [PATCH 09/11] MEX-376: fix farm service unit test Signed-off-by: Claudiu Lataretu --- src/modules/farm/specs/farm.service.spec.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/modules/farm/specs/farm.service.spec.ts b/src/modules/farm/specs/farm.service.spec.ts index 86fedd9ec..3cb9cd053 100644 --- a/src/modules/farm/specs/farm.service.spec.ts +++ b/src/modules/farm/specs/farm.service.spec.ts @@ -159,6 +159,12 @@ describe('FarmService', () => { rewardType: 'customRewards', version: 'v1.3', }, + { + address: + 'erd1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqpqsdtp6mh', + rewardType: 'lockedRewards', + version: 'v2', + }, ]); }); From 0667e7c12c7a733741536736c2bc469442926065 Mon Sep 17 00:00:00 2001 From: Claudiu Lataretu Date: Mon, 9 Oct 2023 15:53:27 +0300 Subject: [PATCH 10/11] MEX-376: remove logging message Signed-off-by: Claudiu Lataretu --- src/modules/farm/specs/farm.v2.transactions.service.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/modules/farm/specs/farm.v2.transactions.service.spec.ts b/src/modules/farm/specs/farm.v2.transactions.service.spec.ts index bc6266891..a0429ffc7 100644 --- a/src/modules/farm/specs/farm.v2.transactions.service.spec.ts +++ b/src/modules/farm/specs/farm.v2.transactions.service.spec.ts @@ -115,7 +115,7 @@ describe('FarmTransactionsServiceV2', () => { ).bech32(), Address.Zero().bech32(), ); - console.log(transactions); + expect(transactions[0].data).toEqual( encodeTransactionData( `ESDTNFTTransfer@EGLDMEXFL-ghijkl@01@12120193336145595@${Address.fromHex( From 71022167e42689201dc9c1a9ecb2dd3ea0d5db5e Mon Sep 17 00:00:00 2001 From: Claudiu Lataretu Date: Wed, 11 Oct 2023 15:18:34 +0300 Subject: [PATCH 11/11] MEX-376: add validator for proxy address Signed-off-by: Claudiu Lataretu --- .../proxy/proxy.transaction.resolver.ts | 3 ++- .../validators/proxy.address.validator.ts | 25 +++++++++++++++++++ 2 files changed, 27 insertions(+), 1 deletion(-) create mode 100644 src/modules/proxy/validators/proxy.address.validator.ts diff --git a/src/modules/proxy/proxy.transaction.resolver.ts b/src/modules/proxy/proxy.transaction.resolver.ts index 6f0fe81af..3e0d5fc73 100644 --- a/src/modules/proxy/proxy.transaction.resolver.ts +++ b/src/modules/proxy/proxy.transaction.resolver.ts @@ -24,6 +24,7 @@ import { InputTokenModel } from 'src/models/inputToken.model'; import { LiquidityTokensValidationPipe } from './validators/add.liquidity.input.validator'; import { ProxyService } from './services/proxy.service'; import { scAddress } from 'src/config'; +import { ProxyAddressValidationPipe } from './validators/proxy.address.validator'; @Resolver() export class ProxyTransactionResolver { @@ -202,7 +203,7 @@ export class ProxyTransactionResolver { 'Generate transactions to initialize the total farm positions for a user', }) async migrateTotalFarmPositionsProxy( - @Args('proxyAddress') proxyAddress: string, + @Args('proxyAddress', ProxyAddressValidationPipe) proxyAddress: string, @AuthUser() user: UserAuthResult, ): Promise { return this.transactionsProxyFarmService.migrateTotalFarmPosition( diff --git a/src/modules/proxy/validators/proxy.address.validator.ts b/src/modules/proxy/validators/proxy.address.validator.ts new file mode 100644 index 000000000..8392864fe --- /dev/null +++ b/src/modules/proxy/validators/proxy.address.validator.ts @@ -0,0 +1,25 @@ +import { ArgumentMetadata, Injectable, PipeTransform } from '@nestjs/common'; +import { UserInputError } from '@nestjs/apollo'; +import { Address } from '@multiversx/sdk-core/out'; +import { scAddress } from 'src/config'; + +@Injectable() +export class ProxyAddressValidationPipe implements PipeTransform { + async transform(value: string, metadata: ArgumentMetadata) { + let address: Address; + try { + address = Address.fromBech32(value); + } catch (error) { + throw new UserInputError('Invalid address format'); + } + + const proxAddresses: string[] = Object.values( + scAddress.proxyDexAddress, + ); + if (!proxAddresses.includes(address.bech32())) { + throw new UserInputError('Invalid proxy address'); + } + + return value; + } +}