Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions src/InvestProvider.Backend/Services/DefaultServiceProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
using poolz.finance.csharp.contracts.InvestProvider;
using InvestProvider.Backend.Services.Web3.Contracts;
using System.Reflection;
using MediatR;
using InvestProvider.Backend.Services.Handlers.ContextBuilders;
using MediatR.Extensions.FluentValidation.AspNetCore;

namespace InvestProvider.Backend.Services;
Expand All @@ -25,6 +27,8 @@ public static class DefaultServiceProvider
#endif
.AddMediatR(x => x.RegisterServicesFromAssembly(Assembly.GetExecutingAssembly()))
.AddFluentValidation([Assembly.GetExecutingAssembly()])
.AddTransient(typeof(IPipelineBehavior<,>), typeof(ContextBuilderBehavior<,>))
.AddTransient(typeof(IRequestContextBuilder<>), typeof(PhaseContextBuilder<>))
.AddSingleton<IRpcProvider, ChainProvider>()
.AddSingleton<IChainProvider<ContractType>, ChainProvider>()
.AddSingleton<IStrapiClient, StrapiClient>()
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
using FluentValidation;
using Net.Utils.ErrorHandler.Extensions;
using InvestProvider.Backend.Services.Strapi;
using Amazon.DynamoDBv2.DataModel;
using InvestProvider.Backend.Services.Web3.Contracts;
using poolz.finance.csharp.contracts.LockDealNFT;
using InvestProvider.Backend.Services.Handlers.AdminCreatePoolzBackId.Models;
Expand All @@ -14,18 +12,16 @@ public class AdminCreatePoolzBackIdValidator : BasePhaseValidator<AdminCreatePoo
private readonly ILockDealNFTService<ContractType> _lockDealNFT;

public AdminCreatePoolzBackIdValidator(
IStrapiClient strapi,
IDynamoDBContext dynamoDb,
ILockDealNFTService<ContractType> lockDealNFT
) : base(strapi, dynamoDb)
)
{
_lockDealNFT = lockDealNFT;

ClassLevelCascadeMode = CascadeMode.Stop;

RuleFor(x => x)
.Cascade(CascadeMode.Stop)
.Must(NotNullCurrentPhase)
.Must(HasCurrentPhase)
.WithError(Error.NOT_FOUND_ACTIVE_PHASE, x => (new { x.ProjectId }))
.MustAsync(CorrectProviders)
.WithError(Error.INVALID_POOL_TYPE);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,12 @@
using FluentValidation;
using Amazon.DynamoDBv2.DataModel;
using InvestProvider.Backend.Services.Strapi;
using InvestProvider.Backend.Services.Handlers.AdminWriteAllocation.Models;

namespace InvestProvider.Backend.Services.Handlers.AdminWriteAllocation;

public class AdminWriteAllocationValidator : BasePhaseValidator<AdminWriteAllocationRequest>
{

public AdminWriteAllocationValidator(IStrapiClient strapi, IDynamoDBContext dynamoDb)
: base(strapi, dynamoDb)
public AdminWriteAllocationValidator()
{
ClassLevelCascadeMode = CascadeMode.Stop;

Expand Down
40 changes: 9 additions & 31 deletions src/InvestProvider.Backend/Services/Handlers/BasePhaseValidator.cs
Original file line number Diff line number Diff line change
@@ -1,32 +1,18 @@
using FluentValidation;
using Net.Utils.ErrorHandler.Extensions;
using Amazon.DynamoDBv2.DataModel;
using InvestProvider.Backend.Services.Strapi;
using InvestProvider.Backend.Services.DynamoDb.Models;
using InvestProvider.Backend.Services.Validators.Models;

namespace InvestProvider.Backend.Services.Handlers;

public abstract class BasePhaseValidator<T>(IStrapiClient strapi, IDynamoDBContext dynamoDb) : AbstractValidator<T>
public abstract class BasePhaseValidator<T> : AbstractValidator<T>
where T : IExistActivePhase
{
protected readonly IStrapiClient _strapi = strapi;
protected readonly IDynamoDBContext _dynamoDb = dynamoDb;
protected static bool HasCurrentPhase(IExistActivePhase model) =>
model.StrapiProjectInfo.CurrentPhase != null;

protected bool NotNullCurrentPhase(IExistActivePhase model)
{
model.StrapiProjectInfo = _strapi.ReceiveProjectInfoAsync(model.ProjectId, filterPhases: model.FilterPhases)
.GetAwaiter()
.GetResult();
return model.StrapiProjectInfo.CurrentPhase != null;
}

protected async Task<bool> NotNullProjectsInformationAsync<TModel>(TModel model, CancellationToken token)
where TModel : IValidatedDynamoDbProjectInfo
{
model.DynamoDbProjectsInfo = await _dynamoDb.LoadAsync<ProjectsInformation>(model.ProjectId, token);
return model.DynamoDbProjectsInfo != null;
}
protected static bool HasProjectsInformation(IValidatedDynamoDbProjectInfo model) =>
model.DynamoDbProjectsInfo != null;

protected static bool SetPhase(IExistPhase model)
{
Expand All @@ -35,25 +21,17 @@ protected static bool SetPhase(IExistPhase model)
return phase != null;
}

protected async Task<bool> NotNullWhiteListAsync<TModel>(TModel model, CancellationToken token)
where TModel : IWhiteListUser
{
model.WhiteList = await _dynamoDb.LoadAsync<WhiteList>(
hashKey: WhiteList.CalculateHashId(model.ProjectId, model.StrapiProjectInfo.CurrentPhase!.Start!.Value),
rangeKey: model.UserAddress.Address,
token
);
return model.WhiteList != null;
}
protected static bool HasWhiteList(IWhiteListUser model) =>
model.WhiteList != null;

protected static IRuleBuilderOptions<TModel, TModel> WhiteListPhaseRules<TModel>(BasePhaseValidator<TModel> validator)
where TModel : IExistPhase, IValidatedDynamoDbProjectInfo
{
return validator.RuleFor(x => x)
.Cascade(CascadeMode.Stop)
.MustAsync((m, ct) => validator.NotNullProjectsInformationAsync(m, ct))
.Must(m => HasProjectsInformation(m))
.WithError(Error.POOLZ_BACK_ID_NOT_FOUND, x => new { x.ProjectId })
.Must(m => validator.NotNullCurrentPhase(m))
.Must(m => HasCurrentPhase(m))
.WithError(Error.NOT_FOUND_ACTIVE_PHASE, x => new { x.ProjectId })
.Must(m => SetPhase(m))
.WithError(Error.PHASE_IN_PROJECT_NOT_FOUND, x => new { x.ProjectId, x.PhaseId })
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
using MediatR;

namespace InvestProvider.Backend.Services.Handlers.ContextBuilders;

public class ContextBuilderBehavior<TRequest, TResponse>(IRequestContextBuilder<TRequest>? builder)
: IPipelineBehavior<TRequest, TResponse>
where TRequest : notnull
{
public async Task<TResponse> Handle(TRequest request, RequestHandlerDelegate<TResponse> next, CancellationToken cancellationToken)
{
if (builder is not null)
await builder.BuildAsync(request, cancellationToken);

return await next();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
using System.Threading;
using System.Threading.Tasks;

namespace InvestProvider.Backend.Services.Handlers.ContextBuilders;

public interface IRequestContextBuilder<in TRequest>
{
Task BuildAsync(TRequest request, CancellationToken cancellationToken);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
using Amazon.DynamoDBv2.DataModel;
using InvestProvider.Backend.Services.DynamoDb.Models;
using InvestProvider.Backend.Services.Strapi;
using InvestProvider.Backend.Services.Validators.Models;

namespace InvestProvider.Backend.Services.Handlers.ContextBuilders;

public class PhaseContextBuilder<T>(IStrapiClient strapi, IDynamoDBContext dynamoDb)
: IRequestContextBuilder<T>
where T : IExistActivePhase
{
public async Task BuildAsync(T request, CancellationToken cancellationToken)
{
request.StrapiProjectInfo = await strapi.ReceiveProjectInfoAsync(
request.ProjectId,
request.FilterPhases);

if (request is IValidatedDynamoDbProjectInfo validated)
{
validated.DynamoDbProjectsInfo = await dynamoDb.LoadAsync<ProjectsInformation>(
request.ProjectId,
cancellationToken);
}

if (request is IWhiteListUser whiteListUser && request.StrapiProjectInfo.CurrentPhase != null)
{
whiteListUser.WhiteList = await dynamoDb.LoadAsync<WhiteList>(
WhiteList.CalculateHashId(request.ProjectId, request.StrapiProjectInfo.CurrentPhase.Start!.Value),
whiteListUser.UserAddress.Address,
cancellationToken);
}
}
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
using FluentValidation;
using Net.Utils.ErrorHandler.Extensions;
using Amazon.DynamoDBv2.DataModel;
using Net.Cache.DynamoDb.ERC20;
using InvestProvider.Backend.Services.Strapi;
using InvestProvider.Backend.Services.Web3;
using InvestProvider.Backend.Services.Web3.Contracts;
using poolz.finance.csharp.contracts.LockDealNFT;
Expand All @@ -19,13 +17,11 @@ public partial class GenerateSignatureRequestValidator : BasePhaseValidator<Gene
private readonly IInvestProviderService<ContractType> _investProvider;

public GenerateSignatureRequestValidator(
IStrapiClient strapi,
IDynamoDBContext dynamoDb,
IRpcProvider rpcProvider,
ERC20CacheProvider erc20Cache,
ILockDealNFTService<ContractType> lockDealNFT,
IInvestProviderService<ContractType> investProvider
) : base(strapi, dynamoDb)
)
{
_rpcProvider = rpcProvider;
_erc20Cache = erc20Cache;
Expand All @@ -39,9 +35,9 @@ IInvestProviderService<ContractType> investProvider

RuleFor(x => x)
.Cascade(CascadeMode.Stop)
.Must(NotNullCurrentPhase)
.Must(HasCurrentPhase)
.WithError(Error.NOT_FOUND_ACTIVE_PHASE, x => new { x.ProjectId })
.MustAsync(NotNullProjectsInformationAsync)
.Must(HasProjectsInformation)
.WithError(Error.POOLZ_BACK_ID_NOT_FOUND, x => new { x.ProjectId })
.MustAsync(MustMoreThanAllowedMinimumAsync)
.WithError(Error.INVEST_AMOUNT_IS_LESS_THAN_ALLOWED, x => new
Expand All @@ -61,7 +57,7 @@ IInvestProviderService<ContractType> investProvider

RuleFor(x => x)
.Cascade(CascadeMode.Stop)
.MustAsync(NotNullWhiteListAsync)
.Must(HasWhiteList)
.WithError(Error.NOT_IN_WHITE_LIST, x => new { x.ProjectId, PhaseId = x.StrapiProjectInfo.CurrentPhase!.Id, UserAddress = x.UserAddress.Address })
.Must(x => x.Amount + x.InvestedAmount <= x.WhiteList.Amount)
.WithError(Error.AMOUNT_EXCEED_MAX_WHITE_LIST_AMOUNT, x => new
Expand Down
Original file line number Diff line number Diff line change
@@ -1,20 +1,17 @@
using FluentValidation;
using Amazon.DynamoDBv2.DataModel;
using Net.Utils.ErrorHandler.Extensions;
using InvestProvider.Backend.Services.Strapi;
using InvestProvider.Backend.Services.Handlers.MyAllocation.Models;

namespace InvestProvider.Backend.Services.Handlers.MyAllocation;

public class MyAllocationValidator : BasePhaseValidator<MyAllocationRequest>
{
public MyAllocationValidator(IStrapiClient strapi, IDynamoDBContext dynamoDb)
: base(strapi, dynamoDb)
public MyAllocationValidator()
{
ClassLevelCascadeMode = CascadeMode.Stop;

WhiteListPhaseRules(this)
.MustAsync(NotNullWhiteListAsync)
.Must(HasWhiteList)
.When(x => x.StrapiProjectInfo.CurrentPhase!.MaxInvest == 0, ApplyConditionTo.CurrentValidator)
.WithError(Error.NOT_IN_WHITE_LIST, x => new { x.ProjectId, PhaseId = x.StrapiProjectInfo.CurrentPhase!.Id, UserAddress = x.UserAddress.Address });
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,19 +1,21 @@
using System;
using System.Collections.Generic;
using System.Numerics;
using System.Threading;
using System.Threading.Tasks;
using Amazon.DynamoDBv2.DataModel;
using Nethereum.RPC.Eth.DTOs;
using Moq;
using Xunit;
using FluentValidation;
using InvestProvider.Backend.Services.Strapi;
using Amazon.DynamoDBv2.DataModel;
using InvestProvider.Backend.Services.Handlers.AdminCreatePoolzBackId;
using InvestProvider.Backend.Services.Handlers.AdminCreatePoolzBackId.Models;
using InvestProvider.Backend.Services.Web3.Contracts;
using poolz.finance.csharp.contracts.LockDealNFT;
using poolz.finance.csharp.contracts.LockDealNFT.ContractDefinition;
using InvestProvider.Backend.Tests;
using InvestProvider.Backend.Services.Handlers.ContextBuilders;

namespace InvestProvider.Backend.Tests.Handlers;

Expand All @@ -39,14 +41,16 @@ public async Task Validate_Succeeds_ForInvestProviderPool()
.ReturnsAsync(fullData);

var dynamoDb = new Mock<IDynamoDBContext>();
var validator = new AdminCreatePoolzBackIdValidator(strapi.Object, dynamoDb.Object, lockDealNFT.Object);
var builder = new PhaseContextBuilder<AdminCreatePoolzBackIdRequest>(strapi.Object, dynamoDb.Object);
var validator = new AdminCreatePoolzBackIdValidator(lockDealNFT.Object);
var request = new AdminCreatePoolzBackIdRequest
{
ProjectId = "pid",
PoolzBackId = 5,
ChainId = 1
};

await builder.BuildAsync(request, CancellationToken.None);
await validator.ValidateAndThrowAsync(request);
}

Expand All @@ -69,14 +73,16 @@ public async Task Validate_Throws_WhenPoolTypeInvalid()
.ReturnsAsync(fullData);

var dynamoDb = new Mock<IDynamoDBContext>();
var validator = new AdminCreatePoolzBackIdValidator(strapi.Object, dynamoDb.Object, lockDealNFT.Object);
var builder = new PhaseContextBuilder<AdminCreatePoolzBackIdRequest>(strapi.Object, dynamoDb.Object);
var validator = new AdminCreatePoolzBackIdValidator(lockDealNFT.Object);
var request = new AdminCreatePoolzBackIdRequest
{
ProjectId = "pid",
PoolzBackId = 5,
ChainId = 1
};

await builder.BuildAsync(request, CancellationToken.None);
await Assert.ThrowsAsync<ValidationException>(() => validator.ValidateAndThrowAsync(request));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
using InvestProvider.Backend.Services.Handlers.AdminWriteAllocation.Models;
using Net.Web3.EthereumWallet;
using FluentValidation;
using InvestProvider.Backend.Services.Handlers.ContextBuilders;

namespace InvestProvider.Backend.Tests.Handlers;

Expand All @@ -33,9 +34,11 @@ public async Task Validate_Succeeds_ForWhitelistPhase()
dynamoDb.Setup(x => x.LoadAsync<ProjectsInformation>("pid", It.IsAny<CancellationToken>()))
.ReturnsAsync(new ProjectsInformation { ProjectId = "pid", PoolzBackId = 5 });

var validator = new AdminWriteAllocationValidator(strapi.Object, dynamoDb.Object);
var builder = new PhaseContextBuilder<AdminWriteAllocationRequest>(strapi.Object, dynamoDb.Object);
var validator = new AdminWriteAllocationValidator();
var request = new AdminWriteAllocationRequest("pid", "1", new[] { new UserWithAmount(new EthereumAddress("0x0000000000000000000000000000000000000001"), 10) });

await builder.BuildAsync(request, CancellationToken.None);
await validator.ValidateAndThrowAsync(request);
}

Expand All @@ -53,9 +56,11 @@ public async Task Validate_Throws_WhenActivePhaseMissing()
dynamoDb.Setup(x => x.LoadAsync<ProjectsInformation>("pid", It.IsAny<CancellationToken>()))
.ReturnsAsync(new ProjectsInformation { ProjectId = "pid", PoolzBackId = 5 });

var validator = new AdminWriteAllocationValidator(strapi.Object, dynamoDb.Object);
var builder = new PhaseContextBuilder<AdminWriteAllocationRequest>(strapi.Object, dynamoDb.Object);
var validator = new AdminWriteAllocationValidator();
var request = new AdminWriteAllocationRequest("pid", "1", Array.Empty<UserWithAmount>());

await builder.BuildAsync(request, CancellationToken.None);
await Assert.ThrowsAsync<ValidationException>(() => validator.ValidateAndThrowAsync(request));
}
}
Loading
Loading