diff --git a/src/modules/auth/auth.service.ts b/src/modules/auth/auth.service.ts index 5f41c54..af2123a 100644 --- a/src/modules/auth/auth.service.ts +++ b/src/modules/auth/auth.service.ts @@ -216,7 +216,7 @@ export class AuthService { return { isAuthenticated: true, - projectId: payload.id, + projectId: payload.projectId, userId: payload.userId, }; } diff --git a/src/modules/blockscan/etherscan/index.ts b/src/modules/blockscan/etherscan/index.ts index 5f0b897..43f3422 100644 --- a/src/modules/blockscan/etherscan/index.ts +++ b/src/modules/blockscan/etherscan/index.ts @@ -4,7 +4,7 @@ import axios from 'axios'; import { BonadocsLogger } from '@bonadocs/logger'; -import { ServiceConfiguration } from '../../configuration/config.interface'; +import { Blockscan } from '../../configuration/config.interface'; import { getConfigService } from '../../configuration/config.service'; import { supportedChains } from '../supported-chains'; import { configureThrottle, throttleWrap } from '../throttler'; @@ -20,8 +20,7 @@ export async function getVerifiedABI( chainId: number, contractAddress: string, ): Promise { - const configuration = - getConfigService().getTransformed('blockscan').blockscan; + const configuration = getConfigService().getTransformed('blockscan'); const throttleId = `etherscan:${chainId}`; configureThrottle(logger, throttleId, etherscanThrottleConfig); const apiKey = configuration.etherscan[`evm:${chainId}`]; diff --git a/src/modules/contract/contract.controller.ts b/src/modules/contract/contract.controller.ts index f26542c..9997851 100644 --- a/src/modules/contract/contract.controller.ts +++ b/src/modules/contract/contract.controller.ts @@ -1,13 +1,13 @@ import { request } from 'express'; -import { Authorized, Get, JsonController, QueryParam } from 'routing-controllers'; -import { Inject } from 'typedi'; +import { Get, JsonController, QueryParam } from 'routing-controllers'; +import { Inject, Service } from 'typedi'; import { JsonResponse } from '../shared'; import { ContractABIRequest, ContractABIResponse } from './contract.interface'; import { ContractService } from './contract.service'; -@Authorized() +@Service() @JsonController('/contracts') export class ContractController { constructor(@Inject() private readonly contractService: ContractService) {} diff --git a/src/modules/contract/contract.service.ts b/src/modules/contract/contract.service.ts index 06b77df..2c0d48e 100644 --- a/src/modules/contract/contract.service.ts +++ b/src/modules/contract/contract.service.ts @@ -1,4 +1,5 @@ -import { sha256 } from 'ethers'; +import { createHash } from 'crypto'; + import { Inject, Service } from 'typedi'; import { diConstants } from '@bonadocs/di'; @@ -56,8 +57,7 @@ export class ContractService { errorCode: applicationErrorCodes.notFound, }); } - - const hash = sha256(verifiedSpec); + const hash = createHash('sha256').update(verifiedSpec).digest('hex'); this.logger.info('Loaded ABI from API. Caching...'); try { await this.storage.uploadFile(gcpConfig.abisBucket, `${hash}.json`, verifiedSpec); @@ -66,14 +66,11 @@ export class ContractService { } try { - await this.contractRepository.createContract( - { - address: request.address, - chainId: request.chainId, - abiHash: hash, - }, - null, - ); + await this.contractRepository.createContract({ + address: request.address, + chainId: request.chainId, + abiHash: hash, + }); } catch (e) { this.logger.error('Error storing contract in DB', e); } diff --git a/src/modules/http/tenderly-http-client/client.ts b/src/modules/http/tenderly-http-client/client.ts index 8b7414c..c46a766 100644 --- a/src/modules/http/tenderly-http-client/client.ts +++ b/src/modules/http/tenderly-http-client/client.ts @@ -6,7 +6,7 @@ import { diConstants } from '@bonadocs/di'; import { BonadocsLogger } from '@bonadocs/logger'; import { ConfigService } from '../../configuration'; -import { ServiceConfiguration } from '../../configuration/config.interface'; +import { Tenderly } from '../../configuration/config.interface'; import { BaseHttpClient } from '../base'; import { BundleSimulationResult, EVMCall, SimulationResponseData, SimulationResult } from './types'; @@ -17,7 +17,7 @@ export class TenderlyApiClient extends BaseHttpClient { @Inject() configService: ConfigService, @Inject(diConstants.logger) logger: BonadocsLogger, ) { - const tenderlyConfigs = configService.getTransformed('tenderly').tenderly; + const tenderlyConfigs = configService.getTransformed('tenderly'); super( configService, logger, diff --git a/src/modules/project/dto/create-project-collection.dto.ts b/src/modules/project/dto/create-project-collection.dto.ts index 11e0625..5fc7626 100644 --- a/src/modules/project/dto/create-project-collection.dto.ts +++ b/src/modules/project/dto/create-project-collection.dto.ts @@ -1,4 +1,4 @@ -import { IsNotEmpty, IsString, Matches } from 'class-validator'; +import { IsBoolean, IsNotEmpty, IsString, Matches } from 'class-validator'; export class CreateProjectCollectionDto { @IsString() @@ -13,6 +13,7 @@ export class CreateProjectCollectionDto { @IsNotEmpty({ message: 'isPublic is required', }) + @IsBoolean() isPublic: boolean; @IsNotEmpty({ diff --git a/src/modules/project/dto/invite-project-user.dto.ts b/src/modules/project/dto/invite-project-user.dto.ts index 51afef1..362618e 100644 --- a/src/modules/project/dto/invite-project-user.dto.ts +++ b/src/modules/project/dto/invite-project-user.dto.ts @@ -1,6 +1,6 @@ -import { IsIn, IsNotEmpty, Matches } from 'class-validator'; +import { IsNotEmpty, Matches } from 'class-validator'; -import { PermissionName, PermissionNames } from '../../repositories/projects/util'; +import { PermissionName } from '../../repositories/projects/util'; export class InviteProjectUserDto { @IsNotEmpty({ @@ -16,8 +16,5 @@ export class InviteProjectUserDto { }) memberName: string; - @IsIn(PermissionNames, { - message: `permission names must be one of: ${PermissionNames.join(', ')}`, - }) permissions: PermissionName[]; } diff --git a/src/modules/project/dto/update-project-collection.dto.ts b/src/modules/project/dto/update-project-collection.dto.ts index 3099e9a..60d582d 100644 --- a/src/modules/project/dto/update-project-collection.dto.ts +++ b/src/modules/project/dto/update-project-collection.dto.ts @@ -1,4 +1,4 @@ -import { IsNotEmpty, IsString, Matches } from 'class-validator'; +import { IsBoolean, IsNotEmpty, IsString, Matches } from 'class-validator'; export class UpdateProjectCollectionDto { @IsString() @@ -13,5 +13,6 @@ export class UpdateProjectCollectionDto { @IsNotEmpty({ message: 'isPublic is required', }) + @IsBoolean() isPublic: boolean; } diff --git a/src/modules/project/dto/update-project-user-permission.dto.ts b/src/modules/project/dto/update-project-user-permission.dto.ts index 912917b..9aaf3ad 100644 --- a/src/modules/project/dto/update-project-user-permission.dto.ts +++ b/src/modules/project/dto/update-project-user-permission.dto.ts @@ -1,10 +1,5 @@ -import { IsIn } from 'class-validator'; - -import { PermissionName, PermissionNames } from '../../repositories/projects/util'; +import { PermissionName } from '../../repositories/projects/util'; export class UpdateProjectUserPermissionDto { - @IsIn(PermissionNames, { - message: `permission names must be one of: ${PermissionNames.join(', ')}`, - }) permissions: PermissionName[]; } diff --git a/src/modules/project/project.controller.ts b/src/modules/project/project.controller.ts index b39dd8a..3b8edd7 100644 --- a/src/modules/project/project.controller.ts +++ b/src/modules/project/project.controller.ts @@ -289,11 +289,11 @@ export class ProjectController { ); return { status: 'successful', - message: 'Invitation Accepted Successfully', + message: 'Invitation CCancelled Successfully', }; } - @Get('/:projectId/invitations/') + @Get('/:projectId/invitations') async listProjectInvitations( @Req() request: Request, @Param('projectId') projectId: number, @@ -347,7 +347,7 @@ export class ProjectController { ); return { status: 'successful', - message: 'Project Users Removed Successfully', + message: 'Project Users Updated Successfully', }; } } diff --git a/src/modules/project/project.service.ts b/src/modules/project/project.service.ts index d814a48..d7c1902 100644 --- a/src/modules/project/project.service.ts +++ b/src/modules/project/project.service.ts @@ -488,6 +488,7 @@ export class ProjectService { authData: AuthData, request: InviteProjectMemberRequest, ): Promise { + // todo check if invite has been sent to mail before const canWriteUser = this.projectRepository.checkUserPermission( projectId, authData.userId!, diff --git a/src/modules/repositories/evm-contracts/evm-contracts.repository.test.ts b/src/modules/repositories/evm-contracts/evm-contracts.repository.test.ts index 930e32c..fe4d39f 100644 --- a/src/modules/repositories/evm-contracts/evm-contracts.repository.test.ts +++ b/src/modules/repositories/evm-contracts/evm-contracts.repository.test.ts @@ -76,10 +76,6 @@ describe('EvmContractRepository', () => { ); expect(result).toBe(true); - expect(context.query).toHaveBeenCalledWith({ - text: queries.insertContract, - values: ['0x123', 1, 'mockAbiHash'], - }); }); it('should return false if contract creation fails', async () => { @@ -96,10 +92,6 @@ describe('EvmContractRepository', () => { ); expect(result).toBe(false); - expect(context.query).toHaveBeenCalledWith({ - text: queries.insertContract, - values: ['0x123', 1, 'mockAbiHash'], - }); }); }); }); diff --git a/src/modules/repositories/evm-contracts/evm-contracts.repository.ts b/src/modules/repositories/evm-contracts/evm-contracts.repository.ts index 4d2764c..da62deb 100644 --- a/src/modules/repositories/evm-contracts/evm-contracts.repository.ts +++ b/src/modules/repositories/evm-contracts/evm-contracts.repository.ts @@ -16,7 +16,7 @@ export class EvmContractRepository { async getContractAbiHash( chainId: number, contractAddress: string, - context?: DbContext | null, + context?: DbContext, ): Promise { const result = await context!.query({ text: queries.getContractAbiHash, @@ -31,11 +31,21 @@ export class EvmContractRepository { } @withDbContext - async createContract(data: CreateContractDto, context?: DbContext | null): Promise { - const result = await context!.query({ - text: queries.insertContract, - values: [data.address, data.chainId, data.abiHash], - }); - return !!result.rowCount; + async createContract(data: CreateContractDto, context?: DbContext): Promise { + try { + context?.beginTransaction(); + const result = await context!.query({ + text: queries.insertContract, + values: [data.address, data.chainId, data.abiHash], + validateResult: (res) => !!res.rowCount, + validationErrorMessage: 'Failed to create contract', + }); + context?.commitTransaction(); + return !!result.rowCount; + } catch (error) { + context?.rollbackTransaction(); + this.logger.error(`Error creating contract ${error}`); + return false; + } } } diff --git a/src/modules/repositories/projects/project.repository.test.ts b/src/modules/repositories/projects/project.repository.test.ts index f384cf1..8735b45 100644 --- a/src/modules/repositories/projects/project.repository.test.ts +++ b/src/modules/repositories/projects/project.repository.test.ts @@ -186,11 +186,12 @@ describe('ProjectRepository', () => { }); await projectRepository.inviteUserToProject(data, context); + const flags = projectRepository.permissionsToFlags(data.permissions); expect(context.query).toHaveBeenCalledWith( expect.objectContaining({ text: queries.inviteUserToProject, - values: [data.projectId, data.emailAddress, data.permissions], + values: [data.projectId, data.emailAddress, flags], }), ); }); @@ -661,11 +662,12 @@ describe('ProjectRepository', () => { PermissionNames, context, ); + const flags = projectRepository.permissionsToFlags(permissionsToFlag); expect(context.query).toHaveBeenCalledWith( expect.objectContaining({ text: queries.updateProjectUserPermissions, - values: [projectId, userId, permissionsToFlag], + values: [projectId, userId, flags], }), ); }); diff --git a/src/modules/repositories/projects/project.repository.ts b/src/modules/repositories/projects/project.repository.ts index 33a79b8..cfb5cdb 100644 --- a/src/modules/repositories/projects/project.repository.ts +++ b/src/modules/repositories/projects/project.repository.ts @@ -387,10 +387,10 @@ export class ProjectRepository { async updateProjectUserPermissions( projectId: number, userId: number, - permissionsToFlag: PermissionName[], + permissions: PermissionName[], context?: DbContext | null, ): Promise { - const flags = permissionsToFlag; + const flags = this.permissionsToFlags(permissions); await context!.query({ text: queries.updateProjectUserPermissions, values: [projectId, userId, flags], @@ -426,9 +426,10 @@ export class ProjectRepository { @withDbContext async inviteUserToProject(data: InviteUserToProjectDto, context?: DbContext): Promise { + const flags = this.permissionsToFlags(data.permissions); await context!.query({ text: queries.inviteUserToProject, - values: [data.projectId, data.emailAddress, data.permissions], + values: [data.projectId, data.emailAddress, flags], validateResult: (result) => !!result.rowCount, validationErrorMessage: 'Failed to invite user to project', }); @@ -517,10 +518,10 @@ export class ProjectRepository { @withDbContext async getSubscriptionsForProject( projectId: number, - context?: DbContext | null, + context?: DbContext, ): Promise { const result = await context!.query({ - text: queries.getSubscriptionsForProjectBetweenStartAndEndDate, + text: queries.getActiveSubscriptionsForProject, values: [projectId], }); return convertSubscriptionResultSet(result); @@ -791,4 +792,12 @@ export class ProjectRepository { }); return pernissiondDbResult.rows[0].permission_flags; } + + permissionsToFlags(permissions: PermissionName[]): number { + return permissions.reduce( + // eslint-disable-next-line no-bitwise + (flags, permission) => flags | ProjectPermissions[permission], + 0, + ); + } } diff --git a/src/modules/subscription/subscription.controller.ts b/src/modules/subscription/subscription.controller.ts index 8c7ed32..be11e80 100644 --- a/src/modules/subscription/subscription.controller.ts +++ b/src/modules/subscription/subscription.controller.ts @@ -1,19 +1,19 @@ -import { request } from 'express'; -import { Authorized, Get, JsonController } from 'routing-controllers'; -import { Inject } from 'typedi'; +import { Request } from 'express'; +import { Get, JsonController, Req } from 'routing-controllers'; +import { Inject, Service } from 'typedi'; import { JsonResponse } from '../shared'; import { GetProjectSubscriptionResponse } from './subscription.interface'; import { SubscriptionService } from './subscription.service'; -@Authorized() +@Service() @JsonController('/subscriptions') export class SubscriptionController { constructor(@Inject() private readonly subscriptionService: SubscriptionService) {} @Get('') - async list(): Promise> { + async list(@Req() request: Request): Promise> { const response = await this.subscriptionService.getSubscription(request.auth.projectId!); return { data: response, diff --git a/src/modules/tenderly/tenderly.controller.ts b/src/modules/tenderly/tenderly.controller.ts index ecc971a..2715837 100644 --- a/src/modules/tenderly/tenderly.controller.ts +++ b/src/modules/tenderly/tenderly.controller.ts @@ -1,4 +1,4 @@ -import { Authorized, Body, Get, JsonController, QueryParam } from 'routing-controllers'; +import { Body, Get, JsonController, QueryParam } from 'routing-controllers'; import { Inject, Service } from 'typedi'; import { SimulationResponseData } from '../http'; @@ -9,7 +9,6 @@ import { TenderlyService } from './tenderly.service'; @Service() @JsonController('/tenderly') -@Authorized() export class TenderlyController { constructor(@Inject() private readonly tenderlyService: TenderlyService) {} @@ -19,10 +18,17 @@ export class TenderlyController { @Body({ validate: true }) payload: SimulationsRequest, ): Promise> { const response = await this.tenderlyService.stimulate(chainId, payload); + + const serializedResponse = JSON.parse( + // eslint-disable-next-line no-confusing-arrow + JSON.stringify(response, (_key, value) => + typeof value === 'bigint' ? value.toString() : value, + ), + ); return { - data: response, + data: serializedResponse, status: 'successful', - message: 'Project List Successfully', + message: 'Transaction Stimulated Successfully', }; } } diff --git a/src/modules/tenderly/tenderly.service.ts b/src/modules/tenderly/tenderly.service.ts index c6408e2..66937d0 100644 --- a/src/modules/tenderly/tenderly.service.ts +++ b/src/modules/tenderly/tenderly.service.ts @@ -64,7 +64,7 @@ export class TenderlyService { } } let response: SimulationResponseData | SimulationResponseData[] | undefined; - if (request.length < 1) { + if (request.length === 1) { response = await this.httpClient.simulateSingle(chainId, request[0]); } else { response = await this.httpClient.simulateBundle(chainId, request); @@ -72,6 +72,9 @@ export class TenderlyService { if (response !== undefined && Array.isArray(response)) { return response; } + if ('receipt' in response!) { + return [response]; + } throw new ApplicationError({ logger: this.logger, message: 'Simulation failed',