Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

[MEX-376] migrate positions transactions #1208

8 changes: 7 additions & 1 deletion src/config/test.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@
"MEX-123456": "erd1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqfqktvqkr",
"WEGLD_USDC": "erd1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqfsr2ycrs",
"proxyDexAddress": {
"v1": "erd1qqqqqqqqqqqqqpgqrc4pg2xarca9z34njcxeur622qmfjp8w2jps89fxnl"
"v1": "erd1qqqqqqqqqqqqqpgqrc4pg2xarca9z34njcxeur622qmfjp8w2jps89fxnl",
"v2": "erd1qqqqqqqqqqqqqpgqt6ltx52ukss9d2qag2k67at28a36xc9lkp2sr06394"
}
},
"farms": {
Expand All @@ -17,6 +18,11 @@
"customRewards": [
"erd1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqes9lzxht"
]
},
"v2": {
"lockedRewards": [
"erd1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqpqsdtp6mh"
]
}
},
"tokenProviderUSD": "WEGLD-123456",
Expand Down
2 changes: 1 addition & 1 deletion src/modules/analytics/specs/analytics.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,6 @@ describe('AnalyticsService', () => {

const totalLockedValueUSDFarms =
await service.computeLockedValueUSDFarms();
expect(totalLockedValueUSDFarms.toString()).toEqual('90');
expect(totalLockedValueUSDFarms.toString()).toEqual('110');
});
});
12 changes: 12 additions & 0 deletions src/modules/farm/mocks/farm.constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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',
},
];
4 changes: 4 additions & 0 deletions src/modules/farm/mocks/farm.v2.abi.service.mock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,10 @@ export class FarmAbiServiceMockV2
): Promise<string> {
throw new Error('Method not implemented.');
}

async farmPositionMigrationNonce(farmAddress: string): Promise<number> {
return 10;
}
}

export const FarmAbiServiceProviderV2 = {
Expand Down
6 changes: 6 additions & 0 deletions src/modules/farm/specs/farm.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,12 @@ describe('FarmService', () => {
rewardType: 'customRewards',
version: 'v1.3',
},
{
address:
'erd1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqpqsdtp6mh',
rewardType: 'lockedRewards',
version: 'v2',
},
]);
});

Expand Down
127 changes: 127 additions & 0 deletions src/modules/farm/specs/farm.v2.transactions.service.spec.ts
Original file line number Diff line number Diff line change
@@ -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>(
FarmTransactionServiceV2,
);
expect(service).toBeDefined();
});

it('should NOT get migrate total farm position transaction', async () => {
const service = module.get<FarmTransactionServiceV2>(
FarmTransactionServiceV2,
);
const mxApi = module.get<MXApiService>(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>(
FarmTransactionServiceV2,
);
const mxApi = module.get<MXApiService>(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);
claudiulataretu marked this conversation as resolved.
Show resolved Hide resolved
expect(transactions[0].data).toEqual(
encodeTransactionData(
`ESDTNFTTransfer@EGLDMEXFL-ghijkl@01@12120193336145595@${Address.fromHex(
'0000000000000000000000000000000000000000000000000000000000000041',
).bech32()}@claimRewards`,
),
);
});
});
2 changes: 2 additions & 0 deletions src/modules/farm/v2/farm.v2.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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: [
Expand All @@ -30,6 +31,7 @@ import { EnergyModule } from '../../energy/energy.module';
FarmComputeServiceV2,
FarmTransactionServiceV2,
FarmResolverV2,
FarmTransactionResolverV2,
],
exports: [
FarmServiceV2,
Expand Down
38 changes: 38 additions & 0 deletions src/modules/farm/v2/farm.v2.transaction.resolver.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
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';
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], {
description:
'Generate transactions to initialize the total farm positions for a user',
})
async migrateTotalFarmPositions(
@Args('farmAddress') farmAddress: string,
@AuthUser() user: UserAuthResult,
): Promise<TransactionModel[]> {
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,
);
}
}
29 changes: 27 additions & 2 deletions src/modules/farm/v2/services/farm.v2.abi.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -410,10 +410,35 @@
]);
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();

Check warning on line 420 in src/modules/farm/v2/services/farm.v2.abi.service.ts

View check run for this annotation

Codecov / codecov/patch

src/modules/farm/v2/services/farm.v2.abi.service.ts#L420

Added line #L420 was not covered by tests
}

@ErrorLoggerAsync({
logArgs: true,
})
@GetOrSetCache({
baseKey: 'farm',
remoteTtl: CacheTtlInfo.ContractInfo.remoteTtl,
localTtl: CacheTtlInfo.ContractInfo.localTtl,
})
async farmPositionMigrationNonce(farmAddress: string): Promise<number> {
return this.getFarmPositionMigrationNonceRaw(farmAddress);

Check warning on line 432 in src/modules/farm/v2/services/farm.v2.abi.service.ts

View check run for this annotation

Codecov / codecov/patch

src/modules/farm/v2/services/farm.v2.abi.service.ts#L432

Added line #L432 was not covered by tests
}

async getFarmPositionMigrationNonceRaw(
farmAddress: string,
): Promise<number> {
const contract = await this.mxProxy.getFarmSmartContract(farmAddress);

Check warning on line 438 in src/modules/farm/v2/services/farm.v2.abi.service.ts

View check run for this annotation

Codecov / codecov/patch

src/modules/farm/v2/services/farm.v2.abi.service.ts#L438

Added line #L438 was not covered by tests
const interaction: Interaction =
contract.methodsExplicit.getFarmPositionMigrationNonce();
const response = await this.getGenericData(interaction);
return response.firstValue.valueOf().toNumber();

Check warning on line 442 in src/modules/farm/v2/services/farm.v2.abi.service.ts

View check run for this annotation

Codecov / codecov/patch

src/modules/farm/v2/services/farm.v2.abi.service.ts#L440-L442

Added lines #L440 - L442 were not covered by tests
}
}
42 changes: 42 additions & 0 deletions src/modules/farm/v2/services/farm.v2.transaction.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
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 {
Expand All @@ -25,6 +26,7 @@
protected readonly farmAbi: FarmAbiServiceV2,
protected readonly pairService: PairService,
protected readonly pairAbi: PairAbiService,
private readonly mxApi: MXApiService,
) {
super(mxProxy, farmAbi, pairService, pairAbi);
}
Expand Down Expand Up @@ -136,4 +138,44 @@
): Promise<TransactionModel> {
throw new Error('Method not implemented.');
}

async migrateTotalFarmPosition(
farmAddress: string,
userAddress: string,
): Promise<TransactionModel[]> {
const [farmTokenID, migrationNonce, userNftsCount] = await Promise.all([
this.farmAbi.farmTokenID(farmAddress),
this.farmAbi.farmPositionMigrationNonce(farmAddress),
this.mxApi.getNftsCountForUser(userAddress),
]);

if (userNftsCount === 0) {
return [];

Check warning on line 153 in src/modules/farm/v2/services/farm.v2.transaction.service.ts

View check run for this annotation

Codecov / codecov/patch

src/modules/farm/v2/services/farm.v2.transaction.service.ts#L153

Added line #L153 was not covered by tests
}

const userNfts = await this.mxApi.getNftsForUser(
userAddress,
0,
userNftsCount,
'MetaESDT',
[farmTokenID],
);

const promises: Promise<TransactionModel>[] = [];
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);
}
}
1 change: 1 addition & 0 deletions src/modules/farm/v2/services/interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ export interface IFarmAbiServiceV2 extends IFarmAbiService {
farmAddress: string,
userAddress: string,
): Promise<string>;
farmPositionMigrationNonce(farmAddress: string): Promise<number>;
}

export interface IFarmComputeServiceV2 extends IFarmComputeService {
Expand Down
6 changes: 3 additions & 3 deletions src/modules/proxy/mocks/proxy.abi.service.mock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,13 @@

export class ProxyAbiServiceMock implements IProxyAbiService {
async lockedAssetTokenID(proxyAddress: string): Promise<string[]> {
return ['LKMEX-1234'];
return ['LKMEX-123456'];
}
}

export class ProxyPairAbiServiceMock implements IProxyPairAbiService {
async wrappedLpTokenID(proxyAddress: string): Promise<string> {
return 'LKLP-abcd';
return 'LKLP-abcdef';

Check warning on line 18 in src/modules/proxy/mocks/proxy.abi.service.mock.ts

View check run for this annotation

Codecov / codecov/patch

src/modules/proxy/mocks/proxy.abi.service.mock.ts#L18

Added line #L18 was not covered by tests
}
intermediatedPairs(proxyAddress: string): Promise<string[]> {
throw new Error('Method not implemented.');
Expand All @@ -24,7 +24,7 @@

export class ProxyFarmAbiServiceMock implements IProxyFarmAbiService {
async wrappedFarmTokenID(proxyAddress: string): Promise<string> {
return 'LKFARM-1234';
return 'LKFARM-123456';
}
intermediatedFarms(proxyAddress: string): Promise<string[]> {
throw new Error('Method not implemented.');
Expand Down
Loading
Loading