From 6ef27bfd9cfa741ebccc42e9ce4a4d02fbeb8271 Mon Sep 17 00:00:00 2001 From: Mehdi <75360886+mrhouzlane@users.noreply.github.com> Date: Thu, 21 Mar 2024 15:53:24 +0000 Subject: [PATCH 01/66] [ReputationOracle] Refactor Auth (#1701) * refactor reputation * add web3 auth remove createWeb3User * fix tests * fix mocks * resolve comments * add migration * add migration * fix auth refactor * fix rep oracle tests --------- Co-authored-by: portuu3 --- .../src/modules/auth/auth.controller.ts | 20 + .../reputation-oracle/server/.env.example | 4 +- .../server/src/common/config/env.ts | 10 +- .../server/src/common/constants/errors.ts | 4 + .../server/src/common/constants/index.ts | 2 + .../src/common/exceptions/auth.filter.ts | 35 + .../server/src/common/pipes/validation.ts | 20 + .../server/src/common/utils/hcaptcha.ts | 27 + .../server/src/database/database.module.ts | 2 - .../migrations/1711022497871-RefactorAuth.ts | 59 ++ .../src/modules/auth/auth.controller.ts | 109 ++- .../server/src/modules/auth/auth.dto.ts | 11 + .../server/src/modules/auth/auth.entity.ts | 30 - .../server/src/modules/auth/auth.error.ts | 5 + .../server/src/modules/auth/auth.module.ts | 11 +- .../src/modules/auth/auth.repository.ts | 64 -- .../src/modules/auth/auth.service.spec.ts | 850 +++++++++--------- .../server/src/modules/auth/auth.service.ts | 269 ++++-- .../src/modules/auth/strategy/jwt.http.ts | 48 +- .../server/src/modules/auth/token.entity.ts | 20 +- .../src/modules/auth/token.repository.ts | 54 +- .../src/modules/cron-job/cron-job.service.ts | 2 - .../server/src/modules/user/user.dto.ts | 16 +- .../server/src/modules/user/user.entity.ts | 4 - .../src/modules/user/user.repository.ts | 73 +- .../src/modules/user/user.service.spec.ts | 168 +--- .../server/src/modules/user/user.service.ts | 79 +- .../apps/reputation-oracle/server/vercel.json | 4 +- 28 files changed, 987 insertions(+), 1013 deletions(-) create mode 100644 packages/apps/reputation-oracle/server/src/common/exceptions/auth.filter.ts create mode 100644 packages/apps/reputation-oracle/server/src/common/utils/hcaptcha.ts create mode 100644 packages/apps/reputation-oracle/server/src/database/migrations/1711022497871-RefactorAuth.ts delete mode 100644 packages/apps/reputation-oracle/server/src/modules/auth/auth.entity.ts create mode 100644 packages/apps/reputation-oracle/server/src/modules/auth/auth.error.ts delete mode 100644 packages/apps/reputation-oracle/server/src/modules/auth/auth.repository.ts diff --git a/packages/apps/job-launcher/server/src/modules/auth/auth.controller.ts b/packages/apps/job-launcher/server/src/modules/auth/auth.controller.ts index 7676ce28ba..ca2bb7fdcb 100644 --- a/packages/apps/job-launcher/server/src/modules/auth/auth.controller.ts +++ b/packages/apps/job-launcher/server/src/modules/auth/auth.controller.ts @@ -83,6 +83,10 @@ export class AuthJwtController { status: 201, description: 'User registered successfully', }) + @ApiResponse({ + status: 400, + description: 'Bad Request. Invalid input parameters.', + }) public async signup( @Body() data: UserCreateDto, @Ip() ip: string, @@ -155,6 +159,14 @@ export class AuthJwtController { status: 204, description: 'Password reset email sent successfully', }) + @ApiResponse({ + status: 401, + description: 'Unauthorized. Missing or invalid credentials.', + }) + @ApiResponse({ + status: 404, + description: 'Not Found. Could not find the requested content.', + }) public async forgotPassword(@Body() data: ForgotPasswordDto): Promise { await this.authService.forgotPassword(data); } @@ -190,6 +202,10 @@ export class AuthJwtController { status: 200, description: 'Email verification successful', }) + @ApiResponse({ + status: 404, + description: 'Not Found. Could not find the requested content.', + }) public async emailVerification(@Body() data: VerifyEmailDto): Promise { await this.authService.emailVerification(data); } @@ -207,6 +223,10 @@ export class AuthJwtController { status: 204, description: 'Email verification resent successfully', }) + @ApiResponse({ + status: 404, + description: 'Not Found. Could not find the requested content.', + }) public async resendEmailVerification( @Body() data: ResendEmailVerificationDto, ): Promise { diff --git a/packages/apps/reputation-oracle/server/.env.example b/packages/apps/reputation-oracle/server/.env.example index f40afe1622..ecd67819d9 100644 --- a/packages/apps/reputation-oracle/server/.env.example +++ b/packages/apps/reputation-oracle/server/.env.example @@ -5,6 +5,7 @@ PORT=3008 FE_URL=http://localhost:3009 SESSION_SECRET=test MAX_RETRY_COUNT=5 +CRON_SECRET=test # Database POSTGRES_HOST=0.0.0.0 @@ -18,7 +19,8 @@ POSTGRES_LOGGING='all' # Auth HASH_SECRET=a328af3fc1dad15342cc3d68936008fa -JWT_SECRET=secret +JWT_PRIVATE_KEY= +JWT_PUBLIC_KEY= JWT_ACCESS_TOKEN_EXPIRES_IN=1000000000 JWT_REFRESH_TOKEN_EXPIRES_IN=1000000000 diff --git a/packages/apps/reputation-oracle/server/src/common/config/env.ts b/packages/apps/reputation-oracle/server/src/common/config/env.ts index 88239e2746..8330feec58 100644 --- a/packages/apps/reputation-oracle/server/src/common/config/env.ts +++ b/packages/apps/reputation-oracle/server/src/common/config/env.ts @@ -7,10 +7,12 @@ export const ConfigNames = { FE_URL: 'FE_URL', SESSION_SECRET: 'SESSION_SECRET', MAX_RETRY_COUNT: 'MAX_RETRY_COUNT', - HASH_SECRET: 'HASH_SECRET', - JWT_SECRET: 'JWT_SECRET', + JWT_PRIVATE_KEY: 'JWT_PRIVATE_KEY', + JWT_PUBLIC_KEY: 'JWT_PUBLIC_KEY', JWT_ACCESS_TOKEN_EXPIRES_IN: 'JWT_ACCESS_TOKEN_EXPIRES_IN', - JWT_REFRESH_TOKEN_EXPIRES_IN: 'JWT_REFRESH_TOKEN_EXPIRES_IN', + REFRESH_TOKEN_EXPIRES_IN: 'REFRESH_TOKEN_EXPIRES_IN', + VERIFY_EMAIL_TOKEN_EXPIRES_IN: 'VERIFY_EMAIL_TOKEN_EXPIRES_IN', + FORGOT_PASSWORD_TOKEN_EXPIRES_IN: 'FORGOT_PASSWORD_TOKEN_EXPIRES_IN', POSTGRES_HOST: 'POSTGRES_HOST', POSTGRES_USER: 'POSTGRES_USER', POSTGRES_PASSWORD: 'POSTGRES_PASSWORD', @@ -38,6 +40,8 @@ export const ConfigNames = { PGP_ENCRYPT: 'PGP_ENCRYPT', SYNAPS_API_KEY: 'SYNAPS_API_KEY', SYNAPS_WEBHOOK_SECRET: 'SYNAPS_WEBHOOK_SECRET', + APIKEY_ITERATIONS: 'APIKEY_ITERATIONS', + APIKEY_KEY_LENGTH: 'APIKEY_KEY_LENGTH', CRON_SECRET: 'CRON_SECRET', }; diff --git a/packages/apps/reputation-oracle/server/src/common/constants/errors.ts b/packages/apps/reputation-oracle/server/src/common/constants/errors.ts index 9b3224a181..58cd6b17f9 100644 --- a/packages/apps/reputation-oracle/server/src/common/constants/errors.ts +++ b/packages/apps/reputation-oracle/server/src/common/constants/errors.ts @@ -54,6 +54,7 @@ export enum ErrorUser { InvalidCredentials = 'Invalid credentials', IncorrectAddress = 'Incorrect address', KycNotApproved = 'KYC not approved', + UserNotActive = 'User not active', } /** @@ -63,9 +64,12 @@ export enum ErrorAuth { NotFound = 'Auth not found', InvalidEmailOrPassword = 'Invalid email or password', RefreshTokenHasExpired = 'Refresh token has expired', + TokenExpired = 'Token has expired', UserNotActive = 'User not active', InvalidSignature = 'Invalid signature', InvalidRole = 'Invalid role in KVStore', + PasswordIsNotStrongEnough = 'Password is not strong enough. Password must be at least eight characters long and contain 1 upper, 1 lowercase, 1 number and 1 special character. (!@#$%^&*()_+={}|\'"/`[]:;<>,.?~-])', + InvalidToken = 'Invalid token', } /** diff --git a/packages/apps/reputation-oracle/server/src/common/constants/index.ts b/packages/apps/reputation-oracle/server/src/common/constants/index.ts index df9da6a1b0..257819997b 100644 --- a/packages/apps/reputation-oracle/server/src/common/constants/index.ts +++ b/packages/apps/reputation-oracle/server/src/common/constants/index.ts @@ -24,6 +24,8 @@ export const CVAT_JOB_TYPES = [ export const HEADER_SIGNATURE_KEY = 'human-signature'; +export const RESEND_EMAIL_VERIFICATION_PATH = '/auth/resend-email-verification'; + export const CURSE_WORDS = [ '4r5e', '5h1t', diff --git a/packages/apps/reputation-oracle/server/src/common/exceptions/auth.filter.ts b/packages/apps/reputation-oracle/server/src/common/exceptions/auth.filter.ts new file mode 100644 index 0000000000..050578c2f1 --- /dev/null +++ b/packages/apps/reputation-oracle/server/src/common/exceptions/auth.filter.ts @@ -0,0 +1,35 @@ +import { + ExceptionFilter, + Catch, + ArgumentsHost, + Logger, + HttpStatus, +} from '@nestjs/common'; +import { Request, Response } from 'express'; +import { AuthError } from '../../modules/auth/auth.error'; +import { ErrorUser } from '../constants/errors'; + +@Catch(AuthError) +export class AuthExceptionFilter implements ExceptionFilter { + private logger = new Logger(AuthExceptionFilter.name); + + catch(exception: AuthError, host: ArgumentsHost) { + const ctx = host.switchToHttp(); + const response = ctx.getResponse(); + const request = ctx.getRequest(); + + let status = HttpStatus.FORBIDDEN; + if (exception.message === ErrorUser.NotFound) { + status = HttpStatus.NO_CONTENT; + } + const message = exception.message; + this.logger.error(`Auth error: ${message}`, exception.stack); + + response.status(status).json({ + statusCode: status, + timestamp: new Date().toISOString(), + message: message, + path: request.url, + }); + } +} diff --git a/packages/apps/reputation-oracle/server/src/common/pipes/validation.ts b/packages/apps/reputation-oracle/server/src/common/pipes/validation.ts index f46e742df2..265d67f6cf 100644 --- a/packages/apps/reputation-oracle/server/src/common/pipes/validation.ts +++ b/packages/apps/reputation-oracle/server/src/common/pipes/validation.ts @@ -1,10 +1,13 @@ import { BadRequestException, Injectable, + PipeTransform, ValidationError, ValidationPipe, ValidationPipeOptions, } from '@nestjs/common'; +import { ValidatePasswordDto } from '../../modules/auth/auth.dto'; +import { ErrorAuth } from '../constants/errors'; @Injectable() export class HttpValidationPipe extends ValidationPipe { @@ -20,3 +23,20 @@ export class HttpValidationPipe extends ValidationPipe { }); } } + +@Injectable() +export class PasswordValidationPipe implements PipeTransform { + transform(value: ValidatePasswordDto) { + if (!this.isValidPassword(value.password)) { + throw new BadRequestException(ErrorAuth.PasswordIsNotStrongEnough); + } + + return value; + } + + private isValidPassword(password: string): boolean { + const regex = + /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[!@#$%^&*()_+={}\|'"/`[\]:;<>,.?~\\-]).*$/; + return regex.test(password); + } +} diff --git a/packages/apps/reputation-oracle/server/src/common/utils/hcaptcha.ts b/packages/apps/reputation-oracle/server/src/common/utils/hcaptcha.ts new file mode 100644 index 0000000000..5a29900178 --- /dev/null +++ b/packages/apps/reputation-oracle/server/src/common/utils/hcaptcha.ts @@ -0,0 +1,27 @@ +import axios from 'axios'; + +export async function verifyToken( + url: string, + sitekey: string, + secret: string, + token: string, + ip?: string, +) { + const queryParams: any = { + secret, + sitekey, + response: token, + }; + + if (ip) { + queryParams.remoteip = ip; + } + + const { data } = await axios.post( + `${url}/siteverify`, + {}, + { params: queryParams }, + ); + + return data; +} diff --git a/packages/apps/reputation-oracle/server/src/database/database.module.ts b/packages/apps/reputation-oracle/server/src/database/database.module.ts index a2e7bed427..c811cbad33 100644 --- a/packages/apps/reputation-oracle/server/src/database/database.module.ts +++ b/packages/apps/reputation-oracle/server/src/database/database.module.ts @@ -8,7 +8,6 @@ import { NS } from '../common/constants'; import { TypeOrmLoggerModule, TypeOrmLoggerService } from './typeorm'; import { WebhookIncomingEntity } from '../modules/webhook/webhook-incoming.entity'; import { ReputationEntity } from '../modules/reputation/reputation.entity'; -import { AuthEntity } from '../modules/auth/auth.entity'; import { TokenEntity } from '../modules/auth/token.entity'; import { UserEntity } from '../modules/user/user.entity'; import { KycEntity } from '../modules/kyc/kyc.entity'; @@ -39,7 +38,6 @@ import { ConfigNames } from '../common/config'; entities: [ WebhookIncomingEntity, ReputationEntity, - AuthEntity, TokenEntity, UserEntity, KycEntity, diff --git a/packages/apps/reputation-oracle/server/src/database/migrations/1711022497871-RefactorAuth.ts b/packages/apps/reputation-oracle/server/src/database/migrations/1711022497871-RefactorAuth.ts new file mode 100644 index 0000000000..8e18f7fe43 --- /dev/null +++ b/packages/apps/reputation-oracle/server/src/database/migrations/1711022497871-RefactorAuth.ts @@ -0,0 +1,59 @@ +import { MigrationInterface, QueryRunner } from 'typeorm'; + +export class RefactorAuth1711022497871 implements MigrationInterface { + name = 'RefactorAuth1711022497871'; + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `ALTER TABLE "hmt"."tokens" DROP COLUMN "token_type"`, + ); + await queryRunner.query(`DROP TYPE "hmt"."tokens_token_type_enum"`); + await queryRunner.query( + `CREATE TYPE "hmt"."tokens_type_enum" AS ENUM('EMAIL', 'PASSWORD', 'REFRESH')`, + ); + await queryRunner.query( + `ALTER TABLE "hmt"."tokens" ADD "type" "hmt"."tokens_type_enum" NOT NULL`, + ); + await queryRunner.query( + `ALTER TABLE "hmt"."tokens" ADD "expires_at" TIMESTAMP WITH TIME ZONE NOT NULL`, + ); + await queryRunner.query( + `ALTER TABLE "hmt"."tokens" DROP CONSTRAINT "FK_8769073e38c365f315426554ca5"`, + ); + await queryRunner.query( + `ALTER TABLE "hmt"."tokens" DROP CONSTRAINT "REL_8769073e38c365f315426554ca"`, + ); + await queryRunner.query( + `CREATE UNIQUE INDEX "IDX_306030d9411d291750fd115857" ON "hmt"."tokens" ("type", "user_id") `, + ); + await queryRunner.query( + `ALTER TABLE "hmt"."tokens" ADD CONSTRAINT "FK_8769073e38c365f315426554ca5" FOREIGN KEY ("user_id") REFERENCES "hmt"."users"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`, + ); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `ALTER TABLE "hmt"."tokens" DROP CONSTRAINT "FK_8769073e38c365f315426554ca5"`, + ); + await queryRunner.query( + `DROP INDEX "hmt"."IDX_306030d9411d291750fd115857"`, + ); + await queryRunner.query( + `ALTER TABLE "hmt"."tokens" ADD CONSTRAINT "REL_8769073e38c365f315426554ca" UNIQUE ("user_id")`, + ); + await queryRunner.query( + `ALTER TABLE "hmt"."tokens" ADD CONSTRAINT "FK_8769073e38c365f315426554ca5" FOREIGN KEY ("user_id") REFERENCES "hmt"."users"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "hmt"."tokens" DROP COLUMN "expires_at"`, + ); + await queryRunner.query(`ALTER TABLE "hmt"."tokens" DROP COLUMN "type"`); + await queryRunner.query(`DROP TYPE "hmt"."tokens_type_enum"`); + await queryRunner.query( + `CREATE TYPE "hmt"."tokens_token_type_enum" AS ENUM('EMAIL', 'PASSWORD')`, + ); + await queryRunner.query( + `ALTER TABLE "hmt"."tokens" ADD "token_type" "hmt"."tokens_token_type_enum" NOT NULL`, + ); + } +} diff --git a/packages/apps/reputation-oracle/server/src/modules/auth/auth.controller.ts b/packages/apps/reputation-oracle/server/src/modules/auth/auth.controller.ts index 88f422032a..8462f0dea8 100644 --- a/packages/apps/reputation-oracle/server/src/modules/auth/auth.controller.ts +++ b/packages/apps/reputation-oracle/server/src/modules/auth/auth.controller.ts @@ -16,6 +16,10 @@ import { Req, UseGuards, UseInterceptors, + UseFilters, + Logger, + UsePipes, + Ip, } from '@nestjs/common'; import { Public } from '../../common/decorators'; import { UserCreateDto } from '../user/user.dto'; @@ -26,18 +30,46 @@ import { RestorePasswordDto, SignInDto, VerifyEmailDto, - Web3SignInDto, Web3SignUpDto, + Web3SignInDto, + RefreshDto, } from './auth.dto'; import { AuthService } from './auth.service'; import { JwtAuthGuard } from '../../common/guards'; import { RequestWithUser } from '../../common/types'; +import { AuthExceptionFilter } from '../../common/exceptions/auth.filter'; +import { TokenRepository } from './token.repository'; +import { PasswordValidationPipe } from '../../common/pipes'; +import { TokenType } from './token.entity'; @ApiTags('Auth') +@ApiResponse({ + status: 400, + description: 'Bad Request. Invalid input parameters.', +}) +@ApiResponse({ + status: 401, + description: 'Unauthorized. Missing or invalid credentials.', +}) +@ApiResponse({ + status: 404, + description: 'Not Found. Could not find the requested content.', +}) +@ApiResponse({ + status: 422, + description: 'Unprocessable entity.', +}) +@UseFilters(AuthExceptionFilter) @Controller('/auth') export class AuthJwtController { - constructor(private readonly authService: AuthService) {} + private readonly logger = new Logger(AuthJwtController.name); + constructor( + private readonly authService: AuthService, + private readonly tokenRepository: TokenRepository, + ) {} + + @UsePipes(new PasswordValidationPipe()) @Public() @Post('/signup') @UseInterceptors(ClassSerializerInterceptor) @@ -54,8 +86,11 @@ export class AuthJwtController { status: 400, description: 'Bad Request. Invalid input parameters.', }) - public async signup(@Body() data: UserCreateDto): Promise { - await this.authService.signup(data); + public async signup( + @Body() data: UserCreateDto, + @Ip() ip: string, + ): Promise { + await this.authService.signup(data, ip); return; } @@ -80,49 +115,49 @@ export class AuthJwtController { status: 404, description: 'Not Found. Could not find the requested content.', }) - public signin(@Body() data: SignInDto): Promise { - return this.authService.signin(data); + public signin(@Body() data: SignInDto, @Ip() ip: string): Promise { + return this.authService.signin(data, ip); } @Public() - @Post('/web3/signin') - @HttpCode(200) + @Post('/web3/signup') @ApiOperation({ - summary: 'Web3 User Signin', - description: 'Endpoint for Web3 user authentication.', + summary: 'Web3 User Signup', + description: 'Endpoint for Web3 user registration.', }) - @ApiBody({ type: Web3SignInDto }) + @ApiBody({ type: Web3SignUpDto }) @ApiResponse({ status: 200, - description: 'User authenticated successfully', + description: 'User registered successfully', type: AuthDto, }) @ApiResponse({ status: 401, description: 'Unauthorized. Missing or invalid credentials.', }) - public async web3SignIn(@Body() data: Web3SignInDto): Promise { - return this.authService.web3Signin(data); + public async web3SignUp(@Body() data: Web3SignUpDto): Promise { + return this.authService.web3Signup(data); } @Public() - @Post('/web3/signup') + @Post('/web3/signin') + @HttpCode(200) @ApiOperation({ - summary: 'Web3 User Signup', - description: 'Endpoint for Web3 user registration.', + summary: 'Web3 User Signin', + description: 'Endpoint for Web3 user authentication.', }) - @ApiBody({ type: Web3SignUpDto }) + @ApiBody({ type: Web3SignInDto }) @ApiResponse({ status: 200, - description: 'User registered successfully', + description: 'User authenticated successfully', type: AuthDto, }) @ApiResponse({ status: 401, description: 'Unauthorized. Missing or invalid credentials.', }) - public async web3SignUp(@Body() data: Web3SignUpDto): Promise { - return this.authService.web3Signup(data); + public async web3SignIn(@Body() data: Web3SignInDto): Promise { + return this.authService.web3Signin(data); } @Public() @@ -143,11 +178,10 @@ export class AuthJwtController { public async getNonce(@Param('address') address: string): Promise { return this.authService.getNonce(address); } - - @ApiBearerAuth() - @UseGuards(JwtAuthGuard) - @Post('/refresh') + @Public() @HttpCode(200) + @Post('/refresh') + @ApiBody({ type: RefreshDto }) @ApiOperation({ summary: 'Refresh Token', description: 'Endpoint to refresh the authentication token.', @@ -157,14 +191,14 @@ export class AuthJwtController { description: 'Token refreshed successfully', type: AuthDto, }) - async refreshToken(@Req() request: RequestWithUser): Promise { - return this.authService.auth(request.user); + async refreshToken(@Body() data: RefreshDto): Promise { + return this.authService.refresh(data); } @ApiBearerAuth() @UseGuards(JwtAuthGuard) - @Post('/logout') @HttpCode(204) + @Post('/logout') @ApiOperation({ summary: 'User Logout', description: 'Endpoint to log out the user.', @@ -174,7 +208,10 @@ export class AuthJwtController { description: 'User logged out successfully', }) public async logout(@Req() request: RequestWithUser): Promise { - await this.authService.logout(request.user); + await this.tokenRepository.deleteOneByTypeAndUserId( + TokenType.REFRESH, + request.user.id, + ); } @Public() @@ -217,13 +254,16 @@ export class AuthJwtController { status: 404, description: 'Not Found. Could not find the requested content.', }) - public restorePassword(@Body() data: RestorePasswordDto): Promise { - return this.authService.restorePassword(data); + public restorePassword( + @Body() data: RestorePasswordDto, + @Ip() ip: string, + ): Promise { + return this.authService.restorePassword(data, ip); } @Public() - @Post('/email-verification') @HttpCode(200) + @Post('/email-verification') @ApiOperation({ summary: 'Email Verification', description: 'Endpoint to verify the user email address.', @@ -241,9 +281,10 @@ export class AuthJwtController { await this.authService.emailVerification(data); } - @Public() - @Post('/resend-email-verification') + @ApiBearerAuth() + @UseGuards(JwtAuthGuard) @HttpCode(204) + @Post('/resend-email-verification') @ApiOperation({ summary: 'Resend Email Verification', description: 'Endpoint to resend the email verification link.', diff --git a/packages/apps/reputation-oracle/server/src/modules/auth/auth.dto.ts b/packages/apps/reputation-oracle/server/src/modules/auth/auth.dto.ts index 66e40419cb..581d62fac1 100644 --- a/packages/apps/reputation-oracle/server/src/modules/auth/auth.dto.ts +++ b/packages/apps/reputation-oracle/server/src/modules/auth/auth.dto.ts @@ -6,6 +6,7 @@ import { IsEthereumAddress, IsString, Matches, + IsUUID, } from 'class-validator'; import { IsPassword } from '../../common/validators'; import { TokenType } from '../auth/token.entity'; @@ -30,6 +31,12 @@ export class SignInDto { public password: string; } +export class RefreshDto { + @ApiProperty({ name: 'refresh_token' }) + @IsUUID() + public refreshToken: string; +} + export class ValidatePasswordDto { @Matches(/^(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.*[!@#$%^&*])(?=.{8,})/, { message: @@ -51,6 +58,10 @@ export class RestorePasswordDto extends ValidatePasswordDto { @ApiProperty() @IsString() public token: string; + + @ApiProperty({ name: 'h_captcha_token' }) + @IsString() + public hCaptchaToken: string; } export class VerifyEmailDto { diff --git a/packages/apps/reputation-oracle/server/src/modules/auth/auth.entity.ts b/packages/apps/reputation-oracle/server/src/modules/auth/auth.entity.ts deleted file mode 100644 index 6a5a108799..0000000000 --- a/packages/apps/reputation-oracle/server/src/modules/auth/auth.entity.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { - Column, - Entity, - JoinColumn, - OneToOne, - PrimaryGeneratedColumn, -} from 'typeorm'; - -import { NS } from '../../common/constants'; -import { UserEntity } from '../user/user.entity'; -import { BaseEntity } from '../../database/base.entity'; - -@Entity({ schema: NS, name: 'auths' }) -export class AuthEntity extends BaseEntity { - @PrimaryGeneratedColumn() - public id: number; - - @Column({ type: 'varchar' }) - public accessToken: string; - - @Column({ type: 'varchar' }) - public refreshToken: string; - - @JoinColumn() - @OneToOne(() => UserEntity) - public user: UserEntity; - - @Column({ type: 'int' }) - public userId: number; -} diff --git a/packages/apps/reputation-oracle/server/src/modules/auth/auth.error.ts b/packages/apps/reputation-oracle/server/src/modules/auth/auth.error.ts new file mode 100644 index 0000000000..58547a5b47 --- /dev/null +++ b/packages/apps/reputation-oracle/server/src/modules/auth/auth.error.ts @@ -0,0 +1,5 @@ +export class AuthError extends Error { + constructor(message: string) { + super(message); + } +} diff --git a/packages/apps/reputation-oracle/server/src/modules/auth/auth.module.ts b/packages/apps/reputation-oracle/server/src/modules/auth/auth.module.ts index 8921d5230b..97fd7b2ffa 100644 --- a/packages/apps/reputation-oracle/server/src/modules/auth/auth.module.ts +++ b/packages/apps/reputation-oracle/server/src/modules/auth/auth.module.ts @@ -7,12 +7,12 @@ import { UserModule } from '../user/user.module'; import { JwtHttpStrategy } from './strategy'; import { AuthService } from './auth.service'; import { AuthJwtController } from './auth.controller'; -import { AuthEntity } from './auth.entity'; import { TokenEntity } from './token.entity'; import { TokenRepository } from './token.repository'; -import { AuthRepository } from './auth.repository'; import { ConfigNames } from '../../common/config'; import { SendGridModule } from '../sendgrid/sendgrid.module'; +import { UserEntity } from '../user/user.entity'; +import { UserRepository } from '../user/user.repository'; import { Web3Module } from '../web3/web3.module'; @Module({ @@ -23,8 +23,9 @@ import { Web3Module } from '../web3/web3.module'; inject: [ConfigService], imports: [ConfigModule], useFactory: (configService: ConfigService) => ({ - secret: configService.get(ConfigNames.JWT_SECRET, 'secretkey'), + privateKey: configService.get(ConfigNames.JWT_PRIVATE_KEY), signOptions: { + algorithm: 'ES256', expiresIn: configService.get( ConfigNames.JWT_ACCESS_TOKEN_EXPIRES_IN, 3600, @@ -32,11 +33,11 @@ import { Web3Module } from '../web3/web3.module'; }, }), }), - TypeOrmModule.forFeature([AuthEntity, TokenEntity]), + TypeOrmModule.forFeature([TokenEntity, UserEntity]), SendGridModule, Web3Module, ], - providers: [JwtHttpStrategy, AuthService, AuthRepository, TokenRepository], + providers: [JwtHttpStrategy, AuthService, TokenRepository, UserRepository], controllers: [AuthJwtController], exports: [AuthService], }) diff --git a/packages/apps/reputation-oracle/server/src/modules/auth/auth.repository.ts b/packages/apps/reputation-oracle/server/src/modules/auth/auth.repository.ts deleted file mode 100644 index 55cc6f53a4..0000000000 --- a/packages/apps/reputation-oracle/server/src/modules/auth/auth.repository.ts +++ /dev/null @@ -1,64 +0,0 @@ -import { Injectable, Logger } from '@nestjs/common'; -import { InjectRepository } from '@nestjs/typeorm'; -import { - FindOptionsWhere, - FindManyOptions, - FindOneOptions, - Repository, - DeleteResult, - UpdateResult, -} from 'typeorm'; -import { AuthEntity } from './auth.entity'; -import { AuthCreateDto, AuthUpdateDto } from './auth.dto'; - -@Injectable() -export class AuthRepository { - private readonly logger = new Logger(AuthRepository.name); - - constructor( - @InjectRepository(AuthEntity) - private readonly authEntityRepository: Repository, - ) {} - - public async update( - where: FindOptionsWhere, - dto: Partial, - ): Promise { - return this.authEntityRepository.update(where, dto); - } - - public async findOne( - where: FindOptionsWhere, - options?: FindOneOptions, - ): Promise { - const authEntity = await this.authEntityRepository.findOne({ - where, - ...options, - }); - - return authEntity; - } - - public find( - where: FindOptionsWhere, - options?: FindManyOptions, - ): Promise { - return this.authEntityRepository.find({ - where, - order: { - createdAt: 'DESC', - }, - ...options, - }); - } - - public async create(dto: AuthCreateDto): Promise { - return this.authEntityRepository.create(dto).save(); - } - - public async delete( - where: FindOptionsWhere, - ): Promise { - return this.authEntityRepository.delete(where); - } -} diff --git a/packages/apps/reputation-oracle/server/src/modules/auth/auth.service.spec.ts b/packages/apps/reputation-oracle/server/src/modules/auth/auth.service.spec.ts index 932525faf4..3a34e07bdb 100644 --- a/packages/apps/reputation-oracle/server/src/modules/auth/auth.service.spec.ts +++ b/packages/apps/reputation-oracle/server/src/modules/auth/auth.service.spec.ts @@ -6,16 +6,11 @@ import { HttpService } from '@nestjs/axios'; import { createMock } from '@golevelup/ts-jest'; import { UserRepository } from '../user/user.repository'; import { JwtService } from '@nestjs/jwt'; -import { Repository } from 'typeorm'; -import { AuthEntity } from './auth.entity'; import { UserService } from '../user/user.service'; -import { getRepositoryToken } from '@nestjs/typeorm'; import { UserEntity } from '../user/user.entity'; -import { AuthRepository } from './auth.repository'; import { ErrorAuth } from '../../common/constants/errors'; import { MOCK_ACCESS_TOKEN, - MOCK_ACCESS_TOKEN_HASHED, MOCK_ADDRESS, MOCK_EMAIL, MOCK_EXPIRES_IN, @@ -23,24 +18,19 @@ import { MOCK_PASSWORD, MOCK_PRIVATE_KEY, MOCK_REFRESH_TOKEN, - MOCK_REFRESH_TOKEN_HASHED, } from '../../../test/constants'; -import { TokenType } from './token.entity'; +import { TokenEntity, TokenType } from './token.entity'; import { v4 } from 'uuid'; import { UserStatus, UserType } from '../../common/enums/user'; import { SendGridService } from '../sendgrid/sendgrid.service'; -import { - BadRequestException, - ConflictException, - NotFoundException, - UnauthorizedException, -} from '@nestjs/common'; +import { BadRequestException, ConflictException } from '@nestjs/common'; import { SENDGRID_TEMPLATES, SERVICE_NAME } from '../../common/constants'; import { getNonce, signMessage } from '../../common/utils/signature'; import { Web3Service } from '../web3/web3.service'; import { KVStoreClient, Role } from '@human-protocol/sdk'; import { PrepareSignatureDto, SignatureBodyDto } from '../web3/web3.dto'; import { SignatureType } from '../../common/enums/web3'; +import { AuthError } from './auth.error'; jest.mock('@human-protocol/sdk', () => ({ ...jest.requireActual('@human-protocol/sdk'), @@ -51,6 +41,9 @@ jest.mock('@human-protocol/sdk', () => ({ })), }, })); +jest.mock('../../common/utils/hcaptcha', () => ({ + verifyToken: jest.fn().mockReturnValue({ success: true }), +})); jest.mock('uuid', () => ({ v4: jest.fn().mockReturnValue('mocked-uuid'), @@ -60,7 +53,7 @@ describe('AuthService', () => { let authService: AuthService; let tokenRepository: TokenRepository; let userService: UserService; - let authRepository: AuthRepository; + let userRepository: UserRepository; let jwtService: JwtService; let sendGridService: SendGridService; let web3Service: Web3Service; @@ -84,17 +77,12 @@ describe('AuthService', () => { providers: [ AuthService, UserService, - { - provide: getRepositoryToken(AuthEntity), - useClass: Repository, - }, { provide: JwtService, useValue: { signAsync: jest.fn(), }, }, - { provide: AuthRepository, useValue: createMock() }, { provide: TokenRepository, useValue: createMock() }, { provide: UserRepository, useValue: createMock() }, { provide: ConfigService, useValue: mockConfigService }, @@ -103,19 +91,19 @@ describe('AuthService', () => { { provide: Web3Service, useValue: { + prepareSignatureBody: jest.fn(), getSigner: jest.fn().mockReturnValue(signerMock), signMessage: jest.fn(), - getOperatorAddress: jest.fn().mockReturnValue(MOCK_ADDRESS), - prepareSignatureBody: jest.fn(), + getOperatorAddress: jest.fn(), }, }, ], }).compile(); authService = moduleRef.get(AuthService); - authRepository = moduleRef.get(AuthRepository); - tokenRepository = moduleRef.get(TokenRepository); + tokenRepository = moduleRef.get(TokenRepository); userService = moduleRef.get(UserService); + userRepository = moduleRef.get(UserRepository); jwtService = moduleRef.get(JwtService); sendGridService = moduleRef.get(SendGridService); web3Service = moduleRef.get(Web3Service); @@ -186,7 +174,7 @@ describe('AuthService', () => { const userCreateDto = { email: MOCK_EMAIL, password: MOCK_PASSWORD, - type: UserType.WORKER, + hCaptchaToken: 'token', }; const userEntity: Partial = { @@ -195,20 +183,12 @@ describe('AuthService', () => { password: MOCK_HASHED_PASSWORD, }; - const tokenEntity = { - uuid: v4(), - tokenType: TokenType.EMAIL, - user: userEntity, - }; - - let createUserMock: any, createTokenMock: any; + let createUserMock: any; beforeEach(() => { createUserMock = jest.spyOn(userService, 'create'); - createTokenMock = jest.spyOn(tokenRepository, 'create'); createUserMock.mockResolvedValue(userEntity); - createTokenMock.mockResolvedValue(tokenEntity); }); afterEach(() => { @@ -219,9 +199,10 @@ describe('AuthService', () => { const result = await authService.signup(userCreateDto); expect(userService.create).toHaveBeenCalledWith(userCreateDto); - expect(tokenRepository.create).toHaveBeenCalledWith({ - tokenType: TokenType.EMAIL, + expect(tokenRepository.createUnique).toHaveBeenCalledWith({ + type: TokenType.EMAIL, user: userEntity, + expiresAt: expect.any(Date), }); expect(result).toBe(userEntity); }); @@ -235,71 +216,24 @@ describe('AuthService', () => { }); }); - describe('logout', () => { - let updateAuth: any; - const userEntity: Partial = { - id: 1, - }; - - const updateResult = {}; - - beforeEach(() => { - updateAuth = jest.spyOn(authRepository, 'update'); - updateAuth.mockResolvedValue(updateResult); - }); - - afterEach(() => { - jest.clearAllMocks(); - }); - - it('should delete the authentication entities based on email', async () => { - const result = await authService.logout(userEntity as UserEntity); - - const expectedUpdateQuery = { - userId: userEntity.id, - }; - - expect(authRepository.delete).toHaveBeenCalledWith(expectedUpdateQuery); - expect(result).toBe(undefined); - }); - }); - describe('auth', () => { + let jwtSignMock: any, findTokenMock: any; + const userEntity: Partial = { id: 1, email: 'user@example.com', }; - const authEntity: Partial = { - id: 1, + const tokenEntity: Partial = { + uuid: v4(), + type: TokenType.REFRESH, + user: userEntity as UserEntity, }; - let createAuthMock: any; - let updateAuthMock: any; - let jwtSignMock: any; - let hashTokenMock: any; - let logoutMock: any; beforeEach(() => { - createAuthMock = jest - .spyOn(authRepository, 'create' as any) - .mockResolvedValueOnce(authEntity); - - updateAuthMock = jest - .spyOn(authRepository, 'update' as any) - .mockResolvedValueOnce(authEntity); - jwtSignMock = jest .spyOn(jwtService, 'signAsync') - .mockResolvedValueOnce(MOCK_ACCESS_TOKEN) - .mockResolvedValueOnce(MOCK_REFRESH_TOKEN); - - hashTokenMock = jest - .spyOn(authService, 'hashToken') - .mockReturnValueOnce(MOCK_ACCESS_TOKEN_HASHED) - .mockReturnValueOnce(MOCK_REFRESH_TOKEN_HASHED); - logoutMock = jest - .spyOn(authService, 'logout' as any) - .mockResolvedValueOnce(undefined); + .mockResolvedValueOnce(MOCK_ACCESS_TOKEN); }); afterEach(() => { @@ -307,459 +241,477 @@ describe('AuthService', () => { }); it('should create authentication tokens and return them', async () => { - const findAuthMock = jest - .spyOn(authRepository, 'findOne' as any) - .mockResolvedValueOnce(undefined); + findTokenMock = jest + .spyOn(tokenRepository, 'findOneByUserIdAndType') + .mockResolvedValueOnce(null); + jest + .spyOn(web3Service, 'getOperatorAddress') + .mockReturnValueOnce(MOCK_ADDRESS); const result = await authService.auth(userEntity as UserEntity); - - expect(findAuthMock).toHaveBeenCalledWith({ userId: userEntity.id }); - expect(updateAuthMock).not.toHaveBeenCalled(); - expect(createAuthMock).toHaveBeenCalledWith({ - user: userEntity, - refreshToken: MOCK_REFRESH_TOKEN_HASHED, - accessToken: MOCK_ACCESS_TOKEN_HASHED, - }); - expect(jwtSignMock).toHaveBeenCalledWith({ - email: userEntity.email, - userId: userEntity.id, - }); + expect(findTokenMock).toHaveBeenCalledWith( + userEntity.id, + TokenType.REFRESH, + ); expect(jwtSignMock).toHaveBeenLastCalledWith( { email: userEntity.email, userId: userEntity.id, + address: userEntity.evmAddress, + kyc_status: userEntity.kyc?.status, + reputation_network: MOCK_ADDRESS, }, { - expiresIn: undefined, + expiresIn: MOCK_EXPIRES_IN, }, ); - expect(logoutMock).not.toHaveBeenCalled(); - expect(hashTokenMock).toHaveBeenCalledWith(MOCK_ACCESS_TOKEN); - expect(hashTokenMock).toHaveBeenLastCalledWith(MOCK_REFRESH_TOKEN); expect(result).toEqual({ accessToken: MOCK_ACCESS_TOKEN, - refreshToken: MOCK_REFRESH_TOKEN, + refreshToken: undefined, }); }); - it('should logout, create authentication tokens and return them', async () => { - const findAuthMock = jest - .spyOn(authRepository, 'findOne' as any) - .mockResolvedValueOnce(authEntity); + describe('forgotPassword', () => { + let findByEmailMock: any, findTokenMock: any; + let userEntity: Partial, tokenEntity: Partial; - const result = await authService.auth(userEntity as UserEntity); - - expect(findAuthMock).toHaveBeenCalledWith({ userId: userEntity.id }); - expect(updateAuthMock).not.toHaveBeenCalled(); - expect(createAuthMock).toHaveBeenCalledWith({ - user: userEntity, - refreshToken: MOCK_REFRESH_TOKEN_HASHED, - accessToken: MOCK_ACCESS_TOKEN_HASHED, - }); - expect(jwtSignMock).toHaveBeenCalledWith({ - email: userEntity.email, - userId: userEntity.id, - }); - expect(jwtSignMock).toHaveBeenLastCalledWith( - { - email: userEntity.email, - userId: userEntity.id, - }, - { - expiresIn: undefined, - }, - ); - expect(logoutMock).toHaveBeenCalled(); - expect(hashTokenMock).toHaveBeenCalledWith(MOCK_ACCESS_TOKEN); - expect(hashTokenMock).toHaveBeenLastCalledWith(MOCK_REFRESH_TOKEN); - expect(result).toEqual({ - accessToken: MOCK_ACCESS_TOKEN, - refreshToken: MOCK_REFRESH_TOKEN, + beforeEach(() => { + userEntity = { + id: 1, + email: 'user@example.com', + status: UserStatus.ACTIVE, + }; + tokenEntity = { + uuid: v4(), + type: TokenType.EMAIL, + user: userEntity as UserEntity, + }; + + findByEmailMock = jest.spyOn(userRepository, 'findByEmail'); + findTokenMock = jest.spyOn(tokenRepository, 'findOneByUserIdAndType'); + findByEmailMock.mockResolvedValue(userEntity); }); - }); - }); - describe('forgotPassword', () => { - const userEntity: Partial = { - id: 1, - email: 'user@example.com', - status: UserStatus.ACTIVE, - }; + afterEach(() => { + jest.clearAllMocks(); + }); - const tokenEntity = { - uuid: v4(), - tokenType: TokenType.EMAIL, - user: userEntity, - }; + it('should throw NotFound exception if user is not found', () => { + findByEmailMock.mockResolvedValue(null); + expect( + authService.forgotPassword({ email: 'user@example.com' }), + ).rejects.toThrow(AuthError); + }); - let createTokenMock: any; + it('should throw Unauthorized exception if user is not active', () => { + userEntity.status = UserStatus.INACTIVE; + findByEmailMock.mockResolvedValue(userEntity); + expect( + authService.forgotPassword({ email: 'user@example.com' }), + ).rejects.toThrow(AuthError); + }); - beforeEach(() => { - createTokenMock = jest.spyOn(tokenRepository, 'create'); + it('should remove existing token if it exists', async () => { + findTokenMock.mockResolvedValue(tokenEntity); + await authService.forgotPassword({ email: 'user@example.com' }); - createTokenMock.mockResolvedValue(tokenEntity); - }); + expect(tokenRepository.deleteOne).toHaveBeenCalled(); + }); - afterEach(() => { - jest.clearAllMocks(); + it('should create a new token and send email', async () => { + sendGridService.sendEmail = jest.fn(); + const email = 'user@example.com'; + + await authService.forgotPassword({ email }); + + expect(sendGridService.sendEmail).toHaveBeenCalledWith( + expect.objectContaining({ + personalizations: [ + { + dynamicTemplateData: { + service_name: SERVICE_NAME, + url: expect.stringContaining( + 'undefined/reset-password?token=', + ), + }, + to: email, + }, + ], + templateId: SENDGRID_TEMPLATES.resetPassword, + }), + ); + }); }); - it('should throw NotFound exception if user is not found', () => { - userService.getByEmail = jest.fn().mockResolvedValueOnce(undefined); - - expect( - authService.forgotPassword({ email: 'user@example.com' }), - ).rejects.toThrow(NotFoundException); - }); + describe('restorePassword', () => { + const userEntity: Partial = { + id: 1, + email: 'user@example.com', + }; - it('should throw Unauthorized exception if user is not active', () => { - userService.getByEmail = jest - .fn() - .mockResolvedValueOnce({ ...userEntity, status: UserStatus.PENDING }); + const tokenEntity: Partial = { + uuid: v4(), + type: TokenType.EMAIL, + user: userEntity as UserEntity, + }; - expect( - authService.forgotPassword({ email: 'user@example.com' }), - ).rejects.toThrow(UnauthorizedException); - }); + let findTokenMock: any; - it('should create a new token and send email', async () => { - userService.getByEmail = jest.fn().mockResolvedValueOnce(userEntity); + beforeEach(() => { + findTokenMock = jest.spyOn(tokenRepository, 'findOneByUuidAndType'); + sendGridService.sendEmail = jest.fn(); + }); - sendGridService.sendEmail = jest.fn(); - const email = 'user@example.com'; - - await authService.forgotPassword({ email }); - - expect(createTokenMock).toHaveBeenCalled(); - expect(sendGridService.sendEmail).toHaveBeenCalledWith( - expect.objectContaining({ - personalizations: [ - { - dynamicTemplateData: { - service_name: SERVICE_NAME, - url: expect.stringContaining( - 'undefined/reset-password?token=mocked-uuid', - ), - }, - to: email, - }, - ], - templateId: SENDGRID_TEMPLATES.resetPassword, - }), - ); - }); - }); + afterEach(() => { + jest.clearAllMocks(); + }); - describe('restorePassword', () => { - const userEntity: Partial = { - id: 1, - email: 'user@example.com', - }; + it('should throw an error if token is not found', () => { + findTokenMock.mockResolvedValue(null); - const tokenEntity = { - uuid: v4(), - tokenType: TokenType.EMAIL, - user: userEntity, - remove: jest.fn(), - }; + expect( + authService.restorePassword({ + token: 'token', + password: 'password', + hCaptchaToken: 'token', + }), + ).rejects.toThrow(AuthError); + }); - let findTokenMock: any; + it('should throw an error if token is expired', () => { + tokenEntity.expiresAt = new Date(new Date().getDate() - 1); + findTokenMock.mockResolvedValue(tokenEntity as TokenEntity); - beforeEach(() => { - findTokenMock = jest.spyOn(tokenRepository, 'findOne'); - }); + expect( + authService.restorePassword({ + token: 'token', + password: 'password', + hCaptchaToken: 'token', + }), + ).rejects.toThrow(AuthError); + }); - afterEach(() => { - jest.clearAllMocks(); - }); + it('should update password and send email', async () => { + tokenEntity.expiresAt = new Date( + new Date().setDate(new Date().getDate() + 1), + ); + findTokenMock.mockResolvedValue(tokenEntity as TokenEntity); + userService.updatePassword = jest.fn(); + sendGridService.sendEmail = jest.fn(); - it('should throw NotFound exception if token is not found', () => { - findTokenMock.mockResolvedValueOnce(undefined); + const updatePasswordMock = jest.spyOn(userService, 'updatePassword'); - expect( - authService.restorePassword({ + await authService.restorePassword({ token: 'token', password: 'password', - }), - ).rejects.toThrow(NotFoundException); - }); - - it('should update password and send email', async () => { - findTokenMock.mockResolvedValueOnce(tokenEntity); - - userService.updatePassword = jest.fn(); - sendGridService.sendEmail = jest.fn(); - - const updatePasswordMock = jest.spyOn(userService, 'updatePassword'); + hCaptchaToken: 'token', + }); - await authService.restorePassword({ - token: 'token', - password: 'password', + expect(updatePasswordMock).toHaveBeenCalled(); + expect(sendGridService.sendEmail).toHaveBeenCalled(); + expect(tokenRepository.deleteOne).toHaveBeenCalled(); }); - - expect(updatePasswordMock).toHaveBeenCalled(); - expect(sendGridService.sendEmail).toHaveBeenCalled(); - expect(tokenEntity.remove).toHaveBeenCalled(); }); - }); - - describe('emailVerification', () => { - const userEntity: Partial = { - id: 1, - email: 'user@example.com', - }; - - const tokenEntity = { - uuid: v4(), - tokenType: TokenType.EMAIL, - user: userEntity, - remove: jest.fn(), - }; - let findTokenMock: any; + describe('emailVerification', () => { + const userEntity: Partial = { + id: 1, + email: 'user@example.com', + }; - beforeEach(() => { - findTokenMock = jest.spyOn(tokenRepository, 'findOne'); - }); + const tokenEntity: Partial = { + uuid: v4(), + type: TokenType.EMAIL, + user: userEntity as UserEntity, + }; - afterEach(() => { - jest.clearAllMocks(); - }); + let findTokenMock: any; - it('should throw NotFound exception if token is not found', () => { - findTokenMock.mockResolvedValueOnce(undefined); + beforeEach(() => { + findTokenMock = jest.spyOn(tokenRepository, 'findOneByUuidAndType'); + }); - expect(authService.emailVerification({ token: 'token' })).rejects.toThrow( - NotFoundException, - ); - }); + afterEach(() => { + jest.clearAllMocks(); + }); - it('should activate user', async () => { - findTokenMock.mockResolvedValueOnce(tokenEntity); + it('should throw an error if token is not found', () => { + findTokenMock.mockResolvedValue(null); + expect( + authService.emailVerification({ token: 'token' }), + ).rejects.toThrow(AuthError); + }); + it('should throw an error if token is expired', () => { + tokenEntity.expiresAt = new Date(new Date().getDate() - 1); + findTokenMock.mockResolvedValue(tokenEntity as TokenEntity); + expect( + authService.emailVerification({ token: 'token' }), + ).rejects.toThrow(AuthError); + }); - userService.activate = jest.fn(); - const userActivateMock = jest.spyOn(userService, 'activate'); + it('should activate user', async () => { + tokenEntity.expiresAt = new Date( + new Date().setDate(new Date().getDate() + 1), + ); + findTokenMock.mockResolvedValue(tokenEntity as TokenEntity); + userRepository.updateOne = jest.fn(); - await authService.emailVerification({ token: 'token' }); + await authService.emailVerification({ token: 'token' }); - expect(userActivateMock).toHaveBeenCalled(); - expect(tokenEntity.remove).toHaveBeenCalled(); + expect(userRepository.updateOne).toHaveBeenCalled(); + expect(tokenEntity.user?.status).toBe(UserStatus.ACTIVE); + }); }); - }); - describe('resendEmailVerification', () => { - const userEntity: Partial = { - id: 1, - email: 'user@example.com', - status: UserStatus.PENDING, - }; - - let createTokenMock: any; + describe('resendEmailVerification', () => { + let findByEmailMock: any, + findTokenMock: any, + createTokenMock: any, + userEntity: Partial; - beforeEach(() => { - createTokenMock = jest.spyOn(tokenRepository, 'create'); - }); - - afterEach(() => { - jest.clearAllMocks(); - }); + beforeEach(() => { + userEntity = { + id: 1, + email: 'user@example.com', + status: UserStatus.PENDING, + }; + findByEmailMock = jest.spyOn(userRepository, 'findByEmail'); + findTokenMock = jest.spyOn(tokenRepository, 'findOneByUserIdAndType'); + createTokenMock = jest.spyOn(tokenRepository, 'createUnique'); + }); - it('should throw NotFound exception if user is not found', () => { - userService.getByEmail = jest.fn().mockResolvedValueOnce(undefined); + afterEach(() => { + jest.clearAllMocks(); + }); - expect( - authService.resendEmailVerification({ email: 'user@example.com' }), - ).rejects.toThrow(NotFoundException); - }); + it('should throw an error if user is not found', () => { + findByEmailMock.mockResolvedValue(null); + expect( + authService.resendEmailVerification({ email: 'user@example.com' }), + ).rejects.toThrow(AuthError); + }); - it('should create token and send email', async () => { - userService.getByEmail = jest.fn().mockResolvedValueOnce(userEntity); + it('should throw an error if user is not pending', () => { + userEntity.status = UserStatus.ACTIVE; + findByEmailMock.mockResolvedValue(userEntity); + expect( + authService.resendEmailVerification({ email: 'user@example.com' }), + ).rejects.toThrow(AuthError); + }); - sendGridService.sendEmail = jest.fn(); - const email = 'user@example.com'; - - await authService.resendEmailVerification({ email }); - - expect(createTokenMock).toHaveBeenCalled(); - expect(sendGridService.sendEmail).toHaveBeenCalledWith( - expect.objectContaining({ - personalizations: [ - { - dynamicTemplateData: { - service_name: SERVICE_NAME, - url: expect.stringContaining('/verify?token=mocked-uuid'), + it('should create token and send email', async () => { + findByEmailMock.mockResolvedValue(userEntity); + findTokenMock.mockResolvedValueOnce(null); + sendGridService.sendEmail = jest.fn(); + const email = 'user@example.com'; + + await authService.resendEmailVerification({ email }); + + expect(createTokenMock).toHaveBeenCalled(); + expect(sendGridService.sendEmail).toHaveBeenCalledWith( + expect.objectContaining({ + personalizations: [ + { + dynamicTemplateData: { + service_name: SERVICE_NAME, + url: expect.stringContaining('/verify?token='), + }, + to: email, }, - to: email, - }, - ], - templateId: SENDGRID_TEMPLATES.signup, - }), - ); + ], + templateId: SENDGRID_TEMPLATES.signup, + }), + ); + }); }); - }); - describe('web3auth', () => { - describe('signin', () => { - const nonce = getNonce(); - const nonce1 = getNonce(); + describe('web3auth', () => { + describe('signin', () => { + const nonce = getNonce(); + const nonce1 = getNonce(); - const userEntity: Partial = { - id: 1, - evmAddress: MOCK_ADDRESS, - nonce, - }; + const userEntity: Partial = { + id: 1, + evmAddress: MOCK_ADDRESS, + nonce, + }; - let getByAddressMock: any; - let updateNonceMock: any; + let getByAddressMock: any; + let updateNonceMock: any; - beforeEach(() => { - getByAddressMock = jest.spyOn(userService, 'getByAddress'); - updateNonceMock = jest.spyOn(userService, 'updateNonce'); + beforeEach(() => { + getByAddressMock = jest.spyOn(userService, 'getByAddress'); + updateNonceMock = jest.spyOn(userService, 'updateNonce'); - jest.spyOn(authService, 'auth').mockResolvedValue({ - accessToken: MOCK_ACCESS_TOKEN, - refreshToken: MOCK_REFRESH_TOKEN, + jest.spyOn(authService, 'auth').mockResolvedValue({ + accessToken: MOCK_ACCESS_TOKEN, + refreshToken: MOCK_REFRESH_TOKEN, + }); }); - }); - afterEach(() => { - jest.clearAllMocks(); - }); + afterEach(() => { + jest.clearAllMocks(); + }); - it('should sign in the user, reset nonce and return the JWT', async () => { - getByAddressMock.mockResolvedValue(userEntity as UserEntity); - updateNonceMock.mockResolvedValue({ - ...userEntity, - nonce: nonce1, - } as UserEntity); + it('should sign in the user, reset nonce and return the JWT', async () => { + getByAddressMock.mockResolvedValue(userEntity as UserEntity); + updateNonceMock.mockResolvedValue({ + ...userEntity, + nonce: nonce1, + } as UserEntity); - const signature = await signMessage(nonce, MOCK_PRIVATE_KEY); - const result = await authService.web3Signin({ - address: MOCK_ADDRESS, - signature, - }); + const signature = await signMessage(nonce, MOCK_PRIVATE_KEY); + const result = await authService.web3Signin({ + address: MOCK_ADDRESS, + signature, + }); - expect(userService.getByAddress).toHaveBeenCalledWith(MOCK_ADDRESS); - expect(userService.updateNonce).toHaveBeenCalledWith(userEntity); + expect(userService.getByAddress).toHaveBeenCalledWith(MOCK_ADDRESS); + expect(userService.updateNonce).toHaveBeenCalledWith(userEntity); - expect(authService.auth).toHaveBeenCalledWith(userEntity); - expect(result).toStrictEqual({ - accessToken: MOCK_ACCESS_TOKEN, - refreshToken: MOCK_REFRESH_TOKEN, + expect(authService.auth).toHaveBeenCalledWith(userEntity); + expect(result).toStrictEqual({ + accessToken: MOCK_ACCESS_TOKEN, + refreshToken: MOCK_REFRESH_TOKEN, + }); + }); + + it("should throw ConflictException if signature doesn't match", async () => { + const invalidSignature = await signMessage( + 'invalid message', + MOCK_PRIVATE_KEY, + ); + + await expect( + authService.web3Signin({ + address: MOCK_ADDRESS, + signature: invalidSignature, + }), + ).rejects.toThrow(ConflictException); }); }); - it("should throw ConflictException if signature doesn't match", async () => { - const invalidSignature = await signMessage( - 'invalid message', - MOCK_PRIVATE_KEY, - ); + describe('signup', () => { + const web3PreSignUpDto: PrepareSignatureDto = { + address: MOCK_ADDRESS, + type: SignatureType.SIGNUP, + }; - await expect( - authService.web3Signin({ - address: MOCK_ADDRESS, - signature: invalidSignature, - }), - ).rejects.toThrow(ConflictException); - }); - }); + const nonce = getNonce(); - describe('signup', () => { - const web3PreSignUpDto: PrepareSignatureDto = { - address: MOCK_ADDRESS, - type: SignatureType.SIGNUP, - }; + const userEntity: Partial = { + id: 1, + evmAddress: web3PreSignUpDto.address, + nonce, + }; - const nonce = getNonce(); + const preSignUpDataMock: SignatureBodyDto = { + from: MOCK_ADDRESS, + to: MOCK_ADDRESS, + contents: 'signup', + }; + let createUserMock: any; - const userEntity: Partial = { - id: 1, - evmAddress: web3PreSignUpDto.address, - nonce, - }; + beforeEach(() => { + createUserMock = jest.spyOn(userService, 'createWeb3User'); - const preSignUpDataMock: SignatureBodyDto = { - from: MOCK_ADDRESS, - to: MOCK_ADDRESS, - contents: 'signup', - }; - let createUserMock: any; + createUserMock.mockResolvedValue(userEntity); - beforeEach(() => { - createUserMock = jest.spyOn(userService, 'createWeb3User'); + jest.spyOn(authService, 'auth').mockResolvedValue({ + accessToken: MOCK_ACCESS_TOKEN, + refreshToken: MOCK_REFRESH_TOKEN, + }); - createUserMock.mockResolvedValue(userEntity); + jest + .spyOn(web3Service as any, 'prepareSignatureBody') + .mockReturnValue(preSignUpDataMock); + }); - jest.spyOn(authService, 'auth').mockResolvedValue({ - accessToken: MOCK_ACCESS_TOKEN, - refreshToken: MOCK_REFRESH_TOKEN, + afterEach(() => { + jest.clearAllMocks(); }); - jest - .spyOn(web3Service as any, 'prepareSignatureBody') - .mockReturnValue(preSignUpDataMock); - }); + it('should prepare the signature body and return it', async () => { + const signatureType = SignatureType.SIGNUP; + const address = '0xCf88b3f1992458C2f5a229573c768D0E9F70C44e'; - afterEach(() => { - jest.clearAllMocks(); - }); + const expectedResult = { + from: address, + to: '0xCf88b3f1992458C2f5a229573c768D0E9F70C44e', + contents: 'signup', + }; - it('should create a new web3 user and return the token', async () => { - (KVStoreClient.build as any).mockImplementationOnce(() => ({ - get: jest.fn().mockResolvedValue(Role.JobLauncher), - set: jest.fn(), - })); + jest + .spyOn(web3Service, 'prepareSignatureBody') + .mockReturnValue(expectedResult); - const signature = await signMessage( - preSignUpDataMock, - MOCK_PRIVATE_KEY, - ); + const result = web3Service.prepareSignatureBody( + signatureType, + address, + ); - const result = await authService.web3Signup({ - ...web3PreSignUpDto, - type: UserType.WORKER, - signature, + expect(result).toEqual(expectedResult); }); - expect(userService.createWeb3User).toHaveBeenCalledWith( - web3PreSignUpDto.address, - UserType.WORKER, - ); + it('should create a new web3 user and return the token', async () => { + (KVStoreClient.build as any).mockImplementationOnce(() => ({ + get: jest.fn().mockResolvedValue(Role.JobLauncher), + set: jest.fn(), + })); - expect(authService.auth).toHaveBeenCalledWith(userEntity); - expect(result).toStrictEqual({ - accessToken: MOCK_ACCESS_TOKEN, - refreshToken: MOCK_REFRESH_TOKEN, - }); - }); + const signature = await signMessage( + preSignUpDataMock, + MOCK_PRIVATE_KEY, + ); - it("should throw ConflictException if signature doesn't match", async () => { - const invalidSignature = await signMessage( - 'invalid message', - MOCK_PRIVATE_KEY, - ); - - await expect( - authService.web3Signup({ - ...web3PreSignUpDto, + const result = await authService.web3Signup({ + address: web3PreSignUpDto.address, type: UserType.WORKER, - signature: invalidSignature, - }), - ).rejects.toThrow(ConflictException); - }); - it('should throw BadRequestException if role is not in KVStore', async () => { - const signature = await signMessage( - preSignUpDataMock, - MOCK_PRIVATE_KEY, - ); + signature, + }); + + expect(userService.createWeb3User).toHaveBeenCalledWith( + expect.objectContaining({ + evmAddress: web3PreSignUpDto.address, + }), + web3PreSignUpDto.address, + ); + + expect(authService.auth).toHaveBeenCalledWith(userEntity); + expect(result).toStrictEqual({ + accessToken: MOCK_ACCESS_TOKEN, + refreshToken: MOCK_REFRESH_TOKEN, + }); + }); - await expect( - authService.web3Signup({ - ...web3PreSignUpDto, - type: UserType.WORKER, - signature: signature, - }), - ).rejects.toThrow(BadRequestException); + it("should throw ConflictException if signature doesn't match", async () => { + const invalidSignature = await signMessage( + 'invalid message', + MOCK_PRIVATE_KEY, + ); + + await expect( + authService.web3Signup({ + ...web3PreSignUpDto, + type: UserType.WORKER, + signature: invalidSignature, + }), + ).rejects.toThrow(ConflictException); + }); + it('should throw BadRequestException if role is not in KVStore', async () => { + const signature = await signMessage( + preSignUpDataMock, + MOCK_PRIVATE_KEY, + ); + + await expect( + authService.web3Signup({ + ...web3PreSignUpDto, + type: UserType.WORKER, + signature: signature, + }), + ).rejects.toThrow(BadRequestException); + }); }); }); }); diff --git a/packages/apps/reputation-oracle/server/src/modules/auth/auth.service.ts b/packages/apps/reputation-oracle/server/src/modules/auth/auth.service.ts index 22ece25ecf..788ac3a1a0 100644 --- a/packages/apps/reputation-oracle/server/src/modules/auth/auth.service.ts +++ b/packages/apps/reputation-oracle/server/src/modules/auth/auth.service.ts @@ -10,12 +10,13 @@ import { JwtService } from '@nestjs/jwt'; import { ErrorAuth, ErrorUser } from '../../common/constants/errors'; import { UserStatus } from '../../common/enums/user'; -import { UserCreateDto } from '../user/user.dto'; +import { UserCreateDto, Web3UserCreateDto } from '../user/user.dto'; import { UserEntity } from '../user/user.entity'; import { UserService } from '../user/user.service'; import { AuthDto, ForgotPasswordDto, + RefreshDto, ResendEmailVerificationDto, RestorePasswordDto, SignInDto, @@ -23,9 +24,8 @@ import { Web3SignInDto, Web3SignUpDto, } from './auth.dto'; -import { TokenType } from './token.entity'; +import { TokenEntity, TokenType } from './token.entity'; import { TokenRepository } from './token.repository'; -import { AuthRepository } from './auth.repository'; import { ConfigNames } from '../../common/config'; import { verifySignature } from '../../common/utils/signature'; import { createHash } from 'crypto'; @@ -34,39 +34,68 @@ import { SENDGRID_TEMPLATES, SERVICE_NAME } from '../../common/constants'; import { Web3Service } from '../web3/web3.service'; import { ChainId, KVStoreClient, KVStoreKeys, Role } from '@human-protocol/sdk'; import { SignatureType, Web3Env } from '../../common/enums/web3'; +import { UserRepository } from '../user/user.repository'; +import { AuthError } from './auth.error'; @Injectable() export class AuthService { private readonly logger = new Logger(AuthService.name); - private readonly refreshTokenExpiresIn: string; - private readonly salt: string; + private readonly refreshTokenExpiresIn: number; + private readonly accessTokenExpiresIn: number; + private readonly verifyEmailTokenExpiresIn: number; + private readonly forgotPasswordTokenExpiresIn: number; private readonly feURL: string; + private readonly salt: string; constructor( private readonly jwtService: JwtService, private readonly userService: UserService, private readonly tokenRepository: TokenRepository, - private readonly authRepository: AuthRepository, private readonly configService: ConfigService, private readonly sendgridService: SendGridService, private readonly web3Service: Web3Service, + private readonly userRepository: UserRepository, ) { - this.refreshTokenExpiresIn = this.configService.get( - ConfigNames.JWT_REFRESH_TOKEN_EXPIRES_IN, - '100000000', + this.refreshTokenExpiresIn = this.configService.get( + ConfigNames.REFRESH_TOKEN_EXPIRES_IN, + 3600000, + ); + + this.accessTokenExpiresIn = this.configService.get( + ConfigNames.JWT_ACCESS_TOKEN_EXPIRES_IN, + 300000, ); - this.salt = this.configService.get( - ConfigNames.HASH_SECRET, - 'a328af3fc1dad15342cc3d68936008fa', + this.verifyEmailTokenExpiresIn = this.configService.get( + ConfigNames.VERIFY_EMAIL_TOKEN_EXPIRES_IN, + 1800000, ); + + this.forgotPasswordTokenExpiresIn = this.configService.get( + ConfigNames.FORGOT_PASSWORD_TOKEN_EXPIRES_IN, + 1800000, + ); + this.feURL = this.configService.get( ConfigNames.FE_URL, 'http://localhost:3005', ); } - public async signin(data: SignInDto): Promise { + public async signin(data: SignInDto, ip?: string): Promise { + // if ( + // !( + // await verifyToken( + // this.configService.get(ConfigNames.HCAPTCHA_EXCHANGE_URL)!, + // this.configService.get(ConfigNames.HCAPTCHA_SITE_KEY)!, + // this.configService.get(ConfigNames.HCAPTCHA_SECRET)!, + // data.hCaptchaToken, + // ip, + // ) + // ).success + // ) { + // throw new UnauthorizedException(ErrorAuth.InvalidCaptchaToken); + // } const userEntity = await this.userService.getByCredentials( data.email, data.password, @@ -83,13 +112,31 @@ export class AuthService { return this.auth(userEntity); } - public async signup(data: UserCreateDto): Promise { + public async signup(data: UserCreateDto, ip?: string): Promise { + // if ( + // !( + // await verifyToken( + // this.configService.get(ConfigNames.HCAPTCHA_SITE_KEY)!, + // this.configService.get(ConfigNames.HCAPTCHA_EXCHANGE_URL)!, + // this.configService.get(ConfigNames.HCAPTCHA_SECRET)!, + // data.hCaptchaToken, + // ip, + // ) + // ).success + // ) { + // throw new UnauthorizedException(ErrorAuth.InvalidCaptchaToken); + // } const userEntity = await this.userService.create(data); - const tokenEntity = await this.tokenRepository.create({ - tokenType: TokenType.EMAIL, - user: userEntity, - }); + const tokenEntity = new TokenEntity(); + tokenEntity.type = TokenType.EMAIL; + tokenEntity.user = userEntity; + const date = new Date(); + tokenEntity.expiresAt = new Date( + date.getTime() + this.verifyEmailTokenExpiresIn, + ); + + await this.tokenRepository.createUnique(tokenEntity); await this.sendgridService.sendEmail({ personalizations: [ @@ -107,71 +154,89 @@ export class AuthService { return userEntity; } - public async logout(user: UserEntity): Promise { - await this.authRepository.delete({ userId: user.id }); + public async refresh(data: RefreshDto): Promise { + const tokenEntity = await this.tokenRepository.findOneByUuidAndType( + data.refreshToken, + TokenType.REFRESH, + ); + + if (!tokenEntity) { + throw new AuthError(ErrorAuth.InvalidToken); + } + + if (new Date() > tokenEntity.expiresAt) { + throw new AuthError(ErrorAuth.TokenExpired); + } + + return this.auth(tokenEntity.user); } public async auth(userEntity: UserEntity): Promise { - const auth = await this.authRepository.findOne({ userId: userEntity.id }); - - const accessToken = await this.jwtService.signAsync({ - email: userEntity.email, - evmAddress: userEntity.evmAddress, - userId: userEntity.id, - kycStatus: userEntity.kyc?.status, - }); + const refreshTokenEntity = + await this.tokenRepository.findOneByUserIdAndType( + userEntity.id, + TokenType.REFRESH, + ); - const refreshToken = await this.jwtService.signAsync( + const accessToken = await this.jwtService.signAsync( { email: userEntity.email, - evmAddress: userEntity.evmAddress, userId: userEntity.id, - kycStatus: userEntity.kyc?.status, + address: userEntity.evmAddress, + kyc_status: userEntity.kyc?.status, + reputation_network: this.web3Service.getOperatorAddress(), }, { - expiresIn: this.refreshTokenExpiresIn, + expiresIn: this.accessTokenExpiresIn, }, ); - const accessTokenHashed = this.hashToken(accessToken); - const refreshTokenHashed = this.hashToken(refreshToken); - - if (auth) { - await this.logout(userEntity); + if (refreshTokenEntity) { + await this.tokenRepository.deleteOne(refreshTokenEntity); } - await this.authRepository.create({ - user: userEntity, - refreshToken: refreshTokenHashed, - accessToken: accessTokenHashed, - }); + const newRefreshTokenEntity = new TokenEntity(); + newRefreshTokenEntity.user = userEntity; + newRefreshTokenEntity.type = TokenType.REFRESH; + const date = new Date(); + newRefreshTokenEntity.expiresAt = new Date( + date.getTime() + this.refreshTokenExpiresIn, + ); + + await this.tokenRepository.createUnique(newRefreshTokenEntity); - return { accessToken, refreshToken }; + return { accessToken, refreshToken: newRefreshTokenEntity.uuid }; } public async forgotPassword(data: ForgotPasswordDto): Promise { - const userEntity = await this.userService.getByEmail(data.email); + const userEntity = await this.userRepository.findByEmail(data.email); if (!userEntity) { - throw new NotFoundException(ErrorUser.NotFound); + throw new AuthError(ErrorUser.NotFound); } - if (userEntity.status !== UserStatus.ACTIVE) - throw new UnauthorizedException(ErrorAuth.UserNotActive); + if (userEntity.status !== UserStatus.ACTIVE) { + throw new AuthError(ErrorUser.UserNotActive); + } - const existingToken = await this.tokenRepository.findOne({ - userId: userEntity.id, - tokenType: TokenType.PASSWORD, - }); + const existingToken = await this.tokenRepository.findOneByUserIdAndType( + userEntity.id, + TokenType.PASSWORD, + ); if (existingToken) { - await existingToken.remove(); + await this.tokenRepository.deleteOne(existingToken); } - const newTokenEntity = await this.tokenRepository.create({ - tokenType: TokenType.PASSWORD, - user: userEntity, - }); + const tokenEntity = new TokenEntity(); + tokenEntity.type = TokenType.PASSWORD; + tokenEntity.user = userEntity; + const date = new Date(); + tokenEntity.expiresAt = new Date( + date.getTime() + this.forgotPasswordTokenExpiresIn, + ); + + await this.tokenRepository.createUnique(tokenEntity); await this.sendgridService.sendEmail({ personalizations: [ @@ -179,7 +244,7 @@ export class AuthService { to: data.email, dynamicTemplateData: { service_name: SERVICE_NAME, - url: `${this.feURL}/reset-password?token=${newTokenEntity.uuid}`, + url: `${this.feURL}/reset-password?token=${tokenEntity.uuid}`, }, }, ], @@ -187,14 +252,35 @@ export class AuthService { }); } - public async restorePassword(data: RestorePasswordDto): Promise { - const tokenEntity = await this.tokenRepository.findOne({ - uuid: data.token, - tokenType: TokenType.PASSWORD, - }); + public async restorePassword( + data: RestorePasswordDto, + ip?: string, + ): Promise { + // if ( + // !( + // await verifyToken( + // this.configService.get(ConfigNames.HCAPTCHA_EXCHANGE_URL)!, + // this.configService.get(ConfigNames.HCAPTCHA_SITE_KEY)!, + // this.configService.get(ConfigNames.HCAPTCHA_SECRET)!, + // data.hCaptchaToken, + // ip, + // ) + // ).success + // ) { + // throw new UnauthorizedException(ErrorAuth.InvalidCaptchaToken); + // } + + const tokenEntity = await this.tokenRepository.findOneByUuidAndType( + data.token, + TokenType.PASSWORD, + ); if (!tokenEntity) { - throw new NotFoundException('Token not found'); + throw new AuthError(ErrorAuth.InvalidToken); + } + + if (new Date() > tokenEntity.expiresAt) { + throw new AuthError(ErrorAuth.TokenExpired); } await this.userService.updatePassword(tokenEntity.user, data); @@ -210,47 +296,54 @@ export class AuthService { templateId: SENDGRID_TEMPLATES.passwordChanged, }); - await tokenEntity.remove(); - - return true; + await this.tokenRepository.deleteOne(tokenEntity); } public async emailVerification(data: VerifyEmailDto): Promise { - const tokenEntity = await this.tokenRepository.findOne({ - uuid: data.token, - tokenType: TokenType.EMAIL, - }); + const tokenEntity = await this.tokenRepository.findOneByUuidAndType( + data.token, + TokenType.EMAIL, + ); if (!tokenEntity) { - throw new NotFoundException('Token not found'); + throw new AuthError(ErrorAuth.NotFound); } - this.userService.activate(tokenEntity.user); - await tokenEntity.remove(); + if (new Date() > tokenEntity.expiresAt) { + throw new AuthError(ErrorAuth.TokenExpired); + } + + tokenEntity.user.status = UserStatus.ACTIVE; + await this.userRepository.updateOne(tokenEntity.user); } public async resendEmailVerification( data: ResendEmailVerificationDto, ): Promise { - const userEntity = await this.userService.getByEmail(data.email); + const userEntity = await this.userRepository.findByEmail(data.email); if (!userEntity || userEntity?.status != UserStatus.PENDING) { - throw new NotFoundException(ErrorUser.NotFound); + throw new AuthError(ErrorUser.NotFound); } - const existingToken = await this.tokenRepository.findOne({ - userId: userEntity.id, - tokenType: TokenType.EMAIL, - }); + const existingToken = await this.tokenRepository.findOneByUserIdAndType( + userEntity.id, + TokenType.EMAIL, + ); if (existingToken) { await existingToken.remove(); } - const newTokenEntity = await this.tokenRepository.create({ - tokenType: TokenType.EMAIL, - user: userEntity, - }); + const tokenEntity = new TokenEntity(); + tokenEntity.type = TokenType.EMAIL; + tokenEntity.user = userEntity; + const date = new Date(); + tokenEntity.expiresAt = new Date( + date.getTime() + this.verifyEmailTokenExpiresIn, + ); + + await this.tokenRepository.createUnique(tokenEntity); await this.sendgridService.sendEmail({ personalizations: [ @@ -258,7 +351,7 @@ export class AuthService { to: data.email, dynamicTemplateData: { service_name: SERVICE_NAME, - url: `${this.feURL}/verify?token=${newTokenEntity.uuid}`, + url: `${this.feURL}/verify?token=${tokenEntity.uuid}`, }, }, ], @@ -309,10 +402,16 @@ export class AuthService { ) { throw new BadRequestException(ErrorAuth.InvalidRole); } + const nonce = await this.getNonce(data.address); + + const web3UserCreateDto: Web3UserCreateDto = { + evmAddress: data.address, + nonce: nonce, + }; const userEntity = await this.userService.createWeb3User( + web3UserCreateDto, data.address, - data.type, ); await kvstore.set(data.address, 'ACTIVE'); diff --git a/packages/apps/reputation-oracle/server/src/modules/auth/strategy/jwt.http.ts b/packages/apps/reputation-oracle/server/src/modules/auth/strategy/jwt.http.ts index 651072be6b..35190d5539 100644 --- a/packages/apps/reputation-oracle/server/src/modules/auth/strategy/jwt.http.ts +++ b/packages/apps/reputation-oracle/server/src/modules/auth/strategy/jwt.http.ts @@ -4,66 +4,42 @@ import { Injectable, Req, UnauthorizedException } from '@nestjs/common'; import { ConfigService } from '@nestjs/config'; import { UserEntity } from '../../user/user.entity'; +import { RESEND_EMAIL_VERIFICATION_PATH } from '../../../common/constants'; import { UserStatus } from '../../../common/enums/user'; import { ConfigNames } from '../../../common/config'; -import { AuthRepository } from '../auth.repository'; -import { AuthService } from '../auth.service'; -import { JWT_PREFIX } from '../../../common/constants'; +import { UserRepository } from '../../user/user.repository'; @Injectable() export class JwtHttpStrategy extends PassportStrategy(Strategy, 'jwt-http') { constructor( - private readonly authRepository: AuthRepository, - private readonly authService: AuthService, + private readonly userRepository: UserRepository, private readonly configService: ConfigService, ) { super({ jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), ignoreExpiration: false, - secretOrKey: configService.get( - ConfigNames.JWT_SECRET, - 'secretkey', - ), + secretOrKey: configService.get(ConfigNames.JWT_PUBLIC_KEY, ''), passReqToCallback: true, }); } public async validate( @Req() request: any, - payload: { email: string; userId: number }, + payload: { userId: number }, ): Promise { - const auth = await this.authRepository.findOne( - { - userId: payload.userId, - }, - { - relations: ['user', 'user.kyc'], - }, - ); + const user = await this.userRepository.findById(payload.userId); - if (!auth?.user) { + if (!user) { throw new UnauthorizedException('User not found'); } - if (auth?.user.status !== UserStatus.ACTIVE) { + if ( + user.status !== UserStatus.ACTIVE && + request.url !== RESEND_EMAIL_VERIFICATION_PATH + ) { throw new UnauthorizedException('User not active'); } - //check that the jwt exists in the database - let jwt = request.headers['authorization'] as string; - if (jwt.toLowerCase().substring(0, JWT_PREFIX.length) === JWT_PREFIX) { - jwt = jwt.substring(JWT_PREFIX.length); - } - if (request.url === '/auth/refresh') { - if (!this.authService.compareToken(jwt, auth?.refreshToken)) { - throw new UnauthorizedException('Token expired'); - } - } else { - if (!this.authService.compareToken(jwt, auth?.accessToken)) { - throw new UnauthorizedException('Token expired'); - } - } - - return auth?.user; + return user; } } diff --git a/packages/apps/reputation-oracle/server/src/modules/auth/token.entity.ts b/packages/apps/reputation-oracle/server/src/modules/auth/token.entity.ts index de132308a4..964544f555 100644 --- a/packages/apps/reputation-oracle/server/src/modules/auth/token.entity.ts +++ b/packages/apps/reputation-oracle/server/src/modules/auth/token.entity.ts @@ -1,4 +1,11 @@ -import { Column, Entity, Generated, JoinColumn, OneToOne } from 'typeorm'; +import { + Column, + Entity, + Generated, + Index, + JoinColumn, + ManyToOne, +} from 'typeorm'; import { UserEntity } from '../user/user.entity'; import { BaseEntity } from '../../database/base.entity'; @@ -8,14 +15,16 @@ import { IBase } from '../../common/interfaces/base'; export enum TokenType { EMAIL = 'EMAIL', PASSWORD = 'PASSWORD', + REFRESH = 'REFRESH', } export interface IToken extends IBase { uuid: string; - tokenType: TokenType; + type: TokenType; } @Entity({ schema: NS, name: 'tokens' }) +@Index(['type', 'userId'], { unique: true }) export class TokenEntity extends BaseEntity implements IToken { @Column({ type: 'uuid', unique: true }) @Generated('uuid') @@ -25,10 +34,13 @@ export class TokenEntity extends BaseEntity implements IToken { type: 'enum', enum: TokenType, }) - public tokenType: TokenType; + public type: TokenType; + + @Column({ type: 'timestamptz' }) + public expiresAt: Date; @JoinColumn() - @OneToOne(() => UserEntity) + @ManyToOne(() => UserEntity, { eager: true }) public user: UserEntity; @Column({ type: 'int' }) diff --git a/packages/apps/reputation-oracle/server/src/modules/auth/token.repository.ts b/packages/apps/reputation-oracle/server/src/modules/auth/token.repository.ts index 7a8ff266da..e349d22fa8 100644 --- a/packages/apps/reputation-oracle/server/src/modules/auth/token.repository.ts +++ b/packages/apps/reputation-oracle/server/src/modules/auth/token.repository.ts @@ -1,30 +1,48 @@ -import { Injectable, Logger } from '@nestjs/common'; -import { InjectRepository } from '@nestjs/typeorm'; -import { FindOptionsWhere, Repository } from 'typeorm'; -import { TokenEntity } from './token.entity'; -import { TokenCreateDto } from './auth.dto'; +import { Injectable } from '@nestjs/common'; +import { BaseRepository } from '../../database/base.repository'; +import { DataSource, DeleteResult } from 'typeorm'; +import { TokenEntity, TokenType } from './token.entity'; @Injectable() -export class TokenRepository { - private readonly logger = new Logger(TokenRepository.name); +export class TokenRepository extends BaseRepository { + constructor(private dataSource: DataSource) { + super(TokenEntity, dataSource); + } - constructor( - @InjectRepository(TokenEntity) - private readonly tokenEntityRepository: Repository, - ) {} + public async findOneByUuidAndType( + uuid: string, + type: TokenType, + ): Promise { + return this.findOne({ + where: { + uuid, + type, + }, + relations: ['user'], + }); + } - public async findOne( - where: FindOptionsWhere, + public async findOneByUserIdAndType( + userId: number, + type: TokenType, ): Promise { - const tokenEntity = await this.tokenEntityRepository.findOne({ - where, + return this.findOne({ + where: { + userId, + type, + }, relations: ['user'], }); + } - return tokenEntity; + public async deleteOne(token: TokenEntity): Promise { + return this.delete({ id: token.id }); } - public async create(dto: TokenCreateDto): Promise { - return this.tokenEntityRepository.create(dto).save(); + public async deleteOneByTypeAndUserId( + type: TokenType, + userId: number, + ): Promise { + return this.delete({ type, userId }); } } diff --git a/packages/apps/reputation-oracle/server/src/modules/cron-job/cron-job.service.ts b/packages/apps/reputation-oracle/server/src/modules/cron-job/cron-job.service.ts index b4fb38217a..cea2ffe557 100644 --- a/packages/apps/reputation-oracle/server/src/modules/cron-job/cron-job.service.ts +++ b/packages/apps/reputation-oracle/server/src/modules/cron-job/cron-job.service.ts @@ -92,7 +92,6 @@ export class CronJobService { * Process a pending webhook job. * @returns {Promise} - Returns a promise that resolves when the operation is complete. */ - @Cron(CronExpression.EVERY_10_MINUTES) public async processPendingWebhooks(): Promise { const isCronJobRunning = await this.isCronJobRunning( CronJobType.ProcessPendingWebhook, @@ -140,7 +139,6 @@ export class CronJobService { * Process a paid webhook job. * @returns {Promise} - Returns a promise that resolves when the operation is complete. */ - @Cron(CronExpression.EVERY_10_MINUTES) public async processPaidWebhooks(): Promise { const isCronJobRunning = await this.isCronJobRunning( CronJobType.ProcessPaidWebhook, diff --git a/packages/apps/reputation-oracle/server/src/modules/user/user.dto.ts b/packages/apps/reputation-oracle/server/src/modules/user/user.dto.ts index 2c81c4702a..b5b1c03472 100644 --- a/packages/apps/reputation-oracle/server/src/modules/user/user.dto.ts +++ b/packages/apps/reputation-oracle/server/src/modules/user/user.dto.ts @@ -11,14 +11,13 @@ export class UserCreateDto extends ValidatePasswordDto { @Transform(({ value }: { value: string }) => value.toLowerCase()) public email: string; - @ApiProperty({ - enum: UserType, - }) - @IsEnum(UserType) - public type: UserType; + @ApiProperty({ name: 'h_captcha_token' }) + @IsString() + public hCaptchaToken: string; } export class UserDto extends UserCreateDto { + public type: UserType; public status: UserStatus; } @@ -30,15 +29,10 @@ export class Web3UserCreateDto { @ApiProperty() @IsString() public nonce: string; - - @ApiProperty({ - enum: UserType, - }) - @IsEnum(UserType) - public type: UserType; } export class Web3UserDto extends Web3UserCreateDto { + public type: UserType; public status: UserStatus; } diff --git a/packages/apps/reputation-oracle/server/src/modules/user/user.entity.ts b/packages/apps/reputation-oracle/server/src/modules/user/user.entity.ts index fe98b4d327..0e652a333b 100644 --- a/packages/apps/reputation-oracle/server/src/modules/user/user.entity.ts +++ b/packages/apps/reputation-oracle/server/src/modules/user/user.entity.ts @@ -5,7 +5,6 @@ import { NS } from '../../common/constants'; import { UserStatus, UserType } from '../../common/enums/user'; import { IUser } from '../../common/interfaces'; import { BaseEntity } from '../../database/base.entity'; -import { AuthEntity } from '../auth/auth.entity'; import { TokenEntity } from '../auth/token.entity'; import { KycEntity } from '../kyc/kyc.entity'; @@ -33,9 +32,6 @@ export class UserEntity extends BaseEntity implements IUser { }) public status: UserStatus; - @OneToOne(() => AuthEntity) - public auth: AuthEntity; - @OneToOne(() => TokenEntity) public token: TokenEntity; diff --git a/packages/apps/reputation-oracle/server/src/modules/user/user.repository.ts b/packages/apps/reputation-oracle/server/src/modules/user/user.repository.ts index a3ca2d7d1f..fd1aeeb26d 100644 --- a/packages/apps/reputation-oracle/server/src/modules/user/user.repository.ts +++ b/packages/apps/reputation-oracle/server/src/modules/user/user.repository.ts @@ -1,70 +1,25 @@ -import { Injectable, Logger, NotFoundException } from '@nestjs/common'; -import { InjectRepository } from '@nestjs/typeorm'; -import { - FindOptionsWhere, - FindManyOptions, - FindOneOptions, - Repository, -} from 'typeorm'; - +import { Injectable } from '@nestjs/common'; +import { DataSource } from 'typeorm'; +import { BaseRepository } from '../../database/base.repository'; import { UserEntity } from './user.entity'; -import { UserDto, UserUpdateDto, Web3UserDto } from './user.dto'; -import { ErrorUser } from '../../common/constants/errors'; @Injectable() -export class UserRepository { - private readonly logger = new Logger(UserRepository.name); - - constructor( - @InjectRepository(UserEntity) - private readonly userEntityRepository: Repository, - ) {} - - public async updateOne( - where: FindOptionsWhere, - dto: Partial, - ): Promise { - const userEntity = await this.userEntityRepository.findOneBy(where); - - if (!userEntity) { - this.logger.log(ErrorUser.NotFound, UserRepository.name); - throw new NotFoundException(ErrorUser.NotFound); - } - - Object.assign(userEntity, dto); - return userEntity.save(); +export class UserRepository extends BaseRepository { + constructor(private dataSource: DataSource) { + super(UserEntity, dataSource); } - public async findOne( - where: FindOptionsWhere, - options?: FindOneOptions, - ): Promise { - const userEntity = await this.userEntityRepository.findOne({ - where, - ...options, - }); - - return userEntity; + async findById(id: number): Promise { + return this.findOne({ where: { id }, relations: { kyc: true } }); } - public find( - where: FindOptionsWhere, - options?: FindManyOptions, - ): Promise { - return this.userEntityRepository.find({ - where, - order: { - createdAt: 'DESC', - }, - ...options, - }); + async findByEmail(email: string): Promise { + return this.findOne({ where: { email }, relations: { kyc: true } }); } - public async create(dto: UserDto): Promise { - return this.userEntityRepository.create(dto).save(); - } - - public async createWeb3User(dto: Web3UserDto): Promise { - return this.userEntityRepository.create(dto).save(); + public async findOneByEvmAddress( + evmAddress: string, + ): Promise { + return this.findOne({ where: { evmAddress } }); } } diff --git a/packages/apps/reputation-oracle/server/src/modules/user/user.service.spec.ts b/packages/apps/reputation-oracle/server/src/modules/user/user.service.spec.ts index 84cc81fc0a..1fd1c9ada1 100644 --- a/packages/apps/reputation-oracle/server/src/modules/user/user.service.spec.ts +++ b/packages/apps/reputation-oracle/server/src/modules/user/user.service.spec.ts @@ -1,14 +1,9 @@ import { Test } from '@nestjs/testing'; -import { - BadRequestException, - ConflictException, - NotFoundException, -} from '@nestjs/common'; +import { BadRequestException, ConflictException } from '@nestjs/common'; import { createMock } from '@golevelup/ts-jest'; -import { ErrorUser } from '../../common/constants/errors'; import { UserRepository } from './user.repository'; import { UserService } from './user.service'; -import { UserCreateDto, UserUpdateDto } from './user.dto'; +import { UserCreateDto } from './user.dto'; import { UserEntity } from './user.entity'; import { KycStatus, @@ -16,7 +11,7 @@ import { UserStatus, UserType, } from '../../common/enums/user'; -import { getNonce, signMessage } from '../../common/utils/signature'; +import { signMessage } from '../../common/utils/signature'; import { MOCK_ADDRESS, MOCK_PRIVATE_KEY } from '../../../test/constants'; import { Web3Service } from '../web3/web3.service'; import { DeepPartial } from 'typeorm'; @@ -82,85 +77,28 @@ describe('UserService', () => { web3Service = moduleRef.get(Web3Service); }); - describe('update', () => { - it('should update a user and return the updated user entity', async () => { - const userId = 1; - const dto: UserUpdateDto = { - email: 'test@example.com', - status: UserStatus.ACTIVE, - }; - - const updatedUser: Partial = { - id: userId, - email: dto.email, - status: dto.status, - }; - - jest - .spyOn(userRepository, 'updateOne') - .mockResolvedValue(updatedUser as UserEntity); - - const result = await userService.update(userId, dto); - - expect(userRepository.updateOne).toHaveBeenCalledWith( - { id: userId }, - dto, - ); - expect(result).toBe(updatedUser); - }); - }); - describe('create', () => { it('should create a new user and return the created user entity', async () => { const dto: UserCreateDto = { email: 'test@example.com', password: 'password123', - type: UserType.WORKER, + hCaptchaToken: 'test', }; - const hashedPassword = - '$2b$12$Z02o9/Ay7CT0n99icApZYORH8iJI9VGtl3mju7d0c4SdDDujhSzOa'; const createdUser: Partial = { - id: 1, email: dto.email, - password: hashedPassword, + password: expect.any(String), + type: UserType.WORKER, + status: UserStatus.PENDING, }; - jest.spyOn(userService, 'checkEmail').mockResolvedValue(undefined); - jest - .spyOn(userRepository, 'create') - .mockResolvedValue(createdUser as UserEntity); - const result = await userService.create(dto); - - expect(userService.checkEmail).toHaveBeenCalledWith(dto.email, 0); - expect(userRepository.create).toHaveBeenCalledWith({ - ...dto, + expect(userRepository.createUnique).toHaveBeenCalledWith({ email: dto.email, password: expect.any(String), type: UserType.WORKER, status: UserStatus.PENDING, }); - expect(result).toBe(createdUser); - }); - - it('should throw ConflictException if the email is already taken', async () => { - const dto: UserCreateDto = { - email: 'test@example.com', - password: 'password123', - type: UserType.WORKER, - }; - - jest - .spyOn(userService, 'checkEmail') - .mockRejectedValue( - new ConflictException(ErrorUser.AccountCannotBeRegistered), - ); - - await expect(userService.create(dto)).rejects.toThrow( - ErrorUser.AccountCannotBeRegistered, - ); - - expect(userService.checkEmail).toHaveBeenCalledWith(dto.email, 0); + expect(result).toMatchObject(createdUser); }); }); @@ -175,110 +113,42 @@ describe('UserService', () => { email, password: hashedPassword, }; - it('should return the user entity if credentials are valid', async () => { jest - .spyOn(userRepository, 'findOne') + .spyOn(userRepository, 'findByEmail') .mockResolvedValue(userEntity as UserEntity); const result = await userService.getByCredentials(email, password); - expect(userRepository.findOne).toHaveBeenCalledWith({ - email, - }); + expect(userRepository.findByEmail).toHaveBeenCalledWith(email); expect(result).toBe(userEntity); }); - it('should throw NotFoundException if credentials are invalid', async () => { - jest.spyOn(userRepository, 'findOne').mockResolvedValue(null); - - await expect( - userService.getByCredentials(email, password), - ).rejects.toThrow(NotFoundException); - - expect(userRepository.findOne).toHaveBeenCalledWith({ - email, - }); - }); - }); - - describe('createWeb3User', () => { - const address = '0x0755D4d722a4a201c1C5A4B5E614D913e7747b36'; - const type = UserType.WORKER; - const nonce = getNonce(); - - it('should create a new user and return the created user entity', async () => { - const createdUser: Partial = { - id: 1, - evmAddress: address, - nonce, - }; - - jest - .spyOn(userRepository, 'createWeb3User') - .mockResolvedValue(createdUser as UserEntity); - - jest.spyOn(userRepository, 'findOne').mockResolvedValue(null); - - const result = await userService.createWeb3User(address, type); - - expect(userRepository.createWeb3User).toHaveBeenCalledWith( - expect.objectContaining({ - evmAddress: address, - type, - status: UserStatus.ACTIVE, - }), - ); - expect(result).toBe(createdUser); - }); - - it('should throw ConflictException if the address is already taken', async () => { - jest - .spyOn(userRepository, 'findOne') - .mockResolvedValue({ evmAddress: MOCK_ADDRESS } as UserEntity); - - await expect(userService.createWeb3User(address, type)).rejects.toThrow( - ConflictException, - ); - - expect(userRepository.findOne).toHaveBeenCalledWith({ - evmAddress: address, - }); + it('should return null if credentials are invalid', async () => { + jest.spyOn(userRepository, 'findByEmail').mockResolvedValue(null); + const result = await userService.getByCredentials(email, password); + expect(result).toBe(null); + expect(userRepository.findByEmail).toHaveBeenCalledWith(email); }); }); describe('getByAddress', () => { - const address = '0x0755D4d722a4a201c1C5A4B5E614D913e7747b36'; - it('should return the user entity if the address exists', async () => { + const address = '0x0755D4d722a4a201c1C5A4B5E614D913e7747b36'; const userEntity: Partial = { id: 1, evmAddress: address, }; jest - .spyOn(userRepository, 'findOne') + .spyOn(userRepository, 'findOneByEvmAddress') .mockResolvedValue(userEntity as UserEntity); const result = await userService.getByAddress(address); - expect(userRepository.findOne).toHaveBeenCalledWith({ - evmAddress: address, - }); + expect(userRepository.findOneByEvmAddress).toHaveBeenCalledWith(address); expect(result).toBe(userEntity); }); - - it('should throw NotFoundException if the address does not exist', async () => { - jest.spyOn(userRepository, 'findOne').mockResolvedValue(null); - - await expect(userService.getByAddress(address)).rejects.toThrow( - NotFoundException, - ); - - expect(userRepository.findOne).toHaveBeenCalledWith({ - evmAddress: address, - }); - }); }); describe('registerAddress', () => { diff --git a/packages/apps/reputation-oracle/server/src/modules/user/user.service.ts b/packages/apps/reputation-oracle/server/src/modules/user/user.service.ts index 98d7bc508a..bdd34fa73e 100644 --- a/packages/apps/reputation-oracle/server/src/modules/user/user.service.ts +++ b/packages/apps/reputation-oracle/server/src/modules/user/user.service.ts @@ -24,7 +24,7 @@ import { UserEntity } from './user.entity'; import { RegisterAddressRequestDto, UserCreateDto, - UserUpdateDto, + Web3UserCreateDto, } from './user.dto'; import { UserRepository } from './user.repository'; import { ValidatePasswordDto } from '../auth/auth.dto'; @@ -45,52 +45,35 @@ export class UserService { private readonly configService: ConfigService, ) {} - public async update(userId: number, dto: UserUpdateDto): Promise { - return this.userRepository.updateOne({ id: userId }, dto); - } - public async create(dto: UserCreateDto): Promise { - const { email, password, ...rest } = dto; - - await this.checkEmail(email, 0); - - return await this.userRepository.create({ - ...rest, - email, - password: bcrypt.hashSync(password, this.HASH_ROUNDS), - status: UserStatus.PENDING, - }); + const newUser = new UserEntity(); + newUser.email = dto.email; + newUser.password = bcrypt.hashSync(dto.password, this.HASH_ROUNDS); + newUser.type = UserType.WORKER; + newUser.status = UserStatus.PENDING; + await this.userRepository.createUnique(newUser); + return newUser; } public async getByCredentials( email: string, password: string, - ): Promise { - const userEntity = await this.userRepository.findOne({ - email, - }); + ): Promise { + const userEntity = await this.userRepository.findByEmail(email); - if (!userEntity) { - throw new NotFoundException(ErrorUser.InvalidCredentials); - } - - if (!bcrypt.compareSync(password, userEntity.password)) { - throw new NotFoundException(ErrorUser.InvalidCredentials); + if (!userEntity || !bcrypt.compareSync(password, userEntity.password)) { + return null; } return userEntity; } - public async getByEmail(email: string): Promise { - return this.userRepository.findOne({ email }); - } - public updatePassword( userEntity: UserEntity, data: ValidatePasswordDto, ): Promise { userEntity.password = bcrypt.hashSync(data.password, this.HASH_ROUNDS); - return userEntity.save(); + return this.userRepository.updateOne(userEntity); } public activate(userEntity: UserEntity): Promise { @@ -98,36 +81,24 @@ export class UserService { return userEntity.save(); } - public async checkEmail(email: string, id: number): Promise { - const userEntity = await this.userRepository.findOne({ - email, - id: Not(id), - }); - - if (userEntity) { - this.logger.log(ErrorUser.AccountCannotBeRegistered, UserService.name); - throw new ConflictException(ErrorUser.AccountCannotBeRegistered); - } - } - public async createWeb3User( + dto: Web3UserCreateDto, address: string, - type: UserType, ): Promise { await this.checkEvmAddress(address); - return await this.userRepository.createWeb3User({ - evmAddress: address, - nonce: getNonce(), - status: UserStatus.ACTIVE, - type, - }); + const newUser = new UserEntity(); + newUser.evmAddress = dto.evmAddress; + newUser.nonce = getNonce(); + newUser.type = UserType.OPERATOR; + newUser.status = UserStatus.ACTIVE; + + await this.userRepository.createUnique(newUser); + return newUser; } public async checkEvmAddress(address: string): Promise { - const userEntity = await this.userRepository.findOne({ - evmAddress: address, - }); + const userEntity = await this.userRepository.findOneByEvmAddress(address); if (userEntity) { this.logger.log(ErrorUser.AccountCannotBeRegistered, UserService.name); @@ -136,9 +107,7 @@ export class UserService { } public async getByAddress(address: string): Promise { - const userEntity = await this.userRepository.findOne({ - evmAddress: address, - }); + const userEntity = await this.userRepository.findOneByEvmAddress(address); if (!userEntity) { throw new NotFoundException(ErrorUser.NotFound); diff --git a/packages/apps/reputation-oracle/server/vercel.json b/packages/apps/reputation-oracle/server/vercel.json index 8956a911db..1d24153cb1 100644 --- a/packages/apps/reputation-oracle/server/vercel.json +++ b/packages/apps/reputation-oracle/server/vercel.json @@ -20,11 +20,11 @@ "crons": [ { "path": "/cron/webhook/pending", - "schedule": "*/1 * * * *" + "schedule": "*/5 * * * *" }, { "path": "/cron/webhook/paid", - "schedule": "*/1 * * * *" + "schedule": "*/5 * * * *" } ], "ignoreCommand": "git diff HEAD^ HEAD --quiet ." From f2bf1680eba241d689d7d01263b806b9cbacc176 Mon Sep 17 00:00:00 2001 From: eugenvoronov <104138627+eugenvoronov@users.noreply.github.com> Date: Thu, 21 Mar 2024 19:14:41 +0300 Subject: [PATCH 02/66] [Job Launcher] Quick launch currency (#1750) * Used fund amount in quick launch * Resolved comments --- .../src/modules/job/job.service.spec.ts | 21 ++++++------- .../server/src/modules/job/job.service.ts | 30 +++++++++++-------- 2 files changed, 28 insertions(+), 23 deletions(-) diff --git a/packages/apps/job-launcher/server/src/modules/job/job.service.spec.ts b/packages/apps/job-launcher/server/src/modules/job/job.service.spec.ts index 09b9ad2a45..bb97db5102 100644 --- a/packages/apps/job-launcher/server/src/modules/job/job.service.spec.ts +++ b/packages/apps/job-launcher/server/src/modules/job/job.service.spec.ts @@ -94,7 +94,7 @@ import { JobRepository } from './job.repository'; import { WebhookRepository } from '../webhook/webhook.repository'; import { JobService } from './job.service'; -import { div, mul } from '../../common/utils/decimal'; +import { add, div, mul } from '../../common/utils/decimal'; import { PaymentRepository } from '../payment/payment.repository'; import { RoutingProtocolService } from './routing-protocol.service'; import { EventType } from '../../common/enums/webhook'; @@ -366,10 +366,11 @@ describe('JobService', () => { }); it('should create a job using quick launch successfully', async () => { - const fundAmount = 10; - const fee = (MOCK_JOB_LAUNCHER_FEE / 100) * fundAmount; + const tokenFundAmount = 100; + const tokenFee = (MOCK_JOB_LAUNCHER_FEE / 100) * tokenFundAmount; + const tokenTotalAmount = add(tokenFundAmount, tokenFee); - const userBalance = 25; + const userBalance = 250; getUserBalanceMock.mockResolvedValue(userBalance); const mockJobEntity: Partial = { @@ -380,8 +381,8 @@ describe('JobService', () => { manifestHash: MOCK_FILE_HASH, requestType: JobRequestType.HCAPTCHA, escrowAddress: MOCK_ADDRESS, - fee, - fundAmount, + fee: tokenFee, + fundAmount: tokenFundAmount, status: JobStatus.PENDING, save: jest.fn().mockResolvedValue(true), }; @@ -403,7 +404,7 @@ describe('JobService', () => { quickLaunchJobDto.requestType = JobRequestType.HCAPTCHA; quickLaunchJobDto.manifestUrl = MOCK_FILE_URL; quickLaunchJobDto.manifestHash = MOCK_FILE_HASH; - quickLaunchJobDto.fundAmount = fundAmount; + quickLaunchJobDto.fundAmount = tokenFundAmount; await jobService.createJob( userId, @@ -424,7 +425,7 @@ describe('JobService', () => { source: PaymentSource.BALANCE, type: PaymentType.WITHDRAWAL, currency: TokenId.HMT, - amount: -mul(fundAmount + fee, rate), + amount: -tokenTotalAmount, rate: div(1, rate), status: PaymentStatus.SUCCEEDED, }); @@ -434,8 +435,8 @@ describe('JobService', () => { manifestUrl: expect.any(String), manifestHash: expect.any(String), requestType: JobRequestType.HCAPTCHA, - fee: mul(fee, rate), - fundAmount: mul(fundAmount, rate), + fee: tokenFee, + fundAmount: tokenFundAmount, status: JobStatus.PENDING, waitUntil: expect.any(Date), }); diff --git a/packages/apps/job-launcher/server/src/modules/job/job.service.ts b/packages/apps/job-launcher/server/src/modules/job/job.service.ts index e1ca677aae..360f3e9acc 100644 --- a/packages/apps/job-launcher/server/src/modules/job/job.service.ts +++ b/packages/apps/job-launcher/server/src/modules/job/job.service.ts @@ -551,13 +551,6 @@ export class JobService { const { calculateFundAmount, createManifest } = this.createJobSpecificActions[requestType]; - let fundAmount = 0; - if (dto instanceof JobQuickLaunchDto) { - fundAmount = dto.fundAmount; - } else { - fundAmount = await calculateFundAmount(dto, rate); - } - const userBalance = await this.paymentService.getUserBalance(userId); const feePercentage = Number( await this.getOracleFee( @@ -565,18 +558,29 @@ export class JobService { chainId, ), ); - const fee = mul(div(feePercentage, 100), fundAmount); - const usdTotalAmount = add(fundAmount, fee); + + let tokenFee, tokenTotalAmount, tokenFundAmount, usdTotalAmount; + + if (dto instanceof JobQuickLaunchDto) { + tokenFee = mul(div(feePercentage, 100), dto.fundAmount); + tokenFundAmount = dto.fundAmount; + tokenTotalAmount = add(tokenFundAmount, tokenFee); + usdTotalAmount = div(tokenTotalAmount, rate); + } else { + const fundAmount = await calculateFundAmount(dto, rate); + const fee = mul(div(feePercentage, 100), fundAmount); + + tokenFundAmount = mul(fundAmount, rate); + tokenFee = mul(fee, rate); + tokenTotalAmount = add(tokenFundAmount, tokenFee); + usdTotalAmount = add(fundAmount, fee); + } if (lt(userBalance, usdTotalAmount)) { this.logger.log(ErrorJob.NotEnoughFunds, JobService.name); throw new BadRequestException(ErrorJob.NotEnoughFunds); } - const tokenFundAmount = mul(fundAmount, rate); - const tokenFee = mul(fee, rate); - const tokenTotalAmount = add(tokenFundAmount, tokenFee); - let jobEntity = new JobEntity(); if (dto instanceof JobQuickLaunchDto) { From 875e6457af34ed50f66ee66b84df71cb47e75d42 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Francisco=20L=C3=B3pez?= <50665615+flopez7@users.noreply.github.com> Date: Mon, 25 Mar 2024 09:56:45 +0100 Subject: [PATCH 03/66] Fix oracles webhook (#1752) * Fix oracles webhook --- .../server/src/modules/webhook/webhook.service.spec.ts | 8 ++++---- .../server/src/modules/webhook/webhook.service.ts | 2 +- .../server/src/modules/webhook/webhook.service.spec.ts | 2 +- .../server/src/modules/webhook/webhook.service.ts | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/apps/job-launcher/server/src/modules/webhook/webhook.service.spec.ts b/packages/apps/job-launcher/server/src/modules/webhook/webhook.service.spec.ts index c4f7276ed2..ca9025a993 100644 --- a/packages/apps/job-launcher/server/src/modules/webhook/webhook.service.spec.ts +++ b/packages/apps/job-launcher/server/src/modules/webhook/webhook.service.spec.ts @@ -138,7 +138,7 @@ describe('WebhookService', () => { .mockResolvedValue(MOCK_EXCHANGE_ORACLE_WEBHOOK_URL); jest.spyOn(httpService as any, 'post').mockImplementation(() => { return of({ - status: HttpStatus.OK, + status: HttpStatus.CREATED, }); }); expect(await (webhookService as any).sendWebhook(webhookEntity)).toBe( @@ -163,7 +163,7 @@ describe('WebhookService', () => { .mockResolvedValue(MOCK_EXCHANGE_ORACLE_WEBHOOK_URL); jest.spyOn(httpService as any, 'post').mockImplementation(() => { return of({ - status: HttpStatus.OK, + status: HttpStatus.CREATED, }); }); expect(await (webhookService as any).sendWebhook(webhookEntity)).toBe( @@ -189,7 +189,7 @@ describe('WebhookService', () => { .mockResolvedValue(MOCK_EXCHANGE_ORACLE_WEBHOOK_URL); jest.spyOn(httpService as any, 'post').mockImplementation(() => { return of({ - status: HttpStatus.OK, + status: HttpStatus.CREATED, }); }); expect(await (webhookService as any).sendWebhook(webhookEntity)).toBe( @@ -215,7 +215,7 @@ describe('WebhookService', () => { .mockResolvedValue(MOCK_EXCHANGE_ORACLE_WEBHOOK_URL); jest.spyOn(httpService as any, 'post').mockImplementation(() => { return of({ - status: HttpStatus.OK, + status: HttpStatus.CREATED, }); }); expect(await (webhookService as any).sendWebhook(webhookEntity)).toBe( diff --git a/packages/apps/job-launcher/server/src/modules/webhook/webhook.service.ts b/packages/apps/job-launcher/server/src/modules/webhook/webhook.service.ts index 6689454421..2210a67c3f 100644 --- a/packages/apps/job-launcher/server/src/modules/webhook/webhook.service.ts +++ b/packages/apps/job-launcher/server/src/modules/webhook/webhook.service.ts @@ -89,7 +89,7 @@ export class WebhookService { ); // Check if the request was successful. - if (status !== HttpStatus.OK) { + if (status !== HttpStatus.CREATED) { this.logger.log(ErrorWebhook.NotSent, WebhookService.name); throw new NotFoundException(ErrorWebhook.NotSent); } diff --git a/packages/apps/reputation-oracle/server/src/modules/webhook/webhook.service.spec.ts b/packages/apps/reputation-oracle/server/src/modules/webhook/webhook.service.spec.ts index ceeb47132b..4623f3101b 100644 --- a/packages/apps/reputation-oracle/server/src/modules/webhook/webhook.service.spec.ts +++ b/packages/apps/reputation-oracle/server/src/modules/webhook/webhook.service.spec.ts @@ -181,7 +181,7 @@ describe('WebhookService', () => { it('should successfully send a webhook', async () => { jest.spyOn(httpService as any, 'post').mockImplementation(() => { return of({ - status: HttpStatus.OK, + status: HttpStatus.CREATED, }); }); expect( diff --git a/packages/apps/reputation-oracle/server/src/modules/webhook/webhook.service.ts b/packages/apps/reputation-oracle/server/src/modules/webhook/webhook.service.ts index f1f448ad12..4610a8db84 100644 --- a/packages/apps/reputation-oracle/server/src/modules/webhook/webhook.service.ts +++ b/packages/apps/reputation-oracle/server/src/modules/webhook/webhook.service.ts @@ -95,7 +95,7 @@ export class WebhookService { }), ); - if (status !== HttpStatus.OK) { + if (status !== HttpStatus.CREATED) { this.logger.log(ErrorWebhook.NotSent, WebhookService.name); throw new NotFoundException(ErrorWebhook.NotSent); } From a18770ce23b84cacf512748436cd933e319d9133 Mon Sep 17 00:00:00 2001 From: eugenvoronov <104138627+eugenvoronov@users.noreply.github.com> Date: Mon, 25 Mar 2024 13:51:15 +0300 Subject: [PATCH 04/66] Fixed get details method (#1721) --- .../apps/job-launcher/server/src/modules/job/job.service.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/apps/job-launcher/server/src/modules/job/job.service.ts b/packages/apps/job-launcher/server/src/modules/job/job.service.ts index 360f3e9acc..43c8f7fb42 100644 --- a/packages/apps/job-launcher/server/src/modules/job/job.service.ts +++ b/packages/apps/job-launcher/server/src/modules/job/job.service.ts @@ -1215,8 +1215,8 @@ export class JobService { } else { manifest = manifest as CvatManifestDto; specificManifestDetails = { - requestType: manifest.annotation.type, - submissionsRequired: manifest.annotation.job_size, + requestType: manifest.annotation?.type, + submissionsRequired: manifest.annotation?.job_size, }; } From fd83901d169701085bdb9210fceb61ebf0d16431 Mon Sep 17 00:00:00 2001 From: portuu3 <61605646+portuu3@users.noreply.github.com> Date: Mon, 25 Mar 2024 12:18:16 +0100 Subject: [PATCH 05/66] [TEMP] Set `thisMonthData` a default value because hCapthca services are down (#1759) Co-authored-by: Sergey Dzeranov --- .../ui/src/hooks/useMonthlyTaskSummaries.ts | 29 ++++++++++++------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/packages/apps/dashboard/ui/src/hooks/useMonthlyTaskSummaries.ts b/packages/apps/dashboard/ui/src/hooks/useMonthlyTaskSummaries.ts index 39bcd8ef43..0dd11988b1 100644 --- a/packages/apps/dashboard/ui/src/hooks/useMonthlyTaskSummaries.ts +++ b/packages/apps/dashboard/ui/src/hooks/useMonthlyTaskSummaries.ts @@ -6,16 +6,25 @@ export function useMonthlyTaskSummaries() { return useSWR(`human-protocol-dashboard-monthly-task-summaries`, async () => { const apiURL = import.meta.env.VITE_APP_ADMIN_API_URL; - const from = dayjs().startOf('month').format('YYYY-MM-DD'); - const to = dayjs().endOf('month').format('YYYY-MM-DD'); - const [cachedData, thisMonthData] = await Promise.all([ - axios - .get(`${apiURL}/monthly-task-summaries?sort=id`) - .then((res) => res.data), - axios - .get(`${apiURL}/stats/tasks?to=${to}&from=${from}`) - .then((res) => res.data), - ]); + // const from = dayjs().startOf('month').format('YYYY-MM-DD'); + // const to = dayjs().endOf('month').format('YYYY-MM-DD'); + // const [cachedData, thisMonthData] = await Promise.all([ + // axios + // .get(`${apiURL}/monthly-task-summaries?sort=id`) + // .then((res) => res.data), + // axios + // .get(`${apiURL}/stats/tasks?to=${to}&from=${from}`) + // .then((res) => res.data), + // ]); + let cachedData, thisMonthData; + try { + cachedData = (await axios.get(`${apiURL}/monthly-task-summaries?sort=id`)) + .data; + thisMonthData = { dailyTasksData: [] }; + } catch (error) { + cachedData = { data: [] }; + thisMonthData = { dailyTasksData: [] }; + } const totalSolvedTasks = thisMonthData.dailyTasksData.reduce( (a: number, b: any) => a + b.tasksSolved, From 8b3fd26618116d403421ccd27a3a751b5191cec4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Francisco=20L=C3=B3pez?= <50665615+flopez7@users.noreply.github.com> Date: Mon, 25 Mar 2024 16:23:37 +0100 Subject: [PATCH 06/66] [Exchange Oracle] Handle escrow_created webhook (#1707) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Handle escrow_created event in Exchange Oracle * Fix exchange oracle tests * Get jobs endpoint in Exchange Oracle (#1712) * Modify jobDto to camelCase * Check jobType filter in getJobList * Add create assignment and get assignments endpoints in Exchange Oracle (#1725) * [Fortune] Add endpoint for workers stats (#1717) * add endpoint * add assignment repo --------- Co-authored-by: Francisco López <50665615+flopez7@users.noreply.github.com> * exchange oracle fixes * comment cors * replace npm by yarn * Add cron job module (#1739) * update yarn.lock * Add endpoint to getOracleStats and optimize getAssignmentStats * Fix database.module.ts path * add docker file to exo * Add heath module in Exchange Oracle * test blueprint render * move render file to root folder * fixes exchange oracle * Fix job launcher webhook type and exchange oracle jwt auth * Fix case in signature guard * Fix outgoing webhook success check * fix kyc status check and add constants * Add exchange oracle unit tests * Add API docs for exchange oracle and fix some bugs * Fix exchange oracle webhook * remove unused render blueprint * delete unuseful test --------- Co-authored-by: Mehdi <75360886+mrhouzlane@users.noreply.github.com> Co-authored-by: portuu3 --- packages/apps/dashboard/ui/package.json | 2 +- .../fortune/exchange-oracle/server/Dockerfile | 17 + .../exchange-oracle/server/package.json | 2 +- .../server/src/app.controller.spec.ts | 15 - .../server/src/app.controller.ts | 7 +- .../exchange-oracle/server/src/app.module.ts | 8 + .../server/src/common/config/env.ts | 17 +- .../server/src/common/constant/errors.ts | 18 + .../server/src/common/constant/index.ts | 6 +- .../server/src/common/enums/collection.ts | 4 + .../server/src/common/enums/cron-job.ts | 3 + .../server/src/common/enums/job.ts | 27 + .../server/src/common/enums/webhook.ts | 1 + .../server/src/common/guards/cron.auth.ts | 25 + .../src/common/guards/signature.auth.spec.ts | 4 +- .../src/common/guards/signature.auth.ts | 4 +- .../src/common/guards/strategy/jwt.http.ts | 24 +- .../server/src/common/interfaces/base.ts | 5 + .../server/src/common/interfaces/cron-job.ts | 8 + .../src/common/pagination/pagination.dto.ts | 79 + .../server/src/common/types/jwt.ts | 10 + .../server/src/database/database.module.ts | 28 +- .../1710248133605-initialMigration.ts | 114 - .../1710871038249-initialMigration.ts | 143 + .../exchange-oracle/server/src/main.ts | 32 +- .../assignment/assignment.controller.spec.ts | 89 + .../assignment/assignment.controller.ts | 100 + .../src/modules/assignment/assignment.dto.ts | 114 + .../modules/assignment/assignment.entity.ts | 5 +- .../assignment/assignment.interface.ts | 20 + .../modules/assignment/assignment.module.ts | 25 + .../assignment/assignment.repository.ts | 161 + .../assignment/assignment.service.spec.ts | 297 + .../modules/assignment/assignment.service.ts | 137 + .../src/modules/cron-job/cron-job.entity.ts | 30 + .../src/modules/cron-job/cron-job.module.ts | 23 + .../modules/cron-job/cron-job.repository.ts | 16 + .../src/modules/cron-job/cron-job.service.ts | 96 + .../modules/cron-job/cron.job.controller.ts | 35 + .../src/modules/health/health.controller.ts | 35 + .../src/modules/health/health.module.ts | 11 + .../src/modules/job/job.controller.spec.ts | 126 +- .../server/src/modules/job/job.controller.ts | 91 +- .../server/src/modules/job/job.dto.ts | 98 +- .../server/src/modules/job/job.entity.ts | 9 +- .../server/src/modules/job/job.interface.ts | 19 + .../server/src/modules/job/job.module.ts | 24 +- .../server/src/modules/job/job.repository.ts | 71 + .../src/modules/job/job.service.spec.ts | 414 +- .../server/src/modules/job/job.service.ts | 210 +- .../modules/stats/stats.controller.spec.ts | 47 + .../src/modules/stats/stats.controller.ts | 55 + .../server/src/modules/stats/stats.dto.ts | 57 + .../server/src/modules/stats/stats.module.ts | 15 + .../server/src/modules/stats/stats.service.ts | 37 + .../webhook/webhook.controller.spec.ts | 128 +- .../src/modules/webhook/webhook.controller.ts | 47 +- .../server/src/modules/webhook/webhook.dto.ts | 13 +- .../src/modules/webhook/webhook.entity.ts | 2 +- .../src/modules/webhook/webhook.module.ts | 20 +- .../src/modules/webhook/webhook.repository.ts | 36 + .../modules/webhook/webhook.service.spec.ts | 217 +- .../src/modules/webhook/webhook.service.ts | 152 +- .../exchange-oracle/server/test/constants.ts | 4 +- .../exchange-oracle/server/typeorm.config.ts | 1 + .../src/app.controller.spec.ts | 15 - .../recording-oracle/src/app.controller.ts | 7 +- packages/apps/job-launcher/client/index.html | 2 +- .../apps/job-launcher/client/package.json | 2 +- .../server/src/modules/job/job.service.ts | 7 +- packages/core/package.json | 9 +- .../human-protocol-sdk/package.json | 6 +- packages/sdk/typescript/subgraph/README.md | 10 +- scripts/Makefile | 11 +- scripts/fortune/.env.exco-server | 2 + scripts/fortune/.env.jl-server | 2 +- scripts/fortune/.env.rec-oracle | 2 + yarn.lock | 5242 +++++++++-------- 78 files changed, 5588 insertions(+), 3419 deletions(-) create mode 100644 packages/apps/fortune/exchange-oracle/server/Dockerfile delete mode 100644 packages/apps/fortune/exchange-oracle/server/src/app.controller.spec.ts create mode 100644 packages/apps/fortune/exchange-oracle/server/src/common/enums/collection.ts create mode 100644 packages/apps/fortune/exchange-oracle/server/src/common/enums/cron-job.ts create mode 100644 packages/apps/fortune/exchange-oracle/server/src/common/guards/cron.auth.ts create mode 100644 packages/apps/fortune/exchange-oracle/server/src/common/interfaces/base.ts create mode 100644 packages/apps/fortune/exchange-oracle/server/src/common/interfaces/cron-job.ts create mode 100644 packages/apps/fortune/exchange-oracle/server/src/common/pagination/pagination.dto.ts create mode 100644 packages/apps/fortune/exchange-oracle/server/src/common/types/jwt.ts delete mode 100644 packages/apps/fortune/exchange-oracle/server/src/database/migrations/1710248133605-initialMigration.ts create mode 100644 packages/apps/fortune/exchange-oracle/server/src/database/migrations/1710871038249-initialMigration.ts create mode 100644 packages/apps/fortune/exchange-oracle/server/src/modules/assignment/assignment.controller.spec.ts create mode 100644 packages/apps/fortune/exchange-oracle/server/src/modules/assignment/assignment.controller.ts create mode 100644 packages/apps/fortune/exchange-oracle/server/src/modules/assignment/assignment.dto.ts create mode 100644 packages/apps/fortune/exchange-oracle/server/src/modules/assignment/assignment.interface.ts create mode 100644 packages/apps/fortune/exchange-oracle/server/src/modules/assignment/assignment.module.ts create mode 100644 packages/apps/fortune/exchange-oracle/server/src/modules/assignment/assignment.repository.ts create mode 100644 packages/apps/fortune/exchange-oracle/server/src/modules/assignment/assignment.service.spec.ts create mode 100644 packages/apps/fortune/exchange-oracle/server/src/modules/assignment/assignment.service.ts create mode 100644 packages/apps/fortune/exchange-oracle/server/src/modules/cron-job/cron-job.entity.ts create mode 100644 packages/apps/fortune/exchange-oracle/server/src/modules/cron-job/cron-job.module.ts create mode 100644 packages/apps/fortune/exchange-oracle/server/src/modules/cron-job/cron-job.repository.ts create mode 100644 packages/apps/fortune/exchange-oracle/server/src/modules/cron-job/cron-job.service.ts create mode 100644 packages/apps/fortune/exchange-oracle/server/src/modules/cron-job/cron.job.controller.ts create mode 100644 packages/apps/fortune/exchange-oracle/server/src/modules/health/health.controller.ts create mode 100644 packages/apps/fortune/exchange-oracle/server/src/modules/health/health.module.ts create mode 100644 packages/apps/fortune/exchange-oracle/server/src/modules/job/job.interface.ts create mode 100644 packages/apps/fortune/exchange-oracle/server/src/modules/job/job.repository.ts create mode 100644 packages/apps/fortune/exchange-oracle/server/src/modules/stats/stats.controller.spec.ts create mode 100644 packages/apps/fortune/exchange-oracle/server/src/modules/stats/stats.controller.ts create mode 100644 packages/apps/fortune/exchange-oracle/server/src/modules/stats/stats.dto.ts create mode 100644 packages/apps/fortune/exchange-oracle/server/src/modules/stats/stats.module.ts create mode 100644 packages/apps/fortune/exchange-oracle/server/src/modules/stats/stats.service.ts create mode 100644 packages/apps/fortune/exchange-oracle/server/src/modules/webhook/webhook.repository.ts delete mode 100644 packages/apps/fortune/recording-oracle/src/app.controller.spec.ts diff --git a/packages/apps/dashboard/ui/package.json b/packages/apps/dashboard/ui/package.json index 0f803fa355..86beaae6e6 100644 --- a/packages/apps/dashboard/ui/package.json +++ b/packages/apps/dashboard/ui/package.json @@ -73,7 +73,7 @@ "test": "vitest -u", "format:prettier": "prettier --write '**/*.{ts,tsx}'", "format:lint": "eslint --fix '**/*.{ts,tsx}'", - "format": "npm run format:prettier && npm run format:lint", + "format": "yarn format:prettier && yarn format:lint", "vercel-build": "yarn workspace @human-protocol/sdk build && yarn build" }, "browserslist": { diff --git a/packages/apps/fortune/exchange-oracle/server/Dockerfile b/packages/apps/fortune/exchange-oracle/server/Dockerfile new file mode 100644 index 0000000000..d2dbc4529c --- /dev/null +++ b/packages/apps/fortune/exchange-oracle/server/Dockerfile @@ -0,0 +1,17 @@ +# Base image +FROM node:18 + +# Create app directory +WORKDIR /usr/src/app + +# Bundle app source +COPY . . + +# Install app dependencies +RUN yarn install + +# Creates a "dist" folder with the production build +RUN yarn workspace @human-protocol/fortune-exchange-oracle-server build + +# Start the server using the production build +CMD [ "node", "packages/apps/fortune/exchange-oracle/server/dist/src/main.js" ] diff --git a/packages/apps/fortune/exchange-oracle/server/package.json b/packages/apps/fortune/exchange-oracle/server/package.json index 3fb6069fb8..c12b0facee 100644 --- a/packages/apps/fortune/exchange-oracle/server/package.json +++ b/packages/apps/fortune/exchange-oracle/server/package.json @@ -10,7 +10,7 @@ "start": "nest start", "start:dev": "NODE_ENV=development nest start --watch", "start:debug": "nest start --debug --watch", - "start:prod": "node dist/main", + "start:prod": "node dist/src/main", "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix", "test": "jest", "migration:create": "yarn build && typeorm-ts-node-commonjs migration:create", diff --git a/packages/apps/fortune/exchange-oracle/server/src/app.controller.spec.ts b/packages/apps/fortune/exchange-oracle/server/src/app.controller.spec.ts deleted file mode 100644 index 741a42c324..0000000000 --- a/packages/apps/fortune/exchange-oracle/server/src/app.controller.spec.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { AppController } from './app.controller'; - -describe('AppController', () => { - let appController: AppController; - - beforeEach(() => { - appController = new AppController(); - }); - - describe('Health Check', () => { - it('should return OK', async () => { - expect(await appController.health()).toBe('OK'); - }); - }); -}); diff --git a/packages/apps/fortune/exchange-oracle/server/src/app.controller.ts b/packages/apps/fortune/exchange-oracle/server/src/app.controller.ts index f82b076b7e..1e0eff8385 100644 --- a/packages/apps/fortune/exchange-oracle/server/src/app.controller.ts +++ b/packages/apps/fortune/exchange-oracle/server/src/app.controller.ts @@ -1,14 +1,13 @@ import { Controller, Get, Redirect } from '@nestjs/common'; import { Public } from './common/decorators'; -import { ApiExcludeController } from '@nestjs/swagger'; +import { ApiTags, ApiExcludeController } from '@nestjs/swagger'; @Controller('/') @ApiExcludeController() +@ApiTags('Main') export class AppController { @Public() @Get('/') @Redirect('/swagger', 301) - public health(): string { - return 'OK'; - } + public redirect(): void {} } diff --git a/packages/apps/fortune/exchange-oracle/server/src/app.module.ts b/packages/apps/fortune/exchange-oracle/server/src/app.module.ts index a3b96aefad..e9cd913a06 100644 --- a/packages/apps/fortune/exchange-oracle/server/src/app.module.ts +++ b/packages/apps/fortune/exchange-oracle/server/src/app.module.ts @@ -10,6 +10,10 @@ import { WebhookModule } from './modules/webhook/webhook.module'; import { JwtAuthGuard } from './common/guards/jwt.auth'; import { JwtHttpStrategy } from './common/guards/strategy'; import { Web3Module } from './modules/web3/web3.module'; +import { StatsModule } from './modules/stats/stats.module'; +import { AssignmentModule } from './modules/assignment/assignment.module'; +import { CronJobModule } from './modules/cron-job/cron-job.module'; +import { HealthModule } from './modules/health/health.module'; @Module({ providers: [ @@ -24,9 +28,13 @@ import { Web3Module } from './modules/web3/web3.module'; JwtHttpStrategy, ], imports: [ + HealthModule, + AssignmentModule, JobModule, WebhookModule, Web3Module, + StatsModule, + CronJobModule, ConfigModule.forRoot({ envFilePath: process.env.NODE_ENV ? `.env.${process.env.NODE_ENV as string}` diff --git a/packages/apps/fortune/exchange-oracle/server/src/common/config/env.ts b/packages/apps/fortune/exchange-oracle/server/src/common/config/env.ts index b135222d8f..9075f4ee82 100644 --- a/packages/apps/fortune/exchange-oracle/server/src/common/config/env.ts +++ b/packages/apps/fortune/exchange-oracle/server/src/common/config/env.ts @@ -5,6 +5,7 @@ export const ConfigNames = { HOST: 'HOST', PORT: 'PORT', WEB3_ENV: 'WEB3_ENV', + POSTGRES_URL: 'POSTGRES_URL', POSTGRES_HOST: 'POSTGRES_HOST', POSTGRES_USER: 'POSTGRES_USER', POSTGRES_PASSWORD: 'POSTGRES_PASSWORD', @@ -13,6 +14,7 @@ export const ConfigNames = { POSTGRES_SYNC: 'POSTGRES_SYNC', POSTGRES_SSL: 'POSTGRES_SSL', POSTGRES_LOGGING: 'POSTGRES_LOGGING', + MAX_RETRY_COUNT: 'MAX_RETRY_COUNT', WEB3_PRIVATE_KEY: 'WEB3_PRIVATE_KEY', S3_ENDPOINT: 'S3_ENDPOINT', S3_PORT: 'S3_PORT', @@ -23,6 +25,7 @@ export const ConfigNames = { PGP_ENCRYPT: 'PGP_ENCRYPT', PGP_PRIVATE_KEY: 'ENCRYPTION_PRIVATE_KEY', PGP_PASSPHRASE: 'PGP_PASSPHRASE', + CRON_SECRET: 'CRON_SECRET', }; export const envValidator = Joi.object({ @@ -30,14 +33,16 @@ export const envValidator = Joi.object({ NODE_ENV: Joi.string().default('development'), HOST: Joi.string().default('localhost'), PORT: Joi.string().default(3002), + MAX_RETRY_COUNT: Joi.number().default(5), WEB3_ENV: Joi.string().default('testnet'), // Database DB_TYPE: Joi.string().default('postgres'), - POSTGRES_HOST: Joi.string().default('127.0.0.1'), - POSTGRES_USER: Joi.string().default('operator'), - POSTGRES_PASSWORD: Joi.string().default('qwerty'), - POSTGRES_DATABASE: Joi.string().default('reputation-oracle'), - POSTGRES_PORT: Joi.string().default('5432'), + POSTGRES_URL: Joi.string().optional(), + POSTGRES_HOST: Joi.string().optional(), + POSTGRES_USER: Joi.string().optional(), + POSTGRES_PASSWORD: Joi.string().optional(), + POSTGRES_DATABASE: Joi.string().optional(), + POSTGRES_PORT: Joi.string().optional(), POSTGRES_SYNC: Joi.string().default('false'), POSTGRES_SSL: Joi.string().default('false'), POSTGRES_LOGGING: Joi.string(), @@ -53,4 +58,6 @@ export const envValidator = Joi.object({ PGP_ENCRYPT: Joi.boolean().default(false), PGP_PRIVATE_KEY: Joi.string().optional(), PGP_PASSPHRASE: Joi.string().optional(), + // Cron Job Secret + CRON_SECRET: Joi.string().optional(), }); diff --git a/packages/apps/fortune/exchange-oracle/server/src/common/constant/errors.ts b/packages/apps/fortune/exchange-oracle/server/src/common/constant/errors.ts index 7a178a4647..9e86e5e730 100644 --- a/packages/apps/fortune/exchange-oracle/server/src/common/constant/errors.ts +++ b/packages/apps/fortune/exchange-oracle/server/src/common/constant/errors.ts @@ -4,3 +4,21 @@ export enum ErrorWeb3 { InvalidChainId = 'Invalid chain id provided for the configured environment', } + +/** + * Represents error messages associated to webhook. + */ +export enum ErrorWebhook { + NotSent = 'Webhook was not sent', + NotFound = 'Webhook not found', + UrlNotFound = 'Webhook URL not found', + NotCreated = 'Webhook has not been created', +} + +/** + * Represents error messages associated with a cron job. + */ +export enum ErrorCronJob { + NotCompleted = 'Cron job is not completed', + Completed = 'Cron job is completed', +} diff --git a/packages/apps/fortune/exchange-oracle/server/src/common/constant/index.ts b/packages/apps/fortune/exchange-oracle/server/src/common/constant/index.ts index 5b18a3d9f9..8e2144a097 100644 --- a/packages/apps/fortune/exchange-oracle/server/src/common/constant/index.ts +++ b/packages/apps/fortune/exchange-oracle/server/src/common/constant/index.ts @@ -1,8 +1,9 @@ import { ChainId } from '@human-protocol/sdk'; export const HEADER_SIGNATURE_KEY = 'human-signature'; -export const ESCROW_FAILED_ENDPOINT = '/job/escrow-failed-webhook'; export const NS = 'hmt'; +export const TOKEN = 'HMT'; +export const DEFAULT_MAX_RETRY_COUNT = 5; export const LOCALHOST_CHAIN_IDS = [ChainId.LOCALHOST]; @@ -16,3 +17,6 @@ export const MAINNET_CHAIN_IDS = [ ChainId.BSC_MAINNET, ChainId.MOONBEAM, ]; + +export const JWT_KVSTORE_KEY = 'jwt_public_key'; +export const KYC_APPROVED = 'APPROVED'; diff --git a/packages/apps/fortune/exchange-oracle/server/src/common/enums/collection.ts b/packages/apps/fortune/exchange-oracle/server/src/common/enums/collection.ts new file mode 100644 index 0000000000..cb05417818 --- /dev/null +++ b/packages/apps/fortune/exchange-oracle/server/src/common/enums/collection.ts @@ -0,0 +1,4 @@ +export enum SortDirection { + ASC = 'ASC', + DESC = 'DESC', +} diff --git a/packages/apps/fortune/exchange-oracle/server/src/common/enums/cron-job.ts b/packages/apps/fortune/exchange-oracle/server/src/common/enums/cron-job.ts new file mode 100644 index 0000000000..3e9e2b90ba --- /dev/null +++ b/packages/apps/fortune/exchange-oracle/server/src/common/enums/cron-job.ts @@ -0,0 +1,3 @@ +export enum CronJobType { + ProcessPendingWebhook = 'process-pending-webhook', +} diff --git a/packages/apps/fortune/exchange-oracle/server/src/common/enums/job.ts b/packages/apps/fortune/exchange-oracle/server/src/common/enums/job.ts index 059d927598..bdb7d0f526 100644 --- a/packages/apps/fortune/exchange-oracle/server/src/common/enums/job.ts +++ b/packages/apps/fortune/exchange-oracle/server/src/common/enums/job.ts @@ -4,6 +4,20 @@ export enum JobStatus { CANCELED = 'CANCELED', } +export enum JobSortField { + CHAIN_ID = 'chain_id', + JOB_TYPE = 'job_type', + REWARD_AMOUNT = 'reward_amount', + CREATED_AT = 'created_at', +} + +export enum JobFieldName { + JobDescription = 'job_description', + RewardAmount = 'reward_amount', + RewardToken = 'reward_token', + CreatedAt = 'created_at', +} + export enum AssignmentStatus { ACTIVE = 'ACTIVE', VALIDATION = 'VALIDATION', @@ -12,3 +26,16 @@ export enum AssignmentStatus { CANCELED = 'CANCELED', REJECTED = 'REJECTED', } + +export enum AssignmentSortField { + CHAIN_ID = 'chain_id', + JOB_TYPE = 'job_type', + STATUS = 'status', + REWARD_AMOUNT = 'reward_amount', + CREATED_AT = 'created_at', + EXPIRES_AT = 'expires_at', +} + +export enum JobType { + FORTUNE = 'FORTUNE', +} diff --git a/packages/apps/fortune/exchange-oracle/server/src/common/enums/webhook.ts b/packages/apps/fortune/exchange-oracle/server/src/common/enums/webhook.ts index da91d77bd1..eb841196c4 100644 --- a/packages/apps/fortune/exchange-oracle/server/src/common/enums/webhook.ts +++ b/packages/apps/fortune/exchange-oracle/server/src/common/enums/webhook.ts @@ -3,6 +3,7 @@ export enum EventType { ESCROW_CANCELED = 'escrow_canceled', TASK_CREATION_FAILED = 'task_creation_failed', SUBMISSION_REJECTED = 'submission_rejected', + SUBMISSION_IN_REVIEW = 'submission_in_review', } export enum WebhookStatus { diff --git a/packages/apps/fortune/exchange-oracle/server/src/common/guards/cron.auth.ts b/packages/apps/fortune/exchange-oracle/server/src/common/guards/cron.auth.ts new file mode 100644 index 0000000000..b368476bbf --- /dev/null +++ b/packages/apps/fortune/exchange-oracle/server/src/common/guards/cron.auth.ts @@ -0,0 +1,25 @@ +import { + CanActivate, + ExecutionContext, + Injectable, + UnauthorizedException, +} from '@nestjs/common'; +import { ConfigService } from '@nestjs/config'; +import { ConfigNames } from '../config'; + +@Injectable() +export class CronAuthGuard implements CanActivate { + constructor(private readonly configService: ConfigService) {} + + public async canActivate(context: ExecutionContext): Promise { + const request = context.switchToHttp().getRequest(); + const cronSecret = this.configService.get(ConfigNames.CRON_SECRET); + if ( + cronSecret && + request.headers['authorization'] === `Bearer ${cronSecret}` + ) { + throw new UnauthorizedException('Unauthorized'); + } + return true; + } +} diff --git a/packages/apps/fortune/exchange-oracle/server/src/common/guards/signature.auth.spec.ts b/packages/apps/fortune/exchange-oracle/server/src/common/guards/signature.auth.spec.ts index b0845b5393..3228e86231 100644 --- a/packages/apps/fortune/exchange-oracle/server/src/common/guards/signature.auth.spec.ts +++ b/packages/apps/fortune/exchange-oracle/server/src/common/guards/signature.auth.spec.ts @@ -60,8 +60,8 @@ describe('SignatureAuthGuard', () => { it('should return true if signature is verified', async () => { mockRequest.headers[HEADER_SIGNATURE_KEY] = 'validSignature'; mockRequest.body = { - escrowAddress: MOCK_ADDRESS, - chainId: ChainId.LOCALHOST, + escrow_address: MOCK_ADDRESS, + chain_id: ChainId.LOCALHOST, }; (verifySignature as jest.Mock).mockReturnValue(true); diff --git a/packages/apps/fortune/exchange-oracle/server/src/common/guards/signature.auth.ts b/packages/apps/fortune/exchange-oracle/server/src/common/guards/signature.auth.ts index 8ecc9d0372..d43041d611 100644 --- a/packages/apps/fortune/exchange-oracle/server/src/common/guards/signature.auth.ts +++ b/packages/apps/fortune/exchange-oracle/server/src/common/guards/signature.auth.ts @@ -21,8 +21,8 @@ export class SignatureAuthGuard implements CanActivate { const oracleAdresses: string[] = []; try { const escrowData = await EscrowUtils.getEscrow( - data.chainId, - data.escrowAddress, + data.chain_id, + data.escrow_address, ); if (this.role.includes(Role.JobLauncher)) oracleAdresses.push(escrowData.launcher); diff --git a/packages/apps/fortune/exchange-oracle/server/src/common/guards/strategy/jwt.http.ts b/packages/apps/fortune/exchange-oracle/server/src/common/guards/strategy/jwt.http.ts index 6ed09189cd..18b0e7a972 100644 --- a/packages/apps/fortune/exchange-oracle/server/src/common/guards/strategy/jwt.http.ts +++ b/packages/apps/fortune/exchange-oracle/server/src/common/guards/strategy/jwt.http.ts @@ -4,7 +4,9 @@ import { ExtractJwt, Strategy } from 'passport-jwt'; import { KVStoreClient, StorageClient } from '@human-protocol/sdk'; import * as jwt from 'jsonwebtoken'; -import { Web3Service } from 'src/modules/web3/web3.service'; +import { Web3Service } from '../../../modules/web3/web3.service'; +import { JwtUser } from '../../../common/types/jwt'; +import { JWT_KVSTORE_KEY, KYC_APPROVED } from '../../../common/constant'; @Injectable() export class JwtHttpStrategy extends PassportStrategy(Strategy, 'jwt-http') { @@ -26,7 +28,7 @@ export class JwtHttpStrategy extends PassportStrategy(Strategy, 'jwt-http') { const url = await kvstoreClient.getFileUrlAndVerifyHash( (payload as any).reputation_network, - 'jwt_public_key', + JWT_KVSTORE_KEY, ); const publicKey = await StorageClient.downloadFileFromUrl(url); @@ -42,14 +44,24 @@ export class JwtHttpStrategy extends PassportStrategy(Strategy, 'jwt-http') { public async validate( @Req() request: any, - payload: { email: string; address: string; kyc_status: string }, - ): Promise { + payload: { + email: string; + address: string; + kyc_status: string; + reputation_network: string; + }, + ): Promise { if (!payload.kyc_status || !payload.email || !payload.address) { throw new UnauthorizedException('Invalid token'); } - if (payload.kyc_status !== 'approved') { + if (payload.kyc_status !== KYC_APPROVED) { throw new UnauthorizedException('Invalid KYC status'); } - return true; + return { + address: payload.address, + email: payload.email, + kycStatus: payload.kyc_status, + reputationNetwork: payload.reputation_network, + }; } } diff --git a/packages/apps/fortune/exchange-oracle/server/src/common/interfaces/base.ts b/packages/apps/fortune/exchange-oracle/server/src/common/interfaces/base.ts new file mode 100644 index 0000000000..8ad596c4d9 --- /dev/null +++ b/packages/apps/fortune/exchange-oracle/server/src/common/interfaces/base.ts @@ -0,0 +1,5 @@ +export interface IBase { + id: number; + createdAt: Date; + updatedAt: Date; +} diff --git a/packages/apps/fortune/exchange-oracle/server/src/common/interfaces/cron-job.ts b/packages/apps/fortune/exchange-oracle/server/src/common/interfaces/cron-job.ts new file mode 100644 index 0000000000..1335866ba9 --- /dev/null +++ b/packages/apps/fortune/exchange-oracle/server/src/common/interfaces/cron-job.ts @@ -0,0 +1,8 @@ +import { CronJobType } from '../enums/cron-job'; +import { IBase } from './base'; + +export interface ICronJob extends IBase { + cronJobType: CronJobType; + startedAt: Date; + completedAt?: Date | null; +} diff --git a/packages/apps/fortune/exchange-oracle/server/src/common/pagination/pagination.dto.ts b/packages/apps/fortune/exchange-oracle/server/src/common/pagination/pagination.dto.ts new file mode 100644 index 0000000000..c03c19050f --- /dev/null +++ b/packages/apps/fortune/exchange-oracle/server/src/common/pagination/pagination.dto.ts @@ -0,0 +1,79 @@ +import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; +import { + IsEnum, + IsNumber, + IsOptional, + Max, + Min, + IsArray, +} from 'class-validator'; +import { Type } from 'class-transformer'; +import { SortDirection } from '../enums/collection'; + +export class PageDto { + @ApiProperty() + readonly page: number; + + @ApiProperty() + readonly pageSize: number; + + @ApiProperty() + readonly totalPages: number; + + @ApiProperty() + readonly totalResults: number; + + @IsArray() + @ApiProperty({ isArray: true }) + readonly results: T[]; + + constructor( + page: number, + pageSize: number, + totalResults: number, + results: T[], + ) { + this.page = page; + this.pageSize = pageSize; + this.totalPages = Math.ceil(totalResults / pageSize) || 0; + this.totalResults = totalResults; + this.results = results; + } +} + +export abstract class PageOptionsDto { + @ApiPropertyOptional({ + minimum: 0, + default: 0, + }) + @Type(() => Number) + @IsNumber() + @Min(0) + @IsOptional() + page?: number = 0; + + @ApiPropertyOptional({ + minimum: 1, + maximum: 10, + default: 5, + name: 'page_size', + }) + @Type(() => Number) + @IsNumber() + @Min(1) + @Max(10) + @IsOptional() + pageSize?: number = 5; + + @ApiPropertyOptional({ enum: SortDirection, default: SortDirection.ASC }) + @IsEnum(SortDirection) + @IsOptional() + sort?: SortDirection = SortDirection.ASC; + + @IsOptional() + abstract sortField?: any; + + get skip(): number { + return this.page! * this.pageSize!; + } +} diff --git a/packages/apps/fortune/exchange-oracle/server/src/common/types/jwt.ts b/packages/apps/fortune/exchange-oracle/server/src/common/types/jwt.ts new file mode 100644 index 0000000000..9f834446d7 --- /dev/null +++ b/packages/apps/fortune/exchange-oracle/server/src/common/types/jwt.ts @@ -0,0 +1,10 @@ +export interface RequestWithUser extends Request { + user: JwtUser; +} + +export interface JwtUser { + email: string; + address: string; + kycStatus: string; + reputationNetwork: string; +} diff --git a/packages/apps/fortune/exchange-oracle/server/src/database/database.module.ts b/packages/apps/fortune/exchange-oracle/server/src/database/database.module.ts index 4292f81b18..f392e7165a 100644 --- a/packages/apps/fortune/exchange-oracle/server/src/database/database.module.ts +++ b/packages/apps/fortune/exchange-oracle/server/src/database/database.module.ts @@ -11,6 +11,7 @@ import { ConfigNames } from '../common/config'; import { JobEntity } from '../modules/job/job.entity'; import { AssignmentEntity } from '../modules/assignment/assignment.entity'; import { WebhookEntity } from '../modules/webhook/webhook.entity'; +import { CronJobEntity } from '../modules/cron-job/cron-job.entity'; @Module({ imports: [ @@ -32,7 +33,7 @@ import { WebhookEntity } from '../modules/webhook/webhook.entity'; return { name: 'default', type: 'postgres', - entities: [JobEntity, AssignmentEntity, WebhookEntity], + entities: [JobEntity, AssignmentEntity, WebhookEntity, CronJobEntity], // We are using migrations, synchronize should be set to false. synchronize: false, // Run migrations automatically, @@ -47,22 +48,29 @@ import { WebhookEntity } from '../modules/webhook/webhook.entity'; migrations: [path.join(__dirname, '/migrations/**/*{.ts,.js}')], //"migrations": ["dist/migrations/*{.ts,.js}"], logger: typeOrmLoggerService, - host: configService.get( + url: configService.get( + ConfigNames.POSTGRES_URL, + undefined, + ), + host: configService.get( ConfigNames.POSTGRES_HOST, - 'localhost', + undefined, + ), + port: configService.get( + ConfigNames.POSTGRES_PORT, + undefined, ), - port: configService.get(ConfigNames.POSTGRES_PORT, 5432), - username: configService.get( + username: configService.get( ConfigNames.POSTGRES_USER, - 'operator', + undefined, ), - password: configService.get( + password: configService.get( ConfigNames.POSTGRES_PASSWORD, - 'qwerty', + undefined, ), - database: configService.get( + database: configService.get( ConfigNames.POSTGRES_DATABASE, - 'exchange-oracle', + undefined, ), keepConnectionAlive: configService.get(ConfigNames.NODE_ENV) === 'test', diff --git a/packages/apps/fortune/exchange-oracle/server/src/database/migrations/1710248133605-initialMigration.ts b/packages/apps/fortune/exchange-oracle/server/src/database/migrations/1710248133605-initialMigration.ts deleted file mode 100644 index 6509dafc6f..0000000000 --- a/packages/apps/fortune/exchange-oracle/server/src/database/migrations/1710248133605-initialMigration.ts +++ /dev/null @@ -1,114 +0,0 @@ -import { NS } from '../../common/constant'; -import { MigrationInterface, QueryRunner } from 'typeorm'; - -export class InitialMigration1710248133605 implements MigrationInterface { - name = 'InitialMigration1710248133605'; - - public async up(queryRunner: QueryRunner): Promise { - await queryRunner.createSchema(NS, true); - await queryRunner.query(` - CREATE TYPE "hmt"."assignment_status_enum" AS ENUM( - 'ACTIVE', - 'VALIDATION', - 'COMPLETED', - 'EXPIRED', - 'CANCELED', - 'REJECTED' - ) - `); - await queryRunner.query(` - CREATE TABLE "hmt"."assignment" ( - "id" SERIAL NOT NULL, - "created_at" TIMESTAMP WITH TIME ZONE NOT NULL, - "updated_at" TIMESTAMP WITH TIME ZONE NOT NULL, - "job_id" integer NOT NULL, - "worker_address" character varying NOT NULL, - "status" "hmt"."assignment_status_enum" NOT NULL, - CONSTRAINT "PK_43c2f5a3859f54cedafb270f37e" PRIMARY KEY ("id") - ) - `); - await queryRunner.query(` - CREATE UNIQUE INDEX "IDX_14aa5962b550a5aaf5a6b6ab61" ON "hmt"."assignment" ("job_id", "worker_address") - `); - await queryRunner.query(` - CREATE TYPE "hmt"."job_status_enum" AS ENUM('ACTIVE', 'COMPLETED', 'CANCELED') - `); - await queryRunner.query(` - CREATE TABLE "hmt"."job" ( - "id" SERIAL NOT NULL, - "created_at" TIMESTAMP WITH TIME ZONE NOT NULL, - "updated_at" TIMESTAMP WITH TIME ZONE NOT NULL, - "chain_id" integer, - "escrow_address" character varying, - "status" "hmt"."job_status_enum" NOT NULL, - CONSTRAINT "PK_98ab1c14ff8d1cf80d18703b92f" PRIMARY KEY ("id") - ) - `); - await queryRunner.query(` - CREATE UNIQUE INDEX "IDX_69bc2165dddf5544733838adbe" ON "hmt"."job" ("chain_id", "escrow_address") - `); - await queryRunner.query(` - CREATE TYPE "hmt"."webhook_event_type_enum" AS ENUM( - 'escrow_created', - 'escrow_canceled', - 'task_creation_failed', - 'submission_rejected' - ) - `); - await queryRunner.query(` - CREATE TYPE "hmt"."webhook_status_enum" AS ENUM('PENDING', 'COMPLETED', 'FAILED') - `); - await queryRunner.query(` - CREATE TABLE "hmt"."webhook" ( - "id" SERIAL NOT NULL, - "created_at" TIMESTAMP WITH TIME ZONE NOT NULL, - "updated_at" TIMESTAMP WITH TIME ZONE NOT NULL, - "chain_id" integer NOT NULL, - "escrow_address" character varying NOT NULL, - "event_type" "hmt"."webhook_event_type_enum" NOT NULL, - "retries_count" integer NOT NULL, - "wait_until" TIMESTAMP WITH TIME ZONE NOT NULL, - "status" "hmt"."webhook_status_enum" NOT NULL, - CONSTRAINT "PK_e6765510c2d078db49632b59020" PRIMARY KEY ("id") - ) - `); - await queryRunner.query(` - ALTER TABLE "hmt"."assignment" - ADD CONSTRAINT "FK_e2262a906a37770a56a1a7168d4" FOREIGN KEY ("job_id") REFERENCES "hmt"."job"("id") ON DELETE NO ACTION ON UPDATE NO ACTION - `); - } - - public async down(queryRunner: QueryRunner): Promise { - await queryRunner.query(` - ALTER TABLE "hmt"."assignment" DROP CONSTRAINT "FK_e2262a906a37770a56a1a7168d4" - `); - await queryRunner.query(` - DROP TABLE "hmt"."webhook" - `); - await queryRunner.query(` - DROP TYPE "hmt"."webhook_status_enum" - `); - await queryRunner.query(` - DROP TYPE "hmt"."webhook_event_type_enum" - `); - await queryRunner.query(` - DROP INDEX "hmt"."IDX_69bc2165dddf5544733838adbe" - `); - await queryRunner.query(` - DROP TABLE "hmt"."job" - `); - await queryRunner.query(` - DROP TYPE "hmt"."job_status_enum" - `); - await queryRunner.query(` - DROP INDEX "hmt"."IDX_14aa5962b550a5aaf5a6b6ab61" - `); - await queryRunner.query(` - DROP TABLE "hmt"."assignment" - `); - await queryRunner.query(` - DROP TYPE "hmt"."assignment_status_enum" - `); - await queryRunner.dropSchema(NS); - } -} diff --git a/packages/apps/fortune/exchange-oracle/server/src/database/migrations/1710871038249-initialMigration.ts b/packages/apps/fortune/exchange-oracle/server/src/database/migrations/1710871038249-initialMigration.ts new file mode 100644 index 0000000000..dfdc3e440b --- /dev/null +++ b/packages/apps/fortune/exchange-oracle/server/src/database/migrations/1710871038249-initialMigration.ts @@ -0,0 +1,143 @@ +import { NS } from '../../common/constant'; +import { MigrationInterface, QueryRunner } from 'typeorm'; + +export class InitialMigration1710871038249 implements MigrationInterface { + name = 'InitialMigration1710871038249'; + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.createSchema(NS, true); + await queryRunner.query(` + CREATE TYPE "hmt"."webhooks_event_type_enum" AS ENUM( + 'escrow_created', + 'escrow_canceled', + 'task_creation_failed', + 'submission_rejected', + 'submission_in_review' + ) + `); + await queryRunner.query(` + CREATE TYPE "hmt"."webhooks_status_enum" AS ENUM('PENDING', 'COMPLETED', 'FAILED') + `); + await queryRunner.query(` + CREATE TABLE "hmt"."webhooks" ( + "id" SERIAL NOT NULL, + "created_at" TIMESTAMP WITH TIME ZONE NOT NULL, + "updated_at" TIMESTAMP WITH TIME ZONE NOT NULL, + "chain_id" integer NOT NULL, + "escrow_address" character varying NOT NULL, + "event_type" "hmt"."webhooks_event_type_enum" NOT NULL, + "retries_count" integer NOT NULL, + "wait_until" TIMESTAMP WITH TIME ZONE NOT NULL, + "status" "hmt"."webhooks_status_enum" NOT NULL, + CONSTRAINT "PK_9e8795cfc899ab7bdaa831e8527" PRIMARY KEY ("id") + ) + `); + await queryRunner.query(` + CREATE TYPE "hmt"."cron-jobs_cron_job_type_enum" AS ENUM('process-pending-webhook') + `); + await queryRunner.query(` + CREATE TABLE "hmt"."cron-jobs" ( + "id" SERIAL NOT NULL, + "created_at" TIMESTAMP WITH TIME ZONE NOT NULL, + "updated_at" TIMESTAMP WITH TIME ZONE NOT NULL, + "cron_job_type" "hmt"."cron-jobs_cron_job_type_enum" NOT NULL, + "started_at" TIMESTAMP WITH TIME ZONE NOT NULL, + "completed_at" TIMESTAMP WITH TIME ZONE, + CONSTRAINT "PK_268498ac0d3e7472960fb0faeb1" PRIMARY KEY ("id") + ) + `); + await queryRunner.query(` + CREATE UNIQUE INDEX "IDX_0dafd70b737e71d21490ad0126" ON "hmt"."cron-jobs" ("cron_job_type") + `); + await queryRunner.query(` + CREATE TYPE "hmt"."assignments_status_enum" AS ENUM( + 'ACTIVE', + 'VALIDATION', + 'COMPLETED', + 'EXPIRED', + 'CANCELED', + 'REJECTED' + ) + `); + await queryRunner.query(` + CREATE TABLE "hmt"."assignments" ( + "id" SERIAL NOT NULL, + "created_at" TIMESTAMP WITH TIME ZONE NOT NULL, + "updated_at" TIMESTAMP WITH TIME ZONE NOT NULL, + "job_id" integer NOT NULL, + "worker_address" character varying NOT NULL, + "status" "hmt"."assignments_status_enum" NOT NULL, + "expires_at" TIMESTAMP WITH TIME ZONE NOT NULL, + CONSTRAINT "PK_c54ca359535e0012b04dcbd80ee" PRIMARY KEY ("id") + ) + `); + await queryRunner.query(` + CREATE UNIQUE INDEX "IDX_f9fea6dcc065d190ed04d7f9d4" ON "hmt"."assignments" ("job_id", "worker_address") + `); + await queryRunner.query(` + CREATE TYPE "hmt"."jobs_status_enum" AS ENUM('ACTIVE', 'COMPLETED', 'CANCELED') + `); + await queryRunner.query(` + CREATE TABLE "hmt"."jobs" ( + "id" SERIAL NOT NULL, + "created_at" TIMESTAMP WITH TIME ZONE NOT NULL, + "updated_at" TIMESTAMP WITH TIME ZONE NOT NULL, + "chain_id" integer NOT NULL, + "escrow_address" character varying NOT NULL, + "status" "hmt"."jobs_status_enum" NOT NULL, + "reputation_network" character varying NOT NULL, + CONSTRAINT "PK_cf0a6c42b72fcc7f7c237def345" PRIMARY KEY ("id") + ) + `); + await queryRunner.query(` + CREATE UNIQUE INDEX "IDX_59f6c552b618c432f019500e7c" ON "hmt"."jobs" ("chain_id", "escrow_address") + `); + await queryRunner.query(` + ALTER TABLE "hmt"."assignments" + ADD CONSTRAINT "FK_4a6cf5345a71aa620ee6a0d9c8c" FOREIGN KEY ("job_id") REFERENCES "hmt"."jobs"("id") ON DELETE NO ACTION ON UPDATE NO ACTION + `); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + ALTER TABLE "hmt"."assignments" DROP CONSTRAINT "FK_4a6cf5345a71aa620ee6a0d9c8c" + `); + await queryRunner.query(` + DROP INDEX "hmt"."IDX_59f6c552b618c432f019500e7c" + `); + await queryRunner.query(` + DROP TABLE "hmt"."jobs" + `); + await queryRunner.query(` + DROP TYPE "hmt"."jobs_status_enum" + `); + await queryRunner.query(` + DROP INDEX "hmt"."IDX_f9fea6dcc065d190ed04d7f9d4" + `); + await queryRunner.query(` + DROP TABLE "hmt"."assignments" + `); + await queryRunner.query(` + DROP TYPE "hmt"."assignments_status_enum" + `); + await queryRunner.query(` + DROP INDEX "hmt"."IDX_0dafd70b737e71d21490ad0126" + `); + await queryRunner.query(` + DROP TABLE "hmt"."cron-jobs" + `); + await queryRunner.query(` + DROP TYPE "hmt"."cron-jobs_cron_job_type_enum" + `); + await queryRunner.query(` + DROP TABLE "hmt"."webhooks" + `); + await queryRunner.query(` + DROP TYPE "hmt"."webhooks_status_enum" + `); + await queryRunner.query(` + DROP TYPE "hmt"."webhooks_event_type_enum" + `); + await queryRunner.dropSchema(NS); + } +} diff --git a/packages/apps/fortune/exchange-oracle/server/src/main.ts b/packages/apps/fortune/exchange-oracle/server/src/main.ts index b58ec80f15..ed46be64d0 100644 --- a/packages/apps/fortune/exchange-oracle/server/src/main.ts +++ b/packages/apps/fortune/exchange-oracle/server/src/main.ts @@ -6,7 +6,7 @@ import { useContainer } from 'class-validator'; import { AppModule } from './app.module'; import { ConfigNames } from './common/config'; -import { INestApplication } from '@nestjs/common'; +import { INestApplication, ValidationPipe } from '@nestjs/common'; async function bootstrap() { const app = await NestFactory.create(AppModule, { @@ -18,20 +18,20 @@ async function bootstrap() { const host = configService.get(ConfigNames.HOST)!; const port = configService.get(ConfigNames.PORT)!; - app.enableCors({ - origin: - process.env.NODE_ENV === 'development' || - process.env.NODE_ENV === 'staging' - ? [ - `http://localhost:${port}`, - `http://127.0.0.1:${port}`, - `http://0.0.0.0:${port}`, - `http://${host}:${port}`, - ] - : [`http://${host}:${port}`], - credentials: true, - exposedHeaders: ['Content-Disposition'], - }); + // app.enableCors({ + // origin: + // process.env.NODE_ENV === 'development' || + // process.env.NODE_ENV === 'staging' + // ? [ + // `http://localhost:${port}`, + // `http://127.0.0.1:${port}`, + // `http://0.0.0.0:${port}`, + // `http://${host}:${port}`, + // ] + // : [`http://${host}:${port}`], + // credentials: true, + // exposedHeaders: ['Content-Disposition'], + // }); useContainer(app.select(AppModule), { fallbackOnErrors: true }); @@ -47,6 +47,8 @@ async function bootstrap() { const document = SwaggerModule.createDocument(app, config); SwaggerModule.setup('swagger', app, document); + app.useGlobalPipes(new ValidationPipe({ transform: true })); + await app.listen(port, host, async () => { console.info(`API server is running on http://${host}:${port}`); }); diff --git a/packages/apps/fortune/exchange-oracle/server/src/modules/assignment/assignment.controller.spec.ts b/packages/apps/fortune/exchange-oracle/server/src/modules/assignment/assignment.controller.spec.ts new file mode 100644 index 0000000000..7c064e4a27 --- /dev/null +++ b/packages/apps/fortune/exchange-oracle/server/src/modules/assignment/assignment.controller.spec.ts @@ -0,0 +1,89 @@ +import { createMock } from '@golevelup/ts-jest'; +import { Test } from '@nestjs/testing'; +import { AssignmentController } from './assignment.controller'; +import { AssignmentService } from './assignment.service'; +import { RequestWithUser } from '../../common/types/jwt'; +import { GetAssignmentsDto, CreateAssignmentDto } from './assignment.dto'; +import { AssignmentStatus, JobType } from '../../common/enums/job'; +import { MOCK_EXCHANGE_ORACLE } from '../../../test/constants'; + +jest.mock('../../common/utils/signature'); + +describe('assignmentController', () => { + let assignmentController: AssignmentController; + let assignmentService: AssignmentService; + const escrowAddress = '0x1234567890123456789012345678901234567890'; + const userAddress = '0x1234567890123456789012345678901234567891'; + const reputationNetwork = '0x1234567890123456789012345678901234567892'; + + beforeAll(async () => { + const moduleRef = await Test.createTestingModule({ + imports: [], + controllers: [AssignmentController], + providers: [ + { + provide: AssignmentService, + useValue: createMock(), + }, + ], + }).compile(); + + assignmentController = + moduleRef.get(AssignmentController); + assignmentService = moduleRef.get(AssignmentService); + }); + + describe('processWebhook', () => { + it('should call assignmentService.getAssignmentList', async () => { + const query: GetAssignmentsDto = { + chainId: 80001, + jobType: JobType.FORTUNE, + escrowAddress: escrowAddress, + status: AssignmentStatus.ACTIVE, + skip: 1, + }; + const expectedResult = { + page: 0, + pageSize: 0, + totalPages: 0, + totalResults: 0, + results: [], + }; + jest + .spyOn(assignmentService, 'getAssignmentList') + .mockResolvedValue(expectedResult); + + const result = await assignmentController.getAssignments( + { + user: { address: userAddress, reputationNetwork: reputationNetwork }, + headers: { referer: MOCK_EXCHANGE_ORACLE }, + } as any, + query, + ); + expect(result).toBe(expectedResult); + expect(assignmentService.getAssignmentList).toHaveBeenCalledWith( + query, + userAddress, + reputationNetwork, + MOCK_EXCHANGE_ORACLE, + ); + }); + + it('should call assignmentService.createAssignment', async () => { + const body: CreateAssignmentDto = { + chainId: 80001, + escrowAddress: escrowAddress, + }; + jest.spyOn(assignmentService, 'createAssignment').mockResolvedValue(); + await assignmentController.createAssignment( + { + user: { address: userAddress }, + } as RequestWithUser, + body, + ); + expect(assignmentService.createAssignment).toHaveBeenCalledWith(body, { + address: userAddress, + }); + }); + }); +}); diff --git a/packages/apps/fortune/exchange-oracle/server/src/modules/assignment/assignment.controller.ts b/packages/apps/fortune/exchange-oracle/server/src/modules/assignment/assignment.controller.ts new file mode 100644 index 0000000000..cfa5361def --- /dev/null +++ b/packages/apps/fortune/exchange-oracle/server/src/modules/assignment/assignment.controller.ts @@ -0,0 +1,100 @@ +import { + Body, + Controller, + Post, + UseGuards, + Request, + Get, + Query, +} from '@nestjs/common'; +import { + ApiBody, + ApiResponse, + ApiBearerAuth, + ApiOperation, + ApiTags, +} from '@nestjs/swagger'; +import { JwtAuthGuard } from '../../common/guards/jwt.auth'; +import { AssignmentService } from './assignment.service'; +import { + AssignmentDto, + CreateAssignmentDto, + GetAssignmentsDto, +} from './assignment.dto'; +import { RequestWithUser } from '../../common/types/jwt'; +import { PageDto } from '../../common/pagination/pagination.dto'; + +@ApiTags('Assignment') +@Controller('assignment') +@UseGuards(JwtAuthGuard) +@ApiBearerAuth() +export class AssignmentController { + constructor(private readonly assignmentService: AssignmentService) {} + + @ApiOperation({ + summary: 'Create Assignment', + description: 'Endpoint to create a new assignment.', + }) + @ApiBody({ + description: 'Details of the assignment to be created.', + type: CreateAssignmentDto, + }) + @ApiBearerAuth() + @ApiResponse({ + status: 200, + description: 'Assignment created successfully.', + }) + @ApiResponse({ + status: 400, + description: 'Bad Request. Invalid input parameters.', + }) + @ApiResponse({ + status: 401, + description: 'Unauthorized. Missing or invalid credentials.', + }) + @Post() + createAssignment( + @Request() req: RequestWithUser, + @Body() body: CreateAssignmentDto, + ): Promise { + return this.assignmentService.createAssignment(body, req.user); + } + + @ApiOperation({ + summary: 'Get Assignments', + description: 'Endpoint to retrieve a list of assignments.', + }) + @ApiBearerAuth() + @ApiResponse({ + status: 200, + description: 'List of assignments retrieved successfully.', + type: PageDto, + }) + @ApiResponse({ + status: 400, + description: 'Bad Request. Invalid input parameters.', + }) + @ApiResponse({ + status: 401, + description: 'Unauthorized. Missing or invalid credentials.', + }) + @Get() + getAssignments( + @Request() req: RequestWithUser, + @Query() query: GetAssignmentsDto, + ): Promise> { + const serverUrl = (req.headers as any).referer.slice( + 0, + (req.headers as any).referer.indexOf( + '/', + (req.headers as any).referer.indexOf('//') + 2, + ) + 1, + ); + return this.assignmentService.getAssignmentList( + query, + req.user.address, + req.user.reputationNetwork, + serverUrl, + ); + } +} diff --git a/packages/apps/fortune/exchange-oracle/server/src/modules/assignment/assignment.dto.ts b/packages/apps/fortune/exchange-oracle/server/src/modules/assignment/assignment.dto.ts new file mode 100644 index 0000000000..0a955d850a --- /dev/null +++ b/packages/apps/fortune/exchange-oracle/server/src/modules/assignment/assignment.dto.ts @@ -0,0 +1,114 @@ +import { ChainId } from '@human-protocol/sdk'; +import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; +import { Type } from 'class-transformer'; +import { IsEnum, IsNumber, IsOptional, IsString } from 'class-validator'; +import { + AssignmentSortField, + AssignmentStatus, + JobSortField, + JobType, +} from '../../common/enums/job'; +import { PageOptionsDto } from '../../common/pagination/pagination.dto'; + +export class CreateAssignmentDto { + @ApiProperty({ + enum: ChainId, + name: 'chain_id', + required: false, + }) + @IsEnum(ChainId) + chainId: ChainId; + + @ApiProperty({ name: 'escrow_address' }) + @IsString() + escrowAddress: string; +} + +export class GetAssignmentsDto extends PageOptionsDto { + @ApiPropertyOptional({ + name: 'sort_field', + enum: JobSortField, + default: JobSortField.CREATED_AT, + }) + @IsOptional() + @IsEnum(AssignmentSortField) + sortField?: AssignmentSortField = AssignmentSortField.CREATED_AT; + + @ApiPropertyOptional({ name: 'chain_id' }) + @IsOptional() + @Type(() => Number) + @IsNumber() + chainId: number; + + @ApiPropertyOptional({ name: 'job_type', enum: JobType }) + @IsEnum(JobType) + @IsOptional() + jobType: JobType; + + @ApiPropertyOptional({ name: 'escrow_address' }) + @IsOptional() + @IsString() + escrowAddress: string; + + @ApiPropertyOptional({ enum: AssignmentStatus }) + @IsEnum(AssignmentStatus) + @IsOptional() + status: AssignmentStatus; +} + +export class AssignmentDto { + @ApiProperty({ name: 'assignment_id' }) + assignmentId: number; + + @ApiProperty({ name: 'escrow_address' }) + escrowAddress: string; + + @ApiProperty({ name: 'chain_id' }) + chainId: number; + + @ApiProperty({ name: 'job_type' }) + jobType: string; + + @ApiProperty() + status: AssignmentStatus; + + @ApiPropertyOptional() + url?: string; + + @ApiProperty({ name: 'reward_amount' }) + rewardAmount: number; + + @ApiProperty({ name: 'reward_token' }) + rewardToken: string; + + @ApiProperty({ name: 'created_at' }) + createdAt: string; + + @ApiPropertyOptional({ name: 'updated_at' }) + updatedAt?: string; + + @ApiProperty({ name: 'expires_at' }) + expiresAt: string; + + constructor( + assignmentId: number, + escrowAddress: string, + chainId: number, + jobType: string, + status: AssignmentStatus, + rewardAmount: number, + rewardToken: string, + createdAt: string, + expiresAt: string, + ) { + this.assignmentId = assignmentId; + this.escrowAddress = escrowAddress; + this.chainId = chainId; + this.jobType = jobType; + this.status = status; + this.rewardAmount = rewardAmount; + this.rewardToken = rewardToken; + this.createdAt = createdAt; + this.expiresAt = expiresAt; + } +} diff --git a/packages/apps/fortune/exchange-oracle/server/src/modules/assignment/assignment.entity.ts b/packages/apps/fortune/exchange-oracle/server/src/modules/assignment/assignment.entity.ts index 495fcbb454..a59223fc1f 100644 --- a/packages/apps/fortune/exchange-oracle/server/src/modules/assignment/assignment.entity.ts +++ b/packages/apps/fortune/exchange-oracle/server/src/modules/assignment/assignment.entity.ts @@ -5,7 +5,7 @@ import { AssignmentStatus } from '../../common/enums/job'; import { BaseEntity } from '../../database/base.entity'; import { JobEntity } from '../job/job.entity'; -@Entity({ schema: NS, name: 'assignment' }) +@Entity({ schema: NS, name: 'assignments' }) @Index(['jobId', 'workerAddress'], { unique: true }) export class AssignmentEntity extends BaseEntity { @Column({ type: 'int' }) @@ -20,6 +20,9 @@ export class AssignmentEntity extends BaseEntity { }) public status: AssignmentStatus; + @Column({ type: 'timestamptz' }) + public expiresAt: Date; + @ManyToOne(() => JobEntity, (job) => job.assignments, { eager: true }) job: JobEntity; } diff --git a/packages/apps/fortune/exchange-oracle/server/src/modules/assignment/assignment.interface.ts b/packages/apps/fortune/exchange-oracle/server/src/modules/assignment/assignment.interface.ts new file mode 100644 index 0000000000..aff666daf3 --- /dev/null +++ b/packages/apps/fortune/exchange-oracle/server/src/modules/assignment/assignment.interface.ts @@ -0,0 +1,20 @@ +import { SortDirection } from '../../common/enums/collection'; +import { AssignmentSortField, AssignmentStatus } from '../../common/enums/job'; +import { AssignmentEntity } from './assignment.entity'; + +export interface AssignmentFilterData { + chainId?: number; + escrowAddress?: string; + status?: AssignmentStatus; + sortField?: AssignmentSortField; + sort?: SortDirection; + skip: number; + pageSize: number; + workerAddress: string; + reputationNetwork: string; +} + +export interface ListResult { + entities: AssignmentEntity[]; + itemCount: number; +} diff --git a/packages/apps/fortune/exchange-oracle/server/src/modules/assignment/assignment.module.ts b/packages/apps/fortune/exchange-oracle/server/src/modules/assignment/assignment.module.ts new file mode 100644 index 0000000000..4974ff3dfc --- /dev/null +++ b/packages/apps/fortune/exchange-oracle/server/src/modules/assignment/assignment.module.ts @@ -0,0 +1,25 @@ +import { Module } from '@nestjs/common'; +import { TypeOrmModule } from '@nestjs/typeorm'; +import { JobEntity } from '../job/job.entity'; +import { ConfigModule } from '@nestjs/config'; +import { AssignmentController } from './assignment.controller'; +import { AssignmentEntity } from './assignment.entity'; +import { AssignmentRepository } from './assignment.repository'; +import { AssignmentService } from './assignment.service'; +import { JobRepository } from '../job/job.repository'; +import { JobModule } from '../job/job.module'; +import { Web3Module } from '../web3/web3.module'; + +@Module({ + imports: [ + TypeOrmModule.forFeature([AssignmentEntity]), + TypeOrmModule.forFeature([JobEntity]), + ConfigModule, + JobModule, + Web3Module, + ], + controllers: [AssignmentController], + providers: [AssignmentService, AssignmentRepository, JobRepository], + exports: [AssignmentService], +}) +export class AssignmentModule {} diff --git a/packages/apps/fortune/exchange-oracle/server/src/modules/assignment/assignment.repository.ts b/packages/apps/fortune/exchange-oracle/server/src/modules/assignment/assignment.repository.ts new file mode 100644 index 0000000000..7578c5a29e --- /dev/null +++ b/packages/apps/fortune/exchange-oracle/server/src/modules/assignment/assignment.repository.ts @@ -0,0 +1,161 @@ +import { Injectable } from '@nestjs/common'; +import { ConfigService } from '@nestjs/config'; +import { DataSource } from 'typeorm'; +import { BaseRepository } from '../../database/base.repository'; +import { AssignmentEntity } from './assignment.entity'; +import { AssignmentStatus } from '../../common/enums/job'; +import { ChainId } from '@human-protocol/sdk'; +import { AssignmentFilterData, ListResult } from './assignment.interface'; +import { AssignmentSortField } from '../../common/enums/job'; + +@Injectable() +export class AssignmentRepository extends BaseRepository { + constructor( + private dataSource: DataSource, + public readonly configService: ConfigService, + ) { + super(AssignmentEntity, dataSource); + } + + public async findByWorkerAddress( + workerAddress: string, + ): Promise { + return this.find({ + where: { + workerAddress, + }, + }); + } + + public async findOneByJobIdAndWorker( + jobId: number, + workerAddress: string, + ): Promise { + return this.findOne({ + where: { + jobId, + workerAddress, + }, + }); + } + + public async findOneByEscrowAndWorker( + escrowAddress: string, + workerAddress: string, + ): Promise { + return this.findOne({ + where: { + job: { escrowAddress }, + workerAddress, + }, + }); + } + + public async countByJobId(jobId: ChainId): Promise { + return this.count({ + where: { + jobId, + }, + }); + } + + public async countTotalAssignments(workerAddress?: string): Promise { + const where: any = {}; + if (workerAddress) { + where.workerAddress = workerAddress; + } + return this.count({ where }); + } + + public async countCompletedAssignments( + workerAddress?: string, + ): Promise { + const where: any = { status: AssignmentStatus.COMPLETED }; + if (workerAddress) { + where.workerAddress = workerAddress; + } + return this.count({ where }); + } + + public async countSentAssignments(workerAddress?: string): Promise { + const where: any = { status: AssignmentStatus.VALIDATION }; + if (workerAddress) { + where.workerAddress = workerAddress; + } + return this.count({ where }); + } + + public async countRejectedAssignments( + workerAddress?: string, + ): Promise { + const where: any = { status: AssignmentStatus.REJECTED }; + if (workerAddress) { + where.workerAddress = workerAddress; + } + return this.count({ where }); + } + + public async countExpiredAssignments( + workerAddress?: string, + ): Promise { + const where: any = { status: AssignmentStatus.EXPIRED }; + if (workerAddress) { + where.workerAddress = workerAddress; + } + return this.count({ where }); + } + + public async countTotalWorkers(): Promise { + const count = await this.createQueryBuilder('assignment') + .select('COUNT(DISTINCT assignment.workerAddress)', 'count') + .getRawOne(); + + return parseInt(count.count, 10); + } + + public async fetchFiltered(data: AssignmentFilterData): Promise { + const queryBuilder = await this.createQueryBuilder( + 'assignment', + ).leftJoinAndSelect('assignment.job', 'job', 'assignment.jobId = job.id'); + + if (data.sortField == AssignmentSortField.CHAIN_ID) + queryBuilder.orderBy(`job.${data.sortField}`, data.sort); + else if (data.sortField == AssignmentSortField.CREATED_AT) + queryBuilder.orderBy(`assignment.${data.sortField}`, data.sort); + else if (data.sortField == AssignmentSortField.STATUS) + queryBuilder.orderBy(`assignment.${data.sortField}`, data.sort); + else if (data.sortField == AssignmentSortField.EXPIRES_AT) + queryBuilder.orderBy(`assignment.${data.sortField}`, data.sort); + + if (data.chainId !== undefined) { + queryBuilder.andWhere('job.chainId = :chainId', { + chainId: data.chainId, + }); + } + if (data.escrowAddress) { + queryBuilder.andWhere('job.escrowAddress = :escrowAddress', { + escrowAddress: data.escrowAddress, + }); + } + if (data.status !== undefined) { + queryBuilder.andWhere('assignment.status = :status', { + status: data.status, + }); + } + + queryBuilder.andWhere('job.reputationNetwork = :reputationNetwork', { + reputationNetwork: data.reputationNetwork, + }); + + queryBuilder.andWhere('assignment.workerAddress = :workerAddress', { + workerAddress: data.workerAddress, + }); + + queryBuilder.offset(data.skip).limit(data.pageSize); + + const itemCount = await queryBuilder.getCount(); + const { entities } = await queryBuilder.getRawAndEntities(); + + return { entities, itemCount }; + } +} diff --git a/packages/apps/fortune/exchange-oracle/server/src/modules/assignment/assignment.service.spec.ts b/packages/apps/fortune/exchange-oracle/server/src/modules/assignment/assignment.service.spec.ts new file mode 100644 index 0000000000..46e49a5981 --- /dev/null +++ b/packages/apps/fortune/exchange-oracle/server/src/modules/assignment/assignment.service.spec.ts @@ -0,0 +1,297 @@ +import { createMock } from '@golevelup/ts-jest'; +import { ConfigService } from '@nestjs/config'; +import { Test } from '@nestjs/testing'; +import { + MOCK_EXCHANGE_ORACLE, + MOCK_PRIVATE_KEY, +} from '../../../test/constants'; +import { TOKEN } from '../../common/constant'; +import { AssignmentStatus, JobType } from '../../common/enums/job'; +import { AssignmentRepository } from '../assignment/assignment.repository'; +import { AssignmentService } from '../assignment/assignment.service'; +import { ManifestDto } from '../job/job.dto'; +import { JobRepository } from '../job/job.repository'; +import { JobService } from '../job/job.service'; +import { Web3Service } from '../web3/web3.service'; +import { AssignmentDto, CreateAssignmentDto } from './assignment.dto'; +import { Escrow__factory } from '@human-protocol/core/typechain-types'; + +jest.mock('@human-protocol/core/typechain-types', () => ({ + ...jest.requireActual('@human-protocol/core/typechain-types'), + Escrow__factory: { + connect: jest.fn(), + }, +})); + +describe('AssignmentService', () => { + let assignmentService: AssignmentService; + let assignmentRepository: AssignmentRepository; + let jobRepository: JobRepository; + let jobService: JobService; + + const chainId = 1; + const escrowAddress = '0x1234567890123456789012345678901234567890'; + const workerAddress = '0x1234567890123456789012345678901234567891'; + const reputationNetwork = '0x1234567890123456789012345678901234567892'; + + const signerMock = { + address: '0x1234567890123456789012345678901234567892', + getNetwork: jest.fn().mockResolvedValue({ chainId: 1 }), + }; + + const configServiceMock: Partial = { + get: jest.fn((key: string) => { + switch (key) { + case 'WEB3_PRIVATE_KEY': + return MOCK_PRIVATE_KEY; + } + }), + }; + + beforeAll(async () => { + const moduleRef = await Test.createTestingModule({ + imports: [], + providers: [ + AssignmentService, + { + provide: ConfigService, + useValue: configServiceMock, + }, + { + provide: Web3Service, + useValue: { + getSigner: jest.fn().mockReturnValue(signerMock), + }, + }, + { provide: JobRepository, useValue: createMock() }, + { provide: JobService, useValue: createMock() }, + { + provide: AssignmentRepository, + useValue: createMock(), + }, + ], + }).compile(); + + assignmentService = moduleRef.get(AssignmentService); + jobService = moduleRef.get(JobService); + jobRepository = moduleRef.get(JobRepository); + assignmentRepository = + moduleRef.get(AssignmentRepository); + }); + + describe('createAssignment', () => { + const manifest: ManifestDto = { + requesterTitle: 'Example Title', + requesterDescription: 'Example Description', + submissionsRequired: 5, + fundAmount: 100, + }; + + beforeAll(async () => { + jest.spyOn(jobRepository, 'createUnique'); + }); + const createAssignmentDto: CreateAssignmentDto = { + chainId, + escrowAddress, + }; + + it('should create a new assignment in the database', async () => { + jest + .spyOn(jobRepository, 'findOneByChainIdAndEscrowAddress') + .mockResolvedValue({ + id: 1, + reputationNetwork: reputationNetwork, + } as any); + jest + .spyOn(assignmentRepository, 'findOneByJobIdAndWorker') + .mockResolvedValue(null); + jest.spyOn(assignmentRepository, 'countByJobId').mockResolvedValue(0); + jest.spyOn(jobService, 'getManifest').mockResolvedValue(manifest); + (Escrow__factory.connect as any).mockImplementation(() => ({ + duration: jest + .fn() + .mockResolvedValue((new Date().getTime() + 1000) / 1000), + })); + + const result = await assignmentService.createAssignment( + createAssignmentDto, + { address: workerAddress, reputationNetwork: reputationNetwork } as any, + ); + + expect(result).toEqual(undefined); + expect(assignmentRepository.createUnique).toHaveBeenCalledWith({ + job: { id: 1, reputationNetwork: reputationNetwork }, + workerAddress: workerAddress, + status: AssignmentStatus.ACTIVE, + expiresAt: expect.any(Date), + }); + expect(jobService.getManifest).toHaveBeenCalledWith( + chainId, + escrowAddress, + ); + }); + + it('should fail if escrow address is invalid', async () => { + jest + .spyOn(jobRepository, 'findOneByChainIdAndEscrowAddress') + .mockResolvedValue(null); + + await expect( + assignmentService.createAssignment(createAssignmentDto, { + address: workerAddress, + reputationNetwork: reputationNetwork, + } as any), + ).rejects.toThrow('Job not found'); + }); + + it('should fail if job is not in the same reputation network', async () => { + const differentReputationNetwork = + '0x1234567890123456789012345678901234567893'; + jest + .spyOn(jobRepository, 'findOneByChainIdAndEscrowAddress') + .mockResolvedValue({ + id: 1, + reputationNetwork: differentReputationNetwork, + } as any); + + await expect( + assignmentService.createAssignment(createAssignmentDto, { + address: workerAddress, + reputationNetwork: reputationNetwork, + } as any), + ).rejects.toThrow('Requested job is not in your reputation network'); + }); + + it('should fail if user already assigned', async () => { + jest + .spyOn(jobRepository, 'findOneByChainIdAndEscrowAddress') + .mockResolvedValue({ + id: 1, + reputationNetwork: reputationNetwork, + } as any); + jest + .spyOn(assignmentRepository, 'findOneByJobIdAndWorker') + .mockResolvedValue({ id: 1 } as any); + + await expect( + assignmentService.createAssignment(createAssignmentDto, { + address: workerAddress, + reputationNetwork: reputationNetwork, + } as any), + ).rejects.toThrow('Assignment already exists'); + }); + + it('should fail if job is fully assigned', async () => { + jest + .spyOn(jobRepository, 'findOneByChainIdAndEscrowAddress') + .mockResolvedValue({ + id: 1, + reputationNetwork: reputationNetwork, + } as any); + jest + .spyOn(assignmentRepository, 'findOneByJobIdAndWorker') + .mockResolvedValue(null); + jest.spyOn(assignmentRepository, 'countByJobId').mockResolvedValue(5); + jest.spyOn(jobService, 'getManifest').mockResolvedValue(manifest); + + await expect( + assignmentService.createAssignment(createAssignmentDto, { + address: workerAddress, + reputationNetwork: reputationNetwork, + } as any), + ).rejects.toThrow('Fully assigned job'); + }); + + it('should fail if job is expired', async () => { + jest + .spyOn(jobRepository, 'findOneByChainIdAndEscrowAddress') + .mockResolvedValue({ + id: 1, + reputationNetwork: reputationNetwork, + } as any); + jest + .spyOn(assignmentRepository, 'findOneByJobIdAndWorker') + .mockResolvedValue(null); + jest.spyOn(assignmentRepository, 'countByJobId').mockResolvedValue(0); + jest.spyOn(jobService, 'getManifest').mockResolvedValue(manifest); + (Escrow__factory.connect as any).mockImplementation(() => ({ + duration: jest + .fn() + .mockResolvedValue((new Date().getTime() - 1000) / 1000), + })); + + await expect( + assignmentService.createAssignment(createAssignmentDto, { + address: workerAddress, + reputationNetwork: reputationNetwork, + } as any), + ).rejects.toThrow('Expired escrow'); + }); + }); + + describe('getAssignmentList', () => { + const assignments = [ + { + id: 1, + job: { + chainId: 1, + escrowAddress, + }, + status: AssignmentStatus.ACTIVE, + createdAt: new Date(), + expiresAt: new Date(), + }, + ]; + + afterEach(() => { + jest.restoreAllMocks(); + }); + + it('should return an array of assignments', async () => { + const manifest: ManifestDto = { + requesterTitle: 'Example Title', + requesterDescription: 'Example Description', + submissionsRequired: 5, + fundAmount: 100, + }; + + jest.spyOn(jobService, 'getManifest').mockResolvedValue(manifest); + jest + .spyOn(assignmentRepository, 'fetchFiltered') + .mockResolvedValueOnce({ entities: assignments as any, itemCount: 1 }); + + const result = await assignmentService.getAssignmentList( + { + chainId, + jobType: JobType.FORTUNE, + escrowAddress, + status: AssignmentStatus.ACTIVE, + page: 0, + pageSize: 10, + skip: 0, + }, + workerAddress, + reputationNetwork, + MOCK_EXCHANGE_ORACLE, + ); + + expect(result.totalResults).toEqual(1); + expect(result.results[0]).toEqual({ + assignmentId: 1, + chainId: 1, + escrowAddress: escrowAddress, + jobType: JobType.FORTUNE, + status: AssignmentStatus.ACTIVE, + rewardToken: TOKEN, + rewardAmount: 20, + url: expect.any(String), + createdAt: expect.any(String), + expiresAt: expect.any(String), + } as AssignmentDto); + expect(jobService.getManifest).toHaveBeenCalledWith( + chainId, + escrowAddress, + ); + }); + }); +}); diff --git a/packages/apps/fortune/exchange-oracle/server/src/modules/assignment/assignment.service.ts b/packages/apps/fortune/exchange-oracle/server/src/modules/assignment/assignment.service.ts new file mode 100644 index 0000000000..410a011a16 --- /dev/null +++ b/packages/apps/fortune/exchange-oracle/server/src/modules/assignment/assignment.service.ts @@ -0,0 +1,137 @@ +import { BadRequestException, Injectable, Logger } from '@nestjs/common'; +import { AssignmentStatus, JobType } from '../../common/enums/job'; +import { JwtUser } from '../../common/types/jwt'; +import { JobRepository } from '../job/job.repository'; +import { + AssignmentDto, + CreateAssignmentDto, + GetAssignmentsDto, +} from './assignment.dto'; +import { AssignmentEntity } from './assignment.entity'; +import { AssignmentRepository } from './assignment.repository'; +import { PageDto } from '../../common/pagination/pagination.dto'; +import { TOKEN } from '../../common/constant'; +import { JobService } from '../job/job.service'; +import { Escrow__factory } from '@human-protocol/core/typechain-types'; +import { Web3Service } from '../web3/web3.service'; + +@Injectable() +export class AssignmentService { + public readonly logger = new Logger(AssignmentService.name); + private storage: { + [key: string]: string[]; + } = {}; + + constructor( + public readonly assignmentRepository: AssignmentRepository, + public readonly jobRepository: JobRepository, + public readonly jobService: JobService, + public readonly web3Service: Web3Service, + ) {} + + public async createAssignment( + data: CreateAssignmentDto, + jwtUser: JwtUser, + ): Promise { + const jobEntity = await this.jobRepository.findOneByChainIdAndEscrowAddress( + data.chainId, + data.escrowAddress, + ); + + if (!jobEntity) { + this.logger.log('Job not found', AssignmentService.name); + throw new BadRequestException('Job not found'); + } else if (jobEntity.reputationNetwork !== jwtUser.reputationNetwork) { + this.logger.log( + 'Requested job is not in your reputation network', + AssignmentService.name, + ); + throw new BadRequestException( + 'Requested job is not in your reputation network', + ); + } + const assignmentEntity = + await this.assignmentRepository.findOneByJobIdAndWorker( + jobEntity.id, + jwtUser.address, + ); + + if (assignmentEntity) { + this.logger.log('Assignment already exists', AssignmentService.name); + throw new BadRequestException('Assignment already exists'); + } + + const currentAssignments = await this.assignmentRepository.countByJobId( + jobEntity.id, + ); + const manifest = await this.jobService.getManifest( + data.chainId, + data.escrowAddress, + ); + + if (currentAssignments >= manifest.submissionsRequired) { + this.logger.log('Fully assigned job', AssignmentService.name); + throw new BadRequestException('Fully assigned job'); + } + + const signer = this.web3Service.getSigner(data.chainId); + const escrow = Escrow__factory.connect(data.escrowAddress, signer); + const expirationDate = new Date(Number(await escrow.duration()) * 1000); + if (expirationDate < new Date()) { + this.logger.log('Expired escrow', AssignmentService.name); + throw new BadRequestException('Expired escrow'); + } + + const newAssignmentEntity = new AssignmentEntity(); + newAssignmentEntity.job = jobEntity; + newAssignmentEntity.workerAddress = jwtUser.address; + newAssignmentEntity.status = AssignmentStatus.ACTIVE; + newAssignmentEntity.expiresAt = expirationDate; + await this.assignmentRepository.createUnique(newAssignmentEntity); + } + + public async getAssignmentList( + data: GetAssignmentsDto, + workerAddress: string, + reputationNetwork: string, + requestUrl: string, + ): Promise> { + if (data.jobType && data.jobType !== JobType.FORTUNE) + return new PageDto(data.page!, data.pageSize!, 0, []); + + const { entities, itemCount } = + await this.assignmentRepository.fetchFiltered({ + ...data, + pageSize: data.pageSize!, + skip: data.skip!, + reputationNetwork, + workerAddress, + }); + const assignments = await Promise.all( + entities.map(async (entity) => { + const manifest = await this.jobService.getManifest( + entity.job.chainId, + entity.job.escrowAddress, + ); + const assignment = new AssignmentDto( + entity.id, + entity.job.escrowAddress, + entity.job.chainId, + JobType.FORTUNE, + entity.status, + manifest.fundAmount / manifest.submissionsRequired, + TOKEN, + entity.createdAt.toISOString(), + entity.expiresAt.toISOString(), + ); + + if (entity.status === AssignmentStatus.ACTIVE) + assignment.url = requestUrl; + else assignment.updatedAt = entity.updatedAt.toISOString(); + + return assignment; + }), + ); + return new PageDto(data.page!, data.pageSize!, itemCount, assignments); + } +} diff --git a/packages/apps/fortune/exchange-oracle/server/src/modules/cron-job/cron-job.entity.ts b/packages/apps/fortune/exchange-oracle/server/src/modules/cron-job/cron-job.entity.ts new file mode 100644 index 0000000000..ebbc5bdbd3 --- /dev/null +++ b/packages/apps/fortune/exchange-oracle/server/src/modules/cron-job/cron-job.entity.ts @@ -0,0 +1,30 @@ +import { BeforeInsert, Column, Entity, Index } from 'typeorm'; + +import { NS } from '../../common/constant'; +import { BaseEntity } from '../../database/base.entity'; +import { ICronJob } from '../../common/interfaces/cron-job'; +import { CronJobType } from '../../common/enums/cron-job'; + +@Entity({ schema: NS, name: 'cron-jobs' }) +@Index(['cronJobType'], { unique: true }) +export class CronJobEntity extends BaseEntity implements ICronJob { + @Column({ + type: 'enum', + enum: CronJobType, + }) + public cronJobType: CronJobType; + + @Column({ type: 'timestamptz' }) + public startedAt: Date; + + @Column({ type: 'timestamptz', nullable: true }) + public completedAt?: Date | null; + + @BeforeInsert() + public beforeInsert(): void { + const date = new Date(); + this.startedAt = date; + this.createdAt = date; + this.updatedAt = date; + } +} diff --git a/packages/apps/fortune/exchange-oracle/server/src/modules/cron-job/cron-job.module.ts b/packages/apps/fortune/exchange-oracle/server/src/modules/cron-job/cron-job.module.ts new file mode 100644 index 0000000000..8fada5ec84 --- /dev/null +++ b/packages/apps/fortune/exchange-oracle/server/src/modules/cron-job/cron-job.module.ts @@ -0,0 +1,23 @@ +import { Global, Module } from '@nestjs/common'; +import { ConfigModule } from '@nestjs/config'; + +import { TypeOrmModule } from '@nestjs/typeorm'; +import { WebhookModule } from '../webhook/webhook.module'; +import { WebhookRepository } from '../webhook/webhook.repository'; +import { CronJobEntity } from './cron-job.entity'; +import { CronJobRepository } from './cron-job.repository'; +import { CronJobService } from './cron-job.service'; +import { CronJobController } from './cron.job.controller'; + +@Global() +@Module({ + imports: [ + TypeOrmModule.forFeature([CronJobEntity]), + WebhookModule, + ConfigModule, + ], + providers: [CronJobService, CronJobRepository, WebhookRepository], + controllers: [CronJobController], + exports: [CronJobService], +}) +export class CronJobModule {} diff --git a/packages/apps/fortune/exchange-oracle/server/src/modules/cron-job/cron-job.repository.ts b/packages/apps/fortune/exchange-oracle/server/src/modules/cron-job/cron-job.repository.ts new file mode 100644 index 0000000000..20cad40abb --- /dev/null +++ b/packages/apps/fortune/exchange-oracle/server/src/modules/cron-job/cron-job.repository.ts @@ -0,0 +1,16 @@ +import { Injectable } from '@nestjs/common'; +import { CronJobType } from '../../common/enums/cron-job'; +import { BaseRepository } from '../../database/base.repository'; +import { DataSource } from 'typeorm'; +import { CronJobEntity } from './cron-job.entity'; + +@Injectable() +export class CronJobRepository extends BaseRepository { + constructor(private dataSource: DataSource) { + super(CronJobEntity, dataSource); + } + + public async findOneByType(type: CronJobType): Promise { + return this.findOne({ where: { cronJobType: type } }); + } +} diff --git a/packages/apps/fortune/exchange-oracle/server/src/modules/cron-job/cron-job.service.ts b/packages/apps/fortune/exchange-oracle/server/src/modules/cron-job/cron-job.service.ts new file mode 100644 index 0000000000..40c37dd13c --- /dev/null +++ b/packages/apps/fortune/exchange-oracle/server/src/modules/cron-job/cron-job.service.ts @@ -0,0 +1,96 @@ +import { BadRequestException, Injectable, Logger } from '@nestjs/common'; + +import { ErrorCronJob } from '../../common/constant/errors'; +import { CronJobType } from '../../common/enums/cron-job'; +import { WebhookStatus } from '../../common/enums/webhook'; +import { WebhookRepository } from '../webhook/webhook.repository'; +import { WebhookService } from '../webhook/webhook.service'; +import { CronJobEntity } from './cron-job.entity'; +import { CronJobRepository } from './cron-job.repository'; + +@Injectable() +export class CronJobService { + private readonly logger = new Logger(CronJobService.name); + + constructor( + private readonly cronJobRepository: CronJobRepository, + private readonly webhookService: WebhookService, + private readonly webhookRepository: WebhookRepository, + ) {} + + public async startCronJob(cronJobType: CronJobType): Promise { + const cronJob = await this.cronJobRepository.findOneByType(cronJobType); + + if (!cronJob) { + const cronJobEntity = new CronJobEntity(); + cronJobEntity.cronJobType = cronJobType; + return this.cronJobRepository.createUnique(cronJobEntity); + } + cronJob.startedAt = new Date(); + cronJob.completedAt = null; + return this.cronJobRepository.updateOne(cronJob); + } + + public async isCronJobRunning(cronJobType: CronJobType): Promise { + const lastCronJob = await this.cronJobRepository.findOneByType(cronJobType); + + if (!lastCronJob || lastCronJob.completedAt) { + return false; + } + + this.logger.log('Previous cron job is not completed yet'); + return true; + } + + public async completeCronJob( + cronJobEntity: CronJobEntity, + ): Promise { + if (cronJobEntity.completedAt) { + this.logger.error(ErrorCronJob.Completed, CronJobService.name); + throw new BadRequestException(ErrorCronJob.Completed); + } + + cronJobEntity.completedAt = new Date(); + return this.cronJobRepository.updateOne(cronJobEntity); + } + + /** + * Process a pending webhook job. + * @returns {Promise} - Returns a promise that resolves when the operation is complete. + */ + public async processPendingWebhooks(): Promise { + const isCronJobRunning = await this.isCronJobRunning( + CronJobType.ProcessPendingWebhook, + ); + + if (isCronJobRunning) { + return; + } + + this.logger.log('Pending webhooks START'); + const cronJob = await this.startCronJob(CronJobType.ProcessPendingWebhook); + + try { + const webhookEntities = await this.webhookRepository.findByStatus( + WebhookStatus.PENDING, + ); + + for (const webhookEntity of webhookEntities) { + try { + await this.webhookService.sendWebhook(webhookEntity); + } catch (err) { + this.logger.error(`Error sending webhook: ${err.message}`); + await this.webhookService.handleWebhookError(webhookEntity); + continue; + } + webhookEntity.status = WebhookStatus.COMPLETED; + await this.webhookRepository.updateOne(webhookEntity); + } + } catch (e) { + this.logger.error(e); + } + + this.logger.log('Pending webhooks STOP'); + await this.completeCronJob(cronJob); + } +} diff --git a/packages/apps/fortune/exchange-oracle/server/src/modules/cron-job/cron.job.controller.ts b/packages/apps/fortune/exchange-oracle/server/src/modules/cron-job/cron.job.controller.ts new file mode 100644 index 0000000000..56529fc8e7 --- /dev/null +++ b/packages/apps/fortune/exchange-oracle/server/src/modules/cron-job/cron.job.controller.ts @@ -0,0 +1,35 @@ +import { Controller, Get, UseGuards } from '@nestjs/common'; +import { + ApiBearerAuth, + ApiOperation, + ApiResponse, + ApiTags, +} from '@nestjs/swagger'; +import { CronJobService } from './cron-job.service'; +import { CronAuthGuard } from '../../common/guards/cron.auth'; + +@UseGuards(CronAuthGuard) +@ApiTags('Cron') +@Controller('/cron') +export class CronJobController { + constructor(private readonly cronJobService: CronJobService) {} + + @ApiOperation({ + summary: 'Process pending webhooks cron job', + description: 'Endpoint to launch Process pending webhooks cron job.', + }) + @ApiResponse({ + status: 200, + description: 'Cron job launched successfully.', + }) + @ApiResponse({ + status: 400, + description: 'Bad Request. Invalid input parameters.', + }) + @ApiBearerAuth() + @Get('/wehbhook/process') + public async processPendingWebhooks(): Promise { + await this.cronJobService.processPendingWebhooks(); + return; + } +} diff --git a/packages/apps/fortune/exchange-oracle/server/src/modules/health/health.controller.ts b/packages/apps/fortune/exchange-oracle/server/src/modules/health/health.controller.ts new file mode 100644 index 0000000000..4c3277bcdc --- /dev/null +++ b/packages/apps/fortune/exchange-oracle/server/src/modules/health/health.controller.ts @@ -0,0 +1,35 @@ +import { Controller, Get } from '@nestjs/common'; +import { + HealthCheck, + HealthCheckResult, + HealthCheckService, + HealthIndicatorResult, + TypeOrmHealthIndicator, +} from '@nestjs/terminus'; +import { Public } from '../../common/decorators'; +import { ApiOperation, ApiTags } from '@nestjs/swagger'; + +@Public() +@ApiTags('Health') +@Controller('/health') +export class HealthController { + constructor( + private readonly health: HealthCheckService, + private readonly db: TypeOrmHealthIndicator, + ) {} + + @Get() + @HealthCheck() + @ApiOperation({ + summary: 'Health Check', + description: 'Endpoint to perform health checks for the application.', + }) + readiness(): Promise { + return this.health.check([ + async (): Promise => + this.db.pingCheck('database', { + timeout: 5000, + }), + ]); + } +} diff --git a/packages/apps/fortune/exchange-oracle/server/src/modules/health/health.module.ts b/packages/apps/fortune/exchange-oracle/server/src/modules/health/health.module.ts new file mode 100644 index 0000000000..9455cee23b --- /dev/null +++ b/packages/apps/fortune/exchange-oracle/server/src/modules/health/health.module.ts @@ -0,0 +1,11 @@ +import { Module } from '@nestjs/common'; +import { TerminusModule } from '@nestjs/terminus'; +import { ConfigModule } from '@nestjs/config'; + +import { HealthController } from './health.controller'; + +@Module({ + imports: [TerminusModule, ConfigModule], + controllers: [HealthController], +}) +export class HealthModule {} diff --git a/packages/apps/fortune/exchange-oracle/server/src/modules/job/job.controller.spec.ts b/packages/apps/fortune/exchange-oracle/server/src/modules/job/job.controller.spec.ts index 545c0a6077..4471098a40 100644 --- a/packages/apps/fortune/exchange-oracle/server/src/modules/job/job.controller.spec.ts +++ b/packages/apps/fortune/exchange-oracle/server/src/modules/job/job.controller.spec.ts @@ -1,20 +1,8 @@ -import { HttpService } from '@nestjs/axios'; -import { ConfigModule, ConfigService, registerAs } from '@nestjs/config'; +import { createMock } from '@golevelup/ts-jest'; import { Test } from '@nestjs/testing'; -import { of } from 'rxjs'; -import { - MOCK_REPUTATION_ORACLE_WEBHOOK_URL, - MOCK_S3_ACCESS_KEY, - MOCK_S3_BUCKET, - MOCK_S3_ENDPOINT, - MOCK_S3_PORT, - MOCK_S3_SECRET_KEY, - MOCK_S3_USE_SSL, -} from '../../../test/constants'; -import { StorageService } from '../storage/storage.service'; -import { Web3Service } from '../web3/web3.service'; +import { RequestWithUser } from 'src/common/types/jwt'; import { JobController } from './job.controller'; -import { JobDetailsDto, SolveJobDto } from './job.dto'; +import { SolveJobDto } from './job.dto'; import { JobService } from './job.service'; jest.mock('../../common/utils/signature'); @@ -27,122 +15,66 @@ describe('JobController', () => { const escrowAddress = '0x1234567890123456789012345678901234567890'; const workerAddress = '0x1234567890123456789012345678901234567891'; - const reputationOracleURL = 'https://example.com/reputationoracle'; - const configServiceMock = { - get: jest.fn().mockReturnValue(reputationOracleURL), - }; - beforeAll(async () => { const moduleRef = await Test.createTestingModule({ - imports: [ - ConfigModule.forFeature( - registerAs('s3', () => ({ - accessKey: MOCK_S3_ACCESS_KEY, - secretKey: MOCK_S3_SECRET_KEY, - endPoint: MOCK_S3_ENDPOINT, - port: MOCK_S3_PORT, - useSSL: MOCK_S3_USE_SSL, - bucket: MOCK_S3_BUCKET, - })), - ), - ConfigModule.forFeature( - registerAs('server', () => ({ - reputationOracleWebhookUrl: MOCK_REPUTATION_ORACLE_WEBHOOK_URL, - })), - ), - ], + imports: [], controllers: [JobController], - providers: [ - JobService, - { - provide: ConfigService, - useValue: configServiceMock, - }, - { - provide: Web3Service, - useValue: { - getSigner: jest.fn().mockReturnValue({ - address: '0x1234567890123456789012345678901234567892', - getNetwork: jest.fn().mockResolvedValue({ chainId: 1 }), - }), - }, - }, - StorageService, - { - provide: HttpService, - useValue: { - post: jest.fn().mockReturnValue(of({ status: 200, data: {} })), - }, - }, - ], + providers: [{ provide: JobService, useValue: createMock() }], }).compile(); jobController = moduleRef.get(JobController); jobService = moduleRef.get(JobService); }); - describe('getDetails', () => { - it('should return job details', async () => { - const expectedDetails: JobDetailsDto = { - escrowAddress, + describe('getJobs', () => { + it('should call jobService.getJobList', async () => { + const solution = 'job-solution'; + const solveJobDto: SolveJobDto = { chainId, - manifest: { - requesterTitle: 'Example Title', - requesterDescription: 'Example Description', - submissionsRequired: 5, - fundAmount: 100, - }, + escrowAddress, + solution, }; - jest.spyOn(jobService, 'getDetails').mockResolvedValue(expectedDetails); - - const result = await jobController.getDetails(chainId, escrowAddress); + jest.spyOn(jobService, 'solveJob').mockResolvedValue(); - expect(result).toBe(expectedDetails); - expect(jobService.getDetails).toHaveBeenCalledWith( - chainId, - escrowAddress, + await jobController.solveJob( + { + user: { address: workerAddress }, + } as RequestWithUser, + solveJobDto, ); - }); - }); - - describe('getPendingJobs', () => { - it('should return pending jobs', async () => { - const expectedJobs: any[] = [ - '0x1234567890123456789012345678901234567891', - '0x1234567890123456789012345678901234567892', - ]; - - jest.spyOn(jobService, 'getPendingJobs').mockResolvedValue(expectedJobs); - const result = await jobController.getPendingJobs(chainId, workerAddress); - - expect(result).toBe(expectedJobs); - expect(jobService.getPendingJobs).toHaveBeenCalledWith( - chainId, + expect(jobService.solveJob).toHaveBeenCalledWith( + solveJobDto.chainId, + solveJobDto.escrowAddress, workerAddress, + solveJobDto.solution, ); }); }); describe('solveJob', () => { - it('should solve a job', async () => { + it('should call jobService.solveJob', async () => { const solution = 'job-solution'; const solveJobDto: SolveJobDto = { chainId, escrowAddress, - workerAddress, solution, }; jest.spyOn(jobService, 'solveJob').mockResolvedValue(); - await jobController.solveJob(solveJobDto); + await jobController.solveJob( + { + user: { address: workerAddress }, + } as RequestWithUser, + solveJobDto, + ); expect(jobService.solveJob).toHaveBeenCalledWith( solveJobDto.chainId, solveJobDto.escrowAddress, - solveJobDto.workerAddress, + workerAddress, solveJobDto.solution, ); }); diff --git a/packages/apps/fortune/exchange-oracle/server/src/modules/job/job.controller.ts b/packages/apps/fortune/exchange-oracle/server/src/modules/job/job.controller.ts index 5c6f779ef3..9cfbb9a828 100644 --- a/packages/apps/fortune/exchange-oracle/server/src/modules/job/job.controller.ts +++ b/packages/apps/fortune/exchange-oracle/server/src/modules/job/job.controller.ts @@ -1,8 +1,24 @@ -import { Body, Controller, Get, Param, Post, UseGuards } from '@nestjs/common'; -import { ApiTags, ApiBearerAuth } from '@nestjs/swagger'; -import { JobDetailsDto, SolveJobDto } from './job.dto'; -import { JobService } from './job.service'; +import { + Body, + Controller, + Get, + Post, + Query, + UseGuards, + Request, +} from '@nestjs/common'; +import { + ApiBearerAuth, + ApiTags, + ApiOperation, + ApiResponse, + ApiBody, +} from '@nestjs/swagger'; import { JwtAuthGuard } from '../../common/guards/jwt.auth'; +import { GetJobsDto, JobDto, SolveJobDto } from './job.dto'; +import { JobService } from './job.service'; +import { RequestWithUser } from '../../common/types/jwt'; +import { PageDto } from '../../common/pagination/pagination.dto'; @ApiTags('Job') @Controller('job') @@ -10,29 +26,62 @@ import { JwtAuthGuard } from '../../common/guards/jwt.auth'; @ApiBearerAuth() export class JobController { constructor(private readonly jobService: JobService) {} - - @Get('pending/:chain_id/:worker_address') - getPendingJobs( - @Param('chain_id') chainId: number, - @Param('worker_address') workerAddress: string, - ): Promise { - return this.jobService.getPendingJobs(chainId, workerAddress); - } - - @Get('details/:chain_id/:escrow_address') - getDetails( - @Param('chain_id') chainId: number, - @Param('escrow_address') escrowAddress: string, - ): Promise { - return this.jobService.getDetails(chainId, escrowAddress); + @ApiOperation({ + summary: 'Get Jobs', + description: 'Endpoint to retrieve a list of jobs.', + }) + @ApiBearerAuth() + @ApiResponse({ + status: 200, + type: PageDto, + description: 'List of jobs retrieved successfully.', + }) + @ApiResponse({ + status: 400, + description: 'Bad Request. Invalid input parameters.', + }) + @ApiResponse({ + status: 401, + description: 'Unauthorized. Missing or invalid credentials.', + }) + @Get() + getJobs( + @Request() req: RequestWithUser, + @Query() query: GetJobsDto, + ): Promise> { + return this.jobService.getJobList(query, req.user.reputationNetwork); } + @ApiOperation({ + summary: 'Solve Job', + description: 'Endpoint to solve a job.', + }) + @ApiBearerAuth() + @ApiBody({ + description: 'Details required to solve the job.', + type: SolveJobDto, + }) + @ApiResponse({ + status: 200, + description: 'Job solved successfully.', + }) + @ApiResponse({ + status: 400, + description: 'Bad Request. Invalid input parameters.', + }) + @ApiResponse({ + status: 401, + description: 'Unauthorized. Missing or invalid credentials.', + }) @Post('solve') - solveJob(@Body() body: SolveJobDto): Promise { + solveJob( + @Request() req: RequestWithUser, + @Body() body: SolveJobDto, + ): Promise { return this.jobService.solveJob( body.chainId, body.escrowAddress, - body.workerAddress, + req.user.address, body.solution, ); } diff --git a/packages/apps/fortune/exchange-oracle/server/src/modules/job/job.dto.ts b/packages/apps/fortune/exchange-oracle/server/src/modules/job/job.dto.ts index e121503f9b..445fc84ea7 100644 --- a/packages/apps/fortune/exchange-oracle/server/src/modules/job/job.dto.ts +++ b/packages/apps/fortune/exchange-oracle/server/src/modules/job/job.dto.ts @@ -1,7 +1,15 @@ import { ChainId } from '@human-protocol/sdk'; -import { ApiProperty } from '@nestjs/swagger'; -import { IsEnum, IsString } from 'class-validator'; +import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; +import { IsOptional, IsEnum, IsString, IsNumber } from 'class-validator'; +import { Type } from 'class-transformer'; import { IsValidEthereumAddress } from '../../common/validators'; +import { + JobFieldName, + JobSortField, + JobStatus, + JobType, +} from '../../common/enums/job'; +import { PageOptionsDto } from '../../common/pagination/pagination.dto'; export class ManifestDto { requesterTitle: string; @@ -10,12 +18,6 @@ export class ManifestDto { fundAmount: number; } -export class JobDetailsDto { - escrowAddress: string; - chainId: number; - manifest: ManifestDto; -} - export class SolveJobDto { @ApiProperty({ name: 'escrow_address' }) @IsString() @@ -29,12 +31,82 @@ export class SolveJobDto { @IsEnum(ChainId) public chainId: ChainId; - @ApiProperty({ name: 'worker_address' }) - @IsString() - @IsValidEthereumAddress() - public workerAddress: string; - @ApiProperty() @IsString() public solution: string; } + +export class GetJobsDto extends PageOptionsDto { + @ApiPropertyOptional({ + name: 'sort_field', + enum: JobSortField, + default: JobSortField.CREATED_AT, + }) + @IsOptional() + @IsEnum(JobSortField) + sortField?: JobSortField = JobSortField.CREATED_AT; + + @ApiPropertyOptional({ name: 'chain_id' }) + @IsOptional() + @Type(() => Number) + @IsNumber() + chainId: number; + + @ApiPropertyOptional({ name: 'job_type', enum: JobType }) + @IsEnum(JobType) + @IsOptional() + jobType: JobType; + + @ApiPropertyOptional({ enum: JobFieldName, isArray: true }) + @IsOptional() + @IsEnum(JobFieldName, { each: true }) + fields: JobFieldName[]; + + @ApiPropertyOptional({ name: 'escrow_address' }) + @IsOptional() + @IsString() + escrowAddress: string; + + @ApiPropertyOptional({ enum: JobStatus }) + @IsEnum(JobStatus) + @IsOptional() + status: JobStatus; +} + +export class JobDto { + @ApiProperty({ name: 'escrow_address' }) + escrowAddress: string; + + @ApiProperty({ name: 'chain_id' }) + chainId: number; + + @ApiProperty({ name: 'job_type' }) + jobType: string; + + @ApiProperty() + status: JobStatus; + + @ApiProperty({ name: 'job_description' }) + jobDescription?: string; + + @ApiProperty({ name: 'reward_amount' }) + rewardAmount?: number; + + @ApiProperty({ name: 'reward_token' }) + rewardToken?: string; + + @ApiProperty({ name: 'created_at' }) + createdAt?: string; + + constructor( + escrowAddress: string, + chainId: number, + jobType: string, + status: JobStatus, + ) { + this.escrowAddress = escrowAddress; + this.chainId = chainId; + this.jobType = jobType; + this.status = status; + } +} diff --git a/packages/apps/fortune/exchange-oracle/server/src/modules/job/job.entity.ts b/packages/apps/fortune/exchange-oracle/server/src/modules/job/job.entity.ts index 956a7a20a1..dd6cb14fd8 100644 --- a/packages/apps/fortune/exchange-oracle/server/src/modules/job/job.entity.ts +++ b/packages/apps/fortune/exchange-oracle/server/src/modules/job/job.entity.ts @@ -5,13 +5,13 @@ import { JobStatus } from '../../common/enums/job'; import { BaseEntity } from '../../database/base.entity'; import { AssignmentEntity } from '../assignment/assignment.entity'; -@Entity({ schema: NS, name: 'job' }) +@Entity({ schema: NS, name: 'jobs' }) @Index(['chainId', 'escrowAddress'], { unique: true }) export class JobEntity extends BaseEntity { - @Column({ type: 'int', nullable: true }) + @Column({ type: 'int' }) public chainId: number; - @Column({ type: 'varchar', nullable: true }) + @Column({ type: 'varchar' }) public escrowAddress: string; @Column({ @@ -20,6 +20,9 @@ export class JobEntity extends BaseEntity { }) public status: JobStatus; + @Column({ type: 'varchar' }) + public reputationNetwork: string; + @OneToMany(() => AssignmentEntity, (assignment) => assignment.job) public assignments: AssignmentEntity[]; } diff --git a/packages/apps/fortune/exchange-oracle/server/src/modules/job/job.interface.ts b/packages/apps/fortune/exchange-oracle/server/src/modules/job/job.interface.ts new file mode 100644 index 0000000000..b7fbea5382 --- /dev/null +++ b/packages/apps/fortune/exchange-oracle/server/src/modules/job/job.interface.ts @@ -0,0 +1,19 @@ +import { SortDirection } from '../../common/enums/collection'; +import { JobSortField, JobStatus } from '../../common/enums/job'; +import { JobEntity } from './job.entity'; + +export interface JobFilterData { + chainId?: number; + escrowAddress?: string; + status?: JobStatus; + sortField?: JobSortField; + sort?: SortDirection; + skip: number; + pageSize: number; + reputationNetwork: string; +} + +export interface ListResult { + entities: JobEntity[]; + itemCount: number; +} diff --git a/packages/apps/fortune/exchange-oracle/server/src/modules/job/job.module.ts b/packages/apps/fortune/exchange-oracle/server/src/modules/job/job.module.ts index a9f5175480..37201ef89a 100644 --- a/packages/apps/fortune/exchange-oracle/server/src/modules/job/job.module.ts +++ b/packages/apps/fortune/exchange-oracle/server/src/modules/job/job.module.ts @@ -1,15 +1,35 @@ import { Module } from '@nestjs/common'; +import { TypeOrmModule } from '@nestjs/typeorm'; import { ConfigModule } from '@nestjs/config'; import { JobController } from './job.controller'; import { JobService } from './job.service'; import { HttpModule } from '@nestjs/axios'; import { Web3Module } from '../web3/web3.module'; import { StorageModule } from '../storage/storage.module'; +import { JobRepository } from './job.repository'; +import { JobEntity } from './job.entity'; +import { WebhookEntity } from '../webhook/webhook.entity'; +import { WebhookRepository } from '../webhook/webhook.repository'; +import { AssignmentEntity } from '../assignment/assignment.entity'; +import { AssignmentRepository } from '../assignment/assignment.repository'; @Module({ - imports: [ConfigModule, HttpModule, Web3Module, StorageModule], + imports: [ + TypeOrmModule.forFeature([JobEntity]), + TypeOrmModule.forFeature([WebhookEntity]), + TypeOrmModule.forFeature([AssignmentEntity]), + ConfigModule, + HttpModule, + Web3Module, + StorageModule, + ], controllers: [JobController], - providers: [JobService], + providers: [ + JobService, + JobRepository, + WebhookRepository, + AssignmentRepository, + ], exports: [JobService], }) export class JobModule {} diff --git a/packages/apps/fortune/exchange-oracle/server/src/modules/job/job.repository.ts b/packages/apps/fortune/exchange-oracle/server/src/modules/job/job.repository.ts new file mode 100644 index 0000000000..ab379d2403 --- /dev/null +++ b/packages/apps/fortune/exchange-oracle/server/src/modules/job/job.repository.ts @@ -0,0 +1,71 @@ +import { ChainId } from '@human-protocol/sdk'; +import { Injectable } from '@nestjs/common'; +import { ConfigService } from '@nestjs/config'; +import { DataSource } from 'typeorm'; +import { BaseRepository } from '../../database/base.repository'; +import { JobEntity } from './job.entity'; +import { JobSortField } from '../../common/enums/job'; +import { JobFilterData, ListResult } from './job.interface'; + +@Injectable() +export class JobRepository extends BaseRepository { + constructor( + private dataSource: DataSource, + public readonly configService: ConfigService, + ) { + super(JobEntity, dataSource); + } + + public async findOneById(id: number): Promise { + return this.findOne({ + where: { + id, + }, + }); + } + + public async findOneByChainIdAndEscrowAddress( + chainId: ChainId, + escrowAddress: string, + ): Promise { + return this.findOne({ + where: { + chainId, + escrowAddress, + }, + }); + } + + public async fetchFiltered(data: JobFilterData): Promise { + const queryBuilder = await this.createQueryBuilder('job'); + if ( + data.sortField == JobSortField.CHAIN_ID || + data.sortField == JobSortField.CREATED_AT + ) + queryBuilder.orderBy(data.sortField!, data.sort); + + if (data.chainId !== undefined) { + queryBuilder.andWhere('job.chainId = :chainId', { + chainId: data.chainId, + }); + } + if (data.escrowAddress) { + queryBuilder.andWhere('job.escrowAddress = :escrowAddress', { + escrowAddress: data.escrowAddress, + }); + } + if (data.status !== undefined) { + queryBuilder.andWhere('job.status = :status', { status: data.status }); + } + + queryBuilder.andWhere('job.reputationNetwork = :reputationNetwork', { + reputationNetwork: data.reputationNetwork, + }); + + queryBuilder.offset(data.skip).limit(data.pageSize); + + const itemCount = await queryBuilder.getCount(); + const { entities } = await queryBuilder.getRawAndEntities(); + return { entities, itemCount }; + } +} diff --git a/packages/apps/fortune/exchange-oracle/server/src/modules/job/job.service.spec.ts b/packages/apps/fortune/exchange-oracle/server/src/modules/job/job.service.spec.ts index 579e919d7f..de84fd502c 100644 --- a/packages/apps/fortune/exchange-oracle/server/src/modules/job/job.service.spec.ts +++ b/packages/apps/fortune/exchange-oracle/server/src/modules/job/job.service.spec.ts @@ -1,19 +1,15 @@ -import { HttpService } from '@nestjs/axios'; -import { ConfigService } from '@nestjs/config'; -import { Test } from '@nestjs/testing'; -import { of } from 'rxjs'; -import { Web3Service } from '../web3/web3.service'; -import { JobService } from './job.service'; +import { createMock } from '@golevelup/ts-jest'; import { + Encryption, EscrowClient, - StorageClient, - EscrowUtils, OperatorUtils, - Encryption, - EncryptionUtils, + StorageClient, } from '@human-protocol/sdk'; +import { HttpService } from '@nestjs/axios'; +import { ConfigModule, ConfigService, registerAs } from '@nestjs/config'; +import { Test } from '@nestjs/testing'; +import { of } from 'rxjs'; import { - JOB_LAUNCHER_WEBHOOK_URL, MOCK_MANIFEST_URL, MOCK_PRIVATE_KEY, MOCK_S3_ACCESS_KEY, @@ -23,15 +19,23 @@ import { MOCK_S3_SECRET_KEY, MOCK_S3_USE_SSL, } from '../../../test/constants'; -import { EventType } from '../../common/enums/webhook'; import { - ESCROW_FAILED_ENDPOINT, - HEADER_SIGNATURE_KEY, -} from '../../common/constant'; -import { signMessage } from '../../common/utils/signature'; -import { ConfigModule, registerAs } from '@nestjs/config'; + AssignmentStatus, + JobFieldName, + JobStatus, + JobType, +} from '../../common/enums/job'; +import { EventType, WebhookStatus } from '../../common/enums/webhook'; +import { AssignmentEntity } from '../assignment/assignment.entity'; +import { AssignmentRepository } from '../assignment/assignment.repository'; import { StorageService } from '../storage/storage.service'; +import { Web3Service } from '../web3/web3.service'; +import { WebhookDto } from '../webhook/webhook.dto'; +import { WebhookRepository } from '../webhook/webhook.repository'; import { ManifestDto } from './job.dto'; +import { JobEntity } from './job.entity'; +import { JobRepository } from './job.repository'; +import { JobService } from './job.service'; jest.mock('@human-protocol/sdk', () => ({ ...jest.requireActual('@human-protocol/sdk'), @@ -65,12 +69,15 @@ jest.mock('minio', () => { describe('JobService', () => { let jobService: JobService; let web3Service: Web3Service; - let httpService: HttpService; let storageService: StorageService; + let jobRepository: JobRepository; + let assignmentRepository: AssignmentRepository; + let webhookRepository: WebhookRepository; const chainId = 1; const escrowAddress = '0x1234567890123456789012345678901234567890'; const workerAddress = '0x1234567890123456789012345678901234567891'; + const reputationNetwork = '0x1234567890123456789012345678901234567892'; const signerMock = { address: '0x1234567890123456789012345678901234567892', @@ -117,6 +124,15 @@ describe('JobService', () => { getSigner: jest.fn().mockReturnValue(signerMock), }, }, + { provide: JobRepository, useValue: createMock() }, + { + provide: AssignmentRepository, + useValue: createMock(), + }, + { + provide: WebhookRepository, + useValue: createMock(), + }, { provide: HttpService, useValue: { @@ -131,211 +147,153 @@ describe('JobService', () => { jobService = moduleRef.get(JobService); web3Service = moduleRef.get(Web3Service); - httpService = moduleRef.get(HttpService); storageService = moduleRef.get(StorageService); + jobRepository = moduleRef.get(JobRepository); + assignmentRepository = + moduleRef.get(AssignmentRepository); + webhookRepository = moduleRef.get(WebhookRepository); }); - describe('getDetails', () => { + describe('createJob', () => { beforeAll(async () => { + jest.spyOn(jobRepository, 'createUnique'); (EscrowClient.build as any).mockImplementation(() => ({ - getManifestUrl: jest.fn().mockResolvedValue(MOCK_MANIFEST_URL), - getJobLauncherAddress: jest + getReputationOracleAddress: jest .fn() - .mockResolvedValue('0x1234567890123456789012345678901234567893'), + .mockResolvedValue(reputationNetwork), })); }); - - it('should return job details encrypted', async () => { - const manifest: ManifestDto = { - requesterTitle: 'Example Title', - requesterDescription: 'Example Description', - submissionsRequired: 5, - fundAmount: 100, - }; - - EncryptionUtils.isEncrypted = jest.fn().mockReturnValue(true); - StorageClient.downloadFileFromUrl = jest - .fn() - .mockResolvedValueOnce('encrypted string'); - - storageService.downloadJobSolutions = jest.fn().mockResolvedValue([]); - - (Encryption.build as any).mockImplementation(() => ({ - decrypt: jest.fn().mockResolvedValue(JSON.stringify(manifest)), - })); - - const result = await jobService.getDetails(chainId, escrowAddress); - - expect(result).toEqual({ - escrowAddress, - chainId, - manifest, + const webhook: WebhookDto = { + chainId, + escrowAddress, + eventType: EventType.ESCROW_CREATED, + }; + + it('should create a new job in the database', async () => { + jest + .spyOn(jobRepository, 'findOneByChainIdAndEscrowAddress') + .mockResolvedValue(null); + const result = await jobService.createJob(webhook); + + expect(result).toEqual(undefined); + expect(jobRepository.createUnique).toHaveBeenCalledWith({ + chainId: chainId, + escrowAddress: escrowAddress, + reputationNetwork: reputationNetwork, + status: JobStatus.ACTIVE, }); }); - it('should return job details not encrypted', async () => { - const manifest: ManifestDto = { - requesterTitle: 'Example Title', - requesterDescription: 'Example Description', - submissionsRequired: 5, - fundAmount: 100, - }; - - EncryptionUtils.isEncrypted = jest.fn().mockReturnValue(false); - StorageClient.downloadFileFromUrl = jest - .fn() - .mockResolvedValueOnce(JSON.stringify(manifest)); - - storageService.downloadJobSolutions = jest.fn().mockResolvedValue([]); - - const result = await jobService.getDetails(chainId, escrowAddress); - - expect(result).toEqual({ - escrowAddress, - chainId, - manifest, - }); + it('should fail if job already exists', async () => { + jest + .spyOn(jobRepository, 'findOneByChainIdAndEscrowAddress') + .mockResolvedValue({ + chainId: chainId, + escrowAddress: escrowAddress, + status: JobStatus.ACTIVE, + } as JobEntity); + + await expect(jobService.createJob(webhook)).rejects.toThrow( + 'Job already exists', + ); }); + }); - it('should call job launcher webhook if manifest is empty', async () => { - StorageClient.downloadFileFromUrl = jest.fn().mockResolvedValueOnce(null); - - OperatorUtils.getLeader = jest.fn().mockResolvedValue({ - webhookUrl: JOB_LAUNCHER_WEBHOOK_URL, - }); - - (Encryption.build as any).mockImplementation(() => ({ - decrypt: jest.fn().mockResolvedValue(null), - })); + describe('getJobList', () => { + const jobs = [ + { + jobId: 1, + chainId: 1, + escrowAddress, + status: JobStatus.ACTIVE, + createdAt: new Date(), + }, + ]; - httpService.axiosRef.get = jest.fn().mockResolvedValue({ - status: 200, - data: null, - }); - await expect( - jobService.getDetails(chainId, escrowAddress), - ).rejects.toThrow('Unable to get manifest'); - - const expectedBody = { - escrow_address: escrowAddress, - chain_id: chainId, - event_type: EventType.TASK_CREATION_FAILED, - event_data: { assignments: [{ reason: 'Unable to get manifest' }] }, - }; - expect(httpServicePostMock).toHaveBeenCalledWith( - JOB_LAUNCHER_WEBHOOK_URL + ESCROW_FAILED_ENDPOINT, - expectedBody, - { - headers: { - [HEADER_SIGNATURE_KEY]: await signMessage( - expectedBody, - MOCK_PRIVATE_KEY, - ), - }, - }, - ); + afterEach(() => { + jest.restoreAllMocks(); }); - it('should fail if job has already been completed', async () => { + it('should return an array of jobs calling the manifest', async () => { const manifest: ManifestDto = { requesterTitle: 'Example Title', requesterDescription: 'Example Description', - submissionsRequired: 1, + submissionsRequired: 5, fundAmount: 100, }; - StorageClient.downloadFileFromUrl = jest - .fn() - .mockResolvedValueOnce(manifest); + jest.spyOn(jobService, 'getManifest').mockResolvedValue(manifest); + jest + .spyOn(jobRepository, 'fetchFiltered') + .mockResolvedValueOnce({ entities: jobs as any, itemCount: 1 }); - storageService.downloadJobSolutions = jest.fn().mockResolvedValueOnce([ + const result = await jobService.getJobList( { - exchangeAddress: '0x1234567890123456789012345678901234567892', - workerAddress: '0x1234567890123456789012345678901234567892', - solution: 'test', + chainId, + jobType: JobType.FORTUNE, + fields: [JobFieldName.JobDescription], + escrowAddress, + status: JobStatus.ACTIVE, + page: 0, + pageSize: 10, + skip: 0, }, - ]); - - (Encryption.build as any).mockImplementation(() => ({ - decrypt: jest.fn().mockResolvedValue(JSON.stringify(manifest)), - })); - - await expect( - jobService.getDetails(chainId, escrowAddress), - ).rejects.toThrow('This job has already been completed'); - }); + workerAddress, + ); - it('should fail if encrypted manifest is invalid', async () => { - const manifest = JSON.stringify({ - requesterTitle: 'Example Title', - requesterDescription: 'Example Description', - submissionsRequired: 5, - fundAmount: 100, + expect(result.totalResults).toEqual(1); + expect(result.results[0]).toEqual({ + chainId: 1, + jobDescription: 'Example Description', + escrowAddress: escrowAddress, + jobType: JobType.FORTUNE, + status: JobStatus.ACTIVE, }); - - EncryptionUtils.isEncrypted = jest.fn().mockReturnValue(true); - StorageClient.downloadFileFromUrl = jest - .fn() - .mockResolvedValueOnce(manifest) - .mockResolvedValueOnce([]); - - (Encryption.build as any).mockImplementation(() => ({ - decrypt: jest.fn().mockRejectedValue(new Error('Invalid manifest')), - })); - - await expect( - jobService.getDetails(chainId, escrowAddress), - ).rejects.toThrow('Unable to decrypt manifest'); - }); - }); - - describe('getPendingJobs', () => { - it('should return an array of pending jobs', async () => { - EscrowUtils.getEscrows = jest - .fn() - .mockReturnValue([ - { address: '0x1234567890123456789012345678901234567893' }, - { address: '0x1234567890123456789012345678901234567894' }, - ]); - - const result = await jobService.getPendingJobs(chainId, workerAddress); - - expect(result).toEqual([ - '0x1234567890123456789012345678901234567893', - '0x1234567890123456789012345678901234567894', - ]); - expect(web3Service.getSigner).toHaveBeenCalledWith(chainId); + expect(jobService.getManifest).toHaveBeenCalledWith( + chainId, + escrowAddress, + ); }); - it('should return an array of pending jobs removing jobs already submitted by worker', async () => { - EscrowUtils.getEscrows = jest - .fn() - .mockReturnValue([ - { address: '0x1234567890123456789012345678901234567893' }, - { address: '0x1234567890123456789012345678901234567894' }, - ]); + it('should return an array of jobs without calling the manifest', async () => { + jest.spyOn(jobService, 'getManifest'); + jest + .spyOn(jobRepository, 'fetchFiltered') + .mockResolvedValueOnce({ entities: jobs as any, itemCount: 1 }); - jobService['storage']['0x1234567890123456789012345678901234567893'] = [ + const result = await jobService.getJobList( + { + chainId, + jobType: JobType.FORTUNE, + fields: [JobFieldName.CreatedAt], + escrowAddress, + status: JobStatus.ACTIVE, + page: 0, + pageSize: 10, + skip: 0, + }, workerAddress, - ]; - - const result = await jobService.getPendingJobs(chainId, workerAddress); - - expect(result).toEqual(['0x1234567890123456789012345678901234567894']); - expect(web3Service.getSigner).toHaveBeenCalledWith(chainId); - }); - - it('should return an empty array if there are no pending jobs', async () => { - EscrowUtils.getEscrows = jest.fn().mockReturnValue([]); - - const result = await jobService.getPendingJobs(chainId, workerAddress); + ); - expect(result).toEqual([]); - expect(web3Service.getSigner).toHaveBeenCalledWith(chainId); + expect(result.totalResults).toEqual(1); + expect(result.results[0]).toEqual({ + chainId: 1, + createdAt: expect.any(String), + escrowAddress: escrowAddress, + jobType: JobType.FORTUNE, + status: JobStatus.ACTIVE, + }); + expect(jobService.getManifest).not.toHaveBeenCalled(); }); }); describe('solveJob', () => { + const assignment = { + jobId: 1, + workerAddress: workerAddress, + status: AssignmentStatus.ACTIVE, + }; + beforeAll(async () => { (EscrowClient.build as any).mockImplementation(() => ({ getManifestUrl: jest.fn().mockResolvedValue(MOCK_MANIFEST_URL), @@ -356,6 +314,10 @@ describe('JobService', () => { fundAmount: 100, }; + jest + .spyOn(assignmentRepository, 'findOneByEscrowAndWorker') + .mockResolvedValue(assignment as AssignmentEntity); + storageService.downloadJobSolutions = jest.fn().mockResolvedValueOnce([]); StorageClient.downloadFileFromUrl = jest @@ -380,24 +342,25 @@ describe('JobService', () => { workerAddress, 'solution', ); - const expectedBody = { - escrow_address: escrowAddress, - chain_id: chainId, - solutions_url: solutionsUrl, - }; expect(web3Service.getSigner).toHaveBeenCalledWith(chainId); - expect(httpServicePostMock).toHaveBeenCalledWith( - recordingOracleURLMock, - expect.objectContaining(expectedBody), - { - headers: { - [HEADER_SIGNATURE_KEY]: await signMessage( - expectedBody, - MOCK_PRIVATE_KEY, - ), - }, - }, - ); + expect(webhookRepository.createUnique).toHaveBeenCalledWith({ + escrowAddress, + chainId, + eventType: EventType.SUBMISSION_IN_REVIEW, + retriesCount: 0, + status: WebhookStatus.PENDING, + waitUntil: expect.any(Date), + }); + expect(assignment.status).toBe(AssignmentStatus.VALIDATION); + }); + + it('should fail if user is not assigned to the job', async () => { + jest + .spyOn(assignmentRepository, 'findOneByEscrowAndWorker') + .mockResolvedValue(null); + await expect( + jobService.solveJob(chainId, escrowAddress, workerAddress, 'solution'), + ).rejects.toThrow('User is not assigned to the job'); }); it('should fail if job has already been completed', async () => { @@ -408,6 +371,10 @@ describe('JobService', () => { fundAmount: 100, }; + jest + .spyOn(assignmentRepository, 'findOneByEscrowAndWorker') + .mockResolvedValue(assignment as AssignmentEntity); + storageService.downloadJobSolutions = jest.fn().mockResolvedValueOnce([ { exchangeAddress: '0x1234567890123456789012345678901234567892', @@ -446,42 +413,13 @@ describe('JobService', () => { expect(web3Service.getSigner).toHaveBeenCalledWith(chainId); }); - it('should fail if recording oracle url is empty', async () => { - const manifest: ManifestDto = { - requesterTitle: 'Example Title', - requesterDescription: 'Example Description', - submissionsRequired: 5, - fundAmount: 100, - }; - - storageService.downloadJobSolutions = jest.fn().mockResolvedValueOnce([]); - - StorageClient.downloadFileFromUrl = jest - .fn() - .mockResolvedValueOnce(manifest); - - const solutionsUrl = - 'http://localhost:9000/solution/0x1234567890123456789012345678901234567890-1.json'; - - storageService.uploadJobSolutions = jest - .fn() - .mockResolvedValue(solutionsUrl); - - const solution = 'job-solution'; - - OperatorUtils.getLeader = jest.fn().mockResolvedValue({ - webhookUrl: '', - }); - - await expect( - jobService.solveJob(chainId, escrowAddress, workerAddress, solution), - ).rejects.toThrow('Unable to get Recording Oracle webhook URL'); - expect(web3Service.getSigner).toHaveBeenCalledWith(chainId); - }); - it('should fail if user has already submitted a solution', async () => { const solution = 'job-solution'; + jest + .spyOn(assignmentRepository, 'findOneByEscrowAndWorker') + .mockResolvedValue(assignment as AssignmentEntity); + (EscrowClient.build as any).mockImplementation(() => ({ getRecordingOracleAddress: jest .fn() @@ -499,8 +437,6 @@ describe('JobService', () => { }, ]); - jobService['storage'][escrowAddress] = [workerAddress]; - await expect( jobService.solveJob(chainId, escrowAddress, workerAddress, solution), ).rejects.toThrow('User has already submitted a solution'); diff --git a/packages/apps/fortune/exchange-oracle/server/src/modules/job/job.service.ts b/packages/apps/fortune/exchange-oracle/server/src/modules/job/job.service.ts index 0aa0ce3b1d..f6e3c230ee 100644 --- a/packages/apps/fortune/exchange-oracle/server/src/modules/job/job.service.ts +++ b/packages/apps/fortune/exchange-oracle/server/src/modules/job/job.service.ts @@ -3,12 +3,8 @@ import { Encryption, EncryptionUtils, EscrowClient, - EscrowStatus, - EscrowUtils, - OperatorUtils, StorageClient, } from '@human-protocol/sdk'; -import { HttpService } from '@nestjs/axios'; import { BadRequestException, Inject, @@ -17,78 +13,119 @@ import { NotFoundException, } from '@nestjs/common'; import { ConfigService } from '@nestjs/config'; -import { ISolution } from 'src/common/interfaces/job'; +import { ethers } from 'ethers'; import { ConfigNames } from '../../common/config'; +import { TOKEN } from '../../common/constant'; import { - ESCROW_FAILED_ENDPOINT, - HEADER_SIGNATURE_KEY, -} from '../../common/constant'; + AssignmentStatus, + JobFieldName, + JobStatus, + JobType, +} from '../../common/enums/job'; import { EventType } from '../../common/enums/webhook'; -import { signMessage } from '../../common/utils/signature'; +import { ISolution } from '../../common/interfaces/job'; +import { PageDto } from '../../common/pagination/pagination.dto'; import { StorageService } from '../storage/storage.service'; import { Web3Service } from '../web3/web3.service'; -import { JobDetailsDto, ManifestDto } from './job.dto'; -import { CaseConverter } from '../../common/utils/case-converter'; -import { firstValueFrom } from 'rxjs'; -import { ethers } from 'ethers'; import { RejectionEventData, WebhookDto } from '../webhook/webhook.dto'; +import { WebhookEntity } from '../webhook/webhook.entity'; +import { WebhookRepository } from '../webhook/webhook.repository'; +import { GetJobsDto, JobDto, ManifestDto } from './job.dto'; +import { JobEntity } from './job.entity'; +import { JobRepository } from './job.repository'; +import { AssignmentRepository } from '../assignment/assignment.repository'; @Injectable() export class JobService { public readonly logger = new Logger(JobService.name); - private storage: { - [key: string]: string[]; - } = {}; constructor( private readonly configService: ConfigService, + public readonly jobRepository: JobRepository, + public readonly assignmentRepository: AssignmentRepository, @Inject(Web3Service) private readonly web3Service: Web3Service, @Inject(StorageService) private readonly storageService: StorageService, - private readonly httpService: HttpService, + public readonly webhookRepository: WebhookRepository, ) {} - public async getDetails( - chainId: number, - escrowAddress: string, - ): Promise { - const manifest = await this.getManifest(chainId, escrowAddress); - - const existingJobSolutions = await this.storageService.downloadJobSolutions( - escrowAddress, - chainId, + public async createJob(webhook: WebhookDto): Promise { + const jobEntity = await this.jobRepository.findOneByChainIdAndEscrowAddress( + webhook.chainId, + webhook.escrowAddress, ); - if ( - existingJobSolutions.filter((solution) => !solution.error).length >= - manifest.submissionsRequired - ) { - throw new BadRequestException('This job has already been completed'); + if (jobEntity) { + this.logger.log('Job already exists', JobService.name); + throw new BadRequestException('Job already exists'); } - return { - escrowAddress, - chainId, - manifest, - }; + const signer = this.web3Service.getSigner(webhook.chainId); + const escrowClient = await EscrowClient.build(signer); + const reputationOracleAddress = + await escrowClient.getReputationOracleAddress(webhook.escrowAddress); + + const newJobEntity = new JobEntity(); + newJobEntity.escrowAddress = webhook.escrowAddress; + newJobEntity.chainId = webhook.chainId; + newJobEntity.status = JobStatus.ACTIVE; + newJobEntity.reputationNetwork = reputationOracleAddress; + await this.jobRepository.createUnique(newJobEntity); } - public async getPendingJobs( - chainId: number, - workerAddress: string, - ): Promise { - const escrows = await EscrowUtils.getEscrows({ - exchangeOracle: this.web3Service.getSigner(chainId).address, - status: EscrowStatus.Pending, - networks: [chainId], + public async getJobList( + data: GetJobsDto, + reputationNetwork: string, + ): Promise> { + if (data.jobType && data.jobType !== JobType.FORTUNE) + return new PageDto(data.page!, data.pageSize!, 0, []); + + const { entities, itemCount } = await this.jobRepository.fetchFiltered({ + ...data, + pageSize: data.pageSize!, + skip: data.skip!, + reputationNetwork, }); + const jobs = await Promise.all( + entities.map(async (entity) => { + const job = new JobDto( + entity.escrowAddress, + entity.chainId, + JobType.FORTUNE, + entity.status, + ); - return escrows - .filter( - (escrow) => !this.storage[escrow.address]?.includes(workerAddress), - ) - .map((escrow) => escrow.address); + if (data.fields) { + if (data.fields.includes(JobFieldName.CreatedAt)) { + job.createdAt = entity.createdAt.toISOString(); + } + if ( + data.fields.includes(JobFieldName.JobDescription) || + data.fields.includes(JobFieldName.RewardAmount) || + data.fields.includes(JobFieldName.RewardToken) + ) { + const manifest = await this.getManifest( + entity.chainId, + entity.escrowAddress, + ); + if (data.fields.includes(JobFieldName.JobDescription)) { + job.jobDescription = manifest.requesterDescription; + } + if (data.fields.includes(JobFieldName.RewardAmount)) { + job.rewardAmount = + manifest.fundAmount / manifest.submissionsRequired; + } + if (data.fields.includes(JobFieldName.RewardToken)) { + job.rewardToken = TOKEN; + } + } + } + + return job; + }), + ); + return new PageDto(data.page!, data.pageSize!, itemCount, jobs); } public async solveJob( @@ -98,35 +135,28 @@ export class JobService { solution: string, ): Promise { if (!ethers.isAddress(escrowAddress)) { - throw new Error('Invalid address'); + throw new BadRequestException('Invalid address'); } - const solutionsUrl = await this.addSolution( - chainId, + const assignment = await this.assignmentRepository.findOneByEscrowAndWorker( escrowAddress, workerAddress, - solution, ); + if (!assignment) { + throw new BadRequestException('User is not assigned to the job'); + } - const signer = this.web3Service.getSigner(chainId); - const escrowClient = await EscrowClient.build(signer); - const recordingOracleAddress = - await escrowClient.getRecordingOracleAddress(escrowAddress); + await this.addSolution(chainId, escrowAddress, workerAddress, solution); - const leader = await OperatorUtils.getLeader( - chainId, - recordingOracleAddress, - ); + assignment.status = AssignmentStatus.VALIDATION; + await this.assignmentRepository.updateOne(assignment); - const recordingOracleWebhookUrl = leader?.webhookUrl; - if (!recordingOracleWebhookUrl) - throw new NotFoundException('Unable to get Recording Oracle webhook URL'); + const webhook = new WebhookEntity(); + webhook.escrowAddress = escrowAddress; + webhook.chainId = chainId; + webhook.eventType = EventType.SUBMISSION_IN_REVIEW; - await this.sendWebhook(recordingOracleWebhookUrl, { - escrowAddress: escrowAddress, - chainId: chainId, - solutionsUrl: solutionsUrl, - }); + await this.webhookRepository.createUnique(webhook); } public async processInvalidJobSolution( @@ -206,20 +236,7 @@ export class JobService { return url; } - private async sendWebhook(url: string, body: any): Promise { - const snake_case_body = CaseConverter.transformToSnakeCase(body); - const signedBody = await signMessage( - snake_case_body, - this.configService.get(ConfigNames.WEB3_PRIVATE_KEY)!, - ); - await firstValueFrom( - this.httpService.post(url, snake_case_body, { - headers: { [HEADER_SIGNATURE_KEY]: signedBody }, - }), - ); - } - - private async getManifest( + public async getManifest( chainId: number, escrowAddress: string, ): Promise { @@ -256,30 +273,13 @@ export class JobService { } if (!manifest) { - const signer = this.web3Service.getSigner(chainId); - const escrowClient = await EscrowClient.build(signer); - const jobLauncherAddress = - await escrowClient.getJobLauncherAddress(escrowAddress); - const jobLauncher = await OperatorUtils.getLeader( - chainId, - jobLauncherAddress, - ); - const jobLauncherWebhookUrl = jobLauncher?.webhookUrl; + const webhook = new WebhookEntity(); + webhook.escrowAddress = escrowAddress; + webhook.chainId = chainId; + webhook.eventType = EventType.TASK_CREATION_FAILED; - if (!jobLauncherWebhookUrl) { - throw new NotFoundException('Unable to get Job Launcher webhook URL'); - } + await this.webhookRepository.createUnique(webhook); - const body: WebhookDto = { - escrowAddress: escrowAddress, - chainId: chainId, - eventType: EventType.TASK_CREATION_FAILED, - eventData: { assignments: [{ reason: 'Unable to get manifest' }] }, - }; - await this.sendWebhook( - jobLauncherWebhookUrl + ESCROW_FAILED_ENDPOINT, - body, - ); throw new NotFoundException('Unable to get manifest'); } else return manifest; } diff --git a/packages/apps/fortune/exchange-oracle/server/src/modules/stats/stats.controller.spec.ts b/packages/apps/fortune/exchange-oracle/server/src/modules/stats/stats.controller.spec.ts new file mode 100644 index 0000000000..5923a6c8d6 --- /dev/null +++ b/packages/apps/fortune/exchange-oracle/server/src/modules/stats/stats.controller.spec.ts @@ -0,0 +1,47 @@ +import { createMock } from '@golevelup/ts-jest'; +import { Test } from '@nestjs/testing'; +import { StatsController } from './stats.controller'; +import { StatsService } from './stats.service'; +import { AssignmentStatsDto, OracleStatsDto } from './stats.dto'; +import { RequestWithUser } from '../../common/types/jwt'; + +jest.mock('../../common/utils/signature'); + +describe('statsController', () => { + let statsController: StatsController; + let statsService: StatsService; + const userAddress = '0x1234567890123456789012345678901234567890'; + + beforeAll(async () => { + const moduleRef = await Test.createTestingModule({ + imports: [], + controllers: [StatsController], + providers: [ + { provide: StatsService, useValue: createMock() }, + ], + }).compile(); + + statsController = moduleRef.get(StatsController); + statsService = moduleRef.get(StatsService); + }); + + describe('processWebhook', () => { + it('should call statsService.getOracleStats', async () => { + const stats = new OracleStatsDto(); + jest.spyOn(statsService, 'getOracleStats').mockResolvedValue(stats); + const result = await statsController.getOracleStats(); + expect(result).toBe(stats); + expect(statsService.getOracleStats).toHaveBeenCalledWith(); + }); + + it('should call statsService.getAssignmentStats', async () => { + const stats = new AssignmentStatsDto(); + jest.spyOn(statsService, 'getAssignmentStats').mockResolvedValue(stats); + const result = await statsController.getAssignmentStats({ + user: { address: userAddress }, + } as RequestWithUser); + expect(result).toBe(stats); + expect(statsService.getAssignmentStats).toHaveBeenCalledWith(userAddress); + }); + }); +}); diff --git a/packages/apps/fortune/exchange-oracle/server/src/modules/stats/stats.controller.ts b/packages/apps/fortune/exchange-oracle/server/src/modules/stats/stats.controller.ts new file mode 100644 index 0000000000..44033ddf46 --- /dev/null +++ b/packages/apps/fortune/exchange-oracle/server/src/modules/stats/stats.controller.ts @@ -0,0 +1,55 @@ +import { StatsService } from './stats.service'; +import { AssignmentStatsDto, OracleStatsDto } from './stats.dto'; +import { + ApiTags, + ApiBearerAuth, + ApiResponse, + ApiOperation, +} from '@nestjs/swagger'; +import { Controller, Get, UseGuards, Request } from '@nestjs/common'; +import { JwtAuthGuard } from '../../common/guards/jwt.auth'; +import { RequestWithUser } from '../../common/types/jwt'; +import { Public } from '../../common/decorators'; + +@ApiTags('Stats') +@Controller('stats') +@UseGuards(JwtAuthGuard) +@ApiBearerAuth() +export class StatsController { + constructor(private readonly statsService: StatsService) {} + + @Get() + @Public() + @ApiOperation({ + summary: 'Get Oracle Stats', + description: 'Endpoint to retrieve statistics related to the Oracle.', + }) + @ApiResponse({ + status: 200, + description: 'Oracle stats retrieved successfully.', + type: OracleStatsDto, + }) + async getOracleStats(): Promise { + return this.statsService.getOracleStats(); + } + + @Get('assignment') + @ApiOperation({ + summary: 'Get Assignment Stats', + description: 'Endpoint to retrieve statistics related to user assignments.', + }) + @ApiResponse({ + status: 200, + description: 'Assignment stats retrieved successfully.', + type: AssignmentStatsDto, + }) + @ApiResponse({ + status: 401, + description: 'Unauthorized. Missing or invalid credentials.', + }) + async getAssignmentStats( + @Request() req: RequestWithUser, + ): Promise { + return this.statsService.getAssignmentStats(req.user.address); + } +} diff --git a/packages/apps/fortune/exchange-oracle/server/src/modules/stats/stats.dto.ts b/packages/apps/fortune/exchange-oracle/server/src/modules/stats/stats.dto.ts new file mode 100644 index 0000000000..3f175e80a3 --- /dev/null +++ b/packages/apps/fortune/exchange-oracle/server/src/modules/stats/stats.dto.ts @@ -0,0 +1,57 @@ +import { ApiProperty } from '@nestjs/swagger'; + +export class OracleStatsDto { + @ApiProperty({ + name: 'workers_total', + }) + workersTotal: number; + + @ApiProperty({ + name: 'assignments_completed', + }) + assignmentsCompleted: number; + + @ApiProperty({ + name: 'assignments_rejected', + }) + assignmentsRejected: number; + + @ApiProperty({ + name: 'assignments_expired', + }) + assignmentsExpired: number; + + constructor(init?: Partial) { + Object.assign(this, init); + } +} +export class AssignmentStatsDto { + @ApiProperty({ + name: 'assignments_total', + }) + assignmentsTotal: number; + + @ApiProperty({ + name: 'submissions_sent', + }) + submissionsSent: number; + + @ApiProperty({ + name: 'assignments_completed', + }) + assignmentsCompleted: number; + + @ApiProperty({ + name: 'assignments_rejected', + }) + assignmentsRejected: number; + + @ApiProperty({ + name: 'assignments_expired', + }) + assignmentsExpired: number; + + constructor(init?: Partial) { + Object.assign(this, init); + } +} diff --git a/packages/apps/fortune/exchange-oracle/server/src/modules/stats/stats.module.ts b/packages/apps/fortune/exchange-oracle/server/src/modules/stats/stats.module.ts new file mode 100644 index 0000000000..86dd695523 --- /dev/null +++ b/packages/apps/fortune/exchange-oracle/server/src/modules/stats/stats.module.ts @@ -0,0 +1,15 @@ +import { Module } from '@nestjs/common'; +import { TypeOrmModule } from '@nestjs/typeorm'; +import { AssignmentEntity } from '../assignment/assignment.entity'; +import { StatsService } from './stats.service'; +import { StatsController } from './stats.controller'; +import { AssignmentRepository } from '../assignment/assignment.repository'; +import { ConfigModule } from '@nestjs/config'; + +@Module({ + imports: [TypeOrmModule.forFeature([AssignmentEntity]), ConfigModule], + controllers: [StatsController], + providers: [StatsService, AssignmentRepository], + exports: [StatsService], +}) +export class StatsModule {} diff --git a/packages/apps/fortune/exchange-oracle/server/src/modules/stats/stats.service.ts b/packages/apps/fortune/exchange-oracle/server/src/modules/stats/stats.service.ts new file mode 100644 index 0000000000..ff62f4d9d0 --- /dev/null +++ b/packages/apps/fortune/exchange-oracle/server/src/modules/stats/stats.service.ts @@ -0,0 +1,37 @@ +import { Injectable, Logger } from '@nestjs/common'; +import { AssignmentRepository } from '../assignment/assignment.repository'; +import { AssignmentStatsDto, OracleStatsDto } from './stats.dto'; + +@Injectable() +export class StatsService { + public readonly logger = new Logger(StatsService.name); + constructor(private assignmentRepository: AssignmentRepository) {} + + async getOracleStats(): Promise { + return new OracleStatsDto({ + workersTotal: await this.assignmentRepository.countTotalWorkers(), + assignmentsCompleted: + await this.assignmentRepository.countCompletedAssignments(), + assignmentsExpired: + await this.assignmentRepository.countExpiredAssignments(), + assignmentsRejected: + await this.assignmentRepository.countRejectedAssignments(), + }); + } + async getAssignmentStats(workerAddress: string): Promise { + return new AssignmentStatsDto({ + assignmentsTotal: + await this.assignmentRepository.countTotalAssignments(workerAddress), + submissionsSent: + await this.assignmentRepository.countSentAssignments(workerAddress), + assignmentsCompleted: + await this.assignmentRepository.countCompletedAssignments( + workerAddress, + ), + assignmentsExpired: + await this.assignmentRepository.countExpiredAssignments(workerAddress), + assignmentsRejected: + await this.assignmentRepository.countRejectedAssignments(workerAddress), + }); + } +} diff --git a/packages/apps/fortune/exchange-oracle/server/src/modules/webhook/webhook.controller.spec.ts b/packages/apps/fortune/exchange-oracle/server/src/modules/webhook/webhook.controller.spec.ts index c7c6adcb74..ff78a09b76 100644 --- a/packages/apps/fortune/exchange-oracle/server/src/modules/webhook/webhook.controller.spec.ts +++ b/packages/apps/fortune/exchange-oracle/server/src/modules/webhook/webhook.controller.spec.ts @@ -1,85 +1,25 @@ -import { ConfigService } from '@nestjs/config'; +import { createMock } from '@golevelup/ts-jest'; import { Test } from '@nestjs/testing'; +import { MOCK_SIGNATURE } from '../../../test/constants'; +import { EventType } from '../../common/enums/webhook'; import { WebhookController } from './webhook.controller'; -import { WebhookService } from './webhook.service'; import { WebhookDto } from './webhook.dto'; -import { Web3Service } from '../web3/web3.service'; -import { HttpService } from '@nestjs/axios'; -import { of } from 'rxjs'; -import { ConfigModule, registerAs } from '@nestjs/config'; -import { - MOCK_REPUTATION_ORACLE_WEBHOOK_URL, - MOCK_S3_ACCESS_KEY, - MOCK_S3_BUCKET, - MOCK_S3_ENDPOINT, - MOCK_S3_PORT, - MOCK_S3_SECRET_KEY, - MOCK_S3_USE_SSL, - MOCK_SIGNATURE, -} from '../../../test/constants'; -import { StorageService } from '../storage/storage.service'; -import { verifySignature } from '../../common/utils/signature'; -import { EventType } from '../../common/enums/webhook'; -import { JobService } from '../job/job.service'; +import { WebhookService } from './webhook.service'; jest.mock('../../common/utils/signature'); describe('webhookController', () => { let webhookController: WebhookController; let webhookService: WebhookService; - const chainId = 1; const escrowAddress = '0x1234567890123456789012345678901234567890'; - const workerAddress = '0x1234567890123456789012345678901234567891'; - - const reputationOracleURL = 'https://example.com/reputationoracle'; - const configServiceMock = { - get: jest.fn().mockReturnValue(reputationOracleURL), - }; beforeAll(async () => { const moduleRef = await Test.createTestingModule({ - imports: [ - ConfigModule.forFeature( - registerAs('s3', () => ({ - accessKey: MOCK_S3_ACCESS_KEY, - secretKey: MOCK_S3_SECRET_KEY, - endPoint: MOCK_S3_ENDPOINT, - port: MOCK_S3_PORT, - useSSL: MOCK_S3_USE_SSL, - bucket: MOCK_S3_BUCKET, - })), - ), - ConfigModule.forFeature( - registerAs('server', () => ({ - reputationOracleWebhookUrl: MOCK_REPUTATION_ORACLE_WEBHOOK_URL, - })), - ), - ], + imports: [], controllers: [WebhookController], providers: [ - WebhookService, - JobService, - { - provide: ConfigService, - useValue: configServiceMock, - }, - { - provide: Web3Service, - useValue: { - getSigner: jest.fn().mockReturnValue({ - address: '0x1234567890123456789012345678901234567892', - getNetwork: jest.fn().mockResolvedValue({ chainId: 1 }), - }), - }, - }, - StorageService, - { - provide: HttpService, - useValue: { - post: jest.fn().mockReturnValue(of({ status: 200, data: {} })), - }, - }, + { provide: WebhookService, useValue: createMock() }, ], }).compile(); @@ -88,71 +28,17 @@ describe('webhookController', () => { }); describe('processWebhook', () => { - afterEach(() => { - jest.restoreAllMocks(); - }); - it('should handle an incoming escrow created webhook', async () => { + it('should call webhookService.handleWebhook', async () => { const webhook: WebhookDto = { chainId, escrowAddress, eventType: EventType.ESCROW_CREATED, }; - jest.spyOn(webhookService, 'handleWebhook'); - - (verifySignature as jest.Mock).mockReturnValue(true); - - await webhookController.processWebhook(MOCK_SIGNATURE, webhook); - - expect(webhookService.handleWebhook).toHaveBeenCalledWith(webhook); - }); - - it('should handle an incoming escrow canceled webhook', async () => { - const webhook: WebhookDto = { - chainId, - escrowAddress, - eventType: EventType.ESCROW_CANCELED, - }; - jest.spyOn(webhookService, 'handleWebhook'); - - (verifySignature as jest.Mock).mockReturnValue(true); - - await webhookController.processWebhook(MOCK_SIGNATURE, webhook); - - expect(webhookService.handleWebhook).toHaveBeenCalledWith(webhook); - }); - - it('should mark a webhook solution as invalid', async () => { - const webhook: WebhookDto = { - chainId, - escrowAddress, - eventType: EventType.SUBMISSION_REJECTED, - eventData: { assignments: [{ assigneeId: workerAddress }] }, - }; - jest.spyOn(webhookService, 'handleWebhook').mockResolvedValue(); - (verifySignature as jest.Mock).mockReturnValue(true); - await webhookController.processWebhook(MOCK_SIGNATURE, webhook); expect(webhookService.handleWebhook).toHaveBeenCalledWith(webhook); }); - - it('should return an error when the event type is invalid', async () => { - const webhook: WebhookDto = { - chainId, - escrowAddress, - eventType: EventType.TASK_CREATION_FAILED, - }; - jest.spyOn(webhookService, 'handleWebhook'); - - (verifySignature as jest.Mock).mockReturnValue(true); - - await expect( - webhookController.processWebhook(MOCK_SIGNATURE, webhook), - ).rejects.toThrow('Invalid webhook event type: task_creation_failed'); - - expect(webhookService.handleWebhook).toHaveBeenCalledWith(webhook); - }); }); }); diff --git a/packages/apps/fortune/exchange-oracle/server/src/modules/webhook/webhook.controller.ts b/packages/apps/fortune/exchange-oracle/server/src/modules/webhook/webhook.controller.ts index cacc7aeb98..d987483662 100644 --- a/packages/apps/fortune/exchange-oracle/server/src/modules/webhook/webhook.controller.ts +++ b/packages/apps/fortune/exchange-oracle/server/src/modules/webhook/webhook.controller.ts @@ -1,11 +1,17 @@ import { Body, Controller, Headers, Post, UseGuards } from '@nestjs/common'; -import { ApiTags } from '@nestjs/swagger'; -import { Public } from '../../common/decorators'; +import { + ApiBody, + ApiResponse, + ApiHeader, + ApiOperation, + ApiTags, +} from '@nestjs/swagger'; import { Role } from '../../common/enums/role'; import { SignatureAuthGuard } from '../../common/guards'; import { WebhookService } from './webhook.service'; import { HEADER_SIGNATURE_KEY } from '../../common/constant'; import { WebhookDto } from './webhook.dto'; +import { Public } from '../../common/decorators'; @Public() @ApiTags('Webhook') @@ -13,14 +19,45 @@ import { WebhookDto } from './webhook.dto'; export class WebhookController { constructor(private readonly webhookService: WebhookService) {} + @Post() @UseGuards( new SignatureAuthGuard([Role.Recording, Role.Reputation, Role.JobLauncher]), ) - @Post('webhook') - processWebhook( + @ApiOperation({ + summary: 'Handle Webhook Events', + description: + 'Receives webhook events related to escrow and task operations.', + }) + @ApiHeader({ + name: HEADER_SIGNATURE_KEY, + description: 'Signature header for authenticating the webhook request.', + required: true, + }) + @ApiBody({ + description: + 'Details of the webhook event, including the type of event and associated data.', + type: WebhookDto, + }) + @ApiResponse({ + status: 200, + description: 'Webhook event processed successfully.', + }) + @ApiResponse({ + status: 400, + description: 'Bad Request.Invalid input parameters.', + }) + @ApiResponse({ + status: 401, + description: 'Unauthorized. Missing or invalid credentials.', + }) + @ApiResponse({ + status: 404, + description: 'Not Found. Could not find the requested content.', + }) + public async processWebhook( @Headers(HEADER_SIGNATURE_KEY) _: string, @Body() body: WebhookDto, - ): Promise { + ): Promise { return this.webhookService.handleWebhook(body); } } diff --git a/packages/apps/fortune/exchange-oracle/server/src/modules/webhook/webhook.dto.ts b/packages/apps/fortune/exchange-oracle/server/src/modules/webhook/webhook.dto.ts index 6f1d077dee..139d064292 100644 --- a/packages/apps/fortune/exchange-oracle/server/src/modules/webhook/webhook.dto.ts +++ b/packages/apps/fortune/exchange-oracle/server/src/modules/webhook/webhook.dto.ts @@ -1,6 +1,12 @@ import { ChainId } from '@human-protocol/sdk'; -import { ApiProperty } from '@nestjs/swagger'; -import { IsArray, IsEnum, IsString, IsObject } from 'class-validator'; +import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; +import { + IsArray, + IsEnum, + IsString, + IsObject, + IsOptional, +} from 'class-validator'; import { IsValidEthereumAddress } from '../../common/validators'; import { EventType } from '../../common/enums/webhook'; @@ -50,9 +56,10 @@ export class WebhookDto { @IsEnum(EventType) public eventType: EventType; - @ApiProperty({ + @ApiPropertyOptional({ name: 'event_data', }) + @IsOptional() @IsObject() public eventData?: EventData; } diff --git a/packages/apps/fortune/exchange-oracle/server/src/modules/webhook/webhook.entity.ts b/packages/apps/fortune/exchange-oracle/server/src/modules/webhook/webhook.entity.ts index 17768a7e3f..05f0d5d164 100644 --- a/packages/apps/fortune/exchange-oracle/server/src/modules/webhook/webhook.entity.ts +++ b/packages/apps/fortune/exchange-oracle/server/src/modules/webhook/webhook.entity.ts @@ -5,7 +5,7 @@ import { BaseEntity } from '../../database/base.entity'; import { EventType, WebhookStatus } from '../../common/enums/webhook'; import { ChainId } from '@human-protocol/sdk'; -@Entity({ schema: NS, name: 'webhook' }) +@Entity({ schema: NS, name: 'webhooks' }) export class WebhookEntity extends BaseEntity { @Column({ type: 'int' }) public chainId: ChainId; diff --git a/packages/apps/fortune/exchange-oracle/server/src/modules/webhook/webhook.module.ts b/packages/apps/fortune/exchange-oracle/server/src/modules/webhook/webhook.module.ts index a8367b96f9..6e627edf55 100644 --- a/packages/apps/fortune/exchange-oracle/server/src/modules/webhook/webhook.module.ts +++ b/packages/apps/fortune/exchange-oracle/server/src/modules/webhook/webhook.module.ts @@ -1,13 +1,27 @@ -import { Module } from '@nestjs/common'; +import { Logger, Module } from '@nestjs/common'; +import { TypeOrmModule } from '@nestjs/typeorm'; +import { ConfigModule } from '@nestjs/config'; import { WebhookController } from './webhook.controller'; import { WebhookService } from './webhook.service'; import { JobModule } from '../job/job.module'; +import { WebhookRepository } from './webhook.repository'; +import { WebhookEntity } from './webhook.entity'; +import { HttpModule } from '@nestjs/axios'; +import { Web3Module } from '../web3/web3.module'; +import { StorageModule } from '../storage/storage.module'; @Module({ - imports: [JobModule], + imports: [ + TypeOrmModule.forFeature([WebhookEntity]), + JobModule, + Web3Module, + ConfigModule, + HttpModule, + StorageModule, + ], controllers: [WebhookController], - providers: [WebhookService], + providers: [Logger, WebhookService, WebhookRepository], exports: [WebhookService], }) export class WebhookModule {} diff --git a/packages/apps/fortune/exchange-oracle/server/src/modules/webhook/webhook.repository.ts b/packages/apps/fortune/exchange-oracle/server/src/modules/webhook/webhook.repository.ts new file mode 100644 index 0000000000..a83f3268de --- /dev/null +++ b/packages/apps/fortune/exchange-oracle/server/src/modules/webhook/webhook.repository.ts @@ -0,0 +1,36 @@ +import { Injectable } from '@nestjs/common'; +import { ConfigService } from '@nestjs/config'; +import { DataSource, LessThanOrEqual } from 'typeorm'; +import { ConfigNames } from '../../common/config'; +import { DEFAULT_MAX_RETRY_COUNT } from '../../common/constant'; +import { WebhookStatus } from '../../common/enums/webhook'; +import { BaseRepository } from '../../database/base.repository'; +import { WebhookEntity } from './webhook.entity'; + +@Injectable() +export class WebhookRepository extends BaseRepository { + constructor( + private dataSource: DataSource, + public readonly configService: ConfigService, + ) { + super(WebhookEntity, dataSource); + } + public findByStatus(status: WebhookStatus): Promise { + return this.find({ + where: { + status: status, + retriesCount: LessThanOrEqual( + this.configService.get( + ConfigNames.MAX_RETRY_COUNT, + DEFAULT_MAX_RETRY_COUNT, + ), + ), + waitUntil: LessThanOrEqual(new Date()), + }, + + order: { + createdAt: 'DESC', + }, + }); + } +} diff --git a/packages/apps/fortune/exchange-oracle/server/src/modules/webhook/webhook.service.spec.ts b/packages/apps/fortune/exchange-oracle/server/src/modules/webhook/webhook.service.spec.ts index 4b6789c2fe..d0d50744fc 100644 --- a/packages/apps/fortune/exchange-oracle/server/src/modules/webhook/webhook.service.spec.ts +++ b/packages/apps/fortune/exchange-oracle/server/src/modules/webhook/webhook.service.spec.ts @@ -1,12 +1,15 @@ +import { createMock } from '@golevelup/ts-jest'; +import { ChainId, EscrowClient, OperatorUtils } from '@human-protocol/sdk'; +import { HttpService } from '@nestjs/axios'; +import { HttpStatus } from '@nestjs/common'; +import { ConfigModule, ConfigService, registerAs } from '@nestjs/config'; import { Test } from '@nestjs/testing'; -import { EventType } from '../../common/enums/webhook'; -import { WebhookDto } from './webhook.dto'; -import { WebhookService } from './webhook.service'; -import { ConfigService } from '@nestjs/config'; -import { JobService } from '../job/job.service'; -import { ConfigModule, registerAs } from '@nestjs/config'; +import { of } from 'rxjs'; import { + JOB_LAUNCHER_WEBHOOK_URL, + MOCK_ADDRESS, MOCK_PRIVATE_KEY, + MOCK_RECORDING_ORACLE_WEBHOOK_URL, MOCK_S3_ACCESS_KEY, MOCK_S3_BUCKET, MOCK_S3_ENDPOINT, @@ -14,13 +17,40 @@ import { MOCK_S3_SECRET_KEY, MOCK_S3_USE_SSL, } from '../../../test/constants'; -import { Web3Service } from '../web3/web3.service'; -import { of } from 'rxjs'; -import { HttpService } from '@nestjs/axios'; +import { + DEFAULT_MAX_RETRY_COUNT, + HEADER_SIGNATURE_KEY, +} from '../../common/constant'; +import { ErrorWebhook } from '../../common/constant/errors'; +import { EventType, WebhookStatus } from '../../common/enums/webhook'; +import { AssignmentRepository } from '../assignment/assignment.repository'; +import { JobRepository } from '../job/job.repository'; +import { JobService } from '../job/job.service'; import { StorageService } from '../storage/storage.service'; +import { Web3Service } from '../web3/web3.service'; +import { WebhookDto } from './webhook.dto'; +import { WebhookEntity } from './webhook.entity'; +import { WebhookRepository } from './webhook.repository'; +import { WebhookService } from './webhook.service'; + +jest.mock('@human-protocol/sdk', () => ({ + ...jest.requireActual('@human-protocol/sdk'), + EscrowClient: { + build: jest.fn(), + }, + OperatorUtils: { + getLeader: jest.fn(), + }, + KVStoreClient: { + build: jest.fn(), + }, +})); describe('WebhookService', () => { - let webhookService: WebhookService, jobService: JobService; + let webhookService: WebhookService, + webhookRepository: WebhookRepository, + jobService: JobService, + httpService: HttpService; const chainId = 1; const escrowAddress = '0x1234567890123456789012345678901234567890'; @@ -36,6 +66,8 @@ describe('WebhookService', () => { switch (key) { case 'WEB3_PRIVATE_KEY': return MOCK_PRIVATE_KEY; + case 'MAX_RETRY_COUNT': + return DEFAULT_MAX_RETRY_COUNT; } }), }; @@ -61,6 +93,15 @@ describe('WebhookService', () => { providers: [ WebhookService, JobService, + { provide: JobRepository, useValue: createMock() }, + { + provide: WebhookRepository, + useValue: createMock(), + }, + { + provide: AssignmentRepository, + useValue: createMock(), + }, StorageService, { provide: ConfigService, @@ -85,7 +126,9 @@ describe('WebhookService', () => { }).compile(); webhookService = moduleRef.get(WebhookService); + webhookRepository = moduleRef.get(WebhookRepository); jobService = moduleRef.get(JobService); + httpService = moduleRef.get(HttpService); }); afterEach(() => { @@ -96,14 +139,16 @@ describe('WebhookService', () => { afterEach(() => { jest.restoreAllMocks(); }); + it('should handle an incoming escrow created webhook', async () => { + jest.spyOn(jobService, 'createJob').mockResolvedValue(); const webhook: WebhookDto = { chainId, escrowAddress, eventType: EventType.ESCROW_CREATED, }; - expect(await webhookService.handleWebhook(webhook)).toBe(undefined); + expect(jobService.createJob).toHaveBeenCalledWith(webhook); }); it('should handle an incoming escrow canceled webhook', async () => { @@ -112,7 +157,6 @@ describe('WebhookService', () => { escrowAddress, eventType: EventType.ESCROW_CANCELED, }; - expect(await webhookService.handleWebhook(webhook)).toBe(undefined); }); @@ -123,11 +167,8 @@ describe('WebhookService', () => { eventType: EventType.SUBMISSION_REJECTED, eventData: { assignments: [{ assigneeId: workerAddress }] }, }; - jest.spyOn(jobService, 'processInvalidJobSolution').mockResolvedValue(); - - await webhookService.handleWebhook(webhook); - + expect(await webhookService.handleWebhook(webhook)).toBe(undefined); expect(jobService.processInvalidJobSolution).toHaveBeenCalledWith( webhook, ); @@ -139,10 +180,152 @@ describe('WebhookService', () => { escrowAddress, eventType: EventType.TASK_CREATION_FAILED, }; - await expect(webhookService.handleWebhook(webhook)).rejects.toThrow( 'Invalid webhook event type: task_creation_failed', ); }); }); + + describe('sendWebhook', () => { + const webhookEntity: Partial = { + id: 1, + chainId: ChainId.LOCALHOST, + escrowAddress: MOCK_ADDRESS, + status: WebhookStatus.PENDING, + waitUntil: new Date(), + eventType: EventType.SUBMISSION_IN_REVIEW, + }; + + it('should throw an error if webhook url is empty', async () => { + jest + .spyOn(webhookService as any, 'getOracleWebhookUrl') + .mockResolvedValue(''); + await expect( + (webhookService as any).sendWebhook(webhookEntity), + ).rejects.toThrowError(ErrorWebhook.UrlNotFound); + }); + + it('should handle error if any exception is thrown', async () => { + jest + .spyOn(webhookService as any, 'getOracleWebhookUrl') + .mockResolvedValue(MOCK_RECORDING_ORACLE_WEBHOOK_URL); + jest.spyOn(httpService as any, 'post').mockImplementation(() => { + return of({ + data: undefined, + }); + }); + await expect( + (webhookService as any).sendWebhook(webhookEntity), + ).rejects.toThrowError(ErrorWebhook.NotSent); + }); + + it('should successfully process a webhook with signature', async () => { + jest + .spyOn(webhookService as any, 'getOracleWebhookUrl') + .mockResolvedValue(MOCK_RECORDING_ORACLE_WEBHOOK_URL); + jest.spyOn(httpService as any, 'post').mockImplementation(() => { + return of({ + status: HttpStatus.CREATED, + }); + }); + expect(await (webhookService as any).sendWebhook(webhookEntity)).toBe( + undefined, + ); + + expect(httpService.post).toHaveBeenCalledWith( + MOCK_RECORDING_ORACLE_WEBHOOK_URL, + { + escrow_address: webhookEntity.escrowAddress, + chain_id: webhookEntity.chainId, + event_type: webhookEntity.eventType, + event_data: { solutions_url: expect.any(String) }, + }, + { headers: { [HEADER_SIGNATURE_KEY]: expect.any(String) } }, + ); + }); + }); + + describe('getOracleWebhookUrl', () => { + it('should get the job launcher webhook URL', async () => { + (EscrowClient.build as any).mockImplementation(() => ({ + getJobLauncherAddress: jest + .fn() + .mockResolvedValue(JOB_LAUNCHER_WEBHOOK_URL), + })); + + (OperatorUtils.getLeader as any).mockResolvedValue({ + webhookUrl: JOB_LAUNCHER_WEBHOOK_URL, + }); + + const result = await (webhookService as any).getOracleWebhookUrl( + JOB_LAUNCHER_WEBHOOK_URL, + ChainId.LOCALHOST, + EventType.TASK_CREATION_FAILED, + ); + + expect(result).toBe(JOB_LAUNCHER_WEBHOOK_URL); + }); + + it('should get the recording oracle webhook URL', async () => { + (EscrowClient.build as any).mockImplementation(() => ({ + getRecordingOracleAddress: jest + .fn() + .mockResolvedValue(MOCK_RECORDING_ORACLE_WEBHOOK_URL), + })); + + (OperatorUtils.getLeader as any).mockResolvedValue({ + webhookUrl: MOCK_RECORDING_ORACLE_WEBHOOK_URL, + }); + + const result = await (webhookService as any).getOracleWebhookUrl( + MOCK_RECORDING_ORACLE_WEBHOOK_URL, + ChainId.LOCALHOST, + EventType.SUBMISSION_IN_REVIEW, + ); + + expect(result).toBe(MOCK_RECORDING_ORACLE_WEBHOOK_URL); + }); + + it('should fail if the event type is not valid', async () => { + await expect( + (webhookService as any).getOracleWebhookUrl( + JOB_LAUNCHER_WEBHOOK_URL, + ChainId.LOCALHOST, + EventType.ESCROW_CREATED, + ), + ).rejects.toThrowError('Invalid outgoing event type'); + }); + }); + + describe('handleWebhookError', () => { + it('should set webhook status to FAILED if retries exceed threshold', async () => { + const webhookEntity: Partial = { + id: 1, + status: WebhookStatus.PENDING, + retriesCount: 5, + }; + await (webhookService as any).handleWebhookError( + webhookEntity, + new Error('Sample error'), + ); + expect(webhookRepository.updateOne).toHaveBeenCalled(); + expect(webhookEntity.status).toBe(WebhookStatus.FAILED); + }); + + it('should increment retries count if below threshold', async () => { + const webhookEntity: Partial = { + id: 1, + status: WebhookStatus.PENDING, + retriesCount: 0, + }; + await (webhookService as any).handleWebhookError( + webhookEntity, + new Error('Sample error'), + ); + expect(webhookRepository.updateOne).toHaveBeenCalled(); + expect(webhookEntity.status).toBe(WebhookStatus.PENDING); + expect(webhookEntity.retriesCount).toBe(1); + expect(webhookEntity.waitUntil).toBeInstanceOf(Date); + }); + }); }); diff --git a/packages/apps/fortune/exchange-oracle/server/src/modules/webhook/webhook.service.ts b/packages/apps/fortune/exchange-oracle/server/src/modules/webhook/webhook.service.ts index 827c47d83e..0c679c42a3 100644 --- a/packages/apps/fortune/exchange-oracle/server/src/modules/webhook/webhook.service.ts +++ b/packages/apps/fortune/exchange-oracle/server/src/modules/webhook/webhook.service.ts @@ -1,16 +1,48 @@ /* eslint-disable @typescript-eslint/no-non-null-assertion */ -import { BadRequestException, Injectable } from '@nestjs/common'; -import { EventType } from '../../common/enums/webhook'; +import { + BadRequestException, + HttpStatus, + Injectable, + Logger, + NotFoundException, +} from '@nestjs/common'; +import { EventType, WebhookStatus } from '../../common/enums/webhook'; import { WebhookDto } from './webhook.dto'; import { JobService } from '../job/job.service'; +import { WebhookEntity } from './webhook.entity'; +import { CaseConverter } from '../../common/utils/case-converter'; +import { signMessage } from '../../common/utils/signature'; +import { ConfigService } from '@nestjs/config'; +import { ConfigNames } from '../../common/config'; +import { + DEFAULT_MAX_RETRY_COUNT, + HEADER_SIGNATURE_KEY, +} from '../../common/constant'; +import { firstValueFrom } from 'rxjs'; +import { HttpService } from '@nestjs/axios'; +import { ErrorWebhook } from '../../common/constant/errors'; +import { WebhookRepository } from './webhook.repository'; +import { ChainId, EscrowClient, OperatorUtils } from '@human-protocol/sdk'; +import { Web3Service } from '../web3/web3.service'; +import { StorageService } from '../storage/storage.service'; @Injectable() export class WebhookService { - constructor(private readonly jobService: JobService) {} + private readonly logger = new Logger(WebhookService.name); + + constructor( + private readonly webhookRepository: WebhookRepository, + private readonly jobService: JobService, + public readonly configService: ConfigService, + public readonly httpService: HttpService, + public readonly web3Service: Web3Service, + public readonly storageService: StorageService, + ) {} public async handleWebhook(wehbook: WebhookDto): Promise { switch (wehbook.eventType) { case EventType.ESCROW_CREATED: + await this.jobService.createJob(wehbook); break; case EventType.ESCROW_CANCELED: @@ -26,4 +58,118 @@ export class WebhookService { ); } } + + /** + * Send a webhook with the provided data. + * @param webhook - Webhook entity containing data for the webhook. + * @returns {Promise} - Returns a promise that resolves when the operation is complete. + * @throws {Error} - Throws an error if an issue occurs during the process. + */ + public async sendWebhook(webhook: WebhookEntity): Promise { + // Configure the HTTP request object. + let config = {}; + const webhookUrl = await this.getOracleWebhookUrl( + webhook.escrowAddress, + webhook.chainId, + webhook.eventType, + ); + + // Check if the webhook URL was found. + if (!webhookUrl) { + this.logger.log(ErrorWebhook.UrlNotFound, WebhookService.name); + throw new NotFoundException(ErrorWebhook.UrlNotFound); + } + + // Build the webhook data object based on the oracle type. + const webhookData: WebhookDto = { + escrowAddress: webhook.escrowAddress, + chainId: webhook.chainId, + eventType: webhook.eventType, + }; + if (webhook.eventType === EventType.SUBMISSION_IN_REVIEW) { + webhookData.eventData = { + solutionsUrl: this.storageService.getJobUrl( + webhook.escrowAddress, + webhook.chainId, + ), + }; + } + const transformedWebhook = CaseConverter.transformToSnakeCase(webhookData); + + const signedBody = await signMessage( + transformedWebhook, + this.configService.get(ConfigNames.WEB3_PRIVATE_KEY)!, + ); + + config = { + headers: { [HEADER_SIGNATURE_KEY]: signedBody }, + }; + + // Make the HTTP request to the webhook. + const { status } = await firstValueFrom( + this.httpService.post(webhookUrl, transformedWebhook, config), + ); + + // Check if the request was successful. + if (status !== HttpStatus.CREATED) { + this.logger.log(ErrorWebhook.NotSent, WebhookService.name); + throw new NotFoundException(ErrorWebhook.NotSent); + } + } + + /** + * Handles errors that occur during webhook processing. + * It logs the error and, based on retry count, updates the webhook status accordingly. + * @param webhookEntity - The entity representing the webhook data. + * @param error - The error object thrown during processing. + * @returns {Promise} - Returns a promise that resolves when the operation is complete. + */ + public async handleWebhookError(webhookEntity: WebhookEntity): Promise { + if ( + webhookEntity.retriesCount >= + this.configService.get( + ConfigNames.MAX_RETRY_COUNT, + DEFAULT_MAX_RETRY_COUNT, + ) + ) { + webhookEntity.status = WebhookStatus.FAILED; + } else { + webhookEntity.waitUntil = new Date(); + webhookEntity.retriesCount = webhookEntity.retriesCount + 1; + } + this.webhookRepository.updateOne(webhookEntity); + } + + /** + * Get the webhook URL from the oracle. + * @param escrowAddress - Escrow contract address. + * @param chainId - Chain ID. + * @param eventType - Webhook event type. + * @returns {Promise} - Returns the webhook URL. + */ + private async getOracleWebhookUrl( + escrowAddress: string, + chainId: ChainId, + eventType: EventType, + ): Promise { + // Get the signer for the given chain. + const signer = this.web3Service.getSigner(chainId); + const escrowClient = await EscrowClient.build(signer); + let oracleAddress: string; + switch (eventType) { + case EventType.TASK_CREATION_FAILED: + oracleAddress = await escrowClient.getJobLauncherAddress(escrowAddress); + break; + case EventType.SUBMISSION_IN_REVIEW: + oracleAddress = + await escrowClient.getRecordingOracleAddress(escrowAddress); + break; + default: + throw new BadRequestException('Invalid outgoing event type'); + } + const oracle = await OperatorUtils.getLeader(chainId, oracleAddress); + const oracleWebhookUrl = oracle.webhookUrl; + + return oracleWebhookUrl; + } } diff --git a/packages/apps/fortune/exchange-oracle/server/test/constants.ts b/packages/apps/fortune/exchange-oracle/server/test/constants.ts index 931d9890aa..eba3a3a323 100644 --- a/packages/apps/fortune/exchange-oracle/server/test/constants.ts +++ b/packages/apps/fortune/exchange-oracle/server/test/constants.ts @@ -11,5 +11,7 @@ export const MOCK_S3_SECRET_KEY = 'secret_key'; export const MOCK_S3_BUCKET = 'solution'; export const MOCK_S3_USE_SSL = false; export const MOCK_REPUTATION_ORACLE_WEBHOOK_URL = 'http://localhost:3000'; +export const MOCK_RECORDING_ORACLE_WEBHOOK_URL = 'http://localhost:3000'; +export const MOCK_EXCHANGE_ORACLE = 'http://localhost:3000/'; export const MOCK_MANIFEST_URL = 'http://localhost/manifest.json'; -export const JOB_LAUNCHER_WEBHOOK_URL = 'https://example.com/reputationoracle'; +export const JOB_LAUNCHER_WEBHOOK_URL = 'https://example.com/joblauncher'; diff --git a/packages/apps/fortune/exchange-oracle/server/typeorm.config.ts b/packages/apps/fortune/exchange-oracle/server/typeorm.config.ts index 4b2b47b997..d636d85e2f 100644 --- a/packages/apps/fortune/exchange-oracle/server/typeorm.config.ts +++ b/packages/apps/fortune/exchange-oracle/server/typeorm.config.ts @@ -10,6 +10,7 @@ dotenv.config({ export default new DataSource({ type: 'postgres', + url: process.env.POSTGRES_URL, host: process.env.POSTGRES_HOST, port: Number(process.env.POSTGRES_PORT), username: process.env.POSTGRES_USER, diff --git a/packages/apps/fortune/recording-oracle/src/app.controller.spec.ts b/packages/apps/fortune/recording-oracle/src/app.controller.spec.ts deleted file mode 100644 index 741a42c324..0000000000 --- a/packages/apps/fortune/recording-oracle/src/app.controller.spec.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { AppController } from './app.controller'; - -describe('AppController', () => { - let appController: AppController; - - beforeEach(() => { - appController = new AppController(); - }); - - describe('Health Check', () => { - it('should return OK', async () => { - expect(await appController.health()).toBe('OK'); - }); - }); -}); diff --git a/packages/apps/fortune/recording-oracle/src/app.controller.ts b/packages/apps/fortune/recording-oracle/src/app.controller.ts index ce6ca41c15..e45abc6909 100644 --- a/packages/apps/fortune/recording-oracle/src/app.controller.ts +++ b/packages/apps/fortune/recording-oracle/src/app.controller.ts @@ -1,15 +1,16 @@ import { Controller, Get, Redirect } from '@nestjs/common'; import { Public } from './common/decorators'; -import { ApiExcludeController } from '@nestjs/swagger'; +import { ApiExcludeController, ApiTags } from '@nestjs/swagger'; @Controller('/') @ApiExcludeController() +@ApiTags('Main') export class AppController { @Public() @Get('/') @Redirect('/swagger', 301) - public health(): string { - return 'OK'; + public redirect(): void { + return; } } diff --git a/packages/apps/job-launcher/client/index.html b/packages/apps/job-launcher/client/index.html index a3cf9a8f13..abbdee95ff 100644 --- a/packages/apps/job-launcher/client/index.html +++ b/packages/apps/job-launcher/client/index.html @@ -19,7 +19,7 @@ Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will work correctly both with client-side routing and a non-root public URL. - Learn how to configure a non-root public URL by running `npm run build`. + Learn how to configure a non-root public URL by running `yarn build`. --> this.createHCaptchaManifest(dto), }, [JobRequestType.FORTUNE]: { - calculateFundAmount: async (dto: JobCvatDto) => dto.fundAmount, + calculateFundAmount: async (dto: JobFortuneDto) => dto.fundAmount, createManifest: async ( dto: JobFortuneDto, requestType: JobRequestType, @@ -752,13 +752,14 @@ export class JobService { jobEntity.status = JobStatus.LAUNCHED; await this.jobRepository.updateOne(jobEntity); + const oracleType = this.getOracleType(jobEntity.requestType); const webhookEntity = new WebhookEntity(); Object.assign(webhookEntity, { escrowAddress: jobEntity.escrowAddress, chainId: jobEntity.chainId, eventType: EventType.ESCROW_CREATED, - oracleType: OracleType.CVAT, - hasSignature: false, + oracleType: oracleType, + hasSignature: oracleType === OracleType.FORTUNE, }); await this.webhookRepository.createUnique(webhookEntity); diff --git a/packages/core/package.json b/packages/core/package.json index da67bae1e8..f3d6e65156 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -11,7 +11,8 @@ "dist/typechain-types" ], "scripts": { - "clean": "hardhat clean && rm -rf abis cache artifacts typechain-types dist", + "clean": "hardhat clean && rm -rf abis cache artifacts typechain-types", + "precompile": "yarn clean", "compile": "hardhat compile", "verify": "hardhat verify", "test": "hardhat test", @@ -24,8 +25,8 @@ "deploy:hub": "hardhat run scripts/deploy-hub.ts", "deploy:spokes": "hardhat run scripts/deploy-spokes.ts", "update:spokes": "hardhat run scripts/update-spokes.ts", - "hub:selfdelegate:vote" : "hardhat run scripts/hub-selfdelegate-vote.ts", - "spoke:selfdelegate:vote" : "hardhat run scripts/spoke-selfdelegate-vote.ts", + "hub:selfdelegate:vote": "hardhat run scripts/hub-selfdelegate-vote.ts", + "spoke:selfdelegate:vote": "hardhat run scripts/spoke-selfdelegate-vote.ts", "create:proposal": "hardhat run scripts/create-proposal.ts", "lint": "eslint .", "lint:fix": "eslint . --fix", @@ -33,7 +34,7 @@ "format:scripts": "prettier --write '**/*.ts'", "format": "yarn format:contracts && yarn format:scripts", "build": "yarn compile && tsc", - "prebuild": "yarn clean", + "prebuild": "rm -rf dist", "prepublish": "yarn build" }, "repository": { diff --git a/packages/sdk/typescript/human-protocol-sdk/package.json b/packages/sdk/typescript/human-protocol-sdk/package.json index a2857f8b68..075727e5fb 100644 --- a/packages/sdk/typescript/human-protocol-sdk/package.json +++ b/packages/sdk/typescript/human-protocol-sdk/package.json @@ -12,9 +12,9 @@ "clean": "rm -rf ./dist", "clean:doc": "rm -rf ../../../../docs/sdk/typescript/", "prebuild": "yarn workspace @human-protocol/core build", - "build": "npm run clean && tsc", - "build:doc": "npm run clean:doc && npx typedoc --plugin typedoc-plugin-markdown --out ../../../../docs/sdk/typescript/", - "prepublish": "npm run build", + "build": "yarn clean && tsc", + "build:doc": "yarn clean:doc && npx typedoc --plugin typedoc-plugin-markdown --out ../../../../docs/sdk/typescript/", + "prepublish": "yarn build", "test": "vitest -u", "lint": "eslint .", "lint:fix": "eslint . --fix", diff --git a/packages/sdk/typescript/subgraph/README.md b/packages/sdk/typescript/subgraph/README.md index e76ba54067..a9e195c21a 100644 --- a/packages/sdk/typescript/subgraph/README.md +++ b/packages/sdk/typescript/subgraph/README.md @@ -17,13 +17,13 @@ npm install 1. Generate & deploy on matic ```bash -npm run quickstart:matic +yarn quickstart:matic ``` 2. Generate & deploy on goerli ```bash -npm run quickstart:goerli +yarn quickstart:goerli ``` You can access it on `http://localhost:8020/` @@ -37,11 +37,11 @@ The deployment of the graph on each network is automatically triggered by the gi To run tests next commands should be executed: ```bash -npm run codegen +yarn codegen -npm run build +yarn build -npm test +yarn test ``` ### Supported networks diff --git a/scripts/Makefile b/scripts/Makefile index 5e06f71614..4499987023 100644 --- a/scripts/Makefile +++ b/scripts/Makefile @@ -41,7 +41,7 @@ minio: database: docker compose -f ./fortune/docker-compose.yml up -d postgres -job-launcher-server: +job-launcher-server: rpc-health-check yarn workspace @human-protocol/job-launcher-server setup:local NODE_ENV=local yarn workspace @human-protocol/job-launcher-server migration:run NODE_ENV=local yarn workspace @human-protocol/job-launcher-server start @@ -49,17 +49,22 @@ job-launcher-server: job-launcher-client: NODE_ENV=local yarn workspace @human-protocol/job-launcher-client start -fortune-exchange-oracle: +fortune-exchange-oracle: rpc-health-check yarn workspace @human-protocol/fortune-exchange-oracle-server setup:local NODE_ENV=local yarn workspace @human-protocol/fortune-exchange-oracle-server migration:run NODE_ENV=local yarn workspace @human-protocol/fortune-exchange-oracle-server start -fortune-recording-oracle: +fortune-recording-oracle: rpc-health-check yarn workspace @human-protocol/fortune-recording-oracle setup:local NODE_ENV=local yarn workspace @human-protocol/fortune-recording-oracle start reputation-oracle: yarn workspace @human-protocol/reputation-oracle setup:local + +rpc-health-check: + @until curl -s -X POST "http://localhost:8545" -H "Content-Type: application/json" --data '{"jsonrpc":"2.0","method":"web3_clientVersion","params":[],"id":1}' | grep -q "result"; do \ + sleep 2; \ + done fortune: check-core-folders minio @echo "RUNNING FORTUNE..." diff --git a/scripts/fortune/.env.exco-server b/scripts/fortune/.env.exco-server index c3a2c6fd83..387769f56d 100644 --- a/scripts/fortune/.env.exco-server +++ b/scripts/fortune/.env.exco-server @@ -17,6 +17,8 @@ S3_ENDPOINT=localhost S3_PORT=9000 S3_ACCESS_KEY=access-key S3_SECRET_KEY=secret-key +S3_BUCKET=bucket +S3_USE_SSL=false WEB3_PRIVATE_KEY=0x5de4111afa1a4b94908f83103eb1f1706367c2e68ca870fc3fb9a804cdab365a #0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC diff --git a/scripts/fortune/.env.jl-server b/scripts/fortune/.env.jl-server index 635b6b7e9a..4fd1f43f5e 100644 --- a/scripts/fortune/.env.jl-server +++ b/scripts/fortune/.env.jl-server @@ -58,7 +58,7 @@ S3_ENDPOINT=localhost S3_PORT=9000 S3_ACCESS_KEY=access-key S3_SECRET_KEY=secret-key -S3_BUCKET=manifests +S3_BUCKET=bucket S3_USE_SSL=false MINIO_ROOT_USER=access-key diff --git a/scripts/fortune/.env.rec-oracle b/scripts/fortune/.env.rec-oracle index e8e7c31321..b75f2f6047 100644 --- a/scripts/fortune/.env.rec-oracle +++ b/scripts/fortune/.env.rec-oracle @@ -8,6 +8,8 @@ S3_ENDPOINT=localhost S3_PORT=9000 S3_ACCESS_KEY=access-key S3_SECRET_KEY=secret-key +S3_BUCKET=bucket +S3_USE_SSL=false WEB3_PRIVATE_KEY=0x7c852118294e51e653712a81e05800f419141751be58f605c371e15141b007a6 #0x90F79bf6EB2c4f870365E785982E1f101E93b906 diff --git a/yarn.lock b/yarn.lock index bd5cf44c11..20dad6d60a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8,27 +8,27 @@ integrity sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA== "@adobe/css-tools@^4.3.2": - version "4.3.2" - resolved "https://registry.yarnpkg.com/@adobe/css-tools/-/css-tools-4.3.2.tgz#a6abc715fb6884851fca9dad37fc34739a04fd11" - integrity sha512-DA5a1C0gD/pLOvhv33YMrbf2FK3oUzwNl9oOJqE4XVjuEtt6XIakRcsd7eLiOSPkp1kTRQGICTA8cKra/vFbjw== + version "4.3.3" + resolved "https://registry.yarnpkg.com/@adobe/css-tools/-/css-tools-4.3.3.tgz#90749bde8b89cd41764224f5aac29cd4138f75ff" + integrity sha512-rE0Pygv0sEZ4vBWHlAgJLGDU7Pm8xoO6p3wsEceb7GYAjScrOHpEo8KK/eVkAcnSM+slAEtXjA2JpdjLp4fJQQ== -"@adraffy/ens-normalize@1.10.0", "@adraffy/ens-normalize@^1.8.8": +"@adraffy/ens-normalize@1.10.0": version "1.10.0" resolved "https://registry.yarnpkg.com/@adraffy/ens-normalize/-/ens-normalize-1.10.0.tgz#d2a39395c587e092d77cbbc80acf956a54f38bf7" integrity sha512-nA9XHtlAkYfJxY7bce8DcN7eKxWWCWkU+1GR9d+U6MbNpfwQp8TI7vqOsBsMcHoT4mBu2kypKoSKnghEzOOq5Q== -"@adraffy/ens-normalize@1.10.1": +"@adraffy/ens-normalize@1.10.1", "@adraffy/ens-normalize@^1.8.8": version "1.10.1" resolved "https://registry.yarnpkg.com/@adraffy/ens-normalize/-/ens-normalize-1.10.1.tgz#63430d04bd8c5e74f8d7d049338f1cd9d4f02069" integrity sha512-96Z2IP3mYmF1Xg2cDm8f1gWGf/HUVedQ3FMifV4kG/PQ4yEP51xDtRAEfhVNt5f/uzpNkZHwWQuUcu6D6K+Ekw== "@ampproject/remapping@^2.2.0": - version "2.2.1" - resolved "https://registry.yarnpkg.com/@ampproject/remapping/-/remapping-2.2.1.tgz#99e8e11851128b8702cd57c33684f1d0f260b630" - integrity sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg== + version "2.3.0" + resolved "https://registry.yarnpkg.com/@ampproject/remapping/-/remapping-2.3.0.tgz#ed441b6fa600072520ce18b43d2c8cc8caecc7f4" + integrity sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw== dependencies: - "@jridgewell/gen-mapping" "^0.3.0" - "@jridgewell/trace-mapping" "^0.3.9" + "@jridgewell/gen-mapping" "^0.3.5" + "@jridgewell/trace-mapping" "^0.3.24" "@angular-devkit/core@16.0.1": version "16.0.1" @@ -65,17 +65,19 @@ rxjs "7.8.1" "@apollo/client@^3.7.12": - version "3.8.8" - resolved "https://registry.yarnpkg.com/@apollo/client/-/client-3.8.8.tgz#1a004b2e6de4af38668249a7d7790f6a3431e475" - integrity sha512-omjd9ryGDkadZrKW6l5ktUAdS4SNaFOccYQ4ZST0HLW83y8kQaSZOCTNlpkoBUK8cv6qP8+AxOKwLm2ho8qQ+Q== + version "3.9.7" + resolved "https://registry.yarnpkg.com/@apollo/client/-/client-3.9.7.tgz#e2b6f2d0240a6420753fb658a021dfd0637f2a56" + integrity sha512-OEEwt55bkFhqihCT5d75KUxZt50JZ9MuIYwG7VZlyPPIAb9K+qzVWlXWlf3tB5DaV43yXkUSLQfNpdIBFOB55Q== dependencies: "@graphql-typed-document-node/core" "^3.1.1" + "@wry/caches" "^1.0.0" "@wry/equality" "^0.5.6" "@wry/trie" "^0.5.0" graphql-tag "^2.12.6" hoist-non-react-statics "^3.3.2" optimism "^0.18.0" prop-types "^15.7.2" + rehackt "0.0.6" response-iterator "^0.2.6" symbol-observable "^4.0.0" ts-invariant "^0.10.3" @@ -128,12 +130,12 @@ tslib "^1.11.1" "@aws-sdk/types@^3.1.0": - version "3.485.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/types/-/types-3.485.0.tgz#9ffebb602bba4b6b75e2b037ee93a8735c06da3e" - integrity sha512-+QW32YQdvZRDOwrAQPo/qCyXoSjgXB6RwJwCwkd8ebJXRXw6tmGKIHaZqYHt/LtBymvnaBgBBADNa4+qFvlOFw== + version "3.535.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/types/-/types-3.535.0.tgz#5e6479f31299dd9df170e63f4d10fe739008cf04" + integrity sha512-aY4MYfduNj+sRR37U7XxYR8wemfbKP6lx00ze2M2uubn7mZotuVrWYAafbMSXrdEMSToE5JDhr28vArSOoLcSg== dependencies: - "@smithy/types" "^2.8.0" - tslib "^2.5.0" + "@smithy/types" "^2.12.0" + tslib "^2.6.2" "@aws-sdk/util-utf8-browser@^3.0.0": version "3.259.0" @@ -142,34 +144,34 @@ dependencies: tslib "^2.3.1" -"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.10.4", "@babel/code-frame@^7.12.13", "@babel/code-frame@^7.16.7", "@babel/code-frame@^7.18.6", "@babel/code-frame@^7.22.13", "@babel/code-frame@^7.23.5": - version "7.23.5" - resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.23.5.tgz#9009b69a8c602293476ad598ff53e4562e15c244" - integrity sha512-CgH3s1a96LipHCmSUmYFPwY7MNx8C3avkq7i4Wl3cfa662ldtUe4VM1TPXX70pfmrlWTb6jLqTYrZyT2ZTJBgA== +"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.10.4", "@babel/code-frame@^7.12.13", "@babel/code-frame@^7.16.7", "@babel/code-frame@^7.18.6", "@babel/code-frame@^7.23.5", "@babel/code-frame@^7.24.1": + version "7.24.2" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.24.2.tgz#718b4b19841809a58b29b68cde80bc5e1aa6d9ae" + integrity sha512-y5+tLQyV8pg3fsiln67BVLD1P13Eg4lh5RW9mF0zUuvLrv9uIQ4MCL+CRT+FTsBlBjcIan6PGsLcBN0m3ClUyQ== dependencies: - "@babel/highlight" "^7.23.4" - chalk "^2.4.2" + "@babel/highlight" "^7.24.2" + picocolors "^1.0.0" -"@babel/compat-data@^7.22.6", "@babel/compat-data@^7.23.3", "@babel/compat-data@^7.23.5": - version "7.23.5" - resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.23.5.tgz#ffb878728bb6bdcb6f4510aa51b1be9afb8cfd98" - integrity sha512-uU27kfDRlhfKl+w1U6vp16IuvSLtjAxdArVXPa9BvLkrr7CYIsxH5adpHObeAGY/41+syctUWOZ140a2Rvkgjw== +"@babel/compat-data@^7.22.6", "@babel/compat-data@^7.23.5", "@babel/compat-data@^7.24.1": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.24.1.tgz#31c1f66435f2a9c329bb5716a6d6186c516c3742" + integrity sha512-Pc65opHDliVpRHuKfzI+gSA4zcgr65O4cl64fFJIWEEh8JoHIHh0Oez1Eo8Arz8zq/JhgKodQaxEwUPRtZylVA== -"@babel/core@^7.11.6", "@babel/core@^7.12.3", "@babel/core@^7.16.0", "@babel/core@^7.22.20", "@babel/core@^7.23.5": - version "7.23.7" - resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.23.7.tgz#4d8016e06a14b5f92530a13ed0561730b5c6483f" - integrity sha512-+UpDgowcmqe36d4NwqvKsyPMlOLNGMsfMmQ5WGCu+siCe3t3dfe9njrzGfdN4qq+bcNUt0+Vw6haRxBOycs4dw== +"@babel/core@^7.11.6", "@babel/core@^7.12.3", "@babel/core@^7.16.0", "@babel/core@^7.22.20", "@babel/core@^7.23.5", "@babel/core@^7.23.9": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.24.1.tgz#b802f931b6498dcb8fed5a4710881a45abbc2784" + integrity sha512-F82udohVyIgGAY2VVj/g34TpFUG606rumIHjTfVbssPg2zTR7PuuEpZcX8JA6sgBfIYmJrFtWgPvHQuJamVqZQ== dependencies: "@ampproject/remapping" "^2.2.0" - "@babel/code-frame" "^7.23.5" - "@babel/generator" "^7.23.6" + "@babel/code-frame" "^7.24.1" + "@babel/generator" "^7.24.1" "@babel/helper-compilation-targets" "^7.23.6" "@babel/helper-module-transforms" "^7.23.3" - "@babel/helpers" "^7.23.7" - "@babel/parser" "^7.23.6" - "@babel/template" "^7.22.15" - "@babel/traverse" "^7.23.7" - "@babel/types" "^7.23.6" + "@babel/helpers" "^7.24.1" + "@babel/parser" "^7.24.1" + "@babel/template" "^7.24.0" + "@babel/traverse" "^7.24.1" + "@babel/types" "^7.24.0" convert-source-map "^2.0.0" debug "^4.1.0" gensync "^1.0.0-beta.2" @@ -177,22 +179,22 @@ semver "^6.3.1" "@babel/eslint-parser@^7.16.3": - version "7.23.3" - resolved "https://registry.yarnpkg.com/@babel/eslint-parser/-/eslint-parser-7.23.3.tgz#7bf0db1c53b54da0c8a12627373554a0828479ca" - integrity sha512-9bTuNlyx7oSstodm1cR1bECj4fkiknsDa1YniISkJemMY3DGhJNYBECbe6QD/q54mp2J8VO66jW3/7uP//iFCw== + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/eslint-parser/-/eslint-parser-7.24.1.tgz#e27eee93ed1d271637165ef3a86e2b9332395c32" + integrity sha512-d5guuzMlPeDfZIbpQ8+g1NaCNuAGBBGNECh0HVqz1sjOeVLh2CEaifuOysCH18URW6R7pqXINvf5PaR/dC6jLQ== dependencies: "@nicolo-ribaudo/eslint-scope-5-internals" "5.1.1-v1" eslint-visitor-keys "^2.1.0" semver "^6.3.1" -"@babel/generator@^7.23.6", "@babel/generator@^7.7.2": - version "7.23.6" - resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.23.6.tgz#9e1fca4811c77a10580d17d26b57b036133f3c2e" - integrity sha512-qrSfCYxYQB5owCmGLbl8XRpX1ytXlpueOb0N0UmQwA073KZxejgQTzAmJezxvpwQD9uGtK2shHdi55QT+MbjIw== +"@babel/generator@^7.24.1", "@babel/generator@^7.7.2": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.24.1.tgz#e67e06f68568a4ebf194d1c6014235344f0476d0" + integrity sha512-DfCRfZsBcrPEHUfuBMgbJ1Ut01Y/itOs+hY2nFLgqsqXd52/iSiVq5TITtUasIUgm+IIKdY2/1I7auiQOEeC9A== dependencies: - "@babel/types" "^7.23.6" - "@jridgewell/gen-mapping" "^0.3.2" - "@jridgewell/trace-mapping" "^0.3.17" + "@babel/types" "^7.24.0" + "@jridgewell/gen-mapping" "^0.3.5" + "@jridgewell/trace-mapping" "^0.3.25" jsesc "^2.5.1" "@babel/helper-annotate-as-pure@^7.22.5": @@ -209,7 +211,7 @@ dependencies: "@babel/types" "^7.22.15" -"@babel/helper-compilation-targets@^7.22.15", "@babel/helper-compilation-targets@^7.22.6", "@babel/helper-compilation-targets@^7.23.6": +"@babel/helper-compilation-targets@^7.22.6", "@babel/helper-compilation-targets@^7.23.6": version "7.23.6" resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.23.6.tgz#4d79069b16cbcf1461289eccfbbd81501ae39991" integrity sha512-9JB548GZoQVmzrFgp8o7KxdgkTGm6xs9DW0o/Pim72UDjzr5ObUQ6ZzYPqA+g9OTS2bBQoctLJrky0RDCAWRgQ== @@ -220,17 +222,17 @@ lru-cache "^5.1.1" semver "^6.3.1" -"@babel/helper-create-class-features-plugin@^7.18.6", "@babel/helper-create-class-features-plugin@^7.22.15", "@babel/helper-create-class-features-plugin@^7.23.6", "@babel/helper-create-class-features-plugin@^7.23.7": - version "7.23.7" - resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.23.7.tgz#b2e6826e0e20d337143655198b79d58fdc9bd43d" - integrity sha512-xCoqR/8+BoNnXOY7RVSgv6X+o7pmT5q1d+gGcRlXYkI+9B31glE4jeejhKVpA04O1AtzOt7OSQ6VYKP5FcRl9g== +"@babel/helper-create-class-features-plugin@^7.18.6", "@babel/helper-create-class-features-plugin@^7.24.1": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.24.1.tgz#db58bf57137b623b916e24874ab7188d93d7f68f" + integrity sha512-1yJa9dX9g//V6fDebXoEfEsxkZHk3Hcbm+zLhyu6qVgYFLvmTALTeV+jNU9e5RnYtioBrGEOdoI2joMSNQ/+aA== dependencies: "@babel/helper-annotate-as-pure" "^7.22.5" "@babel/helper-environment-visitor" "^7.22.20" "@babel/helper-function-name" "^7.23.0" "@babel/helper-member-expression-to-functions" "^7.23.0" "@babel/helper-optimise-call-expression" "^7.22.5" - "@babel/helper-replace-supers" "^7.22.20" + "@babel/helper-replace-supers" "^7.24.1" "@babel/helper-skip-transparent-expression-wrappers" "^7.22.5" "@babel/helper-split-export-declaration" "^7.22.6" semver "^6.3.1" @@ -244,21 +246,10 @@ regexpu-core "^5.3.1" semver "^6.3.1" -"@babel/helper-define-polyfill-provider@^0.4.4": - version "0.4.4" - resolved "https://registry.yarnpkg.com/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.4.4.tgz#64df615451cb30e94b59a9696022cffac9a10088" - integrity sha512-QcJMILQCu2jm5TFPGA3lCpJJTeEP+mqeXooG/NZbg/h5FTFi6V0+99ahlRsW8/kRLyb24LZVCCiclDedhLKcBA== - dependencies: - "@babel/helper-compilation-targets" "^7.22.6" - "@babel/helper-plugin-utils" "^7.22.5" - debug "^4.1.1" - lodash.debounce "^4.0.8" - resolve "^1.14.2" - -"@babel/helper-define-polyfill-provider@^0.5.0": - version "0.5.0" - resolved "https://registry.yarnpkg.com/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.5.0.tgz#465805b7361f461e86c680f1de21eaf88c25901b" - integrity sha512-NovQquuQLAQ5HuyjCz7WQP9MjRj7dx++yspwiyUiGl9ZyadHRSql1HZh5ogRd8W8w6YM6EQ/NTB8rgjLt5W65Q== +"@babel/helper-define-polyfill-provider@^0.6.1": + version "0.6.1" + resolved "https://registry.yarnpkg.com/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.1.tgz#fadc63f0c2ff3c8d02ed905dcea747c5b0fb74fd" + integrity sha512-o7SDgTJuvx5vLKD6SFvkydkSMBvahDKGiNJzG22IZYXhiqoe9efY7zocICBgzHV4IRg5wdgl2nEL/tulKIEIbA== dependencies: "@babel/helper-compilation-targets" "^7.22.6" "@babel/helper-plugin-utils" "^7.22.5" @@ -286,19 +277,19 @@ dependencies: "@babel/types" "^7.22.5" -"@babel/helper-member-expression-to-functions@^7.22.15", "@babel/helper-member-expression-to-functions@^7.23.0": +"@babel/helper-member-expression-to-functions@^7.23.0": version "7.23.0" resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.23.0.tgz#9263e88cc5e41d39ec18c9a3e0eced59a3e7d366" integrity sha512-6gfrPwh7OuT6gZyJZvd6WbTfrqAo7vm4xCzAXOusKqq/vWdKXphTpj5klHKNmRUU6/QRGlBsyU9mAIPaWHlqJA== dependencies: "@babel/types" "^7.23.0" -"@babel/helper-module-imports@^7.0.0", "@babel/helper-module-imports@^7.16.7", "@babel/helper-module-imports@^7.22.15", "@babel/helper-module-imports@^7.22.5": - version "7.22.15" - resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.22.15.tgz#16146307acdc40cc00c3b2c647713076464bdbf0" - integrity sha512-0pYVBnDKZO2fnSPCrgM/6WMc7eS20Fbok+0r88fp+YtWVLZrp4CkafFGIp+W0VKw4a22sgebPT99y+FDNMdP4w== +"@babel/helper-module-imports@^7.0.0", "@babel/helper-module-imports@^7.16.7", "@babel/helper-module-imports@^7.22.15", "@babel/helper-module-imports@^7.22.5", "@babel/helper-module-imports@^7.24.1": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.24.1.tgz#961ea2c12aad6cfc75b8c396c81608a08283027b" + integrity sha512-HfEWzysMyOa7xI5uQHc/OcZf67/jc+xe/RZlznWQHhbb8Pg1SkRdbK4yEi61aY8wxQA7PkSfoojtLQP/Kpe3og== dependencies: - "@babel/types" "^7.22.15" + "@babel/types" "^7.24.0" "@babel/helper-module-transforms@^7.23.3": version "7.23.3" @@ -318,10 +309,10 @@ dependencies: "@babel/types" "^7.22.5" -"@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.10.4", "@babel/helper-plugin-utils@^7.12.13", "@babel/helper-plugin-utils@^7.14.5", "@babel/helper-plugin-utils@^7.18.6", "@babel/helper-plugin-utils@^7.20.2", "@babel/helper-plugin-utils@^7.22.5", "@babel/helper-plugin-utils@^7.8.0", "@babel/helper-plugin-utils@^7.8.3": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.22.5.tgz#dd7ee3735e8a313b9f7b05a773d892e88e6d7295" - integrity sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg== +"@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.10.4", "@babel/helper-plugin-utils@^7.12.13", "@babel/helper-plugin-utils@^7.14.5", "@babel/helper-plugin-utils@^7.18.6", "@babel/helper-plugin-utils@^7.20.2", "@babel/helper-plugin-utils@^7.22.5", "@babel/helper-plugin-utils@^7.24.0", "@babel/helper-plugin-utils@^7.8.0", "@babel/helper-plugin-utils@^7.8.3": + version "7.24.0" + resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.0.tgz#945681931a52f15ce879fd5b86ce2dae6d3d7f2a" + integrity sha512-9cUznXMG0+FxRuJfvL82QlTqIzhVW9sL0KjMPHhAOOvpQGL8QtdxnBKILjBqxlHyliz0yCa1G903ZXI/FuHy2w== "@babel/helper-remap-async-to-generator@^7.22.20": version "7.22.20" @@ -332,13 +323,13 @@ "@babel/helper-environment-visitor" "^7.22.20" "@babel/helper-wrap-function" "^7.22.20" -"@babel/helper-replace-supers@^7.22.20": - version "7.22.20" - resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.22.20.tgz#e37d367123ca98fe455a9887734ed2e16eb7a793" - integrity sha512-qsW0In3dbwQUbK8kejJ4R7IHVGwHJlV6lpG6UA7a9hSa2YEiAib+N1T2kr6PEeUT+Fl7najmSOS6SmAwCHK6Tw== +"@babel/helper-replace-supers@^7.24.1": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.24.1.tgz#7085bd19d4a0b7ed8f405c1ed73ccb70f323abc1" + integrity sha512-QCR1UqC9BzG5vZl8BMicmZ28RuUBnHhAMddD8yHFHDRH9lLTZ9uUPehX8ctVPT8l0TKblJidqcgUUKGVrePleQ== dependencies: "@babel/helper-environment-visitor" "^7.22.20" - "@babel/helper-member-expression-to-functions" "^7.22.15" + "@babel/helper-member-expression-to-functions" "^7.23.0" "@babel/helper-optimise-call-expression" "^7.22.5" "@babel/helper-simple-access@^7.22.5": @@ -363,16 +354,16 @@ "@babel/types" "^7.22.5" "@babel/helper-string-parser@^7.23.4": - version "7.23.4" - resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.23.4.tgz#9478c707febcbbe1ddb38a3d91a2e054ae622d83" - integrity sha512-803gmbQdqwdf4olxrX4AJyFBV/RTr3rSmOj0rKwesmzlfhYNDEs+/iOcznzpNWlJlIlTJC2QfPFcHB6DlzdVLQ== + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.24.1.tgz#f99c36d3593db9540705d0739a1f10b5e20c696e" + integrity sha512-2ofRCjnnA9y+wk8b9IAREroeUP02KHp431N2mhKniy2yKIDKpbrHv9eXwm8cBeWQYcJmzv5qKCu65P47eCF7CQ== "@babel/helper-validator-identifier@^7.22.20": version "7.22.20" resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz#c4ae002c61d2879e724581d96665583dbc1dc0e0" integrity sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A== -"@babel/helper-validator-option@^7.22.15", "@babel/helper-validator-option@^7.23.5": +"@babel/helper-validator-option@^7.23.5": version "7.23.5" resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.23.5.tgz#907a3fbd4523426285365d1206c423c4c5520307" integrity sha512-85ttAOMLsr53VgXkTbkx8oA6YTfT4q7/HzXSLEYmjcSTJPMPQtvq1BD79Byep5xMUYbGRzEpDsjUf3dyp54IKw== @@ -386,52 +377,53 @@ "@babel/template" "^7.22.15" "@babel/types" "^7.22.19" -"@babel/helpers@^7.23.7": - version "7.23.8" - resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.23.8.tgz#fc6b2d65b16847fd50adddbd4232c76378959e34" - integrity sha512-KDqYz4PiOWvDFrdHLPhKtCThtIcKVy6avWD2oG4GEvyQ+XDZwHD4YQd+H2vNMnq2rkdxsDkU82T+Vk8U/WXHRQ== +"@babel/helpers@^7.24.1": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.24.1.tgz#183e44714b9eba36c3038e442516587b1e0a1a94" + integrity sha512-BpU09QqEe6ZCHuIHFphEFgvNSrubve1FtyMton26ekZ85gRGi6LrTF7zArARp2YvyFxloeiRmtSCq5sjh1WqIg== dependencies: - "@babel/template" "^7.22.15" - "@babel/traverse" "^7.23.7" - "@babel/types" "^7.23.6" + "@babel/template" "^7.24.0" + "@babel/traverse" "^7.24.1" + "@babel/types" "^7.24.0" -"@babel/highlight@^7.23.4": - version "7.23.4" - resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.23.4.tgz#edaadf4d8232e1a961432db785091207ead0621b" - integrity sha512-acGdbYSfp2WheJoJm/EBBBLh/ID8KDc64ISZ9DYtBmC8/Q204PZJLHyzeB5qMzJ5trcOkybd78M4x2KWsUq++A== +"@babel/highlight@^7.24.2": + version "7.24.2" + resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.24.2.tgz#3f539503efc83d3c59080a10e6634306e0370d26" + integrity sha512-Yac1ao4flkTxTteCDZLEvdxg2fZfz1v8M4QpaGypq/WPDqg3ijHYbDfs+LG5hvzSoqaSZ9/Z9lKSP3CjZjv+pA== dependencies: "@babel/helper-validator-identifier" "^7.22.20" chalk "^2.4.2" js-tokens "^4.0.0" + picocolors "^1.0.0" -"@babel/parser@^7.1.0", "@babel/parser@^7.14.7", "@babel/parser@^7.16.8", "@babel/parser@^7.20.7", "@babel/parser@^7.22.15", "@babel/parser@^7.23.6": - version "7.23.6" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.23.6.tgz#ba1c9e512bda72a47e285ae42aff9d2a635a9e3b" - integrity sha512-Z2uID7YJ7oNvAI20O9X0bblw7Qqs8Q2hFy0R9tAfnfLkp5MW0UH9eUvnDSnFwKZ0AvgS1ucqR4KzvVHgnke1VQ== +"@babel/parser@^7.1.0", "@babel/parser@^7.14.7", "@babel/parser@^7.16.8", "@babel/parser@^7.20.7", "@babel/parser@^7.23.9", "@babel/parser@^7.24.0", "@babel/parser@^7.24.1": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.24.1.tgz#1e416d3627393fab1cb5b0f2f1796a100ae9133a" + integrity sha512-Zo9c7N3xdOIQrNip7Lc9wvRPzlRtovHVE4lkz8WEDr7uYh/GMQhSiIgFxGIArRHYdJE5kxtZjAf8rT0xhdLCzg== -"@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@^7.23.3": - version "7.23.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.23.3.tgz#5cd1c87ba9380d0afb78469292c954fee5d2411a" - integrity sha512-iRkKcCqb7iGnq9+3G6rZ+Ciz5VywC4XNRHe57lKM+jOeYAoR0lVqdeeDRfh0tQcTfw/+vBhHn926FmQhLtlFLQ== +"@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@^7.24.1": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.24.1.tgz#b645d9ba8c2bc5b7af50f0fe949f9edbeb07c8cf" + integrity sha512-y4HqEnkelJIOQGd+3g1bTeKsA5c6qM7eOn7VggGVbBc0y8MLSKHacwcIE2PplNlQSj0PqS9rrXL/nkPVK+kUNg== dependencies: - "@babel/helper-plugin-utils" "^7.22.5" + "@babel/helper-plugin-utils" "^7.24.0" -"@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@^7.23.3": - version "7.23.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.23.3.tgz#f6652bb16b94f8f9c20c50941e16e9756898dc5d" - integrity sha512-WwlxbfMNdVEpQjZmK5mhm7oSwD3dS6eU+Iwsi4Knl9wAletWem7kaRsGOG+8UEbRyqxY4SS5zvtfXwX+jMxUwQ== +"@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@^7.24.1": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.24.1.tgz#da8261f2697f0f41b0855b91d3a20a1fbfd271d3" + integrity sha512-Hj791Ii4ci8HqnaKHAlLNs+zaLXb0EzSDhiAWp5VNlyvCNymYfacs64pxTxbH1znW/NcArSmwpmG9IKE/TUVVQ== dependencies: - "@babel/helper-plugin-utils" "^7.22.5" + "@babel/helper-plugin-utils" "^7.24.0" "@babel/helper-skip-transparent-expression-wrappers" "^7.22.5" - "@babel/plugin-transform-optional-chaining" "^7.23.3" + "@babel/plugin-transform-optional-chaining" "^7.24.1" -"@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@^7.23.7": - version "7.23.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.23.7.tgz#516462a95d10a9618f197d39ad291a9b47ae1d7b" - integrity sha512-LlRT7HgaifEpQA1ZgLVOIJZZFVPWN5iReq/7/JixwBtwcoeVGDBD53ZV28rrsLYOZs1Y/EHhA8N/Z6aazHR8cw== +"@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@^7.24.1": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.24.1.tgz#1181d9685984c91d657b8ddf14f0487a6bab2988" + integrity sha512-m9m/fXsXLiHfwdgydIFnpk+7jlVbnvlK5B2EKiPdLUb6WX654ZaaEWJUjk8TftRbZpK0XibovlLWX4KIZhV6jw== dependencies: "@babel/helper-environment-visitor" "^7.22.20" - "@babel/helper-plugin-utils" "^7.22.5" + "@babel/helper-plugin-utils" "^7.24.0" "@babel/plugin-proposal-class-properties@^7.16.0": version "7.18.6" @@ -442,13 +434,13 @@ "@babel/helper-plugin-utils" "^7.18.6" "@babel/plugin-proposal-decorators@^7.16.4": - version "7.23.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-decorators/-/plugin-proposal-decorators-7.23.7.tgz#1d827902cbd3d9054e54fb2f2056cdd1eaa0e368" - integrity sha512-b1s5JyeMvqj7d9m9KhJNHKc18gEJiSyVzVX3bwbiPalQBQpuvfPh6lA9F7Kk/dWH0TIiXRpB9yicwijY6buPng== + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-decorators/-/plugin-proposal-decorators-7.24.1.tgz#bab2b9e174a2680f0a80f341f3ec70f809f8bb4b" + integrity sha512-zPEvzFijn+hRvJuX2Vu3KbEBN39LN3f7tW3MQO2LsIs57B26KU+kUc82BdAktS1VCM6libzh45eKGI65lg0cpA== dependencies: - "@babel/helper-create-class-features-plugin" "^7.23.7" - "@babel/helper-plugin-utils" "^7.22.5" - "@babel/plugin-syntax-decorators" "^7.23.3" + "@babel/helper-create-class-features-plugin" "^7.24.1" + "@babel/helper-plugin-utils" "^7.24.0" + "@babel/plugin-syntax-decorators" "^7.24.1" "@babel/plugin-proposal-nullish-coalescing-operator@^7.16.0": version "7.18.6" @@ -516,12 +508,12 @@ dependencies: "@babel/helper-plugin-utils" "^7.14.5" -"@babel/plugin-syntax-decorators@^7.23.3": - version "7.23.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-decorators/-/plugin-syntax-decorators-7.23.3.tgz#a1d351d6c25bfdcf2e16f99b039101bc0ffcb0ca" - integrity sha512-cf7Niq4/+/juY67E0PbgH0TDhLQ5J7zS8C/Q5FFx+DWyrRa9sUQdTXkjqKu8zGvuqr7vw1muKiukseihU+PJDA== +"@babel/plugin-syntax-decorators@^7.24.1": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-decorators/-/plugin-syntax-decorators-7.24.1.tgz#71d9ad06063a6ac5430db126b5df48c70ee885fa" + integrity sha512-05RJdO/cCrtVWuAaSn1tS3bH8jbsJa/Y1uD186u6J4C/1mnHFxseeuWpsqr9anvo7TUulev7tm7GDwRV+VuhDw== dependencies: - "@babel/helper-plugin-utils" "^7.22.5" + "@babel/helper-plugin-utils" "^7.24.0" "@babel/plugin-syntax-dynamic-import@^7.8.3": version "7.8.3" @@ -537,26 +529,26 @@ dependencies: "@babel/helper-plugin-utils" "^7.8.3" -"@babel/plugin-syntax-flow@^7.23.3": - version "7.23.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-flow/-/plugin-syntax-flow-7.23.3.tgz#084564e0f3cc21ea6c70c44cff984a1c0509729a" - integrity sha512-YZiAIpkJAwQXBJLIQbRFayR5c+gJ35Vcz3bg954k7cd73zqjvhacJuL9RbrzPz8qPmZdgqP6EUKwy0PCNhaaPA== +"@babel/plugin-syntax-flow@^7.24.1": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-flow/-/plugin-syntax-flow-7.24.1.tgz#875c25e3428d7896c87589765fc8b9d32f24bd8d" + integrity sha512-sxi2kLTI5DeW5vDtMUsk4mTPwvlUDbjOnoWayhynCwrw4QXRld4QEYwqzY8JmQXaJUtgUuCIurtSRH5sn4c7mA== dependencies: - "@babel/helper-plugin-utils" "^7.22.5" + "@babel/helper-plugin-utils" "^7.24.0" -"@babel/plugin-syntax-import-assertions@^7.20.0", "@babel/plugin-syntax-import-assertions@^7.23.3": - version "7.23.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.23.3.tgz#9c05a7f592982aff1a2768260ad84bcd3f0c77fc" - integrity sha512-lPgDSU+SJLK3xmFDTV2ZRQAiM7UuUjGidwBywFavObCiZc1BeAAcMtHJKUya92hPHO+at63JJPLygilZard8jw== +"@babel/plugin-syntax-import-assertions@^7.20.0", "@babel/plugin-syntax-import-assertions@^7.24.1": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.24.1.tgz#db3aad724153a00eaac115a3fb898de544e34971" + integrity sha512-IuwnI5XnuF189t91XbxmXeCDz3qs6iDRO7GJ++wcfgeXNs/8FmIlKcpDSXNVyuLQxlwvskmI3Ct73wUODkJBlQ== dependencies: - "@babel/helper-plugin-utils" "^7.22.5" + "@babel/helper-plugin-utils" "^7.24.0" -"@babel/plugin-syntax-import-attributes@^7.23.3": - version "7.23.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.23.3.tgz#992aee922cf04512461d7dae3ff6951b90a2dc06" - integrity sha512-pawnE0P9g10xgoP7yKr6CK63K2FMsTE+FZidZO/1PwRdzmAPVs+HS1mAURUsgaoxammTJvULUdIkEK0gOcU2tA== +"@babel/plugin-syntax-import-attributes@^7.24.1": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.24.1.tgz#c66b966c63b714c4eec508fcf5763b1f2d381093" + integrity sha512-zhQTMH0X2nVLnb04tz+s7AMuasX8U0FnpE+nHTOhSOINjWMnopoZTxtIKsd45n4GQ/HIZLyfIpoul8e2m0DnRA== dependencies: - "@babel/helper-plugin-utils" "^7.22.5" + "@babel/helper-plugin-utils" "^7.24.0" "@babel/plugin-syntax-import-meta@^7.10.4", "@babel/plugin-syntax-import-meta@^7.8.3": version "7.10.4" @@ -572,12 +564,12 @@ dependencies: "@babel/helper-plugin-utils" "^7.8.0" -"@babel/plugin-syntax-jsx@^7.22.5", "@babel/plugin-syntax-jsx@^7.23.3", "@babel/plugin-syntax-jsx@^7.7.2": - version "7.23.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.23.3.tgz#8f2e4f8a9b5f9aa16067e142c1ac9cd9f810f473" - integrity sha512-EB2MELswq55OHUoRZLGg/zC7QWUKfNLpE57m/S2yr1uEneIgsTgrSzXP3NXEsMkVn76OlaVVnzN+ugObuYGwhg== +"@babel/plugin-syntax-jsx@^7.22.5", "@babel/plugin-syntax-jsx@^7.23.3", "@babel/plugin-syntax-jsx@^7.24.1", "@babel/plugin-syntax-jsx@^7.7.2": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.24.1.tgz#3f6ca04b8c841811dbc3c5c5f837934e0d626c10" + integrity sha512-2eCtxZXf+kbkMIsXS4poTvT4Yu5rXiRa+9xGVT56raghjmBTKMpFNc9R4IDiB4emao9eO22Ox7CxuJG7BgExqA== dependencies: - "@babel/helper-plugin-utils" "^7.22.5" + "@babel/helper-plugin-utils" "^7.24.0" "@babel/plugin-syntax-logical-assignment-operators@^7.10.4", "@babel/plugin-syntax-logical-assignment-operators@^7.8.3": version "7.10.4" @@ -635,12 +627,12 @@ dependencies: "@babel/helper-plugin-utils" "^7.14.5" -"@babel/plugin-syntax-typescript@^7.23.3", "@babel/plugin-syntax-typescript@^7.7.2": - version "7.23.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.23.3.tgz#24f460c85dbbc983cd2b9c4994178bcc01df958f" - integrity sha512-9EiNjVJOMwCO+43TqoTrgQ8jMwcAd0sWyXi9RPfIsLTj4R2MADDDQXELhffaUx/uJv2AYcxBgPwH6j4TIA4ytQ== +"@babel/plugin-syntax-typescript@^7.24.1", "@babel/plugin-syntax-typescript@^7.7.2": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.24.1.tgz#b3bcc51f396d15f3591683f90239de143c076844" + integrity sha512-Yhnmvy5HZEnHUty6i++gcfH1/l68AHnItFHnaCv6hn9dNh0hQvvQJsxpi4BMBFN5DLeHBuucT/0DgzXif/OyRw== dependencies: - "@babel/helper-plugin-utils" "^7.22.5" + "@babel/helper-plugin-utils" "^7.24.0" "@babel/plugin-syntax-unicode-sets-regex@^7.18.6": version "7.18.6" @@ -650,220 +642,220 @@ "@babel/helper-create-regexp-features-plugin" "^7.18.6" "@babel/helper-plugin-utils" "^7.18.6" -"@babel/plugin-transform-arrow-functions@^7.23.3": - version "7.23.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.23.3.tgz#94c6dcfd731af90f27a79509f9ab7fb2120fc38b" - integrity sha512-NzQcQrzaQPkaEwoTm4Mhyl8jI1huEL/WWIEvudjTCMJ9aBZNpsJbMASx7EQECtQQPS/DcnFpo0FIh3LvEO9cxQ== +"@babel/plugin-transform-arrow-functions@^7.24.1": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.24.1.tgz#2bf263617060c9cc45bcdbf492b8cc805082bf27" + integrity sha512-ngT/3NkRhsaep9ck9uj2Xhv9+xB1zShY3tM3g6om4xxCELwCDN4g4Aq5dRn48+0hasAql7s2hdBOysCfNpr4fw== dependencies: - "@babel/helper-plugin-utils" "^7.22.5" + "@babel/helper-plugin-utils" "^7.24.0" -"@babel/plugin-transform-async-generator-functions@^7.23.9": - version "7.23.9" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.23.9.tgz#9adaeb66fc9634a586c5df139c6240d41ed801ce" - integrity sha512-8Q3veQEDGe14dTYuwagbRtwxQDnytyg1JFu4/HwEMETeofocrB0U0ejBJIXoeG/t2oXZ8kzCyI0ZZfbT80VFNQ== +"@babel/plugin-transform-async-generator-functions@^7.24.1": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.24.1.tgz#b38009d650b3c419e6708ec5ab4fa5eeffe7b489" + integrity sha512-OTkLJM0OtmzcpOgF7MREERUCdCnCBtBsq3vVFbuq/RKMK0/jdYqdMexWi3zNs7Nzd95ase65MbTGrpFJflOb6A== dependencies: "@babel/helper-environment-visitor" "^7.22.20" - "@babel/helper-plugin-utils" "^7.22.5" + "@babel/helper-plugin-utils" "^7.24.0" "@babel/helper-remap-async-to-generator" "^7.22.20" "@babel/plugin-syntax-async-generators" "^7.8.4" -"@babel/plugin-transform-async-to-generator@^7.23.3": - version "7.23.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.23.3.tgz#d1f513c7a8a506d43f47df2bf25f9254b0b051fa" - integrity sha512-A7LFsKi4U4fomjqXJlZg/u0ft/n8/7n7lpffUP/ZULx/DtV9SGlNKZolHH6PE8Xl1ngCc0M11OaeZptXVkfKSw== +"@babel/plugin-transform-async-to-generator@^7.24.1": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.24.1.tgz#0e220703b89f2216800ce7b1c53cb0cf521c37f4" + integrity sha512-AawPptitRXp1y0n4ilKcGbRYWfbbzFWz2NqNu7dacYDtFtz0CMjG64b3LQsb3KIgnf4/obcUL78hfaOS7iCUfw== dependencies: - "@babel/helper-module-imports" "^7.22.15" - "@babel/helper-plugin-utils" "^7.22.5" + "@babel/helper-module-imports" "^7.24.1" + "@babel/helper-plugin-utils" "^7.24.0" "@babel/helper-remap-async-to-generator" "^7.22.20" -"@babel/plugin-transform-block-scoped-functions@^7.23.3": - version "7.23.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.23.3.tgz#fe1177d715fb569663095e04f3598525d98e8c77" - integrity sha512-vI+0sIaPIO6CNuM9Kk5VmXcMVRiOpDh7w2zZt9GXzmE/9KD70CUEVhvPR/etAeNK/FAEkhxQtXOzVF3EuRL41A== +"@babel/plugin-transform-block-scoped-functions@^7.24.1": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.24.1.tgz#1c94799e20fcd5c4d4589523bbc57b7692979380" + integrity sha512-TWWC18OShZutrv9C6mye1xwtam+uNi2bnTOCBUd5sZxyHOiWbU6ztSROofIMrK84uweEZC219POICK/sTYwfgg== dependencies: - "@babel/helper-plugin-utils" "^7.22.5" + "@babel/helper-plugin-utils" "^7.24.0" -"@babel/plugin-transform-block-scoping@^7.23.4": - version "7.23.4" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.23.4.tgz#b2d38589531c6c80fbe25e6b58e763622d2d3cf5" - integrity sha512-0QqbP6B6HOh7/8iNR4CQU2Th/bbRtBp4KS9vcaZd1fZ0wSh5Fyssg0UCIHwxh+ka+pNDREbVLQnHCMHKZfPwfw== +"@babel/plugin-transform-block-scoping@^7.24.1": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.24.1.tgz#27af183d7f6dad890531256c7a45019df768ac1f" + integrity sha512-h71T2QQvDgM2SmT29UYU6ozjMlAt7s7CSs5Hvy8f8cf/GM/Z4a2zMfN+fjVGaieeCrXR3EdQl6C4gQG+OgmbKw== dependencies: - "@babel/helper-plugin-utils" "^7.22.5" + "@babel/helper-plugin-utils" "^7.24.0" -"@babel/plugin-transform-class-properties@^7.23.3": - version "7.23.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.23.3.tgz#35c377db11ca92a785a718b6aa4e3ed1eb65dc48" - integrity sha512-uM+AN8yCIjDPccsKGlw271xjJtGii+xQIF/uMPS8H15L12jZTsLfF4o5vNO7d/oUguOyfdikHGc/yi9ge4SGIg== +"@babel/plugin-transform-class-properties@^7.24.1": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.24.1.tgz#bcbf1aef6ba6085cfddec9fc8d58871cf011fc29" + integrity sha512-OMLCXi0NqvJfORTaPQBwqLXHhb93wkBKZ4aNwMl6WtehO7ar+cmp+89iPEQPqxAnxsOKTaMcs3POz3rKayJ72g== dependencies: - "@babel/helper-create-class-features-plugin" "^7.22.15" - "@babel/helper-plugin-utils" "^7.22.5" + "@babel/helper-create-class-features-plugin" "^7.24.1" + "@babel/helper-plugin-utils" "^7.24.0" -"@babel/plugin-transform-class-static-block@^7.23.4": - version "7.23.4" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.23.4.tgz#2a202c8787a8964dd11dfcedf994d36bfc844ab5" - integrity sha512-nsWu/1M+ggti1SOALj3hfx5FXzAY06fwPJsUZD4/A5e1bWi46VUIWtD+kOX6/IdhXGsXBWllLFDSnqSCdUNydQ== +"@babel/plugin-transform-class-static-block@^7.24.1": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.24.1.tgz#4e37efcca1d9f2fcb908d1bae8b56b4b6e9e1cb6" + integrity sha512-FUHlKCn6J3ERiu8Dv+4eoz7w8+kFLSyeVG4vDAikwADGjUCoHw/JHokyGtr8OR4UjpwPVivyF+h8Q5iv/JmrtA== dependencies: - "@babel/helper-create-class-features-plugin" "^7.22.15" - "@babel/helper-plugin-utils" "^7.22.5" + "@babel/helper-create-class-features-plugin" "^7.24.1" + "@babel/helper-plugin-utils" "^7.24.0" "@babel/plugin-syntax-class-static-block" "^7.14.5" -"@babel/plugin-transform-classes@^7.23.8": - version "7.23.8" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.23.8.tgz#d08ae096c240347badd68cdf1b6d1624a6435d92" - integrity sha512-yAYslGsY1bX6Knmg46RjiCiNSwJKv2IUC8qOdYKqMMr0491SXFhcHqOdRDeCRohOOIzwN/90C6mQ9qAKgrP7dg== +"@babel/plugin-transform-classes@^7.24.1": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.24.1.tgz#5bc8fc160ed96378184bc10042af47f50884dcb1" + integrity sha512-ZTIe3W7UejJd3/3R4p7ScyyOoafetUShSf4kCqV0O7F/RiHxVj/wRaRnQlrGwflvcehNA8M42HkAiEDYZu2F1Q== dependencies: "@babel/helper-annotate-as-pure" "^7.22.5" "@babel/helper-compilation-targets" "^7.23.6" "@babel/helper-environment-visitor" "^7.22.20" "@babel/helper-function-name" "^7.23.0" - "@babel/helper-plugin-utils" "^7.22.5" - "@babel/helper-replace-supers" "^7.22.20" + "@babel/helper-plugin-utils" "^7.24.0" + "@babel/helper-replace-supers" "^7.24.1" "@babel/helper-split-export-declaration" "^7.22.6" globals "^11.1.0" -"@babel/plugin-transform-computed-properties@^7.23.3": - version "7.23.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.23.3.tgz#652e69561fcc9d2b50ba4f7ac7f60dcf65e86474" - integrity sha512-dTj83UVTLw/+nbiHqQSFdwO9CbTtwq1DsDqm3CUEtDrZNET5rT5E6bIdTlOftDTDLMYxvxHNEYO4B9SLl8SLZw== +"@babel/plugin-transform-computed-properties@^7.24.1": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.24.1.tgz#bc7e787f8e021eccfb677af5f13c29a9934ed8a7" + integrity sha512-5pJGVIUfJpOS+pAqBQd+QMaTD2vCL/HcePooON6pDpHgRp4gNRmzyHTPIkXntwKsq3ayUFVfJaIKPw2pOkOcTw== dependencies: - "@babel/helper-plugin-utils" "^7.22.5" - "@babel/template" "^7.22.15" + "@babel/helper-plugin-utils" "^7.24.0" + "@babel/template" "^7.24.0" -"@babel/plugin-transform-destructuring@^7.23.3": - version "7.23.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.23.3.tgz#8c9ee68228b12ae3dff986e56ed1ba4f3c446311" - integrity sha512-n225npDqjDIr967cMScVKHXJs7rout1q+tt50inyBCPkyZ8KxeI6d+GIbSBTT/w/9WdlWDOej3V9HE5Lgk57gw== +"@babel/plugin-transform-destructuring@^7.24.1": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.24.1.tgz#b1e8243af4a0206841973786292b8c8dd8447345" + integrity sha512-ow8jciWqNxR3RYbSNVuF4U2Jx130nwnBnhRw6N6h1bOejNkABmcI5X5oz29K4alWX7vf1C+o6gtKXikzRKkVdw== dependencies: - "@babel/helper-plugin-utils" "^7.22.5" + "@babel/helper-plugin-utils" "^7.24.0" -"@babel/plugin-transform-dotall-regex@^7.23.3": - version "7.23.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.23.3.tgz#3f7af6054882ede89c378d0cf889b854a993da50" - integrity sha512-vgnFYDHAKzFaTVp+mneDsIEbnJ2Np/9ng9iviHw3P/KVcgONxpNULEW/51Z/BaFojG2GI2GwwXck5uV1+1NOYQ== +"@babel/plugin-transform-dotall-regex@^7.24.1": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.24.1.tgz#d56913d2f12795cc9930801b84c6f8c47513ac13" + integrity sha512-p7uUxgSoZwZ2lPNMzUkqCts3xlp8n+o05ikjy7gbtFJSt9gdU88jAmtfmOxHM14noQXBxfgzf2yRWECiNVhTCw== dependencies: "@babel/helper-create-regexp-features-plugin" "^7.22.15" - "@babel/helper-plugin-utils" "^7.22.5" + "@babel/helper-plugin-utils" "^7.24.0" -"@babel/plugin-transform-duplicate-keys@^7.23.3": - version "7.23.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.23.3.tgz#664706ca0a5dfe8d066537f99032fc1dc8b720ce" - integrity sha512-RrqQ+BQmU3Oyav3J+7/myfvRCq7Tbz+kKLLshUmMwNlDHExbGL7ARhajvoBJEvc+fCguPPu887N+3RRXBVKZUA== +"@babel/plugin-transform-duplicate-keys@^7.24.1": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.24.1.tgz#5347a797fe82b8d09749d10e9f5b83665adbca88" + integrity sha512-msyzuUnvsjsaSaocV6L7ErfNsa5nDWL1XKNnDePLgmz+WdU4w/J8+AxBMrWfi9m4IxfL5sZQKUPQKDQeeAT6lA== dependencies: - "@babel/helper-plugin-utils" "^7.22.5" + "@babel/helper-plugin-utils" "^7.24.0" -"@babel/plugin-transform-dynamic-import@^7.23.4": - version "7.23.4" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.23.4.tgz#c7629e7254011ac3630d47d7f34ddd40ca535143" - integrity sha512-V6jIbLhdJK86MaLh4Jpghi8ho5fGzt3imHOBu/x0jlBaPYqDoWz4RDXjmMOfnh+JWNaQleEAByZLV0QzBT4YQQ== +"@babel/plugin-transform-dynamic-import@^7.24.1": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.24.1.tgz#2a5a49959201970dd09a5fca856cb651e44439dd" + integrity sha512-av2gdSTyXcJVdI+8aFZsCAtR29xJt0S5tas+Ef8NvBNmD1a+N/3ecMLeMBgfcK+xzsjdLDT6oHt+DFPyeqUbDA== dependencies: - "@babel/helper-plugin-utils" "^7.22.5" + "@babel/helper-plugin-utils" "^7.24.0" "@babel/plugin-syntax-dynamic-import" "^7.8.3" -"@babel/plugin-transform-exponentiation-operator@^7.23.3": - version "7.23.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.23.3.tgz#ea0d978f6b9232ba4722f3dbecdd18f450babd18" - integrity sha512-5fhCsl1odX96u7ILKHBj4/Y8vipoqwsJMh4csSA8qFfxrZDEA4Ssku2DyNvMJSmZNOEBT750LfFPbtrnTP90BQ== +"@babel/plugin-transform-exponentiation-operator@^7.24.1": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.24.1.tgz#6650ebeb5bd5c012d5f5f90a26613a08162e8ba4" + integrity sha512-U1yX13dVBSwS23DEAqU+Z/PkwE9/m7QQy8Y9/+Tdb8UWYaGNDYwTLi19wqIAiROr8sXVum9A/rtiH5H0boUcTw== dependencies: "@babel/helper-builder-binary-assignment-operator-visitor" "^7.22.15" - "@babel/helper-plugin-utils" "^7.22.5" + "@babel/helper-plugin-utils" "^7.24.0" -"@babel/plugin-transform-export-namespace-from@^7.23.4": - version "7.23.4" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.23.4.tgz#084c7b25e9a5c8271e987a08cf85807b80283191" - integrity sha512-GzuSBcKkx62dGzZI1WVgTWvkkz84FZO5TC5T8dl/Tht/rAla6Dg/Mz9Yhypg+ezVACf/rgDuQt3kbWEv7LdUDQ== +"@babel/plugin-transform-export-namespace-from@^7.24.1": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.24.1.tgz#f033541fc036e3efb2dcb58eedafd4f6b8078acd" + integrity sha512-Ft38m/KFOyzKw2UaJFkWG9QnHPG/Q/2SkOrRk4pNBPg5IPZ+dOxcmkK5IyuBcxiNPyyYowPGUReyBvrvZs7IlQ== dependencies: - "@babel/helper-plugin-utils" "^7.22.5" + "@babel/helper-plugin-utils" "^7.24.0" "@babel/plugin-syntax-export-namespace-from" "^7.8.3" "@babel/plugin-transform-flow-strip-types@^7.16.0": - version "7.23.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-flow-strip-types/-/plugin-transform-flow-strip-types-7.23.3.tgz#cfa7ca159cc3306fab526fc67091556b51af26ff" - integrity sha512-26/pQTf9nQSNVJCrLB1IkHUKyPxR+lMrH2QDPG89+Znu9rAMbtrybdbWeE9bb7gzjmE5iXHEY+e0HUwM6Co93Q== + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-flow-strip-types/-/plugin-transform-flow-strip-types-7.24.1.tgz#fa8d0a146506ea195da1671d38eed459242b2dcc" + integrity sha512-iIYPIWt3dUmUKKE10s3W+jsQ3icFkw0JyRVyY1B7G4yK/nngAOHLVx8xlhA6b/Jzl/Y0nis8gjqhqKtRDQqHWQ== dependencies: - "@babel/helper-plugin-utils" "^7.22.5" - "@babel/plugin-syntax-flow" "^7.23.3" + "@babel/helper-plugin-utils" "^7.24.0" + "@babel/plugin-syntax-flow" "^7.24.1" -"@babel/plugin-transform-for-of@^7.23.6": - version "7.23.6" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.23.6.tgz#81c37e24171b37b370ba6aaffa7ac86bcb46f94e" - integrity sha512-aYH4ytZ0qSuBbpfhuofbg/e96oQ7U2w1Aw/UQmKT+1l39uEhUPoFS3fHevDc1G0OvewyDudfMKY1OulczHzWIw== +"@babel/plugin-transform-for-of@^7.24.1": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.24.1.tgz#67448446b67ab6c091360ce3717e7d3a59e202fd" + integrity sha512-OxBdcnF04bpdQdR3i4giHZNZQn7cm8RQKcSwA17wAAqEELo1ZOwp5FFgeptWUQXFyT9kwHo10aqqauYkRZPCAg== dependencies: - "@babel/helper-plugin-utils" "^7.22.5" + "@babel/helper-plugin-utils" "^7.24.0" "@babel/helper-skip-transparent-expression-wrappers" "^7.22.5" -"@babel/plugin-transform-function-name@^7.23.3": - version "7.23.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.23.3.tgz#8f424fcd862bf84cb9a1a6b42bc2f47ed630f8dc" - integrity sha512-I1QXp1LxIvt8yLaib49dRW5Okt7Q4oaxao6tFVKS/anCdEOMtYwWVKoiOA1p34GOWIZjUK0E+zCp7+l1pfQyiw== +"@babel/plugin-transform-function-name@^7.24.1": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.24.1.tgz#8cba6f7730626cc4dfe4ca2fa516215a0592b361" + integrity sha512-BXmDZpPlh7jwicKArQASrj8n22/w6iymRnvHYYd2zO30DbE277JO20/7yXJT3QxDPtiQiOxQBbZH4TpivNXIxA== dependencies: - "@babel/helper-compilation-targets" "^7.22.15" + "@babel/helper-compilation-targets" "^7.23.6" "@babel/helper-function-name" "^7.23.0" - "@babel/helper-plugin-utils" "^7.22.5" + "@babel/helper-plugin-utils" "^7.24.0" -"@babel/plugin-transform-json-strings@^7.23.4": - version "7.23.4" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.23.4.tgz#a871d9b6bd171976efad2e43e694c961ffa3714d" - integrity sha512-81nTOqM1dMwZ/aRXQ59zVubN9wHGqk6UtqRK+/q+ciXmRy8fSolhGVvG09HHRGo4l6fr/c4ZhXUQH0uFW7PZbg== +"@babel/plugin-transform-json-strings@^7.24.1": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.24.1.tgz#08e6369b62ab3e8a7b61089151b161180c8299f7" + integrity sha512-U7RMFmRvoasscrIFy5xA4gIp8iWnWubnKkKuUGJjsuOH7GfbMkB+XZzeslx2kLdEGdOJDamEmCqOks6e8nv8DQ== dependencies: - "@babel/helper-plugin-utils" "^7.22.5" + "@babel/helper-plugin-utils" "^7.24.0" "@babel/plugin-syntax-json-strings" "^7.8.3" -"@babel/plugin-transform-literals@^7.23.3": - version "7.23.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-literals/-/plugin-transform-literals-7.23.3.tgz#8214665f00506ead73de157eba233e7381f3beb4" - integrity sha512-wZ0PIXRxnwZvl9AYpqNUxpZ5BiTGrYt7kueGQ+N5FiQ7RCOD4cm8iShd6S6ggfVIWaJf2EMk8eRzAh52RfP4rQ== +"@babel/plugin-transform-literals@^7.24.1": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-literals/-/plugin-transform-literals-7.24.1.tgz#0a1982297af83e6b3c94972686067df588c5c096" + integrity sha512-zn9pwz8U7nCqOYIiBaOxoQOtYmMODXTJnkxG4AtX8fPmnCRYWBOHD0qcpwS9e2VDSp1zNJYpdnFMIKb8jmwu6g== dependencies: - "@babel/helper-plugin-utils" "^7.22.5" + "@babel/helper-plugin-utils" "^7.24.0" -"@babel/plugin-transform-logical-assignment-operators@^7.23.4": - version "7.23.4" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.23.4.tgz#e599f82c51d55fac725f62ce55d3a0886279ecb5" - integrity sha512-Mc/ALf1rmZTP4JKKEhUwiORU+vcfarFVLfcFiolKUo6sewoxSEgl36ak5t+4WamRsNr6nzjZXQjM35WsU+9vbg== +"@babel/plugin-transform-logical-assignment-operators@^7.24.1": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.24.1.tgz#719d8aded1aa94b8fb34e3a785ae8518e24cfa40" + integrity sha512-OhN6J4Bpz+hIBqItTeWJujDOfNP+unqv/NJgyhlpSqgBTPm37KkMmZV6SYcOj+pnDbdcl1qRGV/ZiIjX9Iy34w== dependencies: - "@babel/helper-plugin-utils" "^7.22.5" + "@babel/helper-plugin-utils" "^7.24.0" "@babel/plugin-syntax-logical-assignment-operators" "^7.10.4" -"@babel/plugin-transform-member-expression-literals@^7.23.3": - version "7.23.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.23.3.tgz#e37b3f0502289f477ac0e776b05a833d853cabcc" - integrity sha512-sC3LdDBDi5x96LA+Ytekz2ZPk8i/Ck+DEuDbRAll5rknJ5XRTSaPKEYwomLcs1AA8wg9b3KjIQRsnApj+q51Ag== +"@babel/plugin-transform-member-expression-literals@^7.24.1": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.24.1.tgz#896d23601c92f437af8b01371ad34beb75df4489" + integrity sha512-4ojai0KysTWXzHseJKa1XPNXKRbuUrhkOPY4rEGeR+7ChlJVKxFa3H3Bz+7tWaGKgJAXUWKOGmltN+u9B3+CVg== dependencies: - "@babel/helper-plugin-utils" "^7.22.5" + "@babel/helper-plugin-utils" "^7.24.0" -"@babel/plugin-transform-modules-amd@^7.23.3": - version "7.23.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.23.3.tgz#e19b55436a1416829df0a1afc495deedfae17f7d" - integrity sha512-vJYQGxeKM4t8hYCKVBlZX/gtIY2I7mRGFNcm85sgXGMTBcoV3QdVtdpbcWEbzbfUIUZKwvgFT82mRvaQIebZzw== +"@babel/plugin-transform-modules-amd@^7.24.1": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.24.1.tgz#b6d829ed15258536977e9c7cc6437814871ffa39" + integrity sha512-lAxNHi4HVtjnHd5Rxg3D5t99Xm6H7b04hUS7EHIXcUl2EV4yl1gWdqZrNzXnSrHveL9qMdbODlLF55mvgjAfaQ== dependencies: "@babel/helper-module-transforms" "^7.23.3" - "@babel/helper-plugin-utils" "^7.22.5" + "@babel/helper-plugin-utils" "^7.24.0" -"@babel/plugin-transform-modules-commonjs@^7.23.0", "@babel/plugin-transform-modules-commonjs@^7.23.3": - version "7.23.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.23.3.tgz#661ae831b9577e52be57dd8356b734f9700b53b4" - integrity sha512-aVS0F65LKsdNOtcz6FRCpE4OgsP2OFnW46qNxNIX9h3wuzaNcSQsJysuMwqSibC98HPrf2vCgtxKNwS0DAlgcA== +"@babel/plugin-transform-modules-commonjs@^7.23.0", "@babel/plugin-transform-modules-commonjs@^7.24.1": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.24.1.tgz#e71ba1d0d69e049a22bf90b3867e263823d3f1b9" + integrity sha512-szog8fFTUxBfw0b98gEWPaEqF42ZUD/T3bkynW/wtgx2p/XCP55WEsb+VosKceRSd6njipdZvNogqdtI4Q0chw== dependencies: "@babel/helper-module-transforms" "^7.23.3" - "@babel/helper-plugin-utils" "^7.22.5" + "@babel/helper-plugin-utils" "^7.24.0" "@babel/helper-simple-access" "^7.22.5" -"@babel/plugin-transform-modules-systemjs@^7.23.9": - version "7.23.9" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.23.9.tgz#105d3ed46e4a21d257f83a2f9e2ee4203ceda6be" - integrity sha512-KDlPRM6sLo4o1FkiSlXoAa8edLXFsKKIda779fbLrvmeuc3itnjCtaO6RrtoaANsIJANj+Vk1zqbZIMhkCAHVw== +"@babel/plugin-transform-modules-systemjs@^7.24.1": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.24.1.tgz#2b9625a3d4e445babac9788daec39094e6b11e3e" + integrity sha512-mqQ3Zh9vFO1Tpmlt8QPnbwGHzNz3lpNEMxQb1kAemn/erstyqw1r9KeOlOfo3y6xAnFEcOv2tSyrXfmMk+/YZA== dependencies: "@babel/helper-hoist-variables" "^7.22.5" "@babel/helper-module-transforms" "^7.23.3" - "@babel/helper-plugin-utils" "^7.22.5" + "@babel/helper-plugin-utils" "^7.24.0" "@babel/helper-validator-identifier" "^7.22.20" -"@babel/plugin-transform-modules-umd@^7.23.3": - version "7.23.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.23.3.tgz#5d4395fccd071dfefe6585a4411aa7d6b7d769e9" - integrity sha512-zHsy9iXX2nIsCBFPud3jKn1IRPWg3Ing1qOZgeKV39m1ZgIdpJqvlWVeiHBZC6ITRG0MfskhYe9cLgntfSFPIg== +"@babel/plugin-transform-modules-umd@^7.24.1": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.24.1.tgz#69220c66653a19cf2c0872b9c762b9a48b8bebef" + integrity sha512-tuA3lpPj+5ITfcCluy6nWonSL7RvaG0AOTeAuvXqEKS34lnLzXpDb0dcP6K8jD0zWZFNDVly90AGFJPnm4fOYg== dependencies: "@babel/helper-module-transforms" "^7.23.3" - "@babel/helper-plugin-utils" "^7.22.5" + "@babel/helper-plugin-utils" "^7.24.0" "@babel/plugin-transform-named-capturing-groups-regex@^7.22.5": version "7.22.5" @@ -873,103 +865,102 @@ "@babel/helper-create-regexp-features-plugin" "^7.22.5" "@babel/helper-plugin-utils" "^7.22.5" -"@babel/plugin-transform-new-target@^7.23.3": - version "7.23.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.23.3.tgz#5491bb78ed6ac87e990957cea367eab781c4d980" - integrity sha512-YJ3xKqtJMAT5/TIZnpAR3I+K+WaDowYbN3xyxI8zxx/Gsypwf9B9h0VB+1Nh6ACAAPRS5NSRje0uVv5i79HYGQ== +"@babel/plugin-transform-new-target@^7.24.1": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.24.1.tgz#29c59988fa3d0157de1c871a28cd83096363cc34" + integrity sha512-/rurytBM34hYy0HKZQyA0nHbQgQNFm4Q/BOc9Hflxi2X3twRof7NaE5W46j4kQitm7SvACVRXsa6N/tSZxvPug== dependencies: - "@babel/helper-plugin-utils" "^7.22.5" + "@babel/helper-plugin-utils" "^7.24.0" -"@babel/plugin-transform-nullish-coalescing-operator@^7.23.4": - version "7.23.4" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.23.4.tgz#45556aad123fc6e52189ea749e33ce090637346e" - integrity sha512-jHE9EVVqHKAQx+VePv5LLGHjmHSJR76vawFPTdlxR/LVJPfOEGxREQwQfjuZEOPTwG92X3LINSh3M40Rv4zpVA== +"@babel/plugin-transform-nullish-coalescing-operator@^7.24.1": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.24.1.tgz#0cd494bb97cb07d428bd651632cb9d4140513988" + integrity sha512-iQ+caew8wRrhCikO5DrUYx0mrmdhkaELgFa+7baMcVuhxIkN7oxt06CZ51D65ugIb1UWRQ8oQe+HXAVM6qHFjw== dependencies: - "@babel/helper-plugin-utils" "^7.22.5" + "@babel/helper-plugin-utils" "^7.24.0" "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.3" -"@babel/plugin-transform-numeric-separator@^7.23.4": - version "7.23.4" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.23.4.tgz#03d08e3691e405804ecdd19dd278a40cca531f29" - integrity sha512-mps6auzgwjRrwKEZA05cOwuDc9FAzoyFS4ZsG/8F43bTLf/TgkJg7QXOrPO1JO599iA3qgK9MXdMGOEC8O1h6Q== +"@babel/plugin-transform-numeric-separator@^7.24.1": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.24.1.tgz#5bc019ce5b3435c1cadf37215e55e433d674d4e8" + integrity sha512-7GAsGlK4cNL2OExJH1DzmDeKnRv/LXq0eLUSvudrehVA5Rgg4bIrqEUW29FbKMBRT0ztSqisv7kjP+XIC4ZMNw== dependencies: - "@babel/helper-plugin-utils" "^7.22.5" + "@babel/helper-plugin-utils" "^7.24.0" "@babel/plugin-syntax-numeric-separator" "^7.10.4" -"@babel/plugin-transform-object-rest-spread@^7.23.4": - version "7.23.4" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.23.4.tgz#2b9c2d26bf62710460bdc0d1730d4f1048361b83" - integrity sha512-9x9K1YyeQVw0iOXJlIzwm8ltobIIv7j2iLyP2jIhEbqPRQ7ScNgwQufU2I0Gq11VjyG4gI4yMXt2VFags+1N3g== +"@babel/plugin-transform-object-rest-spread@^7.24.1": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.24.1.tgz#5a3ce73caf0e7871a02e1c31e8b473093af241ff" + integrity sha512-XjD5f0YqOtebto4HGISLNfiNMTTs6tbkFf2TOqJlYKYmbo+mN9Dnpl4SRoofiziuOWMIyq3sZEUqLo3hLITFEA== dependencies: - "@babel/compat-data" "^7.23.3" - "@babel/helper-compilation-targets" "^7.22.15" - "@babel/helper-plugin-utils" "^7.22.5" + "@babel/helper-compilation-targets" "^7.23.6" + "@babel/helper-plugin-utils" "^7.24.0" "@babel/plugin-syntax-object-rest-spread" "^7.8.3" - "@babel/plugin-transform-parameters" "^7.23.3" + "@babel/plugin-transform-parameters" "^7.24.1" -"@babel/plugin-transform-object-super@^7.23.3": - version "7.23.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.23.3.tgz#81fdb636dcb306dd2e4e8fd80db5b2362ed2ebcd" - integrity sha512-BwQ8q0x2JG+3lxCVFohg+KbQM7plfpBwThdW9A6TMtWwLsbDA01Ek2Zb/AgDN39BiZsExm4qrXxjk+P1/fzGrA== +"@babel/plugin-transform-object-super@^7.24.1": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.24.1.tgz#e71d6ab13483cca89ed95a474f542bbfc20a0520" + integrity sha512-oKJqR3TeI5hSLRxudMjFQ9re9fBVUU0GICqM3J1mi8MqlhVr6hC/ZN4ttAyMuQR6EZZIY6h/exe5swqGNNIkWQ== dependencies: - "@babel/helper-plugin-utils" "^7.22.5" - "@babel/helper-replace-supers" "^7.22.20" + "@babel/helper-plugin-utils" "^7.24.0" + "@babel/helper-replace-supers" "^7.24.1" -"@babel/plugin-transform-optional-catch-binding@^7.23.4": - version "7.23.4" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.23.4.tgz#318066de6dacce7d92fa244ae475aa8d91778017" - integrity sha512-XIq8t0rJPHf6Wvmbn9nFxU6ao4c7WhghTR5WyV8SrJfUFzyxhCm4nhC+iAp3HFhbAKLfYpgzhJ6t4XCtVwqO5A== +"@babel/plugin-transform-optional-catch-binding@^7.24.1": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.24.1.tgz#92a3d0efe847ba722f1a4508669b23134669e2da" + integrity sha512-oBTH7oURV4Y+3EUrf6cWn1OHio3qG/PVwO5J03iSJmBg6m2EhKjkAu/xuaXaYwWW9miYtvbWv4LNf0AmR43LUA== dependencies: - "@babel/helper-plugin-utils" "^7.22.5" + "@babel/helper-plugin-utils" "^7.24.0" "@babel/plugin-syntax-optional-catch-binding" "^7.8.3" -"@babel/plugin-transform-optional-chaining@^7.23.3", "@babel/plugin-transform-optional-chaining@^7.23.4": - version "7.23.4" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.23.4.tgz#6acf61203bdfc4de9d4e52e64490aeb3e52bd017" - integrity sha512-ZU8y5zWOfjM5vZ+asjgAPwDaBjJzgufjES89Rs4Lpq63O300R/kOz30WCLo6BxxX6QVEilwSlpClnG5cZaikTA== +"@babel/plugin-transform-optional-chaining@^7.24.1": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.24.1.tgz#26e588acbedce1ab3519ac40cc748e380c5291e6" + integrity sha512-n03wmDt+987qXwAgcBlnUUivrZBPZ8z1plL0YvgQalLm+ZE5BMhGm94jhxXtA1wzv1Cu2aaOv1BM9vbVttrzSg== dependencies: - "@babel/helper-plugin-utils" "^7.22.5" + "@babel/helper-plugin-utils" "^7.24.0" "@babel/helper-skip-transparent-expression-wrappers" "^7.22.5" "@babel/plugin-syntax-optional-chaining" "^7.8.3" -"@babel/plugin-transform-parameters@^7.23.3": - version "7.23.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.23.3.tgz#83ef5d1baf4b1072fa6e54b2b0999a7b2527e2af" - integrity sha512-09lMt6UsUb3/34BbECKVbVwrT9bO6lILWln237z7sLaWnMsTi7Yc9fhX5DLpkJzAGfaReXI22wP41SZmnAA3Vw== +"@babel/plugin-transform-parameters@^7.24.1": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.24.1.tgz#983c15d114da190506c75b616ceb0f817afcc510" + integrity sha512-8Jl6V24g+Uw5OGPeWNKrKqXPDw2YDjLc53ojwfMcKwlEoETKU9rU0mHUtcg9JntWI/QYzGAXNWEcVHZ+fR+XXg== dependencies: - "@babel/helper-plugin-utils" "^7.22.5" + "@babel/helper-plugin-utils" "^7.24.0" -"@babel/plugin-transform-private-methods@^7.23.3": - version "7.23.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.23.3.tgz#b2d7a3c97e278bfe59137a978d53b2c2e038c0e4" - integrity sha512-UzqRcRtWsDMTLrRWFvUBDwmw06tCQH9Rl1uAjfh6ijMSmGYQ+fpdB+cnqRC8EMh5tuuxSv0/TejGL+7vyj+50g== +"@babel/plugin-transform-private-methods@^7.24.1": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.24.1.tgz#a0faa1ae87eff077e1e47a5ec81c3aef383dc15a" + integrity sha512-tGvisebwBO5em4PaYNqt4fkw56K2VALsAbAakY0FjTYqJp7gfdrgr7YX76Or8/cpik0W6+tj3rZ0uHU9Oil4tw== dependencies: - "@babel/helper-create-class-features-plugin" "^7.22.15" - "@babel/helper-plugin-utils" "^7.22.5" + "@babel/helper-create-class-features-plugin" "^7.24.1" + "@babel/helper-plugin-utils" "^7.24.0" -"@babel/plugin-transform-private-property-in-object@^7.23.4": - version "7.23.4" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.23.4.tgz#3ec711d05d6608fd173d9b8de39872d8dbf68bf5" - integrity sha512-9G3K1YqTq3F4Vt88Djx1UZ79PDyj+yKRnUy7cZGSMe+a7jkwD259uKKuUzQlPkGam7R+8RJwh5z4xO27fA1o2A== +"@babel/plugin-transform-private-property-in-object@^7.24.1": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.24.1.tgz#756443d400274f8fb7896742962cc1b9f25c1f6a" + integrity sha512-pTHxDVa0BpUbvAgX3Gat+7cSciXqUcY9j2VZKTbSB6+VQGpNgNO9ailxTGHSXlqOnX1Hcx1Enme2+yv7VqP9bg== dependencies: "@babel/helper-annotate-as-pure" "^7.22.5" - "@babel/helper-create-class-features-plugin" "^7.22.15" - "@babel/helper-plugin-utils" "^7.22.5" + "@babel/helper-create-class-features-plugin" "^7.24.1" + "@babel/helper-plugin-utils" "^7.24.0" "@babel/plugin-syntax-private-property-in-object" "^7.14.5" -"@babel/plugin-transform-property-literals@^7.23.3": - version "7.23.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.23.3.tgz#54518f14ac4755d22b92162e4a852d308a560875" - integrity sha512-jR3Jn3y7cZp4oEWPFAlRsSWjxKe4PZILGBSd4nis1TsC5qeSpb+nrtihJuDhNI7QHiVbUaiXa0X2RZY3/TI6Nw== +"@babel/plugin-transform-property-literals@^7.24.1": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.24.1.tgz#d6a9aeab96f03749f4eebeb0b6ea8e90ec958825" + integrity sha512-LetvD7CrHmEx0G442gOomRr66d7q8HzzGGr4PMHGr+5YIm6++Yke+jxj246rpvsbyhJwCLxcTn6zW1P1BSenqA== dependencies: - "@babel/helper-plugin-utils" "^7.22.5" + "@babel/helper-plugin-utils" "^7.24.0" -"@babel/plugin-transform-react-display-name@^7.16.0", "@babel/plugin-transform-react-display-name@^7.23.3": - version "7.23.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.23.3.tgz#70529f034dd1e561045ad3c8152a267f0d7b6200" - integrity sha512-GnvhtVfA2OAtzdX58FJxU19rhoGeQzyVndw3GgtdECQvQFXPEZIOVULHVZGAYmOgmqjXpVpfocAbSjh99V/Fqw== +"@babel/plugin-transform-react-display-name@^7.16.0", "@babel/plugin-transform-react-display-name@^7.24.1": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.24.1.tgz#554e3e1a25d181f040cf698b93fd289a03bfdcdb" + integrity sha512-mvoQg2f9p2qlpDQRBC7M3c3XTr0k7cp/0+kFKKO/7Gtu0LSw16eKB+Fabe2bDT/UpsyasTBBkAnbdsLrkD5XMw== dependencies: - "@babel/helper-plugin-utils" "^7.22.5" + "@babel/helper-plugin-utils" "^7.24.0" "@babel/plugin-transform-react-jsx-development@^7.22.5": version "7.22.5" @@ -979,20 +970,20 @@ "@babel/plugin-transform-react-jsx" "^7.22.5" "@babel/plugin-transform-react-jsx-self@^7.22.5", "@babel/plugin-transform-react-jsx-self@^7.23.3": - version "7.23.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.23.3.tgz#ed3e7dadde046cce761a8e3cf003a13d1a7972d9" - integrity sha512-qXRvbeKDSfwnlJnanVRp0SfuWE5DQhwQr5xtLBzp56Wabyo+4CMosF6Kfp+eOD/4FYpql64XVJ2W0pVLlJZxOQ== + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.24.1.tgz#a21d866d8167e752c6a7c4555dba8afcdfce6268" + integrity sha512-kDJgnPujTmAZ/9q2CN4m2/lRsUUPDvsG3+tSHWUJIzMGTt5U/b/fwWd3RO3n+5mjLrsBrVa5eKFRVSQbi3dF1w== dependencies: - "@babel/helper-plugin-utils" "^7.22.5" + "@babel/helper-plugin-utils" "^7.24.0" "@babel/plugin-transform-react-jsx-source@^7.22.5", "@babel/plugin-transform-react-jsx-source@^7.23.3": - version "7.23.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.23.3.tgz#03527006bdc8775247a78643c51d4e715fe39a3e" - integrity sha512-91RS0MDnAWDNvGC6Wio5XYkyWI39FMFO+JK9+4AlgaTH+yWwVTsw7/sn6LK0lH7c5F+TFkpv/3LfCJ1Ydwof/g== + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.24.1.tgz#a2dedb12b09532846721b5df99e52ef8dc3351d0" + integrity sha512-1v202n7aUq4uXAieRTKcwPzNyphlCuqHHDcdSNc+vdhoTEZcFMh+L5yZuCmGaIO7bs1nJUNfHB89TZyoL48xNA== dependencies: - "@babel/helper-plugin-utils" "^7.22.5" + "@babel/helper-plugin-utils" "^7.24.0" -"@babel/plugin-transform-react-jsx@^7.22.15", "@babel/plugin-transform-react-jsx@^7.22.5": +"@babel/plugin-transform-react-jsx@^7.22.5", "@babel/plugin-transform-react-jsx@^7.23.4": version "7.23.4" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.23.4.tgz#393f99185110cea87184ea47bcb4a7b0c2e39312" integrity sha512-5xOpoPguCZCRbo/JeHlloSkTA8Bld1J/E1/kLfD1nsuiW1m8tduTA1ERCgIZokDflX/IBzKcqR3l7VlRgiIfHA== @@ -1003,138 +994,138 @@ "@babel/plugin-syntax-jsx" "^7.23.3" "@babel/types" "^7.23.4" -"@babel/plugin-transform-react-pure-annotations@^7.23.3": - version "7.23.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-pure-annotations/-/plugin-transform-react-pure-annotations-7.23.3.tgz#fabedbdb8ee40edf5da96f3ecfc6958e3783b93c" - integrity sha512-qMFdSS+TUhB7Q/3HVPnEdYJDQIk57jkntAwSuz9xfSE4n+3I+vHYCli3HoHawN1Z3RfCz/y1zXA/JXjG6cVImQ== +"@babel/plugin-transform-react-pure-annotations@^7.24.1": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-pure-annotations/-/plugin-transform-react-pure-annotations-7.24.1.tgz#c86bce22a53956331210d268e49a0ff06e392470" + integrity sha512-+pWEAaDJvSm9aFvJNpLiM2+ktl2Sn2U5DdyiWdZBxmLc6+xGt88dvFqsHiAiDS+8WqUwbDfkKz9jRxK3M0k+kA== dependencies: "@babel/helper-annotate-as-pure" "^7.22.5" - "@babel/helper-plugin-utils" "^7.22.5" + "@babel/helper-plugin-utils" "^7.24.0" -"@babel/plugin-transform-regenerator@^7.23.3": - version "7.23.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.23.3.tgz#141afd4a2057298602069fce7f2dc5173e6c561c" - integrity sha512-KP+75h0KghBMcVpuKisx3XTu9Ncut8Q8TuvGO4IhY+9D5DFEckQefOuIsB/gQ2tG71lCke4NMrtIPS8pOj18BQ== +"@babel/plugin-transform-regenerator@^7.24.1": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.24.1.tgz#625b7545bae52363bdc1fbbdc7252b5046409c8c" + integrity sha512-sJwZBCzIBE4t+5Q4IGLaaun5ExVMRY0lYwos/jNecjMrVCygCdph3IKv0tkP5Fc87e/1+bebAmEAGBfnRD+cnw== dependencies: - "@babel/helper-plugin-utils" "^7.22.5" + "@babel/helper-plugin-utils" "^7.24.0" regenerator-transform "^0.15.2" -"@babel/plugin-transform-reserved-words@^7.23.3": - version "7.23.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.23.3.tgz#4130dcee12bd3dd5705c587947eb715da12efac8" - integrity sha512-QnNTazY54YqgGxwIexMZva9gqbPa15t/x9VS+0fsEFWplwVpXYZivtgl43Z1vMpc1bdPP2PP8siFeVcnFvA3Cg== +"@babel/plugin-transform-reserved-words@^7.24.1": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.24.1.tgz#8de729f5ecbaaf5cf83b67de13bad38a21be57c1" + integrity sha512-JAclqStUfIwKN15HrsQADFgeZt+wexNQ0uLhuqvqAUFoqPMjEcFCYZBhq0LUdz6dZK/mD+rErhW71fbx8RYElg== dependencies: - "@babel/helper-plugin-utils" "^7.22.5" + "@babel/helper-plugin-utils" "^7.24.0" "@babel/plugin-transform-runtime@^7.16.4": - version "7.23.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.23.7.tgz#52bbd20054855beb9deae3bee9ceb05289c343e6" - integrity sha512-fa0hnfmiXc9fq/weK34MUV0drz2pOL/vfKWvN7Qw127hiUPabFCUMgAbYWcchRzMJit4o5ARsK/s+5h0249pLw== - dependencies: - "@babel/helper-module-imports" "^7.22.15" - "@babel/helper-plugin-utils" "^7.22.5" - babel-plugin-polyfill-corejs2 "^0.4.7" - babel-plugin-polyfill-corejs3 "^0.8.7" - babel-plugin-polyfill-regenerator "^0.5.4" + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.24.1.tgz#3678311f7193ef7cf62c6f34c6f757d0301bf314" + integrity sha512-yHLX14/T+tO0gjgJroDb8JYjOcQuzVC+Brt4CjHAxq/Ghw4xBVG+N02d1rMEcyUnKUQBL4Yy2gA9R72GK961jQ== + dependencies: + "@babel/helper-module-imports" "^7.24.1" + "@babel/helper-plugin-utils" "^7.24.0" + babel-plugin-polyfill-corejs2 "^0.4.10" + babel-plugin-polyfill-corejs3 "^0.10.1" + babel-plugin-polyfill-regenerator "^0.6.1" semver "^6.3.1" -"@babel/plugin-transform-shorthand-properties@^7.23.3": - version "7.23.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.23.3.tgz#97d82a39b0e0c24f8a981568a8ed851745f59210" - integrity sha512-ED2fgqZLmexWiN+YNFX26fx4gh5qHDhn1O2gvEhreLW2iI63Sqm4llRLCXALKrCnbN4Jy0VcMQZl/SAzqug/jg== +"@babel/plugin-transform-shorthand-properties@^7.24.1": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.24.1.tgz#ba9a09144cf55d35ec6b93a32253becad8ee5b55" + integrity sha512-LyjVB1nsJ6gTTUKRjRWx9C1s9hE7dLfP/knKdrfeH9UPtAGjYGgxIbFfx7xyLIEWs7Xe1Gnf8EWiUqfjLhInZA== dependencies: - "@babel/helper-plugin-utils" "^7.22.5" + "@babel/helper-plugin-utils" "^7.24.0" -"@babel/plugin-transform-spread@^7.23.3": - version "7.23.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-spread/-/plugin-transform-spread-7.23.3.tgz#41d17aacb12bde55168403c6f2d6bdca563d362c" - integrity sha512-VvfVYlrlBVu+77xVTOAoxQ6mZbnIq5FM0aGBSFEcIh03qHf+zNqA4DC/3XMUozTg7bZV3e3mZQ0i13VB6v5yUg== +"@babel/plugin-transform-spread@^7.24.1": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-spread/-/plugin-transform-spread-7.24.1.tgz#a1acf9152cbf690e4da0ba10790b3ac7d2b2b391" + integrity sha512-KjmcIM+fxgY+KxPVbjelJC6hrH1CgtPmTvdXAfn3/a9CnWGSTY7nH4zm5+cjmWJybdcPSsD0++QssDsjcpe47g== dependencies: - "@babel/helper-plugin-utils" "^7.22.5" + "@babel/helper-plugin-utils" "^7.24.0" "@babel/helper-skip-transparent-expression-wrappers" "^7.22.5" -"@babel/plugin-transform-sticky-regex@^7.23.3": - version "7.23.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.23.3.tgz#dec45588ab4a723cb579c609b294a3d1bd22ff04" - integrity sha512-HZOyN9g+rtvnOU3Yh7kSxXrKbzgrm5X4GncPY1QOquu7epga5MxKHVpYu2hvQnry/H+JjckSYRb93iNfsioAGg== +"@babel/plugin-transform-sticky-regex@^7.24.1": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.24.1.tgz#f03e672912c6e203ed8d6e0271d9c2113dc031b9" + integrity sha512-9v0f1bRXgPVcPrngOQvLXeGNNVLc8UjMVfebo9ka0WF3/7+aVUHmaJVT3sa0XCzEFioPfPHZiOcYG9qOsH63cw== dependencies: - "@babel/helper-plugin-utils" "^7.22.5" + "@babel/helper-plugin-utils" "^7.24.0" -"@babel/plugin-transform-template-literals@^7.23.3": - version "7.23.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.23.3.tgz#5f0f028eb14e50b5d0f76be57f90045757539d07" - integrity sha512-Flok06AYNp7GV2oJPZZcP9vZdszev6vPBkHLwxwSpaIqx75wn6mUd3UFWsSsA0l8nXAKkyCmL/sR02m8RYGeHg== +"@babel/plugin-transform-template-literals@^7.24.1": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.24.1.tgz#15e2166873a30d8617e3e2ccadb86643d327aab7" + integrity sha512-WRkhROsNzriarqECASCNu/nojeXCDTE/F2HmRgOzi7NGvyfYGq1NEjKBK3ckLfRgGc6/lPAqP0vDOSw3YtG34g== dependencies: - "@babel/helper-plugin-utils" "^7.22.5" + "@babel/helper-plugin-utils" "^7.24.0" -"@babel/plugin-transform-typeof-symbol@^7.23.3": - version "7.23.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.23.3.tgz#9dfab97acc87495c0c449014eb9c547d8966bca4" - integrity sha512-4t15ViVnaFdrPC74be1gXBSMzXk3B4Us9lP7uLRQHTFpV5Dvt33pn+2MyyNxmN3VTTm3oTrZVMUmuw3oBnQ2oQ== +"@babel/plugin-transform-typeof-symbol@^7.24.1": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.24.1.tgz#6831f78647080dec044f7e9f68003d99424f94c7" + integrity sha512-CBfU4l/A+KruSUoW+vTQthwcAdwuqbpRNB8HQKlZABwHRhsdHZ9fezp4Sn18PeAlYxTNiLMlx4xUBV3AWfg1BA== dependencies: - "@babel/helper-plugin-utils" "^7.22.5" + "@babel/helper-plugin-utils" "^7.24.0" -"@babel/plugin-transform-typescript@^7.23.3": - version "7.23.6" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.23.6.tgz#aa36a94e5da8d94339ae3a4e22d40ed287feb34c" - integrity sha512-6cBG5mBvUu4VUD04OHKnYzbuHNP8huDsD3EDqqpIpsswTDoqHCjLoHb6+QgsV1WsT2nipRqCPgxD3LXnEO7XfA== +"@babel/plugin-transform-typescript@^7.24.1": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.24.1.tgz#5c05e28bb76c7dfe7d6c5bed9951324fd2d3ab07" + integrity sha512-liYSESjX2fZ7JyBFkYG78nfvHlMKE6IpNdTVnxmlYUR+j5ZLsitFbaAE+eJSK2zPPkNWNw4mXL51rQ8WrvdK0w== dependencies: "@babel/helper-annotate-as-pure" "^7.22.5" - "@babel/helper-create-class-features-plugin" "^7.23.6" - "@babel/helper-plugin-utils" "^7.22.5" - "@babel/plugin-syntax-typescript" "^7.23.3" + "@babel/helper-create-class-features-plugin" "^7.24.1" + "@babel/helper-plugin-utils" "^7.24.0" + "@babel/plugin-syntax-typescript" "^7.24.1" -"@babel/plugin-transform-unicode-escapes@^7.23.3": - version "7.23.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.23.3.tgz#1f66d16cab01fab98d784867d24f70c1ca65b925" - integrity sha512-OMCUx/bU6ChE3r4+ZdylEqAjaQgHAgipgW8nsCfu5pGqDcFytVd91AwRvUJSBZDz0exPGgnjoqhgRYLRjFZc9Q== +"@babel/plugin-transform-unicode-escapes@^7.24.1": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.24.1.tgz#fb3fa16676549ac7c7449db9b342614985c2a3a4" + integrity sha512-RlkVIcWT4TLI96zM660S877E7beKlQw7Ig+wqkKBiWfj0zH5Q4h50q6er4wzZKRNSYpfo6ILJ+hrJAGSX2qcNw== dependencies: - "@babel/helper-plugin-utils" "^7.22.5" + "@babel/helper-plugin-utils" "^7.24.0" -"@babel/plugin-transform-unicode-property-regex@^7.23.3": - version "7.23.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.23.3.tgz#19e234129e5ffa7205010feec0d94c251083d7ad" - integrity sha512-KcLIm+pDZkWZQAFJ9pdfmh89EwVfmNovFBcXko8szpBeF8z68kWIPeKlmSOkT9BXJxs2C0uk+5LxoxIv62MROA== +"@babel/plugin-transform-unicode-property-regex@^7.24.1": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.24.1.tgz#56704fd4d99da81e5e9f0c0c93cabd91dbc4889e" + integrity sha512-Ss4VvlfYV5huWApFsF8/Sq0oXnGO+jB+rijFEFugTd3cwSObUSnUi88djgR5528Csl0uKlrI331kRqe56Ov2Ng== dependencies: "@babel/helper-create-regexp-features-plugin" "^7.22.15" - "@babel/helper-plugin-utils" "^7.22.5" + "@babel/helper-plugin-utils" "^7.24.0" -"@babel/plugin-transform-unicode-regex@^7.23.3": - version "7.23.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.23.3.tgz#26897708d8f42654ca4ce1b73e96140fbad879dc" - integrity sha512-wMHpNA4x2cIA32b/ci3AfwNgheiva2W0WUKWTK7vBHBhDKfPsc5cFGNWm69WBqpwd86u1qwZ9PWevKqm1A3yAw== +"@babel/plugin-transform-unicode-regex@^7.24.1": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.24.1.tgz#57c3c191d68f998ac46b708380c1ce4d13536385" + integrity sha512-2A/94wgZgxfTsiLaQ2E36XAOdcZmGAaEEgVmxQWwZXWkGhvoHbaqXcKnU8zny4ycpu3vNqg0L/PcCiYtHtA13g== dependencies: "@babel/helper-create-regexp-features-plugin" "^7.22.15" - "@babel/helper-plugin-utils" "^7.22.5" + "@babel/helper-plugin-utils" "^7.24.0" -"@babel/plugin-transform-unicode-sets-regex@^7.23.3": - version "7.23.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.23.3.tgz#4fb6f0a719c2c5859d11f6b55a050cc987f3799e" - integrity sha512-W7lliA/v9bNR83Qc3q1ip9CQMZ09CcHDbHfbLRDNuAhn1Mvkr1ZNF7hPmztMQvtTGVLJ9m8IZqWsTkXOml8dbw== +"@babel/plugin-transform-unicode-sets-regex@^7.24.1": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.24.1.tgz#c1ea175b02afcffc9cf57a9c4658326625165b7f" + integrity sha512-fqj4WuzzS+ukpgerpAoOnMfQXwUHFxXUZUE84oL2Kao2N8uSlvcpnAidKASgsNgzZHBsHWvcm8s9FPWUhAb8fA== dependencies: "@babel/helper-create-regexp-features-plugin" "^7.22.15" - "@babel/helper-plugin-utils" "^7.22.5" + "@babel/helper-plugin-utils" "^7.24.0" "@babel/preset-env@^7.16.4", "@babel/preset-env@^7.23.9": - version "7.23.9" - resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.23.9.tgz#beace3b7994560ed6bf78e4ae2073dff45387669" - integrity sha512-3kBGTNBBk9DQiPoXYS0g0BYlwTQYUTifqgKTjxUwEUkduRT2QOa0FPGBJ+NROQhGyYO5BuTJwGvBnqKDykac6A== + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.24.1.tgz#e63a3f95d9922c07f4a53649b5c2f53f611f2e6c" + integrity sha512-CwCMz1Z28UHLI2iE+cbnWT2epPMV9bzzoBGM6A3mOS22VQd/1TPoWItV7S7iL9TkPmPEf5L/QzurmztyyDN9FA== dependencies: - "@babel/compat-data" "^7.23.5" + "@babel/compat-data" "^7.24.1" "@babel/helper-compilation-targets" "^7.23.6" - "@babel/helper-plugin-utils" "^7.22.5" + "@babel/helper-plugin-utils" "^7.24.0" "@babel/helper-validator-option" "^7.23.5" - "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression" "^7.23.3" - "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining" "^7.23.3" - "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly" "^7.23.7" + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression" "^7.24.1" + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining" "^7.24.1" + "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly" "^7.24.1" "@babel/plugin-proposal-private-property-in-object" "7.21.0-placeholder-for-preset-env.2" "@babel/plugin-syntax-async-generators" "^7.8.4" "@babel/plugin-syntax-class-properties" "^7.12.13" "@babel/plugin-syntax-class-static-block" "^7.14.5" "@babel/plugin-syntax-dynamic-import" "^7.8.3" "@babel/plugin-syntax-export-namespace-from" "^7.8.3" - "@babel/plugin-syntax-import-assertions" "^7.23.3" - "@babel/plugin-syntax-import-attributes" "^7.23.3" + "@babel/plugin-syntax-import-assertions" "^7.24.1" + "@babel/plugin-syntax-import-attributes" "^7.24.1" "@babel/plugin-syntax-import-meta" "^7.10.4" "@babel/plugin-syntax-json-strings" "^7.8.3" "@babel/plugin-syntax-logical-assignment-operators" "^7.10.4" @@ -1146,58 +1137,58 @@ "@babel/plugin-syntax-private-property-in-object" "^7.14.5" "@babel/plugin-syntax-top-level-await" "^7.14.5" "@babel/plugin-syntax-unicode-sets-regex" "^7.18.6" - "@babel/plugin-transform-arrow-functions" "^7.23.3" - "@babel/plugin-transform-async-generator-functions" "^7.23.9" - "@babel/plugin-transform-async-to-generator" "^7.23.3" - "@babel/plugin-transform-block-scoped-functions" "^7.23.3" - "@babel/plugin-transform-block-scoping" "^7.23.4" - "@babel/plugin-transform-class-properties" "^7.23.3" - "@babel/plugin-transform-class-static-block" "^7.23.4" - "@babel/plugin-transform-classes" "^7.23.8" - "@babel/plugin-transform-computed-properties" "^7.23.3" - "@babel/plugin-transform-destructuring" "^7.23.3" - "@babel/plugin-transform-dotall-regex" "^7.23.3" - "@babel/plugin-transform-duplicate-keys" "^7.23.3" - "@babel/plugin-transform-dynamic-import" "^7.23.4" - "@babel/plugin-transform-exponentiation-operator" "^7.23.3" - "@babel/plugin-transform-export-namespace-from" "^7.23.4" - "@babel/plugin-transform-for-of" "^7.23.6" - "@babel/plugin-transform-function-name" "^7.23.3" - "@babel/plugin-transform-json-strings" "^7.23.4" - "@babel/plugin-transform-literals" "^7.23.3" - "@babel/plugin-transform-logical-assignment-operators" "^7.23.4" - "@babel/plugin-transform-member-expression-literals" "^7.23.3" - "@babel/plugin-transform-modules-amd" "^7.23.3" - "@babel/plugin-transform-modules-commonjs" "^7.23.3" - "@babel/plugin-transform-modules-systemjs" "^7.23.9" - "@babel/plugin-transform-modules-umd" "^7.23.3" + "@babel/plugin-transform-arrow-functions" "^7.24.1" + "@babel/plugin-transform-async-generator-functions" "^7.24.1" + "@babel/plugin-transform-async-to-generator" "^7.24.1" + "@babel/plugin-transform-block-scoped-functions" "^7.24.1" + "@babel/plugin-transform-block-scoping" "^7.24.1" + "@babel/plugin-transform-class-properties" "^7.24.1" + "@babel/plugin-transform-class-static-block" "^7.24.1" + "@babel/plugin-transform-classes" "^7.24.1" + "@babel/plugin-transform-computed-properties" "^7.24.1" + "@babel/plugin-transform-destructuring" "^7.24.1" + "@babel/plugin-transform-dotall-regex" "^7.24.1" + "@babel/plugin-transform-duplicate-keys" "^7.24.1" + "@babel/plugin-transform-dynamic-import" "^7.24.1" + "@babel/plugin-transform-exponentiation-operator" "^7.24.1" + "@babel/plugin-transform-export-namespace-from" "^7.24.1" + "@babel/plugin-transform-for-of" "^7.24.1" + "@babel/plugin-transform-function-name" "^7.24.1" + "@babel/plugin-transform-json-strings" "^7.24.1" + "@babel/plugin-transform-literals" "^7.24.1" + "@babel/plugin-transform-logical-assignment-operators" "^7.24.1" + "@babel/plugin-transform-member-expression-literals" "^7.24.1" + "@babel/plugin-transform-modules-amd" "^7.24.1" + "@babel/plugin-transform-modules-commonjs" "^7.24.1" + "@babel/plugin-transform-modules-systemjs" "^7.24.1" + "@babel/plugin-transform-modules-umd" "^7.24.1" "@babel/plugin-transform-named-capturing-groups-regex" "^7.22.5" - "@babel/plugin-transform-new-target" "^7.23.3" - "@babel/plugin-transform-nullish-coalescing-operator" "^7.23.4" - "@babel/plugin-transform-numeric-separator" "^7.23.4" - "@babel/plugin-transform-object-rest-spread" "^7.23.4" - "@babel/plugin-transform-object-super" "^7.23.3" - "@babel/plugin-transform-optional-catch-binding" "^7.23.4" - "@babel/plugin-transform-optional-chaining" "^7.23.4" - "@babel/plugin-transform-parameters" "^7.23.3" - "@babel/plugin-transform-private-methods" "^7.23.3" - "@babel/plugin-transform-private-property-in-object" "^7.23.4" - "@babel/plugin-transform-property-literals" "^7.23.3" - "@babel/plugin-transform-regenerator" "^7.23.3" - "@babel/plugin-transform-reserved-words" "^7.23.3" - "@babel/plugin-transform-shorthand-properties" "^7.23.3" - "@babel/plugin-transform-spread" "^7.23.3" - "@babel/plugin-transform-sticky-regex" "^7.23.3" - "@babel/plugin-transform-template-literals" "^7.23.3" - "@babel/plugin-transform-typeof-symbol" "^7.23.3" - "@babel/plugin-transform-unicode-escapes" "^7.23.3" - "@babel/plugin-transform-unicode-property-regex" "^7.23.3" - "@babel/plugin-transform-unicode-regex" "^7.23.3" - "@babel/plugin-transform-unicode-sets-regex" "^7.23.3" + "@babel/plugin-transform-new-target" "^7.24.1" + "@babel/plugin-transform-nullish-coalescing-operator" "^7.24.1" + "@babel/plugin-transform-numeric-separator" "^7.24.1" + "@babel/plugin-transform-object-rest-spread" "^7.24.1" + "@babel/plugin-transform-object-super" "^7.24.1" + "@babel/plugin-transform-optional-catch-binding" "^7.24.1" + "@babel/plugin-transform-optional-chaining" "^7.24.1" + "@babel/plugin-transform-parameters" "^7.24.1" + "@babel/plugin-transform-private-methods" "^7.24.1" + "@babel/plugin-transform-private-property-in-object" "^7.24.1" + "@babel/plugin-transform-property-literals" "^7.24.1" + "@babel/plugin-transform-regenerator" "^7.24.1" + "@babel/plugin-transform-reserved-words" "^7.24.1" + "@babel/plugin-transform-shorthand-properties" "^7.24.1" + "@babel/plugin-transform-spread" "^7.24.1" + "@babel/plugin-transform-sticky-regex" "^7.24.1" + "@babel/plugin-transform-template-literals" "^7.24.1" + "@babel/plugin-transform-typeof-symbol" "^7.24.1" + "@babel/plugin-transform-unicode-escapes" "^7.24.1" + "@babel/plugin-transform-unicode-property-regex" "^7.24.1" + "@babel/plugin-transform-unicode-regex" "^7.24.1" + "@babel/plugin-transform-unicode-sets-regex" "^7.24.1" "@babel/preset-modules" "0.1.6-no-external-plugins" - babel-plugin-polyfill-corejs2 "^0.4.8" - babel-plugin-polyfill-corejs3 "^0.9.0" - babel-plugin-polyfill-regenerator "^0.5.5" + babel-plugin-polyfill-corejs2 "^0.4.10" + babel-plugin-polyfill-corejs3 "^0.10.1" + babel-plugin-polyfill-regenerator "^0.6.1" core-js-compat "^3.31.0" semver "^6.3.1" @@ -1211,27 +1202,27 @@ esutils "^2.0.2" "@babel/preset-react@^7.16.0", "@babel/preset-react@^7.18.6": - version "7.23.3" - resolved "https://registry.yarnpkg.com/@babel/preset-react/-/preset-react-7.23.3.tgz#f73ca07e7590f977db07eb54dbe46538cc015709" - integrity sha512-tbkHOS9axH6Ysf2OUEqoSZ6T3Fa2SrNH6WTWSPBboxKzdxNc9qOICeLXkNG0ZEwbQ1HY8liwOce4aN/Ceyuq6w== + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/preset-react/-/preset-react-7.24.1.tgz#2450c2ac5cc498ef6101a6ca5474de251e33aa95" + integrity sha512-eFa8up2/8cZXLIpkafhaADTXSnl7IsUFCYenRWrARBz0/qZwcT0RBXpys0LJU4+WfPoF2ZG6ew6s2V6izMCwRA== dependencies: - "@babel/helper-plugin-utils" "^7.22.5" - "@babel/helper-validator-option" "^7.22.15" - "@babel/plugin-transform-react-display-name" "^7.23.3" - "@babel/plugin-transform-react-jsx" "^7.22.15" + "@babel/helper-plugin-utils" "^7.24.0" + "@babel/helper-validator-option" "^7.23.5" + "@babel/plugin-transform-react-display-name" "^7.24.1" + "@babel/plugin-transform-react-jsx" "^7.23.4" "@babel/plugin-transform-react-jsx-development" "^7.22.5" - "@babel/plugin-transform-react-pure-annotations" "^7.23.3" + "@babel/plugin-transform-react-pure-annotations" "^7.24.1" "@babel/preset-typescript@^7.16.0", "@babel/preset-typescript@^7.18.6": - version "7.23.3" - resolved "https://registry.yarnpkg.com/@babel/preset-typescript/-/preset-typescript-7.23.3.tgz#14534b34ed5b6d435aa05f1ae1c5e7adcc01d913" - integrity sha512-17oIGVlqz6CchO9RFYn5U6ZpWRZIngayYCtrPRSgANSwC2V1Jb+iP74nVxzzXJte8b8BYxrL1yY96xfhTBrNNQ== + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/preset-typescript/-/preset-typescript-7.24.1.tgz#89bdf13a3149a17b3b2a2c9c62547f06db8845ec" + integrity sha512-1DBaMmRDpuYQBPWD8Pf/WEwCrtgRHxsZnP4mIy9G/X+hFfbI47Q2G4t1Paakld84+qsk2fSsUPMKg71jkoOOaQ== dependencies: - "@babel/helper-plugin-utils" "^7.22.5" - "@babel/helper-validator-option" "^7.22.15" - "@babel/plugin-syntax-jsx" "^7.23.3" - "@babel/plugin-transform-modules-commonjs" "^7.23.3" - "@babel/plugin-transform-typescript" "^7.23.3" + "@babel/helper-plugin-utils" "^7.24.0" + "@babel/helper-validator-option" "^7.23.5" + "@babel/plugin-syntax-jsx" "^7.24.1" + "@babel/plugin-transform-modules-commonjs" "^7.24.1" + "@babel/plugin-transform-typescript" "^7.24.1" "@babel/regjsgen@^0.8.0": version "0.8.0" @@ -1239,49 +1230,49 @@ integrity sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA== "@babel/runtime-corejs3@^7.9.2": - version "7.23.8" - resolved "https://registry.yarnpkg.com/@babel/runtime-corejs3/-/runtime-corejs3-7.23.8.tgz#b8aa3d47570bdd08fed77fdfd69542118af0df26" - integrity sha512-2ZzmcDugdm0/YQKFVYsXiwUN7USPX8PM7cytpb4PFl87fM+qYPSvTZX//8tyeJB1j0YDmafBJEbl5f8NfLyuKw== + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/runtime-corejs3/-/runtime-corejs3-7.24.1.tgz#f39707b213441dec645ce8285ae14f281a5077c6" + integrity sha512-T9ko/35G+Bkl+win48GduaPlhSlOjjE5s1TeiEcD+QpxlLQnoEfb/nO/T+TQqkm+ipFwORn+rB8w14iJ/uD0bg== dependencies: core-js-pure "^3.30.2" regenerator-runtime "^0.14.0" -"@babel/runtime@^7.0.0", "@babel/runtime@^7.1.2", "@babel/runtime@^7.10.5", "@babel/runtime@^7.12.0", "@babel/runtime@^7.12.1", "@babel/runtime@^7.12.13", "@babel/runtime@^7.12.5", "@babel/runtime@^7.13.10", "@babel/runtime@^7.16.3", "@babel/runtime@^7.17.2", "@babel/runtime@^7.17.9", "@babel/runtime@^7.18.3", "@babel/runtime@^7.18.6", "@babel/runtime@^7.21.0", "@babel/runtime@^7.23.2", "@babel/runtime@^7.23.4", "@babel/runtime@^7.23.6", "@babel/runtime@^7.23.9", "@babel/runtime@^7.5.5", "@babel/runtime@^7.6.2", "@babel/runtime@^7.7.2", "@babel/runtime@^7.8.4", "@babel/runtime@^7.8.7", "@babel/runtime@^7.9.2": - version "7.23.9" - resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.23.9.tgz#47791a15e4603bb5f905bc0753801cf21d6345f7" - integrity sha512-0CX6F+BI2s9dkUqr08KFrAIZgNFj75rdBU/DjCyYLIaV/quFjkk6T+EJ2LkZHyZTbEV4L5p97mNkUsHl2wLFAw== +"@babel/runtime@^7.0.0", "@babel/runtime@^7.1.2", "@babel/runtime@^7.10.5", "@babel/runtime@^7.12.0", "@babel/runtime@^7.12.1", "@babel/runtime@^7.12.13", "@babel/runtime@^7.12.5", "@babel/runtime@^7.13.10", "@babel/runtime@^7.16.3", "@babel/runtime@^7.17.2", "@babel/runtime@^7.17.9", "@babel/runtime@^7.18.3", "@babel/runtime@^7.18.6", "@babel/runtime@^7.21.0", "@babel/runtime@^7.23.2", "@babel/runtime@^7.23.4", "@babel/runtime@^7.23.8", "@babel/runtime@^7.23.9", "@babel/runtime@^7.5.5", "@babel/runtime@^7.6.2", "@babel/runtime@^7.7.2", "@babel/runtime@^7.8.4", "@babel/runtime@^7.8.7", "@babel/runtime@^7.9.2": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.24.1.tgz#431f9a794d173b53720e69a6464abc6f0e2a5c57" + integrity sha512-+BIznRzyqBf+2wCTxcKE3wDjfGeCoVE61KSHGpkzqrLi8qxqFwBeUFyId2cxkTmm55fzDGnm0+yCxaxygrLUnQ== dependencies: regenerator-runtime "^0.14.0" -"@babel/template@^7.22.15", "@babel/template@^7.3.3": - version "7.22.15" - resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.22.15.tgz#09576efc3830f0430f4548ef971dde1350ef2f38" - integrity sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w== +"@babel/template@^7.22.15", "@babel/template@^7.24.0", "@babel/template@^7.3.3": + version "7.24.0" + resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.24.0.tgz#c6a524aa93a4a05d66aaf31654258fae69d87d50" + integrity sha512-Bkf2q8lMB0AFpX0NFEqSbx1OkTHf0f+0j82mkw+ZpzBnkk7e9Ql0891vlfgi+kHwOk8tQjiQHpqh4LaSa0fKEA== dependencies: - "@babel/code-frame" "^7.22.13" - "@babel/parser" "^7.22.15" - "@babel/types" "^7.22.15" + "@babel/code-frame" "^7.23.5" + "@babel/parser" "^7.24.0" + "@babel/types" "^7.24.0" -"@babel/traverse@^7.16.8", "@babel/traverse@^7.23.7", "@babel/traverse@^7.4.5": - version "7.23.7" - resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.23.7.tgz#9a7bf285c928cb99b5ead19c3b1ce5b310c9c305" - integrity sha512-tY3mM8rH9jM0YHFGyfC0/xf+SB5eKUu7HPj7/k3fpi9dAlsMc5YbQvDi0Sh2QTPXqMhyaAtzAr807TIyfQrmyg== +"@babel/traverse@^7.16.8", "@babel/traverse@^7.24.1", "@babel/traverse@^7.4.5": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.24.1.tgz#d65c36ac9dd17282175d1e4a3c49d5b7988f530c" + integrity sha512-xuU6o9m68KeqZbQuDt2TcKSxUw/mrsvavlEqQ1leZ/B+C9tk6E4sRWy97WaXgvq5E+nU3cXMxv3WKOCanVMCmQ== dependencies: - "@babel/code-frame" "^7.23.5" - "@babel/generator" "^7.23.6" + "@babel/code-frame" "^7.24.1" + "@babel/generator" "^7.24.1" "@babel/helper-environment-visitor" "^7.22.20" "@babel/helper-function-name" "^7.23.0" "@babel/helper-hoist-variables" "^7.22.5" "@babel/helper-split-export-declaration" "^7.22.6" - "@babel/parser" "^7.23.6" - "@babel/types" "^7.23.6" + "@babel/parser" "^7.24.1" + "@babel/types" "^7.24.0" debug "^4.3.1" globals "^11.1.0" -"@babel/types@^7.0.0", "@babel/types@^7.16.8", "@babel/types@^7.20.7", "@babel/types@^7.22.15", "@babel/types@^7.22.19", "@babel/types@^7.22.5", "@babel/types@^7.23.0", "@babel/types@^7.23.4", "@babel/types@^7.23.6", "@babel/types@^7.3.3", "@babel/types@^7.4.4": - version "7.23.6" - resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.23.6.tgz#be33fdb151e1f5a56877d704492c240fc71c7ccd" - integrity sha512-+uarb83brBzPKN38NX1MkB6vb6+mwvR6amUulqAE7ccQw1pEl+bCia9TbdG1lsnFP7lZySvUn37CHyXQdfTwzg== +"@babel/types@^7.0.0", "@babel/types@^7.16.8", "@babel/types@^7.20.7", "@babel/types@^7.22.15", "@babel/types@^7.22.19", "@babel/types@^7.22.5", "@babel/types@^7.23.0", "@babel/types@^7.23.4", "@babel/types@^7.24.0", "@babel/types@^7.3.3", "@babel/types@^7.4.4": + version "7.24.0" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.24.0.tgz#3b951f435a92e7333eba05b7566fd297960ea1bf" + integrity sha512-+j7a5c253RfKh8iABBhywc8NSfP5LURe7Uh4qpsh6jc+aLJguvmIUBdjSdEMQv2bENrCR5MfRdjGo7vzS/ob7w== dependencies: "@babel/helper-string-parser" "^7.23.4" "@babel/helper-validator-identifier" "^7.22.20" @@ -1300,9 +1291,9 @@ "@ucast/mongo2js" "^1.3.0" "@codemirror/autocomplete@^6.0.0": - version "6.11.1" - resolved "https://registry.yarnpkg.com/@codemirror/autocomplete/-/autocomplete-6.11.1.tgz#c733900eee58ac2de817317b9fd1e91b857c4329" - integrity sha512-L5UInv8Ffd6BPw0P3EF7JLYAMeEbclY7+6Q11REt8vhih8RuLreKtPy/xk8wPxs4EQgYqzI7cdgpiYwWlbS/ow== + version "6.15.0" + resolved "https://registry.yarnpkg.com/@codemirror/autocomplete/-/autocomplete-6.15.0.tgz#37bc320f20cdda332d6bf4d1fc7f300f8fc5f04c" + integrity sha512-G2Zm0mXznxz97JhaaOdoEG2cVupn4JjPaS4AcNvZzhOsnnG9YVN68VzfoUw6dYTsIxT6a/cmoFEN47KAWhXaOg== dependencies: "@codemirror/language" "^6.0.0" "@codemirror/state" "^6.0.0" @@ -1328,9 +1319,9 @@ "@lezer/json" "^1.0.0" "@codemirror/language@^6.0.0": - version "6.10.0" - resolved "https://registry.yarnpkg.com/@codemirror/language/-/language-6.10.0.tgz#2d0e818716825ee2ed0dacd04595eaa61bae8f23" - integrity sha512-2vaNn9aPGCRFKWcHPFksctzJ8yS5p7YoaT+jHpc0UGKzNuAIx4qy6R5wiqbP+heEEdyaABA582mNqSHzSoYdmg== + version "6.10.1" + resolved "https://registry.yarnpkg.com/@codemirror/language/-/language-6.10.1.tgz#428c932a158cb75942387acfe513c1ece1090b05" + integrity sha512-5GrXzrhq6k+gL5fjkAwt90nYDmjlzTIJV8THnxNFtNKWotMIlzzN+CpqxqwXOECnUdOndmSeWntVrVcv5axWRQ== dependencies: "@codemirror/state" "^6.0.0" "@codemirror/view" "^6.23.0" @@ -1340,27 +1331,27 @@ style-mod "^4.0.0" "@codemirror/lint@^6.0.0": - version "6.4.2" - resolved "https://registry.yarnpkg.com/@codemirror/lint/-/lint-6.4.2.tgz#c13be5320bde9707efdc94e8bcd3c698abae0b92" - integrity sha512-wzRkluWb1ptPKdzlsrbwwjYCPLgzU6N88YBAmlZi8WFyuiEduSd05MnJYNogzyc8rPK7pj6m95ptUApc8sHKVA== + version "6.5.0" + resolved "https://registry.yarnpkg.com/@codemirror/lint/-/lint-6.5.0.tgz#ea43b6e653dcc5bcd93456b55e9fe62e63f326d9" + integrity sha512-+5YyicIaaAZKU8K43IQi8TBy6mF6giGeWAH7N96Z5LC30Wm5JMjqxOYIE9mxwMG1NbhT2mA3l9hA4uuKUM3E5g== dependencies: "@codemirror/state" "^6.0.0" "@codemirror/view" "^6.0.0" crelt "^1.0.5" "@codemirror/search@^6.0.0": - version "6.5.5" - resolved "https://registry.yarnpkg.com/@codemirror/search/-/search-6.5.5.tgz#cf97e201da364da2285c2a250167af25bbd2a4a2" - integrity sha512-PIEN3Ke1buPod2EHbJsoQwlbpkz30qGZKcnmH1eihq9+bPQx8gelauUwLYaY4vBOuBAuEhmpDLii4rj/uO0yMA== + version "6.5.6" + resolved "https://registry.yarnpkg.com/@codemirror/search/-/search-6.5.6.tgz#8f858b9e678d675869112e475f082d1e8488db93" + integrity sha512-rpMgcsh7o0GuCDUXKPvww+muLA1pDJaFrpq/CCHtpQJYz8xopu4D1hPcKRoDD0YlF8gZaqTNIRa4VRBWyhyy7Q== dependencies: "@codemirror/state" "^6.0.0" "@codemirror/view" "^6.0.0" crelt "^1.0.5" "@codemirror/state@^6.0.0", "@codemirror/state@^6.1.1", "@codemirror/state@^6.4.0": - version "6.4.0" - resolved "https://registry.yarnpkg.com/@codemirror/state/-/state-6.4.0.tgz#8bc3e096c84360b34525a84696a84f86b305363a" - integrity sha512-hm8XshYj5Fo30Bb922QX9hXB/bxOAVH+qaqHBzw5TKa72vOeslyGwd4X8M0c1dJ9JqxlaMceOQ8RsL9tC7gU0A== + version "6.4.1" + resolved "https://registry.yarnpkg.com/@codemirror/state/-/state-6.4.1.tgz#da57143695c056d9a3c38705ed34136e2b68171b" + integrity sha512-QkEyUiLhsJoZkbumGZlswmAhA7CBU02Wrz7zvH4SrcifbsqwlXShVXg65f3v/ts57W3dqyamEriMhij1Z3Zz4A== "@codemirror/theme-one-dark@^6.0.0": version "6.1.2" @@ -1373,9 +1364,9 @@ "@lezer/highlight" "^1.0.0" "@codemirror/view@^6.0.0", "@codemirror/view@^6.17.0", "@codemirror/view@^6.23.0": - version "6.23.0" - resolved "https://registry.yarnpkg.com/@codemirror/view/-/view-6.23.0.tgz#8054a2043273abad7f1587d15accb0623e1960ed" - integrity sha512-/51px9N4uW8NpuWkyUX+iam5+PM6io2fm+QmRnzwqBy5v/pwGg9T0kILFtYeum8hjuvENtgsGNKluOfqIICmeQ== + version "6.26.0" + resolved "https://registry.yarnpkg.com/@codemirror/view/-/view-6.26.0.tgz#ab5a85aa8ebfb953cb5534e07d0a3751f9a3869a" + integrity sha512-nSSmzONpqsNzshPOxiKhK203R6BvABepugAe34QfQDbNDslyjkqBuKgrK5ZBvqNXpfxz5iLrlGTmEfhbQyH46A== dependencies: "@codemirror/state" "^6.4.0" style-mod "^4.1.0" @@ -1503,9 +1494,9 @@ "@emotion/memoize" "0.7.4" "@emotion/is-prop-valid@^1.2.1": - version "1.2.1" - resolved "https://registry.yarnpkg.com/@emotion/is-prop-valid/-/is-prop-valid-1.2.1.tgz#23116cf1ed18bfeac910ec6436561ecb1a3885cc" - integrity sha512-61Mf7Ufx4aDxx1xlDeOm8aFFigGHE4z+0sKCa+IHCeZKiyP9RLD0Mmx7m8b9/Cf37f7NAvQOOJAbQQGVr5uERw== + version "1.2.2" + resolved "https://registry.yarnpkg.com/@emotion/is-prop-valid/-/is-prop-valid-1.2.2.tgz#d4175076679c6a26faa92b03bb786f9e52612337" + integrity sha512-uNsoYd37AFmaCdXlg6EYD1KaPOaRWRByMCYzbKUX4+hhMfrxdVSelShywL4JVaAeM/eHUOSprYBQls+/neX3pw== dependencies: "@emotion/memoize" "^0.8.1" @@ -1520,9 +1511,9 @@ integrity sha512-W2P2c/VRW1/1tLox0mVUalvnWXxavmv/Oum2aPsRcoDJuob75FC3Y8FbpfLwUegRcxINtGUMPq0tFCvYNTBXNA== "@emotion/react@^11.10.5", "@emotion/react@^11.8.1": - version "11.11.3" - resolved "https://registry.yarnpkg.com/@emotion/react/-/react-11.11.3.tgz#96b855dc40a2a55f52a72f518a41db4f69c31a25" - integrity sha512-Cnn0kuq4DoONOMcnoVsTOR8E+AdnKFf//6kUWc4LCdnxj31pZWn7rIULd6Y7/Js1PiPHzn7SKCM9vB/jBni8eA== + version "11.11.4" + resolved "https://registry.yarnpkg.com/@emotion/react/-/react-11.11.4.tgz#3a829cac25c1f00e126408fab7f891f00ecc3c1d" + integrity sha512-t8AjMlF0gHpvvxk5mAtCqR4vmxiGHCeJBaQO6gncUSdklELOgtwjerNY2yuJNfwnc6vi16U/+uMF+afIawJ9iw== dependencies: "@babel/runtime" "^7.18.3" "@emotion/babel-plugin" "^11.11.0" @@ -1591,10 +1582,10 @@ resolved "https://registry.yarnpkg.com/@emotion/weak-memoize/-/weak-memoize-0.3.1.tgz#d0fce5d07b0620caa282b5131c297bb60f9d87e6" integrity sha512-EsBwpc7hBUJWAsNPBmJy4hxWx12v6bshQsldrVmjxJoc3isbxhOrF2IcCpaXxfvq03NwkI7sbsOLXbYuqF/8Ww== -"@esbuild/aix-ppc64@0.19.11": - version "0.19.11" - resolved "https://registry.yarnpkg.com/@esbuild/aix-ppc64/-/aix-ppc64-0.19.11.tgz#2acd20be6d4f0458bc8c784103495ff24f13b1d3" - integrity sha512-FnzU0LyE3ySQk7UntJO4+qIiQgI7KoODnZg5xzXIrFJlKd2P2gwHsHY4927xj9y5PJmJSzULiUCWmv7iWnNa7g== +"@esbuild/aix-ppc64@0.20.2": + version "0.20.2" + resolved "https://registry.yarnpkg.com/@esbuild/aix-ppc64/-/aix-ppc64-0.20.2.tgz#a70f4ac11c6a1dfc18b8bbb13284155d933b9537" + integrity sha512-D+EBOJHXdNZcLJRBkhENNG8Wji2kgc9AZ9KiPr1JuZjsNtyHzrsfLRrY0tk2H2aoFu6RANO1y1iPPUCDYWkb5g== "@esbuild/android-arm64@0.16.17": version "0.16.17" @@ -1606,16 +1597,16 @@ resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.18.20.tgz#984b4f9c8d0377443cc2dfcef266d02244593622" integrity sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ== -"@esbuild/android-arm64@0.19.11": - version "0.19.11" - resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.19.11.tgz#b45d000017385c9051a4f03e17078abb935be220" - integrity sha512-aiu7K/5JnLj//KOnOfEZ0D90obUkRzDMyqd/wNAUQ34m4YUPVhRZpnqKV9uqDGxT7cToSDnIHsGooyIczu9T+Q== - "@esbuild/android-arm64@0.19.2": version "0.19.2" resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.19.2.tgz#bc35990f412a749e948b792825eef7df0ce0e073" integrity sha512-lsB65vAbe90I/Qe10OjkmrdxSX4UJDjosDgb8sZUKcg3oefEuW2OT2Vozz8ef7wrJbMcmhvCC+hciF8jY/uAkw== +"@esbuild/android-arm64@0.20.2": + version "0.20.2" + resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.20.2.tgz#db1c9202a5bc92ea04c7b6840f1bbe09ebf9e6b9" + integrity sha512-mRzjLacRtl/tWU0SvD8lUEwb61yP9cqQo6noDZP/O8VkwafSYwZ4yWy24kan8jE/IMERpYncRt2dw438LP3Xmg== + "@esbuild/android-arm@0.16.17": version "0.16.17" resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.16.17.tgz#025b6246d3f68b7bbaa97069144fb5fb70f2fff2" @@ -1626,16 +1617,16 @@ resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.18.20.tgz#fedb265bc3a589c84cc11f810804f234947c3682" integrity sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw== -"@esbuild/android-arm@0.19.11": - version "0.19.11" - resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.19.11.tgz#f46f55414e1c3614ac682b29977792131238164c" - integrity sha512-5OVapq0ClabvKvQ58Bws8+wkLCV+Rxg7tUVbo9xu034Nm536QTII4YzhaFriQ7rMrorfnFKUsArD2lqKbFY4vw== - "@esbuild/android-arm@0.19.2": version "0.19.2" resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.19.2.tgz#edd1c8f23ba353c197f5b0337123c58ff2a56999" integrity sha512-tM8yLeYVe7pRyAu9VMi/Q7aunpLwD139EY1S99xbQkT4/q2qa6eA4ige/WJQYdJ8GBL1K33pPFhPfPdJ/WzT8Q== +"@esbuild/android-arm@0.20.2": + version "0.20.2" + resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.20.2.tgz#3b488c49aee9d491c2c8f98a909b785870d6e995" + integrity sha512-t98Ra6pw2VaDhqNWO2Oph2LXbz/EJcnLmKLGBJwEwXX/JAN83Fym1rU8l0JUWK6HkIbWONCSSatf4sf2NBRx/w== + "@esbuild/android-x64@0.16.17": version "0.16.17" resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.16.17.tgz#c820e0fef982f99a85c4b8bfdd582835f04cd96e" @@ -1646,16 +1637,16 @@ resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.18.20.tgz#35cf419c4cfc8babe8893d296cd990e9e9f756f2" integrity sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg== -"@esbuild/android-x64@0.19.11": - version "0.19.11" - resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.19.11.tgz#bfc01e91740b82011ef503c48f548950824922b2" - integrity sha512-eccxjlfGw43WYoY9QgB82SgGgDbibcqyDTlk3l3C0jOVHKxrjdc9CTwDUQd0vkvYg5um0OH+GpxYvp39r+IPOg== - "@esbuild/android-x64@0.19.2": version "0.19.2" resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.19.2.tgz#2dcdd6e6f1f2d82ea1b746abd8da5b284960f35a" integrity sha512-qK/TpmHt2M/Hg82WXHRc/W/2SGo/l1thtDHZWqFq7oi24AjZ4O/CpPSu6ZuYKFkEgmZlFoa7CooAyYmuvnaG8w== +"@esbuild/android-x64@0.20.2": + version "0.20.2" + resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.20.2.tgz#3b1628029e5576249d2b2d766696e50768449f98" + integrity sha512-btzExgV+/lMGDDa194CcUQm53ncxzeBrWJcncOBxuC6ndBkKxnHdFJn86mCIgTELsooUmwUm9FkhSp5HYu00Rg== + "@esbuild/darwin-arm64@0.16.17": version "0.16.17" resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.16.17.tgz#edef4487af6b21afabba7be5132c26d22379b220" @@ -1666,16 +1657,16 @@ resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.18.20.tgz#08172cbeccf95fbc383399a7f39cfbddaeb0d7c1" integrity sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA== -"@esbuild/darwin-arm64@0.19.11": - version "0.19.11" - resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.19.11.tgz#533fb7f5a08c37121d82c66198263dcc1bed29bf" - integrity sha512-ETp87DRWuSt9KdDVkqSoKoLFHYTrkyz2+65fj9nfXsaV3bMhTCjtQfw3y+um88vGRKRiF7erPrh/ZuIdLUIVxQ== - "@esbuild/darwin-arm64@0.19.2": version "0.19.2" resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.19.2.tgz#55b36bc06d76f5c243987c1f93a11a80d8fc3b26" integrity sha512-Ora8JokrvrzEPEpZO18ZYXkH4asCdc1DLdcVy8TGf5eWtPO1Ie4WroEJzwI52ZGtpODy3+m0a2yEX9l+KUn0tA== +"@esbuild/darwin-arm64@0.20.2": + version "0.20.2" + resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.20.2.tgz#6e8517a045ddd86ae30c6608c8475ebc0c4000bb" + integrity sha512-4J6IRT+10J3aJH3l1yzEg9y3wkTDgDk7TSDFX+wKFiWjqWp/iCfLIYzGyasx9l0SAFPT1HwSCR+0w/h1ES/MjA== + "@esbuild/darwin-x64@0.16.17": version "0.16.17" resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.16.17.tgz#42829168730071c41ef0d028d8319eea0e2904b4" @@ -1686,16 +1677,16 @@ resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.18.20.tgz#d70d5790d8bf475556b67d0f8b7c5bdff053d85d" integrity sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ== -"@esbuild/darwin-x64@0.19.11": - version "0.19.11" - resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.19.11.tgz#62f3819eff7e4ddc656b7c6815a31cf9a1e7d98e" - integrity sha512-fkFUiS6IUK9WYUO/+22omwetaSNl5/A8giXvQlcinLIjVkxwTLSktbF5f/kJMftM2MJp9+fXqZ5ezS7+SALp4g== - "@esbuild/darwin-x64@0.19.2": version "0.19.2" resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.19.2.tgz#982524af33a6424a3b5cb44bbd52559623ad719c" integrity sha512-tP+B5UuIbbFMj2hQaUr6EALlHOIOmlLM2FK7jeFBobPy2ERdohI4Ka6ZFjZ1ZYsrHE/hZimGuU90jusRE0pwDw== +"@esbuild/darwin-x64@0.20.2": + version "0.20.2" + resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.20.2.tgz#90ed098e1f9dd8a9381695b207e1cff45540a0d0" + integrity sha512-tBcXp9KNphnNH0dfhv8KYkZhjc+H3XBkF5DKtswJblV7KlT9EI2+jeA8DgBjp908WEuYll6pF+UStUCfEpdysA== + "@esbuild/freebsd-arm64@0.16.17": version "0.16.17" resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.16.17.tgz#1f4af488bfc7e9ced04207034d398e793b570a27" @@ -1706,16 +1697,16 @@ resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.18.20.tgz#98755cd12707f93f210e2494d6a4b51b96977f54" integrity sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw== -"@esbuild/freebsd-arm64@0.19.11": - version "0.19.11" - resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.19.11.tgz#d478b4195aa3ca44160272dab85ef8baf4175b4a" - integrity sha512-lhoSp5K6bxKRNdXUtHoNc5HhbXVCS8V0iZmDvyWvYq9S5WSfTIHU2UGjcGt7UeS6iEYp9eeymIl5mJBn0yiuxA== - "@esbuild/freebsd-arm64@0.19.2": version "0.19.2" resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.19.2.tgz#8e478a0856645265fe79eac4b31b52193011ee06" integrity sha512-YbPY2kc0acfzL1VPVK6EnAlig4f+l8xmq36OZkU0jzBVHcOTyQDhnKQaLzZudNJQyymd9OqQezeaBgkTGdTGeQ== +"@esbuild/freebsd-arm64@0.20.2": + version "0.20.2" + resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.20.2.tgz#d71502d1ee89a1130327e890364666c760a2a911" + integrity sha512-d3qI41G4SuLiCGCFGUrKsSeTXyWG6yem1KcGZVS+3FYlYhtNoNgYrWcvkOoaqMhwXSMrZRl69ArHsGJ9mYdbbw== + "@esbuild/freebsd-x64@0.16.17": version "0.16.17" resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.16.17.tgz#636306f19e9bc981e06aa1d777302dad8fddaf72" @@ -1726,16 +1717,16 @@ resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.18.20.tgz#c1eb2bff03915f87c29cece4c1a7fa1f423b066e" integrity sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ== -"@esbuild/freebsd-x64@0.19.11": - version "0.19.11" - resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.19.11.tgz#7bdcc1917409178257ca6a1a27fe06e797ec18a2" - integrity sha512-JkUqn44AffGXitVI6/AbQdoYAq0TEullFdqcMY/PCUZ36xJ9ZJRtQabzMA+Vi7r78+25ZIBosLTOKnUXBSi1Kw== - "@esbuild/freebsd-x64@0.19.2": version "0.19.2" resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.19.2.tgz#01b96604f2540db023c73809bb8ae6cd1692d6f3" integrity sha512-nSO5uZT2clM6hosjWHAsS15hLrwCvIWx+b2e3lZ3MwbYSaXwvfO528OF+dLjas1g3bZonciivI8qKR/Hm7IWGw== +"@esbuild/freebsd-x64@0.20.2": + version "0.20.2" + resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.20.2.tgz#aa5ea58d9c1dd9af688b8b6f63ef0d3d60cea53c" + integrity sha512-d+DipyvHRuqEeM5zDivKV1KuXn9WeRX6vqSqIDgwIfPQtwMP4jaDsQsDncjTDDsExT4lR/91OLjRo8bmC1e+Cw== + "@esbuild/linux-arm64@0.16.17": version "0.16.17" resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.16.17.tgz#a003f7ff237c501e095d4f3a09e58fc7b25a4aca" @@ -1746,16 +1737,16 @@ resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.18.20.tgz#bad4238bd8f4fc25b5a021280c770ab5fc3a02a0" integrity sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA== -"@esbuild/linux-arm64@0.19.11": - version "0.19.11" - resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.19.11.tgz#58ad4ff11685fcc735d7ff4ca759ab18fcfe4545" - integrity sha512-LneLg3ypEeveBSMuoa0kwMpCGmpu8XQUh+mL8XXwoYZ6Be2qBnVtcDI5azSvh7vioMDhoJFZzp9GWp9IWpYoUg== - "@esbuild/linux-arm64@0.19.2": version "0.19.2" resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.19.2.tgz#7e5d2c7864c5c83ec789b59c77cd9c20d2594916" integrity sha512-ig2P7GeG//zWlU0AggA3pV1h5gdix0MA3wgB+NsnBXViwiGgY77fuN9Wr5uoCrs2YzaYfogXgsWZbm+HGr09xg== +"@esbuild/linux-arm64@0.20.2": + version "0.20.2" + resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.20.2.tgz#055b63725df678379b0f6db9d0fa85463755b2e5" + integrity sha512-9pb6rBjGvTFNira2FLIWqDk/uaf42sSyLE8j1rnUpuzsODBq7FvpwHYZxQ/It/8b+QOS1RYfqgGFNLRI+qlq2A== + "@esbuild/linux-arm@0.16.17": version "0.16.17" resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.16.17.tgz#b591e6a59d9c4fe0eeadd4874b157ab78cf5f196" @@ -1766,16 +1757,16 @@ resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.18.20.tgz#3e617c61f33508a27150ee417543c8ab5acc73b0" integrity sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg== -"@esbuild/linux-arm@0.19.11": - version "0.19.11" - resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.19.11.tgz#ce82246d873b5534d34de1e5c1b33026f35e60e3" - integrity sha512-3CRkr9+vCV2XJbjwgzjPtO8T0SZUmRZla+UL1jw+XqHZPkPgZiyWvbDvl9rqAN8Zl7qJF0O/9ycMtjU67HN9/Q== - "@esbuild/linux-arm@0.19.2": version "0.19.2" resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.19.2.tgz#c32ae97bc0246664a1cfbdb4a98e7b006d7db8ae" integrity sha512-Odalh8hICg7SOD7XCj0YLpYCEc+6mkoq63UnExDCiRA2wXEmGlK5JVrW50vZR9Qz4qkvqnHcpH+OFEggO3PgTg== +"@esbuild/linux-arm@0.20.2": + version "0.20.2" + resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.20.2.tgz#76b3b98cb1f87936fbc37f073efabad49dcd889c" + integrity sha512-VhLPeR8HTMPccbuWWcEUD1Az68TqaTYyj6nfE4QByZIQEQVWBB8vup8PpR7y1QHL3CpcF6xd5WVBU/+SBEvGTg== + "@esbuild/linux-ia32@0.16.17": version "0.16.17" resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.16.17.tgz#24333a11027ef46a18f57019450a5188918e2a54" @@ -1786,16 +1777,16 @@ resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.18.20.tgz#699391cccba9aee6019b7f9892eb99219f1570a7" integrity sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA== -"@esbuild/linux-ia32@0.19.11": - version "0.19.11" - resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.19.11.tgz#cbae1f313209affc74b80f4390c4c35c6ab83fa4" - integrity sha512-caHy++CsD8Bgq2V5CodbJjFPEiDPq8JJmBdeyZ8GWVQMjRD0sU548nNdwPNvKjVpamYYVL40AORekgfIubwHoA== - "@esbuild/linux-ia32@0.19.2": version "0.19.2" resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.19.2.tgz#3fc4f0fa026057fe885e4a180b3956e704f1ceaa" integrity sha512-mLfp0ziRPOLSTek0Gd9T5B8AtzKAkoZE70fneiiyPlSnUKKI4lp+mGEnQXcQEHLJAcIYDPSyBvsUbKUG2ri/XQ== +"@esbuild/linux-ia32@0.20.2": + version "0.20.2" + resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.20.2.tgz#c0e5e787c285264e5dfc7a79f04b8b4eefdad7fa" + integrity sha512-o10utieEkNPFDZFQm9CoP7Tvb33UutoJqg3qKf1PWVeeJhJw0Q347PxMvBgVVFgouYLGIhFYG0UGdBumROyiig== + "@esbuild/linux-loong64@0.14.54": version "0.14.54" resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.14.54.tgz#de2a4be678bd4d0d1ffbb86e6de779cde5999028" @@ -1811,16 +1802,16 @@ resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.18.20.tgz#e6fccb7aac178dd2ffb9860465ac89d7f23b977d" integrity sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg== -"@esbuild/linux-loong64@0.19.11": - version "0.19.11" - resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.19.11.tgz#5f32aead1c3ec8f4cccdb7ed08b166224d4e9121" - integrity sha512-ppZSSLVpPrwHccvC6nQVZaSHlFsvCQyjnvirnVjbKSHuE5N24Yl8F3UwYUUR1UEPaFObGD2tSvVKbvR+uT1Nrg== - "@esbuild/linux-loong64@0.19.2": version "0.19.2" resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.19.2.tgz#633bcaea443f3505fb0ed109ab840c99ad3451a4" integrity sha512-hn28+JNDTxxCpnYjdDYVMNTR3SKavyLlCHHkufHV91fkewpIyQchS1d8wSbmXhs1fiYDpNww8KTFlJ1dHsxeSw== +"@esbuild/linux-loong64@0.20.2": + version "0.20.2" + resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.20.2.tgz#a6184e62bd7cdc63e0c0448b83801001653219c5" + integrity sha512-PR7sp6R/UC4CFVomVINKJ80pMFlfDfMQMYynX7t1tNTeivQ6XdX5r2XovMmha/VjR1YN/HgHWsVcTRIMkymrgQ== + "@esbuild/linux-mips64el@0.16.17": version "0.16.17" resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.16.17.tgz#4e5967a665c38360b0a8205594377d4dcf9c3726" @@ -1831,16 +1822,16 @@ resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.18.20.tgz#eeff3a937de9c2310de30622a957ad1bd9183231" integrity sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ== -"@esbuild/linux-mips64el@0.19.11": - version "0.19.11" - resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.19.11.tgz#38eecf1cbb8c36a616261de858b3c10d03419af9" - integrity sha512-B5x9j0OgjG+v1dF2DkH34lr+7Gmv0kzX6/V0afF41FkPMMqaQ77pH7CrhWeR22aEeHKaeZVtZ6yFwlxOKPVFyg== - "@esbuild/linux-mips64el@0.19.2": version "0.19.2" resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.19.2.tgz#e0bff2898c46f52be7d4dbbcca8b887890805823" integrity sha512-KbXaC0Sejt7vD2fEgPoIKb6nxkfYW9OmFUK9XQE4//PvGIxNIfPk1NmlHmMg6f25x57rpmEFrn1OotASYIAaTg== +"@esbuild/linux-mips64el@0.20.2": + version "0.20.2" + resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.20.2.tgz#d08e39ce86f45ef8fc88549d29c62b8acf5649aa" + integrity sha512-4BlTqeutE/KnOiTG5Y6Sb/Hw6hsBOZapOVF6njAESHInhlQAghVVZL1ZpIctBOoTFbQyGW+LsVYZ8lSSB3wkjA== + "@esbuild/linux-ppc64@0.16.17": version "0.16.17" resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.16.17.tgz#206443a02eb568f9fdf0b438fbd47d26e735afc8" @@ -1851,16 +1842,16 @@ resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.18.20.tgz#2f7156bde20b01527993e6881435ad79ba9599fb" integrity sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA== -"@esbuild/linux-ppc64@0.19.11": - version "0.19.11" - resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.19.11.tgz#9c5725a94e6ec15b93195e5a6afb821628afd912" - integrity sha512-MHrZYLeCG8vXblMetWyttkdVRjQlQUb/oMgBNurVEnhj4YWOr4G5lmBfZjHYQHHN0g6yDmCAQRR8MUHldvvRDA== - "@esbuild/linux-ppc64@0.19.2": version "0.19.2" resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.19.2.tgz#d75798da391f54a9674f8c143b9a52d1dbfbfdde" integrity sha512-dJ0kE8KTqbiHtA3Fc/zn7lCd7pqVr4JcT0JqOnbj4LLzYnp+7h8Qi4yjfq42ZlHfhOCM42rBh0EwHYLL6LEzcw== +"@esbuild/linux-ppc64@0.20.2": + version "0.20.2" + resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.20.2.tgz#8d252f0b7756ffd6d1cbde5ea67ff8fd20437f20" + integrity sha512-rD3KsaDprDcfajSKdn25ooz5J5/fWBylaaXkuotBDGnMnDP1Uv5DLAN/45qfnf3JDYyJv/ytGHQaziHUdyzaAg== + "@esbuild/linux-riscv64@0.16.17": version "0.16.17" resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.16.17.tgz#c351e433d009bf256e798ad048152c8d76da2fc9" @@ -1871,16 +1862,16 @@ resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.18.20.tgz#6628389f210123d8b4743045af8caa7d4ddfc7a6" integrity sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A== -"@esbuild/linux-riscv64@0.19.11": - version "0.19.11" - resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.19.11.tgz#2dc4486d474a2a62bbe5870522a9a600e2acb916" - integrity sha512-f3DY++t94uVg141dozDu4CCUkYW+09rWtaWfnb3bqe4w5NqmZd6nPVBm+qbz7WaHZCoqXqHz5p6CM6qv3qnSSQ== - "@esbuild/linux-riscv64@0.19.2": version "0.19.2" resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.19.2.tgz#012409bd489ed1bb9b775541d4a46c5ded8e6dd8" integrity sha512-7Z/jKNFufZ/bbu4INqqCN6DDlrmOTmdw6D0gH+6Y7auok2r02Ur661qPuXidPOJ+FSgbEeQnnAGgsVynfLuOEw== +"@esbuild/linux-riscv64@0.20.2": + version "0.20.2" + resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.20.2.tgz#19f6dcdb14409dae607f66ca1181dd4e9db81300" + integrity sha512-snwmBKacKmwTMmhLlz/3aH1Q9T8v45bKYGE3j26TsaOVtjIag4wLfWSiZykXzXuE1kbCE+zJRmwp+ZbIHinnVg== + "@esbuild/linux-s390x@0.16.17": version "0.16.17" resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.16.17.tgz#661f271e5d59615b84b6801d1c2123ad13d9bd87" @@ -1891,16 +1882,16 @@ resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.18.20.tgz#255e81fb289b101026131858ab99fba63dcf0071" integrity sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ== -"@esbuild/linux-s390x@0.19.11": - version "0.19.11" - resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.19.11.tgz#4ad8567df48f7dd4c71ec5b1753b6f37561a65a8" - integrity sha512-A5xdUoyWJHMMlcSMcPGVLzYzpcY8QP1RtYzX5/bS4dvjBGVxdhuiYyFwp7z74ocV7WDc0n1harxmpq2ePOjI0Q== - "@esbuild/linux-s390x@0.19.2": version "0.19.2" resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.19.2.tgz#ece3ed75c5a150de8a5c110f02e97d315761626b" integrity sha512-U+RinR6aXXABFCcAY4gSlv4CL1oOVvSSCdseQmGO66H+XyuQGZIUdhG56SZaDJQcLmrSfRmx5XZOWyCJPRqS7g== +"@esbuild/linux-s390x@0.20.2": + version "0.20.2" + resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.20.2.tgz#3c830c90f1a5d7dd1473d5595ea4ebb920988685" + integrity sha512-wcWISOobRWNm3cezm5HOZcYz1sKoHLd8VL1dl309DiixxVFoFe/o8HnwuIwn6sXre88Nwj+VwZUvJf4AFxkyrQ== + "@esbuild/linux-x64@0.16.17": version "0.16.17" resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.16.17.tgz#e4ba18e8b149a89c982351443a377c723762b85f" @@ -1911,16 +1902,16 @@ resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.18.20.tgz#c7690b3417af318a9b6f96df3031a8865176d338" integrity sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w== -"@esbuild/linux-x64@0.19.11": - version "0.19.11" - resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.19.11.tgz#b7390c4d5184f203ebe7ddaedf073df82a658766" - integrity sha512-grbyMlVCvJSfxFQUndw5mCtWs5LO1gUlwP4CDi4iJBbVpZcqLVT29FxgGuBJGSzyOxotFG4LoO5X+M1350zmPA== - "@esbuild/linux-x64@0.19.2": version "0.19.2" resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.19.2.tgz#dea187019741602d57aaf189a80abba261fbd2aa" integrity sha512-oxzHTEv6VPm3XXNaHPyUTTte+3wGv7qVQtqaZCrgstI16gCuhNOtBXLEBkBREP57YTd68P0VgDgG73jSD8bwXQ== +"@esbuild/linux-x64@0.20.2": + version "0.20.2" + resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.20.2.tgz#86eca35203afc0d9de0694c64ec0ab0a378f6fff" + integrity sha512-1MdwI6OOTsfQfek8sLwgyjOXAu+wKhLEoaOLTjbijk6E2WONYpH9ZU2mNtR+lZ2B4uwr+usqGuVfFT9tMtGvGw== + "@esbuild/netbsd-x64@0.16.17": version "0.16.17" resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.16.17.tgz#7d4f4041e30c5c07dd24ffa295c73f06038ec775" @@ -1931,16 +1922,16 @@ resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.18.20.tgz#30e8cd8a3dded63975e2df2438ca109601ebe0d1" integrity sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A== -"@esbuild/netbsd-x64@0.19.11": - version "0.19.11" - resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.19.11.tgz#d633c09492a1721377f3bccedb2d821b911e813d" - integrity sha512-13jvrQZJc3P230OhU8xgwUnDeuC/9egsjTkXN49b3GcS5BKvJqZn86aGM8W9pd14Kd+u7HuFBMVtrNGhh6fHEQ== - "@esbuild/netbsd-x64@0.19.2": version "0.19.2" resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.19.2.tgz#bbfd7cf9ab236a23ee3a41b26f0628c57623d92a" integrity sha512-WNa5zZk1XpTTwMDompZmvQLHszDDDN7lYjEHCUmAGB83Bgs20EMs7ICD+oKeT6xt4phV4NDdSi/8OfjPbSbZfQ== +"@esbuild/netbsd-x64@0.20.2": + version "0.20.2" + resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.20.2.tgz#e771c8eb0e0f6e1877ffd4220036b98aed5915e6" + integrity sha512-K8/DhBxcVQkzYc43yJXDSyjlFeHQJBiowJ0uVL6Tor3jGQfSGHNNJcWxNbOI8v5k82prYqzPuwkzHt3J1T1iZQ== + "@esbuild/openbsd-x64@0.16.17": version "0.16.17" resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.16.17.tgz#970fa7f8470681f3e6b1db0cc421a4af8060ec35" @@ -1951,16 +1942,16 @@ resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.18.20.tgz#7812af31b205055874c8082ea9cf9ab0da6217ae" integrity sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg== -"@esbuild/openbsd-x64@0.19.11": - version "0.19.11" - resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.19.11.tgz#17388c76e2f01125bf831a68c03a7ffccb65d1a2" - integrity sha512-ysyOGZuTp6SNKPE11INDUeFVVQFrhcNDVUgSQVDzqsqX38DjhPEPATpid04LCoUr2WXhQTEZ8ct/EgJCUDpyNw== - "@esbuild/openbsd-x64@0.19.2": version "0.19.2" resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.19.2.tgz#fa5c4c6ee52a360618f00053652e2902e1d7b4a7" integrity sha512-S6kI1aT3S++Dedb7vxIuUOb3oAxqxk2Rh5rOXOTYnzN8JzW1VzBd+IqPiSpgitu45042SYD3HCoEyhLKQcDFDw== +"@esbuild/openbsd-x64@0.20.2": + version "0.20.2" + resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.20.2.tgz#9a795ae4b4e37e674f0f4d716f3e226dd7c39baf" + integrity sha512-eMpKlV0SThJmmJgiVyN9jTPJ2VBPquf6Kt/nAoo6DgHAoN57K15ZghiHaMvqjCye/uU4X5u3YSMgVBI1h3vKrQ== + "@esbuild/sunos-x64@0.16.17": version "0.16.17" resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.16.17.tgz#abc60e7c4abf8b89fb7a4fe69a1484132238022c" @@ -1971,16 +1962,16 @@ resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.18.20.tgz#d5c275c3b4e73c9b0ecd38d1ca62c020f887ab9d" integrity sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ== -"@esbuild/sunos-x64@0.19.11": - version "0.19.11" - resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.19.11.tgz#e320636f00bb9f4fdf3a80e548cb743370d41767" - integrity sha512-Hf+Sad9nVwvtxy4DXCZQqLpgmRTQqyFyhT3bZ4F2XlJCjxGmRFF0Shwn9rzhOYRB61w9VMXUkxlBy56dk9JJiQ== - "@esbuild/sunos-x64@0.19.2": version "0.19.2" resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.19.2.tgz#52a2ac8ac6284c02d25df22bb4cfde26fbddd68d" integrity sha512-VXSSMsmb+Z8LbsQGcBMiM+fYObDNRm8p7tkUDMPG/g4fhFX5DEFmjxIEa3N8Zr96SjsJ1woAhF0DUnS3MF3ARw== +"@esbuild/sunos-x64@0.20.2": + version "0.20.2" + resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.20.2.tgz#7df23b61a497b8ac189def6e25a95673caedb03f" + integrity sha512-2UyFtRC6cXLyejf/YEld4Hajo7UHILetzE1vsRcGL3earZEW77JxrFjH4Ez2qaTiEfMgAXxfAZCm1fvM/G/o8w== + "@esbuild/win32-arm64@0.16.17": version "0.16.17" resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.16.17.tgz#7b0ff9e8c3265537a7a7b1fd9a24e7bd39fcd87a" @@ -1991,16 +1982,16 @@ resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.18.20.tgz#73bc7f5a9f8a77805f357fab97f290d0e4820ac9" integrity sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg== -"@esbuild/win32-arm64@0.19.11": - version "0.19.11" - resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.19.11.tgz#c778b45a496e90b6fc373e2a2bb072f1441fe0ee" - integrity sha512-0P58Sbi0LctOMOQbpEOvOL44Ne0sqbS0XWHMvvrg6NE5jQ1xguCSSw9jQeUk2lfrXYsKDdOe6K+oZiwKPilYPQ== - "@esbuild/win32-arm64@0.19.2": version "0.19.2" resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.19.2.tgz#719ed5870855de8537aef8149694a97d03486804" integrity sha512-5NayUlSAyb5PQYFAU9x3bHdsqB88RC3aM9lKDAz4X1mo/EchMIT1Q+pSeBXNgkfNmRecLXA0O8xP+x8V+g/LKg== +"@esbuild/win32-arm64@0.20.2": + version "0.20.2" + resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.20.2.tgz#f1ae5abf9ca052ae11c1bc806fb4c0f519bacf90" + integrity sha512-GRibxoawM9ZCnDxnP3usoUDO9vUkpAxIIZ6GQI+IlVmr5kP3zUq+l17xELTHMWTWzjxa2guPNyrpq1GWmPvcGQ== + "@esbuild/win32-ia32@0.16.17": version "0.16.17" resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.16.17.tgz#e90fe5267d71a7b7567afdc403dfd198c292eb09" @@ -2011,16 +2002,16 @@ resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.18.20.tgz#ec93cbf0ef1085cc12e71e0d661d20569ff42102" integrity sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g== -"@esbuild/win32-ia32@0.19.11": - version "0.19.11" - resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.19.11.tgz#481a65fee2e5cce74ec44823e6b09ecedcc5194c" - integrity sha512-6YOrWS+sDJDmshdBIQU+Uoyh7pQKrdykdefC1avn76ss5c+RN6gut3LZA4E2cH5xUEp5/cA0+YxRaVtRAb0xBg== - "@esbuild/win32-ia32@0.19.2": version "0.19.2" resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.19.2.tgz#24832223880b0f581962c8660f8fb8797a1e046a" integrity sha512-47gL/ek1v36iN0wL9L4Q2MFdujR0poLZMJwhO2/N3gA89jgHp4MR8DKCmwYtGNksbfJb9JoTtbkoe6sDhg2QTA== +"@esbuild/win32-ia32@0.20.2": + version "0.20.2" + resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.20.2.tgz#241fe62c34d8e8461cd708277813e1d0ba55ce23" + integrity sha512-HfLOfn9YWmkSKRQqovpnITazdtquEW8/SoHW7pWpuEeguaZI4QnCRW6b+oZTztdBnZOS2hqJ6im/D5cPzBTTlQ== + "@esbuild/win32-x64@0.16.17": version "0.16.17" resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.16.17.tgz#c5a1a4bfe1b57f0c3e61b29883525c6da3e5c091" @@ -2031,16 +2022,16 @@ resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.18.20.tgz#786c5f41f043b07afb1af37683d7c33668858f6d" integrity sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ== -"@esbuild/win32-x64@0.19.11": - version "0.19.11" - resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.19.11.tgz#a5d300008960bb39677c46bf16f53ec70d8dee04" - integrity sha512-vfkhltrjCAb603XaFhqhAF4LGDi2M4OrCRrFusyQ+iTLQ/o60QQXxc9cZC/FFpihBI9N1Grn6SMKVJ4KP7Fuiw== - "@esbuild/win32-x64@0.19.2": version "0.19.2" resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.19.2.tgz#1205014625790c7ff0e471644a878a65d1e34ab0" integrity sha512-tcuhV7ncXBqbt/Ybf0IyrMcwVOAPDckMK9rXNHtF17UTK18OKLpg08glminN06pt2WCoALhXdLfSPbVvK/6fxw== +"@esbuild/win32-x64@0.20.2": + version "0.20.2" + resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.20.2.tgz#9c907b21e30a52db959ba4f80bb01a0cc403d5cc" + integrity sha512-N49X4lJX27+l9jbLKSqZ6bKNjzQvHaT8IIFUy+YIqmXQdjYCToGWwOItDrfby14c78aDd5NHQl29xingXfCdLQ== + "@eslint-community/eslint-utils@^4.2.0", "@eslint-community/eslint-utils@^4.4.0": version "4.4.0" resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz#a23514e8fb9af1269d5f7788aa556798d61c6b59" @@ -2068,10 +2059,10 @@ minimatch "^3.1.2" strip-json-comments "^3.1.1" -"@eslint/js@8.56.0": - version "8.56.0" - resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.56.0.tgz#ef20350fec605a7f7035a01764731b2de0f3782b" - integrity sha512-gMsVel9D7f2HLkBma9VbtzZRehRogVRfbr++f06nL2vnCGCNlzOD+/MUov/F4p8myyAHspEhVobgjpX64q5m6A== +"@eslint/js@8.57.0": + version "8.57.0" + resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.57.0.tgz#a5417ae8427873f1dd08b70b3574b453e67b5f7f" + integrity sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g== "@ethereumjs/rlp@^4.0.1": version "4.0.1" @@ -2445,9 +2436,9 @@ "@ethersproject/strings" "^5.7.0" "@fastify/busboy@^2.0.0": - version "2.1.0" - resolved "https://registry.yarnpkg.com/@fastify/busboy/-/busboy-2.1.0.tgz#0709e9f4cb252351c609c6e6d8d6779a8d25edff" - integrity sha512-+KpH+QxZU7O4675t3mnkQKcZZg56u+K/Ct2K+N2AZYNVK8kyeo/bI18tI8aPm3tvNNRyTWfj6s5tnGNlcbQRsA== + version "2.1.1" + resolved "https://registry.yarnpkg.com/@fastify/busboy/-/busboy-2.1.1.tgz#b9da6a878a371829a0502c9b6c1c143ef6663f4d" + integrity sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA== "@float-capital/float-subgraph-uncrashable@^0.0.0-alpha.4": version "0.0.0-internal-testing.5" @@ -2466,22 +2457,7 @@ dependencies: "@floating-ui/utils" "^0.2.1" -"@floating-ui/core@^1.5.3": - version "1.5.3" - resolved "https://registry.yarnpkg.com/@floating-ui/core/-/core-1.5.3.tgz#b6aa0827708d70971c8679a16cf680a515b8a52a" - integrity sha512-O0WKDOo0yhJuugCx6trZQj5jVJ9yR0ystG2JaNAemYUWce+pmM6WUEFIibnWyEJKdrDxhm75NoSRME35FNaM/Q== - dependencies: - "@floating-ui/utils" "^0.2.0" - -"@floating-ui/dom@^1.0.1", "@floating-ui/dom@^1.5.4": - version "1.5.4" - resolved "https://registry.yarnpkg.com/@floating-ui/dom/-/dom-1.5.4.tgz#28df1e1cb373884224a463235c218dcbd81a16bb" - integrity sha512-jByEsHIY+eEdCjnTVu+E3ephzTOzkQ8hgUfGwos+bg7NlH33Zc5uO+QHz1mrQUOgIKKDD1RtS201P9NvAfq3XQ== - dependencies: - "@floating-ui/core" "^1.5.3" - "@floating-ui/utils" "^0.2.0" - -"@floating-ui/dom@^1.6.1": +"@floating-ui/dom@^1.0.1", "@floating-ui/dom@^1.5.4", "@floating-ui/dom@^1.6.1": version "1.6.3" resolved "https://registry.yarnpkg.com/@floating-ui/dom/-/dom-1.6.3.tgz#954e46c1dd3ad48e49db9ada7218b0985cee75ef" integrity sha512-RnDthu3mzPlQ31Ss/BTwQ1zjzIhr3lk1gZB1OC56h/1vEtaXkESrOqL5fQVMfXpwGtRwX+YsZBdyHtJMQnkArw== @@ -2489,7 +2465,7 @@ "@floating-ui/core" "^1.0.0" "@floating-ui/utils" "^0.2.0" -"@floating-ui/react-dom@^2.0.0", "@floating-ui/react-dom@^2.0.2", "@floating-ui/react-dom@^2.0.4": +"@floating-ui/react-dom@^2.0.0", "@floating-ui/react-dom@^2.0.2": version "2.0.5" resolved "https://registry.yarnpkg.com/@floating-ui/react-dom/-/react-dom-2.0.5.tgz#851522899c34e3e2be1e29f3294f150834936e28" integrity sha512-UsBK30Bg+s6+nsgblXtZmwHhgS2vmbuQK22qgt2pTQM6M3X6H1+cQcLXqgRY3ihVLcZJE6IvqDQozhsnIVqK/Q== @@ -2584,9 +2560,9 @@ integrity sha512-ehgllV/xU8PC+yVyEUtTzhiSQKsr7k5Jz74B6dtCaVJz7/Vo7JiaACsCLvD7/iATlJUAEqvBson0OHewD3JDzQ== "@graphprotocol/graph-cli@^0.68.3": - version "0.68.3" - resolved "https://registry.yarnpkg.com/@graphprotocol/graph-cli/-/graph-cli-0.68.3.tgz#1a7b3d8da34b00f3c3f56d36c11abf584524df31" - integrity sha512-0WMiS7DpxanADLhcj8mhfugx5r4WL8RL46eQ35GcaSfr3H61Pz5RtusEayxfrhtXnzNcFcbnE4fr5TrR9R0iaQ== + version "0.68.5" + resolved "https://registry.yarnpkg.com/@graphprotocol/graph-cli/-/graph-cli-0.68.5.tgz#58cf65d15f41f1a30defe1cd50474d7c1205dd6a" + integrity sha512-3GY2pYr5LksO6JY6s5nvePnSKVdtzDEn1CUGezyjCMR1uq9YIXMPXKqcnrzCX/DLugioEabiEi2+yOg9+rnFDQ== dependencies: "@float-capital/float-subgraph-uncrashable" "^0.0.0-alpha.4" "@oclif/core" "2.8.6" @@ -2849,9 +2825,9 @@ "@hapi/hoek" "^9.0.0" "@hcaptcha/loader@^1.2.1": - version "1.2.2" - resolved "https://registry.yarnpkg.com/@hcaptcha/loader/-/loader-1.2.2.tgz#5451c449e95960f833433edd3409f8e5b805525c" - integrity sha512-TNMNj/H87TB5P4/Y4nUFi7ARWt5cLML+UN0sm+OYV4DWUQdZ2tGAf3oIILdXz/31+ITX2XYWbiApavxN6Yz/4A== + version "1.2.3" + resolved "https://registry.yarnpkg.com/@hcaptcha/loader/-/loader-1.2.3.tgz#d2a7d0925d13009dc18b13dd4e46c399aa24e253" + integrity sha512-tJVkPpvWZ9kIXMAFeH/7B8iIDNN+kpFLBVgtJQgtWa1cAN0bUve2VPuc/UTVa0NUh7tsmd6oAg/C0VUplMt2UQ== "@hcaptcha/react-hcaptcha@^1.10.1": version "1.10.1" @@ -2861,58 +2837,39 @@ "@babel/runtime" "^7.17.9" "@hcaptcha/loader" "^1.2.1" -"@humanwhocodes/config-array@^0.11.13": - version "0.11.13" - resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.11.13.tgz#075dc9684f40a531d9b26b0822153c1e832ee297" - integrity sha512-JSBDMiDKSzQVngfRjOdFXgFfklaXI4K9nLF49Auh21lmBWRLIK3+xTErTWD4KU54pb6coM6ESE7Awz/FNU3zgQ== +"@humanwhocodes/config-array@^0.11.14": + version "0.11.14" + resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.11.14.tgz#d78e481a039f7566ecc9660b4ea7fe6b1fec442b" + integrity sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg== dependencies: - "@humanwhocodes/object-schema" "^2.0.1" - debug "^4.1.1" + "@humanwhocodes/object-schema" "^2.0.2" + debug "^4.3.1" minimatch "^3.0.5" "@humanwhocodes/module-importer@^1.0.1": version "1.0.1" resolved "https://registry.yarnpkg.com/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz#af5b2691a22b44be847b0ca81641c5fb6ad0172c" - integrity sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA== - -"@humanwhocodes/object-schema@^2.0.1": - version "2.0.1" - resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-2.0.1.tgz#e5211452df060fa8522b55c7b3c0c4d1981cb044" - integrity sha512-dvuCeX5fC9dXgJn9t+X5atfmgQAzUOWqS1254Gh0m6i8wKd10ebXkfNKiRK+1GWi/yTvvLDHpoxLr0xxxeslWw== - -"@internationalized/date@^3.5.0": - version "3.5.1" - resolved "https://registry.yarnpkg.com/@internationalized/date/-/date-3.5.1.tgz#14401139f70c1ef14b845d3cac8912e82e82adcc" - integrity sha512-LUQIfwU9e+Fmutc/DpRTGXSdgYZLBegi4wygCWDSVmUdLTaMHsQyASDiJtREwanwKuQLq0hY76fCJ9J/9I2xOQ== - dependencies: - "@swc/helpers" "^0.5.0" + integrity sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA== + +"@humanwhocodes/object-schema@^2.0.2": + version "2.0.2" + resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-2.0.2.tgz#d9fae00a2d5cb40f92cfe64b47ad749fbc38f917" + integrity sha512-6EwiSjwWYP7pTckG6I5eyFANjPhmPjUX9JRLUSfNPC7FX7zK9gyZAfUEaECL6ALTpGX5AjnBq3C9XmVWPitNpw== -"@internationalized/date@^3.5.2": +"@internationalized/date@^3.5.0", "@internationalized/date@^3.5.2": version "3.5.2" resolved "https://registry.yarnpkg.com/@internationalized/date/-/date-3.5.2.tgz#d760ace32bb47e869b8c607a4a786c8b208aacc2" integrity sha512-vo1yOMUt2hzp63IutEaTUxROdvQg1qlMRsbCvbay2AK2Gai7wIgCyK5weEX3nHkiLgo4qCXHijFNC/ILhlRpOQ== dependencies: "@swc/helpers" "^0.5.0" -"@internationalized/number@^3.3.0": - version "3.5.0" - resolved "https://registry.yarnpkg.com/@internationalized/number/-/number-3.5.0.tgz#9de6018424b441a6545f209afa286ad7df4a2906" - integrity sha512-ZY1BW8HT9WKYvaubbuqXbbDdHhOUMfE2zHHFJeTppid0S+pc8HtdIxFxaYMsGjCb4UsF+MEJ4n2TfU7iHnUK8w== - dependencies: - "@swc/helpers" "^0.5.0" - -"@internationalized/number@^3.5.1": +"@internationalized/number@^3.3.0", "@internationalized/number@^3.5.1": version "3.5.1" resolved "https://registry.yarnpkg.com/@internationalized/number/-/number-3.5.1.tgz#8e3359b498aec6bb865be668ef7e794a424067a7" integrity sha512-N0fPU/nz15SwR9IbfJ5xaS9Ss/O5h1sVXMZf43vc9mxEG48ovglvvzBjF53aHlq20uoR6c+88CrIXipU/LSzwg== dependencies: "@swc/helpers" "^0.5.0" -"@ioredis/commands@^1.1.1": - version "1.2.0" - resolved "https://registry.yarnpkg.com/@ioredis/commands/-/commands-1.2.0.tgz#6d61b3097470af1fdbbe622795b8921d42018e11" - integrity sha512-Sx1pU8EM64o2BrqNpEO1CNLtKQwyhuXuqyfH7oGKCk+1a33d2r5saW8zNwm3j6BTExtjrv2BxTgzzkMwts6vGg== - "@ipld/car@^3.0.1", "@ipld/car@^3.2.3": version "3.2.4" resolved "https://registry.yarnpkg.com/@ipld/car/-/car-3.2.4.tgz#115951ba2255ec51d865773a074e422c169fb01c" @@ -2976,7 +2933,7 @@ js-yaml "^3.13.1" resolve-from "^5.0.0" -"@istanbuljs/schema@^0.1.2": +"@istanbuljs/schema@^0.1.2", "@istanbuljs/schema@^0.1.3": version "0.1.3" resolved "https://registry.yarnpkg.com/@istanbuljs/schema/-/schema-0.1.3.tgz#e45e384e4b8ec16bce2fd903af78450f6bf7ec98" integrity sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA== @@ -3173,32 +3130,32 @@ "@types/yargs" "^17.0.8" chalk "^4.0.0" -"@jridgewell/gen-mapping@^0.3.0", "@jridgewell/gen-mapping@^0.3.2": - version "0.3.3" - resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz#7e02e6eb5df901aaedb08514203b096614024098" - integrity sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ== +"@jridgewell/gen-mapping@^0.3.5": + version "0.3.5" + resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz#dcce6aff74bdf6dad1a95802b69b04a2fcb1fb36" + integrity sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg== dependencies: - "@jridgewell/set-array" "^1.0.1" + "@jridgewell/set-array" "^1.2.1" "@jridgewell/sourcemap-codec" "^1.4.10" - "@jridgewell/trace-mapping" "^0.3.9" + "@jridgewell/trace-mapping" "^0.3.24" "@jridgewell/resolve-uri@^3.0.3", "@jridgewell/resolve-uri@^3.1.0": - version "3.1.1" - resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz#c08679063f279615a3326583ba3a90d1d82cc721" - integrity sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA== + version "3.1.2" + resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz#7a0ee601f60f99a20c7c7c5ff0c80388c1189bd6" + integrity sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw== -"@jridgewell/set-array@^1.0.1": - version "1.1.2" - resolved "https://registry.yarnpkg.com/@jridgewell/set-array/-/set-array-1.1.2.tgz#7c6cf998d6d20b914c0a55a91ae928ff25965e72" - integrity sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw== +"@jridgewell/set-array@^1.2.1": + version "1.2.1" + resolved "https://registry.yarnpkg.com/@jridgewell/set-array/-/set-array-1.2.1.tgz#558fb6472ed16a4c850b889530e6b36438c49280" + integrity sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A== "@jridgewell/source-map@^0.3.3": - version "0.3.5" - resolved "https://registry.yarnpkg.com/@jridgewell/source-map/-/source-map-0.3.5.tgz#a3bb4d5c6825aab0d281268f47f6ad5853431e91" - integrity sha512-UTYAUj/wviwdsMfzoSJspJxbkH5o1snzwX0//0ENX1u/55kkZZkcTZP6u9bwKGkv+dkk9at4m1Cpt0uY80kcpQ== + version "0.3.6" + resolved "https://registry.yarnpkg.com/@jridgewell/source-map/-/source-map-0.3.6.tgz#9d71ca886e32502eb9362c9a74a46787c36df81a" + integrity sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ== dependencies: - "@jridgewell/gen-mapping" "^0.3.0" - "@jridgewell/trace-mapping" "^0.3.9" + "@jridgewell/gen-mapping" "^0.3.5" + "@jridgewell/trace-mapping" "^0.3.25" "@jridgewell/sourcemap-codec@^1.4.10", "@jridgewell/sourcemap-codec@^1.4.13", "@jridgewell/sourcemap-codec@^1.4.14", "@jridgewell/sourcemap-codec@^1.4.15": version "1.4.15" @@ -3213,10 +3170,10 @@ "@jridgewell/resolve-uri" "^3.0.3" "@jridgewell/sourcemap-codec" "^1.4.10" -"@jridgewell/trace-mapping@^0.3.12", "@jridgewell/trace-mapping@^0.3.17", "@jridgewell/trace-mapping@^0.3.18", "@jridgewell/trace-mapping@^0.3.20", "@jridgewell/trace-mapping@^0.3.9": - version "0.3.20" - resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.20.tgz#72e45707cf240fa6b081d0366f8265b0cd10197f" - integrity sha512-R8LcPeWZol2zR8mmH3JeKQ6QRCFb7XgUhV9ZlGhHLGyg4wpPiPZNQOOWhFZhxKw8u//yTbNGI42Bx/3paXEQ+Q== +"@jridgewell/trace-mapping@^0.3.12", "@jridgewell/trace-mapping@^0.3.18", "@jridgewell/trace-mapping@^0.3.20", "@jridgewell/trace-mapping@^0.3.24", "@jridgewell/trace-mapping@^0.3.25": + version "0.3.25" + resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz#15f190e98895f3fc23276ee14bc76b675c2e50f0" + integrity sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ== dependencies: "@jridgewell/resolve-uri" "^3.1.0" "@jridgewell/sourcemap-codec" "^1.4.14" @@ -3250,9 +3207,9 @@ integrity sha512-mDJsOucVW8m3Lk2fdQst+P74SgiKebvq1iBk4sXLbADQOwhL9bWGaArvO+tW7jPJZwEfSPWBdHcHoYi11XAwZw== "@lezer/common@^1.0.0", "@lezer/common@^1.1.0", "@lezer/common@^1.2.0": - version "1.2.0" - resolved "https://registry.yarnpkg.com/@lezer/common/-/common-1.2.0.tgz#f10493d12c4a196a02ff5fcf5695a516a4039aae" - integrity sha512-Wmvlm4q6tRpwiy20TnB3yyLTZim38Tkc50dPY8biQRwqE+ati/wD84rm3N15hikvdT4uSg9phs9ubjvcLmkpKg== + version "1.2.1" + resolved "https://registry.yarnpkg.com/@lezer/common/-/common-1.2.1.tgz#198b278b7869668e1bebbe687586e12a42731049" + integrity sha512-yemX0ZD2xS/73llMZIK6KplkjIjf2EvAHcinDi/TfJ9hS25G0388+ClHt6/3but0oOxinTcQHJLDXh6w1crzFQ== "@lezer/highlight@^1.0.0": version "1.2.0" @@ -3271,16 +3228,16 @@ "@lezer/lr" "^1.0.0" "@lezer/lr@^1.0.0": - version "1.3.14" - resolved "https://registry.yarnpkg.com/@lezer/lr/-/lr-1.3.14.tgz#59d4a3b25698bdac0ef182fa6eadab445fc4f29a" - integrity sha512-z5mY4LStlA3yL7aHT/rqgG614cfcvklS+8oFRFBYrs4YaWLJyKKM4+nN6KopToX0o9Hj6zmH6M5kinOYuy06ug== + version "1.4.0" + resolved "https://registry.yarnpkg.com/@lezer/lr/-/lr-1.4.0.tgz#ed52a75dbbfbb0d1eb63710ea84c35ee647cb67e" + integrity sha512-Wst46p51km8gH0ZUmeNrtpRYmdlRHUpN1DQd3GFAyKANi8WVz8c2jHYTf1CVScFaCjQw1iO3ZZdqGDxQPRErTg== dependencies: "@lezer/common" "^1.0.0" "@lit-labs/ssr-dom-shim@^1.0.0", "@lit-labs/ssr-dom-shim@^1.1.0": - version "1.1.2" - resolved "https://registry.yarnpkg.com/@lit-labs/ssr-dom-shim/-/ssr-dom-shim-1.1.2.tgz#d693d972974a354034454ec1317eb6afd0b00312" - integrity sha512-jnOD+/+dSrfTWYfSXBXlo5l5f0q1UuJo3tkbMDCYA2lKUYq79jaxqtGEvnRoh049nt1vdo1+45RinipU6FGY2g== + version "1.2.0" + resolved "https://registry.yarnpkg.com/@lit-labs/ssr-dom-shim/-/ssr-dom-shim-1.2.0.tgz#353ce4a76c83fadec272ea5674ede767650762fd" + integrity sha512-yWJKmpGE6lUURKAaIltoPIE/wrbY3TEkqQt+X0m+7fQNnAv0keydnYvbiJFP1PnMhizmIWRWOG5KLhYyc/xl+g== "@lit/reactive-element@^1.3.0", "@lit/reactive-element@^1.6.0": version "1.6.3" @@ -3335,43 +3292,48 @@ semver "^7.3.8" superstruct "^1.0.3" -"@motionone/animation@^10.15.1", "@motionone/animation@^10.16.3": - version "10.16.3" - resolved "https://registry.yarnpkg.com/@motionone/animation/-/animation-10.16.3.tgz#f5b71e27fd8b88b61f983adb0ed6c8e3e89281f9" - integrity sha512-QUGWpLbMFLhyqKlngjZhjtxM8IqiJQjLK0DF+XOF6od9nhSvlaeEpOY/UMCRVcZn/9Tr2rZO22EkuCIjYdI74g== +"@microsoft/tsdoc@^0.14.2": + version "0.14.2" + resolved "https://registry.yarnpkg.com/@microsoft/tsdoc/-/tsdoc-0.14.2.tgz#c3ec604a0b54b9a9b87e9735dfc59e1a5da6a5fb" + integrity sha512-9b8mPpKrfeGRuhFH5iO1iwCLeIIsV6+H1sRfxbkoGXIyQE2BTsPd9zqSqQJ+pv5sJ/hT5M1zvOFL02MnEezFug== + +"@motionone/animation@^10.15.1", "@motionone/animation@^10.17.0": + version "10.17.0" + resolved "https://registry.yarnpkg.com/@motionone/animation/-/animation-10.17.0.tgz#7633c6f684b5fee2b61c405881b8c24662c68fca" + integrity sha512-ANfIN9+iq1kGgsZxs+Nz96uiNcPLGTXwfNo2Xz/fcJXniPYpaz/Uyrfa+7I5BPLxCP82sh7quVDudf1GABqHbg== dependencies: - "@motionone/easing" "^10.16.3" - "@motionone/types" "^10.16.3" - "@motionone/utils" "^10.16.3" + "@motionone/easing" "^10.17.0" + "@motionone/types" "^10.17.0" + "@motionone/utils" "^10.17.0" tslib "^2.3.1" "@motionone/dom@^10.16.2", "@motionone/dom@^10.16.4": - version "10.16.4" - resolved "https://registry.yarnpkg.com/@motionone/dom/-/dom-10.16.4.tgz#9385716928cc2d5b3208a7dcaf504b69b47fd1ae" - integrity sha512-HPHlVo/030qpRj9R8fgY50KTN4Ko30moWRTA3L3imrsRBmob93cTYmodln49HYFbQm01lFF7X523OkKY0DX6UA== - dependencies: - "@motionone/animation" "^10.16.3" - "@motionone/generators" "^10.16.4" - "@motionone/types" "^10.16.3" - "@motionone/utils" "^10.16.3" + version "10.17.0" + resolved "https://registry.yarnpkg.com/@motionone/dom/-/dom-10.17.0.tgz#519dd78aab0750a94614c69a82da5290cd617383" + integrity sha512-cMm33swRlCX/qOPHWGbIlCl0K9Uwi6X5RiL8Ma6OrlJ/TP7Q+Np5GE4xcZkFptysFjMTi4zcZzpnNQGQ5D6M0Q== + dependencies: + "@motionone/animation" "^10.17.0" + "@motionone/generators" "^10.17.0" + "@motionone/types" "^10.17.0" + "@motionone/utils" "^10.17.0" hey-listen "^1.0.8" tslib "^2.3.1" -"@motionone/easing@^10.16.3": - version "10.16.3" - resolved "https://registry.yarnpkg.com/@motionone/easing/-/easing-10.16.3.tgz#a62abe0ba2841861f167f286782e287eab8d7466" - integrity sha512-HWTMZbTmZojzwEuKT/xCdvoMPXjYSyQvuVM6jmM0yoGU6BWzsmYMeB4bn38UFf618fJCNtP9XeC/zxtKWfbr0w== +"@motionone/easing@^10.17.0": + version "10.17.0" + resolved "https://registry.yarnpkg.com/@motionone/easing/-/easing-10.17.0.tgz#d66cecf7e3ee30104ad00389fb3f0b2282d81aa9" + integrity sha512-Bxe2wSuLu/qxqW4rBFS5m9tMLOw+QBh8v5A7Z5k4Ul4sTj5jAOfZG5R0bn5ywmk+Fs92Ij1feZ5pmC4TeXA8Tg== dependencies: - "@motionone/utils" "^10.16.3" + "@motionone/utils" "^10.17.0" tslib "^2.3.1" -"@motionone/generators@^10.16.4": - version "10.16.4" - resolved "https://registry.yarnpkg.com/@motionone/generators/-/generators-10.16.4.tgz#4a38708244bce733bfcebd4a26d19f4bbabd36af" - integrity sha512-geFZ3w0Rm0ZXXpctWsSf3REGywmLLujEjxPYpBR0j+ymYwof0xbV6S5kGqqsDKgyWKVWpUInqQYvQfL6fRbXeg== +"@motionone/generators@^10.17.0": + version "10.17.0" + resolved "https://registry.yarnpkg.com/@motionone/generators/-/generators-10.17.0.tgz#878d292539c41434c13310d5f863a87a94e6e689" + integrity sha512-T6Uo5bDHrZWhIfxG/2Aut7qyWQyJIWehk6OB4qNvr/jwA/SRmixwbd7SOrxZi1z5rH3LIeFFBKK1xHnSbGPZSQ== dependencies: - "@motionone/types" "^10.16.3" - "@motionone/utils" "^10.16.3" + "@motionone/types" "^10.17.0" + "@motionone/utils" "^10.17.0" tslib "^2.3.1" "@motionone/svelte@^10.16.2": @@ -3382,17 +3344,17 @@ "@motionone/dom" "^10.16.4" tslib "^2.3.1" -"@motionone/types@^10.15.1", "@motionone/types@^10.16.3": - version "10.16.3" - resolved "https://registry.yarnpkg.com/@motionone/types/-/types-10.16.3.tgz#9284ea8a52f6b32c51c54b617214f20e43ac6c59" - integrity sha512-W4jkEGFifDq73DlaZs3HUfamV2t1wM35zN/zX7Q79LfZ2sc6C0R1baUHZmqc/K5F3vSw3PavgQ6HyHLd/MXcWg== +"@motionone/types@^10.15.1", "@motionone/types@^10.17.0": + version "10.17.0" + resolved "https://registry.yarnpkg.com/@motionone/types/-/types-10.17.0.tgz#179571ce98851bac78e19a1c3974767227f08ba3" + integrity sha512-EgeeqOZVdRUTEHq95Z3t8Rsirc7chN5xFAPMYFobx8TPubkEfRSm5xihmMUkbaR2ErKJTUw3347QDPTHIW12IA== -"@motionone/utils@^10.15.1", "@motionone/utils@^10.16.3": - version "10.16.3" - resolved "https://registry.yarnpkg.com/@motionone/utils/-/utils-10.16.3.tgz#ddf07ab6cf3000d89e3bcbdc9a8c3e1fd64f8520" - integrity sha512-WNWDksJIxQkaI9p9Z9z0+K27xdqISGNFy1SsWVGaiedTHq0iaT6iZujby8fT/ZnZxj1EOaxJtSfUPCFNU5CRoA== +"@motionone/utils@^10.15.1", "@motionone/utils@^10.17.0": + version "10.17.0" + resolved "https://registry.yarnpkg.com/@motionone/utils/-/utils-10.17.0.tgz#cc0ba8acdc6848ff48d8c1f2d0d3e7602f4f942e" + integrity sha512-bGwrki4896apMWIj9yp5rAS2m0xyhxblg6gTB/leWDPt+pb410W8lYWsxyurX+DH+gO1zsQsfx2su/c1/LtTpg== dependencies: - "@motionone/types" "^10.16.3" + "@motionone/types" "^10.17.0" hey-listen "^1.0.8" tslib "^2.3.1" @@ -3404,106 +3366,106 @@ "@motionone/dom" "^10.16.4" tslib "^2.3.1" -"@mui/base@5.0.0-beta.30", "@mui/base@^5.0.0-beta.22": - version "5.0.0-beta.30" - resolved "https://registry.yarnpkg.com/@mui/base/-/base-5.0.0-beta.30.tgz#8feca6b70f2b9cd4d5cb97799ae9fcb5376c7f83" - integrity sha512-dc38W4W3K42atE9nSaOeoJ7/x9wGIfawdwC/UmMxMLlZ1iSsITQ8dQJaTATCbn98YvYPINK/EH541YA5enQIPQ== +"@mui/base@5.0.0-beta.40", "@mui/base@^5.0.0-beta.22": + version "5.0.0-beta.40" + resolved "https://registry.yarnpkg.com/@mui/base/-/base-5.0.0-beta.40.tgz#1f8a782f1fbf3f84a961e954c8176b187de3dae2" + integrity sha512-I/lGHztkCzvwlXpjD2+SNmvNQvB4227xBXhISPjEaJUXGImOQ9f3D2Yj/T3KasSI/h0MLWy74X0J6clhPmsRbQ== dependencies: - "@babel/runtime" "^7.23.6" - "@floating-ui/react-dom" "^2.0.4" - "@mui/types" "^7.2.12" - "@mui/utils" "^5.15.3" + "@babel/runtime" "^7.23.9" + "@floating-ui/react-dom" "^2.0.8" + "@mui/types" "^7.2.14" + "@mui/utils" "^5.15.14" "@popperjs/core" "^2.11.8" - clsx "^2.0.0" + clsx "^2.1.0" prop-types "^15.8.1" -"@mui/core-downloads-tracker@^5.15.3": - version "5.15.3" - resolved "https://registry.yarnpkg.com/@mui/core-downloads-tracker/-/core-downloads-tracker-5.15.3.tgz#40fc854d7cf5505a182a4e121149dfe21cd277ef" - integrity sha512-sWeihiVyxdJjpLkp8SHkTy9kt2M/o11M60G1MzwljGL2BXdM3Ktzqv5QaQHdi00y7Y1ulvtI3GOSxP2xU8mQJw== +"@mui/core-downloads-tracker@^5.15.14": + version "5.15.14" + resolved "https://registry.yarnpkg.com/@mui/core-downloads-tracker/-/core-downloads-tracker-5.15.14.tgz#f7c57b261904831877220182303761c012d05046" + integrity sha512-on75VMd0XqZfaQW+9pGjSNiqW+ghc5E2ZSLRBXwcXl/C4YzjfyjrLPhrEpKnR9Uym9KXBvxrhoHfPcczYHweyA== "@mui/icons-material@^5.15.11": - version "5.15.11" - resolved "https://registry.yarnpkg.com/@mui/icons-material/-/icons-material-5.15.11.tgz#263a89ba1b55667890fe0da3f5ca753647a0945b" - integrity sha512-R5ZoQqnKpd+5Ew7mBygTFLxgYsQHPhgR3TDXSgIHYIjGzYuyPLmGLSdcPUoMdi6kxiYqHlpPj4NJxlbaFD0UHA== + version "5.15.14" + resolved "https://registry.yarnpkg.com/@mui/icons-material/-/icons-material-5.15.14.tgz#333468c94988d96203946d1cfeb8f4d7e8e7de34" + integrity sha512-vj/51k7MdFmt+XVw94sl30SCvGx6+wJLsNYjZRgxhS6y3UtnWnypMOsm3Kmg8TN+P0dqwsjy4/fX7B1HufJIhw== dependencies: "@babel/runtime" "^7.23.9" "@mui/lab@^5.0.0-alpha.141": - version "5.0.0-alpha.159" - resolved "https://registry.yarnpkg.com/@mui/lab/-/lab-5.0.0-alpha.159.tgz#d2b97b3e6b1b51d11d85f46096618490fe6d4e67" - integrity sha512-42Y8nf2/mDgYSLOw6PhOfHNV6P7tPcQkQEL0DTbY7a+gc+hXDsyVEzBMYST1MrV64EHTH68msfQm+k3CvLON/g== - dependencies: - "@babel/runtime" "^7.23.6" - "@mui/base" "5.0.0-beta.30" - "@mui/system" "^5.15.3" - "@mui/types" "^7.2.12" - "@mui/utils" "^5.15.3" - clsx "^2.0.0" + version "5.0.0-alpha.169" + resolved "https://registry.yarnpkg.com/@mui/lab/-/lab-5.0.0-alpha.169.tgz#85b88b2f06ad78c586cde2b47970653e5fd895eb" + integrity sha512-h6xe1K6ISKUbyxTDgdvql4qoDP6+q8ad5fg9nXQxGLUrIeT2jVrBuT/jRECSTufbnhzP+V5kulvYxaMfM8rEdA== + dependencies: + "@babel/runtime" "^7.23.9" + "@mui/base" "5.0.0-beta.40" + "@mui/system" "^5.15.14" + "@mui/types" "^7.2.14" + "@mui/utils" "^5.15.14" + clsx "^2.1.0" prop-types "^15.8.1" "@mui/material@^5.14.14": - version "5.15.3" - resolved "https://registry.yarnpkg.com/@mui/material/-/material-5.15.3.tgz#b77f1ac1275e5bf13b735e8224bdd301aab918c4" - integrity sha512-DODBBMouyq1B5f3YkEWL9vO8pGCxuEGqtfpltF6peMJzz/78tJFyLQsDas9MNLC/8AdFu2BQdkK7wox5UBPTAA== - dependencies: - "@babel/runtime" "^7.23.6" - "@mui/base" "5.0.0-beta.30" - "@mui/core-downloads-tracker" "^5.15.3" - "@mui/system" "^5.15.3" - "@mui/types" "^7.2.12" - "@mui/utils" "^5.15.3" + version "5.15.14" + resolved "https://registry.yarnpkg.com/@mui/material/-/material-5.15.14.tgz#a40bd5eccfa9fc925535e1f4d70c6cef77fa3a75" + integrity sha512-kEbRw6fASdQ1SQ7LVdWR5OlWV3y7Y54ZxkLzd6LV5tmz+NpO3MJKZXSfgR0LHMP7meKsPiMm4AuzV0pXDpk/BQ== + dependencies: + "@babel/runtime" "^7.23.9" + "@mui/base" "5.0.0-beta.40" + "@mui/core-downloads-tracker" "^5.15.14" + "@mui/system" "^5.15.14" + "@mui/types" "^7.2.14" + "@mui/utils" "^5.15.14" "@types/react-transition-group" "^4.4.10" - clsx "^2.0.0" - csstype "^3.1.2" + clsx "^2.1.0" + csstype "^3.1.3" prop-types "^15.8.1" react-is "^18.2.0" react-transition-group "^4.4.5" -"@mui/private-theming@^5.15.3": - version "5.15.3" - resolved "https://registry.yarnpkg.com/@mui/private-theming/-/private-theming-5.15.3.tgz#2db0177d847dc6b28721d93308ed05d434a77c53" - integrity sha512-Q79MhVMmywC1l5bMsMZq5PsIudr1MNPJnx9/EqdMP0vpz5iNvFpnLmxsD7d8/hqTWgFAljI+LH3jX8MxlZH9Gw== +"@mui/private-theming@^5.15.14": + version "5.15.14" + resolved "https://registry.yarnpkg.com/@mui/private-theming/-/private-theming-5.15.14.tgz#edd9a82948ed01586a01c842eb89f0e3f68970ee" + integrity sha512-UH0EiZckOWcxiXLX3Jbb0K7rC8mxTr9L9l6QhOZxYc4r8FHUkefltV9VDGLrzCaWh30SQiJvAEd7djX3XXY6Xw== dependencies: - "@babel/runtime" "^7.23.6" - "@mui/utils" "^5.15.3" + "@babel/runtime" "^7.23.9" + "@mui/utils" "^5.15.14" prop-types "^15.8.1" -"@mui/styled-engine@^5.15.3": - version "5.15.3" - resolved "https://registry.yarnpkg.com/@mui/styled-engine/-/styled-engine-5.15.3.tgz#85cb294d701b1a3f197bfc90e87ec0685a0943b2" - integrity sha512-+d5XZCTeemOO/vBfWGEeHgTm8fjU1Psdgm+xAw+uegycO2EnoA/EfGSaG5UwZ6g3b66y48Mkxi35AggShMr88w== +"@mui/styled-engine@^5.15.14": + version "5.15.14" + resolved "https://registry.yarnpkg.com/@mui/styled-engine/-/styled-engine-5.15.14.tgz#168b154c4327fa4ccc1933a498331d53f61c0de2" + integrity sha512-RILkuVD8gY6PvjZjqnWhz8fu68dVkqhM5+jYWfB5yhlSQKg+2rHkmEwm75XIeAqI3qwOndK6zELK5H6Zxn4NHw== dependencies: - "@babel/runtime" "^7.23.6" + "@babel/runtime" "^7.23.9" "@emotion/cache" "^11.11.0" - csstype "^3.1.2" + csstype "^3.1.3" prop-types "^15.8.1" -"@mui/system@^5.15.3": - version "5.15.3" - resolved "https://registry.yarnpkg.com/@mui/system/-/system-5.15.3.tgz#062d0d6b5259c3dc0e1d4026b85ffcc3acf8637b" - integrity sha512-ewVU4eRgo4VfNMGpO61cKlfWmH7l9s6rA8EknRzuMX3DbSLfmtW2WJJg6qPwragvpPIir0Pp/AdWVSDhyNy5Tw== +"@mui/system@^5.15.14": + version "5.15.14" + resolved "https://registry.yarnpkg.com/@mui/system/-/system-5.15.14.tgz#8a0c6571077eeb6b5f1ff7aa7ff6a3dc4a14200d" + integrity sha512-auXLXzUaCSSOLqJXmsAaq7P96VPRXg2Rrz6OHNV7lr+kB8lobUF+/N84Vd9C4G/wvCXYPs5TYuuGBRhcGbiBGg== dependencies: - "@babel/runtime" "^7.23.6" - "@mui/private-theming" "^5.15.3" - "@mui/styled-engine" "^5.15.3" - "@mui/types" "^7.2.12" - "@mui/utils" "^5.15.3" - clsx "^2.0.0" - csstype "^3.1.2" + "@babel/runtime" "^7.23.9" + "@mui/private-theming" "^5.15.14" + "@mui/styled-engine" "^5.15.14" + "@mui/types" "^7.2.14" + "@mui/utils" "^5.15.14" + clsx "^2.1.0" + csstype "^3.1.3" prop-types "^15.8.1" -"@mui/types@^7.2.12": - version "7.2.12" - resolved "https://registry.yarnpkg.com/@mui/types/-/types-7.2.12.tgz#602acbb5aa3eb56a31f569a19f87f75d33de5c01" - integrity sha512-3kaHiNm9khCAo0pVe0RenketDSFoZGAlVZ4zDjB/QNZV0XiCj+sh1zkX0VVhQPgYJDlBEzAag+MHJ1tU3vf0Zw== +"@mui/types@^7.2.14": + version "7.2.14" + resolved "https://registry.yarnpkg.com/@mui/types/-/types-7.2.14.tgz#8a02ac129b70f3d82f2f9b76ded2c8d48e3fc8c9" + integrity sha512-MZsBZ4q4HfzBsywtXgM1Ksj6HDThtiwmOKUXH1pKYISI9gAVXCNHNpo7TlGoGrBaYWZTdNoirIN7JsQcQUjmQQ== -"@mui/utils@^5.14.16", "@mui/utils@^5.15.3": - version "5.15.3" - resolved "https://registry.yarnpkg.com/@mui/utils/-/utils-5.15.3.tgz#421043be5279d31ca9b221a6398feb7c9d61209b" - integrity sha512-mT3LiSt9tZWCdx1pl7q4Q5tNo6gdZbvJel286ZHGuj6LQQXjWNAh8qiF9d+LogvNUI+D7eLkTnj605d1zoazfg== +"@mui/utils@^5.14.16", "@mui/utils@^5.15.14": + version "5.15.14" + resolved "https://registry.yarnpkg.com/@mui/utils/-/utils-5.15.14.tgz#e414d7efd5db00bfdc875273a40c0a89112ade3a" + integrity sha512-0lF/7Hh/ezDv5X7Pry6enMsbYyGKjADzvHyo3Qrc/SSlTsQ1VkbDMbH0m2t3OR5iIVLwMoxwM7yGd+6FCMtTFA== dependencies: - "@babel/runtime" "^7.23.6" + "@babel/runtime" "^7.23.9" "@types/prop-types" "^15.7.11" prop-types "^15.8.1" react-is "^18.2.0" @@ -3563,28 +3525,28 @@ webpack-node-externals "3.0.0" "@nestjs/common@^10.2.7": - version "10.3.0" - resolved "https://registry.yarnpkg.com/@nestjs/common/-/common-10.3.0.tgz#d78f0ff2062d1d53c79c170a79c12a1548e2e598" - integrity sha512-DGv34UHsZBxCM3H5QGE2XE/+oLJzz5+714JQjBhjD9VccFlQs3LRxo/epso4l7nJIiNlZkPyIUC8WzfU/5RTsQ== + version "10.3.4" + resolved "https://registry.yarnpkg.com/@nestjs/common/-/common-10.3.4.tgz#d02ea0e34a38809ad42223856c008e6329e37d81" + integrity sha512-HmehujZhUZjf9TN2o0TyzWYNwEgyRYqZZ5qIcF/mCgIUZ4olIKlazna0kGK56FGlCvviHWNKQM5eTuVeTstIgA== dependencies: uid "2.0.2" iterare "1.2.1" tslib "2.6.2" "@nestjs/config@^3.1.1": - version "3.1.1" - resolved "https://registry.yarnpkg.com/@nestjs/config/-/config-3.1.1.tgz#51e23ed84debd08afb86acf7b92bc4bf341797da" - integrity sha512-qu5QlNiJdqQtOsnB6lx4JCXPQ96jkKUsOGd+JXfXwqJqZcOSAq6heNFg0opW4pq4J/VZoNwoo87TNnx9wthnqQ== + version "3.2.0" + resolved "https://registry.yarnpkg.com/@nestjs/config/-/config-3.2.0.tgz#4ca70f88b0636f86741ba192dd51dd2f01eaa7c1" + integrity sha512-BpYRn57shg7CH35KGT6h+hT7ZucB6Qn2B3NBNdvhD4ApU8huS5pX/Wc2e/aO5trIha606Bz2a9t9/vbiuTBTww== dependencies: - dotenv "16.3.1" + dotenv "16.4.1" dotenv-expand "10.0.0" lodash "4.17.21" - uuid "9.0.0" + uuid "9.0.1" "@nestjs/core@^10.2.8": - version "10.3.0" - resolved "https://registry.yarnpkg.com/@nestjs/core/-/core-10.3.0.tgz#d5c6b26d6d9280664910d5481153d25c5da4ec00" - integrity sha512-N06P5ncknW/Pm8bj964WvLIZn2gNhHliCBoAO1LeBvNImYkecqKcrmLbY49Fa1rmMfEM3MuBHeDys3edeuYAOA== + version "10.3.4" + resolved "https://registry.yarnpkg.com/@nestjs/core/-/core-10.3.4.tgz#ec72f386ffe2f18f8a4c5a8cc5c256c774ab9ee2" + integrity sha512-rF0yebuHmMj+9/CkbjPWWMvlF5x8j5Biw2DRvbl8R8n2X3OdFBN+06x/9xm3/ZssR5tLoB9tsYspFUb+SvnnwA== dependencies: uid "2.0.2" "@nuxtjs/opencollective" "0.3.2" @@ -3601,10 +3563,10 @@ "@types/jsonwebtoken" "9.0.5" jsonwebtoken "9.0.2" -"@nestjs/mapped-types@2.0.4": - version "2.0.4" - resolved "https://registry.yarnpkg.com/@nestjs/mapped-types/-/mapped-types-2.0.4.tgz#97280b06bf85d34ea9bad1e847e5e3cbaca8f04f" - integrity sha512-xl+gUSp0B+ln1VSNoUftlglk8dfpUes3DHGxKZ5knuBxS5g2H/8p9/DSBOYWUfO5f4u9s6ffBPZ71WO+tbe5SA== +"@nestjs/mapped-types@2.0.5": + version "2.0.5" + resolved "https://registry.yarnpkg.com/@nestjs/mapped-types/-/mapped-types-2.0.5.tgz#485d6b44e19779c98d04e52bd1d2bcc7001df0ea" + integrity sha512-bSJv4pd6EY99NX9CjBIyn4TVDoSit82DUZlL4I3bqNfy5Gt+gXTa86i3I/i0iIV9P4hntcGM5GyO+FhZAhxtyg== "@nestjs/passport@^10.0.0": version "10.0.3" @@ -3612,13 +3574,13 @@ integrity sha512-znJ9Y4S8ZDVY+j4doWAJ8EuuVO7SkQN3yOBmzxbGaXbvcSwFDAdGJ+OMCg52NdzIO4tQoN4pYKx8W6M0ArfFRQ== "@nestjs/platform-express@^10.2.6": - version "10.3.0" - resolved "https://registry.yarnpkg.com/@nestjs/platform-express/-/platform-express-10.3.0.tgz#ea69b048ef90b78b1001eb1c6b02d9d798f5f3af" - integrity sha512-E4hUW48bYv8OHbP9XQg6deefmXb0pDSSuE38SdhA0mJ37zGY7C5EqqBUdlQk4ttfD+OdnbIgJ1zOokT6dd2d7A== + version "10.3.4" + resolved "https://registry.yarnpkg.com/@nestjs/platform-express/-/platform-express-10.3.4.tgz#5b9988fe32c226569c0bab99f68d7af022091358" + integrity sha512-rzUUUZCGYNs/viT9I6W5izJ1+oYCG0ym/dAn31NmYJW9UchxJdX5PCJqWF8iIbys6JgfbdcapMR5t+L7OZsasQ== dependencies: body-parser "1.20.2" cors "2.8.5" - express "4.18.2" + express "4.18.3" multer "1.4.4-lts.1" tslib "2.6.2" @@ -3648,28 +3610,29 @@ path-to-regexp "0.2.5" "@nestjs/swagger@^7.1.13": - version "7.1.17" - resolved "https://registry.yarnpkg.com/@nestjs/swagger/-/swagger-7.1.17.tgz#3f8d849db8983cf609264270e83418bf5b379085" - integrity sha512-ASCxBrvMEN2o/8vEEmrIPMNzrr/hVi7QIR4y1oNYvoBNXHuwoF1VSI3+4Rq/3xmwVnVveJxHlBIs2u5xY9VgGQ== + version "7.3.0" + resolved "https://registry.yarnpkg.com/@nestjs/swagger/-/swagger-7.3.0.tgz#0b5e397cc5a592422df9afb24c79af928fea5954" + integrity sha512-zLkfKZ+ioYsIZ3dfv7Bj8YHnZMNAGWFUmx2ZDuLp/fBE4P8BSjB7hldzDueFXsmwaPL90v7lgyd82P+s7KME1Q== dependencies: - "@nestjs/mapped-types" "2.0.4" + "@microsoft/tsdoc" "^0.14.2" + "@nestjs/mapped-types" "2.0.5" js-yaml "4.1.0" lodash "4.17.21" path-to-regexp "3.2.0" - swagger-ui-dist "5.10.3" + swagger-ui-dist "5.11.2" "@nestjs/terminus@^10.2.1": - version "10.2.1" - resolved "https://registry.yarnpkg.com/@nestjs/terminus/-/terminus-10.2.1.tgz#1bb1cd5532f34c9d80b348412dbe681ceaeb3ad4" - integrity sha512-23abPhotIP4+hrCZ8YkLEOmZ3m7eUYh1QOwdyrNkU9eMz/nc5LpVzy7jFbsNUuvlnT4MPV/7KXfyQTruQkTouw== + version "10.2.3" + resolved "https://registry.yarnpkg.com/@nestjs/terminus/-/terminus-10.2.3.tgz#72c8a66d04df52aeaae807551245480fd7239a75" + integrity sha512-iX7gXtAooePcyQqFt57aDke5MzgdkBeYgF5YsFNNFwOiAFdIQEhfv3PR0G+HlH9F6D7nBCDZt9U87Pks/qHijg== dependencies: boxen "5.1.2" check-disk-space "3.4.0" "@nestjs/testing@^10.3.1": - version "10.3.1" - resolved "https://registry.yarnpkg.com/@nestjs/testing/-/testing-10.3.1.tgz#ea28a7d29122dd3a2df1542842e741a52dd7c474" - integrity sha512-74aSAugWT31jSPnStyRWDXgjHXWO3GYaUfAZ2T7Dml88UGkGy95iwaWgYy7aYM8/xVFKcDYkfL5FAYqZYce/yg== + version "10.3.4" + resolved "https://registry.yarnpkg.com/@nestjs/testing/-/testing-10.3.4.tgz#9b01e33097ae9a5f4f2ebb86a1438329dc845b83" + integrity sha512-g3NQnRUFBcYF+ySkB7INg5RiV7CNfkP5zwaf3NFo0WjhBrfih9f1jMZ/19blLZ4djN/ngulYks2E3lzROAW8RQ== dependencies: tslib "2.6.2" @@ -3681,9 +3644,9 @@ tslib "2.5.3" "@nestjs/typeorm@^10.0.1": - version "10.0.1" - resolved "https://registry.yarnpkg.com/@nestjs/typeorm/-/typeorm-10.0.1.tgz#0b5c36c21a06c274f9d283e73988b887e7f0635b" - integrity sha512-YVFYL7D25VAVp5/G+KLXIgsRfYomA+VaFZBpm2rtwrrBOmkXNrxr7kuI2bBBO/Xy4kKBDe6wbvIVVFeEA7/ngA== + version "10.0.2" + resolved "https://registry.yarnpkg.com/@nestjs/typeorm/-/typeorm-10.0.2.tgz#25e3ec3c9a127b085c06fd7ea25f8690dba145c2" + integrity sha512-H738bJyydK4SQkRCTeh1aFBxoO1E9xdL/HaLGThwrqN95os5mEyAtK7BLADOS+vldP4jDZ2VQPLj4epWwRqCeQ== dependencies: uuid "9.0.1" @@ -3694,13 +3657,6 @@ dependencies: eslint-scope "5.1.1" -"@noble/curves@1.1.0", "@noble/curves@~1.1.0": - version "1.1.0" - resolved "https://registry.yarnpkg.com/@noble/curves/-/curves-1.1.0.tgz#f13fc667c89184bc04cccb9b11e8e7bae27d8c3d" - integrity sha512-091oBExgENk/kGj3AZmtBDMpxQPDtxQABR2B9lb1JbVTs6ytdzZNwvhxQ4MWasRNEzlbEH8jCWFCwhF/Obj5AA== - dependencies: - "@noble/hashes" "1.3.1" - "@noble/curves@1.2.0", "@noble/curves@~1.2.0": version "1.2.0" resolved "https://registry.yarnpkg.com/@noble/curves/-/curves-1.2.0.tgz#92d7e12e4e49b23105a2555c6984d41733d65c35" @@ -3708,33 +3664,40 @@ dependencies: "@noble/hashes" "1.3.2" -"@noble/curves@^1.2.0": +"@noble/curves@1.3.0", "@noble/curves@~1.3.0": version "1.3.0" resolved "https://registry.yarnpkg.com/@noble/curves/-/curves-1.3.0.tgz#01be46da4fd195822dab821e72f71bf4aeec635e" integrity sha512-t01iSXPuN+Eqzb4eBX0S5oubSqXbK/xXa1Ne18Hj8f9pStxztHCE2gfboSp/dZRLSqfuLpRK2nDXDK+W9puocA== dependencies: "@noble/hashes" "1.3.3" +"@noble/curves@^1.2.0": + version "1.4.0" + resolved "https://registry.yarnpkg.com/@noble/curves/-/curves-1.4.0.tgz#f05771ef64da724997f69ee1261b2417a49522d6" + integrity sha512-p+4cb332SFCrReJkCYe8Xzm0OWi4Jji5jVdIZRL/PmacmDkFNw6MrrV+gGpiPxLHbV+zKFRywUWbaseT+tZRXg== + dependencies: + "@noble/hashes" "1.4.0" + "@noble/hashes@1.2.0", "@noble/hashes@~1.2.0": version "1.2.0" resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.2.0.tgz#a3150eeb09cc7ab207ebf6d7b9ad311a9bdbed12" integrity sha512-FZfhjEDbT5GRswV3C6uvLPHMiVD6lQBmpoX5+eSiPaMTXte/IKqI5dykDxzZB/WBeK/CDuQRBWarPdi3FNY2zQ== -"@noble/hashes@1.3.1": - version "1.3.1" - resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.3.1.tgz#8831ef002114670c603c458ab8b11328406953a9" - integrity sha512-EbqwksQwz9xDRGfDST86whPBgM65E0OH/pCgqW0GBVzO22bNE+NuIbeTb714+IfSjU3aRk47EUvXIb5bTsenKA== - "@noble/hashes@1.3.2": version "1.3.2" resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.3.2.tgz#6f26dbc8fbc7205873ce3cee2f690eba0d421b39" integrity sha512-MVC8EAQp7MvEcm30KWENFjgR+Mkmf+D189XJTkFIlwohU5hcBbn1ZkKq7KVTi2Hme3PMGF390DaL52beVrIihQ== -"@noble/hashes@1.3.3", "@noble/hashes@^1.3.2", "@noble/hashes@~1.3.0", "@noble/hashes@~1.3.1", "@noble/hashes@~1.3.2": +"@noble/hashes@1.3.3", "@noble/hashes@~1.3.0", "@noble/hashes@~1.3.2": version "1.3.3" resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.3.3.tgz#39908da56a4adc270147bb07968bf3b16cfe1699" integrity sha512-V7/fPHgl+jsVPXqqeOzT8egNj2iBIVt+ECeMMG8TdcnTikP3oaBtUVqpT/gYCR68aEBJSF+XbYUxStjbFMqIIA== +"@noble/hashes@1.4.0", "@noble/hashes@^1.3.3": + version "1.4.0" + resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.4.0.tgz#45814aa329f30e4fe0ba49426f49dfccdd066426" + integrity sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg== + "@noble/secp256k1@1.7.1", "@noble/secp256k1@~1.7.0": version "1.7.1" resolved "https://registry.yarnpkg.com/@noble/secp256k1/-/secp256k1-1.7.1.tgz#b251c70f824ce3ca7f8dc3df08d58f005cc0507c" @@ -3852,9 +3815,9 @@ ethereum-cryptography "0.1.3" "@nomicfoundation/hardhat-chai-matchers@^2.0.3": - version "2.0.3" - resolved "https://registry.yarnpkg.com/@nomicfoundation/hardhat-chai-matchers/-/hardhat-chai-matchers-2.0.3.tgz#f4c074d39b74bd283c99e2c2bf143e3cef51ae18" - integrity sha512-A40s7EAK4Acr8UP1Yudgi9GGD9Cca/K3LHt3DzmRIje14lBfHtg9atGQ7qK56vdPcTwKmeaGn30FzxMUfPGEMw== + version "2.0.6" + resolved "https://registry.yarnpkg.com/@nomicfoundation/hardhat-chai-matchers/-/hardhat-chai-matchers-2.0.6.tgz#ef88be3bd666adf29c06ac7882e96c8dbaaa32ba" + integrity sha512-Te1Uyo9oJcTCF0Jy9dztaLpshmlpjLf2yPtWXlXuLjMt3RRSmJLm/+rKVTW6gfadAEs12U/it6D0ZRnnRGiICQ== dependencies: "@types/chai-as-promised" "^7.1.3" chai-as-promised "^7.1.1" @@ -3882,9 +3845,9 @@ integrity sha512-jhcWHp0aHaL0aDYj8IJl80v4SZXWMS1A2XxXa1CA6pBiFfJKuZinCkO6wb+POAt0LIfXB3gA3AgdcOccrcwBwA== "@nomicfoundation/hardhat-verify@^2.0.3": - version "2.0.3" - resolved "https://registry.yarnpkg.com/@nomicfoundation/hardhat-verify/-/hardhat-verify-2.0.3.tgz#173557f8cfa53c8c9da23a326f54d24fe459ae68" - integrity sha512-ESbRu9by53wu6VvgwtMtm108RSmuNsVqXtzg061D+/4R7jaWh/Wl/8ve+p6SdDX7vA1Z3L02hDO1Q3BY4luLXQ== + version "2.0.5" + resolved "https://registry.yarnpkg.com/@nomicfoundation/hardhat-verify/-/hardhat-verify-2.0.5.tgz#dcc2cb5e5c55a39704c7d492436f80f05a4ca5a3" + integrity sha512-Tg4zu8RkWpyADSFIgF4FlJIUEI4VkxcvELsmbJn2OokbvH2SnUrqKmw0BBfDrtvP0hhmx8wsnrRKP5DV/oTyTA== dependencies: "@ethersproject/abi" "^5.1.2" "@ethersproject/address" "^5.0.2" @@ -4059,14 +4022,14 @@ fast-levenshtein "^3.0.0" "@openzeppelin/contracts-upgradeable@^4.9.2": - version "4.9.5" - resolved "https://registry.yarnpkg.com/@openzeppelin/contracts-upgradeable/-/contracts-upgradeable-4.9.5.tgz#572b5da102fc9be1d73f34968e0ca56765969812" - integrity sha512-f7L1//4sLlflAN7fVzJLoRedrf5Na3Oal5PZfIq55NFcVZ90EpV1q5xOvL4lFvg3MNICSDr2hH0JUBxwlxcoPg== + version "4.9.6" + resolved "https://registry.yarnpkg.com/@openzeppelin/contracts-upgradeable/-/contracts-upgradeable-4.9.6.tgz#38b21708a719da647de4bb0e4802ee235a0d24df" + integrity sha512-m4iHazOsOCv1DgM7eD7GupTJ+NFVujRZt1wzddDPSVGpWdKq1SKkla5htKG7+IS4d2XOCtzkUNwRZ7Vq5aEUMA== "@openzeppelin/contracts@^4.9.2": - version "4.9.5" - resolved "https://registry.yarnpkg.com/@openzeppelin/contracts/-/contracts-4.9.5.tgz#1eed23d4844c861a1835b5d33507c1017fa98de8" - integrity sha512-ZK+W5mVhRppff9BE6YdR8CC52C8zAvsVAiWhEtQ5+oNxFE6h1WdeWo+FJSF8KKvtxxVYZ7MTP/5KoVpAU3aSWg== + version "4.9.6" + resolved "https://registry.yarnpkg.com/@openzeppelin/contracts/-/contracts-4.9.6.tgz#2a880a24eb19b4f8b25adc2a5095f2aa27f39677" + integrity sha512-xSmezSupL+y9VkHZJGDoCBpmnB2ogM13ccaYDWqJTfS3dbuHkgjuwDFUmaFauBCboQMGB/S5UqUl2y54X99BmA== "@openzeppelin/defender-admin-client@^1.52.0": version "1.54.1" @@ -4090,44 +4053,53 @@ lodash "^4.17.19" node-fetch "^2.6.0" -"@openzeppelin/defender-sdk-base-client@^1.8.0": - version "1.8.0" - resolved "https://registry.yarnpkg.com/@openzeppelin/defender-sdk-base-client/-/defender-sdk-base-client-1.8.0.tgz#2209a060ce61b4dfc44c7ac0c2b1d86e18b69f7d" - integrity sha512-XIJat6BW2CTM74AwG5IL0Q/aE6RXj8x7smnVKmBql4wMvmirVW+njfwzZCLhUTiBXg9AlHxIInEF14SabfIisg== +"@openzeppelin/defender-sdk-base-client@^1.10.0", "@openzeppelin/defender-sdk-base-client@^1.11.0": + version "1.11.0" + resolved "https://registry.yarnpkg.com/@openzeppelin/defender-sdk-base-client/-/defender-sdk-base-client-1.11.0.tgz#771ea315d07dc9dc4a7572aef42892ac772b8a2e" + integrity sha512-HNcbRhbcMZZM5Ri5IfUwJaiJZGIrc0yboRZRlXJfG2aFS/EMfUFnQHC0tyyXtCOAoAZcn+iMlsSj5h8CoUeCfw== dependencies: amazon-cognito-identity-js "^6.3.6" async-retry "^1.3.3" -"@openzeppelin/defender-sdk-deploy-client@^1.8.0": - version "1.8.0" - resolved "https://registry.yarnpkg.com/@openzeppelin/defender-sdk-deploy-client/-/defender-sdk-deploy-client-1.8.0.tgz#1e186d2b3ff176c6a4c03e8207bad8022528975f" - integrity sha512-/tNS2EnHuA5l095wzMbIkGMDNHZLcZQ2eLUP8z+AeKaAUeR2z4qzZ1ul21kR3EJURAyoy8aULFZanLggoBWHrA== +"@openzeppelin/defender-sdk-deploy-client@^1.10.0": + version "1.11.0" + resolved "https://registry.yarnpkg.com/@openzeppelin/defender-sdk-deploy-client/-/defender-sdk-deploy-client-1.11.0.tgz#951e8e17b9079fb8744261445514a80ee57af7eb" + integrity sha512-ELYVihsrTOlH7Sy5C/+Yf64hV3ICeTY2OcczOWVQ/o6rHBWKSnHSZCE/oB1cfOpyg/gCrCLXozs4NyrS5z3GUw== dependencies: - "@ethersproject/abi" "^5.7.0" - "@openzeppelin/defender-sdk-base-client" "^1.8.0" - axios "^1.4.0" + "@openzeppelin/defender-sdk-base-client" "^1.11.0" + axios "^1.6.7" + lodash "^4.17.21" + +"@openzeppelin/defender-sdk-network-client@^1.10.0": + version "1.11.0" + resolved "https://registry.yarnpkg.com/@openzeppelin/defender-sdk-network-client/-/defender-sdk-network-client-1.11.0.tgz#1accb636cb7f5c3cde46745e5fb64b73bb7ca1e4" + integrity sha512-CPy1TA6RyFYtACbvXZJhJpsYW2u4yxTSNU8cVIw1lH/9iArXzfWuJ2p8Deidc0sJBbMeJYkv1AvqTBJNifjKMg== + dependencies: + "@openzeppelin/defender-sdk-base-client" "^1.11.0" + axios "^1.6.7" lodash "^4.17.21" "@openzeppelin/hardhat-upgrades@^3.0.1": - version "3.0.1" - resolved "https://registry.yarnpkg.com/@openzeppelin/hardhat-upgrades/-/hardhat-upgrades-3.0.1.tgz#f1a9c5e817ddb9163da0e79cf50a13dfe7e14856" - integrity sha512-NtD2/n2PKNqHBafQy3AM6KCvsDZD0w97po7fFa4wctl0fg/8QwGAg3fB8InkBFEhGn17+IgshRI8G94hUrFPcQ== + version "3.0.5" + resolved "https://registry.yarnpkg.com/@openzeppelin/hardhat-upgrades/-/hardhat-upgrades-3.0.5.tgz#792bef91f78c34aa1b64866dcbbf3b230bd6c4f7" + integrity sha512-7Klg1B6fH45+7Zxzr6d9mLqudrL9Uk6CUG5AeG5NckPfP4ZlQRo1squcQ8yJPwqDS8rQjfChiqKDelp4LTjyZQ== dependencies: "@openzeppelin/defender-admin-client" "^1.52.0" "@openzeppelin/defender-base-client" "^1.52.0" - "@openzeppelin/defender-sdk-base-client" "^1.8.0" - "@openzeppelin/defender-sdk-deploy-client" "^1.8.0" + "@openzeppelin/defender-sdk-base-client" "^1.10.0" + "@openzeppelin/defender-sdk-deploy-client" "^1.10.0" + "@openzeppelin/defender-sdk-network-client" "^1.10.0" "@openzeppelin/upgrades-core" "^1.32.0" chalk "^4.1.0" debug "^4.1.1" ethereumjs-util "^7.1.5" proper-lockfile "^4.1.1" - undici "^5.28.2" + undici "^6.0.0" -"@openzeppelin/upgrades-core@^1.32.0": - version "1.32.2" - resolved "https://registry.yarnpkg.com/@openzeppelin/upgrades-core/-/upgrades-core-1.32.2.tgz#4313bd0a547090a350817cf798af60e0eb0728e8" - integrity sha512-EkXriOHZfn6u00Tbq0zUuhHDeTQB9WyAZKZo3UeYdqULb7E3vqxZgxgXmWJwEzAb6E77DvprzQ4gwCAjMV/S4Q== +"@openzeppelin/upgrades-core@^1.32.0", "@openzeppelin/upgrades-core@^1.32.2": + version "1.32.5" + resolved "https://registry.yarnpkg.com/@openzeppelin/upgrades-core/-/upgrades-core-1.32.5.tgz#2496174fd1f47be4dd8f36b29714d4e1f8240632" + integrity sha512-R0wprsyJ4xWiRW05kaTfZZkRVpG2g0af3/hpjE7t2mX0Eb2n40MQLokTwqIk4LDzpp910JfLSpB0vBuZ6WNPog== dependencies: cbor "^9.0.0" chalk "^4.1.0" @@ -4138,99 +4110,99 @@ proper-lockfile "^4.1.1" solidity-ast "^0.4.51" -"@parcel/watcher-android-arm64@2.3.0": - version "2.3.0" - resolved "https://registry.yarnpkg.com/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.3.0.tgz#d82e74bb564ebd4d8a88791d273a3d2bd61e27ab" - integrity sha512-f4o9eA3dgk0XRT3XhB0UWpWpLnKgrh1IwNJKJ7UJek7eTYccQ8LR7XUWFKqw6aEq5KUNlCcGvSzKqSX/vtWVVA== +"@parcel/watcher-android-arm64@2.4.1": + version "2.4.1" + resolved "https://registry.yarnpkg.com/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.4.1.tgz#c2c19a3c442313ff007d2d7a9c2c1dd3e1c9ca84" + integrity sha512-LOi/WTbbh3aTn2RYddrO8pnapixAziFl6SMxHM69r3tvdSm94JtCenaKgk1GRg5FJ5wpMCpHeW+7yqPlvZv7kg== -"@parcel/watcher-darwin-arm64@2.3.0": - version "2.3.0" - resolved "https://registry.yarnpkg.com/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.3.0.tgz#c9cd03f8f233d512fcfc873d5b4e23f1569a82ad" - integrity sha512-mKY+oijI4ahBMc/GygVGvEdOq0L4DxhYgwQqYAz/7yPzuGi79oXrZG52WdpGA1wLBPrYb0T8uBaGFo7I6rvSKw== +"@parcel/watcher-darwin-arm64@2.4.1": + version "2.4.1" + resolved "https://registry.yarnpkg.com/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.4.1.tgz#c817c7a3b4f3a79c1535bfe54a1c2818d9ffdc34" + integrity sha512-ln41eihm5YXIY043vBrrHfn94SIBlqOWmoROhsMVTSXGh0QahKGy77tfEywQ7v3NywyxBBkGIfrWRHm0hsKtzA== -"@parcel/watcher-darwin-x64@2.3.0": - version "2.3.0" - resolved "https://registry.yarnpkg.com/@parcel/watcher-darwin-x64/-/watcher-darwin-x64-2.3.0.tgz#83c902994a2a49b9e1ab5050dba24876fdc2c219" - integrity sha512-20oBj8LcEOnLE3mgpy6zuOq8AplPu9NcSSSfyVKgfOhNAc4eF4ob3ldj0xWjGGbOF7Dcy1Tvm6ytvgdjlfUeow== +"@parcel/watcher-darwin-x64@2.4.1": + version "2.4.1" + resolved "https://registry.yarnpkg.com/@parcel/watcher-darwin-x64/-/watcher-darwin-x64-2.4.1.tgz#1a3f69d9323eae4f1c61a5f480a59c478d2cb020" + integrity sha512-yrw81BRLjjtHyDu7J61oPuSoeYWR3lDElcPGJyOvIXmor6DEo7/G2u1o7I38cwlcoBHQFULqF6nesIX3tsEXMg== -"@parcel/watcher-freebsd-x64@2.3.0": - version "2.3.0" - resolved "https://registry.yarnpkg.com/@parcel/watcher-freebsd-x64/-/watcher-freebsd-x64-2.3.0.tgz#7a0f4593a887e2752b706aff2dae509aef430cf6" - integrity sha512-7LftKlaHunueAEiojhCn+Ef2CTXWsLgTl4hq0pkhkTBFI3ssj2bJXmH2L67mKpiAD5dz66JYk4zS66qzdnIOgw== +"@parcel/watcher-freebsd-x64@2.4.1": + version "2.4.1" + resolved "https://registry.yarnpkg.com/@parcel/watcher-freebsd-x64/-/watcher-freebsd-x64-2.4.1.tgz#0d67fef1609f90ba6a8a662bc76a55fc93706fc8" + integrity sha512-TJa3Pex/gX3CWIx/Co8k+ykNdDCLx+TuZj3f3h7eOjgpdKM+Mnix37RYsYU4LHhiYJz3DK5nFCCra81p6g050w== -"@parcel/watcher-linux-arm-glibc@2.3.0": - version "2.3.0" - resolved "https://registry.yarnpkg.com/@parcel/watcher-linux-arm-glibc/-/watcher-linux-arm-glibc-2.3.0.tgz#3fc90c3ebe67de3648ed2f138068722f9b1d47da" - integrity sha512-1apPw5cD2xBv1XIHPUlq0cO6iAaEUQ3BcY0ysSyD9Kuyw4MoWm1DV+W9mneWI+1g6OeP6dhikiFE6BlU+AToTQ== +"@parcel/watcher-linux-arm-glibc@2.4.1": + version "2.4.1" + resolved "https://registry.yarnpkg.com/@parcel/watcher-linux-arm-glibc/-/watcher-linux-arm-glibc-2.4.1.tgz#ce5b340da5829b8e546bd00f752ae5292e1c702d" + integrity sha512-4rVYDlsMEYfa537BRXxJ5UF4ddNwnr2/1O4MHM5PjI9cvV2qymvhwZSFgXqbS8YoTk5i/JR0L0JDs69BUn45YA== -"@parcel/watcher-linux-arm64-glibc@2.3.0": - version "2.3.0" - resolved "https://registry.yarnpkg.com/@parcel/watcher-linux-arm64-glibc/-/watcher-linux-arm64-glibc-2.3.0.tgz#f7bbbf2497d85fd11e4c9e9c26ace8f10ea9bcbc" - integrity sha512-mQ0gBSQEiq1k/MMkgcSB0Ic47UORZBmWoAWlMrTW6nbAGoLZP+h7AtUM7H3oDu34TBFFvjy4JCGP43JlylkTQA== +"@parcel/watcher-linux-arm64-glibc@2.4.1": + version "2.4.1" + resolved "https://registry.yarnpkg.com/@parcel/watcher-linux-arm64-glibc/-/watcher-linux-arm64-glibc-2.4.1.tgz#6d7c00dde6d40608f9554e73998db11b2b1ff7c7" + integrity sha512-BJ7mH985OADVLpbrzCLgrJ3TOpiZggE9FMblfO65PlOCdG++xJpKUJ0Aol74ZUIYfb8WsRlUdgrZxKkz3zXWYA== -"@parcel/watcher-linux-arm64-musl@2.3.0": - version "2.3.0" - resolved "https://registry.yarnpkg.com/@parcel/watcher-linux-arm64-musl/-/watcher-linux-arm64-musl-2.3.0.tgz#de131a9fcbe1fa0854e9cbf4c55bed3b35bcff43" - integrity sha512-LXZAExpepJew0Gp8ZkJ+xDZaTQjLHv48h0p0Vw2VMFQ8A+RKrAvpFuPVCVwKJCr5SE+zvaG+Etg56qXvTDIedw== +"@parcel/watcher-linux-arm64-musl@2.4.1": + version "2.4.1" + resolved "https://registry.yarnpkg.com/@parcel/watcher-linux-arm64-musl/-/watcher-linux-arm64-musl-2.4.1.tgz#bd39bc71015f08a4a31a47cd89c236b9d6a7f635" + integrity sha512-p4Xb7JGq3MLgAfYhslU2SjoV9G0kI0Xry0kuxeG/41UfpjHGOhv7UoUDAz/jb1u2elbhazy4rRBL8PegPJFBhA== -"@parcel/watcher-linux-x64-glibc@2.3.0": - version "2.3.0" - resolved "https://registry.yarnpkg.com/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.3.0.tgz#193dd1c798003cdb5a1e59470ff26300f418a943" - integrity sha512-P7Wo91lKSeSgMTtG7CnBS6WrA5otr1K7shhSjKHNePVmfBHDoAOHYRXgUmhiNfbcGk0uMCHVcdbfxtuiZCHVow== +"@parcel/watcher-linux-x64-glibc@2.4.1": + version "2.4.1" + resolved "https://registry.yarnpkg.com/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.4.1.tgz#0ce29966b082fb6cdd3de44f2f74057eef2c9e39" + integrity sha512-s9O3fByZ/2pyYDPoLM6zt92yu6P4E39a03zvO0qCHOTjxmt3GHRMLuRZEWhWLASTMSrrnVNWdVI/+pUElJBBBg== -"@parcel/watcher-linux-x64-musl@2.3.0": - version "2.3.0" - resolved "https://registry.yarnpkg.com/@parcel/watcher-linux-x64-musl/-/watcher-linux-x64-musl-2.3.0.tgz#6dbdb86d96e955ab0fe4a4b60734ec0025a689dd" - integrity sha512-+kiRE1JIq8QdxzwoYY+wzBs9YbJ34guBweTK8nlzLKimn5EQ2b2FSC+tAOpq302BuIMjyuUGvBiUhEcLIGMQ5g== +"@parcel/watcher-linux-x64-musl@2.4.1": + version "2.4.1" + resolved "https://registry.yarnpkg.com/@parcel/watcher-linux-x64-musl/-/watcher-linux-x64-musl-2.4.1.tgz#d2ebbf60e407170bb647cd6e447f4f2bab19ad16" + integrity sha512-L2nZTYR1myLNST0O632g0Dx9LyMNHrn6TOt76sYxWLdff3cB22/GZX2UPtJnaqQPdCRoszoY5rcOj4oMTtp5fQ== -"@parcel/watcher-wasm@2.3.0": - version "2.3.0" - resolved "https://registry.yarnpkg.com/@parcel/watcher-wasm/-/watcher-wasm-2.3.0.tgz#73b66c6fbd2a3326ae86a1ec77eab7139d0dd725" - integrity sha512-ejBAX8H0ZGsD8lSICDNyMbSEtPMWgDL0WFCt/0z7hyf5v8Imz4rAM8xY379mBsECkq/Wdqa5WEDLqtjZ+6NxfA== +"@parcel/watcher-wasm@^2.4.1": + version "2.4.1" + resolved "https://registry.yarnpkg.com/@parcel/watcher-wasm/-/watcher-wasm-2.4.1.tgz#c4353e4fdb96ee14389856f7f6f6d21b7dcef9e1" + integrity sha512-/ZR0RxqxU/xxDGzbzosMjh4W6NdYFMqq2nvo2b8SLi7rsl/4jkL8S5stIikorNkdR50oVDvqb/3JT05WM+CRRA== dependencies: is-glob "^4.0.3" micromatch "^4.0.5" napi-wasm "^1.1.0" -"@parcel/watcher-win32-arm64@2.3.0": - version "2.3.0" - resolved "https://registry.yarnpkg.com/@parcel/watcher-win32-arm64/-/watcher-win32-arm64-2.3.0.tgz#59da26a431da946e6c74fa6b0f30b120ea6650b6" - integrity sha512-35gXCnaz1AqIXpG42evcoP2+sNL62gZTMZne3IackM+6QlfMcJLy3DrjuL6Iks7Czpd3j4xRBzez3ADCj1l7Aw== +"@parcel/watcher-win32-arm64@2.4.1": + version "2.4.1" + resolved "https://registry.yarnpkg.com/@parcel/watcher-win32-arm64/-/watcher-win32-arm64-2.4.1.tgz#eb4deef37e80f0b5e2f215dd6d7a6d40a85f8adc" + integrity sha512-Uq2BPp5GWhrq/lcuItCHoqxjULU1QYEcyjSO5jqqOK8RNFDBQnenMMx4gAl3v8GiWa59E9+uDM7yZ6LxwUIfRg== -"@parcel/watcher-win32-ia32@2.3.0": - version "2.3.0" - resolved "https://registry.yarnpkg.com/@parcel/watcher-win32-ia32/-/watcher-win32-ia32-2.3.0.tgz#3ee6a18b08929cd3b788e8cc9547fd9a540c013a" - integrity sha512-FJS/IBQHhRpZ6PiCjFt1UAcPr0YmCLHRbTc00IBTrelEjlmmgIVLeOx4MSXzx2HFEy5Jo5YdhGpxCuqCyDJ5ow== +"@parcel/watcher-win32-ia32@2.4.1": + version "2.4.1" + resolved "https://registry.yarnpkg.com/@parcel/watcher-win32-ia32/-/watcher-win32-ia32-2.4.1.tgz#94fbd4b497be39fd5c8c71ba05436927842c9df7" + integrity sha512-maNRit5QQV2kgHFSYwftmPBxiuK5u4DXjbXx7q6eKjq5dsLXZ4FJiVvlcw35QXzk0KrUecJmuVFbj4uV9oYrcw== -"@parcel/watcher-win32-x64@2.3.0": - version "2.3.0" - resolved "https://registry.yarnpkg.com/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.3.0.tgz#14e7246289861acc589fd608de39fe5d8b4bb0a7" - integrity sha512-dLx+0XRdMnVI62kU3wbXvbIRhLck4aE28bIGKbRGS7BJNt54IIj9+c/Dkqb+7DJEbHUZAX1bwaoM8PqVlHJmCA== +"@parcel/watcher-win32-x64@2.4.1": + version "2.4.1" + resolved "https://registry.yarnpkg.com/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.4.1.tgz#4bf920912f67cae5f2d264f58df81abfea68dadf" + integrity sha512-+DvS92F9ezicfswqrvIRM2njcYJbd5mb9CUgtrHCHmvn7pPPa+nMDRu1o1bYYz/l5IB2NVGNJWiH7h1E58IF2A== -"@parcel/watcher@^2.3.0": - version "2.3.0" - resolved "https://registry.yarnpkg.com/@parcel/watcher/-/watcher-2.3.0.tgz#803517abbc3981a1a1221791d9f59dc0590d50f9" - integrity sha512-pW7QaFiL11O0BphO+bq3MgqeX/INAk9jgBldVDYjlQPO4VddoZnF22TcF9onMhnLVHuNqBJeRf+Fj7eezi/+rQ== +"@parcel/watcher@^2.4.1": + version "2.4.1" + resolved "https://registry.yarnpkg.com/@parcel/watcher/-/watcher-2.4.1.tgz#a50275151a1bb110879c6123589dba90c19f1bf8" + integrity sha512-HNjmfLQEVRZmHRET336f20H/8kOozUGwk7yajvsonjNxbj2wBTK1WsQuHkD5yYh9RxFGL2EyDHryOihOwUoKDA== dependencies: detect-libc "^1.0.3" is-glob "^4.0.3" micromatch "^4.0.5" node-addon-api "^7.0.0" optionalDependencies: - "@parcel/watcher-android-arm64" "2.3.0" - "@parcel/watcher-darwin-arm64" "2.3.0" - "@parcel/watcher-darwin-x64" "2.3.0" - "@parcel/watcher-freebsd-x64" "2.3.0" - "@parcel/watcher-linux-arm-glibc" "2.3.0" - "@parcel/watcher-linux-arm64-glibc" "2.3.0" - "@parcel/watcher-linux-arm64-musl" "2.3.0" - "@parcel/watcher-linux-x64-glibc" "2.3.0" - "@parcel/watcher-linux-x64-musl" "2.3.0" - "@parcel/watcher-win32-arm64" "2.3.0" - "@parcel/watcher-win32-ia32" "2.3.0" - "@parcel/watcher-win32-x64" "2.3.0" - -"@peculiar/asn1-schema@^2.3.6": + "@parcel/watcher-android-arm64" "2.4.1" + "@parcel/watcher-darwin-arm64" "2.4.1" + "@parcel/watcher-darwin-x64" "2.4.1" + "@parcel/watcher-freebsd-x64" "2.4.1" + "@parcel/watcher-linux-arm-glibc" "2.4.1" + "@parcel/watcher-linux-arm64-glibc" "2.4.1" + "@parcel/watcher-linux-arm64-musl" "2.4.1" + "@parcel/watcher-linux-x64-glibc" "2.4.1" + "@parcel/watcher-linux-x64-musl" "2.4.1" + "@parcel/watcher-win32-arm64" "2.4.1" + "@parcel/watcher-win32-ia32" "2.4.1" + "@parcel/watcher-win32-x64" "2.4.1" + +"@peculiar/asn1-schema@^2.3.8": version "2.3.8" resolved "https://registry.yarnpkg.com/@peculiar/asn1-schema/-/asn1-schema-2.3.8.tgz#04b38832a814e25731232dd5be883460a156da3b" integrity sha512-ULB1XqHKx1WBU/tTFIA+uARuRoBVZ4pNdOA878RDrRbBfBGcSzi5HBkdScC6ZbHn8z7L8gmKCgPC1LHRrP46tA== @@ -4247,15 +4219,15 @@ tslib "^2.0.0" "@peculiar/webcrypto@^1.4.0": - version "1.4.3" - resolved "https://registry.yarnpkg.com/@peculiar/webcrypto/-/webcrypto-1.4.3.tgz#078b3e8f598e847b78683dc3ba65feb5029b93a7" - integrity sha512-VtaY4spKTdN5LjJ04im/d/joXuvLbQdgy5Z4DXF4MFZhQ+MTrejbNMkfZBp1Bs3O5+bFqnJgyGdPuZQflvIa5A== + version "1.4.5" + resolved "https://registry.yarnpkg.com/@peculiar/webcrypto/-/webcrypto-1.4.5.tgz#424bed6b0d133b772f5cbffd143d0468a90f40a0" + integrity sha512-oDk93QCDGdxFRM8382Zdminzs44dg3M2+E5Np+JWkpqLDyJC9DviMh8F8mEJkYuUcUOGA5jHO5AJJ10MFWdbZw== dependencies: - "@peculiar/asn1-schema" "^2.3.6" + "@peculiar/asn1-schema" "^2.3.8" "@peculiar/json-schema" "^1.1.12" - pvtsutils "^1.3.2" - tslib "^2.5.0" - webcrypto-core "^1.7.7" + pvtsutils "^1.3.5" + tslib "^2.6.2" + webcrypto-core "^1.7.8" "@pkgjs/parseargs@^0.11.0": version "0.11.0" @@ -4263,9 +4235,9 @@ integrity sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg== "@pkgr/core@^0.1.0": - version "0.1.0" - resolved "https://registry.yarnpkg.com/@pkgr/core/-/core-0.1.0.tgz#7d8dacb7fdef0e4387caf7396cbd77f179867d06" - integrity sha512-Zwq5OCzuwJC2jwqmpEQt7Ds1DTi6BWSwoGkbb1n9pO3hzb35BoJELx7c0T23iDkBGkh2e7tvOtjF3tr3OaQHDQ== + version "0.1.1" + resolved "https://registry.yarnpkg.com/@pkgr/core/-/core-0.1.1.tgz#1ec17e2edbec25c8306d424ecfbf13c7de1aaa31" + integrity sha512-cq8o4cWH0ibXh9VGi5P20Tu9XF/0fFXl9EUinr9QfTM7a7p0oTA4iJRCQWppXR1Pg8dSM0UCItCkPwsk9qWWYA== "@pkgr/utils@^2.3.1": version "2.4.2" @@ -4316,9 +4288,9 @@ config-chain "^1.1.11" "@polka/url@^1.0.0-next.24": - version "1.0.0-next.24" - resolved "https://registry.yarnpkg.com/@polka/url/-/url-1.0.0-next.24.tgz#58601079e11784d20f82d0585865bb42305c4df3" - integrity sha512-2LuNTFBIO0m7kKIQvvPHN6UE63VjpmL9rnEEaOOaiSPbZK+zUOYIzBAWcED+3XYzhYsd/0mD57VdxAEqqV52CQ== + version "1.0.0-next.25" + resolved "https://registry.yarnpkg.com/@polka/url/-/url-1.0.0-next.25.tgz#f077fdc0b5d0078d30893396ff4827a13f99e817" + integrity sha512-j7P6Rgr3mmtdkeDGTe0E/aYyWEWVtc5yFXtHCRHs28/jptDEWfaVOc5T7cblqy1XKPPfCxJc/8DwQ5YgLOZOVQ== "@popperjs/core@^2.11.8": version "2.11.8" @@ -4713,10 +4685,10 @@ redux-thunk "^2.4.2" reselect "^4.1.8" -"@remix-run/router@1.14.1": - version "1.14.1" - resolved "https://registry.yarnpkg.com/@remix-run/router/-/router-1.14.1.tgz#6d2dd03d52e604279c38911afc1079d58c50a755" - integrity sha512-Qg4DMQsfPNAs88rb2xkdk03N3bjK4jgX5fR24eHCTR9q6PrhZQZ4UJBPzCHJkIpTRN1UKxx2DzjZmnC+7Lj0Ow== +"@remix-run/router@1.15.3": + version "1.15.3" + resolved "https://registry.yarnpkg.com/@remix-run/router/-/router-1.15.3.tgz#d2509048d69dbb72d5389a14945339f1430b2d3c" + integrity sha512-Oy8rmScVrVxWZVOpEF57ovlnhpZ8CCPlnIIumVcV9nFdiSIrus99+Lw78ekXyGvVDlIsFJbSfmSovJUhCWYV3w== "@repeaterjs/repeater@3.0.4": version "3.0.4" @@ -4807,84 +4779,104 @@ estree-walker "^2.0.2" picomatch "^2.3.1" -"@rollup/rollup-android-arm-eabi@4.9.6": - version "4.9.6" - resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.9.6.tgz#66b8d9cb2b3a474d115500f9ebaf43e2126fe496" - integrity sha512-MVNXSSYN6QXOulbHpLMKYi60ppyO13W9my1qogeiAqtjb2yR4LSmfU2+POvDkLzhjYLXz9Rf9+9a3zFHW1Lecg== - -"@rollup/rollup-android-arm64@4.9.6": - version "4.9.6" - resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.9.6.tgz#46327d5b86420d2307946bec1535fdf00356e47d" - integrity sha512-T14aNLpqJ5wzKNf5jEDpv5zgyIqcpn1MlwCrUXLrwoADr2RkWA0vOWP4XxbO9aiO3dvMCQICZdKeDrFl7UMClw== - -"@rollup/rollup-darwin-arm64@4.9.6": - version "4.9.6" - resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.9.6.tgz#166987224d2f8b1e2fd28ee90c447d52271d5e90" - integrity sha512-CqNNAyhRkTbo8VVZ5R85X73H3R5NX9ONnKbXuHisGWC0qRbTTxnF1U4V9NafzJbgGM0sHZpdO83pLPzq8uOZFw== - -"@rollup/rollup-darwin-x64@4.9.6": - version "4.9.6" - resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.9.6.tgz#a2e6e096f74ccea6e2f174454c26aef6bcdd1274" - integrity sha512-zRDtdJuRvA1dc9Mp6BWYqAsU5oeLixdfUvkTHuiYOHwqYuQ4YgSmi6+/lPvSsqc/I0Omw3DdICx4Tfacdzmhog== - -"@rollup/rollup-linux-arm-gnueabihf@4.9.6": - version "4.9.6" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.9.6.tgz#09fcd4c55a2d6160c5865fec708a8e5287f30515" - integrity sha512-oNk8YXDDnNyG4qlNb6is1ojTOGL/tRhbbKeE/YuccItzerEZT68Z9gHrY3ROh7axDc974+zYAPxK5SH0j/G+QQ== - -"@rollup/rollup-linux-arm64-gnu@4.9.6": - version "4.9.6" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.9.6.tgz#19a3c0b6315c747ca9acf86e9b710cc2440f83c9" - integrity sha512-Z3O60yxPtuCYobrtzjo0wlmvDdx2qZfeAWTyfOjEDqd08kthDKexLpV97KfAeUXPosENKd8uyJMRDfFMxcYkDQ== - -"@rollup/rollup-linux-arm64-musl@4.9.6": - version "4.9.6" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.9.6.tgz#94aaf95fdaf2ad9335983a4552759f98e6b2e850" - integrity sha512-gpiG0qQJNdYEVad+1iAsGAbgAnZ8j07FapmnIAQgODKcOTjLEWM9sRb+MbQyVsYCnA0Im6M6QIq6ax7liws6eQ== - -"@rollup/rollup-linux-riscv64-gnu@4.9.6": - version "4.9.6" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.9.6.tgz#160510e63f4b12618af4013bddf1761cf9fc9880" - integrity sha512-+uCOcvVmFUYvVDr27aiyun9WgZk0tXe7ThuzoUTAukZJOwS5MrGbmSlNOhx1j80GdpqbOty05XqSl5w4dQvcOA== - -"@rollup/rollup-linux-x64-gnu@4.9.6": - version "4.9.6" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.9.6.tgz#5ac5d068ce0726bd0a96ca260d5bd93721c0cb98" - integrity sha512-HUNqM32dGzfBKuaDUBqFB7tP6VMN74eLZ33Q9Y1TBqRDn+qDonkAUyKWwF9BR9unV7QUzffLnz9GrnKvMqC/fw== - -"@rollup/rollup-linux-x64-musl@4.9.6": - version "4.9.6" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.9.6.tgz#bafa759ab43e8eab9edf242a8259ffb4f2a57a5d" - integrity sha512-ch7M+9Tr5R4FK40FHQk8VnML0Szi2KRujUgHXd/HjuH9ifH72GUmw6lStZBo3c3GB82vHa0ZoUfjfcM7JiiMrQ== - -"@rollup/rollup-win32-arm64-msvc@4.9.6": - version "4.9.6" - resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.9.6.tgz#1cc3416682e5a20d8f088f26657e6e47f8db468e" - integrity sha512-VD6qnR99dhmTQ1mJhIzXsRcTBvTjbfbGGwKAHcu+52cVl15AC/kplkhxzW/uT0Xl62Y/meBKDZvoJSJN+vTeGA== +"@rollup/rollup-android-arm-eabi@4.13.0": + version "4.13.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.13.0.tgz#b98786c1304b4ff8db3a873180b778649b5dff2b" + integrity sha512-5ZYPOuaAqEH/W3gYsRkxQATBW3Ii1MfaT4EQstTnLKViLi2gLSQmlmtTpGucNP3sXEpOiI5tdGhjdE111ekyEg== + +"@rollup/rollup-android-arm64@4.13.0": + version "4.13.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.13.0.tgz#8833679af11172b1bf1ab7cb3bad84df4caf0c9e" + integrity sha512-BSbaCmn8ZadK3UAQdlauSvtaJjhlDEjS5hEVVIN3A4bbl3X+otyf/kOJV08bYiRxfejP3DXFzO2jz3G20107+Q== + +"@rollup/rollup-darwin-arm64@4.13.0": + version "4.13.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.13.0.tgz#ef02d73e0a95d406e0eb4fd61a53d5d17775659b" + integrity sha512-Ovf2evVaP6sW5Ut0GHyUSOqA6tVKfrTHddtmxGQc1CTQa1Cw3/KMCDEEICZBbyppcwnhMwcDce9ZRxdWRpVd6g== + +"@rollup/rollup-darwin-x64@4.13.0": + version "4.13.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.13.0.tgz#3ce5b9bcf92b3341a5c1c58a3e6bcce0ea9e7455" + integrity sha512-U+Jcxm89UTK592vZ2J9st9ajRv/hrwHdnvyuJpa5A2ngGSVHypigidkQJP+YiGL6JODiUeMzkqQzbCG3At81Gg== + +"@rollup/rollup-linux-arm-gnueabihf@4.13.0": + version "4.13.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.13.0.tgz#3d3d2c018bdd8e037c6bfedd52acfff1c97e4be4" + integrity sha512-8wZidaUJUTIR5T4vRS22VkSMOVooG0F4N+JSwQXWSRiC6yfEsFMLTYRFHvby5mFFuExHa/yAp9juSphQQJAijQ== + +"@rollup/rollup-linux-arm64-gnu@4.13.0": + version "4.13.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.13.0.tgz#5fc8cc978ff396eaa136d7bfe05b5b9138064143" + integrity sha512-Iu0Kno1vrD7zHQDxOmvweqLkAzjxEVqNhUIXBsZ8hu8Oak7/5VTPrxOEZXYC1nmrBVJp0ZcL2E7lSuuOVaE3+w== + +"@rollup/rollup-linux-arm64-musl@4.13.0": + version "4.13.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.13.0.tgz#f2ae7d7bed416ffa26d6b948ac5772b520700eef" + integrity sha512-C31QrW47llgVyrRjIwiOwsHFcaIwmkKi3PCroQY5aVq4H0A5v/vVVAtFsI1nfBngtoRpeREvZOkIhmRwUKkAdw== + +"@rollup/rollup-linux-riscv64-gnu@4.13.0": + version "4.13.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.13.0.tgz#303d57a328ee9a50c85385936f31cf62306d30b6" + integrity sha512-Oq90dtMHvthFOPMl7pt7KmxzX7E71AfyIhh+cPhLY9oko97Zf2C9tt/XJD4RgxhaGeAraAXDtqxvKE1y/j35lA== + +"@rollup/rollup-linux-x64-gnu@4.13.0": + version "4.13.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.13.0.tgz#f672f6508f090fc73f08ba40ff76c20b57424778" + integrity sha512-yUD/8wMffnTKuiIsl6xU+4IA8UNhQ/f1sAnQebmE/lyQ8abjsVyDkyRkWop0kdMhKMprpNIhPmYlCxgHrPoXoA== + +"@rollup/rollup-linux-x64-musl@4.13.0": + version "4.13.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.13.0.tgz#d2f34b1b157f3e7f13925bca3288192a66755a89" + integrity sha512-9RyNqoFNdF0vu/qqX63fKotBh43fJQeYC98hCaf89DYQpv+xu0D8QFSOS0biA7cGuqJFOc1bJ+m2rhhsKcw1hw== + +"@rollup/rollup-win32-arm64-msvc@4.13.0": + version "4.13.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.13.0.tgz#8ffecc980ae4d9899eb2f9c4ae471a8d58d2da6b" + integrity sha512-46ue8ymtm/5PUU6pCvjlic0z82qWkxv54GTJZgHrQUuZnVH+tvvSP0LsozIDsCBFO4VjJ13N68wqrKSeScUKdA== + +"@rollup/rollup-win32-ia32-msvc@4.13.0": + version "4.13.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.13.0.tgz#a7505884f415662e088365b9218b2b03a88fc6f2" + integrity sha512-P5/MqLdLSlqxbeuJ3YDeX37srC8mCflSyTrUsgbU1c/U9j6l2g2GiIdYaGD9QjdMQPMSgYm7hgg0551wHyIluw== + +"@rollup/rollup-win32-x64-msvc@4.13.0": + version "4.13.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.13.0.tgz#6abd79db7ff8d01a58865ba20a63cfd23d9e2a10" + integrity sha512-UKXUQNbO3DOhzLRwHSpa0HnhhCgNODvfoPWv2FCXme8N/ANFfhIPMGuOT+QuKd16+B5yxZ0HdpNlqPvTMS1qfw== -"@rollup/rollup-win32-ia32-msvc@4.9.6": - version "4.9.6" - resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.9.6.tgz#7d2251e1aa5e8a1e47c86891fe4547a939503461" - integrity sha512-J9AFDq/xiRI58eR2NIDfyVmTYGyIZmRcvcAoJ48oDld/NTR8wyiPUu2X/v1navJ+N/FGg68LEbX3Ejd6l8B7MQ== +"@rushstack/eslint-patch@^1.1.0": + version "1.8.0" + resolved "https://registry.yarnpkg.com/@rushstack/eslint-patch/-/eslint-patch-1.8.0.tgz#c5545e6a5d2bd5c26b4021c357177a28698c950e" + integrity sha512-0HejFckBN2W+ucM6cUOlwsByTKt9/+0tWhqUffNIcHqCXkthY/mZ7AuYPK/2IIaGWhdl0h+tICDO0ssLMd6XMQ== -"@rollup/rollup-win32-x64-msvc@4.9.6": - version "4.9.6" - resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.9.6.tgz#2c1fb69e02a3f1506f52698cfdc3a8b6386df9a6" - integrity sha512-jqzNLhNDvIZOrt69Ce4UjGRpXJBzhUBzawMwnaDAwyHriki3XollsewxWzOzz+4yOFDkuJHtTsZFwMxhYJWmLQ== +"@rushstack/node-core-library@4.0.2": + version "4.0.2" + resolved "https://registry.yarnpkg.com/@rushstack/node-core-library/-/node-core-library-4.0.2.tgz#e26854a3314b279d57e8abdb4acce7797d02f554" + integrity sha512-hyES82QVpkfQMeBMteQUnrhASL/KHPhd7iJ8euduwNJG4mu2GSOKybf0rOEjOm1Wz7CwJEUm9y0yD7jg2C1bfg== + dependencies: + fs-extra "~7.0.1" + import-lazy "~4.0.0" + jju "~1.4.0" + resolve "~1.22.1" + semver "~7.5.4" + z-schema "~5.0.2" -"@rushstack/eslint-patch@^1.1.0": - version "1.6.1" - resolved "https://registry.yarnpkg.com/@rushstack/eslint-patch/-/eslint-patch-1.6.1.tgz#9ab8f811930d7af3e3d549183a50884f9eb83f36" - integrity sha512-UY+FGM/2jjMkzQLn8pxcHGMaVLh9aEitG3zY2CiY7XHdLiz3bZOwa6oDxNqEMv7zZkV+cj5DOdz0cQ1BP5Hjgw== +"@rushstack/terminal@0.10.0": + version "0.10.0" + resolved "https://registry.yarnpkg.com/@rushstack/terminal/-/terminal-0.10.0.tgz#e81909fa0e5c8016b6df4739f0f381f44358269f" + integrity sha512-UbELbXnUdc7EKwfH2sb8ChqNgapUOdqcCIdQP4NGxBpTZV2sQyeekuK3zmfQSa/MN+/7b4kBogl2wq0vpkpYGw== + dependencies: + "@rushstack/node-core-library" "4.0.2" + supports-color "~8.1.1" "@rushstack/ts-command-line@^4.12.2": - version "4.17.1" - resolved "https://registry.yarnpkg.com/@rushstack/ts-command-line/-/ts-command-line-4.17.1.tgz#c78db928ce5b93f2e98fd9e14c24f3f3876e57f1" - integrity sha512-2jweO1O57BYP5qdBGl6apJLB+aRIn5ccIRTPDyULh0KMwVzFqWtw6IZWt1qtUoZD/pD2RNkIOosH6Cq45rIYeg== + version "4.19.1" + resolved "https://registry.yarnpkg.com/@rushstack/ts-command-line/-/ts-command-line-4.19.1.tgz#288ee54dd607e558a8be07705869c16c31b5c3ef" + integrity sha512-J7H768dgcpG60d7skZ5uSSwyCZs/S2HrWP1Ds8d1qYAyaaeJmpmmLr9BVw97RjFzmQPOYnoXcKA4GkqDCkduQg== dependencies: + "@rushstack/terminal" "0.10.0" "@types/argparse" "1.0.38" argparse "~1.0.9" - colors "~1.2.1" string-argv "~0.3.1" "@safe-global/safe-apps-provider@^0.15.2": @@ -4912,11 +4904,11 @@ ethers "^5.7.2" "@safe-global/safe-gateway-typescript-sdk@^3.5.3": - version "3.13.3" - resolved "https://registry.yarnpkg.com/@safe-global/safe-gateway-typescript-sdk/-/safe-gateway-typescript-sdk-3.13.3.tgz#f0093af02ee0af3c0dcad9ef2dc96890a80fe481" - integrity sha512-qBDM469cVCedpBpeTSn+k5FUr9+rq5bMTflp/mKd7h35uafcexvOR/PHZn2qftqV8b1kc9b8t22cPRJ2365jew== + version "3.19.0" + resolved "https://registry.yarnpkg.com/@safe-global/safe-gateway-typescript-sdk/-/safe-gateway-typescript-sdk-3.19.0.tgz#18637c205c83bfc0a6be5fddbf202d6bb4927302" + integrity sha512-TRlP05KY6t3wjLJ74FiirWlEt3xTclnUQM2YdYto1jx5G1o0meMnugIUZXhzm7Bs3rDEDNhz/aDf2KMSZtoCFg== -"@scure/base@~1.1.0", "@scure/base@~1.1.2": +"@scure/base@~1.1.0", "@scure/base@~1.1.2", "@scure/base@~1.1.4": version "1.1.5" resolved "https://registry.yarnpkg.com/@scure/base/-/base-1.1.5.tgz#1d85d17269fe97694b9c592552dd9e5e33552157" integrity sha512-Brj9FiG2W1MRQSTB212YVPRrcbjkv48FoZi/u4l/zds/ieRrqsh7aUf6CLwkAq61oKXr/ZlTzlY66gLIj3TFTQ== @@ -4930,15 +4922,6 @@ "@noble/secp256k1" "~1.7.0" "@scure/base" "~1.1.0" -"@scure/bip32@1.3.1": - version "1.3.1" - resolved "https://registry.yarnpkg.com/@scure/bip32/-/bip32-1.3.1.tgz#7248aea723667f98160f593d621c47e208ccbb10" - integrity sha512-osvveYtyzdEVbt3OfwwXFr4P2iVBL5u1Q3q4ONBfDY/UpOuXmOlbgwc1xECEboY8wIays8Yt6onaWMUdUbfl0A== - dependencies: - "@noble/curves" "~1.1.0" - "@noble/hashes" "~1.3.1" - "@scure/base" "~1.1.0" - "@scure/bip32@1.3.2": version "1.3.2" resolved "https://registry.yarnpkg.com/@scure/bip32/-/bip32-1.3.2.tgz#90e78c027d5e30f0b22c1f8d50ff12f3fb7559f8" @@ -4948,6 +4931,15 @@ "@noble/hashes" "~1.3.2" "@scure/base" "~1.1.2" +"@scure/bip32@1.3.3": + version "1.3.3" + resolved "https://registry.yarnpkg.com/@scure/bip32/-/bip32-1.3.3.tgz#a9624991dc8767087c57999a5d79488f48eae6c8" + integrity sha512-LJaN3HwRbfQK0X1xFSi0Q9amqOgzQnnDngIt+ZlsBC3Bm7/nE7K0kwshZHyaru79yIVRv/e1mQAjZyuZG6jOFQ== + dependencies: + "@noble/curves" "~1.3.0" + "@noble/hashes" "~1.3.2" + "@scure/base" "~1.1.4" + "@scure/bip39@1.1.1": version "1.1.1" resolved "https://registry.yarnpkg.com/@scure/bip39/-/bip39-1.1.1.tgz#b54557b2e86214319405db819c4b6a370cf340c5" @@ -4964,6 +4956,14 @@ "@noble/hashes" "~1.3.0" "@scure/base" "~1.1.0" +"@scure/bip39@1.2.2": + version "1.2.2" + resolved "https://registry.yarnpkg.com/@scure/bip39/-/bip39-1.2.2.tgz#f3426813f4ced11a47489cbcf7294aa963966527" + integrity sha512-HYf9TUXG80beW+hGAt3TRM8wU6pQoYur9iNypTROm42dorCGmLnFe3eWjz3gOq6G62H2WRh0FCzAR1PI+29zIA== + dependencies: + "@noble/hashes" "~1.3.2" + "@scure/base" "~1.1.4" + "@sendgrid/client@^7.7.0": version "7.7.0" resolved "https://registry.yarnpkg.com/@sendgrid/client/-/client-7.7.0.tgz#f8f67abd604205a0d0b1af091b61517ef465fdbf" @@ -5174,9 +5174,9 @@ type-detect "4.0.8" "@sinonjs/commons@^3.0.0": - version "3.0.0" - resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-3.0.0.tgz#beb434fe875d965265e04722ccfc21df7f755d72" - integrity sha512-jXBtWAF4vmdNmZgD5FoKsVLv3rPgDnLgPbU84LIJ3otV44vJlDRokVng5v8NFJdCf/da9legHcKaRuZs4L7faA== + version "3.0.1" + resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-3.0.1.tgz#1029357e44ca901a615585f6d27738dbc89084cd" + integrity sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ== dependencies: type-detect "4.0.8" @@ -5252,12 +5252,12 @@ bn.js "^5.2.1" web3-utils "^1.8.1" -"@smithy/types@^2.8.0": - version "2.8.0" - resolved "https://registry.yarnpkg.com/@smithy/types/-/types-2.8.0.tgz#bdbaa0a54c9c3538d6c763c6f32d3e4f76fe0df9" - integrity sha512-h9sz24cFgt/W1Re22OlhQKmUZkNh244ApgRsUDYinqF8R+QgcsBIX344u2j61TPshsTz3CvL6HYU1DnQdsSrHA== +"@smithy/types@^2.12.0": + version "2.12.0" + resolved "https://registry.yarnpkg.com/@smithy/types/-/types-2.12.0.tgz#c44845f8ba07e5e8c88eda5aed7e6a0c462da041" + integrity sha512-QwYgloJ0sVNBeBuBs65cIkTbfzV/Q6ZNPCJ99EICFEdJYG50nGIY/uYXp+TbsdJReIuPr0a0kXmCvren3MbRRw== dependencies: - tslib "^2.5.0" + tslib "^2.6.2" "@solana/buffer-layout@^4.0.1": version "4.0.1" @@ -5267,13 +5267,13 @@ buffer "~6.0.3" "@solana/web3.js@^1.70.1": - version "1.88.0" - resolved "https://registry.yarnpkg.com/@solana/web3.js/-/web3.js-1.88.0.tgz#24e1482f63ac54914430b4ce5ab36eaf433ecdb8" - integrity sha512-E4BdfB0HZpb66OPFhIzPApNE2tG75Mc6XKIoeymUkx/IV+USSYuxDX29sjgE/KGNYxggrOf4YuYnRMI6UiPL8w== + version "1.91.1" + resolved "https://registry.yarnpkg.com/@solana/web3.js/-/web3.js-1.91.1.tgz#d49d2f982b52070be3b987fd8d892fcbddd064b5" + integrity sha512-cPgjZXm688oM9cULvJ8u2VH6Qp5rvptE1N1VODVxn2mAbpZsWrvWNPjmASkMYT/HzyrtqFkPvFdSHg8Xjt7aQA== dependencies: "@babel/runtime" "^7.23.4" "@noble/curves" "^1.2.0" - "@noble/hashes" "^1.3.2" + "@noble/hashes" "^1.3.3" "@solana/buffer-layout" "^4.0.1" agentkeepalive "^4.5.0" bigint-buffer "^1.1.5" @@ -5294,18 +5294,16 @@ dependencies: antlr4ts "^0.5.0-alpha.4" -"@solidity-parser/parser@^0.16.0": - version "0.16.2" - resolved "https://registry.yarnpkg.com/@solidity-parser/parser/-/parser-0.16.2.tgz#42cb1e3d88b3e8029b0c9befff00b634cd92d2fa" - integrity sha512-PI9NfoA3P8XK2VBkK5oIfRgKDsicwDZfkVq9ZTBCQYGOP1N2owgY2dyLGyU5/J/hQs8KRk55kdmvTLjy3Mu3vg== - dependencies: - antlr4ts "^0.5.0-alpha.4" - "@solidity-parser/parser@^0.17.0": version "0.17.0" resolved "https://registry.yarnpkg.com/@solidity-parser/parser/-/parser-0.17.0.tgz#52a2fcc97ff609f72011014e4c5b485ec52243ef" integrity sha512-Nko8R0/kUo391jsEHHxrGM07QFdnPGvlmox4rmH0kNiNAashItAilhy4Mv4pK5gQmW5f4sXAF58fwJbmlkGcVw== +"@solidity-parser/parser@^0.18.0": + version "0.18.0" + resolved "https://registry.yarnpkg.com/@solidity-parser/parser/-/parser-0.18.0.tgz#8e77a02a09ecce957255a2f48c9a7178ec191908" + integrity sha512-yfORGUIPgLck41qyN7nbwJRAx17/jAIXCTanHOJZhB6PJ1iAk/84b/xlsVKFSyNyLXIj0dhppoE0+CRws7wlzA== + "@sqltools/formatter@^1.2.5": version "1.2.5" resolved "https://registry.yarnpkg.com/@sqltools/formatter/-/formatter-1.2.5.tgz#3abc203c79b8c3e90fd6c156a0c62d5403520e12" @@ -6022,34 +6020,7 @@ prettier "2.8.4" typescript "5.2.2" -"@strapi/ui-primitives@^1.13.0", "@strapi/ui-primitives@^1.13.1": - version "1.14.1" - resolved "https://registry.yarnpkg.com/@strapi/ui-primitives/-/ui-primitives-1.14.1.tgz#f4100f68874754088322bdb6da98a206fa9fbc2d" - integrity sha512-AmwyfZuazN7J1AgVf7i7oly+zwcJdWFqh/UCd3uPtoonnPmdCIRjkK8aBWlU9M+k3277FGIaCHOwNHiMSiBbbA== - dependencies: - "@radix-ui/number" "^1.0.1" - "@radix-ui/primitive" "^1.0.1" - "@radix-ui/react-collection" "1.0.3" - "@radix-ui/react-compose-refs" "^1.0.1" - "@radix-ui/react-context" "^1.0.1" - "@radix-ui/react-direction" "1.0.1" - "@radix-ui/react-dismissable-layer" "^1.0.5" - "@radix-ui/react-focus-guards" "1.0.1" - "@radix-ui/react-focus-scope" "1.0.4" - "@radix-ui/react-id" "^1.0.1" - "@radix-ui/react-popper" "^1.1.3" - "@radix-ui/react-portal" "^1.0.4" - "@radix-ui/react-primitive" "^1.0.3" - "@radix-ui/react-slot" "^1.0.2" - "@radix-ui/react-use-callback-ref" "^1.0.1" - "@radix-ui/react-use-controllable-state" "^1.0.1" - "@radix-ui/react-use-layout-effect" "1.0.1" - "@radix-ui/react-use-previous" "^1.0.1" - "@radix-ui/react-visually-hidden" "^1.0.3" - aria-hidden "^1.2.3" - react-remove-scroll "^2.5.7" - -"@strapi/ui-primitives@^1.16.0": +"@strapi/ui-primitives@^1.13.0", "@strapi/ui-primitives@^1.13.1", "@strapi/ui-primitives@^1.16.0": version "1.16.0" resolved "https://registry.yarnpkg.com/@strapi/ui-primitives/-/ui-primitives-1.16.0.tgz#6156c1493c2929945ed6591ca3f340477be4c4b0" integrity sha512-ATJPrLI9K/Cq9gGlLa93KAXBdc4rkWRg7GOogOeukohy4a8CzcQTK0l7Lp3EeXSZKwcOU5ohDcC43BXB0eSlyg== @@ -6113,9 +6084,9 @@ yup "0.32.9" "@stripe/react-stripe-js@^2.4.0": - version "2.4.0" - resolved "https://registry.yarnpkg.com/@stripe/react-stripe-js/-/react-stripe-js-2.4.0.tgz#b69d383a2b13e1104c5766695e7a865899cc84fb" - integrity sha512-1jVQEL3OuhuzNlf4OdfqovHt+MkWh8Uh8xpLxx/xUFUDdF+7/kDOrGKy+xJO3WLCfZUL7NAy+/ypwXbbYZi0tg== + version "2.6.2" + resolved "https://registry.yarnpkg.com/@stripe/react-stripe-js/-/react-stripe-js-2.6.2.tgz#0b0c81d0c4174b44fdd1b5c7fa58754dd4f86bca" + integrity sha512-FSjNg4v7BiCfojvx25PQ8DugOa09cGk1t816R/DLI/lT+1bgRAYpMvoPirLT4ZQ3ev/0VDtPdWNaabPsLDTOMA== dependencies: prop-types "^15.7.2" @@ -6125,9 +6096,9 @@ integrity sha512-WFkQx1mbs2b5+7looI9IV1BLa3bIApuN3ehp9FP58xGg7KL9hCHDECgW3BwO9l9L+xBPVAD7Yjn1EhGe6EDTeA== "@swc/helpers@^0.5.0": - version "0.5.3" - resolved "https://registry.yarnpkg.com/@swc/helpers/-/helpers-0.5.3.tgz#98c6da1e196f5f08f977658b80d6bd941b5f294f" - integrity sha512-FaruWX6KdudYloq1AHD/4nU+UsMTdNE8CKyrseXWEcgjDAbvkwJg2QGPAnfIJLIWsjZOSPLOAykK6fuYp4vp4A== + version "0.5.7" + resolved "https://registry.yarnpkg.com/@swc/helpers/-/helpers-0.5.7.tgz#36c05f61b412abcff3616ecc8634623bcc7c9618" + integrity sha512-BVvNZhx362+l2tSwSuyEUV4h7+jk9raNdoTSdLfwTshXJSaGmYKluGRJznziCI3KX02Z19DdsQrdfrpXAU3Hfg== dependencies: tslib "^2.4.0" @@ -6173,17 +6144,19 @@ use-sync-external-store "^1.2.0" "@tenderly/hardhat-tenderly@^2.0.1": - version "2.1.0" - resolved "https://registry.yarnpkg.com/@tenderly/hardhat-tenderly/-/hardhat-tenderly-2.1.0.tgz#20036426da8f11a5d8860703ef64cdb9086cc98d" - integrity sha512-wy6WnvrT4fxqTsln5DH3MgT+lvUV7AyqHVtSyGJgQh6NX0Q59ZXKoqedB8Hi3IkYMOhbjbPFlR0Z/zr8sYGEzQ== + version "2.2.2" + resolved "https://registry.yarnpkg.com/@tenderly/hardhat-tenderly/-/hardhat-tenderly-2.2.2.tgz#e9ff3e149af2244a8448e0ff968717f6f1ba602f" + integrity sha512-JZINDVHW0ob+tCtNppgXMKiVcpbtcdEeHGaIsRFDXGeVQ1061asouf1lILvyWSfhv5ZeIkEX/LmeOo9IlC7rkw== dependencies: "@ethersproject/bignumber" "^5.7.0" "@nomicfoundation/hardhat-ethers" "^3.0.4" - axios "^0.27.2" + "@openzeppelin/hardhat-upgrades" "^3.0.1" + "@openzeppelin/upgrades-core" "^1.32.2" + axios "^1.6.7" ethers "^6.8.1" fs-extra "^10.1.0" hardhat-deploy "^0.11.43" - tenderly "^0.8.0" + tenderly "^0.9.1" ts-node "^10.9.1" tslog "^4.3.1" typescript "^5.2.2" @@ -6217,9 +6190,9 @@ redent "^3.0.0" "@testing-library/react@^14.2.1": - version "14.2.1" - resolved "https://registry.yarnpkg.com/@testing-library/react/-/react-14.2.1.tgz#bf69aa3f71c36133349976a4a2da3687561d8310" - integrity sha512-sGdjws32ai5TLerhvzThYFbpnF9XtL65Cjf+gB0Dhr29BGqK+mAeN7SURSdu+eqgET4ANcWoC7FQpkaiGvBr+A== + version "14.2.2" + resolved "https://registry.yarnpkg.com/@testing-library/react/-/react-14.2.2.tgz#74f855215c57d423282486a395a4348a837d3c5a" + integrity sha512-SOUuM2ysCvjUWBXTNfQ/ztmnKDmqaiPV3SvoIuyxMUca45rbSWWAT/qB8CUs/JQ/ux/8JFs9DNdFQ3f6jH3crA== dependencies: "@babel/runtime" "^7.12.5" "@testing-library/dom" "^9.0.0" @@ -6372,9 +6345,9 @@ "@types/chai" "*" "@types/chai@*", "@types/chai@^4.3.3", "@types/chai@^4.3.4": - version "4.3.11" - resolved "https://registry.yarnpkg.com/@types/chai/-/chai-4.3.11.tgz#e95050bf79a932cb7305dd130254ccdf9bde671c" - integrity sha512-qQR1dr2rGIHYlJulmr8Ioq3De0Le9E4MJ5AiaeAETJJpndT1uUNHsGFK3L/UIu+rbkQSdj8J/w2bCsBZc/Y5fQ== + version "4.3.13" + resolved "https://registry.yarnpkg.com/@types/chai/-/chai-4.3.13.tgz#1b152232b93959829842177cbdfc756ed27e9fd9" + integrity sha512-+LxQEbg4BDUf88utmhpUpTyYn1zHao443aGnXIAQak9ZMt9Rtsic0Oig0OS1xyIqdDXc5uMekoC6NaiUlkT/qA== "@types/chrome@^0.0.246": version "0.0.246" @@ -6406,9 +6379,9 @@ "@types/node" "*" "@types/cookie-parser@^1.4.3": - version "1.4.6" - resolved "https://registry.yarnpkg.com/@types/cookie-parser/-/cookie-parser-1.4.6.tgz#002643c514cccf883a65cbe044dbdc38c0b92ade" - integrity sha512-KoooCrD56qlLskXPLGUiJxOMnv5l/8m7cQD2OxJ73NPMhuSz9PmvwRD6EpjDyKBVrdJDdQ4bQK7JFNHnNmax0w== + version "1.4.7" + resolved "https://registry.yarnpkg.com/@types/cookie-parser/-/cookie-parser-1.4.7.tgz#c874471f888c72423d78d2b3c32d1e8579cf3c8f" + integrity sha512-Fvuyi354Z+uayxzIGCwYTayFKocfV7TuDYZClCdIP9ckhvAu/ixDtCB6qx2TT0FKjPLf1f3P/J1rgf6lPs64mw== dependencies: "@types/express" "*" @@ -6425,9 +6398,9 @@ "@types/node" "*" "@types/crypto-js@^4.1.2": - version "4.2.1" - resolved "https://registry.yarnpkg.com/@types/crypto-js/-/crypto-js-4.2.1.tgz#480edd76991a63050cb88db1a8840758c55a7135" - integrity sha512-FSPGd9+OcSok3RsM0UZ/9fcvMOXJ1ENE/ZbLfOPlBWj7BgXtEAM8VYfTtT760GiLbQIMoVozwVuisjvsVwqYWw== + version "4.2.2" + resolved "https://registry.yarnpkg.com/@types/crypto-js/-/crypto-js-4.2.2.tgz#771c4a768d94eb5922cc202a3009558204df0cea" + integrity sha512-sDOLlVbHhXpAUAL0YHDUUwDZf3iN4Bwi4W6a0W0b+QcAezUbRtH4FVb+9J4h+XFPW7l/gQ9F8qC7P+Ec4k8QVQ== "@types/d3-array@^3.0.3": version "3.2.1" @@ -6452,9 +6425,9 @@ "@types/d3-color" "*" "@types/d3-path@*": - version "3.0.2" - resolved "https://registry.yarnpkg.com/@types/d3-path/-/d3-path-3.0.2.tgz#4327f4a05d475cf9be46a93fc2e0f8d23380805a" - integrity sha512-WAIEVlOCdd/NKRYTsqCpOMHQHemKBEINf8YXMYOtXH0GA7SY0dqMB78P3Uhgfy+4X+/Mlw2wDtlETkN6kQUCMA== + version "3.1.0" + resolved "https://registry.yarnpkg.com/@types/d3-path/-/d3-path-3.1.0.tgz#2b907adce762a78e98828f0b438eaca339ae410a" + integrity sha512-P2dlU/q51fkOc/Gfl3Ul9kicV7l+ra934qBFXCFhrZMOL6du1TM0pm1ThYvENukyOn5h9v+yMJ9Fn5JK4QozrQ== "@types/d3-scale@^4.0.2": version "4.0.8" @@ -6496,22 +6469,22 @@ "@types/estree" "*" "@types/eslint@*": - version "8.56.1" - resolved "https://registry.yarnpkg.com/@types/eslint/-/eslint-8.56.1.tgz#988cabb39c973e9200f35fdbb29d17992965bb08" - integrity sha512-18PLWRzhy9glDQp3+wOgfLYRWlhgX0azxgJ63rdpoUHyrC9z0f5CkFburjQx4uD7ZCruw85ZtMt6K+L+R8fLJQ== + version "8.56.6" + resolved "https://registry.yarnpkg.com/@types/eslint/-/eslint-8.56.6.tgz#d5dc16cac025d313ee101108ba5714ea10eb3ed0" + integrity sha512-ymwc+qb1XkjT/gfoQwxIeHZ6ixH23A+tCT2ADSA/DPVKzAjwYkTXBMCQ/f6fe4wEa85Lhp26VPeUxI7wMhAi7A== dependencies: "@types/estree" "*" "@types/json-schema" "*" -"@types/estree@*", "@types/estree@1.0.5", "@types/estree@^1.0.0": +"@types/estree@*", "@types/estree@1.0.5", "@types/estree@^1.0.0", "@types/estree@^1.0.5": version "1.0.5" resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.5.tgz#a6ce3e556e00fd9895dd872dd172ad0d4bd687f4" integrity sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw== "@types/express-serve-static-core@^4.17.33": - version "4.17.41" - resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.17.41.tgz#5077defa630c2e8d28aa9ffc2c01c157c305bef6" - integrity sha512-OaJ7XLaelTgrvlZD8/aa0vvvxZdUmlCn6MtWeB7TkiKW70BQLc9XEPpDLPdbo52ZhXUCrznlWdCHWxJWtdyajA== + version "4.17.43" + resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.17.43.tgz#10d8444be560cb789c4735aea5eac6e5af45df54" + integrity sha512-oaYtiBirUOPQGSWNGPWnzyAFJ0BP3cwvN4oWZQY+zUBwpVIGsKUkpBpSztp74drYcjavs7SKFZ4DX1V2QeN8rg== dependencies: "@types/node" "*" "@types/qs" "*" @@ -6519,9 +6492,9 @@ "@types/send" "*" "@types/express-session@^1.17.10": - version "1.17.10" - resolved "https://registry.yarnpkg.com/@types/express-session/-/express-session-1.17.10.tgz#3a9394f1f314a4c657af3fb1cdb52f00fc207fd2" - integrity sha512-U32bC/s0ejXijw5MAzyaV4tuZopCh/K7fPoUDyNbsRXHvPSeymygYD1RFL99YOLhF5PNOkzswvOTRaVHdL1zMw== + version "1.18.0" + resolved "https://registry.yarnpkg.com/@types/express-session/-/express-session-1.18.0.tgz#7c6f25c3604b28d6bc08a2e3929997bbc7672fa2" + integrity sha512-27JdDRgor6PoYlURY+Y5kCakqp5ulC0kmf7y+QwaY+hv9jEFuQOThgkjyA53RP3jmKuBsH5GR6qEfFmvb8mwOA== dependencies: "@types/express" "*" @@ -6548,9 +6521,9 @@ "@types/filewriter" "*" "@types/filewriter@*": - version "0.0.32" - resolved "https://registry.yarnpkg.com/@types/filewriter/-/filewriter-0.0.32.tgz#3cf7e0f870e54e60ed1bbd9280fa24a9444d3b48" - integrity sha512-Kpi2GXQyYJdjL8mFclL1eDgihn1SIzorMZjD94kdPZh9E4VxGOeyjPxi5LpsM4Zku7P0reqegZTt2GxhmA9VBg== + version "0.0.33" + resolved "https://registry.yarnpkg.com/@types/filewriter/-/filewriter-0.0.33.tgz#d9d611db9d9cd99ae4e458de420eeb64ad604ea8" + integrity sha512-xFU8ZXTw4gd358lb2jw25nxY9QAgqn2+bKKjKOYfNCzN4DKCFetK7sPtrlpg66Ywe3vWY9FNxprZawAh9wfJ3g== "@types/fined@*": version "1.1.5" @@ -6669,7 +6642,7 @@ expect "^29.0.0" pretty-format "^29.0.0" -"@types/jest@29.5.11", "@types/jest@^29.5.11": +"@types/jest@29.5.11": version "29.5.11" resolved "https://registry.yarnpkg.com/@types/jest/-/jest-29.5.11.tgz#0c13aa0da7d0929f078ab080ae5d4ced80fa2f2c" integrity sha512-S2mHmYIVe13vrm6q4kN6fLYYAka15ALQki/vgDC3mIukEOx8WJlv0kQPM+d4w8Gp6u0uSdKND04IlTXBv0rwnQ== @@ -6677,6 +6650,14 @@ expect "^29.0.0" pretty-format "^29.0.0" +"@types/jest@^29.5.11": + version "29.5.12" + resolved "https://registry.yarnpkg.com/@types/jest/-/jest-29.5.12.tgz#7f7dc6eb4cf246d2474ed78744b05d06ce025544" + integrity sha512-eDC8bTvT/QhYdxJAulQikueigY5AsdBRH2yDKW3yveW7svY3+DzN84/2NUgkw10RTiJbWqZrTtoGVdYlvFJdLw== + dependencies: + expect "^29.0.0" + pretty-format "^29.0.0" + "@types/jsdom@^20.0.0": version "20.0.1" resolved "https://registry.yarnpkg.com/@types/jsdom/-/jsdom-20.0.1.tgz#07c14bc19bd2f918c1929541cdaacae894744808" @@ -6701,7 +6682,14 @@ resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee" integrity sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ== -"@types/jsonwebtoken@*", "@types/jsonwebtoken@9.0.5": +"@types/jsonwebtoken@*": + version "9.0.6" + resolved "https://registry.yarnpkg.com/@types/jsonwebtoken/-/jsonwebtoken-9.0.6.tgz#d1af3544d99ad992fb6681bbe60676e06b032bd3" + integrity sha512-/5hndP5dCjloafCXns6SZyESp3Ldq7YjH3zwzwczYnjxIT0Fqzk5ROSYVGfFyczIue7IUEj8hkvLbPoLQ18vQw== + dependencies: + "@types/node" "*" + +"@types/jsonwebtoken@9.0.5": version "9.0.5" resolved "https://registry.yarnpkg.com/@types/jsonwebtoken/-/jsonwebtoken-9.0.5.tgz#0bd9b841c9e6c5a937c17656e2368f65da025588" integrity sha512-VRLSGzik+Unrup6BsouBeHsf4d1hOEgYWTm/7Nmw1sXoN1+tRly/Gy/po3yeahnP4jfnQWWAhQAqcNfH7ngOkA== @@ -6725,9 +6713,9 @@ "@types/node" "*" "@types/lodash@^4.14.149", "@types/lodash@^4.14.165": - version "4.14.202" - resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.202.tgz#f09dbd2fb082d507178b2f2a5c7e74bd72ff98f8" - integrity sha512-OvlIYQK9tNneDlS0VN54LLd5uiPCBOp7gS5Z0f1mjoJYBrtStzgmJBxONW3U6OZqdtNzZPmn9BS/7WI7BFFcFQ== + version "4.17.0" + resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.17.0.tgz#d774355e41f372d5350a4d0714abb48194a489c3" + integrity sha512-t7dhREVv6dbNj0q17X12j7yDG4bD/DHYX7o5/DbDxobP0HnGPgpRz2Ej77aL7TZT3DSw13fqUTj8J4mMnqa7WA== "@types/long@^4.0.1": version "4.0.2" @@ -6784,10 +6772,10 @@ resolved "https://registry.yarnpkg.com/@types/ms/-/ms-0.7.34.tgz#10964ba0dee6ac4cd462e2795b6bebd407303433" integrity sha512-nG96G3Wp6acyAgJqGasjODb+acrI7KltPiRxzHPXnP3NgI28bpQDRv53olbqGXbfcgF5aiiHmO3xpwEpS5Ld9g== -"@types/node@*", "@types/node@20.11.20", "@types/node@>=13.7.0", "@types/node@>=8.1.0", "@types/node@^20.11.20": - version "20.11.20" - resolved "https://registry.yarnpkg.com/@types/node/-/node-20.11.20.tgz#f0a2aee575215149a62784210ad88b3a34843659" - integrity sha512-7/rR21OS+fq8IyHTgtLkDK949uzsa6n8BkziAKtPVpugIkO6D+/ooXMvzXxDnZrmtXVfjb1bKQafYpb8s89LOg== +"@types/node@*", "@types/node@>=13.7.0", "@types/node@>=8.1.0", "@types/node@^20.11.20": + version "20.11.30" + resolved "https://registry.yarnpkg.com/@types/node/-/node-20.11.30.tgz#9c33467fc23167a347e73834f788f4b9f399d66f" + integrity sha512-dHM6ZxwlmuZaRmUPfv1p+KrdD1Dci04FbdEm/9wEMouFqxYoFl5aMkt0VMAUtYRQDyYvD41WJLukhq/ha3YuTw== dependencies: undici-types "~5.26.4" @@ -6801,6 +6789,13 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-18.15.13.tgz#f64277c341150c979e42b00e4ac289290c9df469" integrity sha512-N+0kuo9KgrUQ1Sn/ifDXsvg0TTleP7rIy4zOBGECxAljqvqfqpTfzx0Q1NUedOixRMBfe2Whhb056a42cWs26Q== +"@types/node@20.11.20": + version "20.11.20" + resolved "https://registry.yarnpkg.com/@types/node/-/node-20.11.20.tgz#f0a2aee575215149a62784210ad88b3a34843659" + integrity sha512-7/rR21OS+fq8IyHTgtLkDK949uzsa6n8BkziAKtPVpugIkO6D+/ooXMvzXxDnZrmtXVfjb1bKQafYpb8s89LOg== + dependencies: + undici-types "~5.26.4" + "@types/node@^10.0.3": version "10.17.60" resolved "https://registry.yarnpkg.com/@types/node/-/node-10.17.60.tgz#35f3d6213daed95da7f0f73e75bcc6980e90597b" @@ -6873,9 +6868,9 @@ integrity sha512-ga8y9v9uyeiLdpKddhxYQkxNDrfvuPrlFb0N1qnZZByvcElJaXthF1UhvCh9TLWJBEHeNtdnbysW7Y6Uq8CVng== "@types/qs@*", "@types/qs@^6.2.31", "@types/qs@^6.9.7": - version "6.9.11" - resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.9.11.tgz#208d8a30bc507bd82e03ada29e4732ea46a6bbda" - integrity sha512-oGk0gmhnEJK4Yyk+oI7EfXsLayXatCWPHary1MtcmbAifkobT9cM9yutG/hZKIseOU0MqbIwQ/u2nn/Gb+ltuQ== + version "6.9.13" + resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.9.13.tgz#c7e2406bdc6bd512f8a3651632568c72d43eb1e7" + integrity sha512-iLR+1vTTJ3p0QaOUq6ACbY1mzKTODFDT/XedZI8BksOotFmL4ForwDfRQ/DZeuTHR7/2i4lI1D203gdfxuqTlA== "@types/range-parser@*": version "1.2.7" @@ -6883,9 +6878,9 @@ integrity sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ== "@types/react-dom@^18.0.0", "@types/react-dom@^18.2.14": - version "18.2.18" - resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-18.2.18.tgz#16946e6cd43971256d874bc3d0a72074bb8571dd" - integrity sha512-TJxDm6OfAX2KJWJdMEVTwWke5Sc/E/RlnPGvGfS0W7+6ocy2xhDVQVh/KvC2Uf7kACs+gDytdusDSdWfWkaNzw== + version "18.2.22" + resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-18.2.22.tgz#d332febf0815403de6da8a97e5fe282cbe609bae" + integrity sha512-fHkBXPeNtfvri6gdsMYyW+dW7RXFo6Ad09nLFK0VQWR7yGLai/Cyvyj696gbwYvBnhGtevUG9cET0pmUbMtoPQ== dependencies: "@types/react" "*" @@ -6909,9 +6904,9 @@ "@types/react" "*" "@types/react@*", "@types/react@16 || 17 || 18", "@types/react@^18.2.43": - version "18.2.47" - resolved "https://registry.yarnpkg.com/@types/react/-/react-18.2.47.tgz#85074b27ab563df01fbc3f68dc64bf7050b0af40" - integrity sha512-xquNkkOirwyCgoClNk85BjP+aqnIS+ckAJ8i37gAbDs14jfW/J23f2GItAf33oiUPQnqNMALiFeoM9Y5mbjpVQ== + version "18.2.67" + resolved "https://registry.yarnpkg.com/@types/react/-/react-18.2.67.tgz#96b7af0b5e79c756f4bdd981de2ca28472c858e5" + integrity sha512-vkIE2vTIMHQ/xL0rgmuoECBCkZFZeHr49HeWSc24AptMbNRo7pwSBvj73rlJJs9fGKj0koS+V7kQB1jHS0uCgw== dependencies: "@types/prop-types" "*" "@types/scheduler" "*" @@ -6947,9 +6942,9 @@ "@types/node" "*" "@types/semver@^7.3.12", "@types/semver@^7.5.0": - version "7.5.6" - resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.5.6.tgz#c65b2bfce1bec346582c07724e3f8c1017a20339" - integrity sha512-dn1l8LaMea/IjDoHNd9J52uBbInB796CDffS6VdIxvqYCPSG0V0DzHp76GpaWnlhg88uYyPbXCDIowa86ybd5A== + version "7.5.8" + resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.5.8.tgz#8268a8c57a3e4abd25c165ecd36237db7948a55e" + integrity sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ== "@types/send@*": version "0.17.4" @@ -6973,19 +6968,10 @@ resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-2.0.3.tgz#6209321eb2c1712a7e7466422b8cb1fc0d9dd5d8" integrity sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw== -"@types/superagent@*": - version "8.1.3" - resolved "https://registry.yarnpkg.com/@types/superagent/-/superagent-8.1.3.tgz#6222a466e89eac9c84ad8de11870d92097e6554a" - integrity sha512-R/CfN6w2XsixLb1Ii8INfn+BT9sGPvw74OavfkW4SwY+jeUcAwLZv2+bXLJkndnimxjEBm0RPHgcjW9pLCa8cw== - dependencies: - "@types/cookiejar" "^2.1.5" - "@types/methods" "^1.1.4" - "@types/node" "*" - -"@types/superagent@^8.1.0": - version "8.1.1" - resolved "https://registry.yarnpkg.com/@types/superagent/-/superagent-8.1.1.tgz#dbc620c5df3770b0c3092f947d6d5e808adae2bc" - integrity sha512-YQyEXA4PgCl7EVOoSAS3o0fyPFU6erv5mMixztQYe1bqbWmmn8c+IrqoxjQeZe4MgwXikgcaZPiI/DsbmOVlzA== +"@types/superagent@*", "@types/superagent@^8.1.0": + version "8.1.4" + resolved "https://registry.yarnpkg.com/@types/superagent/-/superagent-8.1.4.tgz#f8290d1b7d6081f84f3047851c190c4a3c8cdb21" + integrity sha512-uzSBYwrpal8y2X2Pul5ZSWpzRiDha2FLcquaN95qUPnOjYgm/zQ5LIdqeJpQJTRWNTN+Rhm0aC8H06Ds2rqCYw== dependencies: "@types/cookiejar" "^2.1.5" "@types/methods" "^1.1.4" @@ -7034,14 +7020,14 @@ integrity sha512-EwmlvuaxPNej9+T4v5AuBPJa2x2UOJVdjCtDHgcDqitUeOtjnJKJ+apYjVcAoBEMjKW1VVFGZLUb5+qqa09XFA== "@types/uuid@^9.0.6": - version "9.0.7" - resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-9.0.7.tgz#b14cebc75455eeeb160d5fe23c2fcc0c64f724d8" - integrity sha512-WUtIVRUZ9i5dYXefDEAI7sh9/O7jGvHg7Df/5O/gtH3Yabe5odI3UWopVR1qbPXQtvOxWu3mM4XxlYeZtMWF4g== + version "9.0.8" + resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-9.0.8.tgz#7545ba4fc3c003d6c756f651f3bf163d8f0f29ba" + integrity sha512-jg+97EGIcY9AGHJJRaaPVgetKDsrTgbRjQ5Msgjh/DQKEFl0DtyRr/VCOyD1T2R1MNeWPK/u7JoGhlDZnKBAfA== "@types/validator@^13.11.8": - version "13.11.8" - resolved "https://registry.yarnpkg.com/@types/validator/-/validator-13.11.8.tgz#bb1162ec0fe6f87c95ca812f15b996fcc5e1e2dc" - integrity sha512-c/hzNDBh7eRF+KbCf+OoZxKbnkpaK/cKp9iLQWqB7muXtM+MtL9SUUH8vCFcLn6dH1Qm05jiexK0ofWY7TfOhQ== + version "13.11.9" + resolved "https://registry.yarnpkg.com/@types/validator/-/validator-13.11.9.tgz#adfe96520b437a0eaa798a475877bf2f75ee402d" + integrity sha512-FCTsikRozryfayPuiI46QzH3fnrOoctTjvOYZkho9BTFLCOZ2rgZJHMOVgCOfttjPJcgOx52EpkY0CMfy87MIw== "@types/ws@8.5.3": version "8.5.3" @@ -7136,13 +7122,13 @@ "@typescript-eslint/types" "5.62.0" "@typescript-eslint/visitor-keys" "5.62.0" -"@typescript-eslint/scope-manager@6.18.1": - version "6.18.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-6.18.1.tgz#28c31c60f6e5827996aa3560a538693cb4bd3848" - integrity sha512-BgdBwXPFmZzaZUuw6wKiHKIovms97a7eTImjkXCZE04TGHysG+0hDQPmygyvgtkoB/aOQwSM/nWv3LzrOIQOBw== +"@typescript-eslint/scope-manager@6.21.0": + version "6.21.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-6.21.0.tgz#ea8a9bfc8f1504a6ac5d59a6df308d3a0630a2b1" + integrity sha512-OwLUIWZJry80O99zvqXVEioyniJMa+d2GrqpUTqi5/v5D5rOrppJVBPa0yKCblcigC0/aYAzxxqQ1B+DS2RYsg== dependencies: - "@typescript-eslint/types" "6.18.1" - "@typescript-eslint/visitor-keys" "6.18.1" + "@typescript-eslint/types" "6.21.0" + "@typescript-eslint/visitor-keys" "6.21.0" "@typescript-eslint/type-utils@5.62.0": version "5.62.0" @@ -7159,10 +7145,10 @@ resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.62.0.tgz#258607e60effa309f067608931c3df6fed41fd2f" integrity sha512-87NVngcbVXUahrRTqIK27gD2t5Cu1yuCXxbLcFtCzZGlfyVWWh8mLHkoxzjsB6DDNnvdL+fW8MiwPEJyGJQDgQ== -"@typescript-eslint/types@6.18.1": - version "6.18.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-6.18.1.tgz#91617d8080bcd99ac355d9157079970d1d49fefc" - integrity sha512-4TuMAe+tc5oA7wwfqMtB0Y5OrREPF1GeJBAjqwgZh1lEMH5PJQgWgHGfYufVB51LtjD+peZylmeyxUXPfENLCw== +"@typescript-eslint/types@6.21.0": + version "6.21.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-6.21.0.tgz#205724c5123a8fef7ecd195075fa6e85bac3436d" + integrity sha512-1kFmZ1rOm5epu9NZEZm1kckCDGj5UJEf7P1kliH4LKu/RkwpsfqqGmY2OOcUs18lSlQBKLDYBOGxRVtrMN5lpg== "@typescript-eslint/typescript-estree@5.62.0": version "5.62.0" @@ -7177,13 +7163,13 @@ semver "^7.3.7" tsutils "^3.21.0" -"@typescript-eslint/typescript-estree@6.18.1": - version "6.18.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-6.18.1.tgz#a12b6440175b4cbc9d09ab3c4966c6b245215ab4" - integrity sha512-fv9B94UAhywPRhUeeV/v+3SBDvcPiLxRZJw/xZeeGgRLQZ6rLMG+8krrJUyIf6s1ecWTzlsbp0rlw7n9sjufHA== +"@typescript-eslint/typescript-estree@6.21.0": + version "6.21.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-6.21.0.tgz#c47ae7901db3b8bddc3ecd73daff2d0895688c46" + integrity sha512-6npJTkZcO+y2/kr+z0hc4HwNfrrP4kNYh57ek7yCNlrBjWQ1Y0OS7jiZTkgumrvkX5HkEKXFZkkdFNkaW2wmUQ== dependencies: - "@typescript-eslint/types" "6.18.1" - "@typescript-eslint/visitor-keys" "6.18.1" + "@typescript-eslint/types" "6.21.0" + "@typescript-eslint/visitor-keys" "6.21.0" debug "^4.3.4" globby "^11.1.0" is-glob "^4.0.3" @@ -7206,16 +7192,16 @@ semver "^7.3.7" "@typescript-eslint/utils@^6.10.0": - version "6.18.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-6.18.1.tgz#3451cfe2e56babb6ac657e10b6703393d4b82955" - integrity sha512-zZmTuVZvD1wpoceHvoQpOiewmWu3uP9FuTWo8vqpy2ffsmfCE8mklRPi+vmnIYAIk9t/4kOThri2QCDgor+OpQ== + version "6.21.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-6.21.0.tgz#4714e7a6b39e773c1c8e97ec587f520840cd8134" + integrity sha512-NfWVaC8HP9T8cbKQxHcsJBY5YE1O33+jpMwN45qzWWaPDZgLIbo12toGMWnmhvCpd3sIxkpDw3Wv1B3dYrbDQQ== dependencies: "@eslint-community/eslint-utils" "^4.4.0" "@types/json-schema" "^7.0.12" "@types/semver" "^7.5.0" - "@typescript-eslint/scope-manager" "6.18.1" - "@typescript-eslint/types" "6.18.1" - "@typescript-eslint/typescript-estree" "6.18.1" + "@typescript-eslint/scope-manager" "6.21.0" + "@typescript-eslint/types" "6.21.0" + "@typescript-eslint/typescript-estree" "6.21.0" semver "^7.5.4" "@typescript-eslint/visitor-keys@5.62.0": @@ -7226,12 +7212,12 @@ "@typescript-eslint/types" "5.62.0" eslint-visitor-keys "^3.3.0" -"@typescript-eslint/visitor-keys@6.18.1": - version "6.18.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-6.18.1.tgz#704d789bda2565a15475e7d22f145b8fe77443f4" - integrity sha512-/kvt0C5lRqGoCfsbmm7/CwMqoSkY3zzHLIjdhHZQW3VFrnz7ATecOHR7nb7V+xn4286MBxfnQfQhAmCI0u+bJA== +"@typescript-eslint/visitor-keys@6.21.0": + version "6.21.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-6.21.0.tgz#87a99d077aa507e20e238b11d56cc26ade45fe47" + integrity sha512-JJtkDduxLi9bivAB+cYOVMtbkqdPOhZ+ZI5LC47MIRrDV4Yn2o+ZnW10Nkmr28xRpSpdJ6Sm42Hjf2+REYXm0A== dependencies: - "@typescript-eslint/types" "6.18.1" + "@typescript-eslint/types" "6.21.0" eslint-visitor-keys "^3.4.1" "@ucast/core@^1.0.0", "@ucast/core@^1.4.1", "@ucast/core@^1.6.1": @@ -7240,9 +7226,9 @@ integrity sha512-ons5CwXZ/51wrUPfoduC+cO7AS1/wRb0ybpQJ9RrssossDxVy4t49QxWoWgfBDvVKsz9VXzBk9z0wqTdZ+Cq8g== "@ucast/js@^3.0.0": - version "3.0.3" - resolved "https://registry.yarnpkg.com/@ucast/js/-/js-3.0.3.tgz#6ff618a85bd95f1a8f46658cc663a1f798de327f" - integrity sha512-jBBqt57T5WagkAjqfCIIE5UYVdaXYgGkOFYv2+kjq2AVpZ2RIbwCo/TujJpDlwTVluUI+WpnRpoGU2tSGlEvFQ== + version "3.0.4" + resolved "https://registry.yarnpkg.com/@ucast/js/-/js-3.0.4.tgz#c57ec2182505c9ad63a5b08ff5911f89ac605262" + integrity sha512-TgG1aIaCMdcaEyckOZKQozn1hazE0w90SVdlpIJ/er8xVumE11gYAtSbw/LBeUnA4fFnFWTcw3t6reqseeH/4Q== dependencies: "@ucast/core" "^1.0.0" @@ -7262,19 +7248,6 @@ dependencies: "@ucast/core" "^1.4.1" -"@uiw/codemirror-extensions-basic-setup@4.21.21": - version "4.21.21" - resolved "https://registry.yarnpkg.com/@uiw/codemirror-extensions-basic-setup/-/codemirror-extensions-basic-setup-4.21.21.tgz#243ef309cb53253b14187649a7abc0d996420a20" - integrity sha512-+0i9dPrRSa8Mf0CvyrMvnAhajnqwsP3IMRRlaHDRgsSGL8igc4z7MhvUPn+7cWFAAqWzQRhMdMSWzo6/TEa3EA== - dependencies: - "@codemirror/autocomplete" "^6.0.0" - "@codemirror/commands" "^6.0.0" - "@codemirror/language" "^6.0.0" - "@codemirror/lint" "^6.0.0" - "@codemirror/search" "^6.0.0" - "@codemirror/state" "^6.0.0" - "@codemirror/view" "^6.0.0" - "@uiw/codemirror-extensions-basic-setup@4.21.24": version "4.21.24" resolved "https://registry.yarnpkg.com/@uiw/codemirror-extensions-basic-setup/-/codemirror-extensions-basic-setup-4.21.24.tgz#b936c3daff0100e1a3d5b0500478747cfc80f7db" @@ -7288,19 +7261,7 @@ "@codemirror/state" "^6.0.0" "@codemirror/view" "^6.0.0" -"@uiw/react-codemirror@^4.21.20": - version "4.21.21" - resolved "https://registry.yarnpkg.com/@uiw/react-codemirror/-/react-codemirror-4.21.21.tgz#986b18dbd6dc69aa470fc3d4e47b89b504af6778" - integrity sha512-PaxBMarufMWoR0qc5zuvBSt76rJ9POm9qoOaJbqRmnNL2viaF+d+Paf2blPSlm1JSnqn7hlRjio+40nZJ9TKzw== - dependencies: - "@babel/runtime" "^7.18.6" - "@codemirror/commands" "^6.1.0" - "@codemirror/state" "^6.1.1" - "@codemirror/theme-one-dark" "^6.0.0" - "@uiw/codemirror-extensions-basic-setup" "4.21.21" - codemirror "^6.0.0" - -"@uiw/react-codemirror@^4.21.24": +"@uiw/react-codemirror@^4.21.20", "@uiw/react-codemirror@^4.21.24": version "4.21.24" resolved "https://registry.yarnpkg.com/@uiw/react-codemirror/-/react-codemirror-4.21.24.tgz#38b05e0a24d2307313b2e73390b20d0251837170" integrity sha512-8zs5OuxbhikHocHBsVBMuW1vqlv4ccZAkt4rFwr7ebLP2Q6RwHsjpsR9GeGyAigAqonKRoeHugqF78UMrkaTgg== @@ -7884,10 +7845,10 @@ resolved "https://registry.yarnpkg.com/@web3-storage/multipart-parser/-/multipart-parser-1.0.0.tgz#6b69dc2a32a5b207ba43e556c25cc136a56659c4" integrity sha512-BEO6al7BYqcnfX15W2cnGR+Q566ACXAT9UQykORCWW80lmkpWsnEob6zJS1ZVBKsSJC8+7vJkHwlp+lXG1UCdw== -"@webassemblyjs/ast@1.11.6", "@webassemblyjs/ast@^1.11.5": - version "1.11.6" - resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.11.6.tgz#db046555d3c413f8966ca50a95176a0e2c642e24" - integrity sha512-IN1xI7PwOvLPgjcf180gC1bqn3q/QaOCwYUahIOhbYUu8KA/3tw2RT/T0Gidi1l7Hhj5D/INhJxiICObqpMu4Q== +"@webassemblyjs/ast@1.12.1", "@webassemblyjs/ast@^1.11.5": + version "1.12.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.12.1.tgz#bb16a0e8b1914f979f45864c23819cc3e3f0d4bb" + integrity sha512-EKfMUOPRRUTy5UII4qJDGPpqfwjOmZ5jeGFwid9mnoqIFK+e0vqoi1qH56JpmZSzEL53jKnNzScdmftJyG5xWg== dependencies: "@webassemblyjs/helper-numbers" "1.11.6" "@webassemblyjs/helper-wasm-bytecode" "1.11.6" @@ -7902,10 +7863,10 @@ resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.6.tgz#6132f68c4acd59dcd141c44b18cbebbd9f2fa768" integrity sha512-o0YkoP4pVu4rN8aTJgAyj9hC2Sv5UlkzCHhxqWj8butaLvnpdc2jOwh4ewE6CX0txSfLn/UYaV/pheS2Txg//Q== -"@webassemblyjs/helper-buffer@1.11.6": - version "1.11.6" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.6.tgz#b66d73c43e296fd5e88006f18524feb0f2c7c093" - integrity sha512-z3nFzdcp1mb8nEOFFk8DrYLpHvhKC3grJD2ardfKOzmbmJvEf/tPIqCY+sNcwZIY8ZD7IkB2l7/pqhUhqm7hLA== +"@webassemblyjs/helper-buffer@1.12.1": + version "1.12.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-buffer/-/helper-buffer-1.12.1.tgz#6df20d272ea5439bf20ab3492b7fb70e9bfcb3f6" + integrity sha512-nzJwQw99DNDKr9BVCOZcLuJJUlqkJh+kVzVl6Fmq/tI5ZtEyWT1KZMyOXltXLZJmDtvLCDgwsyrkohEtopTXCw== "@webassemblyjs/helper-numbers@1.11.6": version "1.11.6" @@ -7921,15 +7882,15 @@ resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.6.tgz#bb2ebdb3b83aa26d9baad4c46d4315283acd51e9" integrity sha512-sFFHKwcmBprO9e7Icf0+gddyWYDViL8bpPjJJl0WHxCdETktXdmtWLGVzoHbqUcY4Be1LkNfwTmXOJUFZYSJdA== -"@webassemblyjs/helper-wasm-section@1.11.6": - version "1.11.6" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.6.tgz#ff97f3863c55ee7f580fd5c41a381e9def4aa577" - integrity sha512-LPpZbSOwTpEC2cgn4hTydySy1Ke+XEu+ETXuoyvuyezHO3Kjdu90KK95Sh9xTbmjrCsUwvWwCOQQNta37VrS9g== +"@webassemblyjs/helper-wasm-section@1.12.1": + version "1.12.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.12.1.tgz#3da623233ae1a60409b509a52ade9bc22a37f7bf" + integrity sha512-Jif4vfB6FJlUlSbgEMHUyk1j234GTNG9dBJ4XJdOySoj518Xj0oGsNi59cUQF4RRMS9ouBUxDDdyBVfPTypa5g== dependencies: - "@webassemblyjs/ast" "1.11.6" - "@webassemblyjs/helper-buffer" "1.11.6" + "@webassemblyjs/ast" "1.12.1" + "@webassemblyjs/helper-buffer" "1.12.1" "@webassemblyjs/helper-wasm-bytecode" "1.11.6" - "@webassemblyjs/wasm-gen" "1.11.6" + "@webassemblyjs/wasm-gen" "1.12.1" "@webassemblyjs/ieee754@1.11.6": version "1.11.6" @@ -7951,58 +7912,58 @@ integrity sha512-vtXf2wTQ3+up9Zsg8sa2yWiQpzSsMyXj0qViVP6xKGCUT8p8YJ6HqI7l5eCnWx1T/FYdsv07HQs2wTFbbof/RA== "@webassemblyjs/wasm-edit@^1.11.5": - version "1.11.6" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.6.tgz#c72fa8220524c9b416249f3d94c2958dfe70ceab" - integrity sha512-Ybn2I6fnfIGuCR+Faaz7YcvtBKxvoLV3Lebn1tM4o/IAJzmi9AWYIPWpyBfU8cC+JxAO57bk4+zdsTjJR+VTOw== + version "1.12.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-edit/-/wasm-edit-1.12.1.tgz#9f9f3ff52a14c980939be0ef9d5df9ebc678ae3b" + integrity sha512-1DuwbVvADvS5mGnXbE+c9NfA8QRcZ6iKquqjjmR10k6o+zzsRVesil54DKexiowcFCPdr/Q0qaMgB01+SQ1u6g== dependencies: - "@webassemblyjs/ast" "1.11.6" - "@webassemblyjs/helper-buffer" "1.11.6" + "@webassemblyjs/ast" "1.12.1" + "@webassemblyjs/helper-buffer" "1.12.1" "@webassemblyjs/helper-wasm-bytecode" "1.11.6" - "@webassemblyjs/helper-wasm-section" "1.11.6" - "@webassemblyjs/wasm-gen" "1.11.6" - "@webassemblyjs/wasm-opt" "1.11.6" - "@webassemblyjs/wasm-parser" "1.11.6" - "@webassemblyjs/wast-printer" "1.11.6" - -"@webassemblyjs/wasm-gen@1.11.6": - version "1.11.6" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.6.tgz#fb5283e0e8b4551cc4e9c3c0d7184a65faf7c268" - integrity sha512-3XOqkZP/y6B4F0PBAXvI1/bky7GryoogUtfwExeP/v7Nzwo1QLcq5oQmpKlftZLbT+ERUOAZVQjuNVak6UXjPA== - dependencies: - "@webassemblyjs/ast" "1.11.6" + "@webassemblyjs/helper-wasm-section" "1.12.1" + "@webassemblyjs/wasm-gen" "1.12.1" + "@webassemblyjs/wasm-opt" "1.12.1" + "@webassemblyjs/wasm-parser" "1.12.1" + "@webassemblyjs/wast-printer" "1.12.1" + +"@webassemblyjs/wasm-gen@1.12.1": + version "1.12.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-gen/-/wasm-gen-1.12.1.tgz#a6520601da1b5700448273666a71ad0a45d78547" + integrity sha512-TDq4Ojh9fcohAw6OIMXqiIcTq5KUXTGRkVxbSo1hQnSy6lAM5GSdfwWeSxpAo0YzgsgF182E/U0mDNhuA0tW7w== + dependencies: + "@webassemblyjs/ast" "1.12.1" "@webassemblyjs/helper-wasm-bytecode" "1.11.6" "@webassemblyjs/ieee754" "1.11.6" "@webassemblyjs/leb128" "1.11.6" "@webassemblyjs/utf8" "1.11.6" -"@webassemblyjs/wasm-opt@1.11.6": - version "1.11.6" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.6.tgz#d9a22d651248422ca498b09aa3232a81041487c2" - integrity sha512-cOrKuLRE7PCe6AsOVl7WasYf3wbSo4CeOk6PkrjS7g57MFfVUF9u6ysQBBODX0LdgSvQqRiGz3CXvIDKcPNy4g== +"@webassemblyjs/wasm-opt@1.12.1": + version "1.12.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-opt/-/wasm-opt-1.12.1.tgz#9e6e81475dfcfb62dab574ac2dda38226c232bc5" + integrity sha512-Jg99j/2gG2iaz3hijw857AVYekZe2SAskcqlWIZXjji5WStnOpVoat3gQfT/Q5tb2djnCjBtMocY/Su1GfxPBg== dependencies: - "@webassemblyjs/ast" "1.11.6" - "@webassemblyjs/helper-buffer" "1.11.6" - "@webassemblyjs/wasm-gen" "1.11.6" - "@webassemblyjs/wasm-parser" "1.11.6" + "@webassemblyjs/ast" "1.12.1" + "@webassemblyjs/helper-buffer" "1.12.1" + "@webassemblyjs/wasm-gen" "1.12.1" + "@webassemblyjs/wasm-parser" "1.12.1" -"@webassemblyjs/wasm-parser@1.11.6", "@webassemblyjs/wasm-parser@^1.11.5": - version "1.11.6" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.6.tgz#bb85378c527df824004812bbdb784eea539174a1" - integrity sha512-6ZwPeGzMJM3Dqp3hCsLgESxBGtT/OeCvCZ4TA1JUPYgmhAx38tTPR9JaKy0S5H3evQpO/h2uWs2j6Yc/fjkpTQ== +"@webassemblyjs/wasm-parser@1.12.1", "@webassemblyjs/wasm-parser@^1.11.5": + version "1.12.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-parser/-/wasm-parser-1.12.1.tgz#c47acb90e6f083391e3fa61d113650eea1e95937" + integrity sha512-xikIi7c2FHXysxXe3COrVUPSheuBtpcfhbpFj4gmu7KRLYOzANztwUU0IbsqvMqzuNK2+glRGWCEqZo1WCLyAQ== dependencies: - "@webassemblyjs/ast" "1.11.6" + "@webassemblyjs/ast" "1.12.1" "@webassemblyjs/helper-api-error" "1.11.6" "@webassemblyjs/helper-wasm-bytecode" "1.11.6" "@webassemblyjs/ieee754" "1.11.6" "@webassemblyjs/leb128" "1.11.6" "@webassemblyjs/utf8" "1.11.6" -"@webassemblyjs/wast-printer@1.11.6": - version "1.11.6" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wast-printer/-/wast-printer-1.11.6.tgz#a7bf8dd7e362aeb1668ff43f35cb849f188eff20" - integrity sha512-JM7AhRcE+yW2GWYaKeHL5vt4xqee5N2WcezptmgyhNS+ScggqcT1OtXykhAb13Sn5Yas0j2uv9tHgrjwvzAP4A== +"@webassemblyjs/wast-printer@1.12.1": + version "1.12.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wast-printer/-/wast-printer-1.12.1.tgz#bcecf661d7d1abdaf989d8341a4833e33e2b31ac" + integrity sha512-+X4WAlOisVWQMikjbcvY2e0rwPsKQ9F688lksZhBcPycBBuii3O7m8FACbDMWDojpAqvjIncrG8J0XHKyQfVeA== dependencies: - "@webassemblyjs/ast" "1.11.6" + "@webassemblyjs/ast" "1.12.1" "@xtuc/long" "4.2.2" "@whatwg-node/events@^0.0.3": @@ -8172,20 +8133,15 @@ acorn-jsx@^5.3.2: integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== acorn-walk@^8.0.0, acorn-walk@^8.0.2, acorn-walk@^8.1.1, acorn-walk@^8.2.0: - version "8.3.1" - resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.3.1.tgz#2f10f5b69329d90ae18c58bf1fa8fccd8b959a43" - integrity sha512-TgUZgYvqZprrl7YldZNoa9OciCAyZR+Ejm9eXzKCmjsF5IKp/wgQ7Z/ZpjpGTIUPwrHQIcYeI8qDh4PsEwxMbw== + version "8.3.2" + resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.3.2.tgz#7703af9415f1b6db9315d6895503862e231d34aa" + integrity sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A== -acorn@^8.0.4, acorn@^8.1.0, acorn@^8.10.0, acorn@^8.4.1, acorn@^8.6.0, acorn@^8.7.1, acorn@^8.8.1, acorn@^8.8.2, acorn@^8.9.0: +acorn@^8.0.4, acorn@^8.1.0, acorn@^8.10.0, acorn@^8.11.3, acorn@^8.4.1, acorn@^8.6.0, acorn@^8.7.1, acorn@^8.8.1, acorn@^8.8.2, acorn@^8.9.0: version "8.11.3" resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.11.3.tgz#71e0b14e13a4ec160724b38fb7b0f233b1b81d7a" integrity sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg== -address@^1.0.1: - version "1.2.2" - resolved "https://registry.yarnpkg.com/address/-/address-1.2.2.tgz#2b5248dac5485a6390532c6a517fda2e3faac89e" - integrity sha512-4B/qKCfeE/ODUaAUpSwfzazo5x29WD4r3vXiWsB7I2mSDAihwEqKO+g8GELZUQSSAo5e1XTYh3ZVfLyxBc12nA== - addressparser@1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/addressparser/-/addressparser-1.0.1.tgz#47afbe1a2a9262191db6838e4fd1d39b40821746" @@ -8293,9 +8249,9 @@ ajv@^6.11.0, ajv@^6.12.4, ajv@^6.12.5: uri-js "^4.2.2" amazon-cognito-identity-js@^6.0.1, amazon-cognito-identity-js@^6.3.6: - version "6.3.7" - resolved "https://registry.yarnpkg.com/amazon-cognito-identity-js/-/amazon-cognito-identity-js-6.3.7.tgz#65c3d7ee4e0c0a1ffea01927248989c5bd1d1868" - integrity sha512-tSjnM7KyAeOZ7UMah+oOZ6cW4Gf64FFcc7BE2l7MTcp7ekAPrXaCbpcW2xEpH1EiDS4cPcAouHzmCuc2tr72vQ== + version "6.3.12" + resolved "https://registry.yarnpkg.com/amazon-cognito-identity-js/-/amazon-cognito-identity-js-6.3.12.tgz#af73df033094ad4c679c19cf6122b90058021619" + integrity sha512-s7NKDZgx336cp+oDeUtB2ZzT8jWJp/v2LWuYl+LQtMEODe22RF1IJ4nRiDATp+rp1pTffCZcm44Quw4jx2bqNg== dependencies: "@aws-crypto/sha256-js" "1.2.2" buffer "4.9.2" @@ -8547,13 +8503,13 @@ array-back@^4.0.1, array-back@^4.0.2: resolved "https://registry.yarnpkg.com/array-back/-/array-back-4.0.2.tgz#8004e999a6274586beeb27342168652fdb89fa1e" integrity sha512-NbdMezxqf94cnNfWLL7V/im0Ub+Anbb0IoZhvzie8+4HJ4nMQuzHuy49FkGYCJK2yAloZ3meiB6AVMClbrI1vg== -array-buffer-byte-length@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/array-buffer-byte-length/-/array-buffer-byte-length-1.0.0.tgz#fabe8bc193fea865f317fe7807085ee0dee5aead" - integrity sha512-LPuwb2P+NrQw3XhxGc36+XSvuBPopovXYTR9Ew++Du9Yb/bx5AzBfrIsBoj0EZUifjQU+sHL21sseZ3jerWO/A== +array-buffer-byte-length@^1.0.0, array-buffer-byte-length@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/array-buffer-byte-length/-/array-buffer-byte-length-1.0.1.tgz#1e5583ec16763540a27ae52eed99ff899223568f" + integrity sha512-ahC5W1xgou+KTXix4sAO8Ki12Q+jf4i0+tmk3sC+zgcynshkHxzpXdImBehiUYKKKDwvfFiJl1tZt6ewscS1Mg== dependencies: - call-bind "^1.0.2" - is-array-buffer "^3.0.1" + call-bind "^1.0.5" + is-array-buffer "^3.0.4" array-each@^1.0.1: version "1.0.1" @@ -8606,27 +8562,29 @@ array-unique@^0.3.2: resolved "https://registry.yarnpkg.com/array-unique/-/array-unique-0.3.2.tgz#a894b75d4bc4f6cd679ef3244a9fd8f46ae2d428" integrity sha512-SleRWjh9JUud2wH1hPs9rZBZ33H6T9HOiL0uwGnGx9FpE6wKGyfWugmbkEOIs6qWrZhg0LWeLziLrEwQJhs5mQ== -array.prototype.findlast@^1.2.2: - version "1.2.3" - resolved "https://registry.yarnpkg.com/array.prototype.findlast/-/array.prototype.findlast-1.2.3.tgz#4e4b375de5adf4897fed155e2d2771564865cc3b" - integrity sha512-kcBubumjciBg4JKp5KTKtI7ec7tRefPk88yjkWJwaVKYd9QfTaxcsOxoMNKd7iBr447zCfDV0z1kOF47umv42g== +array.prototype.findlast@^1.2.2, array.prototype.findlast@^1.2.4: + version "1.2.5" + resolved "https://registry.yarnpkg.com/array.prototype.findlast/-/array.prototype.findlast-1.2.5.tgz#3e4fbcb30a15a7f5bf64cf2faae22d139c2e4904" + integrity sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ== dependencies: - call-bind "^1.0.2" - define-properties "^1.2.0" - es-abstract "^1.22.1" - es-shim-unscopables "^1.0.0" - get-intrinsic "^1.2.1" + call-bind "^1.0.7" + define-properties "^1.2.1" + es-abstract "^1.23.2" + es-errors "^1.3.0" + es-object-atoms "^1.0.0" + es-shim-unscopables "^1.0.2" array.prototype.findlastindex@^1.2.3: - version "1.2.3" - resolved "https://registry.yarnpkg.com/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.3.tgz#b37598438f97b579166940814e2c0493a4f50207" - integrity sha512-LzLoiOMAxvy+Gd3BAq3B7VeIgPdo+Q8hthvKtXybMvRV0jrXfJM/t8mw7nNlpEcVlVUnCnM2KSX4XU5HmpodOA== + version "1.2.5" + resolved "https://registry.yarnpkg.com/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.5.tgz#8c35a755c72908719453f87145ca011e39334d0d" + integrity sha512-zfETvRFA8o7EiNn++N5f/kaCw221hrpGsDmcpndVupkPzEc1Wuf3VgC0qby1BbHs7f5DVYjgtEU2LLh5bqeGfQ== dependencies: - call-bind "^1.0.2" - define-properties "^1.2.0" - es-abstract "^1.22.1" - es-shim-unscopables "^1.0.0" - get-intrinsic "^1.2.1" + call-bind "^1.0.7" + define-properties "^1.2.1" + es-abstract "^1.23.2" + es-errors "^1.3.0" + es-object-atoms "^1.0.0" + es-shim-unscopables "^1.0.2" array.prototype.flat@^1.3.1, array.prototype.flat@^1.3.2: version "1.3.2" @@ -8638,7 +8596,7 @@ array.prototype.flat@^1.3.1, array.prototype.flat@^1.3.2: es-abstract "^1.22.1" es-shim-unscopables "^1.0.0" -array.prototype.flatmap@^1.3.1, array.prototype.flatmap@^1.3.2: +array.prototype.flatmap@^1.3.2: version "1.3.2" resolved "https://registry.yarnpkg.com/array.prototype.flatmap/-/array.prototype.flatmap-1.3.2.tgz#c9a7c6831db8e719d6ce639190146c24bbd3e527" integrity sha512-Ewyx0c9PmpcsByhSW4r+9zDU7sGjFc86qf/kKtuSCRdhfbk0SNLLkaT5qvcHnRGgc5NP/ly/y+qkXkqONX54CQ== @@ -8648,28 +8606,39 @@ array.prototype.flatmap@^1.3.1, array.prototype.flatmap@^1.3.2: es-abstract "^1.22.1" es-shim-unscopables "^1.0.0" -array.prototype.tosorted@^1.1.1: +array.prototype.toreversed@^1.1.2: version "1.1.2" - resolved "https://registry.yarnpkg.com/array.prototype.tosorted/-/array.prototype.tosorted-1.1.2.tgz#620eff7442503d66c799d95503f82b475745cefd" - integrity sha512-HuQCHOlk1Weat5jzStICBCd83NxiIMwqDg/dHEsoefabn/hJRj5pVdWcPUSpRrwhwxZOsQassMpgN/xRYFBMIg== + resolved "https://registry.yarnpkg.com/array.prototype.toreversed/-/array.prototype.toreversed-1.1.2.tgz#b989a6bf35c4c5051e1dc0325151bf8088954eba" + integrity sha512-wwDCoT4Ck4Cz7sLtgUmzR5UV3YF5mFHUlbChCzZBQZ+0m2cl/DH3tKgvphv1nKgFsJ48oCSg6p91q2Vm0I/ZMA== dependencies: call-bind "^1.0.2" define-properties "^1.2.0" es-abstract "^1.22.1" es-shim-unscopables "^1.0.0" - get-intrinsic "^1.2.1" -arraybuffer.prototype.slice@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.2.tgz#98bd561953e3e74bb34938e77647179dfe6e9f12" - integrity sha512-yMBKppFur/fbHu9/6USUe03bZ4knMYiwFBcyiaXB8Go0qNehwX6inYPzK9U0NeQvGxKthcmHcaR8P5MStSRBAw== +array.prototype.tosorted@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/array.prototype.tosorted/-/array.prototype.tosorted-1.1.3.tgz#c8c89348337e51b8a3c48a9227f9ce93ceedcba8" + integrity sha512-/DdH4TiTmOKzyQbp/eadcCVexiCb36xJg7HshYOYJnNZFDj33GEv0P7GxsynpShhq4OLYJzbGcBDkLsDt7MnNg== dependencies: - array-buffer-byte-length "^1.0.0" - call-bind "^1.0.2" - define-properties "^1.2.0" - es-abstract "^1.22.1" - get-intrinsic "^1.2.1" - is-array-buffer "^3.0.2" + call-bind "^1.0.5" + define-properties "^1.2.1" + es-abstract "^1.22.3" + es-errors "^1.1.0" + es-shim-unscopables "^1.0.2" + +arraybuffer.prototype.slice@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.3.tgz#097972f4255e41bc3425e37dc3f6421cf9aefde6" + integrity sha512-bMxMKAjg13EBSVscxTaYA4mRc5t1UAXa2kXiGTNfZ079HIWXEkKmkgFrh/nJqamaLSrXO5H4WFFkPEaLJWbs3A== + dependencies: + array-buffer-byte-length "^1.0.1" + call-bind "^1.0.5" + define-properties "^1.2.1" + es-abstract "^1.22.3" + es-errors "^1.2.1" + get-intrinsic "^1.2.3" + is-array-buffer "^3.0.4" is-shared-array-buffer "^1.0.2" arrify@^1.0.1: @@ -8682,7 +8651,16 @@ asap@^2.0.0, asap@~2.0.6: resolved "https://registry.yarnpkg.com/asap/-/asap-2.0.6.tgz#e50347611d7e690943208bbdafebcbc2fb866d46" integrity sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA== -asn1.js@^5.0.0, asn1.js@^5.2.0, asn1.js@^5.3.0: +asn1.js@^4.10.1: + version "4.10.1" + resolved "https://registry.yarnpkg.com/asn1.js/-/asn1.js-4.10.1.tgz#b9c2bf5805f1e64aadeed6df3a2bfafb5a73f5a0" + integrity sha512-p32cOF5q0Zqs9uBiONKYLm6BClCoBCM5O9JfeUSlnQLBTxYdTK+pW+nXflm8UkKd2UYlEbYz5qEi0JuZR9ckSw== + dependencies: + bn.js "^4.0.0" + inherits "^2.0.1" + minimalistic-assert "^1.0.0" + +asn1.js@^5.0.0, asn1.js@^5.3.0: version "5.4.1" resolved "https://registry.yarnpkg.com/asn1.js/-/asn1.js-5.4.1.tgz#11a980b84ebb91781ce35b0fdc2ee294e3783f07" integrity sha512-+I//4cYPccV8LdmBLiX8CYvf9Sp3vQsrqu2QNXRcrbiWvcx/UdlFiqUJJzxRQxgsZmvhXhn4cSKeSmoFjVdupA== @@ -8788,13 +8766,6 @@ async@^3.2.3, async@^3.2.4: resolved "https://registry.yarnpkg.com/async/-/async-3.2.5.tgz#ebd52a8fdaf7a2289a24df399f8d8485c8a46b66" integrity sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg== -asynciterator.prototype@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/asynciterator.prototype/-/asynciterator.prototype-1.0.0.tgz#8c5df0514936cdd133604dfcc9d3fb93f09b2b62" - integrity sha512-wwHYEIS0Q80f5mosx3L/dfG5t5rjEa9Ft51GTaNt862EnpyGHpgz2RkZvLPp1oF5TnAiTohkEKVEu8pQPJI7Vg== - dependencies: - has-symbols "^1.0.3" - asynckit@^0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" @@ -8815,15 +8786,17 @@ atomic-sleep@^1.0.0: resolved "https://registry.yarnpkg.com/atomic-sleep/-/atomic-sleep-1.0.0.tgz#eb85b77a601fc932cfe432c5acd364a9e2c9075b" integrity sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ== -available-typed-arrays@^1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz#92f95616501069d07d10edb2fc37d3e1c65123b7" - integrity sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw== +available-typed-arrays@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz#a5cc375d6a03c2efc87a553f3e0b1522def14846" + integrity sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ== + dependencies: + possible-typed-array-names "^1.0.0" aws-sdk@^2.1528.0: - version "2.1532.0" - resolved "https://registry.yarnpkg.com/aws-sdk/-/aws-sdk-2.1532.0.tgz#7c78054e53584ce6396406e87d3edfbadb0ffa81" - integrity sha512-4QVQs01LEAxo7UpSHlq/HaO+SJ1WrYF8W1otO2WhKpVRYXkSxXIgZgfYaK+sQ762XTtB6tSuD2ZS2HGsKNXVLw== + version "2.1581.0" + resolved "https://registry.yarnpkg.com/aws-sdk/-/aws-sdk-2.1581.0.tgz#dea12e848aaf619fe96ec54c2901819dde3fc5b1" + integrity sha512-lWvpj36dL0HC8i4l8N0NnV2ljedOrij1Ox0SjHwQTIvePes4lNm+khOLV0T7NDA1C6igsGozBO+8OcNGPDhfAw== dependencies: buffer "4.9.2" events "1.1.1" @@ -8834,7 +8807,7 @@ aws-sdk@^2.1528.0: url "0.10.3" util "^0.12.4" uuid "8.0.0" - xml2js "0.5.0" + xml2js "0.6.2" axe-core@=4.7.0: version "4.7.0" @@ -8872,12 +8845,12 @@ axios@^0.27.2: follow-redirects "^1.14.9" form-data "^4.0.0" -axios@^1.1.3, axios@^1.2.3, axios@^1.3.4, axios@^1.4.0, axios@^1.5.1: - version "1.6.5" - resolved "https://registry.yarnpkg.com/axios/-/axios-1.6.5.tgz#2c090da14aeeab3770ad30c3a1461bc970fb0cd8" - integrity sha512-Ii012v05KEVuUoFWmMW/UQv9aRIc3ZwkWDcM+h5Il8izZCtRVpDUfwpoFf7eOtajT3QiGR4yDUx7lPqHJULgbg== +axios@^1.1.3, axios@^1.2.3, axios@^1.3.4, axios@^1.4.0, axios@^1.5.1, axios@^1.6.7: + version "1.6.8" + resolved "https://registry.yarnpkg.com/axios/-/axios-1.6.8.tgz#66d294951f5d988a00e87a0ffb955316a619ea66" + integrity sha512-v/ZHtJDU39mDpyBoFVkETcd/uNdxrWRrg3bKpOKzXFA6Bvqopts6ALSMU3y6ijYxbw2B+wPrIv46egTzJXCLGQ== dependencies: - follow-redirects "^1.15.4" + follow-redirects "^1.15.6" form-data "^4.0.0" proxy-from-env "^1.1.0" @@ -8889,9 +8862,9 @@ axobject-query@^3.2.1: dequal "^2.0.3" b4a@^1.6.4: - version "1.6.4" - resolved "https://registry.yarnpkg.com/b4a/-/b4a-1.6.4.tgz#ef1c1422cae5ce6535ec191baeed7567443f36c9" - integrity sha512-fpWrvyVHEKyeEvbKZTVOeZF3VSKKWtJxFIxX/jaVPf+cLbGUSitjb49pHLqPV2BUNNZ0LcoeEGfE/YCpyDYHIw== + version "1.6.6" + resolved "https://registry.yarnpkg.com/b4a/-/b4a-1.6.6.tgz#a4cc349a3851987c3c4ac2d7785c18744f6da9ba" + integrity sha512-5Tk1HLk6b6ctmjIkAcU/Ujv/1WqiDl0F0JdRCR80VsOcUlHcu7pWeWRlOqQLHfDEsVx9YH/aif5AG4ehoCtTmg== babel-jest@^29.7.0: version "29.7.0" @@ -8936,37 +8909,29 @@ babel-plugin-macros@^3.1.0: cosmiconfig "^7.0.0" resolve "^1.19.0" -babel-plugin-polyfill-corejs2@^0.4.7, babel-plugin-polyfill-corejs2@^0.4.8: - version "0.4.8" - resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.8.tgz#dbcc3c8ca758a290d47c3c6a490d59429b0d2269" - integrity sha512-OtIuQfafSzpo/LhnJaykc0R/MMnuLSSVjVYy9mHArIZ9qTCSZ6TpWCuEKZYVoN//t8HqBNScHrOtCrIK5IaGLg== +babel-plugin-polyfill-corejs2@^0.4.10: + version "0.4.10" + resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.10.tgz#276f41710b03a64f6467433cab72cbc2653c38b1" + integrity sha512-rpIuu//y5OX6jVU+a5BCn1R5RSZYWAl2Nar76iwaOdycqb6JPxediskWFMMl7stfwNJR4b7eiQvh5fB5TEQJTQ== dependencies: "@babel/compat-data" "^7.22.6" - "@babel/helper-define-polyfill-provider" "^0.5.0" + "@babel/helper-define-polyfill-provider" "^0.6.1" semver "^6.3.1" -babel-plugin-polyfill-corejs3@^0.8.7: - version "0.8.7" - resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.8.7.tgz#941855aa7fdaac06ed24c730a93450d2b2b76d04" - integrity sha512-KyDvZYxAzkC0Aj2dAPyDzi2Ym15e5JKZSK+maI7NAwSqofvuFglbSsxE7wUOvTg9oFVnHMzVzBKcqEb4PJgtOA== - dependencies: - "@babel/helper-define-polyfill-provider" "^0.4.4" - core-js-compat "^3.33.1" - -babel-plugin-polyfill-corejs3@^0.9.0: - version "0.9.0" - resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.9.0.tgz#9eea32349d94556c2ad3ab9b82ebb27d4bf04a81" - integrity sha512-7nZPG1uzK2Ymhy/NbaOWTg3uibM2BmGASS4vHS4szRZAIR8R6GwA/xAujpdrXU5iyklrimWnLWU+BLF9suPTqg== +babel-plugin-polyfill-corejs3@^0.10.1: + version "0.10.1" + resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.10.1.tgz#cd8750e0b7da30ec2f66007b6151792f02e1138e" + integrity sha512-XiFei6VGwM4ii6nKC1VCenGD8Z4bjiNYcrdkM8oqM3pbuemmyb8biMgrDX1ZHSbIuMLXatM6JJ/StPYIuTl6MQ== dependencies: - "@babel/helper-define-polyfill-provider" "^0.5.0" - core-js-compat "^3.34.0" + "@babel/helper-define-polyfill-provider" "^0.6.1" + core-js-compat "^3.36.0" -babel-plugin-polyfill-regenerator@^0.5.4, babel-plugin-polyfill-regenerator@^0.5.5: - version "0.5.5" - resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.5.5.tgz#8b0c8fc6434239e5d7b8a9d1f832bb2b0310f06a" - integrity sha512-OJGYZlhLqBh2DDHeqAxWB1XIvr49CxiJ2gIt61/PU55CQK4Z58OzMqjDe1zwQdQk+rBYsRc+1rJmdajM3gimHg== +babel-plugin-polyfill-regenerator@^0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.1.tgz#4f08ef4c62c7a7f66a35ed4c0d75e30506acc6be" + integrity sha512-JfTApdE++cgcTWjsiCQlLyFBMbTUft9ja17saCc93lgV33h4tuCVj7tlvu//qpLwaG+3yEz7/KhahGrUMkVq9g== dependencies: - "@babel/helper-define-polyfill-provider" "^0.5.0" + "@babel/helper-define-polyfill-provider" "^0.6.1" "babel-plugin-styled-components@>= 1.12.0": version "2.1.4" @@ -9037,6 +9002,33 @@ balanced-match@^1.0.0: resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== +bare-events@^2.0.0, bare-events@^2.2.0: + version "2.2.1" + resolved "https://registry.yarnpkg.com/bare-events/-/bare-events-2.2.1.tgz#7b6d421f26a7a755e20bf580b727c84b807964c1" + integrity sha512-9GYPpsPFvrWBkelIhOhTWtkeZxVxZOdb3VnFTCzlOo3OjvmTvzLoZFUT8kNFACx0vJej6QPney1Cf9BvzCNE/A== + +bare-fs@^2.1.1: + version "2.2.2" + resolved "https://registry.yarnpkg.com/bare-fs/-/bare-fs-2.2.2.tgz#286bf54cc6f15f613bee6bb26f0c61c79fb14f06" + integrity sha512-X9IqgvyB0/VA5OZJyb5ZstoN62AzD7YxVGog13kkfYWYqJYcK0kcqLZ6TrmH5qr4/8//ejVcX4x/a0UvaogXmA== + dependencies: + bare-events "^2.0.0" + bare-os "^2.0.0" + bare-path "^2.0.0" + streamx "^2.13.0" + +bare-os@^2.0.0, bare-os@^2.1.0: + version "2.2.1" + resolved "https://registry.yarnpkg.com/bare-os/-/bare-os-2.2.1.tgz#c94a258c7a408ca6766399e44675136c0964913d" + integrity sha512-OwPyHgBBMkhC29Hl3O4/YfxW9n7mdTr2+SsO29XBWKKJsbgj3mnorDB80r5TiCQgQstgE5ga1qNYrpes6NvX2w== + +bare-path@^2.0.0, bare-path@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/bare-path/-/bare-path-2.1.0.tgz#830f17fd39842813ca77d211ebbabe238a88cb4c" + integrity sha512-DIIg7ts8bdRKwJRJrUMy/PICEaQZaPGZ26lsSx9MJSwIhSrcdHn7/C8W+XmnG/rKi6BaRcz+JO00CjZteybDtw== + dependencies: + bare-os "^2.1.0" + base-x@^3.0.2: version "3.0.9" resolved "https://registry.yarnpkg.com/base-x/-/base-x-3.0.9.tgz#6349aaabb58526332de9f60995e548a53fe21320" @@ -9103,9 +9095,9 @@ bignumber.js@^9.0.1, bignumber.js@^9.1.0: integrity sha512-2/mKyZH9K85bzOEfhXDBFZTGd1CTs+5IHpeFQo9luiBG7hghdC851Pj2WAhb6E3R6b9tZj/XKhbg4fum+Kepug== binary-extensions@^2.0.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.2.0.tgz#75f502eeaf9ffde42fc98829645be4ea76bd9e2d" - integrity sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA== + version "2.3.0" + resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.3.0.tgz#f6e14a97858d327252200242d4ccfe522c445522" + integrity sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw== binary-install-raw@0.0.13: version "0.0.13" @@ -9217,24 +9209,6 @@ bn.js@^5.0.0, bn.js@^5.1.1, bn.js@^5.1.2, bn.js@^5.2.0, bn.js@^5.2.1: resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-5.2.1.tgz#0bc527a6a0d18d0aa8d5b0538ce4a77dccfa7b70" integrity sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ== -body-parser@1.20.1: - version "1.20.1" - resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.20.1.tgz#b1812a8912c195cd371a3ee5e66faa2338a5c668" - integrity sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw== - dependencies: - bytes "3.1.2" - content-type "~1.0.4" - debug "2.6.9" - depd "2.0.0" - destroy "1.2.0" - http-errors "2.0.0" - iconv-lite "0.4.24" - on-finished "2.4.1" - qs "6.11.0" - raw-body "2.5.1" - type-is "~1.6.18" - unpipe "1.0.0" - body-parser@1.20.2, body-parser@^1.20.0, body-parser@^1.20.2: version "1.20.2" resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.20.2.tgz#6feb0e21c4724d06de7ff38da36dad4f57a747fd" @@ -9381,7 +9355,7 @@ browser-stdout@1.3.1: resolved "https://registry.yarnpkg.com/browser-stdout/-/browser-stdout-1.3.1.tgz#baa559ee14ced73452229bad7326467c61fabd60" integrity sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw== -browserify-aes@^1.0.0, browserify-aes@^1.0.4, browserify-aes@^1.2.0: +browserify-aes@^1.0.4, browserify-aes@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/browserify-aes/-/browserify-aes-1.2.0.tgz#326734642f403dabc3003209853bb70ad428ef48" integrity sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA== @@ -9421,18 +9395,19 @@ browserify-rsa@^4.0.0, browserify-rsa@^4.1.0: randombytes "^2.0.1" browserify-sign@^4.0.0: - version "4.2.2" - resolved "https://registry.yarnpkg.com/browserify-sign/-/browserify-sign-4.2.2.tgz#e78d4b69816d6e3dd1c747e64e9947f9ad79bc7e" - integrity sha512-1rudGyeYY42Dk6texmv7c4VcQ0EsvVbLwZkA+AQB7SxvXxmcD93jcHie8bzecJ+ChDlmAm2Qyu0+Ccg5uhZXCg== + version "4.2.3" + resolved "https://registry.yarnpkg.com/browserify-sign/-/browserify-sign-4.2.3.tgz#7afe4c01ec7ee59a89a558a4b75bd85ae62d4208" + integrity sha512-JWCZW6SKhfhjJxO8Tyiiy+XYB7cqd2S5/+WeYHsKdNKFlCBhKbblba1A/HN/90YwtxKc8tCErjffZl++UNmGiw== dependencies: bn.js "^5.2.1" browserify-rsa "^4.1.0" create-hash "^1.2.0" create-hmac "^1.1.7" - elliptic "^6.5.4" + elliptic "^6.5.5" + hash-base "~3.0" inherits "^2.0.4" - parse-asn1 "^5.1.6" - readable-stream "^3.6.2" + parse-asn1 "^5.1.7" + readable-stream "^2.3.8" safe-buffer "^5.2.1" browserify-zlib@^0.2.0: @@ -9449,17 +9424,7 @@ browserslist-to-esbuild@1.2.0: dependencies: browserslist "^4.17.3" -browserslist@^4.14.5, browserslist@^4.17.3, browserslist@^4.22.1, browserslist@^4.22.2: - version "4.22.2" - resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.22.2.tgz#704c4943072bd81ea18997f3bd2180e89c77874b" - integrity sha512-0UgcrvQmBDvZHFGdYUehrCNIazki7/lUP3kkoi/r3YB2amZbFM9J43ZRkJTXBUZK4gmx56+Sqk9+Vs9mwZx9+A== - dependencies: - caniuse-lite "^1.0.30001565" - electron-to-chromium "^1.4.601" - node-releases "^2.0.14" - update-browserslist-db "^1.0.13" - -browserslist@^4.22.3: +browserslist@^4.14.5, browserslist@^4.17.3, browserslist@^4.21.10, browserslist@^4.22.1, browserslist@^4.22.2, browserslist@^4.23.0: version "4.23.0" resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.23.0.tgz#8f3acc2bbe73af7213399430890f86c63a5674ab" integrity sha512-QW8HiM1shhT2GuzkvklfjcKDiWFXHOeFCIA/huJPwHsslwcydgk7X+z2zXpEijP98UCY7HbubZt5J2Zgvf0CaQ== @@ -9676,14 +9641,16 @@ cacheable-request@^7.0.2: normalize-url "^6.0.1" responselike "^2.0.0" -call-bind@^1.0.0, call-bind@^1.0.2, call-bind@^1.0.4, call-bind@^1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.5.tgz#6fa2b7845ce0ea49bf4d8b9ef64727a2c2e2e513" - integrity sha512-C3nQxfFZxFRVoJoGKKI8y3MOEo129NQ+FgQ08iye+Mk4zNZZGdjfs06bVTr+DBSlA66Q2VEcMki/cUCP4SercQ== +call-bind@^1.0.0, call-bind@^1.0.2, call-bind@^1.0.5, call-bind@^1.0.6, call-bind@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.7.tgz#06016599c40c56498c18769d2730be242b6fa3b9" + integrity sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w== dependencies: + es-define-property "^1.0.0" + es-errors "^1.3.0" function-bind "^1.1.2" - get-intrinsic "^1.2.1" - set-function-length "^1.1.1" + get-intrinsic "^1.2.4" + set-function-length "^1.2.1" callsites@^3.0.0: version "3.1.0" @@ -9735,15 +9702,10 @@ camelize@^1.0.0: resolved "https://registry.yarnpkg.com/camelize/-/camelize-1.0.1.tgz#89b7e16884056331a35d6b5ad064332c91daa6c3" integrity sha512-dU+Tx2fsypxTgtLoE36npi3UqcjSSMNYfkqgmoEhtZrraP5VWq0K7FkWVTYa8eMPtnU/G2txVsfdCJTn9uzpuQ== -caniuse-lite@^1.0.30001565: - version "1.0.30001576" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001576.tgz#893be772cf8ee6056d6c1e2d07df365b9ec0a5c4" - integrity sha512-ff5BdakGe2P3SQsMsiqmt1Lc8221NR1VzHj5jXN5vBny9A6fpze94HiVV/n7XRosOlsShJcvMv5mdnpjOGCEgg== - caniuse-lite@^1.0.30001587: - version "1.0.30001589" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001589.tgz#7ad6dba4c9bf6561aec8291976402339dc157dfb" - integrity sha512-vNQWS6kI+q6sBlHbh71IIeC+sRwK2N3EDySc/updIGhIee2x5z00J4c1242/5/d6EpEMdOnk/m+6tuk4/tcsqg== + version "1.0.30001599" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001599.tgz#571cf4f3f1506df9bf41fcbb6d10d5d017817bce" + integrity sha512-LRAQHZ4yT1+f9LemSMeqdMpMxZcc4RMWdj4tiFe3G8tNkWK+E58g+/tzotb5cU6TbcVJLr4fySiAW7XmxQvZQA== carbites@^1.0.6: version "1.0.6" @@ -9776,9 +9738,9 @@ cbor@^8.1.0: nofilter "^3.1.0" cbor@^9.0.0: - version "9.0.1" - resolved "https://registry.yarnpkg.com/cbor/-/cbor-9.0.1.tgz#b16e393d4948d44758cd54ac6151379d443b37ae" - integrity sha512-/TQOWyamDxvVIv+DY9cOLNuABkoyz8K/F3QE56539pGVYohx0+MEA1f4lChFTX79dBTBS7R1PF6ovH7G+VtBfQ== + version "9.0.2" + resolved "https://registry.yarnpkg.com/cbor/-/cbor-9.0.2.tgz#536b4f2d544411e70ec2b19a2453f10f83cd9fdb" + integrity sha512-JPypkxsB10s9QOWwa6zwPzqE1Md3vqpPc+cai4sAecuCsRyAtAl/pMyhPlMbT/xtPnm2dznJZYRLui57qiRhaQ== dependencies: nofilter "^3.1.0" @@ -9936,7 +9898,7 @@ cheerio@^1.0.0-rc.3: parse5 "^7.0.0" parse5-htmlparser2-tree-adapter "^7.0.0" -chokidar@3.5.3, chokidar@^3.4.0, chokidar@^3.5.2, chokidar@^3.5.3: +chokidar@3.5.3: version "3.5.3" resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.3.tgz#1cf37c8707b932bd1af1ae22c0432e2acd1903bd" integrity sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw== @@ -9951,6 +9913,21 @@ chokidar@3.5.3, chokidar@^3.4.0, chokidar@^3.5.2, chokidar@^3.5.3: optionalDependencies: fsevents "~2.3.2" +chokidar@^3.4.0, chokidar@^3.5.2, chokidar@^3.5.3, chokidar@^3.6.0: + version "3.6.0" + resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.6.0.tgz#197c6cc669ef2a8dc5e7b4d97ee4e092c3eb0d5b" + integrity sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw== + dependencies: + anymatch "~3.1.2" + braces "~3.0.2" + glob-parent "~5.1.2" + is-binary-path "~2.1.0" + is-glob "~4.0.1" + normalize-path "~3.0.0" + readdirp "~3.6.0" + optionalDependencies: + fsevents "~2.3.2" + chownr@^1.0.1, chownr@^1.1.1: version "1.1.4" resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.4.tgz#6fc9d7b42d32a583596337666e7d08084da2cc6b" @@ -9989,10 +9966,10 @@ cipher-base@^1.0.0, cipher-base@^1.0.1, cipher-base@^1.0.3: inherits "^2.0.1" safe-buffer "^5.0.1" -citty@^0.1.4, citty@^0.1.5: - version "0.1.5" - resolved "https://registry.yarnpkg.com/citty/-/citty-0.1.5.tgz#fe37ceae5dc764af75eb2fece99d2bf527ea4e50" - integrity sha512-AS7n5NSc0OQVMV9v6wt3ByujNIrne0/cTjiC2MYqhvao57VNfiuVksTSr2p17nVOhEr2KtqiAkGwHcgMC/qUuQ== +citty@^0.1.5, citty@^0.1.6: + version "0.1.6" + resolved "https://registry.yarnpkg.com/citty/-/citty-0.1.6.tgz#0f7904da1ed4625e1a9ea7e0fa780981aab7c5e4" + integrity sha512-tskPPKEs8D2KPafUypv2gxwJP8h/OaJmC82QQGGDQcHvXX43xF2VDACcJVmZ0EuSxkpO9Kc4MlrA3q0+FG58AQ== dependencies: consola "^3.2.3" @@ -10160,7 +10137,7 @@ client-only@^0.0.1: resolved "https://registry.yarnpkg.com/client-only/-/client-only-0.0.1.tgz#38bba5d403c41ab150bff64a95c85013cf73bca1" integrity sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA== -clipboardy@3.0.0, clipboardy@^3.0.0: +clipboardy@3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/clipboardy/-/clipboardy-3.0.0.tgz#f3876247404d334c9ed01b6f269c11d09a5e3092" integrity sha512-Su+uU5sr1jkUy1sGRpLKjKrvEOVXgSgiSInwa/qeID6aJ07yh+5NWc3h2QfjHjBnfX4LhtFcuAWKUsJ3r+fjbg== @@ -10169,6 +10146,15 @@ clipboardy@3.0.0, clipboardy@^3.0.0: execa "^5.1.1" is-wsl "^2.2.0" +clipboardy@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/clipboardy/-/clipboardy-4.0.0.tgz#e73ced93a76d19dd379ebf1f297565426dffdca1" + integrity sha512-5mOlNS0mhX0707P2I0aZ2V/cmHUEO/fL7VFLqszkhUsxt7RwnmrInf/eEQKlf5GzvYeHIjT+Ov1HRfNmymlG0w== + dependencies: + execa "^8.0.1" + is-wsl "^3.1.0" + is64bit "^2.0.0" + cliui@^6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/cliui/-/cliui-6.0.0.tgz#511d702c0c4e41ca156d7d0e96021f23e13225b1" @@ -10218,16 +10204,11 @@ clsx@^1.1.0: resolved "https://registry.yarnpkg.com/clsx/-/clsx-1.2.1.tgz#0ddc4a20a549b59c93a4116bb26f5294ca17dc12" integrity sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg== -clsx@^2.0.0: +clsx@^2.0.0, clsx@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/clsx/-/clsx-2.1.0.tgz#e851283bcb5c80ee7608db18487433f7b23f77cb" integrity sha512-m3iNNWpd9rl3jvvcBnu70ylMdrXt8Vlq4HYadnU5fwcOtvkSQWPmj7amUcDT2qYI7risszBjI5AUIUox9D16pg== -cluster-key-slot@^1.1.0: - version "1.1.2" - resolved "https://registry.yarnpkg.com/cluster-key-slot/-/cluster-key-slot-1.1.2.tgz#88ddaa46906e303b5de30d3153b7d9fe0a0c19ac" - integrity sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA== - co-body@^5.1.1: version "5.2.0" resolved "https://registry.yarnpkg.com/co-body/-/co-body-5.2.0.tgz#5a0a658c46029131e0e3a306f67647302f71c124" @@ -10347,11 +10328,6 @@ colors@1.4.0, colors@^1.1.2: resolved "https://registry.yarnpkg.com/colors/-/colors-1.4.0.tgz#c50491479d4c1bdaed2c9ced32cf7c7dc2360f78" integrity sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA== -colors@~1.2.1: - version "1.2.5" - resolved "https://registry.yarnpkg.com/colors/-/colors-1.2.5.tgz#89c7ad9a374bc030df8013241f68136ed8835afc" - integrity sha512-erNRLao/Y3Fv54qUa0LBB+//Uf3YwMUmdJinN20yMXm9zdKKqH9wt7R9IIVZ+K7ShzfpLV/Zg8+VyrBJYB4lpg== - colorspace@1.1.x: version "1.1.4" resolved "https://registry.yarnpkg.com/colorspace/-/colorspace-1.1.4.tgz#8d442d1186152f60453bf8070cd66eb364e59243" @@ -10634,6 +10610,11 @@ cookie-signature@1.0.6: resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c" integrity sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ== +cookie-signature@1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.7.tgz#ab5dd7ab757c54e60f37ef6550f481c426d10454" + integrity sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA== + cookie-signature@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.2.1.tgz#790dea2cce64638c7ae04d9fabed193bd7ccf3b4" @@ -10644,16 +10625,21 @@ cookie@0.4.1: resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.1.tgz#afd713fe26ebd21ba95ceb61f9a8116e50a537d1" integrity sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA== -cookie@0.4.2, cookie@^0.4.1: - version "0.4.2" - resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.2.tgz#0e41f24de5ecf317947c82fc789e06a884824432" - integrity sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA== - cookie@0.5.0, cookie@^0.5.0: version "0.5.0" resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.5.0.tgz#d1f5d71adec6558c58f389987c366aa47e994f8b" integrity sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw== +cookie@0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.6.0.tgz#2798b04b071b0ecbff0dbb62a505a8efa4e19051" + integrity sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw== + +cookie@^0.4.1: + version "0.4.2" + resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.2.tgz#0e41f24de5ecf317947c82fc789e06a884824432" + integrity sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA== + cookiejar@^2.1.4: version "2.1.4" resolved "https://registry.yarnpkg.com/cookiejar/-/cookiejar-2.1.4.tgz#ee669c1fea2cf42dc31585469d193fef0d65771b" @@ -10692,17 +10678,17 @@ copyfiles@2.4.1: untildify "^4.0.0" yargs "^16.1.0" -core-js-compat@^3.31.0, core-js-compat@^3.33.1, core-js-compat@^3.34.0: - version "3.36.0" - resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.36.0.tgz#087679119bc2fdbdefad0d45d8e5d307d45ba190" - integrity sha512-iV9Pd/PsgjNWBXeq8XRtWVSgz2tKAfhfvBs7qxYty+RlRd+OCksaWmOnc4JKrTc1cToXL1N0s3l/vwlxPtdElw== +core-js-compat@^3.31.0, core-js-compat@^3.36.0: + version "3.36.1" + resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.36.1.tgz#1818695d72c99c25d621dca94e6883e190cea3c8" + integrity sha512-Dk997v9ZCt3X/npqzyGdTlq6t7lDBhZwGvV94PKzDArjp7BTRm7WlDAXYd/OWdeFHO8OChQYRJNJvUCqCbrtKA== dependencies: - browserslist "^4.22.3" + browserslist "^4.23.0" core-js-pure@^3.23.3, core-js-pure@^3.30.2: - version "3.35.0" - resolved "https://registry.yarnpkg.com/core-js-pure/-/core-js-pure-3.35.0.tgz#4660033304a050215ae82e476bd2513a419fbb34" - integrity sha512-f+eRYmkou59uh7BPcyJ8MC76DiGhspj1KMxVIcF24tzP8NA9HVa1uC7BTW2tgx7E1QVCzDzsgp7kArrzhlz8Ew== + version "3.36.1" + resolved "https://registry.yarnpkg.com/core-js-pure/-/core-js-pure-3.36.1.tgz#1461c89e76116528b54eba20a0aff30164087a94" + integrity sha512-NXCvHvSVYSrewP0L5OhltzXeWFJLo2AL2TYnj6iLV3Bw8mM62wAQMNgUCRI6EBu6hVVpbCxmOPlxh1Ikw2PfUA== core-util-is@^1.0.2, core-util-is@~1.0.0: version "1.0.3" @@ -10877,6 +10863,11 @@ cross-spawn@7.0.3, cross-spawn@^7.0.0, cross-spawn@^7.0.1, cross-spawn@^7.0.2, c shebang-command "^2.0.0" which "^2.0.1" +crossws@^0.2.0, crossws@^0.2.2: + version "0.2.4" + resolved "https://registry.yarnpkg.com/crossws/-/crossws-0.2.4.tgz#82a8b518bff1018ab1d21ced9e35ffbe1681ad03" + integrity sha512-DAxroI2uSOgUKLz00NX6A8U/8EE3SZHmIND+10jkVSaypvyt57J5JEOxAQOL6lQxyzi/wZbTIwssU1uy69h5Vg== + "crypt@>= 0.0.1": version "0.0.2" resolved "https://registry.yarnpkg.com/crypt/-/crypt-0.0.2.tgz#88d7ff7ec0dfb86f713dc87bbb42d044d3e6c41b" @@ -10920,18 +10911,18 @@ css-color-keywords@^1.0.0: integrity sha512-FyyrDHZKEjXDpNJYvVsV960FiqQyXc/LlYmsxl2BcdMb2WPx0OGRVgTg55rPSyLSNMqP52R9r8geSp7apN3Ofg== css-loader@^6.8.1: - version "6.8.1" - resolved "https://registry.yarnpkg.com/css-loader/-/css-loader-6.8.1.tgz#0f8f52699f60f5e679eab4ec0fcd68b8e8a50a88" - integrity sha512-xDAXtEVGlD0gJ07iclwWVkLoZOpEvAWaSyf6W18S2pOC//K8+qUDIx8IIT3D+HjnmkJPQeesOPv5aiUaJsCM2g== + version "6.10.0" + resolved "https://registry.yarnpkg.com/css-loader/-/css-loader-6.10.0.tgz#7c172b270ec7b833951b52c348861206b184a4b7" + integrity sha512-LTSA/jWbwdMlk+rhmElbDR2vbtQoTBPr7fkJE+mxrHj+7ru0hUmHafDRzWIjIHTwpitWVaqY2/UWGRca3yUgRw== dependencies: icss-utils "^5.1.0" - postcss "^8.4.21" + postcss "^8.4.33" postcss-modules-extract-imports "^3.0.0" - postcss-modules-local-by-default "^4.0.3" - postcss-modules-scope "^3.0.0" + postcss-modules-local-by-default "^4.0.4" + postcss-modules-scope "^3.1.1" postcss-modules-values "^4.0.0" postcss-value-parser "^4.2.0" - semver "^7.3.8" + semver "^7.5.4" css-select@^4.1.3: version "4.3.0" @@ -11003,7 +10994,7 @@ cssstyle@^3.0.0: dependencies: rrweb-cssom "^0.6.0" -csstype@^3.0.2, csstype@^3.1.2: +csstype@^3.0.2, csstype@^3.1.3: version "3.1.3" resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.1.3.tgz#d80ff294d114fb0e6ac500fbf85b60137d7eff81" integrity sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw== @@ -11107,6 +11098,33 @@ data-urls@^4.0.0: whatwg-mimetype "^3.0.0" whatwg-url "^12.0.0" +data-view-buffer@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/data-view-buffer/-/data-view-buffer-1.0.1.tgz#8ea6326efec17a2e42620696e671d7d5a8bc66b2" + integrity sha512-0lht7OugA5x3iJLOWFhWK/5ehONdprk0ISXqVFn/NFrDu+cuc8iADFrGQz5BnRK7LLU3JmkbXSxaqX+/mXYtUA== + dependencies: + call-bind "^1.0.6" + es-errors "^1.3.0" + is-data-view "^1.0.1" + +data-view-byte-length@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/data-view-byte-length/-/data-view-byte-length-1.0.1.tgz#90721ca95ff280677eb793749fce1011347669e2" + integrity sha512-4J7wRJD3ABAzr8wP+OcIcqq2dlUKp4DVflx++hs5h5ZKydWMI6/D/fAot+yh6g2tHh8fLFTvNOaVN357NvSrOQ== + dependencies: + call-bind "^1.0.7" + es-errors "^1.3.0" + is-data-view "^1.0.1" + +data-view-byte-offset@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/data-view-byte-offset/-/data-view-byte-offset-1.0.0.tgz#5e0bbfb4828ed2d1b9b400cd8a7d119bca0ff18a" + integrity sha512-t/Ygsytq+R995EJ5PZlD4Cu56sWa8InXySaViRzw9apusqsOO2bQP+SbYzAhR0pFKoB+43lYy8rWban9JSuXnA== + dependencies: + call-bind "^1.0.6" + es-errors "^1.3.0" + is-data-view "^1.0.1" + dataloader@^2.2.2: version "2.2.2" resolved "https://registry.yarnpkg.com/dataloader/-/dataloader-2.2.2.tgz#216dc509b5abe39d43a9b9d97e6e5e473dfbe3e0" @@ -11300,14 +11318,14 @@ defer-to-connect@^2.0.0: resolved "https://registry.yarnpkg.com/defer-to-connect/-/defer-to-connect-2.0.1.tgz#8016bdb4143e4632b77a3449c6236277de520587" integrity sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg== -define-data-property@^1.0.1, define-data-property@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/define-data-property/-/define-data-property-1.1.1.tgz#c35f7cd0ab09883480d12ac5cb213715587800b3" - integrity sha512-E7uGkTzkk1d0ByLeSc6ZsFS79Axg+m1P/VsgYsxHgiuc3tFSj+MjMIwe90FC4lOAZzNBdY7kkO2P2wKdsQ1vgQ== +define-data-property@^1.0.1, define-data-property@^1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/define-data-property/-/define-data-property-1.1.4.tgz#894dc141bb7d3060ae4366f6a0107e68fbe48c5e" + integrity sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A== dependencies: - get-intrinsic "^1.2.1" + es-define-property "^1.0.0" + es-errors "^1.3.0" gopd "^1.0.1" - has-property-descriptors "^1.0.0" define-lazy-prop@^2.0.0: version "2.0.0" @@ -11350,7 +11368,7 @@ define-property@^2.0.2: is-descriptor "^1.0.2" isobject "^3.0.1" -defu@^6.1.2, defu@^6.1.3: +defu@^6.1.3, defu@^6.1.4: version "6.1.4" resolved "https://registry.yarnpkg.com/defu/-/defu-6.1.4.tgz#4e0c9cf9ff68fe5f3d7f2765cc1a012dfdcb0479" integrity sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg== @@ -11394,11 +11412,6 @@ delete-empty@^3.0.0: path-starts-with "^2.0.0" rimraf "^2.6.2" -denque@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/denque/-/denque-2.1.0.tgz#e93e1a6569fb5e66f16a3c2a2964617d349d6ab1" - integrity sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw== - depd@2.0.0, depd@^2.0.0, depd@~2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/depd/-/depd-2.0.0.tgz#b696163cc757560d09cf22cc8fad1571b79e76df" @@ -11422,10 +11435,10 @@ des.js@^1.0.0: inherits "^2.0.1" minimalistic-assert "^1.0.0" -destr@^2.0.1, destr@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/destr/-/destr-2.0.2.tgz#8d3c0ee4ec0a76df54bc8b819bca215592a8c218" - integrity sha512-65AlobnZMiCET00KaFFjUefxDX0khFA/E4myqZ7a6Sq1yZtR8+FVIvilVX66vF2uobSumxooYZChiRPCKNqhmg== +destr@^2.0.1, destr@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/destr/-/destr-2.0.3.tgz#7f9e97cb3d16dbdca7be52aca1644ce402cfe449" + integrity sha512-2N3BOUU4gYMpTP24s5rF5iP7BDr7uNTCs4ozw3kf/eKfvWSIu93GEBi5m427YoyJoeOzQ5smuu4nNAPGb8idSQ== destroy@1.2.0, destroy@^1.0.4: version "1.2.0" @@ -11453,9 +11466,9 @@ detect-libc@^1.0.3: integrity sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg== detect-libc@^2.0.0, detect-libc@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-2.0.2.tgz#8ccf2ba9315350e1241b88d0ac3b0e1fbd99605d" - integrity sha512-UX6sGumvvqSaXgdKGUsgZWqcUyIXZ/vZTrlRT/iobiKhGL0zL4d3osHj3uqllWJK+i+sixDS/3COVEOFbupFyw== + version "2.0.3" + resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-2.0.3.tgz#f0cd503b40f9939b894697d19ad50895e30cf700" + integrity sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw== detect-newline@^3.0.0: version "3.1.0" @@ -11477,14 +11490,6 @@ detect-node@^2.0.4, detect-node@^2.1.0: resolved "https://registry.yarnpkg.com/detect-node/-/detect-node-2.1.0.tgz#c9c70775a49c3d03bc2c06d9a73be550f978f8b1" integrity sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g== -detect-port@^1.3.0: - version "1.5.1" - resolved "https://registry.yarnpkg.com/detect-port/-/detect-port-1.5.1.tgz#451ca9b6eaf20451acb0799b8ab40dff7718727b" - integrity sha512-aBzdj76lueB6uUst5iAs7+0H/oOjqI5D16XUWxlWMIMROhcM0rfsNVk93zTngq1dDNpoXRr++Sus7ETAExppAQ== - dependencies: - address "^1.0.1" - debug "4" - dezalgo@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/dezalgo/-/dezalgo-1.0.4.tgz#751235260469084c132157dfa857f386d4c33d81" @@ -11514,9 +11519,9 @@ diff@^4.0.1: integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A== diff@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/diff/-/diff-5.1.0.tgz#bc52d298c5ea8df9194800224445ed43ffc87e40" - integrity sha512-D+mk+qE8VC/PAUrlAU34N+VfXev0ghe5ywmpqrawphmVZc1bEfn56uo9qpyGp1p4xpzOHkSW4ztBd6L7Xx4ACw== + version "5.2.0" + resolved "https://registry.yarnpkg.com/diff/-/diff-5.2.0.tgz#26ded047cd1179b78b9537d5ef725503ce1ae531" + integrity sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A== diffie-hellman@^5.0.0: version "5.0.3" @@ -11738,9 +11743,9 @@ dot-prop@^5.2.0: is-obj "^2.0.0" dotenv-cli@^7.3.0: - version "7.3.0" - resolved "https://registry.yarnpkg.com/dotenv-cli/-/dotenv-cli-7.3.0.tgz#21e33e7944713001677658d68856063968edfbd2" - integrity sha512-314CA4TyK34YEJ6ntBf80eUY+t1XaFLyem1k9P0sX1gn30qThZ5qZr/ZwE318gEnzyYP9yj9HJk6SqwE0upkfw== + version "7.4.1" + resolved "https://registry.yarnpkg.com/dotenv-cli/-/dotenv-cli-7.4.1.tgz#be3895878775c257e9864e5e57ff801d7492dcf8" + integrity sha512-fE1aywjRrWGxV3miaiUr3d2zC/VAiuzEGghi+QzgIA9fEf/M5hLMaRSXb4IxbUAwGmaLi0IozdZddnVU96acag== dependencies: cross-spawn "^7.0.3" dotenv "^16.3.0" @@ -11757,15 +11762,15 @@ dotenv@14.2.0: resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-14.2.0.tgz#7e77fd5dd6cff5942c4496e1acf2d0f37a9e67aa" integrity sha512-05POuPJyPpO6jqzTNweQFfAyMSD4qa4lvsMOWyTRTdpHKy6nnnN+IYWaXF+lHivhBH/ufDKlR4IWCAN3oPnHuw== -dotenv@16.3.1: - version "16.3.1" - resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.3.1.tgz#369034de7d7e5b120972693352a3bf112172cc3e" - integrity sha512-IPzF4w4/Rd94bA9imS68tZBaYyBWSCE47V1RGuMrB94iyTOIEwRmVL2x/4An+6mETpLrKJ5hQkB8W4kFAadeIQ== +dotenv@16.4.1: + version "16.4.1" + resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.4.1.tgz#1d9931f1d3e5d2959350d1250efab299561f7f11" + integrity sha512-CjA3y+Dr3FyFDOAMnxZEGtnW9KBR2M0JvvUtXNW+dYJL5ROWxP9DUHCwgFqpMk0OXCc0ljhaNTr2w/kutYIcHQ== dotenv@^16.0.3, dotenv@^16.3.0, dotenv@^16.3.2: - version "16.3.2" - resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.3.2.tgz#3cb611ce5a63002dbabf7c281bc331f69d28f03f" - integrity sha512-HTlk5nmhkm8F6JcdXvHIzaorzCoziNQT9mGxLPVXW8wJF1TiGSL60ZGB4gHWabHOaMmWmhvk2/lPHfnBiT78AQ== + version "16.4.5" + resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.4.5.tgz#cdd3b3b604cb327e286b4762e13502f717cb099f" + integrity sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg== dset@^3.1.2: version "3.1.3" @@ -11778,14 +11783,14 @@ duplexer@^0.1.2: integrity sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg== duplexify@^4.1.2: - version "4.1.2" - resolved "https://registry.yarnpkg.com/duplexify/-/duplexify-4.1.2.tgz#18b4f8d28289132fa0b9573c898d9f903f81c7b0" - integrity sha512-fz3OjcNCHmRP12MJoZMPglx8m4rrFP8rovnk4vT8Fs+aonZoCwGg10dSsQsfP/E62eZcPTMSMP6686fu9Qlqtw== + version "4.1.3" + resolved "https://registry.yarnpkg.com/duplexify/-/duplexify-4.1.3.tgz#a07e1c0d0a2c001158563d32592ba58bddb0236f" + integrity sha512-M3BmBhwJRZsSx38lZyhE53Csddgzl5R7xGJNk7CVddZD6CcmwMCH8J+7AprIrQKH7TonKxaCjcv27Qmf+sQ+oA== dependencies: end-of-stream "^1.4.1" inherits "^2.0.3" readable-stream "^3.1.1" - stream-shift "^1.0.0" + stream-shift "^1.0.2" eastasianwidth@^0.2.0: version "0.2.0" @@ -11833,17 +11838,12 @@ electron-fetch@^1.7.2: dependencies: encoding "^0.1.13" -electron-to-chromium@^1.4.601: - version "1.4.625" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.625.tgz#a9a1d18ee911f9074a9c42d9e84b1c79b29f4059" - integrity sha512-DENMhh3MFgaPDoXWrVIqSPInQoLImywfCwrSmVl3cf9QHzoZSiutHwGaB/Ql3VkqcQV30rzgdM+BjKqBAJxo5Q== - electron-to-chromium@^1.4.668: - version "1.4.681" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.681.tgz#5f23fad8aa7e1f64cbb7dd9d15c7e39a1cd7e6e3" - integrity sha512-1PpuqJUFWoXZ1E54m8bsLPVYwIVCRzvaL+n5cjigGga4z854abDnFRc+cTa2th4S79kyGqya/1xoR7h+Y5G5lg== + version "1.4.711" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.711.tgz#f9fd04007878cc27ac327d5c6ce300f8b516f635" + integrity sha512-hRg81qzvUEibX2lDxnFlVCHACa+LtrCPIsWAxo161LDYIB3jauf57RGsMZV9mvGwE98yGH06icj3zBEoOkxd/w== -elliptic@6.5.4, elliptic@^6.5.2, elliptic@^6.5.3, elliptic@^6.5.4: +elliptic@6.5.4: version "6.5.4" resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.5.4.tgz#da37cebd31e79a1367e941b592ed1fbebd58abbb" integrity sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ== @@ -11856,6 +11856,19 @@ elliptic@6.5.4, elliptic@^6.5.2, elliptic@^6.5.3, elliptic@^6.5.4: minimalistic-assert "^1.0.1" minimalistic-crypto-utils "^1.0.1" +elliptic@^6.5.2, elliptic@^6.5.3, elliptic@^6.5.4, elliptic@^6.5.5: + version "6.5.5" + resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.5.5.tgz#c715e09f78b6923977610d4c2346d6ce22e6dded" + integrity sha512-7EjbcmUm17NQFu4Pmgmq2olYMj8nwMnpcddByChSUjArp8F5DQWcIcpriwO4ZToLNAJig0yiyjswfyGNje/ixw== + dependencies: + bn.js "^4.11.9" + brorand "^1.1.0" + hash.js "^1.0.0" + hmac-drbg "^1.0.1" + inherits "^2.0.4" + minimalistic-assert "^1.0.1" + minimalistic-crypto-utils "^1.0.1" + emittery@^0.12.1: version "0.12.1" resolved "https://registry.yarnpkg.com/emittery/-/emittery-0.12.1.tgz#cb9a4a18745816f7a1fa03a8953e7eaededb45f2" @@ -11916,9 +11929,9 @@ end-of-stream@^1.0.0, end-of-stream@^1.1.0, end-of-stream@^1.4.1: once "^1.4.0" enhanced-resolve@^5.0.0, enhanced-resolve@^5.12.0, enhanced-resolve@^5.14.0, enhanced-resolve@^5.15.0, enhanced-resolve@^5.7.0: - version "5.15.0" - resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.15.0.tgz#1af946c7d93603eb88e9896cee4904dc012e9c35" - integrity sha512-LXYT42KJ7lpIKECr2mAXIaMldcNCh/7E0KBKOu4KSfkHmP+mZmSs+8V5gBAqisWBy0OO4W5Oyys0GO1Y8KtdKg== + version "5.16.0" + resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.16.0.tgz#65ec88778083056cb32487faa9aef82ed0864787" + integrity sha512-O+QWCviPNSSLAD9Ucn8Awv+poAkqn3T1XY5/N7kR7rQO9yfSGWkYZDwpJ+iKF7B8rxaQKWngSqACpgzeapSyoA== dependencies: graceful-fs "^4.2.4" tapable "^2.2.0" @@ -11977,50 +11990,116 @@ error-stack-parser@^2.0.6: dependencies: stackframe "^1.3.4" -es-abstract@^1.22.1: - version "1.22.3" - resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.22.3.tgz#48e79f5573198de6dee3589195727f4f74bc4f32" - integrity sha512-eiiY8HQeYfYH2Con2berK+To6GrK2RxbPawDkGq4UiCQQfZHb6wX9qQqkbpPqaxQFcl8d9QzZqo0tGE0VcrdwA== - dependencies: - array-buffer-byte-length "^1.0.0" - arraybuffer.prototype.slice "^1.0.2" - available-typed-arrays "^1.0.5" - call-bind "^1.0.5" - es-set-tostringtag "^2.0.1" +es-abstract@^1.22.1, es-abstract@^1.22.3: + version "1.22.5" + resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.22.5.tgz#1417df4e97cc55f09bf7e58d1e614bc61cb8df46" + integrity sha512-oW69R+4q2wG+Hc3KZePPZxOiisRIqfKBVo/HLx94QcJeWGU/8sZhCvc829rd1kS366vlJbzBfXf9yWwf0+Ko7w== + dependencies: + array-buffer-byte-length "^1.0.1" + arraybuffer.prototype.slice "^1.0.3" + available-typed-arrays "^1.0.7" + call-bind "^1.0.7" + es-define-property "^1.0.0" + es-errors "^1.3.0" + es-set-tostringtag "^2.0.3" es-to-primitive "^1.2.1" function.prototype.name "^1.1.6" - get-intrinsic "^1.2.2" - get-symbol-description "^1.0.0" + get-intrinsic "^1.2.4" + get-symbol-description "^1.0.2" globalthis "^1.0.3" gopd "^1.0.1" - has-property-descriptors "^1.0.0" - has-proto "^1.0.1" + has-property-descriptors "^1.0.2" + has-proto "^1.0.3" has-symbols "^1.0.3" - hasown "^2.0.0" - internal-slot "^1.0.5" - is-array-buffer "^3.0.2" + hasown "^2.0.1" + internal-slot "^1.0.7" + is-array-buffer "^3.0.4" is-callable "^1.2.7" - is-negative-zero "^2.0.2" + is-negative-zero "^2.0.3" is-regex "^1.1.4" - is-shared-array-buffer "^1.0.2" + is-shared-array-buffer "^1.0.3" is-string "^1.0.7" - is-typed-array "^1.1.12" + is-typed-array "^1.1.13" is-weakref "^1.0.2" object-inspect "^1.13.1" object-keys "^1.1.1" - object.assign "^4.1.4" - regexp.prototype.flags "^1.5.1" - safe-array-concat "^1.0.1" - safe-regex-test "^1.0.0" + object.assign "^4.1.5" + regexp.prototype.flags "^1.5.2" + safe-array-concat "^1.1.0" + safe-regex-test "^1.0.3" string.prototype.trim "^1.2.8" string.prototype.trimend "^1.0.7" string.prototype.trimstart "^1.0.7" - typed-array-buffer "^1.0.0" - typed-array-byte-length "^1.0.0" - typed-array-byte-offset "^1.0.0" - typed-array-length "^1.0.4" + typed-array-buffer "^1.0.2" + typed-array-byte-length "^1.0.1" + typed-array-byte-offset "^1.0.2" + typed-array-length "^1.0.5" unbox-primitive "^1.0.2" - which-typed-array "^1.1.13" + which-typed-array "^1.1.14" + +es-abstract@^1.23.0, es-abstract@^1.23.1, es-abstract@^1.23.2: + version "1.23.2" + resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.23.2.tgz#693312f3940f967b8dd3eebacb590b01712622e0" + integrity sha512-60s3Xv2T2p1ICykc7c+DNDPLDMm9t4QxCOUU0K9JxiLjM3C1zB9YVdN7tjxrFd4+AkZ8CdX1ovUga4P2+1e+/w== + dependencies: + array-buffer-byte-length "^1.0.1" + arraybuffer.prototype.slice "^1.0.3" + available-typed-arrays "^1.0.7" + call-bind "^1.0.7" + data-view-buffer "^1.0.1" + data-view-byte-length "^1.0.1" + data-view-byte-offset "^1.0.0" + es-define-property "^1.0.0" + es-errors "^1.3.0" + es-object-atoms "^1.0.0" + es-set-tostringtag "^2.0.3" + es-to-primitive "^1.2.1" + function.prototype.name "^1.1.6" + get-intrinsic "^1.2.4" + get-symbol-description "^1.0.2" + globalthis "^1.0.3" + gopd "^1.0.1" + has-property-descriptors "^1.0.2" + has-proto "^1.0.3" + has-symbols "^1.0.3" + hasown "^2.0.2" + internal-slot "^1.0.7" + is-array-buffer "^3.0.4" + is-callable "^1.2.7" + is-data-view "^1.0.1" + is-negative-zero "^2.0.3" + is-regex "^1.1.4" + is-shared-array-buffer "^1.0.3" + is-string "^1.0.7" + is-typed-array "^1.1.13" + is-weakref "^1.0.2" + object-inspect "^1.13.1" + object-keys "^1.1.1" + object.assign "^4.1.5" + regexp.prototype.flags "^1.5.2" + safe-array-concat "^1.1.2" + safe-regex-test "^1.0.3" + string.prototype.trim "^1.2.9" + string.prototype.trimend "^1.0.8" + string.prototype.trimstart "^1.0.7" + typed-array-buffer "^1.0.2" + typed-array-byte-length "^1.0.1" + typed-array-byte-offset "^1.0.2" + typed-array-length "^1.0.5" + unbox-primitive "^1.0.2" + which-typed-array "^1.1.15" + +es-define-property@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/es-define-property/-/es-define-property-1.0.0.tgz#c7faefbdff8b2696cf5f46921edfb77cc4ba3845" + integrity sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ== + dependencies: + get-intrinsic "^1.2.4" + +es-errors@^1.1.0, es-errors@^1.2.1, es-errors@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/es-errors/-/es-errors-1.3.0.tgz#05f75a25dab98e4fb1dcd5e1472c0546d5057c8f" + integrity sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw== es-get-iterator@^1.1.3: version "1.1.3" @@ -12037,41 +12116,48 @@ es-get-iterator@^1.1.3: isarray "^2.0.5" stop-iteration-iterator "^1.0.0" -es-iterator-helpers@^1.0.12, es-iterator-helpers@^1.0.15: - version "1.0.15" - resolved "https://registry.yarnpkg.com/es-iterator-helpers/-/es-iterator-helpers-1.0.15.tgz#bd81d275ac766431d19305923707c3efd9f1ae40" - integrity sha512-GhoY8uYqd6iwUl2kgjTm4CZAf6oo5mHK7BPqx3rKgx893YSsy0LGHV6gfqqQvZt/8xM8xeOnfXBCfqclMKkJ5g== +es-iterator-helpers@^1.0.15, es-iterator-helpers@^1.0.17: + version "1.0.18" + resolved "https://registry.yarnpkg.com/es-iterator-helpers/-/es-iterator-helpers-1.0.18.tgz#4d3424f46b24df38d064af6fbbc89274e29ea69d" + integrity sha512-scxAJaewsahbqTYrGKJihhViaM6DDZDDoucfvzNbK0pOren1g/daDQ3IAhzn+1G14rBG7w+i5N+qul60++zlKA== dependencies: - asynciterator.prototype "^1.0.0" - call-bind "^1.0.2" + call-bind "^1.0.7" define-properties "^1.2.1" - es-abstract "^1.22.1" - es-set-tostringtag "^2.0.1" - function-bind "^1.1.1" - get-intrinsic "^1.2.1" + es-abstract "^1.23.0" + es-errors "^1.3.0" + es-set-tostringtag "^2.0.3" + function-bind "^1.1.2" + get-intrinsic "^1.2.4" globalthis "^1.0.3" - has-property-descriptors "^1.0.0" - has-proto "^1.0.1" + has-property-descriptors "^1.0.2" + has-proto "^1.0.3" has-symbols "^1.0.3" - internal-slot "^1.0.5" + internal-slot "^1.0.7" iterator.prototype "^1.1.2" - safe-array-concat "^1.0.1" + safe-array-concat "^1.1.2" es-module-lexer@1.4.1, es-module-lexer@^1.2.1: version "1.4.1" resolved "https://registry.yarnpkg.com/es-module-lexer/-/es-module-lexer-1.4.1.tgz#41ea21b43908fe6a287ffcbe4300f790555331f5" integrity sha512-cXLGjP0c4T3flZJKQSuziYoq7MlT+rnvfZjfp7h+I7K9BNX54kP9nyWvdbwjQ4u1iWbOL4u96fgeZLToQlZC7w== -es-set-tostringtag@^2.0.1: - version "2.0.2" - resolved "https://registry.yarnpkg.com/es-set-tostringtag/-/es-set-tostringtag-2.0.2.tgz#11f7cc9f63376930a5f20be4915834f4bc74f9c9" - integrity sha512-BuDyupZt65P9D2D2vA/zqcI3G5xRsklm5N3xCwuiy+/vKy8i0ifdsQP1sLgO4tZDSCaQUSnmC48khknGMV3D2Q== +es-object-atoms@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/es-object-atoms/-/es-object-atoms-1.0.0.tgz#ddb55cd47ac2e240701260bc2a8e31ecb643d941" + integrity sha512-MZ4iQ6JwHOBQjahnjwaC1ZtIBH+2ohjamzAO3oaHcXYup7qxjF2fixyH+Q71voWHeOkI2q/TnJao/KfXYIZWbw== dependencies: - get-intrinsic "^1.2.2" - has-tostringtag "^1.0.0" - hasown "^2.0.0" + es-errors "^1.3.0" -es-shim-unscopables@^1.0.0: +es-set-tostringtag@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/es-set-tostringtag/-/es-set-tostringtag-2.0.3.tgz#8bb60f0a440c2e4281962428438d58545af39777" + integrity sha512-3T8uNMC3OQTHkFUsFq8r/BwAXLHvU/9O9mE0fBc/MY5iq/8H7ncvO947LmYA6ldWw9Uh8Yhf25zu6n7nML5QWQ== + dependencies: + get-intrinsic "^1.2.4" + has-tostringtag "^1.0.2" + hasown "^2.0.1" + +es-shim-unscopables@^1.0.0, es-shim-unscopables@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/es-shim-unscopables/-/es-shim-unscopables-1.0.2.tgz#1f6942e71ecc7835ed1c8a83006d8771a63a3763" integrity sha512-J3yBRXCzDu4ULnQwxyToo/OjdMx6akgVC7K6few0a7F/0wLtmKKN7I73AH5T2836UuXRqN7Qg+IIUw/+YJksRw== @@ -12455,39 +12541,39 @@ esbuild@^0.18.10: "@esbuild/win32-ia32" "0.18.20" "@esbuild/win32-x64" "0.18.20" -esbuild@^0.19.3: - version "0.19.11" - resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.19.11.tgz#4a02dca031e768b5556606e1b468fe72e3325d96" - integrity sha512-HJ96Hev2hX/6i5cDVwcqiJBBtuo9+FeIJOtZ9W1kA5M6AMJRHUZlpYZ1/SbEwtO0ioNAW8rUooVpC/WehY2SfA== +esbuild@^0.20.1: + version "0.20.2" + resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.20.2.tgz#9d6b2386561766ee6b5a55196c6d766d28c87ea1" + integrity sha512-WdOOppmUNU+IbZ0PaDiTst80zjnrOkyJNHoKupIcVyU8Lvla3Ugx94VzkQ32Ijqd7UhHJy75gNWDMUekcrSJ6g== optionalDependencies: - "@esbuild/aix-ppc64" "0.19.11" - "@esbuild/android-arm" "0.19.11" - "@esbuild/android-arm64" "0.19.11" - "@esbuild/android-x64" "0.19.11" - "@esbuild/darwin-arm64" "0.19.11" - "@esbuild/darwin-x64" "0.19.11" - "@esbuild/freebsd-arm64" "0.19.11" - "@esbuild/freebsd-x64" "0.19.11" - "@esbuild/linux-arm" "0.19.11" - "@esbuild/linux-arm64" "0.19.11" - "@esbuild/linux-ia32" "0.19.11" - "@esbuild/linux-loong64" "0.19.11" - "@esbuild/linux-mips64el" "0.19.11" - "@esbuild/linux-ppc64" "0.19.11" - "@esbuild/linux-riscv64" "0.19.11" - "@esbuild/linux-s390x" "0.19.11" - "@esbuild/linux-x64" "0.19.11" - "@esbuild/netbsd-x64" "0.19.11" - "@esbuild/openbsd-x64" "0.19.11" - "@esbuild/sunos-x64" "0.19.11" - "@esbuild/win32-arm64" "0.19.11" - "@esbuild/win32-ia32" "0.19.11" - "@esbuild/win32-x64" "0.19.11" + "@esbuild/aix-ppc64" "0.20.2" + "@esbuild/android-arm" "0.20.2" + "@esbuild/android-arm64" "0.20.2" + "@esbuild/android-x64" "0.20.2" + "@esbuild/darwin-arm64" "0.20.2" + "@esbuild/darwin-x64" "0.20.2" + "@esbuild/freebsd-arm64" "0.20.2" + "@esbuild/freebsd-x64" "0.20.2" + "@esbuild/linux-arm" "0.20.2" + "@esbuild/linux-arm64" "0.20.2" + "@esbuild/linux-ia32" "0.20.2" + "@esbuild/linux-loong64" "0.20.2" + "@esbuild/linux-mips64el" "0.20.2" + "@esbuild/linux-ppc64" "0.20.2" + "@esbuild/linux-riscv64" "0.20.2" + "@esbuild/linux-s390x" "0.20.2" + "@esbuild/linux-x64" "0.20.2" + "@esbuild/netbsd-x64" "0.20.2" + "@esbuild/openbsd-x64" "0.20.2" + "@esbuild/sunos-x64" "0.20.2" + "@esbuild/win32-arm64" "0.20.2" + "@esbuild/win32-ia32" "0.20.2" + "@esbuild/win32-x64" "0.20.2" escalade@^3.1.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40" - integrity sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw== + version "3.1.2" + resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.2.tgz#54076e9ab29ea5bf3d8f1ed62acffbb88272df27" + integrity sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA== escape-html@^1.0.3, escape-html@~1.0.3: version "1.0.3" @@ -12580,9 +12666,9 @@ eslint-import-resolver-typescript@^3.5.2, eslint-import-resolver-typescript@^3.5 is-glob "^4.0.3" eslint-module-utils@^2.7.4, eslint-module-utils@^2.8.0: - version "2.8.0" - resolved "https://registry.yarnpkg.com/eslint-module-utils/-/eslint-module-utils-2.8.0.tgz#e439fee65fc33f6bba630ff621efc38ec0375c49" - integrity sha512-aWajIYfsqCKRDgUfjEXNN/JlrzauMuSEy5sbd7WXbtW3EH6A6MpwEh42c7qD+MqQo9QMJ6fWLAeIJynx0g6OAw== + version "2.8.1" + resolved "https://registry.yarnpkg.com/eslint-module-utils/-/eslint-module-utils-2.8.1.tgz#52f2404300c3bd33deece9d7372fb337cc1d7c34" + integrity sha512-rXDXR3h7cs7dy9RNpUlQf80nX31XWJEyGq1tRMo+6GsO5VmTe4UTwtmonAD4ZkAsrfMVDA2wlGJ3790Ys+D49Q== dependencies: debug "^3.2.7" @@ -12625,9 +12711,9 @@ eslint-plugin-jest@^25.3.0: "@typescript-eslint/experimental-utils" "^5.0.0" eslint-plugin-jest@^27.1.5: - version "27.6.1" - resolved "https://registry.yarnpkg.com/eslint-plugin-jest/-/eslint-plugin-jest-27.6.1.tgz#5e43b07f3ca48d72e4b4fa243531e5153d9ca1dc" - integrity sha512-WEYkyVXD9NlmFBKvrkmzrC+C9yZoz5pAml2hO19PlS3spJtoiwj4p2u8spd/7zx5IvRsZsCmsoImaAvBB9X93Q== + version "27.9.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-jest/-/eslint-plugin-jest-27.9.0.tgz#7c98a33605e1d8b8442ace092b60e9919730000b" + integrity sha512-QIT7FH7fNmd9n4se7FFKHbsLKGQiw885Ds6Y/sxKgCZ6natwCsXdgPOADnYVxN2QrRweF0FZWbJ6S7Rsn7llug== dependencies: "@typescript-eslint/utils" "^5.10.0" @@ -12667,26 +12753,28 @@ eslint-plugin-react-hooks@^4.3.0, eslint-plugin-react-hooks@^4.6.0: integrity sha512-oFc7Itz9Qxh2x4gNHStv3BqJq54ExXmfC+a1NjAta66IAN87Wu0R/QArgIS9qKzX3dXKPI9H5crl9QchNMY9+g== eslint-plugin-react@^7.27.1, eslint-plugin-react@^7.31.10, eslint-plugin-react@^7.32.2: - version "7.33.2" - resolved "https://registry.yarnpkg.com/eslint-plugin-react/-/eslint-plugin-react-7.33.2.tgz#69ee09443ffc583927eafe86ffebb470ee737608" - integrity sha512-73QQMKALArI8/7xGLNI/3LylrEYrlKZSb5C9+q3OtOewTnMQi5cT+aE9E41sLCmli3I9PGGmD1yiZydyo4FEPw== + version "7.34.1" + resolved "https://registry.yarnpkg.com/eslint-plugin-react/-/eslint-plugin-react-7.34.1.tgz#6806b70c97796f5bbfb235a5d3379ece5f4da997" + integrity sha512-N97CxlouPT1AHt8Jn0mhhN2RrADlUAsk1/atcT2KyA/l9Q/E6ll7OIGwNumFmWfZ9skV3XXccYS19h80rHtgkw== dependencies: - array-includes "^3.1.6" - array.prototype.flatmap "^1.3.1" - array.prototype.tosorted "^1.1.1" + array-includes "^3.1.7" + array.prototype.findlast "^1.2.4" + array.prototype.flatmap "^1.3.2" + array.prototype.toreversed "^1.1.2" + array.prototype.tosorted "^1.1.3" doctrine "^2.1.0" - es-iterator-helpers "^1.0.12" + es-iterator-helpers "^1.0.17" estraverse "^5.3.0" jsx-ast-utils "^2.4.1 || ^3.0.0" minimatch "^3.1.2" - object.entries "^1.1.6" - object.fromentries "^2.0.6" - object.hasown "^1.1.2" - object.values "^1.1.6" + object.entries "^1.1.7" + object.fromentries "^2.0.7" + object.hasown "^1.1.3" + object.values "^1.1.7" prop-types "^15.8.1" - resolve "^2.0.0-next.4" + resolve "^2.0.0-next.5" semver "^6.3.1" - string.prototype.matchall "^4.0.8" + string.prototype.matchall "^4.0.10" eslint-plugin-testing-library@^5.0.1: version "5.11.1" @@ -12722,15 +12810,15 @@ eslint-visitor-keys@^3.3.0, eslint-visitor-keys@^3.4.1, eslint-visitor-keys@^3.4 integrity sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag== eslint@^8.24.0, eslint@^8.55.0: - version "8.56.0" - resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.56.0.tgz#4957ce8da409dc0809f99ab07a1b94832ab74b15" - integrity sha512-Go19xM6T9puCOWntie1/P997aXxFsOi37JIHRWI514Hc6ZnaHGKY9xFhrU65RT6CcBEzZoGG1e6Nq+DT04ZtZQ== + version "8.57.0" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.57.0.tgz#c786a6fd0e0b68941aaf624596fb987089195668" + integrity sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ== dependencies: "@eslint-community/eslint-utils" "^4.2.0" "@eslint-community/regexpp" "^4.6.1" "@eslint/eslintrc" "^2.1.4" - "@eslint/js" "8.56.0" - "@humanwhocodes/config-array" "^0.11.13" + "@eslint/js" "8.57.0" + "@humanwhocodes/config-array" "^0.11.14" "@humanwhocodes/module-importer" "^1.0.1" "@nodelib/fs.walk" "^1.2.8" "@ungap/structured-clone" "^1.2.0" @@ -12934,14 +13022,14 @@ ethereum-cryptography@^1.0.3: "@scure/bip39" "1.1.1" ethereum-cryptography@^2.0.0, ethereum-cryptography@^2.1.2: - version "2.1.2" - resolved "https://registry.yarnpkg.com/ethereum-cryptography/-/ethereum-cryptography-2.1.2.tgz#18fa7108622e56481157a5cb7c01c0c6a672eb67" - integrity sha512-Z5Ba0T0ImZ8fqXrJbpHcbpAvIswRte2wGNR/KePnu8GbbvgJ47lMxT/ZZPG6i9Jaht4azPDop4HaM00J0J59ug== + version "2.1.3" + resolved "https://registry.yarnpkg.com/ethereum-cryptography/-/ethereum-cryptography-2.1.3.tgz#1352270ed3b339fe25af5ceeadcf1b9c8e30768a" + integrity sha512-BlwbIL7/P45W8FGW2r7LGuvoEZ+7PWsniMvQ4p5s2xCyw9tmaDlpfsN9HjAucbF+t/qpVHwZUisgfK24TCW8aA== dependencies: - "@noble/curves" "1.1.0" - "@noble/hashes" "1.3.1" - "@scure/bip32" "1.3.1" - "@scure/bip39" "1.2.1" + "@noble/curves" "1.3.0" + "@noble/hashes" "1.3.3" + "@scure/bip32" "1.3.3" + "@scure/bip39" "1.2.2" ethereumjs-abi@^0.6.8: version "0.6.8" @@ -13006,30 +13094,17 @@ ethers@^5.7.0, ethers@^5.7.2: "@ethersproject/solidity" "5.7.0" "@ethersproject/strings" "5.7.0" "@ethersproject/transactions" "5.7.0" - "@ethersproject/units" "5.7.0" - "@ethersproject/wallet" "5.7.0" - "@ethersproject/web" "5.7.1" - "@ethersproject/wordlists" "5.7.0" - -ethers@^6.11.0: - version "6.11.0" - resolved "https://registry.yarnpkg.com/ethers/-/ethers-6.11.0.tgz#6d3e884ad36454c29d4662ae49439d5d04556c66" - integrity sha512-kPHNTnhVWiWU6AVo6CAeTjXEK24SpCXyZvwG9ROFjT0Vlux0EOhWKBAeC+45iDj80QNJTYaT1SDEmeunT0vDNw== - dependencies: - "@adraffy/ens-normalize" "1.10.1" - "@noble/curves" "1.2.0" - "@noble/hashes" "1.3.2" - "@types/node" "18.15.13" - aes-js "4.0.0-beta.5" - tslib "2.4.0" - ws "8.5.0" + "@ethersproject/units" "5.7.0" + "@ethersproject/wallet" "5.7.0" + "@ethersproject/web" "5.7.1" + "@ethersproject/wordlists" "5.7.0" -ethers@^6.8.1, ethers@^6.9.1: - version "6.9.2" - resolved "https://registry.yarnpkg.com/ethers/-/ethers-6.9.2.tgz#6f4632f62e2350fa8354ff28624027a175ef85a4" - integrity sha512-YpkrtILnMQz5jSEsJQRTpduaGT/CXuLnUIuOYzHA0v/7c8IX91m2J48wSKjzGL5L9J/Us3tLoUdb+OwE3U+FFQ== +ethers@^6.11.0, ethers@^6.8.1, ethers@^6.9.1: + version "6.11.1" + resolved "https://registry.yarnpkg.com/ethers/-/ethers-6.11.1.tgz#96aae00b627c2e35f9b0a4d65c7ab658259ee6af" + integrity sha512-mxTAE6wqJQAbp5QAe/+o+rXOID7Nw91OZXvgpjDa1r4fAbq2Nu314oEZSbjoRLacuCzs7kUC3clEvkCQowffGg== dependencies: - "@adraffy/ens-normalize" "1.10.0" + "@adraffy/ens-normalize" "1.10.1" "@noble/curves" "1.2.0" "@noble/hashes" "1.3.2" "@types/node" "18.15.13" @@ -13101,7 +13176,7 @@ execa@5.1.1, execa@^5.0.0, execa@^5.1.1: signal-exit "^3.0.3" strip-final-newline "^2.0.0" -execa@8.0.1: +execa@8.0.1, execa@^8.0.1: version "8.0.1" resolved "https://registry.yarnpkg.com/execa/-/execa-8.0.1.tgz#51f6a5943b580f963c3ca9c6321796db8cc39b8c" integrity sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg== @@ -13188,17 +13263,17 @@ expect@^29.0.0, expect@^29.7.0: jest-util "^29.7.0" express-rate-limit@^7.1.3: - version "7.1.5" - resolved "https://registry.yarnpkg.com/express-rate-limit/-/express-rate-limit-7.1.5.tgz#af4c81143a945ea97f2599d13957440a0ddbfcfe" - integrity sha512-/iVogxu7ueadrepw1bS0X0kaRC/U0afwiYRSLg68Ts+p4Dc85Q5QKsOnPS/QUjPMHvOJQtBDrZgvkOzf8ejUYw== + version "7.2.0" + resolved "https://registry.yarnpkg.com/express-rate-limit/-/express-rate-limit-7.2.0.tgz#06ce387dd5388f429cab8263c514fc07bf90a445" + integrity sha512-T7nul1t4TNyfZMJ7pKRKkdeVJWa2CqB8NA1P8BwYaoDI5QSBZARv5oMS43J7b7I5P+4asjVXjb7ONuwDKucahg== express-session@^1.17.3: - version "1.17.3" - resolved "https://registry.yarnpkg.com/express-session/-/express-session-1.17.3.tgz#14b997a15ed43e5949cb1d073725675dd2777f36" - integrity sha512-4+otWXlShYlG1Ma+2Jnn+xgKUZTMJ5QD3YvfilX3AcocOAbIkVylSWEklzALe/+Pu4qV6TYBj5GwOBFfdKqLBw== + version "1.18.0" + resolved "https://registry.yarnpkg.com/express-session/-/express-session-1.18.0.tgz#a6ae39d9091f2efba5f20fc5c65a3ce7c9ce16a3" + integrity sha512-m93QLWr0ju+rOwApSsyso838LQwgfs44QtOP/WBiwtAgPIo/SAh1a5c6nn2BR6mFNZehTpqKDESzP+fRHVbxwQ== dependencies: - cookie "0.4.2" - cookie-signature "1.0.6" + cookie "0.6.0" + cookie-signature "1.0.7" debug "2.6.9" depd "~2.0.0" on-headers "~1.0.2" @@ -13206,44 +13281,7 @@ express-session@^1.17.3: safe-buffer "5.2.1" uid-safe "~2.1.5" -express@4.18.2: - version "4.18.2" - resolved "https://registry.yarnpkg.com/express/-/express-4.18.2.tgz#3fabe08296e930c796c19e3c516979386ba9fd59" - integrity sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ== - dependencies: - accepts "~1.3.8" - array-flatten "1.1.1" - body-parser "1.20.1" - content-disposition "0.5.4" - content-type "~1.0.4" - cookie "0.5.0" - cookie-signature "1.0.6" - debug "2.6.9" - depd "2.0.0" - encodeurl "~1.0.2" - escape-html "~1.0.3" - etag "~1.8.1" - finalhandler "1.2.0" - fresh "0.5.2" - http-errors "2.0.0" - merge-descriptors "1.0.1" - methods "~1.1.2" - on-finished "2.4.1" - parseurl "~1.3.3" - path-to-regexp "0.1.7" - proxy-addr "~2.0.7" - qs "6.11.0" - range-parser "~1.2.1" - safe-buffer "5.2.1" - send "0.18.0" - serve-static "1.15.0" - setprototypeof "1.2.0" - statuses "2.0.1" - type-is "~1.6.18" - utils-merge "1.0.1" - vary "~1.1.2" - -express@^4.18.3: +express@4.18.3, express@^4.18.3: version "4.18.3" resolved "https://registry.yarnpkg.com/express/-/express-4.18.3.tgz#6870746f3ff904dee1819b82e4b51509afffb0d4" integrity sha512-6VyCijWQ+9O7WuVMTRBTl+cjNNIzD5cY5mQ1WM8r/LEkI2u8EYpOotESNwzNlyCn3g+dmjKYI6BmNneSr/FSRw== @@ -13353,7 +13391,7 @@ fast-diff@^1.1.2, fast-diff@^1.2.0: resolved "https://registry.yarnpkg.com/fast-diff/-/fast-diff-1.3.0.tgz#ece407fa550a64d638536cd727e129c61616e0f0" integrity sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw== -fast-equals@^5.0.0: +fast-equals@^5.0.0, fast-equals@^5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/fast-equals/-/fast-equals-5.0.1.tgz#a4eefe3c5d1c0d021aeed0bc10ba5e0c12ee405d" integrity sha512-WF1Wi8PwwSY7/6Kx0vKXtw8RwuSGoM1bvDaJbu7MxDlR1vovZjIAKrnzyrThgAjm6JDTu0fVgWXDlMGspodfoQ== @@ -13399,9 +13437,9 @@ fast-querystring@^1.1.1: fast-decode-uri-component "^1.0.1" fast-redact@^3.0.0: - version "3.3.0" - resolved "https://registry.yarnpkg.com/fast-redact/-/fast-redact-3.3.0.tgz#7c83ce3a7be4898241a46560d51de10f653f7634" - integrity sha512-6T5V1QK1u4oF+ATxs1lWUmlEk6P2T9HqJG3e2DnHOdVgZy2rFJBoEnrIedcTXlkAHU/zKC+7KETJ+KGGKwxgMQ== + version "3.5.0" + resolved "https://registry.yarnpkg.com/fast-redact/-/fast-redact-3.5.0.tgz#e9ea02f7e57d0cd8438180083e93077e496285e4" + integrity sha512-dwsoQlS7h9hMeYUq1W++23NDcBLV4KqONnITDV9DjfS3q1SgDGVrBdvvTLUotWtPSD7asWDV9/CmsZPy8Hf70A== fast-safe-stringify@2.1.1, fast-safe-stringify@^2.0.6, fast-safe-stringify@^2.1.1: version "2.1.1" @@ -13421,9 +13459,9 @@ fast-url-parser@1.1.3, fast-url-parser@^1.1.3: punycode "^1.3.2" fast-xml-parser@^4.2.2: - version "4.3.2" - resolved "https://registry.yarnpkg.com/fast-xml-parser/-/fast-xml-parser-4.3.2.tgz#761e641260706d6e13251c4ef8e3f5694d4b0d79" - integrity sha512-rmrXUXwbJedoXkStenj1kkljNF7ugn5ZjR9FJcwmCfcCbtOMDghPajbc+Tck6vE6F5XsDmx+Pr2le9fw8+pXBg== + version "4.3.6" + resolved "https://registry.yarnpkg.com/fast-xml-parser/-/fast-xml-parser-4.3.6.tgz#190f9d99097f0c8f2d3a0e681a10404afca052ff" + integrity sha512-M2SovcRxD4+vC493Uc2GZVcZaj66CCJhWurC4viynVSTvrpErCShNcDz1lAho6n9REQKvL/ll4A4/fw6Y9z8nw== dependencies: strnum "^1.0.5" @@ -13433,9 +13471,9 @@ fastest-levenshtein@^1.0.7: integrity sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg== fastq@^1.6.0: - version "1.16.0" - resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.16.0.tgz#83b9a9375692db77a822df081edb6a9cf6839320" - integrity sha512-ifCoaXsDrsdkWTtiNJX5uzHDsrck5TzfKKDcuFFTIrrc/BS076qgEIfoIy1VeZqViznfKiysPYTh/QeHtnIsYA== + version "1.17.1" + resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.17.1.tgz#2a523f07a4e7b1e81a42b91b8bf2254107753b47" + integrity sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w== dependencies: reusify "^1.0.4" @@ -13600,9 +13638,9 @@ flat@^5.0.2: integrity sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ== flatted@^3.2.9: - version "3.2.9" - resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.2.9.tgz#7eb4c67ca1ba34232ca9d2d93e9886e611ad7daf" - integrity sha512-36yxDn5H7OFZQla0/jFJmbIKTdZAQHngCedGxiMmpNfEZM0sdEeT+WczLQrjK6D7o2aiyLYDnkw0R3JK0Qv1RQ== + version "3.3.1" + resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.3.1.tgz#21db470729a6734d4997002f439cb308987f567a" + integrity sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw== fmix@^0.1.0: version "0.1.0" @@ -13616,10 +13654,10 @@ fn.name@1.x.x: resolved "https://registry.yarnpkg.com/fn.name/-/fn.name-1.1.0.tgz#26cad8017967aea8731bc42961d04a3d5988accc" integrity sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw== -follow-redirects@^1.12.1, follow-redirects@^1.14.0, follow-redirects@^1.14.8, follow-redirects@^1.14.9, follow-redirects@^1.15.0, follow-redirects@^1.15.2, follow-redirects@^1.15.4: - version "1.15.4" - resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.4.tgz#cdc7d308bf6493126b17ea2191ea0ccf3e535adf" - integrity sha512-Cr4D/5wlrb0z9dgERpUL3LrmPKVDsETIJhaCMeDfuFYcqa5bldGV6wBsAN6X/vxlXQtFBMrXdXxdL8CbDTGniw== +follow-redirects@^1.12.1, follow-redirects@^1.14.0, follow-redirects@^1.14.8, follow-redirects@^1.14.9, follow-redirects@^1.15.0, follow-redirects@^1.15.6: + version "1.15.6" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.6.tgz#7f815c0cda4249c74ff09e95ef97c23b5fd0399b" + integrity sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA== for-each@^0.3.3: version "0.3.3" @@ -13810,7 +13848,7 @@ fs-extra@^10.0.0, fs-extra@^10.1.0: jsonfile "^6.0.1" universalify "^2.0.0" -fs-extra@^7.0.0, fs-extra@^7.0.1: +fs-extra@^7.0.0, fs-extra@^7.0.1, fs-extra@~7.0.1: version "7.0.1" resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-7.0.1.tgz#4f189c44aa123b895f722804f55ea23eadc348e9" integrity sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw== @@ -13863,7 +13901,7 @@ fsevents@^2.3.2, fsevents@~2.3.2, fsevents@~2.3.3: resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6" integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw== -function-bind@^1.1.1, function-bind@^1.1.2: +function-bind@^1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.2.tgz#2c02d864d97f3ea6c8830c464cbd11ab6eab7a1c" integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA== @@ -13918,24 +13956,25 @@ get-func-name@^2.0.1, get-func-name@^2.0.2: resolved "https://registry.yarnpkg.com/get-func-name/-/get-func-name-2.0.2.tgz#0d7cf20cd13fda808669ffa88f4ffc7a3943fc41" integrity sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ== -get-intrinsic@^1.0.2, get-intrinsic@^1.1.1, get-intrinsic@^1.1.3, get-intrinsic@^1.2.0, get-intrinsic@^1.2.1, get-intrinsic@^1.2.2: - version "1.2.2" - resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.2.2.tgz#281b7622971123e1ef4b3c90fd7539306da93f3b" - integrity sha512-0gSo4ml/0j98Y3lngkFEot/zhiCeWsbYIlZ+uZOVgzLyLaUw7wxUL+nCTP0XJvJg1AXulJRI3UJi8GsbDuxdGA== +get-intrinsic@^1.1.3, get-intrinsic@^1.2.1, get-intrinsic@^1.2.2, get-intrinsic@^1.2.3, get-intrinsic@^1.2.4: + version "1.2.4" + resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.2.4.tgz#e385f5a4b5227d449c3eabbad05494ef0abbeadd" + integrity sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ== dependencies: + es-errors "^1.3.0" function-bind "^1.1.2" has-proto "^1.0.1" has-symbols "^1.0.3" hasown "^2.0.0" get-it@^8.0.9: - version "8.4.4" - resolved "https://registry.yarnpkg.com/get-it/-/get-it-8.4.4.tgz#8c1c4b16f6f2da4120c00fffa66c5afe2d454e23" - integrity sha512-Pu3pnJfnYuLEhwJgMlFqk19ugvtazzTxh7rg8wATaBL4c5Fy4ahM5B+bGdluiNSNYYK89F5vSa+N3sTa/qqtlg== + version "8.4.15" + resolved "https://registry.yarnpkg.com/get-it/-/get-it-8.4.15.tgz#24b0a05a5e371713338b18376deb25c0c93b7a7f" + integrity sha512-UcFJxHQcEgZdP6ZoUUP/95HlLKPaysrL82UO81qEUre/WO8vxhIws9u7divaotsErAF6RsZfGcZc6WCRvadYHw== dependencies: debug "^4.3.4" decompress-response "^7.0.0" - follow-redirects "^1.15.2" + follow-redirects "^1.15.6" into-stream "^6.0.0" is-plain-object "^5.0.0" is-retry-allowed "^2.2.0" @@ -13969,10 +14008,10 @@ get-package-type@^0.1.0: resolved "https://registry.yarnpkg.com/get-package-type/-/get-package-type-0.1.0.tgz#8de2d803cff44df3bc6c456e6668b36c3926e11a" integrity sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q== -get-port-please@^3.1.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/get-port-please/-/get-port-please-3.1.1.tgz#2556623cddb4801d823c0a6a15eec038abb483be" - integrity sha512-3UBAyM3u4ZBVYDsxOQfJDxEa6XTbpBDrOjp4mf7ExFRt5BKs/QywQQiJsh2B+hxcZLSapWqCRvElUe8DnKcFHA== +get-port-please@^3.1.2: + version "3.1.2" + resolved "https://registry.yarnpkg.com/get-port-please/-/get-port-please-3.1.2.tgz#502795e56217128e4183025c89a48c71652f4e49" + integrity sha512-Gxc29eLs1fbn6LQ4jSU4vXjlwyZhF5HsGuMAa7gqBP4Rw4yxxltyDUuF5MBclFzDTXO+ACchGQoeela4DSfzdQ== get-port@^3.1.0: version "3.2.0" @@ -14001,18 +14040,19 @@ get-stream@^8.0.1: resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-8.0.1.tgz#def9dfd71742cd7754a7761ed43749a27d02eca2" integrity sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA== -get-symbol-description@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/get-symbol-description/-/get-symbol-description-1.0.0.tgz#7fdb81c900101fbd564dd5f1a30af5aadc1e58d6" - integrity sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw== +get-symbol-description@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/get-symbol-description/-/get-symbol-description-1.0.2.tgz#533744d5aa20aca4e079c8e5daf7fd44202821f5" + integrity sha512-g0QYk1dZBxGwk+Ngc+ltRH2IBp2f7zBkBMBJZCDerh6EhlhSR6+9irMCuT/09zD6qkarHUSn529sK/yL4S27mg== dependencies: - call-bind "^1.0.2" - get-intrinsic "^1.1.1" + call-bind "^1.0.5" + es-errors "^1.3.0" + get-intrinsic "^1.2.4" get-tsconfig@^4.5.0: - version "4.7.2" - resolved "https://registry.yarnpkg.com/get-tsconfig/-/get-tsconfig-4.7.2.tgz#0dcd6fb330391d46332f4c6c1bf89a6514c2ddce" - integrity sha512-wuMsz4leaj5hbGgg4IvDU0bqJagpftG5l5cXIAvo8uZrqn0NJqwtfupTN00VnkQJPcIRrxYrm1Ue24btpCha2A== + version "4.7.3" + resolved "https://registry.yarnpkg.com/get-tsconfig/-/get-tsconfig-4.7.3.tgz#0498163d98f7b58484dd4906999c0c9d5f103f83" + integrity sha512-ZvkrzoUA0PQZM6fy6+/Hce561s+faD1rsNwhnO5FelNjyy7EMGJ3Rz1AQ8GYDWjhRs/7dBLOEJvhK8MiEJOAFg== dependencies: resolve-pkg-maps "^1.0.0" @@ -14114,6 +14154,17 @@ glob@7.2.3, glob@^7.0.0, glob@^7.0.5, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6: once "^1.3.0" path-is-absolute "^1.0.0" +glob@8.1.0, glob@^8.0.3: + version "8.1.0" + resolved "https://registry.yarnpkg.com/glob/-/glob-8.1.0.tgz#d388f656593ef708ee3e34640fdfb99a9fd1c33e" + integrity sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^5.0.1" + once "^1.3.0" + glob@9.3.5, glob@^9.2.0: version "9.3.5" resolved "https://registry.yarnpkg.com/glob/-/glob-9.3.5.tgz#ca2ed8ca452781a3009685607fdf025a899dfe21" @@ -14146,17 +14197,6 @@ glob@^5.0.15: once "^1.3.0" path-is-absolute "^1.0.0" -glob@^8.0.3: - version "8.1.0" - resolved "https://registry.yarnpkg.com/glob/-/glob-8.1.0.tgz#d388f656593ef708ee3e34640fdfb99a9fd1c33e" - integrity sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ== - dependencies: - fs.realpath "^1.0.0" - inflight "^1.0.4" - inherits "2" - minimatch "^5.0.1" - once "^1.3.0" - global-modules@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/global-modules/-/global-modules-1.0.0.tgz#6d770f0eb523ac78164d72b5e71a8877265cc3ea" @@ -14411,19 +14451,21 @@ gzip-size@^6.0.0: dependencies: duplexer "^0.1.2" -h3@^1.8.1, h3@^1.8.2: - version "1.10.0" - resolved "https://registry.yarnpkg.com/h3/-/h3-1.10.0.tgz#55ac36deb6e250ada5ff1940b6324bc6acc4085f" - integrity sha512-Tw1kcIC+AeimwRmviiObaD5EB430Yt+lTgOxLJxNr96Vd/fGRu04EF7aKfOAcpwKCI+U2JlbxOLhycD86p3Ciw== +h3@^1.10.2, h3@^1.11.1: + version "1.11.1" + resolved "https://registry.yarnpkg.com/h3/-/h3-1.11.1.tgz#e9414ae6f2a076a345ea07256b320edb29bab9f7" + integrity sha512-AbaH6IDnZN6nmbnJOH72y3c5Wwh9P97soSVdGSBbcDACRdkC0FEWf25pzx4f/NuOCK6quHmW18yF2Wx+G4Zi1A== dependencies: cookie-es "^1.0.0" - defu "^6.1.3" - destr "^2.0.2" + crossws "^0.2.2" + defu "^6.1.4" + destr "^2.0.3" iron-webcrypto "^1.0.0" + ohash "^1.1.3" radix3 "^1.1.0" - ufo "^1.3.2" + ufo "^1.4.0" uncrypto "^0.1.3" - unenv "^1.8.0" + unenv "^1.9.0" hamt-sharding@^2.0.0: version "2.0.1" @@ -14515,9 +14557,9 @@ hardhat-deploy@^0.11.43: zksync-web3 "^0.14.3" hardhat-gas-reporter@^1.0.9: - version "1.0.9" - resolved "https://registry.yarnpkg.com/hardhat-gas-reporter/-/hardhat-gas-reporter-1.0.9.tgz#9a2afb354bc3b6346aab55b1c02ca556d0e16450" - integrity sha512-INN26G3EW43adGKBNzYWOlI3+rlLnasXTwW79YNnUhXPDa+yHESgt639dJEs37gCjhkbNKcRRJnomXEuMFBXJg== + version "1.0.10" + resolved "https://registry.yarnpkg.com/hardhat-gas-reporter/-/hardhat-gas-reporter-1.0.10.tgz#ebe5bda5334b5def312747580cd923c2b09aef1b" + integrity sha512-02N4+So/fZrzJ88ci54GqwVA3Zrf0C9duuTyGt0CFRIh/CdNwbnTgkXkRfojOMLBQ+6t+lBIkgbsOtqMvNwikA== dependencies: array-uniq "1.0.3" eth-gas-reporter "^0.2.25" @@ -14604,29 +14646,29 @@ has-flag@^4.0.0: resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== -has-property-descriptors@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/has-property-descriptors/-/has-property-descriptors-1.0.1.tgz#52ba30b6c5ec87fd89fa574bc1c39125c6f65340" - integrity sha512-VsX8eaIewvas0xnvinAe9bw4WfIeODpGYikiWYLH+dma0Jw6KHYqWiWfhQlgOVK8D6PvjubK5Uc4P0iIhIcNVg== +has-property-descriptors@^1.0.0, has-property-descriptors@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz#963ed7d071dc7bf5f084c5bfbe0d1b6222586854" + integrity sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg== dependencies: - get-intrinsic "^1.2.2" + es-define-property "^1.0.0" -has-proto@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/has-proto/-/has-proto-1.0.1.tgz#1885c1305538958aff469fef37937c22795408e0" - integrity sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg== +has-proto@^1.0.1, has-proto@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/has-proto/-/has-proto-1.0.3.tgz#b31ddfe9b0e6e9914536a6ab286426d0214f77fd" + integrity sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q== has-symbols@^1.0.2, has-symbols@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.3.tgz#bb7b2c4349251dce87b125f7bdf874aa7c8b39f8" integrity sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A== -has-tostringtag@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/has-tostringtag/-/has-tostringtag-1.0.0.tgz#7e133818a7d394734f941e73c3d3f9291e658b25" - integrity sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ== +has-tostringtag@^1.0.0, has-tostringtag@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/has-tostringtag/-/has-tostringtag-1.0.2.tgz#2cdc42d40bef2e5b4eeab7c01a73c54ce7ab5abc" + integrity sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw== dependencies: - has-symbols "^1.0.2" + has-symbols "^1.0.3" has-unicode@^2.0.1: version "2.0.1" @@ -14673,6 +14715,14 @@ hash-base@^3.0.0: readable-stream "^3.6.0" safe-buffer "^5.2.0" +hash-base@~3.0: + version "3.0.4" + resolved "https://registry.yarnpkg.com/hash-base/-/hash-base-3.0.4.tgz#5fc8686847ecd73499403319a6b0a3f3f6ae4918" + integrity sha512-EeeoJKjTyt868liAlVmcv2ZsUfGHlE3Q+BICOXcZiwN3osr5Q/zFGYmTJpoIzuaSTAwndFy+GqhEwlU4L3j4Ow== + dependencies: + inherits "^2.0.1" + safe-buffer "^5.0.1" + hash.js@1.1.7, hash.js@^1.0.0, hash.js@^1.0.3, hash.js@^1.1.7: version "1.1.7" resolved "https://registry.yarnpkg.com/hash.js/-/hash.js-1.1.7.tgz#0babca538e8d4ee4a0f8988d68866537a003cf42" @@ -14681,10 +14731,10 @@ hash.js@1.1.7, hash.js@^1.0.0, hash.js@^1.0.3, hash.js@^1.1.7: inherits "^2.0.3" minimalistic-assert "^1.0.1" -hasown@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/hasown/-/hasown-2.0.0.tgz#f4c513d454a57b7c7e1650778de226b11700546c" - integrity sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA== +hasown@^2.0.0, hasown@^2.0.1, hasown@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/hasown/-/hasown-2.0.2.tgz#003eaf91be7adc372e84ec59dc37252cedb80003" + integrity sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ== dependencies: function-bind "^1.1.2" @@ -14786,9 +14836,9 @@ html-encoding-sniffer@^3.0.0: whatwg-encoding "^2.0.0" html-entities@^2.1.0: - version "2.4.0" - resolved "https://registry.yarnpkg.com/html-entities/-/html-entities-2.4.0.tgz#edd0cee70402584c8c76cc2c0556db09d1f45061" - integrity sha512-igBTJcNNNhvZFRtm8uA6xMY6xYleeDwn3PeBCkDz7tHttv4F2hsDI2aPgNERWzvRcNYHNT3ymRaQzllmXj4YsQ== + version "2.5.2" + resolved "https://registry.yarnpkg.com/html-entities/-/html-entities-2.5.2.tgz#201a3cf95d3a15be7099521620d19dfb4f65359f" + integrity sha512-K//PSRMQk4FZ78Kyau+mZurHn3FH0Vwr+H36eE0rPbeYkRRi9YxceYPhuN60UwWorxyKHhqoAJl2OFKa4BVtaA== html-escaper@^2.0.0, html-escaper@^2.0.2: version "2.0.2" @@ -15025,9 +15075,9 @@ ieee754@^1.1.13, ieee754@^1.1.4, ieee754@^1.2.1: integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== ignore@^5.1.1, ignore@^5.2.0, ignore@^5.2.4: - version "5.3.0" - resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.3.0.tgz#67418ae40d34d6999c95ff56016759c718c82f78" - integrity sha512-g7dmpshy+gD7mh88OC9NwSGTKoc3kyLAZQRU1mt53Aw/vnvfXnbC+F/7F7QoYVKbV+KNvJx8wArewKy1vXMtlg== + version "5.3.1" + resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.3.1.tgz#5073e554cd42c5b33b394375f538b8593e34d4ef" + integrity sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw== immediate@~3.0.5: version "3.0.6" @@ -15050,9 +15100,9 @@ immutable@4.2.1: integrity sha512-7WYV7Q5BTs0nlQm7tl92rDYYoyELLKHoDMBKhrxEoiV4mrfVdRz8hzPiYOzH7yWjzoVEamxRuAqhxL2PLRwZYQ== immutable@^4.0.0-rc.12: - version "4.3.4" - resolved "https://registry.yarnpkg.com/immutable/-/immutable-4.3.4.tgz#2e07b33837b4bb7662f288c244d1ced1ef65a78f" - integrity sha512-fsXeu4J4i6WNWSikpI88v/PcVflZz+6kMhUfIwc5SY+poQRPnaf5V7qds6SUyUN3cVxEzuCab7QIoLOQ+DQ1wA== + version "4.3.5" + resolved "https://registry.yarnpkg.com/immutable/-/immutable-4.3.5.tgz#f8b436e66d59f99760dc577f5c99a4fd2a5cc5a0" + integrity sha512-8eabxkth9gZatlwl5TBuJnCsoTADlL6ftEr7A4qgdaTsPyreilDSnUk57SO+jfKcNtxPa22U5KK6DSeAYhpBJw== import-fresh@^3.1.0, import-fresh@^3.2.1: version "3.3.0" @@ -15062,6 +15112,11 @@ import-fresh@^3.1.0, import-fresh@^3.2.1: parent-module "^1.0.0" resolve-from "^4.0.0" +import-lazy@~4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/import-lazy/-/import-lazy-4.0.0.tgz#e8eb627483a0a43da3c03f3e35548be5cb0cc153" + integrity sha512-rKtvo6a868b5Hu3heneU+L4yEQ4jYKLtjpnPeUdK7h0yzXGmyBTypknlkCvHFBqfX9YlorEiMM6Dnq/5atfHkw== + import-local@^3.0.2: version "3.1.0" resolved "https://registry.yarnpkg.com/import-local/-/import-local-3.1.0.tgz#b4479df8a5fd44f6cdce24070675676063c95cb4" @@ -15201,12 +15256,12 @@ interface-store@^2.0.1, interface-store@^2.0.2: resolved "https://registry.yarnpkg.com/interface-store/-/interface-store-2.0.2.tgz#83175fd2b0c501585ed96db54bb8ba9d55fce34c" integrity sha512-rScRlhDcz6k199EkHqT8NpM87ebN89ICOzILoBHgaG36/WX50N32BnU/kpZgCGPLhARRAWUUX5/cyaIjt7Kipg== -internal-slot@^1.0.4, internal-slot@^1.0.5: - version "1.0.6" - resolved "https://registry.yarnpkg.com/internal-slot/-/internal-slot-1.0.6.tgz#37e756098c4911c5e912b8edbf71ed3aa116f930" - integrity sha512-Xj6dv+PsbtwyPpEflsejS+oIZxmMlV44zAhG479uYu89MsjcYOhCFnNyKrkJrihbsiasQyY0afoCl/9BLR65bg== +internal-slot@^1.0.4, internal-slot@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/internal-slot/-/internal-slot-1.0.7.tgz#c06dcca3ed874249881007b0a5523b172a190802" + integrity sha512-NGnrKwXzSms2qUUih/ILZ5JBqNTSa1+ZmP6flaIp6KmSElgE9qdndzS3cqjrDovwFdmwsGsLdeFgB6suw+1e9g== dependencies: - get-intrinsic "^1.2.2" + es-errors "^1.3.0" hasown "^2.0.0" side-channel "^1.0.4" @@ -15257,21 +15312,6 @@ io-ts@1.10.4: dependencies: fp-ts "^1.0.0" -ioredis@^5.3.2: - version "5.3.2" - resolved "https://registry.yarnpkg.com/ioredis/-/ioredis-5.3.2.tgz#9139f596f62fc9c72d873353ac5395bcf05709f7" - integrity sha512-1DKMMzlIHM02eBBVOFQ1+AolGjs6+xEcM4PDL7NqOS6szq7H9jSaEkIUH6/a5Hl241LzW6JLSiAbNvTQjUupUA== - dependencies: - "@ioredis/commands" "^1.1.1" - cluster-key-slot "^1.1.0" - debug "^4.3.4" - denque "^2.1.0" - lodash.defaults "^4.2.0" - lodash.isarguments "^3.1.0" - redis-errors "^1.2.0" - redis-parser "^3.0.0" - standard-as-callback "^2.1.0" - ip-regex@^4.0.0: version "4.3.0" resolved "https://registry.yarnpkg.com/ip-regex/-/ip-regex-4.3.0.tgz#687275ab0f57fa76978ff8f4dddc8a23d5990db5" @@ -15477,9 +15517,9 @@ ipfs-utils@^9.0.2: stream-to-it "^0.2.2" iron-webcrypto@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/iron-webcrypto/-/iron-webcrypto-1.0.0.tgz#e3b689c0c61b434a0a4cb82d0aeabbc8b672a867" - integrity sha512-anOK1Mktt8U1Xi7fCM3RELTuYbnFikQY5VtrDj7kPgpejV7d43tWKhzgioO0zpkazLEL/j/iayRqnJhrGfqUsg== + version "1.1.0" + resolved "https://registry.yarnpkg.com/iron-webcrypto/-/iron-webcrypto-1.1.0.tgz#f902f0cdbd77554b2195ecbb65558c311b01edfd" + integrity sha512-5vgYsCakNlaQub1orZK5QmNYhwYtcllTkZBp5sfIaCqY93Cf6l+v2rtE+E4TMbcfjxDMCdrO8wmp7+ZvhDECLA== is-absolute@^1.0.0: version "1.0.0" @@ -15504,14 +15544,13 @@ is-arguments@^1.0.4, is-arguments@^1.1.1: call-bind "^1.0.2" has-tostringtag "^1.0.0" -is-array-buffer@^3.0.1, is-array-buffer@^3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/is-array-buffer/-/is-array-buffer-3.0.2.tgz#f2653ced8412081638ecb0ebbd0c41c6e0aecbbe" - integrity sha512-y+FyyR/w8vfIRq4eQcM1EYgSTnmHXPqaF+IgzgraytCFq5Xh8lllDVmAZolPJiZttZLeFSINPYMaEJ7/vWUa1w== +is-array-buffer@^3.0.2, is-array-buffer@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/is-array-buffer/-/is-array-buffer-3.0.4.tgz#7a1f92b3d61edd2bc65d24f130530ea93d7fae98" + integrity sha512-wcjaerHw0ydZwfhiKbXJWLDY8A7yV7KhjQOpb83hGgGfId/aQa4TOvwyzn2PuswW2gPCYEL/nEAiSVpdOj1lXw== dependencies: call-bind "^1.0.2" - get-intrinsic "^1.2.0" - is-typed-array "^1.1.10" + get-intrinsic "^1.2.1" is-arrayish@^0.2.1: version "0.2.1" @@ -15588,6 +15627,13 @@ is-data-descriptor@^1.0.1: dependencies: hasown "^2.0.0" +is-data-view@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/is-data-view/-/is-data-view-1.0.1.tgz#4b4d3a511b70f3dc26d42c03ca9ca515d847759f" + integrity sha512-AHkaJrsUVW6wq6JS8y3JnM/GJF/9cf+k20+iDzlSaJrinEo5+7vRiteOSwBhHRiAyQATN1AmY4hwzxJKPmYf+w== + dependencies: + is-typed-array "^1.1.13" + is-date-object@^1.0.1, is-date-object@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.5.tgz#0841d5536e724c25597bf6ea62e1bd38298df31f" @@ -15734,10 +15780,10 @@ is-lower-case@^1.1.0: dependencies: lower-case "^1.1.0" -is-map@^2.0.1, is-map@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/is-map/-/is-map-2.0.2.tgz#00922db8c9bf73e81b7a335827bc2a43f2b91127" - integrity sha512-cOZFQQozTha1f4MxLFzlgKYPTyj26picdZTx82hbc/Xf4K/tZOOXSCkMvU4pKioRXGDLJRn0GM7Upe7kR721yg== +is-map@^2.0.2, is-map@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/is-map/-/is-map-2.0.3.tgz#ede96b7fe1e270b3c4465e3a465658764926d62e" + integrity sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw== is-module@^1.0.0: version "1.0.0" @@ -15752,10 +15798,10 @@ is-nan@^1.3.2: call-bind "^1.0.0" define-properties "^1.1.3" -is-negative-zero@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/is-negative-zero/-/is-negative-zero-2.0.2.tgz#7bf6f03a28003b8b3965de3ac26f664d765f3150" - integrity sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA== +is-negative-zero@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/is-negative-zero/-/is-negative-zero-2.0.3.tgz#ced903a027aca6381b777a5743069d7376a49747" + integrity sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw== is-number-object@^1.0.4: version "1.0.7" @@ -15855,17 +15901,17 @@ is-retry-allowed@^2.2.0: resolved "https://registry.yarnpkg.com/is-retry-allowed/-/is-retry-allowed-2.2.0.tgz#88f34cbd236e043e71b6932d09b0c65fb7b4d71d" integrity sha512-XVm7LOeLpTW4jV19QSH38vkswxoLud8sQ57YwJVTPWdiaI9I8keEhGFpBlslyVsgdQy4Opg8QOLb8YRgsyZiQg== -is-set@^2.0.1, is-set@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/is-set/-/is-set-2.0.2.tgz#90755fa4c2562dc1c5d4024760d6119b94ca18ec" - integrity sha512-+2cnTEZeY5z/iXGbLhPrOAaK/Mau5k5eXq9j14CpRTftq0pAJu2MwVRSZhyZWBzx3o6X795Lz6Bpb6R0GKf37g== +is-set@^2.0.2, is-set@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/is-set/-/is-set-2.0.3.tgz#8ab209ea424608141372ded6e0cb200ef1d9d01d" + integrity sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg== -is-shared-array-buffer@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz#8f259c573b60b6a32d4058a1a07430c0a7344c79" - integrity sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA== +is-shared-array-buffer@^1.0.2, is-shared-array-buffer@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/is-shared-array-buffer/-/is-shared-array-buffer-1.0.3.tgz#1237f1cba059cdb62431d378dcc37d9680181688" + integrity sha512-nA2hv5XIhLR3uVzDDfCIknerhx8XUKnstuOERPNNIinXG7v9u+ohXF67vxm4TPTEPU6lm61ZkwP3c9PCB97rhg== dependencies: - call-bind "^1.0.2" + call-bind "^1.0.7" is-ssh@^1.4.0: version "1.4.0" @@ -15907,12 +15953,12 @@ is-type-of@^1.2.1: is-class-hotfix "~0.0.6" isstream "~0.1.2" -is-typed-array@^1.1.10, is-typed-array@^1.1.12, is-typed-array@^1.1.3, is-typed-array@^1.1.9: - version "1.1.12" - resolved "https://registry.yarnpkg.com/is-typed-array/-/is-typed-array-1.1.12.tgz#d0bab5686ef4a76f7a73097b95470ab199c57d4a" - integrity sha512-Z14TF2JNG8Lss5/HMqt0//T9JeHXttXy5pH/DBU4vi98ozO2btxzq9MwYDZYnKwU8nRsz/+GVFVRDq3DkVuSPg== +is-typed-array@^1.1.13, is-typed-array@^1.1.3: + version "1.1.13" + resolved "https://registry.yarnpkg.com/is-typed-array/-/is-typed-array-1.1.13.tgz#d6c5ca56df62334959322d7d7dd1cca50debe229" + integrity sha512-uZ25/bUAlUY5fR4OKT4rZQEBrzQWYV9ZJYGGsUmEJ6thodVJ1HX64ePQ6Z0qPWP+m+Uq6e9UugrE38jeYsDSMw== dependencies: - which-typed-array "^1.1.11" + which-typed-array "^1.1.14" is-typedarray@1.0.0, is-typedarray@^1.0.0: version "1.0.0" @@ -15938,10 +15984,10 @@ is-upper-case@^1.1.0: dependencies: upper-case "^1.1.0" -is-weakmap@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/is-weakmap/-/is-weakmap-2.0.1.tgz#5008b59bdc43b698201d18f62b37b2ca243e8cf2" - integrity sha512-NSBR4kH5oVj1Uwvv970ruUkCV7O1mzgVFO4/rev2cLRda9Tm9HrL70ZPut4rOHgY0FNrUu9BCbXA2sdQ+x0chA== +is-weakmap@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/is-weakmap/-/is-weakmap-2.0.2.tgz#bf72615d649dfe5f699079c54b83e47d1ae19cfd" + integrity sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w== is-weakref@^1.0.2: version "1.0.2" @@ -15950,13 +15996,13 @@ is-weakref@^1.0.2: dependencies: call-bind "^1.0.2" -is-weakset@^2.0.1: - version "2.0.2" - resolved "https://registry.yarnpkg.com/is-weakset/-/is-weakset-2.0.2.tgz#4569d67a747a1ce5a994dfd4ef6dcea76e7c0a1d" - integrity sha512-t2yVvttHkQktwnNNmBQ98AhENLdPUTDTE21uPqAQ0ARwQfGeQKRVS0NNurH7bTf7RrvcVn1OOge45CnBeHCSmg== +is-weakset@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/is-weakset/-/is-weakset-2.0.3.tgz#e801519df8c0c43e12ff2834eead84ec9e624007" + integrity sha512-LvIm3/KWzS9oRFHugab7d+M/GcBXuXX5xZkzPmN+NxihdQlZUQ4dWuSV1xR/sq6upL1TJEDrfBgRepHFdBtSNQ== dependencies: - call-bind "^1.0.2" - get-intrinsic "^1.1.1" + call-bind "^1.0.7" + get-intrinsic "^1.2.4" is-windows@^1.0.1, is-windows@^1.0.2: version "1.0.2" @@ -15970,6 +16016,20 @@ is-wsl@^2.2.0: dependencies: is-docker "^2.0.0" +is-wsl@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-3.1.0.tgz#e1c657e39c10090afcbedec61720f6b924c3cbd2" + integrity sha512-UcVfVfaK4Sc4m7X3dUSoHoozQGBEFeDC+zVo06t98xe8CzHSZZBekNXH+tu0NalHolcJ/QAGqS46Hef7QXBIMw== + dependencies: + is-inside-container "^1.0.0" + +is64bit@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/is64bit/-/is64bit-2.0.0.tgz#198c627cbcb198bbec402251f88e5e1a51236c07" + integrity sha512-jv+8jaWCl0g2lSBkNSVXdzfBA0npK1HGC2KtWM9FumFRoGS94g3NbCCLVnCYHLjp4GrW2KZeeSTMo5ddtznmGw== + dependencies: + system-architecture "^0.1.0" + isarray@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/isarray/-/isarray-0.0.1.tgz#8a18acfca9a8f4177e09abfc6038939b05d1eedf" @@ -16062,13 +16122,13 @@ istanbul-lib-instrument@^5.0.4: semver "^6.3.0" istanbul-lib-instrument@^6.0.0: - version "6.0.1" - resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.1.tgz#71e87707e8041428732518c6fb5211761753fbdf" - integrity sha512-EAMEJBsYuyyztxMxW3g7ugGPkrZsV57v0Hmv3mm1uQsmB+QnZuepg731CRaIgeUVSdmsTngOkSnauNF8p7FIhA== + version "6.0.2" + resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.2.tgz#91655936cf7380e4e473383081e38478b69993b1" + integrity sha512-1WUsZ9R1lA0HtBSohTkm39WTPlNKSJ5iFk7UwqXkBLoHQT+hfqPsfsTDVuZdKGaBwn7din9bS7SsnoAr943hvw== dependencies: - "@babel/core" "^7.12.3" - "@babel/parser" "^7.14.7" - "@istanbuljs/schema" "^0.1.2" + "@babel/core" "^7.23.9" + "@babel/parser" "^7.23.9" + "@istanbuljs/schema" "^0.1.3" istanbul-lib-coverage "^3.2.0" semver "^7.5.4" @@ -16091,9 +16151,9 @@ istanbul-lib-source-maps@^4.0.0: source-map "^0.6.1" istanbul-reports@^3.1.3: - version "3.1.6" - resolved "https://registry.yarnpkg.com/istanbul-reports/-/istanbul-reports-3.1.6.tgz#2544bcab4768154281a2f0870471902704ccaa1a" - integrity sha512-TLgnMkKg3iTDsQ9PbPTdpfAK2DzjF9mqUG7RMgcQl8oFjad8ob4laGxv5XV5U9MAfx8D6tSJiUyuAwzLicaxlg== + version "3.1.7" + resolved "https://registry.yarnpkg.com/istanbul-reports/-/istanbul-reports-3.1.7.tgz#daed12b9e1dca518e15c056e1e537e741280fa0b" + integrity sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g== dependencies: html-escaper "^2.0.0" istanbul-lib-report "^3.0.0" @@ -16642,11 +16702,16 @@ jiti@1.17.1: resolved "https://registry.yarnpkg.com/jiti/-/jiti-1.17.1.tgz#264daa43ee89a03e8be28c3d712ccc4eb9f1e8ed" integrity sha512-NZIITw8uZQFuzQimqjUxIrIcEdxYDFIe/0xYfIlVXTkiBjjyBEvgasj5bb0/cHtPRD/NziPbT312sFrkI5ALpw== -jiti@^1.20.0: +jiti@^1.21.0: version "1.21.0" resolved "https://registry.yarnpkg.com/jiti/-/jiti-1.21.0.tgz#7c97f8fe045724e136a397f7340475244156105d" integrity sha512-gFqAIbuKyyso/3G2qhiO2OM6shY6EPP/R0+mkDbyspxKazh8BXDC5FiFsUjlczgdNz/vfra0da2y+aHrusLG/Q== +jju@~1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/jju/-/jju-1.4.0.tgz#a3abe2718af241a2b2904f84a625970f389ae32a" + integrity sha512-8wb9Yw966OSxApiCt0K3yNJL8pnNeIv+OEq2YMidz4FKP6nonSRoOXc80iXY4JaN2FC11B9qsNmDsm+ZOfMROA== + jmespath@0.16.0: version "0.16.0" resolved "https://registry.yarnpkg.com/jmespath/-/jmespath-0.16.0.tgz#b15b0a85dfd4d930d43e69ed605943c802785076" @@ -16852,11 +16917,16 @@ json5@^2.1.2, json5@^2.2.0, json5@^2.2.2, json5@^2.2.3: resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283" integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg== -jsonc-parser@3.2.0, jsonc-parser@^3.2.0: +jsonc-parser@3.2.0: version "3.2.0" resolved "https://registry.yarnpkg.com/jsonc-parser/-/jsonc-parser-3.2.0.tgz#31ff3f4c2b9793f89c67212627c51c6394f88e76" integrity sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w== +jsonc-parser@^3.2.0: + version "3.2.1" + resolved "https://registry.yarnpkg.com/jsonc-parser/-/jsonc-parser-3.2.1.tgz#031904571ccf929d7670ee8c547545081cb37f1a" + integrity sha512-AilxAyFOAcK5wA1+LeaySVBrHsGQvUFCDWXKpZjzaL0PqW+xfBOttn8GNtWKFWqneyMZj41MWF9Kl6iPWLwgOA== + jsonfile@^2.1.0: version "2.4.0" resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-2.4.0.tgz#3736a2b428b87bbda0cc83b53fa3d633a35c2ae8" @@ -17281,9 +17351,9 @@ libmime@^2.0.3: libqp "1.1.0" libphonenumber-js@^1.10.53: - version "1.10.54" - resolved "https://registry.yarnpkg.com/libphonenumber-js/-/libphonenumber-js-1.10.54.tgz#8dfba112f49d1b9c2a160e55f9697f22e50f0841" - integrity sha512-P+38dUgJsmh0gzoRDoM4F5jLbyfztkU6PY6eSK6S5HwTi/LPvnwXqVCQZlAy1FxZ5c48q25QhxGQ0pq+WQcSlQ== + version "1.10.58" + resolved "https://registry.yarnpkg.com/libphonenumber-js/-/libphonenumber-js-1.10.58.tgz#2015877bd47fd3d32d9fbfcedd75df35be230c9a" + integrity sha512-53A0IpJFL9LdHbpeatwizf8KSwPICrqn9H0g3Y7WQ+Jgeu9cQ4Ew3WrRtrLBu/CX2lXd5+rgT01/tGlkbkzOjw== libqp@1.1.0: version "1.1.0" @@ -17329,48 +17399,49 @@ linkify-it@^3.0.1: uc.micro "^1.0.1" lint-staged@^15.2.0: - version "15.2.0" - resolved "https://registry.yarnpkg.com/lint-staged/-/lint-staged-15.2.0.tgz#3111534ca58096a3c8f70b044b6e7fe21b36f859" - integrity sha512-TFZzUEV00f+2YLaVPWBWGAMq7So6yQx+GG8YRMDeOEIf95Zn5RyiLMsEiX4KTNl9vq/w+NqRJkLA1kPIo15ufQ== + version "15.2.2" + resolved "https://registry.yarnpkg.com/lint-staged/-/lint-staged-15.2.2.tgz#ad7cbb5b3ab70e043fa05bff82a09ed286bc4c5f" + integrity sha512-TiTt93OPh1OZOsb5B7k96A/ATl2AjIZo+vnzFZ6oHK5FuTk63ByDtxGQpHm+kFETjEWqgkF95M8FRXKR/LEBcw== dependencies: chalk "5.3.0" commander "11.1.0" debug "4.3.4" execa "8.0.1" lilconfig "3.0.0" - listr2 "8.0.0" + listr2 "8.0.1" micromatch "4.0.5" pidtree "0.6.0" string-argv "0.3.2" yaml "2.3.4" -listhen@^1.5.5: - version "1.5.5" - resolved "https://registry.yarnpkg.com/listhen/-/listhen-1.5.5.tgz#58915512af70f770aa3e9fb19367adf479bb58c4" - integrity sha512-LXe8Xlyh3gnxdv4tSjTjscD1vpr/2PRpzq8YIaMJgyKzRG8wdISlWVWnGThJfHnlJ6hmLt2wq1yeeix0TEbuoA== +listhen@^1.7.2: + version "1.7.2" + resolved "https://registry.yarnpkg.com/listhen/-/listhen-1.7.2.tgz#66b81740692269d5d8cafdc475020f2fc51afbae" + integrity sha512-7/HamOm5YD9Wb7CFgAZkKgVPA96WwhcTQoqtm2VTZGVbVVn3IWKRBTgrU7cchA3Q8k9iCsG8Osoi9GX4JsGM9g== dependencies: - "@parcel/watcher" "^2.3.0" - "@parcel/watcher-wasm" "2.3.0" - citty "^0.1.4" - clipboardy "^3.0.0" + "@parcel/watcher" "^2.4.1" + "@parcel/watcher-wasm" "^2.4.1" + citty "^0.1.6" + clipboardy "^4.0.0" consola "^3.2.3" - defu "^6.1.2" - get-port-please "^3.1.1" - h3 "^1.8.1" + crossws "^0.2.0" + defu "^6.1.4" + get-port-please "^3.1.2" + h3 "^1.10.2" http-shutdown "^1.2.2" - jiti "^1.20.0" - mlly "^1.4.2" + jiti "^1.21.0" + mlly "^1.6.1" node-forge "^1.3.1" - pathe "^1.1.1" - std-env "^3.4.3" - ufo "^1.3.0" - untun "^0.1.2" + pathe "^1.1.2" + std-env "^3.7.0" + ufo "^1.4.0" + untun "^0.1.3" uqr "^0.1.2" -listr2@8.0.0: - version "8.0.0" - resolved "https://registry.yarnpkg.com/listr2/-/listr2-8.0.0.tgz#aa7c230995f8ce378585f7c96c0c6d1cefa4700d" - integrity sha512-u8cusxAcyqAiQ2RhYvV7kRKNLgUvtObIbhOX2NCXqvp1UU32xIg5CT22ykS2TPKJXZWJwtK3IKLiqAGlGNE+Zg== +listr2@8.0.1: + version "8.0.1" + resolved "https://registry.yarnpkg.com/listr2/-/listr2-8.0.1.tgz#4d3f50ae6cec3c62bdf0e94f5c2c9edebd4b9c34" + integrity sha512-ovJXBXkKGfq+CwmKTjluEqFi3p4h8xvkxGQQAQan22YCgef4KZ1mKGjzfGh6PL6AW5Csw0QiQPNuQyH+6Xk3hA== dependencies: cli-truncate "^4.0.0" colorette "^2.0.20" @@ -17478,11 +17549,6 @@ lodash.deburr@^4.1.0: resolved "https://registry.yarnpkg.com/lodash.deburr/-/lodash.deburr-4.1.0.tgz#ddb1bbb3ef07458c0177ba07de14422cb033ff9b" integrity sha512-m/M1U1f3ddMCs6Hq2tAsYThTBDaAKFDX3dwDo97GEYzamXi9SqUpjWi/Rrj/gf3X2n8ktwgZrlP1z6E3v/IExQ== -lodash.defaults@^4.2.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/lodash.defaults/-/lodash.defaults-4.2.0.tgz#d09178716ffea4dde9e5fb7b37f6f0802274580c" - integrity sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ== - lodash.difference@^4.5.0: version "4.5.0" resolved "https://registry.yarnpkg.com/lodash.difference/-/lodash.difference-4.5.0.tgz#9ccb4e505d486b91651345772885a2df27fd017c" @@ -17503,11 +17569,6 @@ lodash.includes@^4.3.0: resolved "https://registry.yarnpkg.com/lodash.includes/-/lodash.includes-4.3.0.tgz#60bb98a87cb923c68ca1e51325483314849f553f" integrity sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w== -lodash.isarguments@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz#2f573d85c6a24289ff00663b491c1d338ff3458a" - integrity sha512-chi4NHZlZqZD18a0imDHnZPrDeBbTtVN7GXMwuGdRH9qotxAjYs3aVLKc7zNOG9eddR5Ksd8rvFEBc9SsggPpg== - lodash.isboolean@^3.0.3: version "3.0.3" resolved "https://registry.yarnpkg.com/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz#6c2e171db2a257cd96802fd43b01b20d5f5870f6" @@ -17743,10 +17804,10 @@ lowercase-keys@^2.0.0: resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-2.0.0.tgz#2603e78b7b4b0006cbca2fbcc8a3202558ac9479" integrity sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA== -lru-cache@^10.0.2, "lru-cache@^9.1.1 || ^10.0.0": - version "10.1.0" - resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-10.1.0.tgz#2098d41c2dc56500e6c88584aa656c84de7d0484" - integrity sha512-/1clY/ui8CzjKFyjdvwPWJUYKiFVXG2I2cY0ssG7h4+hwk+XOIX7ZSG9Q7TW8TW3Kp3BUSqgFWBLgL4PJ+Blag== +lru-cache@^10.2.0, "lru-cache@^9.1.1 || ^10.0.0": + version "10.2.0" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-10.2.0.tgz#0bd445ca57363465900f4d1f9bd8db343a4d95c3" + integrity sha512-2bIM8x+VAf6JT4bKAljS1qUWgMsqZRPGJS6FSahIMPVvctcNhyVp7AJu7quxOW9jwkryBReKZY5tY5JYv2n/7Q== lru-cache@^5.1.1: version "5.1.1" @@ -17800,9 +17861,9 @@ magic-string@0.30.0: "@jridgewell/sourcemap-codec" "^1.4.13" magic-string@^0.30.0, magic-string@^0.30.3: - version "0.30.5" - resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.30.5.tgz#1994d980bd1c8835dc6e78db7cbd4ae4f24746f9" - integrity sha512-7xlpfBaQaP/T6Vh8MO/EqXSW5En6INHEvEXQiuff7Gku0PWjU3uf6w/j9o7O+SpB5fOAkrI5HeoNgwjEO0pFsA== + version "0.30.8" + resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.30.8.tgz#14e8624246d2bedba70d5462aa99ac9681844613" + integrity sha512-ISQTe55T2ao7XtlAStud6qwYPZjE4GK1S/BeVPus4jrq6JuOnQ00YKQC581RWhR122W7msZV263KzVeLoqidyQ== dependencies: "@jridgewell/sourcemap-codec" "^1.4.15" @@ -17948,12 +18009,12 @@ match-all@^1.2.6: integrity sha512-0EESkXiTkWzrQQntBu2uzKvLu6vVkUGz40nGPbSZuegcfE5UuSzNjLaIu76zJWuaT/2I3Z/8M06OlUOZLGwLlQ== match-sorter@^6.0.2: - version "6.3.1" - resolved "https://registry.yarnpkg.com/match-sorter/-/match-sorter-6.3.1.tgz#98cc37fda756093424ddf3cbc62bfe9c75b92bda" - integrity sha512-mxybbo3pPNuA+ZuCUhm5bwNkXrJTbsk5VWbR5wiwz/GC6LIiegBGn2w3O08UG/jdbYLinw51fSQ5xNU1U3MgBw== + version "6.3.4" + resolved "https://registry.yarnpkg.com/match-sorter/-/match-sorter-6.3.4.tgz#afa779d8e922c81971fbcb4781c7003ace781be7" + integrity sha512-jfZW7cWS5y/1xswZo8VBOdudUiSd9nifYRWphc9M5D/ee4w4AoXLgBEdRbgVaxbMuagBPeUC5y2Hi8DO6o9aDg== dependencies: - "@babel/runtime" "^7.12.5" - remove-accents "0.4.2" + "@babel/runtime" "^7.23.8" + remove-accents "0.5.0" matchstick-as@^0.5.2: version "0.5.2" @@ -18351,15 +18412,15 @@ mkdirp@^2.1.3: resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-2.1.6.tgz#964fbcb12b2d8c5d6fbc62a963ac95a273e2cc19" integrity sha512-+hEnITedc8LAtIP9u3HJDFIdcLV2vXP33sqLLIzkv1Db1zO/1OxbvYf0Y1OC/S/Qo5dxHXepofhmxL02PsKe+A== -mlly@^1.2.0, mlly@^1.4.2: - version "1.4.2" - resolved "https://registry.yarnpkg.com/mlly/-/mlly-1.4.2.tgz#7cf406aa319ff6563d25da6b36610a93f2a8007e" - integrity sha512-i/Ykufi2t1EZ6NaPLdfnZk2AX8cs0d+mTzVKuPfqPKPatxLApaBoxJQ9x1/uckXtrS/U5oisPMDkNs0yQTaBRg== +mlly@^1.2.0, mlly@^1.6.1: + version "1.6.1" + resolved "https://registry.yarnpkg.com/mlly/-/mlly-1.6.1.tgz#0983067dc3366d6314fc5e12712884e6978d028f" + integrity sha512-vLgaHvaeunuOXHSmEbZ9izxPx3USsk8KCQ8iC+aTlp5sKRSoZvwhHh5L9VbKSaVC6sJDqbyohIS76E2VmHIPAA== dependencies: - acorn "^8.10.0" - pathe "^1.1.1" + acorn "^8.11.3" + pathe "^1.1.2" pkg-types "^1.0.3" - ufo "^1.3.0" + ufo "^1.3.2" mnemonist@^0.38.0: version "0.38.5" @@ -18368,10 +18429,10 @@ mnemonist@^0.38.0: dependencies: obliterator "^2.0.0" -mocha@10.2.0, mocha@^10.0.0, mocha@^10.2.0: - version "10.2.0" - resolved "https://registry.yarnpkg.com/mocha/-/mocha-10.2.0.tgz#1fd4a7c32ba5ac372e03a17eef435bd00e5c68b8" - integrity sha512-IDY7fl/BecMwFHzoqF2sg/SHHANeBoMMXFlS9r0OXKDssYE1M5O43wUY/9BVPeIvfH2zmEbBfseqN9gBQZzXkg== +mocha@^10.0.0, mocha@^10.2.0: + version "10.3.0" + resolved "https://registry.yarnpkg.com/mocha/-/mocha-10.3.0.tgz#0e185c49e6dccf582035c05fa91084a4ff6e3fe9" + integrity sha512-uF2XJs+7xSLsrmIvn37i/wnc91nw7XjOQB8ccyx5aEgdnohr7n+rEiZP23WkCYHjilR6+EboEnbq/ZQDz4LSbg== dependencies: ansi-colors "4.1.1" browser-stdout "1.3.1" @@ -18380,13 +18441,12 @@ mocha@10.2.0, mocha@^10.0.0, mocha@^10.2.0: diff "5.0.0" escape-string-regexp "4.0.0" find-up "5.0.0" - glob "7.2.0" + glob "8.1.0" he "1.2.0" js-yaml "4.1.0" log-symbols "4.1.0" minimatch "5.0.1" ms "2.1.3" - nanoid "3.3.3" serialize-javascript "6.0.0" strip-json-comments "3.1.1" supports-color "8.1.1" @@ -18534,11 +18594,6 @@ nanoclone@^0.2.1: resolved "https://registry.yarnpkg.com/nanoclone/-/nanoclone-0.2.1.tgz#dd4090f8f1a110d26bb32c49ed2f5b9235209ed4" integrity sha512-wynEP02LmIbLpcYw8uBKpcfF6dmg2vcpKqxeH5UcoKEYdExslsdUA4ugFauuaeYdTB76ez6gJW8XAZ6CgkXYxA== -nanoid@3.3.3: - version "3.3.3" - resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.3.tgz#fd8e8b7aa761fe807dba2d1b98fb7241bb724a25" - integrity sha512-p1sjXuopFs0xg+fPASzQ28agW1oHD7xDsd9Xkf3T15H3c/cifrFHVwrh74PdoklAPi+i7MdRsE47vm2r6JoB+w== - nanoid@^3.0.2, nanoid@^3.1.20, nanoid@^3.1.23, nanoid@^3.3.7: version "3.3.7" resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.7.tgz#d0c301a691bc8d54efa0a2226ccf3fe2fd656bd8" @@ -18646,9 +18701,9 @@ nise@^1.5.2: path-to-regexp "^1.7.0" nise@^5.1.5: - version "5.1.7" - resolved "https://registry.yarnpkg.com/nise/-/nise-5.1.7.tgz#03ca96539efb306612eb60a8c5d6beeb208e27e5" - integrity sha512-wWtNUhkT7k58uvWTB/Gy26eA/EJKtPZFVAhEilN5UYVmmGRYOURbejRUyKm0Uu9XVEW7K5nBOZfR8VMB4QR2RQ== + version "5.1.9" + resolved "https://registry.yarnpkg.com/nise/-/nise-5.1.9.tgz#0cb73b5e4499d738231a473cd89bd8afbb618139" + integrity sha512-qOnoujW4SV6e40dYxJOb3uvuoPHtmLzIk4TFo+j0jPJoC+5Z9xja5qH5JZobEPsa8+YYphMrOSwnrshEhG2qww== dependencies: "@sinonjs/commons" "^3.0.0" "@sinonjs/fake-timers" "^11.2.2" @@ -18672,18 +18727,18 @@ no-case@^3.0.4: tslib "^2.0.3" nock@^13.5.1: - version "13.5.1" - resolved "https://registry.yarnpkg.com/nock/-/nock-13.5.1.tgz#4e40f9877ad0d43b7cdb474261c190f3715dd806" - integrity sha512-+s7b73fzj5KnxbKH4Oaqz07tQ8degcMilU4rrmnKvI//b0JMBU4wEXFQ8zqr+3+L4eWSfU3H/UoIVGUV0tue1Q== + version "13.5.4" + resolved "https://registry.yarnpkg.com/nock/-/nock-13.5.4.tgz#8918f0addc70a63736170fef7106a9721e0dc479" + integrity sha512-yAyTfdeNJGGBFxWdzSKCBYxs5FxLbCg5X5Q4ets974hcQzG1+qCxvIyOo4j2Ry6MUlhWVMX4OoYDefAIIwupjw== dependencies: debug "^4.1.0" json-stringify-safe "^5.0.1" propagate "^2.0.0" node-abi@^3.3.0: - version "3.54.0" - resolved "https://registry.yarnpkg.com/node-abi/-/node-abi-3.54.0.tgz#f6386f7548817acac6434c6cba02999c9aebcc69" - integrity sha512-p7eGEiQil0YUV3ItH4/tBb781L5impVmmx2E9FRKF7d18XXzp4PGT2tdYMFY6wQqgxD0IwNZOiSJ0/K0fSi/OA== + version "3.56.0" + resolved "https://registry.yarnpkg.com/node-abi/-/node-abi-3.56.0.tgz#ca807d5ff735ac6bbbd684ae3ff2debc1c2a40a7" + integrity sha512-fZjdhDOeRcaS+rcpve7XuwHBmktS1nS1gzgghwKUQQ8nTy2FdSDr6ZT8k6YhvlJeHmmQMYiT/IH9hfco5zeW2Q== dependencies: semver "^7.3.5" @@ -18708,9 +18763,9 @@ node-addon-api@^6.1.0: integrity sha512-+eawOlIgy680F0kBzPUNFhMZGtJ1YmqM6l4+Crf4IkImjYrO/mqPwRMh352g23uIaQKFItcQ64I7KMaJxHgAVA== node-addon-api@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-7.0.0.tgz#8136add2f510997b3b94814f4af1cce0b0e3962e" - integrity sha512-vgbBJTS4m5/KkE16t5Ly0WW9hz46swAstv0hYYwMtbG7AznRhNyfLRe8HZAiWIpcHzoO7HxhLuBQj9rJ/Ho0ZA== + version "7.1.0" + resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-7.1.0.tgz#71f609369379c08e251c558527a107107b5e0fdb" + integrity sha512-mNcltoe1R8o7STTegSOHdnJNN7s5EUvhoS7ShnTHDyOSd+8H+UdWODq6qSv67PjC8Zc5JRT8+oLAMCr0SIXw7g== node-cache@^5.1.2: version "5.1.2" @@ -18726,10 +18781,10 @@ node-emoji@1.11.0, node-emoji@^1.10.0: dependencies: lodash "^4.17.21" -node-fetch-native@^1.4.0, node-fetch-native@^1.4.1, node-fetch-native@^1.6.1: - version "1.6.1" - resolved "https://registry.yarnpkg.com/node-fetch-native/-/node-fetch-native-1.6.1.tgz#f95c74917d3cebc794cdae0cd2a9c7594aad0cb4" - integrity sha512-bW9T/uJDPAJB2YNYEpWzE54U5O3MQidXsOyTfnbKYtTtFexRvGzb1waphBN4ZwP6EcIvYYEOwW0b72BpAqydTw== +node-fetch-native@^1.4.0, node-fetch-native@^1.6.1, node-fetch-native@^1.6.2: + version "1.6.2" + resolved "https://registry.yarnpkg.com/node-fetch-native/-/node-fetch-native-1.6.2.tgz#f439000d972eb0c8a741b65dcda412322955e1c6" + integrity sha512-69mtXOFZ6hSkYiXAVB5SqaRvrbITC/NPyqv7yuu/qw0nmgPyYbIMYYNIDhNtwPrzk0ptrimrLz/hhjvm4w5Z+w== node-fetch@2.6.9, node-fetch@2.7.0, node-fetch@^2.6.0, node-fetch@^2.6.1, node-fetch@^2.6.12, node-fetch@^2.6.7, node-fetch@^2.6.8, node-fetch@^2.7.0: version "2.7.0" @@ -18906,9 +18961,9 @@ npm-run-path@^4.0.0, npm-run-path@^4.0.1: path-key "^3.0.0" npm-run-path@^5.1.0: - version "5.2.0" - resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-5.2.0.tgz#224cdd22c755560253dd71b83a1ef2f758b2e955" - integrity sha512-W4/tgAXFqFA0iL7fk0+uQ3g7wkL8xJmx3XdK0VGb4cHW//eZTtKGvFBBoRKVTpY7n6ze4NL9ly7rgXcHufqXKg== + version "5.3.0" + resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-5.3.0.tgz#e23353d0ebb9317f174e93417e4a4d82d0249e9f" + integrity sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ== dependencies: path-key "^4.0.0" @@ -18966,18 +19021,18 @@ object-copy@^0.1.0: define-property "^0.2.5" kind-of "^3.0.3" -object-inspect@^1.13.1, object-inspect@^1.9.0: +object-inspect@^1.13.1: version "1.13.1" resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.13.1.tgz#b96c6109324ccfef6b12216a956ca4dc2ff94bc2" integrity sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ== object-is@^1.1.5: - version "1.1.5" - resolved "https://registry.yarnpkg.com/object-is/-/object-is-1.1.5.tgz#b9deeaa5fc7f1846a0faecdceec138e5778f53ac" - integrity sha512-3cyDsyHgtmi7I7DfSSI2LDp6SK2lwvtbg0p0R1e0RvTqF5ceGx+K2dfSjm1bKDMVCFEDAQvy+o8c6a7VujOddw== + version "1.1.6" + resolved "https://registry.yarnpkg.com/object-is/-/object-is-1.1.6.tgz#1a6a53aed2dd8f7e6775ff870bea58545956ab07" + integrity sha512-F8cZ+KfGlSGi09lJT7/Nd6KJZ9ygtvYC0/UYYLI9nmQKLMnydpB9yvbv9K1uSkEu7FU9vYPmVwLg328tX+ot3Q== dependencies: - call-bind "^1.0.2" - define-properties "^1.1.3" + call-bind "^1.0.7" + define-properties "^1.2.1" object-keys@^1.1.1: version "1.1.1" @@ -18996,7 +19051,7 @@ object-visit@^1.0.0: dependencies: isobject "^3.0.0" -object.assign@^4.1.4: +object.assign@^4.1.4, object.assign@^4.1.5: version "4.1.5" resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.5.tgz#3a833f9ab7fdb80fc9e8d2300c803d216d8fdbb0" integrity sha512-byy+U7gp+FVwmyzKPYhW2h5l3crpmGsxl7X2s8y43IgxvG4g3QZ6CffDtsNQy1WsmZpQbO+ybo0AlW7TY6DcBQ== @@ -19016,35 +19071,35 @@ object.defaults@^1.1.0: for-own "^1.0.0" isobject "^3.0.0" -object.entries@^1.1.6, object.entries@^1.1.7: - version "1.1.7" - resolved "https://registry.yarnpkg.com/object.entries/-/object.entries-1.1.7.tgz#2b47760e2a2e3a752f39dd874655c61a7f03c131" - integrity sha512-jCBs/0plmPsOnrKAfFQXRG2NFjlhZgjjcBLSmTnEhU8U6vVTsVe8ANeQJCHTl3gSsI4J+0emOoCgoKlmQPMgmA== +object.entries@^1.1.7: + version "1.1.8" + resolved "https://registry.yarnpkg.com/object.entries/-/object.entries-1.1.8.tgz#bffe6f282e01f4d17807204a24f8edd823599c41" + integrity sha512-cmopxi8VwRIAw/fkijJohSfpef5PdN0pMQJN6VC/ZKvn0LIknWD8KtgY6KlQdEc4tIjcQ3HxSMmnvtzIscdaYQ== dependencies: - call-bind "^1.0.2" - define-properties "^1.2.0" - es-abstract "^1.22.1" + call-bind "^1.0.7" + define-properties "^1.2.1" + es-object-atoms "^1.0.0" -object.fromentries@^2.0.6, object.fromentries@^2.0.7: - version "2.0.7" - resolved "https://registry.yarnpkg.com/object.fromentries/-/object.fromentries-2.0.7.tgz#71e95f441e9a0ea6baf682ecaaf37fa2a8d7e616" - integrity sha512-UPbPHML6sL8PI/mOqPwsH4G6iyXcCGzLin8KvEPenOZN5lpCNBZZQ+V62vdjB1mQHrmqGQt5/OJzemUA+KJmEA== +object.fromentries@^2.0.7: + version "2.0.8" + resolved "https://registry.yarnpkg.com/object.fromentries/-/object.fromentries-2.0.8.tgz#f7195d8a9b97bd95cbc1999ea939ecd1a2b00c65" + integrity sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ== dependencies: - call-bind "^1.0.2" - define-properties "^1.2.0" - es-abstract "^1.22.1" + call-bind "^1.0.7" + define-properties "^1.2.1" + es-abstract "^1.23.2" + es-object-atoms "^1.0.0" object.groupby@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/object.groupby/-/object.groupby-1.0.1.tgz#d41d9f3c8d6c778d9cbac86b4ee9f5af103152ee" - integrity sha512-HqaQtqLnp/8Bn4GL16cj+CUYbnpe1bh0TtEaWvybszDG4tgxCJuRpV8VGuvNaI1fAnI4lUJzDG55MXcOH4JZcQ== + version "1.0.3" + resolved "https://registry.yarnpkg.com/object.groupby/-/object.groupby-1.0.3.tgz#9b125c36238129f6f7b61954a1e7176148d5002e" + integrity sha512-+Lhy3TQTuzXI5hevh8sBGqbmurHbbIjAi0Z4S63nthVLmLxfbj4T54a4CfZrXIrt9iP4mVAPYMo/v99taj3wjQ== dependencies: - call-bind "^1.0.2" - define-properties "^1.2.0" - es-abstract "^1.22.1" - get-intrinsic "^1.2.1" + call-bind "^1.0.7" + define-properties "^1.2.1" + es-abstract "^1.23.2" -object.hasown@^1.1.2: +object.hasown@^1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/object.hasown/-/object.hasown-1.1.3.tgz#6a5f2897bb4d3668b8e79364f98ccf971bda55ae" integrity sha512-fFI4VcYpRHvSLXxP7yiZOMAd331cPfd2p7PFDVbgUsYOfCT3tICVqXWngbjr4m49OvsBwUBQ6O2uQoJvy3RexA== @@ -19068,13 +19123,13 @@ object.pick@^1.2.0, object.pick@^1.3.0: isobject "^3.0.1" object.values@^1.1.6, object.values@^1.1.7: - version "1.1.7" - resolved "https://registry.yarnpkg.com/object.values/-/object.values-1.1.7.tgz#617ed13272e7e1071b43973aa1655d9291b8442a" - integrity sha512-aU6xnDFYT3x17e/f0IiiwlGPTy2jzMySGfUB4fq6z7CV8l85CWHDk5ErhyhpfDHhrOMwGFhSQkhMGHaIotA6Ng== + version "1.2.0" + resolved "https://registry.yarnpkg.com/object.values/-/object.values-1.2.0.tgz#65405a9d92cee68ac2d303002e0b8470a4d9ab1b" + integrity sha512-yBYjY9QX2hnRmZHAjG/f13MzmBzxzYgQhFrke06TTyKY5zSTEqkOeukBzIdVA3j3ulu8Qa3MbVFShV7T2RmGtQ== dependencies: - call-bind "^1.0.2" - define-properties "^1.2.0" - es-abstract "^1.22.1" + call-bind "^1.0.7" + define-properties "^1.2.1" + es-object-atoms "^1.0.0" obliterator@^2.0.0: version "2.0.4" @@ -19095,6 +19150,11 @@ ofetch@^1.3.3: node-fetch-native "^1.4.0" ufo "^1.3.0" +ohash@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/ohash/-/ohash-1.1.3.tgz#f12c3c50bfe7271ce3fd1097d42568122ccdcf07" + integrity sha512-zuHHiGTYTA1sYJ/wZN+t5HKZaH23i4yI1HMwbuXm24Nid7Dv0KcuRlKoNKS9UNfAVSBlnGLcuQrnOKWOZoEGaw== + on-exit-leak-free@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/on-exit-leak-free/-/on-exit-leak-free-0.2.0.tgz#b39c9e3bf7690d890f4861558b0d7b90a442d209" @@ -19193,9 +19253,9 @@ openpgp@5.10.2: asn1.js "^5.0.0" openpgp@^5.10.2: - version "5.11.0" - resolved "https://registry.yarnpkg.com/openpgp/-/openpgp-5.11.0.tgz#cec5b285d188148f7b5201b9aceb53850cc286a2" - integrity sha512-hytHsxIPtRhuh6uAmoBUThHSwHSX3imLu7x4453T+xkVqIw49rl22MRD4KQIAQdCDoVdouejzYgcuLmMA/2OAA== + version "5.11.1" + resolved "https://registry.yarnpkg.com/openpgp/-/openpgp-5.11.1.tgz#97f3a1dfb3d3573a0a73fe2efb29e6b1f8fefb1c" + integrity sha512-TynUBPuaSI7dN0gP+A38CjNRLxkOkkptefNanalDQ71BFAKKm+dLbksymSW5bUrB7RcAneMySL/Y+r/TbLpOnQ== dependencies: asn1.js "^5.0.0" @@ -19459,16 +19519,17 @@ parent-module@^1.0.0: dependencies: callsites "^3.0.0" -parse-asn1@^5.0.0, parse-asn1@^5.1.6: - version "5.1.6" - resolved "https://registry.yarnpkg.com/parse-asn1/-/parse-asn1-5.1.6.tgz#385080a3ec13cb62a62d39409cb3e88844cdaed4" - integrity sha512-RnZRo1EPU6JBnra2vGHj0yhp6ebyjBZpmUCLHWiFhxlzvBCCpAuZ7elsBp1PVAbQN0/04VD/19rfzlBSwLstMw== +parse-asn1@^5.0.0, parse-asn1@^5.1.7: + version "5.1.7" + resolved "https://registry.yarnpkg.com/parse-asn1/-/parse-asn1-5.1.7.tgz#73cdaaa822125f9647165625eb45f8a051d2df06" + integrity sha512-CTM5kuWR3sx9IFamcl5ErfPl6ea/N8IYwiJ+vpeB2g+1iknv7zBl5uPwbMbRVznRVbrNY6lGuDoE5b30grmbqg== dependencies: - asn1.js "^5.2.0" - browserify-aes "^1.0.0" - evp_bytestokey "^1.0.0" - pbkdf2 "^3.0.3" - safe-buffer "^5.1.1" + asn1.js "^4.10.1" + browserify-aes "^1.2.0" + evp_bytestokey "^1.0.3" + hash-base "~3.0" + pbkdf2 "^3.1.2" + safe-buffer "^5.2.1" parse-cache-control@^1.0.1: version "1.0.1" @@ -19737,10 +19798,10 @@ path-type@^4.0.0: resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b" integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== -pathe@^1.1.0, pathe@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/pathe/-/pathe-1.1.1.tgz#1dd31d382b974ba69809adc9a7a347e65d84829a" - integrity sha512-d+RQGp0MAYTIaDBIMmOfMwz3E+LOZnxx1HZd5R18mmCZY0QBlK0LDZfPc8FW8Ed2DlvsuE6PRjroDY+wg4+j/Q== +pathe@^1.1.0, pathe@^1.1.1, pathe@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/pathe/-/pathe-1.1.2.tgz#6c4cb47a945692e48a1ddd6e4094d170516437ec" + integrity sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ== pathval@^1.1.1: version "1.1.1" @@ -19752,7 +19813,7 @@ pause@0.0.1: resolved "https://registry.yarnpkg.com/pause/-/pause-0.0.1.tgz#1d408b3fdb76923b9543d96fb4c9dfd535d9cb5d" integrity sha512-KG8UEiEVkR3wGEb4m5yZkVCzigAD+cVEJck2CzYZO37ZGJfctvVptVO192MwrtPhzONn6go8ylnOdMhKqi4nfg== -pbkdf2@^3.0.17, pbkdf2@^3.0.3: +pbkdf2@^3.0.17, pbkdf2@^3.0.3, pbkdf2@^3.1.2: version "3.1.2" resolved "https://registry.yarnpkg.com/pbkdf2/-/pbkdf2-3.1.2.tgz#dd822aa0887580e52f1a039dc3eda108efae3075" integrity sha512-iuh7L6jA7JEGu2WxDwtQP1ddOpaJNC4KlDEFfdQajSGgGPNi4OyDc2R7QnbY2bR9QjBVGwgvTdNJZoE7RaxUMA== @@ -19955,24 +20016,29 @@ posix-character-classes@^0.1.0: resolved "https://registry.yarnpkg.com/posix-character-classes/-/posix-character-classes-0.1.1.tgz#01eac0fe3b5af71a2a6c02feabb8c1fef7e00eab" integrity sha512-xTgYBc3fuo7Yt7JbiuFxSYGToMoz8fLoE6TC9Wx1P/u+LfeThMOAqmuyECnlBaaJb+u1m9hHiXUEtwW4OzfUJg== +possible-typed-array-names@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/possible-typed-array-names/-/possible-typed-array-names-1.0.0.tgz#89bb63c6fada2c3e90adc4a647beeeb39cc7bf8f" + integrity sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q== + postcss-modules-extract-imports@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.0.0.tgz#cda1f047c0ae80c97dbe28c3e76a43b88025741d" integrity sha512-bdHleFnP3kZ4NYDhuGlVK+CMrQ/pqUm8bx/oGL93K6gVwiclvX5x0n76fYMKuIGKzlABOy13zsvqjb0f92TEXw== -postcss-modules-local-by-default@^4.0.3: - version "4.0.3" - resolved "https://registry.yarnpkg.com/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.0.3.tgz#b08eb4f083050708998ba2c6061b50c2870ca524" - integrity sha512-2/u2zraspoACtrbFRnTijMiQtb4GW4BvatjaG/bCjYQo8kLTdevCUlwuBHx2sCnSyrI3x3qj4ZK1j5LQBgzmwA== +postcss-modules-local-by-default@^4.0.4: + version "4.0.4" + resolved "https://registry.yarnpkg.com/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.0.4.tgz#7cbed92abd312b94aaea85b68226d3dec39a14e6" + integrity sha512-L4QzMnOdVwRm1Qb8m4x8jsZzKAaPAgrUF1r/hjDR2Xj7R+8Zsf97jAlSQzWtKx5YNiNGN8QxmPFIc/sh+RQl+Q== dependencies: icss-utils "^5.0.0" postcss-selector-parser "^6.0.2" postcss-value-parser "^4.1.0" -postcss-modules-scope@^3.0.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/postcss-modules-scope/-/postcss-modules-scope-3.1.0.tgz#fbfddfda93a31f310f1d152c2bb4d3f3c5592ee0" - integrity sha512-SaIbK8XW+MZbd0xHPf7kdfA/3eOt7vxJ72IRecn3EzuZVLr1r0orzf0MX/pN8m+NMDoo6X/SQd8oeKqGZd8PXg== +postcss-modules-scope@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/postcss-modules-scope/-/postcss-modules-scope-3.1.1.tgz#32cfab55e84887c079a19bbb215e721d683ef134" + integrity sha512-uZgqzdTleelWjzJY+Fhti6F3C9iF1JR/dODLs/JDefozYcKTBCdD8BIl6nNPbTbcLnGrk56hzwZC2DaGNvYjzA== dependencies: postcss-selector-parser "^6.0.4" @@ -19984,9 +20050,9 @@ postcss-modules-values@^4.0.0: icss-utils "^5.0.0" postcss-selector-parser@^6.0.2, postcss-selector-parser@^6.0.4: - version "6.0.15" - resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-6.0.15.tgz#11cc2b21eebc0b99ea374ffb9887174855a01535" - integrity sha512-rEYkQOMUCEMhsKbK66tbEU9QVIxbhN18YiniAwA7XQYTVBqrBy+P2p5JcdqsHgKM2zWylp8d7J6eszocfds5Sw== + version "6.0.16" + resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-6.0.16.tgz#3b88b9f5c5abd989ef4e2fc9ec8eedd34b20fb04" + integrity sha512-A0RVJrX+IUkVZbW3ClroRWurercFhieevHB38sr2+l9eUClMqome3LmEmnhlNy+5Mr2EYN6B2Kaw9wYdd+VHiw== dependencies: cssesc "^3.0.0" util-deprecate "^1.0.2" @@ -19996,14 +20062,14 @@ postcss-value-parser@^4.0.2, postcss-value-parser@^4.1.0, postcss-value-parser@^ resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz#723c09920836ba6d3e5af019f92bc0971c02e514" integrity sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ== -postcss@^8.3.11, postcss@^8.4.13, postcss@^8.4.21, postcss@^8.4.27, postcss@^8.4.32: - version "8.4.33" - resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.33.tgz#1378e859c9f69bf6f638b990a0212f43e2aaa742" - integrity sha512-Kkpbhhdjw2qQs2O2DGX+8m5OVqEcbB9HRBvuYM9pgrjEFUg30A9LmXNlTAUj4S9kgtGyrMbTzVjH7E+s5Re2yg== +postcss@^8.3.11, postcss@^8.4.13, postcss@^8.4.27, postcss@^8.4.33, postcss@^8.4.36: + version "8.4.37" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.37.tgz#4505f992cd0c20e03d25f13b31901640b2db731a" + integrity sha512-7iB/v/r7Woof0glKLH8b1SPHrsX7uhdO+Geb41QpF/+mWZHU3uxxSlN+UXGVit1PawOYDToO+AbZzhBzWRDwbQ== dependencies: nanoid "^3.3.7" picocolors "^1.0.0" - source-map-js "^1.0.2" + source-map-js "^1.2.0" postgres-array@~2.0.0: version "2.0.0" @@ -20028,14 +20094,14 @@ postgres-interval@^1.1.0: xtend "^4.0.0" preact@^10.12.0, preact@^10.5.9: - version "10.19.3" - resolved "https://registry.yarnpkg.com/preact/-/preact-10.19.3.tgz#7a7107ed2598a60676c943709ea3efb8aaafa899" - integrity sha512-nHHTeFVBTHRGxJXKkKu5hT8C/YWBkPso4/Gad6xuj5dbptt9iF9NZr9pHbPhBrnT2klheu7mHTxTZ/LjwJiEiQ== + version "10.20.0" + resolved "https://registry.yarnpkg.com/preact/-/preact-10.20.0.tgz#191c10a2ee3b9fca1a7ded6375266266380212f6" + integrity sha512-wU7iZw2BjsaKDal3pDRDy/HpPB6cuFOnVUCcw9aIPKG98+ZrXx3F+szkos8BVME5bquyKDKvRlOJFG8kMkcAbg== prebuild-install@^7.1.1: - version "7.1.1" - resolved "https://registry.yarnpkg.com/prebuild-install/-/prebuild-install-7.1.1.tgz#de97d5b34a70a0c81334fd24641f2a1702352e45" - integrity sha512-jAXscXWMcCK8GgCoHOfIr0ODh5ai8mj63L2nWrjuAgXE6tDyYGnx4/8o/rCgU+B4JSyZBKbeZqzhtwtC3ovxjw== + version "7.1.2" + resolved "https://registry.yarnpkg.com/prebuild-install/-/prebuild-install-7.1.2.tgz#a5fd9986f5a6251fbc47e1e5c65de71e68c0a056" + integrity sha512-UnNke3IQb6sgarcZIDU3gbMeTp/9SSU1DAIkil7PrqG1vZlBtY5msYccSKSHDqa3hNg436IXK+SNImReuA1wEQ== dependencies: detect-libc "^2.0.0" expand-template "^2.0.3" @@ -20356,11 +20422,11 @@ qs@6.11.1: side-channel "^1.0.4" qs@^6.10.3, qs@^6.11.0, qs@^6.11.2, qs@^6.4.0, qs@^6.9.4, qs@^6.9.6: - version "6.11.2" - resolved "https://registry.yarnpkg.com/qs/-/qs-6.11.2.tgz#64bea51f12c1f5da1bc01496f48ffcff7c69d7d9" - integrity sha512-tDNIz22aBzCDxLtVH++VnTfzxlfeK5CbqohpSqpJgj1Wg/cQbStNAz3NuqCs5vV+pjBsK4x4pN9HlVh7rcYRiA== + version "6.12.0" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.12.0.tgz#edd40c3b823995946a8a0b1f208669c7a200db77" + integrity sha512-trVZiI6RMOkO476zLGaBIzszOdFPnCCXHPG9kn0yuS1uz6xdVxPfZdB3vUig9pxPFDM9BRAgz/YUIVQ1/vuiUg== dependencies: - side-channel "^1.0.4" + side-channel "^1.0.6" query-string@7.1.3, query-string@^7.1.3: version "7.1.3" @@ -20435,9 +20501,9 @@ rabin-wasm@^0.1.4: readable-stream "^3.6.0" radix3@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/radix3/-/radix3-1.1.0.tgz#9745df67a49c522e94a33d0a93cf743f104b6e0d" - integrity sha512-pNsHDxbGORSvuSScqNJ+3Km6QAVqk8CfsCBIEoDgpqLrkD2f3QM4I7d1ozJJ172OmIcoUcerZaNWqtLkRXTV3A== + version "1.1.1" + resolved "https://registry.yarnpkg.com/radix3/-/radix3-1.1.1.tgz#60a56876ffec62c88a22396a6a1c4c7efe9eb4b1" + integrity sha512-yUUd5VTiFtcMEx0qFUxGAv5gbMc1un4RvEO1JZdP7ZUl/RHygZK6PknIKntmQRZxnMY3ZXD2ISaw1ij8GYW1yg== random-bytes@~1.0.0: version "1.0.0" @@ -20469,16 +20535,6 @@ range-parser@^1.2.1, range-parser@~1.2.1: resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031" integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg== -raw-body@2.5.1: - version "2.5.1" - resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.5.1.tgz#fe1b1628b181b700215e5fd42389f98b71392857" - integrity sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig== - dependencies: - bytes "3.1.2" - http-errors "2.0.0" - iconv-lite "0.4.24" - unpipe "1.0.0" - raw-body@2.5.2, raw-body@^2.2.0, raw-body@^2.4.1: version "2.5.2" resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.5.2.tgz#99febd83b90e08975087e8f1f9419a149366b68a" @@ -20594,9 +20650,9 @@ react-lifecycles-compat@^3.0.4: integrity sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA== react-loading-skeleton@^3.3.1: - version "3.3.1" - resolved "https://registry.yarnpkg.com/react-loading-skeleton/-/react-loading-skeleton-3.3.1.tgz#cd6e3a626ee86c76a46c14e2379243f2f8834e1b" - integrity sha512-NilqqwMh2v9omN7LteiDloEVpFyMIa0VGqF+ukqp0ncVlYu1sKYbYGX9JEl+GtOT9TKsh04zCHAbavnQ2USldA== + version "3.4.0" + resolved "https://registry.yarnpkg.com/react-loading-skeleton/-/react-loading-skeleton-3.4.0.tgz#c71a3a17259d08e4064974aa0b07f150a09dfd57" + integrity sha512-1oJEBc9+wn7BbkQQk7YodlYEIjgeR+GrRjD+QXkVjwZN7LGIcAFHrx4NhT7UHGBxNY1+zax3c+Fo6XQM4R7CgA== react-native-fetch-api@^3.0.0: version "3.0.0" @@ -20639,10 +20695,10 @@ react-refresh@0.14.0, react-refresh@^0.14.0: resolved "https://registry.yarnpkg.com/react-refresh/-/react-refresh-0.14.0.tgz#4e02825378a5f227079554d4284889354e5f553e" integrity sha512-wViHqhAd8OHeLS/IRMJjTSDHF3U9eWi62F/MledQGPdJGDhodXJ9PBLNGr6WWL7qlH12Mt3TyTpbS+hGXMjCzQ== -react-remove-scroll-bar@^2.3.3, react-remove-scroll-bar@^2.3.4: - version "2.3.4" - resolved "https://registry.yarnpkg.com/react-remove-scroll-bar/-/react-remove-scroll-bar-2.3.4.tgz#53e272d7a5cb8242990c7f144c44d8bd8ab5afd9" - integrity sha512-63C4YQBUt0m6ALadE9XV56hV8BgJWDmmTPY758iIJjfQKt2nYwoUrPk0LXRXcB/yIj82T1/Ixfdpdk68LwIB0A== +react-remove-scroll-bar@^2.3.3, react-remove-scroll-bar@^2.3.6: + version "2.3.6" + resolved "https://registry.yarnpkg.com/react-remove-scroll-bar/-/react-remove-scroll-bar-2.3.6.tgz#3e585e9d163be84a010180b18721e851ac81a29c" + integrity sha512-DtSYaao4mBmX+HDo5YWYdBWQwYIQQshUV/dVxFxK+KM26Wjwp1gZ6rv6OC3oujI6Bfu6Xyg3TwK533AQutsn/g== dependencies: react-style-singleton "^2.2.1" tslib "^2.0.0" @@ -20659,11 +20715,11 @@ react-remove-scroll@2.5.5: use-sidecar "^1.1.2" react-remove-scroll@^2.5.7: - version "2.5.7" - resolved "https://registry.yarnpkg.com/react-remove-scroll/-/react-remove-scroll-2.5.7.tgz#15a1fd038e8497f65a695bf26a4a57970cac1ccb" - integrity sha512-FnrTWO4L7/Bhhf3CYBNArEG/yROV0tKmTv7/3h9QCFvH6sndeFf1wPqOcbFVu5VAulS5dV1wGT3GZZ/1GawqiA== + version "2.5.9" + resolved "https://registry.yarnpkg.com/react-remove-scroll/-/react-remove-scroll-2.5.9.tgz#6a38e7d46043abc2c6b0fb39db650b9f2e38be3e" + integrity sha512-bvHCLBrFfM2OgcrpPY2YW84sPdS2o2HKWJUf1xGyGLnSoEnOTOBpahIarjRuYtN0ryahCeP242yf+5TrBX/pZA== dependencies: - react-remove-scroll-bar "^2.3.4" + react-remove-scroll-bar "^2.3.6" react-style-singleton "^2.2.1" tslib "^2.1.0" use-callback-ref "^1.3.0" @@ -20690,12 +20746,12 @@ react-router-dom@5.3.4: tiny-warning "^1.0.0" react-router-dom@^6.14.1, react-router-dom@^6.4.3: - version "6.21.1" - resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-6.21.1.tgz#58b459d2fe1841388c95bb068f85128c45e27349" - integrity sha512-QCNrtjtDPwHDO+AO21MJd7yIcr41UetYt5jzaB9Y1UYaPTCnVuJq6S748g1dE11OQlCFIQg+RtAA1SEZIyiBeA== + version "6.22.3" + resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-6.22.3.tgz#9781415667fd1361a475146c5826d9f16752a691" + integrity sha512-7ZILI7HjcE+p31oQvwbokjk6OA/bnFxrhJ19n82Ex9Ph8fNAq+Hm/7KchpMGlTgWhUxRHMMCut+vEtNpWpowKw== dependencies: - "@remix-run/router" "1.14.1" - react-router "6.21.1" + "@remix-run/router" "1.15.3" + react-router "6.22.3" react-router@5.3.4: version "5.3.4" @@ -20712,12 +20768,12 @@ react-router@5.3.4: tiny-invariant "^1.0.2" tiny-warning "^1.0.0" -react-router@6.21.1: - version "6.21.1" - resolved "https://registry.yarnpkg.com/react-router/-/react-router-6.21.1.tgz#8db7ee8d7cfc36513c9a66b44e0897208c33be34" - integrity sha512-W0l13YlMTm1YrpVIOpjCADJqEUpz1vm+CMo47RuFX4Ftegwm6KOYsL5G3eiE52jnJpKvzm6uB/vTKTPKM8dmkA== +react-router@6.22.3: + version "6.22.3" + resolved "https://registry.yarnpkg.com/react-router/-/react-router-6.22.3.tgz#9d9142f35e08be08c736a2082db5f0c9540a885e" + integrity sha512-dr2eb3Mj5zK2YISHK++foM9w4eBnO23eKnZEDs7c880P6oKbrjz/Svg9+nxqtHQK+oMW4OtjZca0RqPglXxguQ== dependencies: - "@remix-run/router" "1.14.1" + "@remix-run/router" "1.15.3" react-select@5.7.0: version "5.7.0" @@ -20747,7 +20803,7 @@ react-side-effect@^2.1.0: resolved "https://registry.yarnpkg.com/react-side-effect/-/react-side-effect-2.1.2.tgz#dc6345b9e8f9906dc2eeb68700b615e0b4fe752a" integrity sha512-PVjOcvVOyIILrYoyGEpDN3vmYNLdy1CajSFNt4TDsVQC5KpTijDvWVoR+/7Rz2xT978D8/ZtFceXxzsPwZEDvw== -react-smooth@^2.0.4, react-smooth@^2.0.5: +react-smooth@^2.0.4: version "2.0.5" resolved "https://registry.yarnpkg.com/react-smooth/-/react-smooth-2.0.5.tgz#d153b7dffc7143d0c99e82db1532f8cf93f20ecd" integrity sha512-BMP2Ad42tD60h0JW6BFaib+RJuV5dsXJK9Baxiv/HlNFjvRLqA9xrNKxVWnUIZPQfzUwGXIlU/dSYLU+54YGQA== @@ -20755,6 +20811,15 @@ react-smooth@^2.0.4, react-smooth@^2.0.5: fast-equals "^5.0.0" react-transition-group "2.9.0" +react-smooth@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/react-smooth/-/react-smooth-4.0.0.tgz#69e560ab69b69a066187d70cb92c1a664f7f046a" + integrity sha512-2NMXOBY1uVUQx1jBeENGA497HK20y6CPGYL1ZnJLeoQ8rrc3UfmOM82sRxtzpcoCkUMy4CS0RGylfuVhuFjBgg== + dependencies: + fast-equals "^5.0.1" + prop-types "^15.8.1" + react-transition-group "^4.4.5" + react-style-singleton@^2.2.1: version "2.2.1" resolved "https://registry.yarnpkg.com/react-style-singleton/-/react-style-singleton-2.2.1.tgz#f99e420492b2d8f34d38308ff660b60d0b1205b4" @@ -20827,7 +20892,7 @@ read-pkg@^5.2.0: parse-json "^5.0.0" type-fest "^0.6.0" -readable-stream@3, readable-stream@^3.1.1, readable-stream@^3.4.0, readable-stream@^3.5.0, readable-stream@^3.6.0, readable-stream@^3.6.2: +readable-stream@3, readable-stream@^3.1.1, readable-stream@^3.4.0, readable-stream@^3.5.0, readable-stream@^3.6.0: version "3.6.2" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.2.tgz#56a9b36ea965c00c5a93ef31eb111a0f11056967" integrity sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA== @@ -20836,7 +20901,7 @@ readable-stream@3, readable-stream@^3.1.1, readable-stream@^3.4.0, readable-stre string_decoder "^1.1.1" util-deprecate "^1.0.1" -readable-stream@^2.0.0, readable-stream@^2.2.2, readable-stream@^2.3.0, readable-stream@^2.3.5, readable-stream@~2.3.6: +readable-stream@^2.0.0, readable-stream@^2.2.2, readable-stream@^2.3.0, readable-stream@^2.3.5, readable-stream@^2.3.8, readable-stream@~2.3.6: version "2.3.8" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.8.tgz#91125e8042bba1b9887f49345f6277027ce8be9b" integrity sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA== @@ -20901,15 +20966,15 @@ recharts@2.9.0: victory-vendor "^36.6.8" recharts@^2.7.2: - version "2.10.3" - resolved "https://registry.yarnpkg.com/recharts/-/recharts-2.10.3.tgz#a5dbe219354d744701e8bbd116fe42393af92f6b" - integrity sha512-G4J96fKTZdfFQd6aQnZjo2nVNdXhp+uuLb00+cBTGLo85pChvm1+E67K3wBOHDE/77spcYb2Cy9gYWVqiZvQCg== + version "2.12.3" + resolved "https://registry.yarnpkg.com/recharts/-/recharts-2.12.3.tgz#c059bd34eaf1c80d23fc2b953ad3abfa9474694a" + integrity sha512-vE/F7wTlokf5mtCqVDJlVKelCjliLSJ+DJxj79XlMREm7gpV7ljwbrwE3CfeaoDlOaLX+6iwHaVRn9587YkwIg== dependencies: clsx "^2.0.0" eventemitter3 "^4.0.1" - lodash "^4.17.19" + lodash "^4.17.21" react-is "^16.10.2" - react-smooth "^2.0.5" + react-smooth "^4.0.0" recharts-scale "^0.4.4" tiny-invariant "^1.3.1" victory-vendor "^36.6.8" @@ -20950,18 +21015,6 @@ redeyed@~2.1.0: dependencies: esprima "~4.0.0" -redis-errors@^1.0.0, redis-errors@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/redis-errors/-/redis-errors-1.2.0.tgz#eb62d2adb15e4eaf4610c04afe1529384250abad" - integrity sha512-1qny3OExCf0UvUV/5wpYKf2YwPcOqXzkwKKSmKHiE6ZMQs5heeE/c8eXK+PNllPvmjgAbfnsbpkGZWy8cBpn9w== - -redis-parser@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/redis-parser/-/redis-parser-3.0.0.tgz#b66d828cdcafe6b4b8a428a7def4c6bcac31c8b4" - integrity sha512-DJnGAeenTdpMEH6uAJRK/uiyEIH9WVsUmoLwzudwGJUwZPp80PDBWPHXSAGNPwNvIXAbe7MSUB1zQFugFml66A== - dependencies: - redis-errors "^1.0.0" - reduce-flatten@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/reduce-flatten/-/reduce-flatten-2.0.0.tgz#734fd84e65f375d7ca4465c69798c25c9d10ae27" @@ -20984,15 +21037,21 @@ reflect-metadata@^0.1.13: resolved "https://registry.yarnpkg.com/reflect-metadata/-/reflect-metadata-0.1.14.tgz#24cf721fe60677146bb77eeb0e1f9dece3d65859" integrity sha512-ZhYeb6nRaXCfhnndflDK8qI6ZQ/YcWZCISRAWICW9XYqMUwjZM9Z0DveWX/ABN01oxSHwVxKQmxeYZSsm0jh5A== +reflect-metadata@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/reflect-metadata/-/reflect-metadata-0.2.1.tgz#8d5513c0f5ef2b4b9c3865287f3c0940c1f67f74" + integrity sha512-i5lLI6iw9AU3Uu4szRNPPEkomnkjRTaVt9hy/bn5g/oSzekBSMeLZblcjP74AW0vBabqERLLIrz+gR8QYR54Tw== + reflect.getprototypeof@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/reflect.getprototypeof/-/reflect.getprototypeof-1.0.4.tgz#aaccbf41aca3821b87bb71d9dcbc7ad0ba50a3f3" - integrity sha512-ECkTw8TmJwW60lOTR+ZkODISW6RQ8+2CL3COqtiJKLd6MmB45hN51HprHFziKLGkAuTGQhBb91V8cy+KHlaCjw== + version "1.0.6" + resolved "https://registry.yarnpkg.com/reflect.getprototypeof/-/reflect.getprototypeof-1.0.6.tgz#3ab04c32a8390b770712b7a8633972702d278859" + integrity sha512-fmfw4XgoDke3kdI6h4xcUz1dG8uaiv5q9gcEwLS4Pnth2kxT+GZ7YehS1JTMGBQmtV7Y4GFGbs2re2NqhdozUg== dependencies: - call-bind "^1.0.2" - define-properties "^1.2.0" - es-abstract "^1.22.1" - get-intrinsic "^1.2.1" + call-bind "^1.0.7" + define-properties "^1.2.1" + es-abstract "^1.23.1" + es-errors "^1.3.0" + get-intrinsic "^1.2.4" globalthis "^1.0.3" which-builtin-type "^1.1.3" @@ -21028,14 +21087,15 @@ regex-not@^1.0.0, regex-not@^1.0.2: extend-shallow "^3.0.2" safe-regex "^1.1.0" -regexp.prototype.flags@^1.5.0, regexp.prototype.flags@^1.5.1: - version "1.5.1" - resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.5.1.tgz#90ce989138db209f81492edd734183ce99f9677e" - integrity sha512-sy6TXMN+hnP/wMy+ISxg3krXx7BAtWVO4UouuCN/ziM9UEne0euamVNafDfvC83bRNr95y0V5iijeDQFUNpvrg== +regexp.prototype.flags@^1.5.1, regexp.prototype.flags@^1.5.2: + version "1.5.2" + resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.5.2.tgz#138f644a3350f981a858c44f6bb1a61ff59be334" + integrity sha512-NcDiDkTLuPR+++OCKB0nWafEmhg/Da8aUPLPMQbK+bxKKCm1/S5he+AqYa4PlMCVBalb4/yxIRub6qkEx5yJbw== dependencies: - call-bind "^1.0.2" - define-properties "^1.2.0" - set-function-name "^2.0.0" + call-bind "^1.0.6" + define-properties "^1.2.1" + es-errors "^1.3.0" + set-function-name "^2.0.1" regexpu-core@^5.3.1: version "5.3.2" @@ -21092,15 +21152,20 @@ regjsparser@^0.9.1: dependencies: jsesc "~0.5.0" +rehackt@0.0.6: + version "0.0.6" + resolved "https://registry.yarnpkg.com/rehackt/-/rehackt-0.0.6.tgz#7a0a2247f2295e7548915417e44fbbf03bf004f4" + integrity sha512-l3WEzkt4ntlEc/IB3/mF6SRgNHA6zfQR7BlGOgBTOmx7IJJXojDASav+NsgXHFjHn+6RmwqsGPFgZpabWpeOdw== + relateurl@^0.2.7: version "0.2.7" resolved "https://registry.yarnpkg.com/relateurl/-/relateurl-0.2.7.tgz#54dbf377e51440aca90a4cd274600d3ff2d888a9" integrity sha512-G08Dxvm4iDN3MLM0EsP62EDV9IuhXPR6blNz6Utcp7zyV3tr4HVNINt6MpaRWbxoOHT3Q7YN2P+jaHX8vUbgog== -remove-accents@0.4.2: - version "0.4.2" - resolved "https://registry.yarnpkg.com/remove-accents/-/remove-accents-0.4.2.tgz#0a43d3aaae1e80db919e07ae254b285d9e1c7bb5" - integrity sha512-7pXIJqJOq5tFgG1A2Zxti3Ht8jJF337m4sowbuHsW30ZnkQFnDzy9qBNhgzX8ZLW4+UBcXiiR7SwR6pokHsxiA== +remove-accents@0.5.0: + version "0.5.0" + resolved "https://registry.yarnpkg.com/remove-accents/-/remove-accents-0.5.0.tgz#77991f37ba212afba162e375b627631315bed687" + integrity sha512-8g3/Otx1eJaVD12e31UbJj1YzdtVvzH85HV7t+9MJYk/u3XmkOUJ5Ys9wQrf9PCPK8+xn4ymzqYCiZl6QWKn+A== remove-trailing-separator@^1.0.1: version "1.1.0" @@ -21277,7 +21342,7 @@ resolve@1.17.0: dependencies: path-parse "^1.0.6" -resolve@^1.1.6, resolve@^1.1.7, resolve@^1.10.0, resolve@^1.12.0, resolve@^1.14.2, resolve@^1.17.0, resolve@^1.19.0, resolve@^1.20.0, resolve@^1.22.0, resolve@^1.22.1, resolve@^1.22.4: +resolve@^1.1.6, resolve@^1.1.7, resolve@^1.10.0, resolve@^1.12.0, resolve@^1.14.2, resolve@^1.17.0, resolve@^1.19.0, resolve@^1.20.0, resolve@^1.22.0, resolve@^1.22.1, resolve@^1.22.4, resolve@~1.22.1: version "1.22.8" resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.8.tgz#b6c87a9f2aa06dfab52e3d70ac8cde321fa5a48d" integrity sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw== @@ -21286,7 +21351,7 @@ resolve@^1.1.6, resolve@^1.1.7, resolve@^1.10.0, resolve@^1.12.0, resolve@^1.14. path-parse "^1.0.7" supports-preserve-symlinks-flag "^1.0.0" -resolve@^2.0.0-next.4: +resolve@^2.0.0-next.5: version "2.0.0-next.5" resolved "https://registry.yarnpkg.com/resolve/-/resolve-2.0.0-next.5.tgz#6b0ec3107e671e52b68cd068ef327173b90dc03c" integrity sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA== @@ -21362,9 +21427,9 @@ reusify@^1.0.4: integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw== rfdc@^1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/rfdc/-/rfdc-1.3.0.tgz#d0b7c441ab2720d05dc4cf26e01c89631d9da08b" - integrity sha512-V2hovdzFbOi77/WajaSMXk2OLm+xNIeQdMMuB7icj7bk6zi2F8GGAxigcnDFpJHbNyNcgyJDiP+8nOrY5cZGrA== + version "1.3.1" + resolved "https://registry.yarnpkg.com/rfdc/-/rfdc-1.3.1.tgz#2b6d4df52dffe8bb346992a10ea9451f24373a8f" + integrity sha512-r5a3l5HzYlIC68TpmYKlxWjmOP6wiPJ1vWv2HeLhNsRZMrCkxeqxiHlQ21oXmQ4F3SiryXBHhAD7JZqvOJjFmg== rimraf@3.0.2, rimraf@^3.0.0, rimraf@^3.0.2: version "3.0.2" @@ -21423,26 +21488,26 @@ rollup@^3.27.1: optionalDependencies: fsevents "~2.3.2" -rollup@^4.2.0, rollup@^4.9.6: - version "4.9.6" - resolved "https://registry.yarnpkg.com/rollup/-/rollup-4.9.6.tgz#4515facb0318ecca254a2ee1315e22e09efc50a0" - integrity sha512-05lzkCS2uASX0CiLFybYfVkwNbKZG5NFQ6Go0VWyogFTXXbR039UVsegViTntkk4OglHBdF54ccApXRRuXRbsg== +rollup@^4.13.0, rollup@^4.9.6: + version "4.13.0" + resolved "https://registry.yarnpkg.com/rollup/-/rollup-4.13.0.tgz#dd2ae144b4cdc2ea25420477f68d4937a721237a" + integrity sha512-3YegKemjoQnYKmsBlOHfMLVPPA5xLkQ8MHLLSw/fBrFaVkEayL51DilPpNNLq1exr98F2B1TzrV0FUlN3gWRPg== dependencies: "@types/estree" "1.0.5" optionalDependencies: - "@rollup/rollup-android-arm-eabi" "4.9.6" - "@rollup/rollup-android-arm64" "4.9.6" - "@rollup/rollup-darwin-arm64" "4.9.6" - "@rollup/rollup-darwin-x64" "4.9.6" - "@rollup/rollup-linux-arm-gnueabihf" "4.9.6" - "@rollup/rollup-linux-arm64-gnu" "4.9.6" - "@rollup/rollup-linux-arm64-musl" "4.9.6" - "@rollup/rollup-linux-riscv64-gnu" "4.9.6" - "@rollup/rollup-linux-x64-gnu" "4.9.6" - "@rollup/rollup-linux-x64-musl" "4.9.6" - "@rollup/rollup-win32-arm64-msvc" "4.9.6" - "@rollup/rollup-win32-ia32-msvc" "4.9.6" - "@rollup/rollup-win32-x64-msvc" "4.9.6" + "@rollup/rollup-android-arm-eabi" "4.13.0" + "@rollup/rollup-android-arm64" "4.13.0" + "@rollup/rollup-darwin-arm64" "4.13.0" + "@rollup/rollup-darwin-x64" "4.13.0" + "@rollup/rollup-linux-arm-gnueabihf" "4.13.0" + "@rollup/rollup-linux-arm64-gnu" "4.13.0" + "@rollup/rollup-linux-arm64-musl" "4.13.0" + "@rollup/rollup-linux-riscv64-gnu" "4.13.0" + "@rollup/rollup-linux-x64-gnu" "4.13.0" + "@rollup/rollup-linux-x64-musl" "4.13.0" + "@rollup/rollup-win32-arm64-msvc" "4.13.0" + "@rollup/rollup-win32-ia32-msvc" "4.13.0" + "@rollup/rollup-win32-x64-msvc" "4.13.0" fsevents "~2.3.2" rpc-websockets@^7.5.1: @@ -21496,13 +21561,13 @@ rxjs@^6.4.0, rxjs@^6.6.0, rxjs@^6.6.3: dependencies: tslib "^1.9.0" -safe-array-concat@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/safe-array-concat/-/safe-array-concat-1.0.1.tgz#91686a63ce3adbea14d61b14c99572a8ff84754c" - integrity sha512-6XbUAseYE2KtOuGueyeobCySj9L4+66Tn6KQMOPQJrAJEowYKW/YR/MGJZl7FdydUdaFu4LYyDZjxf4/Nmo23Q== +safe-array-concat@^1.1.0, safe-array-concat@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/safe-array-concat/-/safe-array-concat-1.1.2.tgz#81d77ee0c4e8b863635227c721278dd524c20edb" + integrity sha512-vj6RsCsWBCf19jIeHEfkRMw8DPiBb+DMXklQ/1SGDHOMlHdPUkZXFQ2YdplS23zESTijAcurb1aSgJA3AgMu1Q== dependencies: - call-bind "^1.0.2" - get-intrinsic "^1.2.1" + call-bind "^1.0.7" + get-intrinsic "^1.2.4" has-symbols "^1.0.3" isarray "^2.0.5" @@ -21516,13 +21581,13 @@ safe-buffer@5.2.1, safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, s resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== -safe-regex-test@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/safe-regex-test/-/safe-regex-test-1.0.0.tgz#793b874d524eb3640d1873aad03596db2d4f2295" - integrity sha512-JBUUzyOgEwXQY1NuPtvcj/qcBDbDmEvWufhlnXZIm75DEHp+afM1r1ujJpJsV/gSM4t59tpDyPi1sd6ZaPFfsA== +safe-regex-test@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/safe-regex-test/-/safe-regex-test-1.0.3.tgz#a5b4c0f06e0ab50ea2c395c14d8371232924c377" + integrity sha512-CdASjNJPvRa7roO6Ra/gLYBTzYzzPyyBXxIMdGW3USQLyjWEls2RgW5UBTXaQVp+OrpeCK3bLem8smtmheoRuw== dependencies: - call-bind "^1.0.2" - get-intrinsic "^1.1.3" + call-bind "^1.0.6" + es-errors "^1.3.0" is-regex "^1.1.4" safe-regex@^1.1.0: @@ -21638,7 +21703,7 @@ secp256k1@^4.0.1, secp256k1@^4.0.3: node-addon-api "^2.0.0" node-gyp-build "^4.2.0" -"semver@2 || 3 || 4 || 5", semver@7.3.5, semver@7.4.0, semver@7.5.4, semver@^5.5.0, semver@^6.0.0, semver@^6.3.0, semver@^6.3.1, semver@^7.3.2, semver@^7.3.4, semver@^7.3.5, semver@^7.3.7, semver@^7.3.8, semver@^7.5.2, semver@^7.5.3, semver@^7.5.4: +"semver@2 || 3 || 4 || 5", semver@7.3.5, semver@7.4.0, semver@7.5.4, semver@^5.5.0, semver@^6.0.0, semver@^6.3.0, semver@^6.3.1, semver@^7.3.2, semver@^7.3.4, semver@^7.3.5, semver@^7.3.7, semver@^7.3.8, semver@^7.5.2, semver@^7.5.3, semver@^7.5.4, semver@~7.5.4: version "7.6.0" resolved "https://registry.yarnpkg.com/semver/-/semver-7.6.0.tgz#1a46a4db4bffcccd97b743b5005c8325f23d4e2d" integrity sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg== @@ -21740,24 +21805,27 @@ set-blocking@^2.0.0: resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" integrity sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw== -set-function-length@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/set-function-length/-/set-function-length-1.1.1.tgz#4bc39fafb0307224a33e106a7d35ca1218d659ed" - integrity sha512-VoaqjbBJKiWtg4yRcKBQ7g7wnGnLV3M8oLvVWwOk2PdYY6PEFegR1vezXR0tw6fZGF9csVakIRjrJiy2veSBFQ== +set-function-length@^1.2.1: + version "1.2.2" + resolved "https://registry.yarnpkg.com/set-function-length/-/set-function-length-1.2.2.tgz#aac72314198eaed975cf77b2c3b6b880695e5449" + integrity sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg== dependencies: - define-data-property "^1.1.1" - get-intrinsic "^1.2.1" + define-data-property "^1.1.4" + es-errors "^1.3.0" + function-bind "^1.1.2" + get-intrinsic "^1.2.4" gopd "^1.0.1" - has-property-descriptors "^1.0.0" + has-property-descriptors "^1.0.2" -set-function-name@^2.0.0, set-function-name@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/set-function-name/-/set-function-name-2.0.1.tgz#12ce38b7954310b9f61faa12701620a0c882793a" - integrity sha512-tMNCiqYVkXIZgc2Hnoy2IvC/f8ezc5koaRFkCjrpWzGpCd3qbZXPzVy9MAZzK1ch/X0jvSkojys3oqJN0qCmdA== +set-function-name@^2.0.1, set-function-name@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/set-function-name/-/set-function-name-2.0.2.tgz#16a705c5a0dc2f5e638ca96d8a8cd4e1c2b90985" + integrity sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ== dependencies: - define-data-property "^1.0.1" + define-data-property "^1.1.4" + es-errors "^1.3.0" functions-have-names "^1.2.3" - has-property-descriptors "^1.0.0" + has-property-descriptors "^1.0.2" set-value@^2.0.0, set-value@^2.0.1: version "2.0.1" @@ -21855,14 +21923,15 @@ shiki@^0.14.7: vscode-oniguruma "^1.7.0" vscode-textmate "^8.0.0" -side-channel@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.4.tgz#efce5c8fdc104ee751b25c58d4290011fa5ea2cf" - integrity sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw== +side-channel@^1.0.4, side-channel@^1.0.6: + version "1.0.6" + resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.6.tgz#abd25fb7cd24baf45466406b1096b7831c9215f2" + integrity sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA== dependencies: - call-bind "^1.0.0" - get-intrinsic "^1.0.2" - object-inspect "^1.9.0" + call-bind "^1.0.7" + es-errors "^1.3.0" + get-intrinsic "^1.2.4" + object-inspect "^1.13.1" sift@16.0.1: version "16.0.1" @@ -22077,9 +22146,9 @@ solc@0.7.3: tmp "0.0.33" solidity-ast@^0.4.51: - version "0.4.55" - resolved "https://registry.yarnpkg.com/solidity-ast/-/solidity-ast-0.4.55.tgz#00b685e6eefb2e8dfb67df1fe0afbe3b3bfb4b28" - integrity sha512-qeEU/r/K+V5lrAw8iswf2/yfWAnSGs3WKPHI+zAFKFjX0dIBVXEU/swQ8eJQYHf6PJWUZFO2uWV4V1wEOkeQbA== + version "0.4.56" + resolved "https://registry.yarnpkg.com/solidity-ast/-/solidity-ast-0.4.56.tgz#94fe296f12e8de1a3bed319bc06db8d05a113d7a" + integrity sha512-HgmsA/Gfklm/M8GFbCX/J1qkVH0spXHgALCNZ8fA8x5X+MFdn/8CP2gr5OVyXjXw6RZTPC/Sxl2RUDQOXyNMeA== dependencies: array.prototype.findlast "^1.2.2" @@ -22089,15 +22158,14 @@ solidity-comments-extractor@^0.0.8: integrity sha512-htM7Vn6LhHreR+EglVMd2s+sZhcXAirB1Zlyrv5zBuTxieCvjfnRpd7iZk75m/u6NOlEyQ94C6TWbBn2cY7w8g== solidity-coverage@^0.8.2: - version "0.8.5" - resolved "https://registry.yarnpkg.com/solidity-coverage/-/solidity-coverage-0.8.5.tgz#64071c3a0c06a0cecf9a7776c35f49edc961e875" - integrity sha512-6C6N6OV2O8FQA0FWA95FdzVH+L16HU94iFgg5wAFZ29UpLFkgNI/DRR2HotG1bC0F4gAc/OMs2BJI44Q/DYlKQ== + version "0.8.11" + resolved "https://registry.yarnpkg.com/solidity-coverage/-/solidity-coverage-0.8.11.tgz#c95798f2c3e885c49dcfc9c43ee570d112214785" + integrity sha512-yy0Yk+olovBbXn0Me8BWULmmv7A69ZKkP5aTOJGOO8u61Tu2zS989erfjtFlUjDnfWtxRAVkd8BsQD704yLWHw== dependencies: "@ethersproject/abi" "^5.0.9" - "@solidity-parser/parser" "^0.16.0" + "@solidity-parser/parser" "^0.18.0" chalk "^2.4.2" death "^1.1.0" - detect-port "^1.3.0" difflib "^0.2.4" fs-extra "^8.1.0" ghost-testrpc "^0.0.2" @@ -22105,7 +22173,7 @@ solidity-coverage@^0.8.2: globby "^10.0.1" jsonschema "^1.2.4" lodash "^4.17.15" - mocha "10.2.0" + mocha "^10.2.0" node-emoji "^1.10.0" pify "^4.0.1" recursive-readdir "^2.2.2" @@ -22149,10 +22217,10 @@ source-list-map@^2.0.0: resolved "https://registry.yarnpkg.com/source-list-map/-/source-list-map-2.0.1.tgz#3993bd873bfc48479cca9ea3a547835c7c154b34" integrity sha512-qnQ7gVMxGNxsiL4lEuJwe/To8UnK7fAnmbGEEH8RpLouuKbeEm0lhbQVFIrNSuB+G7tVrAlVsZgETT5nljf+Iw== -source-map-js@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.0.2.tgz#adbc361d9c62df380125e7f161f71c826f1e490c" - integrity sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw== +source-map-js@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.2.0.tgz#16b809c162517b5b8c3e7dcd315a2a5c2612b2af" + integrity sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg== source-map-resolve@^0.5.0: version "0.5.3" @@ -22227,9 +22295,9 @@ spdx-correct@^3.0.0: spdx-license-ids "^3.0.0" spdx-exceptions@^2.1.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz#3f28ce1a77a00372683eade4a433183527a2163d" - integrity sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A== + version "2.5.0" + resolved "https://registry.yarnpkg.com/spdx-exceptions/-/spdx-exceptions-2.5.0.tgz#5d607d27fc806f66d7b64a766650fa890f04ed66" + integrity sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w== spdx-expression-parse@^3.0.0: version "3.0.1" @@ -22240,9 +22308,9 @@ spdx-expression-parse@^3.0.0: spdx-license-ids "^3.0.0" spdx-license-ids@^3.0.0: - version "3.0.16" - resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-3.0.16.tgz#a14f64e0954f6e25cc6587bd4f392522db0d998f" - integrity sha512-eWN+LnM3GR6gPu35WxNgbGl8rmY1AEmoMDvL/QD6zYmPWgywxWqJWNdLGT+ke8dKNWrcYgYjPpG5gbTfghP8rw== + version "3.0.17" + resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-3.0.17.tgz#887da8aa73218e51a1d917502d79863161a93f9c" + integrity sha512-sh8PWc/ftMqAAdFiBu6Fy6JUOYjqDJBJvIhpfDMyHrr0Rbp5liZqd4TjtQ/RgfLjKFZb+LMx5hpml5qOWy0qvg== speedometer@~1.0.0: version "1.0.0" @@ -22305,11 +22373,6 @@ stacktrace-parser@^0.1.10: dependencies: type-fest "^0.7.1" -standard-as-callback@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/standard-as-callback/-/standard-as-callback-2.1.0.tgz#8953fc05359868a77b5b9739a665c5977bb7df45" - integrity sha512-qoRRSyROncaz1z0mvYqIE4lCd9p2R90i6GxW3uZv5ucSu8tU7B5HXUP1gG8pVZsYNVaXjk8ClXHPttLyxAL48A== - static-extend@^0.1.1: version "0.1.2" resolved "https://registry.yarnpkg.com/static-extend/-/static-extend-0.1.2.tgz#60809c39cbff55337226fd5e0b520f341f1fb5c6" @@ -22328,7 +22391,7 @@ statuses@2.0.1, statuses@^2.0.1: resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c" integrity sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA== -std-env@^3.3.2, std-env@^3.4.3: +std-env@^3.3.2, std-env@^3.7.0: version "3.7.0" resolved "https://registry.yarnpkg.com/std-env/-/std-env-3.7.0.tgz#c9f7386ced6ecf13360b6c6c55b8aaa4ef7481d2" integrity sha512-JPbdCEQLj1w5GilpiHAx3qJvFndqybBysA3qUOnznweH4QbNYUsW/ea8QzSrnh0vNsezMMw5bcVool8lM0gwzg== @@ -22370,10 +22433,10 @@ stream-json@1.8.0: dependencies: stream-chain "^2.2.5" -stream-shift@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/stream-shift/-/stream-shift-1.0.1.tgz#d7088281559ab2778424279b0877da3c392d5a3d" - integrity sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ== +stream-shift@^1.0.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/stream-shift/-/stream-shift-1.0.3.tgz#85b8fab4d71010fc3ba8772e8046cc49b8a3864b" + integrity sha512-76ORR0DO1o1hlKwTbi/DM3EXWGf3ZJYO8cXX5RJwnul2DEg2oyoZyjLNoQM8WsvZiFKCRfC1O0J7iCvie3RZmQ== stream-slice@^0.1.2: version "0.1.2" @@ -22397,13 +22460,15 @@ streamsearch@^1.1.0: resolved "https://registry.yarnpkg.com/streamsearch/-/streamsearch-1.1.0.tgz#404dd1e2247ca94af554e841a8ef0eaa238da764" integrity sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg== -streamx@^2.15.0: - version "2.15.6" - resolved "https://registry.yarnpkg.com/streamx/-/streamx-2.15.6.tgz#28bf36997ebc7bf6c08f9eba958735231b833887" - integrity sha512-q+vQL4AAz+FdfT137VF69Cc/APqUbxy+MDOImRrMvchJpigHj9GksgDU2LYbO9rx7RX6osWgxJB2WxhYv4SZAw== +streamx@^2.13.0, streamx@^2.15.0: + version "2.16.1" + resolved "https://registry.yarnpkg.com/streamx/-/streamx-2.16.1.tgz#2b311bd34832f08aa6bb4d6a80297c9caef89614" + integrity sha512-m9QYj6WygWyWa3H1YY69amr4nVgy61xfjys7xO7kviL5rfIEc2naf+ewFiOA+aEJD7y0JO3h2GoiUv4TDwEGzQ== dependencies: fast-fifo "^1.1.0" queue-tick "^1.0.1" + optionalDependencies: + bare-events "^2.2.0" strict-uri-encode@^2.0.0: version "2.0.0" @@ -22465,46 +22530,50 @@ string-width@^5.0.1, string-width@^5.1.2: strip-ansi "^7.0.1" string-width@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-7.0.0.tgz#14aa1b7aaa126d5b64fa79d3c894da8a9650ba06" - integrity sha512-GPQHj7row82Hjo9hKZieKcHIhaAIKOJvFSIZXuCU9OASVZrMNUaZuz++SPVrBjnLsnk4k+z9f2EIypgxf2vNFw== + version "7.1.0" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-7.1.0.tgz#d994252935224729ea3719c49f7206dc9c46550a" + integrity sha512-SEIJCWiX7Kg4c129n48aDRwLbFb2LJmXXFrWBG4NGaRtMQ3myKPKbwrD1BKqQn74oCoNMBVrfDEr5M9YxCsrkw== dependencies: emoji-regex "^10.3.0" get-east-asian-width "^1.0.0" strip-ansi "^7.1.0" -string.prototype.matchall@^4.0.8: - version "4.0.10" - resolved "https://registry.yarnpkg.com/string.prototype.matchall/-/string.prototype.matchall-4.0.10.tgz#a1553eb532221d4180c51581d6072cd65d1ee100" - integrity sha512-rGXbGmOEosIQi6Qva94HUjgPs9vKW+dkG7Y8Q5O2OYkWL6wFaTRZO8zM4mhP94uX55wgyrXzfS2aGtGzUL7EJQ== +string.prototype.matchall@^4.0.10: + version "4.0.11" + resolved "https://registry.yarnpkg.com/string.prototype.matchall/-/string.prototype.matchall-4.0.11.tgz#1092a72c59268d2abaad76582dccc687c0297e0a" + integrity sha512-NUdh0aDavY2og7IbBPenWqR9exH+E26Sv8e0/eTe1tltDGZL+GtBkDAnnyBtmekfK6/Dq3MkcGtzXFEd1LQrtg== dependencies: - call-bind "^1.0.2" - define-properties "^1.2.0" - es-abstract "^1.22.1" - get-intrinsic "^1.2.1" + call-bind "^1.0.7" + define-properties "^1.2.1" + es-abstract "^1.23.2" + es-errors "^1.3.0" + es-object-atoms "^1.0.0" + get-intrinsic "^1.2.4" + gopd "^1.0.1" has-symbols "^1.0.3" - internal-slot "^1.0.5" - regexp.prototype.flags "^1.5.0" - set-function-name "^2.0.0" - side-channel "^1.0.4" + internal-slot "^1.0.7" + regexp.prototype.flags "^1.5.2" + set-function-name "^2.0.2" + side-channel "^1.0.6" -string.prototype.trim@^1.2.8: - version "1.2.8" - resolved "https://registry.yarnpkg.com/string.prototype.trim/-/string.prototype.trim-1.2.8.tgz#f9ac6f8af4bd55ddfa8895e6aea92a96395393bd" - integrity sha512-lfjY4HcixfQXOfaqCvcBuOIapyaroTXhbkfJN3gcB1OtyupngWK4sEET9Knd0cXd28kTUqu/kHoV4HKSJdnjiQ== +string.prototype.trim@^1.2.8, string.prototype.trim@^1.2.9: + version "1.2.9" + resolved "https://registry.yarnpkg.com/string.prototype.trim/-/string.prototype.trim-1.2.9.tgz#b6fa326d72d2c78b6df02f7759c73f8f6274faa4" + integrity sha512-klHuCNxiMZ8MlsOihJhJEBJAiMVqU3Z2nEXWfWnIqjN0gEFS9J9+IxKozWWtQGcgoa1WUZzLjKPTr4ZHNFTFxw== dependencies: - call-bind "^1.0.2" - define-properties "^1.2.0" - es-abstract "^1.22.1" + call-bind "^1.0.7" + define-properties "^1.2.1" + es-abstract "^1.23.0" + es-object-atoms "^1.0.0" -string.prototype.trimend@^1.0.7: - version "1.0.7" - resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.7.tgz#1bb3afc5008661d73e2dc015cd4853732d6c471e" - integrity sha512-Ni79DqeB72ZFq1uH/L6zJ+DKZTkOtPIHovb3YZHQViE+HDouuU4mBrLOLDn5Dde3RF8qw5qVETEjhu9locMLvA== +string.prototype.trimend@^1.0.7, string.prototype.trimend@^1.0.8: + version "1.0.8" + resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.8.tgz#3651b8513719e8a9f48de7f2f77640b26652b229" + integrity sha512-p73uL5VCHCO2BZZ6krwwQE3kCzM7NKmis8S//xEC6fQonchbum4eP6kR4DLEjQFO3Wnj3Fuo8NM0kOSjVdHjZQ== dependencies: - call-bind "^1.0.2" - define-properties "^1.2.0" - es-abstract "^1.22.1" + call-bind "^1.0.7" + define-properties "^1.2.1" + es-object-atoms "^1.0.0" string.prototype.trimstart@^1.0.7: version "1.0.7" @@ -22621,9 +22690,9 @@ strip-literal@^1.0.1: acorn "^8.10.0" stripe@^14.20.0: - version "14.20.0" - resolved "https://registry.yarnpkg.com/stripe/-/stripe-14.20.0.tgz#3cc5bce37750a6f900b9fccd1f7921dfef4dccaa" - integrity sha512-+3EP8GSWnKVHNATChhDzwAKk3nqSJKQOf2Q+dMGdgEk2sQXWYoA8GXY0A1TjL0m6895FVNavgvno6+0+6lC+kw== + version "14.21.0" + resolved "https://registry.yarnpkg.com/stripe/-/stripe-14.21.0.tgz#6b984b06c16765ca08fb86b9185fe4ea0a8409fd" + integrity sha512-PFmpl35Myn6UDdVLTHcuppdbkPVvlQfkMHOmgGZh5QOdSUxVmvz090Z4obLg8ta1MNs1PNpzr9i7E39iAIv07A== dependencies: "@types/node" ">=8.1.0" qs "^6.11.0" @@ -22639,9 +22708,9 @@ style-loader@3.3.1: integrity sha512-GPcQ+LDJbrcxHORTRes6Jy2sfvK2kS6hpSfI/fXhPt+spVzxF6LJ1dHLN9zIGmVaaP044YKaIatFaufENRiDoQ== style-mod@^4.0.0, style-mod@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/style-mod/-/style-mod-4.1.0.tgz#a313a14f4ae8bb4d52878c0053c4327fb787ec09" - integrity sha512-Ca5ib8HrFn+f+0n4N4ScTIA9iTOQ7MaGS1ylHcoVqW9J7w2w8PzN6g9gKmTYgGEBH8e120+RCmhpje6jC5uGWA== + version "4.1.2" + resolved "https://registry.yarnpkg.com/style-mod/-/style-mod-4.1.2.tgz#ca238a1ad4786520f7515a8539d5a63691d7bf67" + integrity sha512-wnD1HyVqpJUI2+eKZ+eo1UwghftP6yuFheBqqe+bWCotBjC2K1YnteJILRMs3SM4V/0dLEW1SC27MWP5y+mwmw== styled-components@5.3.3: version "5.3.3" @@ -22686,9 +22755,9 @@ superstruct@^0.14.2: integrity sha512-nPewA6m9mR3d6k7WkZ8N8zpTWfenFH3q9pA2PkuiZxINr9DKB2+40wEQf0ixn8VaGuJ78AB6iWOtStI+/4FKZQ== superstruct@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/superstruct/-/superstruct-1.0.3.tgz#de626a5b49c6641ff4d37da3c7598e7a87697046" - integrity sha512-8iTn3oSS8nRGn+C2pgXSKPI3jmpm6FExNazNpjvqS6ZUJQCej3PUXEKM8NjHBOs54ExM+LPW/FBRhymrdcCiSg== + version "1.0.4" + resolved "https://registry.yarnpkg.com/superstruct/-/superstruct-1.0.4.tgz#0adb99a7578bd2f1c526220da6571b2d485d91ca" + integrity sha512-7JpaAoX2NGyoFlI9NBh66BQXGONc+uE+MRS5i2iOBKuS4e+ccgMDjATgZldkah+33DakBxDHiss9kvUcGAO8UQ== supertest@^6.1.3, supertest@^6.3.4: version "6.3.4" @@ -22698,7 +22767,7 @@ supertest@^6.1.3, supertest@^6.3.4: methods "^1.1.2" superagent "^8.1.2" -supports-color@8.1.1, supports-color@^8.0.0, supports-color@^8.1.0, supports-color@^8.1.1: +supports-color@8.1.1, supports-color@^8.0.0, supports-color@^8.1.0, supports-color@^8.1.1, supports-color@~8.1.1: version "8.1.1" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-8.1.1.tgz#cd6fc17e28500cff56c1b86c0a7fd4a54a73005c" integrity sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q== @@ -22744,10 +22813,10 @@ supports-preserve-symlinks-flag@^1.0.0: resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== -swagger-ui-dist@5.10.3: - version "5.10.3" - resolved "https://registry.yarnpkg.com/swagger-ui-dist/-/swagger-ui-dist-5.10.3.tgz#903adbfbecc0670a802b6d8b770e5dd07b5a36cb" - integrity sha512-fu3aozjxFWsmcO1vyt1q1Ji2kN7KlTd1vHy27E9WgPyXo9nrEzhQPqgxaAjbMsOmb8XFKNGo4Sa3Q+84Fh+pFw== +swagger-ui-dist@5.11.2: + version "5.11.2" + resolved "https://registry.yarnpkg.com/swagger-ui-dist/-/swagger-ui-dist-5.11.2.tgz#b423e820928df703586ff58f80b09ffcf2434e08" + integrity sha512-jQG0cRgJNMZ7aCoiFofnoojeSaa/+KgWaDlfgs8QN+BXoGMpxeMVY5OEnjq4OlNvF3yjftO8c9GRAgcHlO+u7A== swap-case@^1.1.0: version "1.1.2" @@ -22758,9 +22827,9 @@ swap-case@^1.1.0: upper-case "^1.1.1" swr@^2.2.4: - version "2.2.4" - resolved "https://registry.yarnpkg.com/swr/-/swr-2.2.4.tgz#03ec4c56019902fbdc904d78544bd7a9a6fa3f07" - integrity sha512-njiZ/4RiIhoOlAaLYDqwz5qH/KZXVilRLvomrx83HjzCWTfa+InyfAjv05PSFxnmLzZkNO9ZfvgoqzAaEI4sGQ== + version "2.2.5" + resolved "https://registry.yarnpkg.com/swr/-/swr-2.2.5.tgz#063eea0e9939f947227d5ca760cc53696f46446b" + integrity sha512-QtxqyclFeAsxEUeZIYmsaQ0UjimSq1RZ9Un7I68/0ClKK/U3LoyQunwkQfJZr2fc22DfIXLNDc2wFyTEikCUpg== dependencies: client-only "^0.0.1" use-sync-external-store "^1.2.0" @@ -22807,6 +22876,11 @@ synckit@^0.8.6: "@pkgr/core" "^0.1.0" tslib "^2.6.2" +system-architecture@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/system-architecture/-/system-architecture-0.1.0.tgz#71012b3ac141427d97c67c56bc7921af6bff122d" + integrity sha512-ulAk51I9UVUyJgxlv9M6lFot2WP3e7t8Kz9+IS6D4rVba1tR9kON+Ey69f+1R4Q8cd45Lod6a4IcJIxnzGc/zA== + table-layout@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/table-layout/-/table-layout-1.0.2.tgz#c4038a1853b0136d63365a734b6931cf4fad4a04" @@ -22844,13 +22918,15 @@ tar-fs@^2.0.0: tar-stream "^2.1.4" tar-fs@^3.0.4: - version "3.0.4" - resolved "https://registry.yarnpkg.com/tar-fs/-/tar-fs-3.0.4.tgz#a21dc60a2d5d9f55e0089ccd78124f1d3771dbbf" - integrity sha512-5AFQU8b9qLfZCX9zp2duONhPmZv0hGYiBPJsyUdqMjzq/mqVpy/rEUSeHk1+YitmxugaptgBh5oDGU3VsAJq4w== + version "3.0.5" + resolved "https://registry.yarnpkg.com/tar-fs/-/tar-fs-3.0.5.tgz#f954d77767e4e6edf973384e1eb95f8f81d64ed9" + integrity sha512-JOgGAmZyMgbqpLwct7ZV8VzkEB6pxXFBVErLtb+XCOqzc6w1xiWKI9GVd6bwk68EX7eJ4DWmfXVmq8K2ziZTGg== dependencies: - mkdirp-classic "^0.5.2" pump "^3.0.0" tar-stream "^3.1.5" + optionalDependencies: + bare-fs "^2.1.1" + bare-path "^2.1.0" tar-fs@~1.16.3: version "1.16.3" @@ -22887,9 +22963,9 @@ tar-stream@^1.1.2: xtend "^4.0.0" tar-stream@^3.1.5: - version "3.1.6" - resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-3.1.6.tgz#6520607b55a06f4a2e2e04db360ba7d338cc5bab" - integrity sha512-B/UyjYwPpMBv+PaFSWAmtYjwdrlEaZQEhMIBFNC5oEG8lpiW8XjcSdmEaClj28ArfKScKHs2nshz3k2le6crsg== + version "3.1.7" + resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-3.1.7.tgz#24b3fb5eabada19fe7338ed6d26e5f7c482e792b" + integrity sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ== dependencies: b4a "^1.6.4" fast-fifo "^1.2.0" @@ -22937,10 +23013,10 @@ tenderly@^0.7.0: prompts "^2.4.2" tslog "^4.4.0" -tenderly@^0.8.0: - version "0.8.0" - resolved "https://registry.yarnpkg.com/tenderly/-/tenderly-0.8.0.tgz#ffda1f40597b91470e728900e3bdfe6a4f152ec5" - integrity sha512-4Faw9jkwMuBOva82lAtvhTa9isc503GkWwVWSsR8ONm+i3SeFatv7hNyYPZIifQBeuU9GOVNkWHCAXon6NE/aw== +tenderly@^0.9.1: + version "0.9.1" + resolved "https://registry.yarnpkg.com/tenderly/-/tenderly-0.9.1.tgz#de988bc5b65a106c2e1f84faf17863ba9bb81173" + integrity sha512-EGhYYbOgIC0EUebrMIwCRIL9NrGrC8q3gTY/3JNSqvQrNX4RLUgMHungTG4bkgGAwJoehC57vsAeKqR1PVIyjw== dependencies: axios "^0.27.2" cli-table3 "^0.6.2" @@ -22950,7 +23026,7 @@ tenderly@^0.8.0: prompts "^2.4.2" tslog "^4.4.0" -terser-webpack-plugin@^5.3.7: +terser-webpack-plugin@^5.3.10, terser-webpack-plugin@^5.3.7: version "5.3.10" resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-5.3.10.tgz#904f4c9193c6fd2a03f693a2150c62a92f40d199" integrity sha512-BKFPWlPDndPs+NGGCr1U59t0XScL5317Y0UReNrHaw9/FwhPENlq6bfgs+4yPfyP51vqC1bQ4rp1EfXW5ZSH9w== @@ -22962,9 +23038,9 @@ terser-webpack-plugin@^5.3.7: terser "^5.26.0" terser@^5.10.0, terser@^5.26.0: - version "5.26.0" - resolved "https://registry.yarnpkg.com/terser/-/terser-5.26.0.tgz#ee9f05d929f4189a9c28a0feb889d96d50126fe1" - integrity sha512-dytTGoE2oHgbNV9nTzgBEPaqAWvcJNl66VZ0BkJqlvp71IjO8CxdBx/ykCNb47cLnCmCvRZ6ZR0tLkqvZCdVBQ== + version "5.29.2" + resolved "https://registry.yarnpkg.com/terser/-/terser-5.29.2.tgz#c17d573ce1da1b30f21a877bffd5655dd86fdb35" + integrity sha512-ZiGkhUBIM+7LwkNjXYJq8svgkd+QK3UUr0wJqY4MieaezBSAIPgbSPZyIx0idM6XWK5CMzSWa8MJIzmRcB8Caw== dependencies: "@jridgewell/source-map" "^0.3.3" acorn "^8.8.2" @@ -23110,9 +23186,9 @@ tiny-invariant@1.0.6: integrity sha512-FOyLWWVjG+aC0UqG76V53yAWdXfH8bO6FNmyZOuUrzDzK8DI3/JRY25UD7+g49JWM1LXwymsKERB+DzI0dTEQA== tiny-invariant@^1.0.2, tiny-invariant@^1.3.1: - version "1.3.1" - resolved "https://registry.yarnpkg.com/tiny-invariant/-/tiny-invariant-1.3.1.tgz#8560808c916ef02ecfd55e66090df23a4b7aa642" - integrity sha512-AD5ih2NlSssTCwsMznbvwMZpJ1cbhkGd2uueNxzv2jDlEeZdU04JQfRnggJQ8DrcVBGjAsCKwFBbDlVNtEMlzw== + version "1.3.3" + resolved "https://registry.yarnpkg.com/tiny-invariant/-/tiny-invariant-1.3.3.tgz#46680b7a873a0d5d10005995eb90a70d74d60127" + integrity sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg== tiny-warning@^1.0.0, tiny-warning@^1.0.2, tiny-warning@^1.0.3: version "1.0.3" @@ -23120,9 +23196,9 @@ tiny-warning@^1.0.0, tiny-warning@^1.0.2, tiny-warning@^1.0.3: integrity sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA== tinybench@^2.4.0: - version "2.5.1" - resolved "https://registry.yarnpkg.com/tinybench/-/tinybench-2.5.1.tgz#3408f6552125e53a5a48adee31261686fd71587e" - integrity sha512-65NKvSuAVDP/n4CqH+a9w2kTlLReS9vhsAP06MWx+/89nMinJyB2icyl58RIcqCmIggpojIGeuJGhjU1aGMBSg== + version "2.6.0" + resolved "https://registry.yarnpkg.com/tinybench/-/tinybench-2.6.0.tgz#1423284ee22de07c91b3752c048d2764714b341b" + integrity sha512-N8hW3PG/3aOoZAN5V/NSAEDz0ZixDSSt5b/a05iqtpgfLWMSVuCo7w0k2vVvEjdrIoeGqZzweX2WlyioNIHchA== tinypool@^0.4.0: version "0.4.0" @@ -23130,9 +23206,9 @@ tinypool@^0.4.0: integrity sha512-2ksntHOKf893wSAH4z/+JbPpi92esw8Gn9N2deXX+B0EO92hexAVI9GIZZPx7P5aYo5KULfeOSt3kMOmSOy6uA== tinyspy@^2.1.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/tinyspy/-/tinyspy-2.2.0.tgz#9dc04b072746520b432f77ea2c2d17933de5d6ce" - integrity sha512-d2eda04AN/cPOR89F7Xv5bK/jrQEhmcLFe6HFldoeO9AJtps+fqEnh486vnT/8y4bw38pSyxDcTCAq+Ks2aJTg== + version "2.2.1" + resolved "https://registry.yarnpkg.com/tinyspy/-/tinyspy-2.2.1.tgz#117b2342f1f38a0dbdcc73a50a454883adf861d1" + integrity sha512-KYad6Vy5VDWV4GH3fjpseMQ/XU2BhIYP7Vzd0LG44qRWm/Yt2WCOTicFdvmgo6gWaqooMQCawTtILVQJupKu7A== title-case@^2.1.0: version "2.1.1" @@ -23162,11 +23238,9 @@ tmp@0.0.33, tmp@^0.0.33: os-tmpdir "~1.0.2" tmp@^0.2.0: - version "0.2.1" - resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.2.1.tgz#8457fc3037dcf4719c251367a1af6500ee1ccf14" - integrity sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ== - dependencies: - rimraf "^3.0.0" + version "0.2.3" + resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.2.3.tgz#eb783cc22bc1e8bebd0671476d46ea4eb32a79ae" + integrity sha512-nZD7m9iCPC5g0pYmcaxogYKggSfLsdxl8of3Q/oIbqCqLLIO9IAF0GWjX1z9NZRHPiXv8Wex4yDCaZsgEw0Y8w== tmpl@1.0.5: version "1.0.5" @@ -23285,9 +23359,9 @@ triple-beam@^1.3.0: integrity sha512-aZbgViZrg1QNcG+LULa7nhZpJTZSLm/mXnHXnbAbjmN5aSa0y7V+wvv6+4WaBtpISJzThKy+PIPxc1Nq1EJ9mg== ts-api-utils@^1.0.1: - version "1.0.3" - resolved "https://registry.yarnpkg.com/ts-api-utils/-/ts-api-utils-1.0.3.tgz#f12c1c781d04427313dbac808f453f050e54a331" - integrity sha512-wNMeqtMz5NtwpT/UZGY5alT+VoKdSsOOP/kqHFcUW1P/VRhH2wJ48+DN2WwUliNbQ976ETwDL0Ifd2VVvgonvg== + version "1.3.0" + resolved "https://registry.yarnpkg.com/ts-api-utils/-/ts-api-utils-1.3.0.tgz#4b490e27129f1e8e686b45cc4ab63714dc60eea1" + integrity sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ== ts-command-line-args@^2.2.0: version "2.5.1" @@ -23311,7 +23385,7 @@ ts-invariant@^0.10.3: dependencies: tslib "^2.1.0" -ts-jest@29.1.1, ts-jest@^29.1.1: +ts-jest@29.1.1: version "29.1.1" resolved "https://registry.yarnpkg.com/ts-jest/-/ts-jest-29.1.1.tgz#f58fe62c63caf7bfcc5cc6472082f79180f0815b" integrity sha512-D6xjnnbP17cC85nliwGiL+tpoKN0StpgE0TeOjXQTU6MVCfsB4v7aW05CgQ/1OywGb0x/oy9hHFnN+sczTiRaA== @@ -23325,6 +23399,20 @@ ts-jest@29.1.1, ts-jest@^29.1.1: semver "^7.5.3" yargs-parser "^21.0.1" +ts-jest@^29.1.1: + version "29.1.2" + resolved "https://registry.yarnpkg.com/ts-jest/-/ts-jest-29.1.2.tgz#7613d8c81c43c8cb312c6904027257e814c40e09" + integrity sha512-br6GJoH/WUX4pu7FbZXuWGKGNDuU7b8Uj77g/Sp7puZV6EXzuByl6JrECvm0MzVzSTkSHWTihsXt+5XYER5b+g== + dependencies: + bs-logger "0.x" + fast-json-stable-stringify "2.x" + jest-util "^29.0.0" + json5 "^2.2.3" + lodash.memoize "4.x" + make-error "1.x" + semver "^7.5.3" + yargs-parser "^21.0.1" + ts-loader@^9.2.3: version "9.5.1" resolved "https://registry.yarnpkg.com/ts-loader/-/ts-loader-9.5.1.tgz#63d5912a86312f1fbe32cef0859fb8b2193d9b89" @@ -23562,44 +23650,49 @@ typechain@^8.3.2: ts-command-line-args "^2.2.0" ts-essentials "^7.0.1" -typed-array-buffer@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/typed-array-buffer/-/typed-array-buffer-1.0.0.tgz#18de3e7ed7974b0a729d3feecb94338d1472cd60" - integrity sha512-Y8KTSIglk9OZEr8zywiIHG/kmQ7KWyjseXs1CbSo8vC42w7hg2HgYTxSWwP0+is7bWDc1H+Fo026CpHFwm8tkw== +typed-array-buffer@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/typed-array-buffer/-/typed-array-buffer-1.0.2.tgz#1867c5d83b20fcb5ccf32649e5e2fc7424474ff3" + integrity sha512-gEymJYKZtKXzzBzM4jqa9w6Q1Jjm7x2d+sh19AdsD4wqnMPDYyvwpsIc2Q/835kHuo3BEQ7CjelGhfTsoBb2MQ== dependencies: - call-bind "^1.0.2" - get-intrinsic "^1.2.1" - is-typed-array "^1.1.10" + call-bind "^1.0.7" + es-errors "^1.3.0" + is-typed-array "^1.1.13" -typed-array-byte-length@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/typed-array-byte-length/-/typed-array-byte-length-1.0.0.tgz#d787a24a995711611fb2b87a4052799517b230d0" - integrity sha512-Or/+kvLxNpeQ9DtSydonMxCx+9ZXOswtwJn17SNLvhptaXYDJvkFFP5zbfU/uLmvnBJlI4yrnXRxpdWH/M5tNA== +typed-array-byte-length@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/typed-array-byte-length/-/typed-array-byte-length-1.0.1.tgz#d92972d3cff99a3fa2e765a28fcdc0f1d89dec67" + integrity sha512-3iMJ9q0ao7WE9tWcaYKIptkNBuOIcZCCT0d4MRvuuH88fEoEH62IuQe0OtraD3ebQEoTRk8XCBoknUNc1Y67pw== dependencies: - call-bind "^1.0.2" + call-bind "^1.0.7" for-each "^0.3.3" - has-proto "^1.0.1" - is-typed-array "^1.1.10" + gopd "^1.0.1" + has-proto "^1.0.3" + is-typed-array "^1.1.13" -typed-array-byte-offset@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/typed-array-byte-offset/-/typed-array-byte-offset-1.0.0.tgz#cbbe89b51fdef9cd6aaf07ad4707340abbc4ea0b" - integrity sha512-RD97prjEt9EL8YgAgpOkf3O4IF9lhJFr9g0htQkm0rchFp/Vx7LW5Q8fSXXub7BXAODyUQohRMyOc3faCPd0hg== +typed-array-byte-offset@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/typed-array-byte-offset/-/typed-array-byte-offset-1.0.2.tgz#f9ec1acb9259f395093e4567eb3c28a580d02063" + integrity sha512-Ous0vodHa56FviZucS2E63zkgtgrACj7omjwd/8lTEMEPFFyjfixMZ1ZXenpgCFBBt4EC1J2XsyVS2gkG0eTFA== dependencies: - available-typed-arrays "^1.0.5" - call-bind "^1.0.2" + available-typed-arrays "^1.0.7" + call-bind "^1.0.7" for-each "^0.3.3" - has-proto "^1.0.1" - is-typed-array "^1.1.10" + gopd "^1.0.1" + has-proto "^1.0.3" + is-typed-array "^1.1.13" -typed-array-length@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/typed-array-length/-/typed-array-length-1.0.4.tgz#89d83785e5c4098bec72e08b319651f0eac9c1bb" - integrity sha512-KjZypGq+I/H7HI5HlOoGHkWUUGq+Q0TPhQurLbyrVrvnKTBgzLhIJ7j6J/XTQOi0d1RjyZ0wdas8bKs2p0x3Ng== +typed-array-length@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/typed-array-length/-/typed-array-length-1.0.5.tgz#57d44da160296d8663fd63180a1802ebf25905d5" + integrity sha512-yMi0PlwuznKHxKmcpoOdeLwxBoVPkqZxd7q2FgMkmD3bNwvF5VW0+UlUQ1k1vmktTu4Yu13Q0RIxEP8+B+wloA== dependencies: - call-bind "^1.0.2" + call-bind "^1.0.7" for-each "^0.3.3" - is-typed-array "^1.1.9" + gopd "^1.0.1" + has-proto "^1.0.3" + is-typed-array "^1.1.13" + possible-typed-array-names "^1.0.0" typedarray-to-buffer@3.1.5, typedarray-to-buffer@^3.1.5: version "3.1.5" @@ -23621,9 +23714,9 @@ typedoc-plugin-markdown@^3.16.0: handlebars "^4.7.7" typedoc@^0.25.1: - version "0.25.7" - resolved "https://registry.yarnpkg.com/typedoc/-/typedoc-0.25.7.tgz#11e3f527ca80ca3c029cb8e15f362e6d9f715e25" - integrity sha512-m6A6JjQRg39p2ZVRIN3NKXgrN8vzlHhOS+r9ymUYtcUP/TIQPvWSq7YgE5ZjASfv5Vd5BW5xrir6Gm2XNNcOow== + version "0.25.12" + resolved "https://registry.yarnpkg.com/typedoc/-/typedoc-0.25.12.tgz#f73f0a8d3731d418cc604d4230f95a857799e27a" + integrity sha512-F+qhkK2VoTweDXd1c42GS/By2DvI2uDF4/EpG424dTexSHdtCH52C6IcAvMA6jR3DzAWZjHpUOW+E02kyPNUNw== dependencies: lunr "^2.3.9" marked "^4.3.0" @@ -23636,9 +23729,9 @@ typeorm-naming-strategies@^4.1.0: integrity sha512-vPekJXzZOTZrdDvTl1YoM+w+sUIfQHG4kZTpbFYoTsufyv9NIBRe4Q+PdzhEAFA2std3D9LZHEb1EjE9zhRpiQ== typeorm@^0.3.16, typeorm@^0.3.17: - version "0.3.19" - resolved "https://registry.yarnpkg.com/typeorm/-/typeorm-0.3.19.tgz#a985ce8ae36d266018e44fed5e27a4a5da34ad2a" - integrity sha512-OGelrY5qEoAU80mR1iyvmUHiKCPUydL6xp6bebXzS7jyv/X70Gp/jBWRAfF4qGOfy2A7orMiGRfwsBUNbEL65g== + version "0.3.20" + resolved "https://registry.yarnpkg.com/typeorm/-/typeorm-0.3.20.tgz#4b61d737c6fed4e9f63006f88d58a5e54816b7ab" + integrity sha512-sJ0T08dV5eoZroaq9uPKBoNcGslHBR4E4y+EBHs//SiGbblGe7IeduP/IH4ddCcj0qp3PHwDwGnuvqEAnKlq/Q== dependencies: "@sqltools/formatter" "^1.2.5" app-root-path "^3.1.0" @@ -23650,7 +23743,7 @@ typeorm@^0.3.16, typeorm@^0.3.17: dotenv "^16.0.3" glob "^10.3.10" mkdirp "^2.1.3" - reflect-metadata "^0.1.13" + reflect-metadata "^0.2.1" sha.js "^2.4.11" tslib "^2.5.0" uuid "^9.0.0" @@ -23667,9 +23760,9 @@ typescript@5.2.2: integrity sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w== typescript@^5.0.0, typescript@^5.2.2: - version "5.3.3" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.3.3.tgz#b3ce6ba258e72e6305ba66f5c9b452aaee3ffe37" - integrity sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw== + version "5.4.2" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.4.2.tgz#0ae9cebcfae970718474fe0da2c090cad6577372" + integrity sha512-+2/g0Fds1ERlP6JsakQQDXjZdZMM+rqpamFZJEKh4kwTIn3iDkgKtby0CeNd5ATNZ4Ry1ax15TMx0W2V+miizQ== typical@^4.0.0: version "4.0.0" @@ -23686,10 +23779,10 @@ uc.micro@^1.0.1, uc.micro@^1.0.5: resolved "https://registry.yarnpkg.com/uc.micro/-/uc.micro-1.0.6.tgz#9c411a802a409a91fc6cf74081baba34b24499ac" integrity sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA== -ufo@^1.3.0, ufo@^1.3.1, ufo@^1.3.2: - version "1.3.2" - resolved "https://registry.yarnpkg.com/ufo/-/ufo-1.3.2.tgz#c7d719d0628a1c80c006d2240e0d169f6e3c0496" - integrity sha512-o+ORpgGwaYQXgqGDwd+hkS4PuZ3QnmqMMxRuajK/a38L6fTpcE5GPIfrf+L/KemFzfUpeUQc1rRS1iDBozvnFA== +ufo@^1.3.0, ufo@^1.3.2, ufo@^1.4.0: + version "1.5.2" + resolved "https://registry.yarnpkg.com/ufo/-/ufo-1.5.2.tgz#e547561ac56896fc8b9a3f2fb2552169f3629035" + integrity sha512-eiutMaL0J2MKdhcOM1tUy13pIrYnyR87fEd8STJQFrrAwImwvlXkxlZEjaKah8r2viPohld08lt73QfLG1NxMg== uglify-js@^3.1.4: version "3.17.4" @@ -23761,14 +23854,19 @@ undici@5.26.5: dependencies: "@fastify/busboy" "^2.0.0" -undici@^5.14.0, undici@^5.28.2: +undici@^5.14.0: version "5.28.2" resolved "https://registry.yarnpkg.com/undici/-/undici-5.28.2.tgz#fea200eac65fc7ecaff80a023d1a0543423b4c91" integrity sha512-wh1pHJHnUeQV5Xa8/kyQhO7WFa8M34l026L5P/+2TYiakvGy5Rdc8jWZVyG7ieht/0WgJLEd3kcU5gKx+6GC8w== dependencies: "@fastify/busboy" "^2.0.0" -unenv@^1.8.0: +undici@^6.0.0: + version "6.9.0" + resolved "https://registry.yarnpkg.com/undici/-/undici-6.9.0.tgz#281b3c8bf29cafa957e743ab2a710b586b01e66e" + integrity sha512-XPWfXzJedevUziHwun70EKNvGnxv4CnfraFZ4f/JV01+fcvMYzHE26r/j8AY/9c/70nkN4B1zX7E2Oyuqwz4+Q== + +unenv@^1.9.0: version "1.9.0" resolved "https://registry.yarnpkg.com/unenv/-/unenv-1.9.0.tgz#469502ae85be1bd3a6aa60f810972b1a904ca312" integrity sha512-QKnFNznRxmbOF1hDgzpqrlIf6NC5sbZ2OJ+5Wl3OX8uM+LUJXbj4TXvLJCtwbPTmbMHCLIz6JLKNinNsMShK9g== @@ -23868,28 +23966,27 @@ unset-value@^1.0.0: isobject "^3.0.0" unstorage@^1.9.0: - version "1.10.1" - resolved "https://registry.yarnpkg.com/unstorage/-/unstorage-1.10.1.tgz#bf8cc00a406e40a6293e893da9807057d95875b0" - integrity sha512-rWQvLRfZNBpF+x8D3/gda5nUCQL2PgXy2jNG4U7/Rc9BGEv9+CAJd0YyGCROUBKs9v49Hg8huw3aih5Bf5TAVw== + version "1.10.2" + resolved "https://registry.yarnpkg.com/unstorage/-/unstorage-1.10.2.tgz#fb7590ada8b30e83be9318f85100158b02a76dae" + integrity sha512-cULBcwDqrS8UhlIysUJs2Dk0Mmt8h7B0E6mtR+relW9nZvsf/u4SkAYyNliPiPW7XtFNb5u3IUMkxGxFTTRTgQ== dependencies: anymatch "^3.1.3" - chokidar "^3.5.3" - destr "^2.0.2" - h3 "^1.8.2" - ioredis "^5.3.2" - listhen "^1.5.5" - lru-cache "^10.0.2" + chokidar "^3.6.0" + destr "^2.0.3" + h3 "^1.11.1" + listhen "^1.7.2" + lru-cache "^10.2.0" mri "^1.2.0" - node-fetch-native "^1.4.1" + node-fetch-native "^1.6.2" ofetch "^1.3.3" - ufo "^1.3.1" + ufo "^1.4.0" untildify@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/untildify/-/untildify-4.0.0.tgz#2bc947b953652487e4600949fb091e3ae8cd919b" integrity sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw== -untun@^0.1.2: +untun@^0.1.3: version "0.1.3" resolved "https://registry.yarnpkg.com/untun/-/untun-0.1.3.tgz#5d10dee37a3a5737ff03d158be877dae0a0e58a6" integrity sha512-4luGP9LMYszMRZwsvyUd9MrxgEGZdZuZgpVQHEEX0lCYFESasVRvZd0EYpCkOIbJKHMuv0LskpXc/8Un+MJzEQ== @@ -24060,11 +24157,6 @@ uuid@8.0.0: resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.0.0.tgz#bc6ccf91b5ff0ac07bbcdbf1c7c4e150db4dbb6c" integrity sha512-jOXGuXZAWdsTH7eZLtyXMqUb9EcWMGZNbL9YcGBJl4MH4nrxHmZJhEHvyLFrkxo+28uLb/NYRcStH48fnD0Vzw== -uuid@9.0.0: - version "9.0.0" - resolved "https://registry.yarnpkg.com/uuid/-/uuid-9.0.0.tgz#592f550650024a38ceb0c562f2f6aa435761efb5" - integrity sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg== - uuid@9.0.1, uuid@^9.0.0: version "9.0.1" resolved "https://registry.yarnpkg.com/uuid/-/uuid-9.0.1.tgz#e188d4c8853cc722220392c424cd637f32293f30" @@ -24104,7 +24196,7 @@ validate-npm-package-license@^3.0.1: spdx-correct "^3.0.0" spdx-expression-parse "^3.0.0" -validator@^13.9.0: +validator@^13.7.0, validator@^13.9.0: version "13.11.0" resolved "https://registry.yarnpkg.com/validator/-/validator-13.11.0.tgz#23ab3fd59290c61248364eabf4067f04955fbb1b" integrity sha512-Ii+sehpSfZy+At5nPdnyMhx78fEoPDkR2XW/zimHEL3MyGJQOCQ7WeP20jPYRz7ZCpcKLB21NxuXHF3bxjStBQ== @@ -24138,9 +24230,9 @@ vary@^1, vary@^1.1.2, vary@~1.1.2: integrity sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg== victory-vendor@^36.6.8: - version "36.7.0" - resolved "https://registry.yarnpkg.com/victory-vendor/-/victory-vendor-36.7.0.tgz#e02af33e249e74e659fa65c6d5936042c42e7aa8" - integrity sha512-nqYuTkLSdTTeACyXcCLbL7rl0y6jpzLPtTNGOtSnajdR+xxMxBdjMxDjfNJNlhR+ZU8vbXz+QejntcbY7h9/ZA== + version "36.9.2" + resolved "https://registry.yarnpkg.com/victory-vendor/-/victory-vendor-36.9.2.tgz#668b02a448fa4ea0f788dbf4228b7e64669ff801" + integrity sha512-PnpQQMuxlwYdocC8fIJqVXvkeViHYzotI+NJrCuav0ZYFoq912ZHBk3mCeuj+5/VpodOjPe1z0Fk2ihgzlXqjQ== dependencies: "@types/d3-array" "^3.0.3" "@types/d3-ease" "^3.0.0" @@ -24250,13 +24342,13 @@ vite@^2.2.0: fsevents "~2.3.2" vite@^5.0.12: - version "5.0.12" - resolved "https://registry.yarnpkg.com/vite/-/vite-5.0.12.tgz#8a2ffd4da36c132aec4adafe05d7adde38333c47" - integrity sha512-4hsnEkG3q0N4Tzf1+t6NdN9dg/L3BM+q8SWgbSPnJvrgH2kgdyzfVJwbR1ic69/4uMJJ/3dqDZZE5/WwqW8U1w== + version "5.2.0" + resolved "https://registry.yarnpkg.com/vite/-/vite-5.2.0.tgz#a3f899b2fbfb7849f9ffdc3fb2ccd00b1acdef05" + integrity sha512-xMSLJNEjNk/3DJRgWlPADDwaU9AgYRodDH2t6oENhJnIlmU9Hx1Q6VpjyXua/JdMw1WJRbnAgHJ9xgET9gnIAg== dependencies: - esbuild "^0.19.3" - postcss "^8.4.32" - rollup "^4.2.0" + esbuild "^0.20.1" + postcss "^8.4.36" + rollup "^4.13.0" optionalDependencies: fsevents "~2.3.3" @@ -24344,9 +24436,9 @@ walker@^1.0.8: makeerror "1.0.12" watchpack@^2.4.0: - version "2.4.0" - resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-2.4.0.tgz#fa33032374962c78113f93c7f2fb4c54c9862a5d" - integrity sha512-Lcvm7MGST/4fup+ifyKi2hjyIAwcdI4HRgtvTpIUxBRhB+RFtUh8XtDOxUfctVCnhVi+QQj49i91OyvzkJl6cg== + version "2.4.1" + resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-2.4.1.tgz#29308f2cac150fa8e4c92f90e0ec954a9fed7fff" + integrity sha512-8wrBCMtVhqcXP2Sup1ctSkga6uc2Bx0IIvKyT7yTFier5AXHooSI+QyQQAtTb7+E0IUCCKyTFmXqdqgum2XWGg== dependencies: glob-to-regexp "^0.4.1" graceful-fs "^4.1.2" @@ -24368,9 +24460,9 @@ web-encoding@1.1.5, web-encoding@^1.1.5: "@zxing/text-encoding" "0.9.0" web-streams-polyfill@^3.1.1, web-streams-polyfill@^3.2.1: - version "3.3.2" - resolved "https://registry.yarnpkg.com/web-streams-polyfill/-/web-streams-polyfill-3.3.2.tgz#32e26522e05128203a7de59519be3c648004343b" - integrity sha512-3pRGuxRF5gpuZc0W+EpwQRmCD7gRqcDOMt688KmdlDAgAyaB1XlN0zq2njfDNm44XVdIouE7pZ6GzbdyH47uIQ== + version "3.3.3" + resolved "https://registry.yarnpkg.com/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz#2073b91a2fdb1fbfbd401e7de0ac9f8214cecb4b" + integrity sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw== web-vitals@^3.5.2: version "3.5.2" @@ -24408,57 +24500,57 @@ web3-eth-abi@1.7.0: "@ethersproject/abi" "5.0.7" web3-utils "1.7.0" -web3-eth-abi@^4.1.4: - version "4.1.4" - resolved "https://registry.yarnpkg.com/web3-eth-abi/-/web3-eth-abi-4.1.4.tgz#56ae7ebb1385db1a948e69fb35f4057bff6743af" - integrity sha512-YLOBVVxxxLYKXjaiwZjEWYEnkMmmrm0nswZsvzSsINy/UgbWbzfoiZU+zn4YNWIEhORhx1p37iS3u/dP6VyC2w== +web3-eth-abi@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/web3-eth-abi/-/web3-eth-abi-4.2.0.tgz#398d415e7783442d06fb7939e40ce3de7a3f04e9" + integrity sha512-x7dUCmk6th+5N63s5kUusoNtsDJKUUQgl9+jECvGTBOTiyHe/V6aOY0120FUjaAGaapOnR7BImQdhqHv6yT2YQ== dependencies: abitype "0.7.1" - web3-errors "^1.1.3" - web3-types "^1.3.0" - web3-utils "^4.0.7" - web3-validator "^2.0.3" + web3-errors "^1.1.4" + web3-types "^1.3.1" + web3-utils "^4.1.1" + web3-validator "^2.0.4" -web3-eth-accounts@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/web3-eth-accounts/-/web3-eth-accounts-4.1.0.tgz#5b5e6c60d457e7b829ec590021fc87ada8585920" - integrity sha512-UFtAsOANsvihTQ6SSvOKguupmQkResyR9M9JNuOxYpKh7+3W+sTnbLXw2UbOSYIsKlc1mpqqW9bVr1SjqHDpUQ== +web3-eth-accounts@^4.1.0, web3-eth-accounts@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/web3-eth-accounts/-/web3-eth-accounts-4.1.1.tgz#55225e5510b961e1cacb4eccc996544998e907fc" + integrity sha512-9JqhRi1YhO1hQOEmmBHgEGsME/B1FHMxpA/AK3vhpvQ8QeP6KbJW+cForTLfPpUbkmPxnRunG4PNNaETNlZfrA== dependencies: "@ethereumjs/rlp" "^4.0.1" crc-32 "^1.2.2" ethereum-cryptography "^2.0.0" - web3-errors "^1.1.3" - web3-types "^1.3.0" - web3-utils "^4.0.7" - web3-validator "^2.0.3" + web3-errors "^1.1.4" + web3-types "^1.3.1" + web3-utils "^4.1.1" + web3-validator "^2.0.4" -web3-eth-contract@^4.1.2, web3-eth-contract@^4.1.4: - version "4.1.4" - resolved "https://registry.yarnpkg.com/web3-eth-contract/-/web3-eth-contract-4.1.4.tgz#513720fc674e42180ec1b88384dc2ce31715488e" - integrity sha512-tJ4z6QLgtu8EQu2sXnLA7g427oxmngnbAUh+9kJKbP6Yep/oe+z79PqJv7H3MwqwUNW9T+/FeB2PnSQSyxz6ig== +web3-eth-contract@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/web3-eth-contract/-/web3-eth-contract-4.2.0.tgz#73f70b19217cd4854211c05846f0c841763b3b29" + integrity sha512-K7bUypsomTs8/Oa0Lgvq5plsSB5afgKJ79NMuXxvC5jfV+AxNrQyKcr5Vd5yEGNqrdQuIPduGQa8DpuY+rMe1g== dependencies: web3-core "^4.3.2" web3-errors "^1.1.4" - web3-eth "^4.3.1" - web3-eth-abi "^4.1.4" + web3-eth "^4.4.0" + web3-eth-abi "^4.2.0" web3-types "^1.3.1" - web3-utils "^4.1.0" - web3-validator "^2.0.3" + web3-utils "^4.1.1" + web3-validator "^2.0.4" -web3-eth-ens@^4.0.8: - version "4.0.8" - resolved "https://registry.yarnpkg.com/web3-eth-ens/-/web3-eth-ens-4.0.8.tgz#f4e0a018ce6cc69e37007ee92063867feb5994f0" - integrity sha512-nj0JfeD45BbzVJcVYpUJnSo8iwDcY9CQ7CZhhIVVOFjvpMAPw0zEwjTvZEIQyCW61OoDG9xcBzwxe2tZoYhMRw== +web3-eth-ens@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/web3-eth-ens/-/web3-eth-ens-4.1.0.tgz#8e1f94dcf9a5ee051ab961389e9cc2975e81f5b9" + integrity sha512-B+QsXXJb/gJkHb1ZGfErNLeFI9zUf2TsQcvi2+NsSuzFwvjIO5IyrrGtqBmXMLWC8ZikMOHuc8ZfFuGrELl31Q== dependencies: "@adraffy/ens-normalize" "^1.8.8" - web3-core "^4.3.0" - web3-errors "^1.1.3" - web3-eth "^4.3.1" - web3-eth-contract "^4.1.2" + web3-core "^4.3.2" + web3-errors "^1.1.4" + web3-eth "^4.5.0" + web3-eth-contract "^4.2.0" web3-net "^4.0.7" - web3-types "^1.3.0" - web3-utils "^4.0.7" - web3-validator "^2.0.3" + web3-types "^1.5.0" + web3-utils "^4.2.1" + web3-validator "^2.0.4" web3-eth-iban@^4.0.7: version "4.0.7" @@ -24482,22 +24574,22 @@ web3-eth-personal@^4.0.8: web3-utils "^4.0.7" web3-validator "^2.0.3" -web3-eth@^4.3.1: - version "4.3.1" - resolved "https://registry.yarnpkg.com/web3-eth/-/web3-eth-4.3.1.tgz#cb4224356716da71e694091aa3fbf10bcb813fb5" - integrity sha512-zJir3GOXooHQT85JB8SrufE+Voo5TtXdjhf1D8IGXmxM8MrhI8AT+Pgt4siBTupJcu5hF17iGmTP/Nj2XnaibQ== +web3-eth@^4.3.1, web3-eth@^4.4.0, web3-eth@^4.5.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/web3-eth/-/web3-eth-4.5.0.tgz#57f5cc020c9b3c4c20d0dacbd87eaa1a9d6c86c0" + integrity sha512-crisE46o/SHMVm+XHAXEaR8k76NCImq+hi0QQEJ+VaLZbDobI/Gvog1HwTukDUDRgnYSAFGqD0cTRyAwDurwpA== dependencies: setimmediate "^1.0.5" - web3-core "^4.3.0" - web3-errors "^1.1.3" - web3-eth-abi "^4.1.4" - web3-eth-accounts "^4.1.0" + web3-core "^4.3.2" + web3-errors "^1.1.4" + web3-eth-abi "^4.2.0" + web3-eth-accounts "^4.1.1" web3-net "^4.0.7" web3-providers-ws "^4.0.7" - web3-rpc-methods "^1.1.3" - web3-types "^1.3.0" - web3-utils "^4.0.7" - web3-validator "^2.0.3" + web3-rpc-methods "^1.2.0" + web3-types "^1.5.0" + web3-utils "^4.2.1" + web3-validator "^2.0.4" web3-net@^4.0.7: version "4.0.7" @@ -24540,19 +24632,19 @@ web3-providers-ws@^4.0.7: web3-utils "^4.0.7" ws "^8.8.1" -web3-rpc-methods@^1.1.3, web3-rpc-methods@^1.1.4: - version "1.1.4" - resolved "https://registry.yarnpkg.com/web3-rpc-methods/-/web3-rpc-methods-1.1.4.tgz#0b478e38231d3f3260ac504307c6dc4059af6fda" - integrity sha512-LTFNg4LFaeU8K9ecuT8fHDp/LOXyxCneeZjCrRYIW1u82Ly52SrY55FIzMIISGoG/iT5Wh7UiHOB3CQsWLBmbQ== +web3-rpc-methods@^1.1.3, web3-rpc-methods@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/web3-rpc-methods/-/web3-rpc-methods-1.2.0.tgz#761dcb036ab16edb2b03e80c11e3f5df24690345" + integrity sha512-CWJ/g4I4WyYvLkf21wCZAehdhU/VjX/OAPHnqF5/FPDJlogOsOnGXHqi1Z5AP+ocdt395PNubd8jyMMJoYGSBA== dependencies: web3-core "^4.3.2" - web3-types "^1.3.1" - web3-validator "^2.0.3" + web3-types "^1.5.0" + web3-validator "^2.0.4" -web3-types@^1.3.0, web3-types@^1.3.1: - version "1.3.1" - resolved "https://registry.yarnpkg.com/web3-types/-/web3-types-1.3.1.tgz#cf6148ad46b68c5c89714613380b270d31e297be" - integrity sha512-8fXi7h/t95VKRtgU4sxprLPZpsTh3jYDfSghshIDBgUD/OoGe5S+syP24SUzBZYllZ/L+hMr2gdp/0bGJa8pYQ== +web3-types@^1.3.0, web3-types@^1.3.1, web3-types@^1.5.0: + version "1.5.0" + resolved "https://registry.yarnpkg.com/web3-types/-/web3-types-1.5.0.tgz#35b5c0ab149b0d566efeaed8ddaa40db159c748e" + integrity sha512-geWuMIeegQ8AedKAO6wO4G4j1gyQ1F/AyKLMw2vud4bsfZayyzWJgCMDZtjYMm5uo2a7i8j1W3/4QFmzlSy5cw== web3-utils@1.7.0: version "1.7.0" @@ -24568,9 +24660,9 @@ web3-utils@1.7.0: utf8 "3.0.0" web3-utils@^1.3.4, web3-utils@^1.3.6, web3-utils@^1.8.1: - version "1.10.3" - resolved "https://registry.yarnpkg.com/web3-utils/-/web3-utils-1.10.3.tgz#f1db99c82549c7d9f8348f04ffe4e0188b449714" - integrity sha512-OqcUrEE16fDBbGoQtZXWdavsPzbGIDc5v3VrRTZ0XrIpefC/viZ1ZU9bGEemazyS0catk/3rkOOxpzTfY+XsyQ== + version "1.10.4" + resolved "https://registry.yarnpkg.com/web3-utils/-/web3-utils-1.10.4.tgz#0daee7d6841641655d8b3726baf33b08eda1cbec" + integrity sha512-tsu8FiKJLk2PzhDl9fXbGUWTkkVXYhtTA+SmEFkKft+9BgwLxfCRpU96sWv7ICC8zixBNd3JURVoiR3dUXgP8A== dependencies: "@ethereumjs/util" "^8.1.0" bn.js "^5.2.1" @@ -24581,59 +24673,60 @@ web3-utils@^1.3.4, web3-utils@^1.3.6, web3-utils@^1.8.1: randombytes "^2.1.0" utf8 "3.0.0" -web3-utils@^4.0.7, web3-utils@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/web3-utils/-/web3-utils-4.1.0.tgz#3fcb77261fe1e4d02c46564fdee26f690f58a76a" - integrity sha512-+VJWR6FtCsgwuJr5tvSvQlSEG06586df8h2CxGc9tcNtIDyJKNkSDDWJkdNPvyDhhXFzQYFh8QOGymD1CIP6fw== +web3-utils@^4.0.7, web3-utils@^4.1.0, web3-utils@^4.1.1, web3-utils@^4.2.1: + version "4.2.1" + resolved "https://registry.yarnpkg.com/web3-utils/-/web3-utils-4.2.1.tgz#326bc6e9e4d047f7b38ba68bee1399c4f9f621e3" + integrity sha512-Fk29BlEqD9Q9Cnw4pBkKw7czcXiRpsSco/BzEUl4ye0ZTSHANQFfjsfQmNm4t7uY11u6Ah+8F3tNjBeU4CA80A== dependencies: ethereum-cryptography "^2.0.0" + eventemitter3 "^5.0.1" web3-errors "^1.1.4" - web3-types "^1.3.1" - web3-validator "^2.0.3" + web3-types "^1.5.0" + web3-validator "^2.0.4" -web3-validator@^2.0.3: - version "2.0.3" - resolved "https://registry.yarnpkg.com/web3-validator/-/web3-validator-2.0.3.tgz#e5dcd4b4902612cff21b7f8817dd233393999d97" - integrity sha512-fJbAQh+9LSNWy+l5Ze6HABreml8fra98o5+vS073T35jUcLbRZ0IOjF/ZPJhJNbJDt+jP1vseZsc3z3uX9mxxQ== +web3-validator@^2.0.3, web3-validator@^2.0.4: + version "2.0.4" + resolved "https://registry.yarnpkg.com/web3-validator/-/web3-validator-2.0.4.tgz#66f34c94f21a3c94d0dc2a2d30deb8a379825d38" + integrity sha512-qRxVePwdW+SByOmTpDZFWHIUAa7PswvxNszrOua6BoGqAhERo5oJZBN+EbWtK/+O+ApNxt5FR3nCPmiZldiOQA== dependencies: ethereum-cryptography "^2.0.0" util "^0.12.5" - web3-errors "^1.1.3" - web3-types "^1.3.0" + web3-errors "^1.1.4" + web3-types "^1.3.1" zod "^3.21.4" web3@^4.3.0: - version "4.3.0" - resolved "https://registry.yarnpkg.com/web3/-/web3-4.3.0.tgz#3f1d5e878295ef8bc7d2195f0f42aa2f7b8224cf" - integrity sha512-YiLCsb5wmgJlSxRLzt7Z7H+CmlVVIKD8VaUQaZ+xKVG3Q7CpsO5Z6jmeKnlr6M9c6fDDsDnRM6G8g+nchZehbA== + version "4.6.0" + resolved "https://registry.yarnpkg.com/web3/-/web3-4.6.0.tgz#202b31489d843d278547002b9a73327f19952b70" + integrity sha512-hoI6r29B4kjxINI21rBVaE0Bz0hwtW+Sbppn5ZDTWn5PSQpBW4ecYFDVKVE6K3gbmSjY2fknu2cjBTqha7S53A== dependencies: web3-core "^4.3.2" web3-errors "^1.1.4" - web3-eth "^4.3.1" - web3-eth-abi "^4.1.4" - web3-eth-accounts "^4.1.0" - web3-eth-contract "^4.1.4" - web3-eth-ens "^4.0.8" + web3-eth "^4.5.0" + web3-eth-abi "^4.2.0" + web3-eth-accounts "^4.1.1" + web3-eth-contract "^4.2.0" + web3-eth-ens "^4.1.0" web3-eth-iban "^4.0.7" web3-eth-personal "^4.0.8" web3-net "^4.0.7" web3-providers-http "^4.1.0" web3-providers-ws "^4.0.7" - web3-rpc-methods "^1.1.4" - web3-types "^1.3.1" - web3-utils "^4.1.0" - web3-validator "^2.0.3" + web3-rpc-methods "^1.2.0" + web3-types "^1.5.0" + web3-utils "^4.2.1" + web3-validator "^2.0.4" -webcrypto-core@^1.7.7: - version "1.7.7" - resolved "https://registry.yarnpkg.com/webcrypto-core/-/webcrypto-core-1.7.7.tgz#06f24b3498463e570fed64d7cab149e5437b162c" - integrity sha512-7FjigXNsBfopEj+5DV2nhNpfic2vumtjjgPmeDKk45z+MJwXKKfhPB7118Pfzrmh4jqOMST6Ch37iPAHoImg5g== +webcrypto-core@^1.7.8: + version "1.7.8" + resolved "https://registry.yarnpkg.com/webcrypto-core/-/webcrypto-core-1.7.8.tgz#056918036e846c72cfebbb04052e283f57f1114a" + integrity sha512-eBR98r9nQXTqXt/yDRtInszPMjTaSAMJAFDg2AHsgrnczawT1asx9YNBX6k5p+MekbPF4+s/UJJrr88zsTqkSg== dependencies: - "@peculiar/asn1-schema" "^2.3.6" + "@peculiar/asn1-schema" "^2.3.8" "@peculiar/json-schema" "^1.1.12" asn1js "^3.0.1" - pvtsutils "^1.3.2" - tslib "^2.4.0" + pvtsutils "^1.3.5" + tslib "^2.6.2" webextension-polyfill@^0.6.0: version "0.6.0" @@ -24738,18 +24831,18 @@ webpack@5.82.1: webpack-sources "^3.2.3" webpack@^5.88.1: - version "5.89.0" - resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.89.0.tgz#56b8bf9a34356e93a6625770006490bf3a7f32dc" - integrity sha512-qyfIC10pOr70V+jkmud8tMfajraGCZMBWJtrmuBymQKCrLTRejBI8STDp1MCyZu/QTdZSeacCQYpYNQVOzX5kw== + version "5.90.3" + resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.90.3.tgz#37b8f74d3ded061ba789bb22b31e82eed75bd9ac" + integrity sha512-h6uDYlWCctQRuXBs1oYpVe6sFcWedl0dpcVaTf/YF67J9bKvwJajFulMVSYKHrksMB3I/pIagRzDxwxkebuzKA== dependencies: "@types/eslint-scope" "^3.7.3" - "@types/estree" "^1.0.0" + "@types/estree" "^1.0.5" "@webassemblyjs/ast" "^1.11.5" "@webassemblyjs/wasm-edit" "^1.11.5" "@webassemblyjs/wasm-parser" "^1.11.5" acorn "^8.7.1" acorn-import-assertions "^1.9.0" - browserslist "^4.14.5" + browserslist "^4.21.10" chrome-trace-event "^1.0.2" enhanced-resolve "^5.15.0" es-module-lexer "^1.2.1" @@ -24763,7 +24856,7 @@ webpack@^5.88.1: neo-async "^2.6.2" schema-utils "^3.2.0" tapable "^2.1.1" - terser-webpack-plugin "^5.3.7" + terser-webpack-plugin "^5.3.10" watchpack "^2.4.0" webpack-sources "^3.2.3" @@ -24838,30 +24931,30 @@ which-builtin-type@^1.1.3: which-typed-array "^1.1.9" which-collection@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/which-collection/-/which-collection-1.0.1.tgz#70eab71ebbbd2aefaf32f917082fc62cdcb70906" - integrity sha512-W8xeTUwaln8i3K/cY1nGXzdnVZlidBcagyNFtBdD5kxnb4TvGKR7FfSIS3mYpwWS1QUCutfKz8IY8RjftB0+1A== + version "1.0.2" + resolved "https://registry.yarnpkg.com/which-collection/-/which-collection-1.0.2.tgz#627ef76243920a107e7ce8e96191debe4b16c2a0" + integrity sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw== dependencies: - is-map "^2.0.1" - is-set "^2.0.1" - is-weakmap "^2.0.1" - is-weakset "^2.0.1" + is-map "^2.0.3" + is-set "^2.0.3" + is-weakmap "^2.0.2" + is-weakset "^2.0.3" which-module@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.1.tgz#776b1fe35d90aebe99e8ac15eb24093389a4a409" integrity sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ== -which-typed-array@^1.1.11, which-typed-array@^1.1.13, which-typed-array@^1.1.2, which-typed-array@^1.1.9: - version "1.1.13" - resolved "https://registry.yarnpkg.com/which-typed-array/-/which-typed-array-1.1.13.tgz#870cd5be06ddb616f504e7b039c4c24898184d36" - integrity sha512-P5Nra0qjSncduVPEAr7xhoF5guty49ArDTwzJ/yNuPIbZppyRxFQsRCWrocxIY+CnMVG+qfbU2FmDKyvSGClow== +which-typed-array@^1.1.13, which-typed-array@^1.1.14, which-typed-array@^1.1.15, which-typed-array@^1.1.2, which-typed-array@^1.1.9: + version "1.1.15" + resolved "https://registry.yarnpkg.com/which-typed-array/-/which-typed-array-1.1.15.tgz#264859e9b11a649b388bfaaf4f767df1f779b38d" + integrity sha512-oV0jmFtUky6CXfkqehVvBP/LSWJ2sy4vWMioiENyJLePrBO/yKyV9OyJySfAKosh+RYkIl5zJCNZ8/4JncrpdA== dependencies: - available-typed-arrays "^1.0.5" - call-bind "^1.0.4" + available-typed-arrays "^1.0.7" + call-bind "^1.0.7" for-each "^0.3.3" gopd "^1.0.1" - has-tostringtag "^1.0.0" + has-tostringtag "^1.0.2" which@2.0.2, which@^2.0.1: version "2.0.2" @@ -24913,10 +25006,10 @@ windows-release@^4.0.0: dependencies: execa "^4.0.2" -winston-transport@^4.5.0: - version "4.6.0" - resolved "https://registry.yarnpkg.com/winston-transport/-/winston-transport-4.6.0.tgz#f1c1a665ad1b366df72199e27892721832a19e1b" - integrity sha512-wbBA9PbPAHxKiygo7ub7BYRiKxms0tpfU2ljtWzb3SjRjv5yl6Ozuy/TkXf00HTAt+Uylo3gSkNwzc4ME0wiIg== +winston-transport@^4.5.0, winston-transport@^4.7.0: + version "4.7.0" + resolved "https://registry.yarnpkg.com/winston-transport/-/winston-transport-4.7.0.tgz#e302e6889e6ccb7f383b926df6936a5b781bd1f0" + integrity sha512-ajBj65K5I7denzer2IYW6+2bNIVqLGDHqDw3Ow8Ohh+vdW+rv4MZ6eiDvHoKhfJFZ2auyN8byXieDDJ96ViONg== dependencies: logform "^2.3.2" readable-stream "^3.6.0" @@ -24940,9 +25033,9 @@ winston@3.10.0: winston-transport "^4.5.0" winston@^3.8.2: - version "3.11.0" - resolved "https://registry.yarnpkg.com/winston/-/winston-3.11.0.tgz#2d50b0a695a2758bb1c95279f0a88e858163ed91" - integrity sha512-L3yR6/MzZAOl0DsysUXHVjOwv8mKZ71TrA/41EIduGpOOV5LQVodqN+QdQ6BS6PJ/RdIshZhq84P/fStEZkk7g== + version "3.12.0" + resolved "https://registry.yarnpkg.com/winston/-/winston-3.12.0.tgz#a5d965a41d3dc31be5408f8c66e927958846c0d0" + integrity sha512-OwbxKaOlESDi01mC9rkM0dQqQt2I8DAUMRLZ/HpbwvDXm85IryEHgoogy5fziQy38PntgZsLlhAYHz//UPHZ5w== dependencies: "@colors/colors" "^1.6.0" "@dabh/diagnostics" "^2.0.2" @@ -24954,7 +25047,7 @@ winston@^3.8.2: safe-stable-stringify "^2.3.1" stack-trace "0.0.x" triple-beam "^1.3.0" - winston-transport "^4.5.0" + winston-transport "^4.7.0" word-wrap@~1.2.3: version "1.2.5" @@ -25078,18 +25171,18 @@ xml-name-validator@^4.0.0: resolved "https://registry.yarnpkg.com/xml-name-validator/-/xml-name-validator-4.0.0.tgz#79a006e2e63149a8600f15430f0a4725d1524835" integrity sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw== -xml2js@0.5.0, xml2js@^0.5.0: - version "0.5.0" - resolved "https://registry.yarnpkg.com/xml2js/-/xml2js-0.5.0.tgz#d9440631fbb2ed800203fad106f2724f62c493b7" - integrity sha512-drPFnkQJik/O+uPKpqSgr22mpuFHqKdbS835iAQrUC73L2F5WkboIRd63ai/2Yg6I1jzifPFKH2NTK+cfglkIA== +xml2js@0.6.2, xml2js@^0.6.2: + version "0.6.2" + resolved "https://registry.yarnpkg.com/xml2js/-/xml2js-0.6.2.tgz#dd0b630083aa09c161e25a4d0901e2b2a929b499" + integrity sha512-T4rieHaC1EXcES0Kxxj4JWgaUQHDk+qwHcYOCFHfiwKz7tOVPLq7Hjq9dM1WCMhylqMEfP7hMcOIChvotiZegA== dependencies: sax ">=0.6.0" xmlbuilder "~11.0.0" -xml2js@^0.6.2: - version "0.6.2" - resolved "https://registry.yarnpkg.com/xml2js/-/xml2js-0.6.2.tgz#dd0b630083aa09c161e25a4d0901e2b2a929b499" - integrity sha512-T4rieHaC1EXcES0Kxxj4JWgaUQHDk+qwHcYOCFHfiwKz7tOVPLq7Hjq9dM1WCMhylqMEfP7hMcOIChvotiZegA== +xml2js@^0.5.0: + version "0.5.0" + resolved "https://registry.yarnpkg.com/xml2js/-/xml2js-0.5.0.tgz#d9440631fbb2ed800203fad106f2724f62c493b7" + integrity sha512-drPFnkQJik/O+uPKpqSgr22mpuFHqKdbS835iAQrUC73L2F5WkboIRd63ai/2Yg6I1jzifPFKH2NTK+cfglkIA== dependencies: sax ">=0.6.0" xmlbuilder "~11.0.0" @@ -25254,15 +25347,26 @@ yup@0.32.9: toposort "^2.0.2" yup@^1.2.0: - version "1.3.3" - resolved "https://registry.yarnpkg.com/yup/-/yup-1.3.3.tgz#d2f6020ad1679754c5f8178a29243d5447dead04" - integrity sha512-v8QwZSsHH2K3/G9WSkp6mZKO+hugKT1EmnMqLNUcfu51HU9MDyhlETT/JgtzprnrnQHPWsjc6MUDMBp/l9fNnw== + version "1.4.0" + resolved "https://registry.yarnpkg.com/yup/-/yup-1.4.0.tgz#898dcd660f9fb97c41f181839d3d65c3ee15a43e" + integrity sha512-wPbgkJRCqIf+OHyiTBQoJiP5PFuAXaWiJK6AmYkzQAh5/c2K9hzSApBZG5wV9KoKSePF7sAxmNSvh/13YHkFDg== dependencies: property-expr "^2.0.5" tiny-case "^1.0.3" toposort "^2.0.2" type-fest "^2.19.0" +z-schema@~5.0.2: + version "5.0.6" + resolved "https://registry.yarnpkg.com/z-schema/-/z-schema-5.0.6.tgz#46d6a687b15e4a4369e18d6cb1c7b8618fc256c5" + integrity sha512-+XR1GhnWklYdfr8YaZv/iu+vY+ux7V5DS5zH1DQf6bO5ufrt/5cgNhVO5qyhsjFXvsqQb/f08DWE9b6uPscyAg== + dependencies: + lodash.get "^4.4.2" + lodash.isequal "^4.5.0" + validator "^13.7.0" + optionalDependencies: + commander "^10.0.0" + zen-observable-ts@^1.2.5: version "1.2.5" resolved "https://registry.yarnpkg.com/zen-observable-ts/-/zen-observable-ts-1.2.5.tgz#6c6d9ea3d3a842812c6e9519209365a122ba8b58" @@ -25286,9 +25390,9 @@ zod@^3.21.4: integrity sha512-iC+8Io04lddc+mVqQ9AZ7OQ2MrUKGN+oIQyq1vemgt46jwCwLfhq7/pwnBnNXXXZb8VTVLKwp9EDkx+ryxIWmg== zustand@^4.3.1: - version "4.4.7" - resolved "https://registry.yarnpkg.com/zustand/-/zustand-4.4.7.tgz#355406be6b11ab335f59a66d2cf9815e8f24038c" - integrity sha512-QFJWJMdlETcI69paJwhSMJz7PPWjVP8Sjhclxmxmxv/RYI7ZOvR5BHX+ktH0we9gTWQMxcne8q1OY8xxz604gw== + version "4.5.2" + resolved "https://registry.yarnpkg.com/zustand/-/zustand-4.5.2.tgz#fddbe7cac1e71d45413b3682cdb47b48034c3848" + integrity sha512-2cN1tPkDVkwCy5ickKrI7vijSjPksFRfqS6237NzT0vqSsztTNnQdHw9mmN7uBdk3gceVXU0a+21jFzFzAc9+g== dependencies: use-sync-external-store "1.2.0" From 83ba2855c1dcb2b150ef29cfdca0df22df6385b3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 25 Mar 2024 16:48:16 +0100 Subject: [PATCH 07/66] Bump express from 4.18.3 to 4.19.2 (#1766) Bumps [express](https://github.com/expressjs/express) from 4.18.3 to 4.19.2. - [Release notes](https://github.com/expressjs/express/releases) - [Changelog](https://github.com/expressjs/express/blob/master/History.md) - [Commits](https://github.com/expressjs/express/compare/4.18.3...4.19.2) --- updated-dependencies: - dependency-name: express dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- packages/apps/faucet-server/package.json | 2 +- yarn.lock | 39 +++++++++++++++++++++++- 2 files changed, 39 insertions(+), 2 deletions(-) diff --git a/packages/apps/faucet-server/package.json b/packages/apps/faucet-server/package.json index cb83c8d9ed..5d063ef402 100644 --- a/packages/apps/faucet-server/package.json +++ b/packages/apps/faucet-server/package.json @@ -18,7 +18,7 @@ "axios": "^1.3.4", "body-parser": "^1.20.0", "cors": "^2.8.5", - "express": "^4.18.3", + "express": "^4.19.2", "express-rate-limit": "^7.1.3", "node-cache": "^5.1.2", "web3": "^4.3.0" diff --git a/yarn.lock b/yarn.lock index 20dad6d60a..0a9c47bae5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -13281,7 +13281,7 @@ express-session@^1.17.3: safe-buffer "5.2.1" uid-safe "~2.1.5" -express@4.18.3, express@^4.18.3: +express@4.18.3: version "4.18.3" resolved "https://registry.yarnpkg.com/express/-/express-4.18.3.tgz#6870746f3ff904dee1819b82e4b51509afffb0d4" integrity sha512-6VyCijWQ+9O7WuVMTRBTl+cjNNIzD5cY5mQ1WM8r/LEkI2u8EYpOotESNwzNlyCn3g+dmjKYI6BmNneSr/FSRw== @@ -13318,6 +13318,43 @@ express@4.18.3, express@^4.18.3: utils-merge "1.0.1" vary "~1.1.2" +express@^4.19.2: + version "4.19.2" + resolved "https://registry.yarnpkg.com/express/-/express-4.19.2.tgz#e25437827a3aa7f2a827bc8171bbbb664a356465" + integrity sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q== + dependencies: + accepts "~1.3.8" + array-flatten "1.1.1" + body-parser "1.20.2" + content-disposition "0.5.4" + content-type "~1.0.4" + cookie "0.6.0" + cookie-signature "1.0.6" + debug "2.6.9" + depd "2.0.0" + encodeurl "~1.0.2" + escape-html "~1.0.3" + etag "~1.8.1" + finalhandler "1.2.0" + fresh "0.5.2" + http-errors "2.0.0" + merge-descriptors "1.0.1" + methods "~1.1.2" + on-finished "2.4.1" + parseurl "~1.3.3" + path-to-regexp "0.1.7" + proxy-addr "~2.0.7" + qs "6.11.0" + range-parser "~1.2.1" + safe-buffer "5.2.1" + send "0.18.0" + serve-static "1.15.0" + setprototypeof "1.2.0" + statuses "2.0.1" + type-is "~1.6.18" + utils-merge "1.0.1" + vary "~1.1.2" + extend-shallow@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/extend-shallow/-/extend-shallow-2.0.1.tgz#51af7d614ad9a9f610ea1bafbb989d6b1c56890f" From fd2624eda5495e9d1d7e24d22759f7bdbcfc373c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 25 Mar 2024 16:48:58 +0100 Subject: [PATCH 08/66] Bump hardhat-gas-reporter from 1.0.10 to 2.0.2 (#1765) Bumps [hardhat-gas-reporter](https://github.com/cgewecke/hardhat-gas-reporter) from 1.0.10 to 2.0.2. - [Release notes](https://github.com/cgewecke/hardhat-gas-reporter/releases) - [Changelog](https://github.com/cgewecke/hardhat-gas-reporter/blob/master/CHANGELOG.md) - [Commits](https://github.com/cgewecke/hardhat-gas-reporter/commits/v2.0.2) --- updated-dependencies: - dependency-name: hardhat-gas-reporter dependency-type: direct:development update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- packages/core/package.json | 2 +- yarn.lock | 164 +++++++++++++------------------------ 2 files changed, 56 insertions(+), 110 deletions(-) diff --git a/packages/core/package.json b/packages/core/package.json index f3d6e65156..822177ae47 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -69,7 +69,7 @@ "hardhat-abi-exporter": "^2.10.1", "hardhat-contract-sizer": "^2.6.1", "hardhat-dependency-compiler": "^1.1.3", - "hardhat-gas-reporter": "^1.0.9", + "hardhat-gas-reporter": "^2.0.2", "openpgp": "5.10.2", "prettier-plugin-solidity": "^1.3.1", "solidity-coverage": "^0.8.2", diff --git a/yarn.lock b/yarn.lock index 0a9c47bae5..82a0fd6b7b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2383,7 +2383,7 @@ "@ethersproject/rlp" "^5.7.0" "@ethersproject/signing-key" "^5.7.0" -"@ethersproject/units@5.7.0": +"@ethersproject/units@5.7.0", "@ethersproject/units@^5.7.0": version "5.7.0" resolved "https://registry.yarnpkg.com/@ethersproject/units/-/units-5.7.0.tgz#637b563d7e14f42deeee39245275d477aae1d8b1" integrity sha512-pD3xLMy3SJu9kG5xDGI7+xhTEmGXlEqXU4OfNapmfnxLVY4EMSSRp7j1k7eezutBPH7RBN/7QPnwR7hzNlEFeg== @@ -5287,13 +5287,6 @@ rpc-websockets "^7.5.1" superstruct "^0.14.2" -"@solidity-parser/parser@^0.14.0": - version "0.14.5" - resolved "https://registry.yarnpkg.com/@solidity-parser/parser/-/parser-0.14.5.tgz#87bc3cc7b068e08195c219c91cd8ddff5ef1a804" - integrity sha512-6dKnHZn7fg/iQATVEzqyUOyEidbn05q7YA2mQ9hC0MMXhhV3/JrsxmFSYZAcr7j1yUP700LLhTruvJ3MiQmjJg== - dependencies: - antlr4ts "^0.5.0-alpha.4" - "@solidity-parser/parser@^0.17.0": version "0.17.0" resolved "https://registry.yarnpkg.com/@solidity-parser/parser/-/parser-0.17.0.tgz#52a2fcc97ff609f72011014e4c5b485ec52243ef" @@ -8089,6 +8082,11 @@ abitype@0.9.8: resolved "https://registry.yarnpkg.com/abitype/-/abitype-0.9.8.tgz#1f120b6b717459deafd213dfbf3a3dd1bf10ae8c" integrity sha512-puLifILdm+8sjyss4S+fsUN09obiT1g2YW6CtcQF+QDzxR0euzgEB29MZujC6zMk2a6SVmtttq1fc6+YFA7WYQ== +abitype@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/abitype/-/abitype-1.0.0.tgz#237176dace81d90d018bebf3a45cb42f2a2d9e97" + integrity sha512-NMeMah//6bJ56H5XRj8QCV4AwuW6hB6zqz2LnhhLdcWVQOsXki6/Pn3APeqxCma62nXIcmZWdu1DlHWS74umVQ== + abitype@^0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/abitype/-/abitype-0.3.0.tgz#75150e337d88cc0b2423ed0d3fc36935f139d04c" @@ -8305,11 +8303,6 @@ ansi-regex@^2.0.0: resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df" integrity sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA== -ansi-regex@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-3.0.1.tgz#123d6479e92ad45ad897d4054e3c7ca7db4944e1" - integrity sha512-+O9Jct8wf++lXxxFc4hc8LsjaSq0HFzzL7cVsw8pRDIPdjKD2mT4ytDZlLuSBZ4cLKZFXIrMGO7DbQCtMJJMKw== - ansi-regex@^4.1.0: version "4.1.1" resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-4.1.1.tgz#164daac87ab2d6f6db3a29875e2d1766582dabed" @@ -8364,11 +8357,6 @@ ansicolors@~0.3.2: resolved "https://registry.yarnpkg.com/ansicolors/-/ansicolors-0.3.2.tgz#665597de86a9ffe3aa9bfbe6cae5c6ea426b4979" integrity sha512-QXu7BPrP29VllRxH8GwB7x5iX5qWKAAMLqKQGWTeLWVlNHNOpVMJ91dsxQAIWXpjuW5wqvxu3Jd/nRjrJ+0pqg== -antlr4ts@^0.5.0-alpha.4: - version "0.5.0-alpha.4" - resolved "https://registry.yarnpkg.com/antlr4ts/-/antlr4ts-0.5.0-alpha.4.tgz#71702865a87478ed0b40c0709f422cf14d51652a" - integrity sha512-WPQDt1B74OfPv/IMS2ekXAKkTZIHl88uMetg6q3OTqgFxZ/dxDXI0EWLyZid/1Pe6hTftyg5N7gel5wNAGxXyQ== - any-promise@^1.0.0: version "1.3.0" resolved "https://registry.yarnpkg.com/any-promise/-/any-promise-1.3.0.tgz#abc6afeedcea52e809cdc0376aed3ce39635d17f" @@ -8552,11 +8540,6 @@ array-union@^2.1.0: resolved "https://registry.yarnpkg.com/array-union/-/array-union-2.1.0.tgz#b798420adbeb1de828d84acd8a2e23d3efe85e8d" integrity sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw== -array-uniq@1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/array-uniq/-/array-uniq-1.0.3.tgz#af6ac877a25cc7f74e058894753858dfdb24fdb6" - integrity sha512-MNha4BWQ6JbwhFhj03YK552f7cb3AzoE8SzeljgChvL1dl3IcvggXVz1DilzySZkCja+CXuZbdW7yATchWn8/Q== - array-unique@^0.3.2: version "0.3.2" resolved "https://registry.yarnpkg.com/array-unique/-/array-unique-0.3.2.tgz#a894b75d4bc4f6cd679ef3244a9fd8f46ae2d428" @@ -10100,7 +10083,7 @@ cli-table3@0.6.2: optionalDependencies: "@colors/colors" "1.5.0" -cli-table3@0.6.3, cli-table3@^0.6.0, cli-table3@^0.6.2: +cli-table3@0.6.3: version "0.6.3" resolved "https://registry.yarnpkg.com/cli-table3/-/cli-table3-0.6.3.tgz#61ab765aac156b52f222954ffc607a6f01dbeeb2" integrity sha512-w5Jac5SykAeZJKntOxJCrm63Eg5/4dhMWIcuTbo9rpE+brgaSZo0RuNJZeOyMgsUdhDeojvgyQLmjI+K50ZGyg== @@ -10109,15 +10092,14 @@ cli-table3@0.6.3, cli-table3@^0.6.0, cli-table3@^0.6.2: optionalDependencies: "@colors/colors" "1.5.0" -cli-table3@^0.5.0: - version "0.5.1" - resolved "https://registry.yarnpkg.com/cli-table3/-/cli-table3-0.5.1.tgz#0252372d94dfc40dbd8df06005f48f31f656f202" - integrity sha512-7Qg2Jrep1S/+Q3EceiZtQcDPWxhAvBw+ERf1162v4sikJrvojMHFqXt8QIVha8UlH9rgU0BeWPytZ9/TzYqlUw== +cli-table3@^0.6.0, cli-table3@^0.6.2, cli-table3@^0.6.3: + version "0.6.4" + resolved "https://registry.yarnpkg.com/cli-table3/-/cli-table3-0.6.4.tgz#d1c536b8a3f2e7bec58f67ac9e5769b1b30088b0" + integrity sha512-Lm3L0p+/npIQWNIiyF/nAn7T5dnOwR3xNTHXYEBFBFVPXzCVNZ5lqEC/1eo/EVfpDsQ1I+TX4ORPQgp+UI0CRw== dependencies: - object-assign "^4.1.0" - string-width "^2.1.1" + string-width "^4.2.0" optionalDependencies: - colors "^1.1.2" + "@colors/colors" "1.5.0" cli-truncate@^4.0.0: version "4.0.0" @@ -12931,25 +12913,6 @@ eth-block-tracker@6.1.0: json-rpc-random-id "^1.0.1" pify "^3.0.0" -eth-gas-reporter@^0.2.25: - version "0.2.27" - resolved "https://registry.yarnpkg.com/eth-gas-reporter/-/eth-gas-reporter-0.2.27.tgz#928de8548a674ed64c7ba0bf5795e63079150d4e" - integrity sha512-femhvoAM7wL0GcI8ozTdxfuBtBFJ9qsyIAsmKVjlWAHUbdnnXHt+lKzz/kmldM5lA9jLuNHGwuIxorNpLbR1Zw== - dependencies: - "@solidity-parser/parser" "^0.14.0" - axios "^1.5.1" - cli-table3 "^0.5.0" - colors "1.4.0" - ethereum-cryptography "^1.0.3" - ethers "^5.7.2" - fs-readdir-recursive "^1.1.0" - lodash "^4.17.14" - markdown-table "^1.1.3" - mocha "^10.2.0" - req-cwd "^2.0.0" - sha1 "^1.1.1" - sync-request "^6.0.0" - eth-json-rpc-filters@5.1.0: version "5.1.0" resolved "https://registry.yarnpkg.com/eth-json-rpc-filters/-/eth-json-rpc-filters-5.1.0.tgz#f0c2aeaec2a45e2dc6ca1b9843d8e85447821427" @@ -13021,7 +12984,7 @@ ethereum-cryptography@^1.0.3: "@scure/bip32" "1.1.5" "@scure/bip39" "1.1.1" -ethereum-cryptography@^2.0.0, ethereum-cryptography@^2.1.2: +ethereum-cryptography@^2.0.0, ethereum-cryptography@^2.1.2, ethereum-cryptography@^2.1.3: version "2.1.3" resolved "https://registry.yarnpkg.com/ethereum-cryptography/-/ethereum-cryptography-2.1.3.tgz#1352270ed3b339fe25af5ceeadcf1b9c8e30768a" integrity sha512-BlwbIL7/P45W8FGW2r7LGuvoEZ+7PWsniMvQ4p5s2xCyw9tmaDlpfsN9HjAucbF+t/qpVHwZUisgfK24TCW8aA== @@ -13923,11 +13886,6 @@ fs-monkey@^1.0.4: resolved "https://registry.yarnpkg.com/fs-monkey/-/fs-monkey-1.0.5.tgz#fe450175f0db0d7ea758102e1d84096acb925788" integrity sha512-8uMbBjrhzW76TYgEV27Y5E//W2f/lTFmx78P2w19FZSxarhI/798APGQyuGCwmkNxgwGRhrLfvWyLBvNtuOmew== -fs-readdir-recursive@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/fs-readdir-recursive/-/fs-readdir-recursive-1.1.0.tgz#e32fc030a2ccee44a6b5371308da54be0b397d27" - integrity sha512-GNanXlVr2pf02+sPN40XN8HG+ePaNcvM0q5mZBd668Obwb0yD5GiUbZOFgwn8kGMY6I3mdyDJzieUy3PTYyTRA== - fs.realpath@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" @@ -14593,14 +14551,25 @@ hardhat-deploy@^0.11.43: qs "^6.9.4" zksync-web3 "^0.14.3" -hardhat-gas-reporter@^1.0.9: - version "1.0.10" - resolved "https://registry.yarnpkg.com/hardhat-gas-reporter/-/hardhat-gas-reporter-1.0.10.tgz#ebe5bda5334b5def312747580cd923c2b09aef1b" - integrity sha512-02N4+So/fZrzJ88ci54GqwVA3Zrf0C9duuTyGt0CFRIh/CdNwbnTgkXkRfojOMLBQ+6t+lBIkgbsOtqMvNwikA== +hardhat-gas-reporter@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/hardhat-gas-reporter/-/hardhat-gas-reporter-2.0.2.tgz#3727ed915256570e09a56cffe0d2183723009e4e" + integrity sha512-i/+g+dX+/+MZ7L4M5NE78TgjDgnE3dINhsNUJmjwbqR3cLSMPrsEyiee+/1c5w32JSD08SKyDf+8W9Q5iC+zLw== dependencies: - array-uniq "1.0.3" - eth-gas-reporter "^0.2.25" + "@ethersproject/abi" "^5.7.0" + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/units" "^5.7.0" + "@solidity-parser/parser" "^0.18.0" + axios "^1.6.7" + chalk "4.1.2" + cli-table3 "^0.6.3" + ethereum-cryptography "^2.1.3" + glob "^10.3.10" + jsonschema "^1.4.1" + lodash "^4.17.21" + markdown-table "2.0.0" sha1 "^1.1.1" + viem "2.7.14" hardhat@^2.22.1: version "2.22.1" @@ -15733,11 +15702,6 @@ is-finalizationregistry@^1.0.2: dependencies: call-bind "^1.0.2" -is-fullwidth-code-point@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz#a3b30a5c4f199183167aaab93beefae3ddfb654f" - integrity sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w== - is-fullwidth-code-point@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" @@ -17002,7 +16966,7 @@ jsonpath-plus@^4.0.0: resolved "https://registry.yarnpkg.com/jsonpath-plus/-/jsonpath-plus-4.0.0.tgz#954b69faa3d8b07f30ae2f9e601176a4b0d2806e" integrity sha512-e0Jtg4KAzDJKKwzbLaUtinCn0RZseWBVRTRGihSpvFlM3wTR7ExSp+PTdeTsDrLNJUe7L7JYJe8mblHX5SCT6A== -jsonschema@^1.2.4: +jsonschema@^1.2.4, jsonschema@^1.4.1: version "1.4.1" resolved "https://registry.yarnpkg.com/jsonschema/-/jsonschema-1.4.1.tgz#cc4c3f0077fb4542982973d8a083b6b34f482dab" integrity sha512-S6cATIPVv1z0IlxdN+zUk5EPjkGCdnhN4wVSBlvoUO1tOLJootbo9CquNJmbIh4yikWHiUedhRYrNPn1arpEmQ== @@ -17726,7 +17690,7 @@ lodash.upperfirst@^4.3.1: resolved "https://registry.yarnpkg.com/lodash.upperfirst/-/lodash.upperfirst-4.3.1.tgz#1365edf431480481ef0d1c68957a5ed99d49f7ce" integrity sha512-sReKOYJIJf74dhJONhU4e0/shzi1trVbSWDOhKYE5XV2O+H7Sb2Dihwuc7xWxVl+DgFPyTqIN3zMfT9cq5iWDg== -lodash@4.17.21, lodash@^4.16.3, lodash@^4.17.11, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.20, lodash@^4.17.21, lodash@^4.17.4: +lodash@4.17.21, lodash@^4.16.3, lodash@^4.17.11, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.20, lodash@^4.17.21, lodash@^4.17.4: version "4.17.21" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== @@ -18030,10 +17994,12 @@ markdown-it@^12.3.2: mdurl "^1.0.1" uc.micro "^1.0.5" -markdown-table@^1.1.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/markdown-table/-/markdown-table-1.1.3.tgz#9fcb69bcfdb8717bfd0398c6ec2d93036ef8de60" - integrity sha512-1RUZVgQlpJSPWYbFSpmudq5nHY1doEIv89gBtF0s4gW1GF2XorxcA/70M5vq7rLv0a6mhOUccRsqkwhwLCIQ2Q== +markdown-table@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/markdown-table/-/markdown-table-2.0.0.tgz#194a90ced26d31fe753d8b9434430214c011865b" + integrity sha512-Ezda85ToJUBhM6WGaG6veasyym+Tbs3cMAw/ZhOPqXiYsr0jgocBV3j3nx+4lk47plLlIqjwuTm/ywVI+zjJ/A== + dependencies: + repeat-string "^1.0.0" marked@^4.3.0: version "4.3.0" @@ -21225,25 +21191,11 @@ repeat-element@^1.1.2: resolved "https://registry.yarnpkg.com/repeat-element/-/repeat-element-1.1.4.tgz#be681520847ab58c7568ac75fbfad28ed42d39e9" integrity sha512-LFiNfRcSu7KK3evMyYOuCzv3L10TW7yC1G2/+StMjK8Y6Vqd2MG7r/Qjw4ghtuCOjFvlnms/iMmLqpvW/ES/WQ== -repeat-string@^1.6.1: +repeat-string@^1.0.0, repeat-string@^1.6.1: version "1.6.1" resolved "https://registry.yarnpkg.com/repeat-string/-/repeat-string-1.6.1.tgz#8dcae470e1c88abc2d600fff4a776286da75e637" integrity sha512-PV0dzCYDNfRi1jCDbJzpW7jNNDRuCOG/jI5ctQcGKt/clZD+YcPS3yIlWuTJMmESC8aevCFmWJy5wjAFgNqN6w== -req-cwd@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/req-cwd/-/req-cwd-2.0.0.tgz#d4082b4d44598036640fb73ddea01ed53db49ebc" - integrity sha512-ueoIoLo1OfB6b05COxAA9UpeoscNpYyM+BqYlA7H6LVF4hKGPXQQSSaD2YmvDVJMkk4UDpAHIeU1zG53IqjvlQ== - dependencies: - req-from "^2.0.0" - -req-from@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/req-from/-/req-from-2.0.0.tgz#d74188e47f93796f4aa71df6ee35ae689f3e0e70" - integrity sha512-LzTfEVDVQHBRfjOUMgNBA+V6DWsSnoeKzf42J7l0xa/B4jyPOuuF5MlNSmomLNGemWTnV2TIdjSSLnEn95fOQA== - dependencies: - resolve-from "^3.0.0" - request-compose@^2.1.4, request-compose@^2.1.6: version "2.1.6" resolved "https://registry.yarnpkg.com/request-compose/-/request-compose-2.1.6.tgz#f498d6afd4ce983066432446d4b1e3d3d51fbd0f" @@ -21329,11 +21281,6 @@ resolve-from@5.0.0, resolve-from@^5.0.0: resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-5.0.0.tgz#c35225843df8f776df21c57557bc087e9dfdfc69" integrity sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw== -resolve-from@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-3.0.0.tgz#b22c7af7d9d6881bc8b6e653335eebcb0a188748" - integrity sha512-GnlH6vxLymXJNMBo7XP1fJIzBFbdYt49CuTwmB/6N53t+kMPRMFKz783LlQ4tv28XoQfMWinAJX6WCGf2IlaIw== - resolve-from@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6" @@ -22549,14 +22496,6 @@ string-natural-compare@^3.0.1: is-fullwidth-code-point "^3.0.0" strip-ansi "^6.0.1" -string-width@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-2.1.1.tgz#ab93f27a8dc13d28cac815c462143a6d9012ae9e" - integrity sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw== - dependencies: - is-fullwidth-code-point "^2.0.0" - strip-ansi "^4.0.0" - string-width@^5.0.1, string-width@^5.1.2: version "5.1.2" resolved "https://registry.yarnpkg.com/string-width/-/string-width-5.1.2.tgz#14f8daec6d81e7221d2a357e668cab73bdbca794" @@ -22654,13 +22593,6 @@ strip-ansi@^3.0.0: dependencies: ansi-regex "^2.0.0" -strip-ansi@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-4.0.0.tgz#a8479022eb1ac368a871389b635262c505ee368f" - integrity sha512-4XaJ2zQdCzROZDivEVIDPkcQn8LMFSa8kj8Gxb/Lnwzv9A8VctNZ+lfivC/sV3ivW8ElJTERXZoPBRrZKkNKow== - dependencies: - ansi-regex "^3.0.0" - strip-ansi@^5.2.0: version "5.2.0" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-5.2.0.tgz#8c9a536feb6afc962bdfa5b104a5091c1ad9c0ae" @@ -22881,7 +22813,7 @@ symbol-tree@^3.2.4: resolved "https://registry.yarnpkg.com/symbol-tree/-/symbol-tree-3.2.4.tgz#430637d248ba77e078883951fb9aa0eed7c63fa2" integrity sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw== -sync-request@6.1.0, sync-request@^6.0.0: +sync-request@6.1.0: version "6.1.0" resolved "https://registry.yarnpkg.com/sync-request/-/sync-request-6.1.0.tgz#e96217565b5e50bbffe179868ba75532fb597e68" integrity sha512-8fjNkrNlNCrVc/av+Jn+xxqfCjYaBoHqCsDz6mt030UMxJGr+GSfCV1dQt2gRtlL63+VPidwDVLr7V2OcTSdRw== @@ -24286,6 +24218,20 @@ victory-vendor@^36.6.8: d3-time "^3.0.0" d3-timer "^3.0.1" +viem@2.7.14: + version "2.7.14" + resolved "https://registry.yarnpkg.com/viem/-/viem-2.7.14.tgz#347d316cb5400f0b896b2205b1bc8073aa5e27e0" + integrity sha512-5b1KB1gXli02GOQHZIUsRluNUwssl2t4hqdFAzyWPwJ744N83jAOBOjOkrGz7K3qMIv9b0GQt3DoZIErSQTPkQ== + dependencies: + "@adraffy/ens-normalize" "1.10.0" + "@noble/curves" "1.2.0" + "@noble/hashes" "1.3.2" + "@scure/bip32" "1.3.2" + "@scure/bip39" "1.2.1" + abitype "1.0.0" + isows "1.0.3" + ws "8.13.0" + viem@^1.16.6: version "1.21.4" resolved "https://registry.yarnpkg.com/viem/-/viem-1.21.4.tgz#883760e9222540a5a7e0339809202b45fe6a842d" From 3213769839dbff37824b7381b0bac88bbaac8eff Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 26 Mar 2024 08:37:10 +0100 Subject: [PATCH 09/66] Bump winston from 3.11.0 to 3.13.0 (#1763) Bumps [winston](https://github.com/winstonjs/winston) from 3.11.0 to 3.13.0. - [Release notes](https://github.com/winstonjs/winston/releases) - [Changelog](https://github.com/winstonjs/winston/blob/master/CHANGELOG.md) - [Commits](https://github.com/winstonjs/winston/compare/v3.11.0...v3.13.0) --- updated-dependencies: - dependency-name: winston dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- packages/sdk/typescript/human-protocol-sdk/package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/sdk/typescript/human-protocol-sdk/package.json b/packages/sdk/typescript/human-protocol-sdk/package.json index 075727e5fb..1d2550bc11 100644 --- a/packages/sdk/typescript/human-protocol-sdk/package.json +++ b/packages/sdk/typescript/human-protocol-sdk/package.json @@ -50,7 +50,7 @@ "openpgp": "^5.10.2", "secp256k1": "^4.0.3", "vitest": "^0.30.1", - "winston": "^3.8.2" + "winston": "^3.13.0" }, "devDependencies": { "typedoc": "^0.25.1", diff --git a/yarn.lock b/yarn.lock index 82a0fd6b7b..634973c17a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -25015,10 +25015,10 @@ winston@3.10.0: triple-beam "^1.3.0" winston-transport "^4.5.0" -winston@^3.8.2: - version "3.12.0" - resolved "https://registry.yarnpkg.com/winston/-/winston-3.12.0.tgz#a5d965a41d3dc31be5408f8c66e927958846c0d0" - integrity sha512-OwbxKaOlESDi01mC9rkM0dQqQt2I8DAUMRLZ/HpbwvDXm85IryEHgoogy5fziQy38PntgZsLlhAYHz//UPHZ5w== +winston@^3.13.0: + version "3.13.0" + resolved "https://registry.yarnpkg.com/winston/-/winston-3.13.0.tgz#e76c0d722f78e04838158c61adc1287201de7ce3" + integrity sha512-rwidmA1w3SE4j0E5MuIufFhyJPBDG7Nu71RkZor1p2+qHvJSZ9GYDA81AyleQcZbh/+V6HjeBdfnTZJm9rSeQQ== dependencies: "@colors/colors" "^1.6.0" "@dabh/diagnostics" "^2.0.2" From dbb0ef63173291a69697145d7823a0376e4e682d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Francisco=20L=C3=B3pez?= <50665615+flopez7@users.noreply.github.com> Date: Thu, 28 Mar 2024 10:12:13 +0100 Subject: [PATCH 10/66] [Job Launcher] Add IMAGE_SKELETONS_FROM_BOXES job type (#1770) * Added `IMAGE_BOXES_FROM_POINTS` and `IMAGE_SKELETONS_FROM_BOXES` job types for CVAT. API schema for `/job/cvat` endpoint is changed. Slightly changed the way manifest is being created for a CVAT job. * Use `ApiPropertyOptional` instead of `IsOptional()` * Adding missing `IsOptional` decorators * Add the new IMAGE_BOXES_FROM_POINTS and IMAGE_SKELETONS_FROM_BOXES job types in JL Frontend * Added missing migration. Minor fixes * Send nodes only for IMAGE_SKELETONS_FROM_BOXES * Tests fix --------- Co-authored-by: Sergey Dzeranov --- .../Jobs/Create/CvatJobRequestForm.tsx | 197 +++++++++++++++++- .../src/components/Jobs/Create/schema.ts | 45 +++- .../job-launcher/client/src/types/index.ts | 18 +- .../server/src/common/constants/index.ts | 2 + .../server/src/common/enums/job.ts | 2 + .../server/src/common/utils/storage.ts | 4 +- .../1711543013267-newCvatJobTypes.ts | 89 ++++++++ .../server/src/modules/job/job.dto.ts | 58 ++++-- .../src/modules/job/job.service.spec.ts | 59 +++--- .../server/src/modules/job/job.service.ts | 70 ++++++- .../job-launcher/server/test/constants.ts | 18 +- 11 files changed, 491 insertions(+), 71 deletions(-) create mode 100644 packages/apps/job-launcher/server/src/database/migrations/1711543013267-newCvatJobTypes.ts diff --git a/packages/apps/job-launcher/client/src/components/Jobs/Create/CvatJobRequestForm.tsx b/packages/apps/job-launcher/client/src/components/Jobs/Create/CvatJobRequestForm.tsx index eac28ebf7f..2aee2e59e3 100644 --- a/packages/apps/job-launcher/client/src/components/Jobs/Create/CvatJobRequestForm.tsx +++ b/packages/apps/job-launcher/client/src/components/Jobs/Create/CvatJobRequestForm.tsx @@ -30,8 +30,9 @@ import { CvatJobType, GCSRegions, StorageProviders, + Label, } from '../../../types'; -import { CvatJobRequestValidationSchema } from './schema'; +import { CvatJobRequestValidationSchema, dataValidationSchema } from './schema'; export const CvatJobRequestForm = () => { const { jobRequest, updateJobRequest, goToPrevStep, goToNextStep } = @@ -41,6 +42,7 @@ export const CvatJobRequestForm = () => { const initialValues = { labels: [], + nodes: [], type: CvatJobType.IMAGE_BOXES, description: '', userGuide: '', @@ -49,6 +51,10 @@ export const CvatJobRequestForm = () => { dataRegion: '', dataBucketName: '', dataPath: '', + bpProvider: StorageProviders.AWS, + bpRegion: '', + bpBucketName: '', + bpPath: '', gtProvider: StorageProviders.AWS, gtRegion: '', gtBucketName: '', @@ -66,12 +72,17 @@ export const CvatJobRequestForm = () => { const handleNext = ({ labels, + nodes, type, description, dataProvider, dataRegion, dataBucketName, dataPath, + bpProvider, + bpRegion, + bpBucketName, + bpPath, gtProvider, gtRegion, gtBucketName, @@ -79,17 +90,44 @@ export const CvatJobRequestForm = () => { userGuide, accuracyTarget, }: any) => { + let bp = undefined; + if (type === CvatJobType.IMAGE_BOXES_FROM_POINTS) + bp = { + points: { + provider: bpProvider, + region: bpRegion, + bucketName: bpBucketName, + path: bpPath, + }, + }; + else if (type === CvatJobType.IMAGE_SKELETONS_FROM_BOXES) + bp = { + boxes: { + provider: bpProvider, + region: bpRegion, + bucketName: bpBucketName, + path: bpPath, + }, + }; + const labelArray: Label[] = labels.map((name: string) => { + if (type === CvatJobType.IMAGE_SKELETONS_FROM_BOXES) + return { name: name, nodes: nodes }; + else return { name: name }; + }); updateJobRequest?.({ ...jobRequest, cvatRequest: { - labels, + labels: labelArray, type, description, data: { - provider: dataProvider, - region: dataRegion, - bucketName: dataBucketName, - path: dataPath, + dataset: { + provider: dataProvider, + region: dataRegion, + bucketName: dataBucketName, + path: dataPath, + }, + ...bp, }, groundTruth: { provider: gtProvider, @@ -104,6 +142,10 @@ export const CvatJobRequestForm = () => { goToNextStep?.(); }; + const [updatedValidationSchema, setUpdatedValidationSchema] = useState( + CvatJobRequestValidationSchema, + ); + const { errors, touched, @@ -115,8 +157,10 @@ export const CvatJobRequestForm = () => { setFieldValue, } = useFormik({ initialValues, - validationSchema: CvatJobRequestValidationSchema, + validationSchema: updatedValidationSchema, onSubmit: handleNext, + validateOnChange: true, + validateOnMount: true, }); useEffect(() => { @@ -158,7 +202,14 @@ export const CvatJobRequestForm = () => { id="cvat-job-type-select" label="Type of job" value={values.type} - onChange={(e) => setFieldValue('type', e.target.value)} + onChange={(e) => { + setUpdatedValidationSchema( + CvatJobRequestValidationSchema.concat( + dataValidationSchema(e.target.value as CvatJobType), + ), + ); + setFieldValue('type', e.target.value); + }} error={touched.type && Boolean(errors.type)} onBlur={handleBlur} > @@ -166,6 +217,12 @@ export const CvatJobRequestForm = () => { Bounding Boxes + + Bounding Boxes from points + + + Skeletons from Bounding Boxes + @@ -194,6 +251,33 @@ export const CvatJobRequestForm = () => { )} + {values.type === CvatJobType.IMAGE_SKELETONS_FROM_BOXES && ( + + + + value.map((option, index) => ( + + )) + } + renderInput={(params) => ( + + )} + onChange={(e, value) => setFieldValue('nodes', value)} + onBlur={handleBlur} + /> + {errors.nodes && ( + + {errors.nodes} + + )} + + + )} { + {[ + CvatJobType.IMAGE_BOXES_FROM_POINTS, + CvatJobType.IMAGE_SKELETONS_FROM_BOXES, + ].includes(values.type) && ( + + + + {values.type === CvatJobType.IMAGE_BOXES_FROM_POINTS + ? 'Points' + : 'Boxes'} + + + + + + Storage Provider + + + + + + + + Region + + + {errors.bpRegion && ( + + {errors.bpRegion} + + )} + + + + + + setFieldValue('bpBucketName', e.target.value) + } + error={ + touched.bpBucketName && Boolean(errors.bpBucketName) + } + helperText={errors.bpBucketName} + /> + + + + + + setFieldValue('bpPath', e.target.value) + } + error={touched.bpPath && Boolean(errors.bpPath)} + helperText={errors.bpPath} + /> + + + + )} diff --git a/packages/apps/job-launcher/client/src/components/Jobs/Create/schema.ts b/packages/apps/job-launcher/client/src/components/Jobs/Create/schema.ts index 94709da643..def7e1b07b 100644 --- a/packages/apps/job-launcher/client/src/components/Jobs/Create/schema.ts +++ b/packages/apps/job-launcher/client/src/components/Jobs/Create/schema.ts @@ -1,15 +1,16 @@ import * as Yup from 'yup'; +import { CvatJobType } from '../../../types'; export const CvatJobRequestValidationSchema = Yup.object().shape({ labels: Yup.array().of(Yup.string()).min(1, 'At least one label is required'), description: Yup.string().required('Description is required'), - dataProvider: Yup.string().required('Data provider is required'), - dataRegion: Yup.string().required('Data region is required'), - dataBucketName: Yup.string().required('Data bucket name is required'), + dataProvider: Yup.string().required('Provider is required'), + dataRegion: Yup.string().required('Region is required'), + dataBucketName: Yup.string().required('Bucket name is required'), dataPath: Yup.string().optional(), - gtProvider: Yup.string().required('Ground truth provider is required'), - gtRegion: Yup.string().required('Ground truth region is required'), - gtBucketName: Yup.string().required('Ground truth bucket name is required'), + gtProvider: Yup.string().required('Provider is required'), + gtRegion: Yup.string().required('Region is required'), + gtBucketName: Yup.string().required('Bucket name is required'), gtPath: Yup.string().optional(), userGuide: Yup.string() .required('User Guide URL is required') @@ -20,6 +21,38 @@ export const CvatJobRequestValidationSchema = Yup.object().shape({ .max(100, 'Accuracy target must be less than or equal to 100'), }); +export const dataValidationSchema = (type: CvatJobType) => { + let schema; + if ( + type === CvatJobType.IMAGE_BOXES_FROM_POINTS || + type === CvatJobType.IMAGE_SKELETONS_FROM_BOXES + ) { + schema = Yup.object().shape({ + bpProvider: Yup.string().required('Provider is required'), + bpRegion: Yup.string().required('Region is required'), + bpBucketName: Yup.string().required('Bucket name is required'), + bpPath: Yup.string().optional(), + }); + if (type === CvatJobType.IMAGE_SKELETONS_FROM_BOXES) { + schema = schema.concat( + Yup.object().shape({ + nodes: Yup.array() + .of(Yup.string()) + .min(1, 'At least one node is required'), + }), + ); + } + } else { + schema = Yup.object().shape({ + bpProvider: Yup.string().optional(), + bpRegion: Yup.string().optional(), + bpBucketName: Yup.string().optional(), + bpPath: Yup.string().optional(), + }); + } + return schema; +}; + export const FortuneJobRequestValidationSchema = Yup.object().shape({ title: Yup.string().required('Title is required'), description: Yup.string().required('Description is required'), diff --git a/packages/apps/job-launcher/client/src/types/index.ts b/packages/apps/job-launcher/client/src/types/index.ts index 0ce48dc02e..da10a02a63 100644 --- a/packages/apps/job-launcher/client/src/types/index.ts +++ b/packages/apps/job-launcher/client/src/types/index.ts @@ -74,6 +74,8 @@ export enum JobType { export enum CvatJobType { IMAGE_POINTS = 'IMAGE_POINTS', IMAGE_BOXES = 'IMAGE_BOXES', + IMAGE_BOXES_FROM_POINTS = 'IMAGE_BOXES_FROM_POINTS', + IMAGE_SKELETONS_FROM_BOXES = 'IMAGE_SKELETONS_FROM_BOXES', } export enum HCaptchaJobType { @@ -179,11 +181,23 @@ type CvatDataSource = { path: string; }; +type CvatData = { + dataset: CvatDataSource; + points?: CvatDataSource; + boxes?: CvatDataSource; +}; + +export type Label = { + name: string; + nodes?: string[]; + joints?: string[]; +}; + export type CvatRequest = { - labels: string[]; + labels: Label[]; type: CvatJobType; description: string; - data: CvatDataSource; + data: CvatData; groundTruth: CvatDataSource; userGuide: string; accuracyTarget: number; diff --git a/packages/apps/job-launcher/server/src/common/constants/index.ts b/packages/apps/job-launcher/server/src/common/constants/index.ts index 605e181602..d99c2036ac 100644 --- a/packages/apps/job-launcher/server/src/common/constants/index.ts +++ b/packages/apps/job-launcher/server/src/common/constants/index.ts @@ -31,6 +31,8 @@ export const HEADER_SIGNATURE_KEY = 'human-signature'; export const CVAT_JOB_TYPES = [ JobRequestType.IMAGE_BOXES, JobRequestType.IMAGE_POINTS, + JobRequestType.IMAGE_BOXES_FROM_POINTS, + JobRequestType.IMAGE_SKELETONS_FROM_BOXES, ]; export const CANCEL_JOB_STATUSES = [ diff --git a/packages/apps/job-launcher/server/src/common/enums/job.ts b/packages/apps/job-launcher/server/src/common/enums/job.ts index 4775e03b79..1ced28c009 100644 --- a/packages/apps/job-launcher/server/src/common/enums/job.ts +++ b/packages/apps/job-launcher/server/src/common/enums/job.ts @@ -21,6 +21,8 @@ export enum JobStatusFilter { export enum JobRequestType { IMAGE_POINTS = 'IMAGE_POINTS', IMAGE_BOXES = 'IMAGE_BOXES', + IMAGE_BOXES_FROM_POINTS = 'IMAGE_BOXES_FROM_POINTS', + IMAGE_SKELETONS_FROM_BOXES = 'IMAGE_SKELETONS_FROM_BOXES', HCAPTCHA = 'HCAPTCHA', FORTUNE = 'FORTUNE', } diff --git a/packages/apps/job-launcher/server/src/common/utils/storage.ts b/packages/apps/job-launcher/server/src/common/utils/storage.ts index eb58a61949..d304087fa9 100644 --- a/packages/apps/job-launcher/server/src/common/utils/storage.ts +++ b/packages/apps/job-launcher/server/src/common/utils/storage.ts @@ -13,7 +13,9 @@ export function generateBucketUrl( ): string { if ( (jobType === JobRequestType.IMAGE_BOXES || - jobType === JobRequestType.IMAGE_POINTS) && + jobType === JobRequestType.IMAGE_POINTS || + jobType === JobRequestType.IMAGE_BOXES_FROM_POINTS || + jobType === JobRequestType.IMAGE_SKELETONS_FROM_BOXES) && storageData.provider != StorageProviders.AWS ) { throw new BadRequestException(ErrorBucket.InvalidProvider); diff --git a/packages/apps/job-launcher/server/src/database/migrations/1711543013267-newCvatJobTypes.ts b/packages/apps/job-launcher/server/src/database/migrations/1711543013267-newCvatJobTypes.ts new file mode 100644 index 0000000000..177f698c76 --- /dev/null +++ b/packages/apps/job-launcher/server/src/database/migrations/1711543013267-newCvatJobTypes.ts @@ -0,0 +1,89 @@ +import { MigrationInterface, QueryRunner } from 'typeorm'; + +export class NewCvatJobTypes1711543013267 implements MigrationInterface { + name = 'NewCvatJobTypes1711543013267'; + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + ALTER TYPE "hmt"."webhook_event_type_enum" + RENAME TO "webhook_event_type_enum_old" + `); + await queryRunner.query(` + CREATE TYPE "hmt"."webhook_event_type_enum" AS ENUM( + 'escrow_created', + 'escrow_canceled', + 'escrow_completed', + 'task_creation_failed', + 'escrow_failed' + ) + `); + await queryRunner.query(` + ALTER TABLE "hmt"."webhook" + ALTER COLUMN "event_type" TYPE "hmt"."webhook_event_type_enum" USING "event_type"::"text"::"hmt"."webhook_event_type_enum" + `); + await queryRunner.query(` + DROP TYPE "hmt"."webhook_event_type_enum_old" + `); + await queryRunner.query(` + ALTER TYPE "hmt"."jobs_request_type_enum" + RENAME TO "jobs_request_type_enum_old" + `); + await queryRunner.query(` + CREATE TYPE "hmt"."jobs_request_type_enum" AS ENUM( + 'IMAGE_POINTS', + 'IMAGE_BOXES', + 'IMAGE_BOXES_FROM_POINTS', + 'IMAGE_SKELETONS_FROM_BOXES', + 'HCAPTCHA', + 'FORTUNE' + ) + `); + await queryRunner.query(` + ALTER TABLE "hmt"."jobs" + ALTER COLUMN "request_type" TYPE "hmt"."jobs_request_type_enum" USING "request_type"::"text"::"hmt"."jobs_request_type_enum" + `); + await queryRunner.query(` + DROP TYPE "hmt"."jobs_request_type_enum_old" + `); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + CREATE TYPE "hmt"."jobs_request_type_enum_old" AS ENUM( + 'IMAGE_POINTS', + 'IMAGE_BOXES', + 'HCAPTCHA', + 'FORTUNE' + ) + `); + await queryRunner.query(` + ALTER TABLE "hmt"."jobs" + ALTER COLUMN "request_type" TYPE "hmt"."jobs_request_type_enum_old" USING "request_type"::"text"::"hmt"."jobs_request_type_enum_old" + `); + await queryRunner.query(` + DROP TYPE "hmt"."jobs_request_type_enum" + `); + await queryRunner.query(` + ALTER TYPE "hmt"."jobs_request_type_enum_old" + RENAME TO "jobs_request_type_enum" + `); + await queryRunner.query(` + CREATE TYPE "hmt"."webhook_event_type_enum_old" AS ENUM( + 'escrow_created', + 'escrow_canceled', + 'task_creation_failed' + ) + `); + await queryRunner.query(` + ALTER TABLE "hmt"."webhook" + ALTER COLUMN "event_type" TYPE "hmt"."webhook_event_type_enum_old" USING "event_type"::"text"::"hmt"."webhook_event_type_enum_old" + `); + await queryRunner.query(` + DROP TYPE "hmt"."webhook_event_type_enum" + `); + await queryRunner.query(` + ALTER TYPE "hmt"."webhook_event_type_enum_old" + RENAME TO "webhook_event_type_enum" + `); + } +} diff --git a/packages/apps/job-launcher/server/src/modules/job/job.dto.ts b/packages/apps/job-launcher/server/src/modules/job/job.dto.ts index ea327adcef..811721f934 100644 --- a/packages/apps/job-launcher/server/src/modules/job/job.dto.ts +++ b/packages/apps/job-launcher/server/src/modules/job/job.dto.ts @@ -144,6 +144,22 @@ export class StorageDataDto { public path: string; } +export class CvatDataDto { + @ApiProperty() + @IsObject() + public dataset: StorageDataDto; + + @ApiPropertyOptional() + @IsObject() + @IsOptional() + public points?: StorageDataDto; + + @ApiPropertyOptional() + @IsObject() + @IsOptional() + public boxes?: StorageDataDto; +} + export class JobCvatDto extends JobDto { @ApiProperty({ name: 'requester_description' }) @IsString() @@ -151,12 +167,12 @@ export class JobCvatDto extends JobDto { @ApiProperty() @IsObject() - public data: StorageDataDto; + public data: CvatDataDto; @ApiProperty() @IsArray() @ArrayMinSize(1) - public labels: string[]; + public labels: Label[]; @ApiProperty({ name: 'min_quality' }) @IsNumber() @@ -382,75 +398,75 @@ export class FortuneManifestDto { } export class CvatData { - @ApiProperty() - @IsString() + @IsUrl() public data_url: string; + + @IsUrl() + @IsOptional() + public points_url?: string; + + @IsUrl() + @IsOptional() + public boxes_url?: string; } export class Label { @ApiProperty() @IsString() public name: string; + + @ApiPropertyOptional() + @IsArray() + @IsOptional() + public nodes?: string[]; + + @ApiPropertyOptional() + @IsArray() + @IsOptional() + public joints?: string[]; } export class Annotation { - @ApiProperty() @IsArray() public labels: Label[]; - @ApiProperty() @IsString() public description: string; - @ApiProperty() @IsString() public user_guide: string; - @ApiProperty({ enum: JobRequestType }) @IsEnum(JobRequestType) public type: JobRequestType; - @ApiProperty() @IsNumber() @IsPositive() public job_size: number; - - @ApiProperty() - @IsNumber() - @IsPositive() - public max_time: number; } export class Validation { - @ApiProperty() @IsNumber() @IsPositive() public min_quality: number; - @ApiProperty() @IsNumber() @IsPositive() public val_size: number; - @ApiProperty() @IsString() public gt_url: string; } export class CvatManifestDto { - @ApiProperty() @IsObject() public data: CvatData; - @ApiProperty() @IsObject() public annotation: Annotation; - @ApiProperty() @IsObject() public validation: Validation; - @ApiProperty() @IsString() public job_bounty: string; } diff --git a/packages/apps/job-launcher/server/src/modules/job/job.service.spec.ts b/packages/apps/job-launcher/server/src/modules/job/job.service.spec.ts index bb97db5102..b894c30415 100644 --- a/packages/apps/job-launcher/server/src/modules/job/job.service.spec.ts +++ b/packages/apps/job-launcher/server/src/modules/job/job.service.spec.ts @@ -65,6 +65,8 @@ import { MOCK_TRANSACTION_HASH, MOCK_USER_ID, MOCK_STORAGE_DATA, + MOCK_CVAT_DATA, + MOCK_CVAT_LABELS, MOCK_CVAT_JOB_SIZE, MOCK_CVAT_MAX_TIME, MOCK_CVAT_VAL_SIZE, @@ -88,6 +90,7 @@ import { JobCaptchaDto, CvatManifestDto, JobQuickLaunchDto, + CvatDataDto, } from './job.dto'; import { JobEntity } from './job.entity'; import { JobRepository } from './job.repository'; @@ -516,8 +519,8 @@ describe('JobService', () => { .mockResolvedValueOnce(jobBounty); const dto: JobCvatDto = { - data: MOCK_STORAGE_DATA, - labels: ['label1', 'label2'], + data: MOCK_CVAT_DATA, + labels: MOCK_CVAT_LABELS, requesterDescription: MOCK_REQUESTER_DESCRIPTION, userGuide: MOCK_FILE_URL, minQuality: 0.8, @@ -545,7 +548,6 @@ describe('JobService', () => { user_guide: MOCK_FILE_URL, type: requestType, job_size: 1, - max_time: 300, }, validation: { min_quality: 0.8, @@ -946,8 +948,8 @@ describe('JobService', () => { const imageLabelBinaryJobDto: JobCvatDto = { chainId: MOCK_CHAIN_ID, - data: MOCK_STORAGE_DATA, - labels: ['cat', 'dog'], + data: MOCK_CVAT_DATA, + labels: MOCK_CVAT_LABELS, requesterDescription: MOCK_REQUESTER_DESCRIPTION, minQuality: 0.95, fundAmount: 10, @@ -1032,17 +1034,19 @@ describe('JobService', () => { const userBalance = 25; getUserBalanceMock.mockResolvedValue(userBalance); - const storageDataMock: StorageDataDto = { - provider: StorageProviders.GCS, - region: AWSRegions.EU_CENTRAL_1, - bucketName: 'bucket', - path: 'folder/test', + const storageDataMock: any = { + dataset: { + provider: StorageProviders.GCS, + region: AWSRegions.EU_CENTRAL_1, + bucketName: 'bucket', + path: 'folder/test', + }, }; const imageLabelBinaryJobDto: JobCvatDto = { chainId: MOCK_CHAIN_ID, data: storageDataMock, - labels: ['cat', 'dog'], + labels: MOCK_CVAT_LABELS, requesterDescription: MOCK_REQUESTER_DESCRIPTION, minQuality: 0.95, fundAmount: 10, @@ -1071,16 +1075,18 @@ describe('JobService', () => { getUserBalanceMock.mockResolvedValue(userBalance); const storageDataMock: any = { - provider: StorageProviders.AWS, - region: 'test-region', - bucketName: 'bucket', - path: 'folder/test', + dataset: { + provider: StorageProviders.AWS, + region: 'test-region', + bucketName: 'bucket', + path: 'folder/test', + }, }; const imageLabelBinaryJobDto: JobCvatDto = { chainId: MOCK_CHAIN_ID, data: storageDataMock, - labels: ['cat', 'dog'], + labels: MOCK_CVAT_LABELS, requesterDescription: MOCK_REQUESTER_DESCRIPTION, minQuality: 0.95, fundAmount: 10, @@ -1109,15 +1115,17 @@ describe('JobService', () => { getUserBalanceMock.mockResolvedValue(userBalance); const storageDataMock: any = { - provider: StorageProviders.AWS, - bucketName: 'bucket', - path: 'folder/test', + dataset: { + provider: StorageProviders.AWS, + bucketName: 'bucket', + path: 'folder/test', + }, }; const imageLabelBinaryJobDto: JobCvatDto = { chainId: MOCK_CHAIN_ID, data: storageDataMock, - labels: ['cat', 'dog'], + labels: MOCK_CVAT_LABELS, requesterDescription: MOCK_REQUESTER_DESCRIPTION, minQuality: 0.95, fundAmount: 10, @@ -1146,15 +1154,17 @@ describe('JobService', () => { getUserBalanceMock.mockResolvedValue(userBalance); const storageDataMock: any = { - provider: StorageProviders.AWS, - region: AWSRegions.EU_CENTRAL_1, - path: 'folder/test', + dataset: { + provider: StorageProviders.AWS, + region: AWSRegions.EU_CENTRAL_1, + path: 'folder/test', + }, }; const imageLabelBinaryJobDto: JobCvatDto = { chainId: MOCK_CHAIN_ID, data: storageDataMock, - labels: ['cat', 'dog'], + labels: MOCK_CVAT_LABELS, requesterDescription: MOCK_REQUESTER_DESCRIPTION, minQuality: 0.95, fundAmount: 10, @@ -1895,7 +1905,6 @@ describe('JobService', () => { user_guide: MOCK_FILE_URL, type: JobRequestType.IMAGE_POINTS, job_size: 10, - max_time: 300, }, validation: { min_quality: 1, diff --git a/packages/apps/job-launcher/server/src/modules/job/job.service.ts b/packages/apps/job-launcher/server/src/modules/job/job.service.ts index 5b2cb07de8..21de196a45 100644 --- a/packages/apps/job-launcher/server/src/modules/job/job.service.ts +++ b/packages/apps/job-launcher/server/src/modules/job/job.service.ts @@ -141,23 +141,27 @@ export class JobService { requestType: JobRequestType, tokenFundAmount: number, ): Promise { - const elementsCount = (await listObjectsInBucket(dto.data, requestType)) - .length; + const elementsCount = ( + await listObjectsInBucket(dto.data.dataset, requestType) + ).length; return { data: { - data_url: generateBucketUrl(dto.data, requestType), + data_url: generateBucketUrl(dto.data.dataset, requestType), + ...(dto.data.points && { + points_url: generateBucketUrl(dto.data.points, requestType), + }), + ...(dto.data.boxes && { + boxes_url: generateBucketUrl(dto.data.boxes, requestType), + }), }, annotation: { - labels: dto.labels.map((item) => ({ name: item })), + labels: dto.labels, description: dto.requesterDescription, user_guide: dto.userGuide, type: requestType, job_size: Number( this.configService.get(ConfigNames.CVAT_JOB_SIZE)!, ), - max_time: Number( - this.configService.get(ConfigNames.CVAT_MAX_TIME)!, - ), }, validation: { min_quality: dto.minQuality, @@ -454,6 +458,22 @@ export class JobService { fundAmount: number, ) => this.createCvatManifest(dto, requestType, fundAmount), }, + [JobRequestType.IMAGE_BOXES_FROM_POINTS]: { + calculateFundAmount: async (dto: JobCvatDto) => dto.fundAmount, + createManifest: ( + dto: JobCvatDto, + requestType: JobRequestType, + fundAmount: number, + ) => this.createCvatManifest(dto, requestType, fundAmount), + }, + [JobRequestType.IMAGE_SKELETONS_FROM_BOXES]: { + calculateFundAmount: async (dto: JobCvatDto) => dto.fundAmount, + createManifest: ( + dto: JobCvatDto, + requestType: JobRequestType, + fundAmount: number, + ) => this.createCvatManifest(dto, requestType, fundAmount), + }, }; private createEscrowSpecificActions: Record = { @@ -469,6 +489,12 @@ export class JobService { [JobRequestType.IMAGE_POINTS]: { getTrustedHandlers: () => [], }, + [JobRequestType.IMAGE_BOXES_FROM_POINTS]: { + getTrustedHandlers: () => [], + }, + [JobRequestType.IMAGE_SKELETONS_FROM_BOXES]: { + getTrustedHandlers: () => [], + }, }; private getOraclesSpecificActions: Record = { @@ -532,6 +558,36 @@ export class JobService { return { exchangeOracle, recordingOracle, reputationOracle }; }, }, + [JobRequestType.IMAGE_BOXES_FROM_POINTS]: { + getOracleAddresses: (): OracleAddresses => { + const exchangeOracle = this.configService.get( + ConfigNames.CVAT_EXCHANGE_ORACLE_ADDRESS, + )!; + const recordingOracle = this.configService.get( + ConfigNames.CVAT_RECORDING_ORACLE_ADDRESS, + )!; + const reputationOracle = this.configService.get( + ConfigNames.REPUTATION_ORACLE_ADDRESS, + )!; + + return { exchangeOracle, recordingOracle, reputationOracle }; + }, + }, + [JobRequestType.IMAGE_SKELETONS_FROM_BOXES]: { + getOracleAddresses: (): OracleAddresses => { + const exchangeOracle = this.configService.get( + ConfigNames.CVAT_EXCHANGE_ORACLE_ADDRESS, + )!; + const recordingOracle = this.configService.get( + ConfigNames.CVAT_RECORDING_ORACLE_ADDRESS, + )!; + const reputationOracle = this.configService.get( + ConfigNames.REPUTATION_ORACLE_ADDRESS, + )!; + + return { exchangeOracle, recordingOracle, reputationOracle }; + }, + }, }; public async createJob( diff --git a/packages/apps/job-launcher/server/test/constants.ts b/packages/apps/job-launcher/server/test/constants.ts index 94b8ff6247..b7451961aa 100644 --- a/packages/apps/job-launcher/server/test/constants.ts +++ b/packages/apps/job-launcher/server/test/constants.ts @@ -1,6 +1,11 @@ import { AWSRegions, StorageProviders } from '../src/common/enums/storage'; import { JobRequestType } from '../src/common/enums/job'; -import { FortuneManifestDto, StorageDataDto } from '../src/modules/job/job.dto'; +import { + FortuneManifestDto, + StorageDataDto, + CvatDataDto, + Label, +} from '../src/modules/job/job.dto'; export const MOCK_REQUESTER_TITLE = 'Mock job title'; export const MOCK_REQUESTER_DESCRIPTION = 'Mock job description'; @@ -113,5 +118,16 @@ export const MOCK_STORAGE_DATA: StorageDataDto = { bucketName: 'bucket', path: 'folder/test', }; +export const MOCK_CVAT_DATA: CvatDataDto = { + dataset: MOCK_STORAGE_DATA, +}; +export const MOCK_CVAT_LABELS: Label[] = [ + { + name: 'label1', + }, + { + name: 'label2', + }, +]; export const MOCK_BUCKET_FILE = 'https://bucket.s3.eu-central-1.amazonaws.com/folder/test'; From a7355b6d9e2735f44aadf77a5fecbe6369086420 Mon Sep 17 00:00:00 2001 From: Maxim Zhiltsov Date: Thu, 28 Mar 2024 15:27:31 +0300 Subject: [PATCH 11/66] Merge CVAT milestone 2 changes (#1746) * Fix job event handling when no assignments exist * Implement boxes from points task creation * Refactor cloud storage api * Implement job downloading * Estimate max bbox * Little refactoring * Copy some shared code from excor to recor * Refactor task creation and export * Implement boxes from points validation * Integrate SDK updates * Fix escrow access * [Exchange/Recoring oracles] Add GCS support * Remove local dev mocks * Remove some extra changes * Update tests * Update some tests * Remove extra launcher change * Refactor code * Add google-cloud-storage dependency * Fix exception classes * Set env variables in docker-compose.test.yml * [CVAT] Points to boxes task (#1560) * Fix job event handling when no assignments exist * Implement boxes from points task creation * Refactor cloud storage api * Implement job downloading * Estimate max bbox * Little refactoring * Copy some shared code from excor to recor * Refactor task creation and export * Implement boxes from points validation * Integrate SDK updates * Fix escrow access * Remove local dev mocks * Remove some extra changes * Update tests * Update some tests * Remove extra launcher change * Use virtual hosted bucket style * Fix creating CS in CVAT * Fix tests * Add basic implementation for skeletons from boxes * Refactor and fix some errors * Implement job uploading * Implement downloading * Fix extra gt boxes in merged results from excor * Implement skeleton matching * Fix labels in merged annotations * Update project events handling in excor * Improve oks sigma comment * Remove local testing assets * Fix label mapping * Add extra skeleton validations in manifest * Update .env template * Update several comments * Fix failing tests * Update enum name * Add more annotation validations for skeletons * Extend input annotations validation * Fix quality computation, refactor * Unify assignment accuracy checks between different job types * Refactor GT downloading for validation * Add gcs support - fixes (#1) * Update code formatting * Update poetry lock * Refactor * Use dict for gcs file contents * Update class name * Add backward compatibility for data bucket env var * Remove extra changes * Remove extra import * Fix enum name * Update bucket access info parsing * Fix error type in cloud provider parsing * Update tests * Add job annotation mode param into assignment links * Add CD trigger for experimental cvat oracles * Update code formatting * Fix test * Format code * Fix bucket uses * Fix result sending * Remove extra changes * Fix extra code * Remove unused code * Align enum naming convention * Use relative paths in bucket in task creation * Improve error messages * Improve skeleton and bbox validation * Fix escrow manifest downloading * Update tests * Fix tests * Implement general image bans * Fix import * Make default gt ban threshold more strict * Fix comparison for absent points * Fix comparison for omitted points in jobs * Finish escrows with too many unverifiable assignments * Check if an increased healthcheck interval will fix unhealthy container (#1700) * Fix validations for boxes from points task creation * Clean code * disable roi estimation for unreliable cases * Fix manifest parsing * Remove m0 launcher stubs * Remove m0 rep or stubs * Make max assignment time optional in manifest * Make label type fully optional in manifest * Fix test * Enable and fix a disabled m1 task creation test * Fix RecOr tests --------- Co-authored-by: maya Co-authored-by: Ivan Co-authored-by: Dzeranov --- .../workflows/cd-cvat-exchange-oracle.yaml | 4 +- .../workflows/cd-cvat-recording-oracle.yaml | 4 +- .../c1e74c227cfe_non_unique_escrows.py | 29 + .../exchange-oracle/docker-compose.test.yml | 1 + .../examples/cvat/exchange-oracle/poetry.lock | 266 +- .../cvat/exchange-oracle/pyproject.toml | 1 + .../cvat/exchange-oracle/src/.env.template | 11 +- .../cvat/exchange-oracle/src/chain/escrow.py | 5 + .../src/core/annotation_meta.py | 2 +- .../cvat/exchange-oracle/src/core/config.py | 68 +- .../cvat/exchange-oracle/src/core/manifest.py | 143 +- .../exchange-oracle/src/core/oracle_events.py | 30 +- .../cvat/exchange-oracle/src/core/storage.py | 10 + .../src/core/tasks/__init__.py | 0 .../src/core/tasks/boxes_from_points.py | 92 + .../src/core/tasks/skeletons_from_boxes.py | 127 + .../cvat/exchange-oracle/src/core/types.py | 23 +- .../exchange-oracle/src/crons/__init__.py | 6 +- .../crons/process_job_launcher_webhooks.py | 15 +- .../process_recording_oracle_webhooks.py | 112 +- .../src/crons/state_trackers.py | 163 +- .../exchange-oracle/src/cvat/api_calls.py | 66 +- .../cvat/exchange-oracle/src/cvat/tasks.py | 210 -- .../exchange-oracle/src/endpoints/webhook.py | 3 +- .../src/handlers/annotation.py | 217 -- .../src/handlers/completed_escrows.py | 352 +++ .../src/handlers/cvat_events.py | 8 +- .../src/handlers/job_creation.py | 2225 +++++++++++++++++ .../src/handlers/job_export.py | 601 +++++ .../cvat/exchange-oracle/src/models/cvat.py | 20 +- .../exchange-oracle/src/schemas/exchange.py | 6 +- .../exchange-oracle/src/schemas/webhook.py | 4 +- .../src/services/cloud/__init__.py | 16 +- .../src/services/cloud/client.py | 86 +- .../exchange-oracle/src/services/cloud/gcs.py | 70 + .../exchange-oracle/src/services/cloud/s3.py | 71 + .../src/services/cloud/types.py | 187 ++ .../src/services/cloud/utils.py | 44 + .../cvat/exchange-oracle/src/services/cvat.py | 92 +- .../exchange-oracle/src/services/exchange.py | 22 +- .../exchange-oracle/src/services/webhook.py | 14 +- .../exchange-oracle/src/utils/annotations.py | 388 +++ .../exchange-oracle/src/utils/assignments.py | 30 +- .../src/utils/cloud_storage.py | 64 - .../cvat/exchange-oracle/src/utils/logging.py | 6 + .../exchange-oracle/src/utils/webhooks.py | 3 + .../tests/api/test_cvat_webhook_api.py | 6 +- .../tests/api/test_exchange_api.py | 4 +- .../tests/api/test_webhook_api.py | 8 +- .../tests/integration/chain/test_escrow.py | 34 +- .../tests/integration/chain/test_kvstore.py | 32 +- .../state_trackers/test_track_assignments.py | 16 +- ...ons.py => test_track_completed_escrows.py} | 110 +- .../test_track_completed_projects.py | 12 +- .../test_track_completed_tasks.py | 14 +- .../test_track_task_creation.py | 61 +- .../test_process_job_launcher_webhooks.py | 109 +- .../test_process_recording_oracle_webhooks.py | 76 +- .../tests/integration/services/test_cvat.py | 88 +- .../integration/services/test_exchange.py | 8 +- .../integration/services/test_webhook.py | 44 +- .../exchange-oracle/tests/utils/constants.py | 3 +- .../exchange-oracle/tests/utils/db_helper.py | 6 +- .../versions/a0c5c3a4c13f_add_gt_stats.py | 36 + .../recording-oracle/docker-compose.test.yml | 17 +- .../cvat/recording-oracle/poetry.lock | 266 +- .../cvat/recording-oracle/pyproject.toml | 1 + .../cvat/recording-oracle/src/.env.template | 14 +- .../src/core/annotation_meta.py | 2 +- .../cvat/recording-oracle/src/core/config.py | 117 +- .../recording-oracle/src/core/manifest.py | 144 +- .../src/core/oracle_events.py | 18 +- .../cvat/recording-oracle/src/core/storage.py | 10 + .../src/core/tasks/__init__.py | 0 .../src/core/tasks/boxes_from_points.py | 92 + .../src/core/tasks/skeletons_from_boxes.py | 126 + .../cvat/recording-oracle/src/core/types.py | 13 +- .../src/core/validation_errors.py | 15 + .../src/core/validation_results.py | 17 + .../crons/process_exchange_oracle_webhooks.py | 159 +- .../process_reputation_oracle_webhooks.py | 11 - .../handlers/process_intermediate_results.py | 982 +++++++- .../src/handlers/validation.py | 224 ++ .../recording-oracle/src/models/validation.py | 19 + .../recording-oracle/src/schemas/agreement.py | 35 - .../recording-oracle/src/schemas/webhook.py | 4 +- .../src/services/cloud/__init__.py | 16 +- .../src/services/cloud/client.py | 86 +- .../src/services/cloud/gcs.py | 70 + .../recording-oracle/src/services/cloud/s3.py | 71 + .../src/services/cloud/types.py | 187 ++ .../src/services/cloud/utils.py | 44 + .../src/services/validation.py | 42 +- .../recording-oracle/src/services/webhook.py | 14 +- .../recording-oracle/src/utils/annotations.py | 206 ++ .../recording-oracle/src/utils/assignments.py | 6 - .../src/utils/cloud_storage.py | 64 - .../recording-oracle/src/utils/logging.py | 6 + .../recording-oracle/src/utils/storage.py | 5 - .../recording-oracle/src/utils/webhooks.py | 3 + .../src/validation/annotation_matching.py | 4 +- .../src/validation/dataset_comparison.py | 409 ++- .../tests/integration/chain/test_escrow.py | 36 +- .../tests/integration/chain/test_kvstore.py | 26 +- .../test_process_exchange_oracle_webhooks.py | 8 +- ...test_process_reputation_oracle_webhooks.py | 14 +- .../integration/endpoints/test_webhook.py | 8 +- .../services/cloud/test_client_service.py | 43 +- .../services/test_webhook_service.py | 4 +- .../recording-oracle/tests/utils/constants.py | 3 +- .../tests/utils/setup_escrow.py | 8 +- 111 files changed, 8328 insertions(+), 1925 deletions(-) create mode 100644 packages/examples/cvat/exchange-oracle/alembic/versions/c1e74c227cfe_non_unique_escrows.py create mode 100644 packages/examples/cvat/exchange-oracle/src/core/storage.py create mode 100644 packages/examples/cvat/exchange-oracle/src/core/tasks/__init__.py create mode 100644 packages/examples/cvat/exchange-oracle/src/core/tasks/boxes_from_points.py create mode 100644 packages/examples/cvat/exchange-oracle/src/core/tasks/skeletons_from_boxes.py delete mode 100644 packages/examples/cvat/exchange-oracle/src/cvat/tasks.py delete mode 100644 packages/examples/cvat/exchange-oracle/src/handlers/annotation.py create mode 100644 packages/examples/cvat/exchange-oracle/src/handlers/completed_escrows.py create mode 100644 packages/examples/cvat/exchange-oracle/src/handlers/job_creation.py create mode 100644 packages/examples/cvat/exchange-oracle/src/handlers/job_export.py create mode 100644 packages/examples/cvat/exchange-oracle/src/services/cloud/gcs.py create mode 100644 packages/examples/cvat/exchange-oracle/src/services/cloud/s3.py create mode 100644 packages/examples/cvat/exchange-oracle/src/services/cloud/types.py create mode 100644 packages/examples/cvat/exchange-oracle/src/services/cloud/utils.py create mode 100644 packages/examples/cvat/exchange-oracle/src/utils/annotations.py delete mode 100644 packages/examples/cvat/exchange-oracle/src/utils/cloud_storage.py rename packages/examples/cvat/exchange-oracle/tests/integration/cron/state_trackers/{test_retrieve_annotations.py => test_track_completed_escrows.py} (71%) create mode 100644 packages/examples/cvat/recording-oracle/alembic/versions/a0c5c3a4c13f_add_gt_stats.py create mode 100644 packages/examples/cvat/recording-oracle/src/core/storage.py create mode 100644 packages/examples/cvat/recording-oracle/src/core/tasks/__init__.py create mode 100644 packages/examples/cvat/recording-oracle/src/core/tasks/boxes_from_points.py create mode 100644 packages/examples/cvat/recording-oracle/src/core/tasks/skeletons_from_boxes.py create mode 100644 packages/examples/cvat/recording-oracle/src/core/validation_errors.py create mode 100644 packages/examples/cvat/recording-oracle/src/core/validation_results.py create mode 100644 packages/examples/cvat/recording-oracle/src/handlers/validation.py delete mode 100644 packages/examples/cvat/recording-oracle/src/schemas/agreement.py create mode 100644 packages/examples/cvat/recording-oracle/src/services/cloud/gcs.py create mode 100644 packages/examples/cvat/recording-oracle/src/services/cloud/s3.py create mode 100644 packages/examples/cvat/recording-oracle/src/services/cloud/types.py create mode 100644 packages/examples/cvat/recording-oracle/src/services/cloud/utils.py create mode 100644 packages/examples/cvat/recording-oracle/src/utils/annotations.py delete mode 100644 packages/examples/cvat/recording-oracle/src/utils/cloud_storage.py delete mode 100644 packages/examples/cvat/recording-oracle/src/utils/storage.py diff --git a/.github/workflows/cd-cvat-exchange-oracle.yaml b/.github/workflows/cd-cvat-exchange-oracle.yaml index 4273e6bb43..a1f2679eec 100644 --- a/.github/workflows/cd-cvat-exchange-oracle.yaml +++ b/.github/workflows/cd-cvat-exchange-oracle.yaml @@ -2,11 +2,11 @@ name: Deploy CVAT Exchange Oracle on: push: - branches: [ develop, main ] + branches: [ develop, main, cvat-milestone-2 ] paths: - 'packages/examples/cvat/exchange-oracle/**' pull_request: - branches: [ develop, main ] + branches: [ develop, main, cvat-milestone-2 ] paths: - 'packages/examples/cvat/exchange-oracle/**' workflow_dispatch: diff --git a/.github/workflows/cd-cvat-recording-oracle.yaml b/.github/workflows/cd-cvat-recording-oracle.yaml index dbf8d96b21..487c51876b 100644 --- a/.github/workflows/cd-cvat-recording-oracle.yaml +++ b/.github/workflows/cd-cvat-recording-oracle.yaml @@ -2,11 +2,11 @@ name: Deploy CVAT Recording Oracle on: push: - branches: [ develop, main ] + branches: [ develop, main, cvat-milestone-2 ] paths: - 'packages/examples/cvat/recording-oracle/**' pull_request: - branches: [ develop, main ] + branches: [ develop, main, cvat-milestone-2 ] paths: - 'packages/examples/cvat/recording-oracle/**' workflow_dispatch: diff --git a/packages/examples/cvat/exchange-oracle/alembic/versions/c1e74c227cfe_non_unique_escrows.py b/packages/examples/cvat/exchange-oracle/alembic/versions/c1e74c227cfe_non_unique_escrows.py new file mode 100644 index 0000000000..f2ea93a456 --- /dev/null +++ b/packages/examples/cvat/exchange-oracle/alembic/versions/c1e74c227cfe_non_unique_escrows.py @@ -0,0 +1,29 @@ +"""non-unique-escrows + +Revision ID: c1e74c227cfe +Revises: 16ecc586d685 +Create Date: 2024-02-05 22:54:42.478270 + +""" +from alembic import op +import sqlalchemy as sa +import sqlalchemy_utils + + +# revision identifiers, used by Alembic. +revision = 'c1e74c227cfe' +down_revision = '16ecc586d685' +branch_labels = None +depends_on = None + + +def upgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + op.drop_constraint('projects_escrow_address_key', 'projects', type_='unique') + # ### end Alembic commands ### + + +def downgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + op.create_unique_constraint('projects_escrow_address_key', 'projects', ['escrow_address']) + # ### end Alembic commands ### diff --git a/packages/examples/cvat/exchange-oracle/docker-compose.test.yml b/packages/examples/cvat/exchange-oracle/docker-compose.test.yml index cc45d1bfd7..8e02741e6e 100644 --- a/packages/examples/cvat/exchange-oracle/docker-compose.test.yml +++ b/packages/examples/cvat/exchange-oracle/docker-compose.test.yml @@ -70,6 +70,7 @@ services: STORAGE_SECRET_KEY: 'devdevdev' STORAGE_RESULTS_BUCKET_NAME: 'results' STORAGE_USE_SSL: False + STORAGE_PROVIDER: 'aws' ENABLE_CUSTOM_CLOUD_HOST: Yes depends_on: postgres: diff --git a/packages/examples/cvat/exchange-oracle/poetry.lock b/packages/examples/cvat/exchange-oracle/poetry.lock index 9017ffb635..9f09bf8ce1 100644 --- a/packages/examples/cvat/exchange-oracle/poetry.lock +++ b/packages/examples/cvat/exchange-oracle/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.6.1 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.7.1 and should not be changed by hand. [[package]] name = "aiohttp" @@ -481,6 +481,17 @@ urllib3 = {version = ">=1.25.4,<2.1", markers = "python_version >= \"3.10\""} [package.extras] crt = ["awscrt (==0.19.19)"] +[[package]] +name = "cachetools" +version = "5.3.2" +description = "Extensible memoizing collections and decorators" +optional = false +python-versions = ">=3.7" +files = [ + {file = "cachetools-5.3.2-py3-none-any.whl", hash = "sha256:861f35a13a451f94e301ce2bec7cac63e881232ccce7ed67fab9b5df4d3beaa1"}, + {file = "cachetools-5.3.2.tar.gz", hash = "sha256:086ee420196f7b2ab9ca2db2520aca326318b68fe5ba8bc4d49cca91add450f2"}, +] + [[package]] name = "certifi" version = "2023.11.17" @@ -1424,6 +1435,206 @@ files = [ {file = "future-0.18.3.tar.gz", hash = "sha256:34a17436ed1e96697a86f9de3d15a3b0be01d8bc8de9c1dffd59fb8234ed5307"}, ] +[[package]] +name = "google-api-core" +version = "2.17.1" +description = "Google API client core library" +optional = false +python-versions = ">=3.7" +files = [ + {file = "google-api-core-2.17.1.tar.gz", hash = "sha256:9df18a1f87ee0df0bc4eea2770ebc4228392d8cc4066655b320e2cfccb15db95"}, + {file = "google_api_core-2.17.1-py3-none-any.whl", hash = "sha256:610c5b90092c360736baccf17bd3efbcb30dd380e7a6dc28a71059edb8bd0d8e"}, +] + +[package.dependencies] +google-auth = ">=2.14.1,<3.0.dev0" +googleapis-common-protos = ">=1.56.2,<2.0.dev0" +protobuf = ">=3.19.5,<3.20.0 || >3.20.0,<3.20.1 || >3.20.1,<4.21.0 || >4.21.0,<4.21.1 || >4.21.1,<4.21.2 || >4.21.2,<4.21.3 || >4.21.3,<4.21.4 || >4.21.4,<4.21.5 || >4.21.5,<5.0.0.dev0" +requests = ">=2.18.0,<3.0.0.dev0" + +[package.extras] +grpc = ["grpcio (>=1.33.2,<2.0dev)", "grpcio (>=1.49.1,<2.0dev)", "grpcio-status (>=1.33.2,<2.0.dev0)", "grpcio-status (>=1.49.1,<2.0.dev0)"] +grpcgcp = ["grpcio-gcp (>=0.2.2,<1.0.dev0)"] +grpcio-gcp = ["grpcio-gcp (>=0.2.2,<1.0.dev0)"] + +[[package]] +name = "google-auth" +version = "2.28.0" +description = "Google Authentication Library" +optional = false +python-versions = ">=3.7" +files = [ + {file = "google-auth-2.28.0.tar.gz", hash = "sha256:3cfc1b6e4e64797584fb53fc9bd0b7afa9b7c0dba2004fa7dcc9349e58cc3195"}, + {file = "google_auth-2.28.0-py2.py3-none-any.whl", hash = "sha256:7634d29dcd1e101f5226a23cbc4a0c6cda6394253bf80e281d9c5c6797869c53"}, +] + +[package.dependencies] +cachetools = ">=2.0.0,<6.0" +pyasn1-modules = ">=0.2.1" +rsa = ">=3.1.4,<5" + +[package.extras] +aiohttp = ["aiohttp (>=3.6.2,<4.0.0.dev0)", "requests (>=2.20.0,<3.0.0.dev0)"] +enterprise-cert = ["cryptography (==36.0.2)", "pyopenssl (==22.0.0)"] +pyopenssl = ["cryptography (>=38.0.3)", "pyopenssl (>=20.0.0)"] +reauth = ["pyu2f (>=0.1.5)"] +requests = ["requests (>=2.20.0,<3.0.0.dev0)"] + +[[package]] +name = "google-cloud-core" +version = "2.4.1" +description = "Google Cloud API client core library" +optional = false +python-versions = ">=3.7" +files = [ + {file = "google-cloud-core-2.4.1.tar.gz", hash = "sha256:9b7749272a812bde58fff28868d0c5e2f585b82f37e09a1f6ed2d4d10f134073"}, + {file = "google_cloud_core-2.4.1-py2.py3-none-any.whl", hash = "sha256:a9e6a4422b9ac5c29f79a0ede9485473338e2ce78d91f2370c01e730eab22e61"}, +] + +[package.dependencies] +google-api-core = ">=1.31.6,<2.0.dev0 || >2.3.0,<3.0.0dev" +google-auth = ">=1.25.0,<3.0dev" + +[package.extras] +grpc = ["grpcio (>=1.38.0,<2.0dev)", "grpcio-status (>=1.38.0,<2.0.dev0)"] + +[[package]] +name = "google-cloud-storage" +version = "2.14.0" +description = "Google Cloud Storage API client library" +optional = false +python-versions = ">=3.7" +files = [ + {file = "google-cloud-storage-2.14.0.tar.gz", hash = "sha256:2d23fcf59b55e7b45336729c148bb1c464468c69d5efbaee30f7201dd90eb97e"}, + {file = "google_cloud_storage-2.14.0-py2.py3-none-any.whl", hash = "sha256:8641243bbf2a2042c16a6399551fbb13f062cbc9a2de38d6c0bb5426962e9dbd"}, +] + +[package.dependencies] +google-api-core = ">=1.31.5,<2.0.dev0 || >2.3.0,<3.0.0dev" +google-auth = ">=2.23.3,<3.0dev" +google-cloud-core = ">=2.3.0,<3.0dev" +google-crc32c = ">=1.0,<2.0dev" +google-resumable-media = ">=2.6.0" +requests = ">=2.18.0,<3.0.0dev" + +[package.extras] +protobuf = ["protobuf (<5.0.0dev)"] + +[[package]] +name = "google-crc32c" +version = "1.5.0" +description = "A python wrapper of the C library 'Google CRC32C'" +optional = false +python-versions = ">=3.7" +files = [ + {file = "google-crc32c-1.5.0.tar.gz", hash = "sha256:89284716bc6a5a415d4eaa11b1726d2d60a0cd12aadf5439828353662ede9dd7"}, + {file = "google_crc32c-1.5.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:596d1f98fc70232fcb6590c439f43b350cb762fb5d61ce7b0e9db4539654cc13"}, + {file = "google_crc32c-1.5.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:be82c3c8cfb15b30f36768797a640e800513793d6ae1724aaaafe5bf86f8f346"}, + {file = "google_crc32c-1.5.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:461665ff58895f508e2866824a47bdee72497b091c730071f2b7575d5762ab65"}, + {file = "google_crc32c-1.5.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e2096eddb4e7c7bdae4bd69ad364e55e07b8316653234a56552d9c988bd2d61b"}, + {file = "google_crc32c-1.5.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:116a7c3c616dd14a3de8c64a965828b197e5f2d121fedd2f8c5585c547e87b02"}, + {file = "google_crc32c-1.5.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:5829b792bf5822fd0a6f6eb34c5f81dd074f01d570ed7f36aa101d6fc7a0a6e4"}, + {file = "google_crc32c-1.5.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:64e52e2b3970bd891309c113b54cf0e4384762c934d5ae56e283f9a0afcd953e"}, + {file = "google_crc32c-1.5.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:02ebb8bf46c13e36998aeaad1de9b48f4caf545e91d14041270d9dca767b780c"}, + {file = "google_crc32c-1.5.0-cp310-cp310-win32.whl", hash = "sha256:2e920d506ec85eb4ba50cd4228c2bec05642894d4c73c59b3a2fe20346bd00ee"}, + {file = "google_crc32c-1.5.0-cp310-cp310-win_amd64.whl", hash = "sha256:07eb3c611ce363c51a933bf6bd7f8e3878a51d124acfc89452a75120bc436289"}, + {file = "google_crc32c-1.5.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:cae0274952c079886567f3f4f685bcaf5708f0a23a5f5216fdab71f81a6c0273"}, + {file = "google_crc32c-1.5.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1034d91442ead5a95b5aaef90dbfaca8633b0247d1e41621d1e9f9db88c36298"}, + {file = "google_crc32c-1.5.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7c42c70cd1d362284289c6273adda4c6af8039a8ae12dc451dcd61cdabb8ab57"}, + {file = "google_crc32c-1.5.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8485b340a6a9e76c62a7dce3c98e5f102c9219f4cfbf896a00cf48caf078d438"}, + {file = "google_crc32c-1.5.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:77e2fd3057c9d78e225fa0a2160f96b64a824de17840351b26825b0848022906"}, + {file = "google_crc32c-1.5.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:f583edb943cf2e09c60441b910d6a20b4d9d626c75a36c8fcac01a6c96c01183"}, + {file = "google_crc32c-1.5.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:a1fd716e7a01f8e717490fbe2e431d2905ab8aa598b9b12f8d10abebb36b04dd"}, + {file = "google_crc32c-1.5.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:72218785ce41b9cfd2fc1d6a017dc1ff7acfc4c17d01053265c41a2c0cc39b8c"}, + {file = "google_crc32c-1.5.0-cp311-cp311-win32.whl", hash = "sha256:66741ef4ee08ea0b2cc3c86916ab66b6aef03768525627fd6a1b34968b4e3709"}, + {file = "google_crc32c-1.5.0-cp311-cp311-win_amd64.whl", hash = "sha256:ba1eb1843304b1e5537e1fca632fa894d6f6deca8d6389636ee5b4797affb968"}, + {file = "google_crc32c-1.5.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:98cb4d057f285bd80d8778ebc4fde6b4d509ac3f331758fb1528b733215443ae"}, + {file = "google_crc32c-1.5.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fd8536e902db7e365f49e7d9029283403974ccf29b13fc7028b97e2295b33556"}, + {file = "google_crc32c-1.5.0-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:19e0a019d2c4dcc5e598cd4a4bc7b008546b0358bd322537c74ad47a5386884f"}, + {file = "google_crc32c-1.5.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:02c65b9817512edc6a4ae7c7e987fea799d2e0ee40c53ec573a692bee24de876"}, + {file = "google_crc32c-1.5.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:6ac08d24c1f16bd2bf5eca8eaf8304812f44af5cfe5062006ec676e7e1d50afc"}, + {file = "google_crc32c-1.5.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:3359fc442a743e870f4588fcf5dcbc1bf929df1fad8fb9905cd94e5edb02e84c"}, + {file = "google_crc32c-1.5.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:1e986b206dae4476f41bcec1faa057851f3889503a70e1bdb2378d406223994a"}, + {file = "google_crc32c-1.5.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:de06adc872bcd8c2a4e0dc51250e9e65ef2ca91be023b9d13ebd67c2ba552e1e"}, + {file = "google_crc32c-1.5.0-cp37-cp37m-win32.whl", hash = "sha256:d3515f198eaa2f0ed49f8819d5732d70698c3fa37384146079b3799b97667a94"}, + {file = "google_crc32c-1.5.0-cp37-cp37m-win_amd64.whl", hash = "sha256:67b741654b851abafb7bc625b6d1cdd520a379074e64b6a128e3b688c3c04740"}, + {file = "google_crc32c-1.5.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:c02ec1c5856179f171e032a31d6f8bf84e5a75c45c33b2e20a3de353b266ebd8"}, + {file = "google_crc32c-1.5.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:edfedb64740750e1a3b16152620220f51d58ff1b4abceb339ca92e934775c27a"}, + {file = "google_crc32c-1.5.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:84e6e8cd997930fc66d5bb4fde61e2b62ba19d62b7abd7a69920406f9ecca946"}, + {file = "google_crc32c-1.5.0-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:024894d9d3cfbc5943f8f230e23950cd4906b2fe004c72e29b209420a1e6b05a"}, + {file = "google_crc32c-1.5.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:998679bf62b7fb599d2878aa3ed06b9ce688b8974893e7223c60db155f26bd8d"}, + {file = "google_crc32c-1.5.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:83c681c526a3439b5cf94f7420471705bbf96262f49a6fe546a6db5f687a3d4a"}, + {file = "google_crc32c-1.5.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:4c6fdd4fccbec90cc8a01fc00773fcd5fa28db683c116ee3cb35cd5da9ef6c37"}, + {file = "google_crc32c-1.5.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:5ae44e10a8e3407dbe138984f21e536583f2bba1be9491239f942c2464ac0894"}, + {file = "google_crc32c-1.5.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:37933ec6e693e51a5b07505bd05de57eee12f3e8c32b07da7e73669398e6630a"}, + {file = "google_crc32c-1.5.0-cp38-cp38-win32.whl", hash = "sha256:fe70e325aa68fa4b5edf7d1a4b6f691eb04bbccac0ace68e34820d283b5f80d4"}, + {file = "google_crc32c-1.5.0-cp38-cp38-win_amd64.whl", hash = "sha256:74dea7751d98034887dbd821b7aae3e1d36eda111d6ca36c206c44478035709c"}, + {file = "google_crc32c-1.5.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:c6c777a480337ac14f38564ac88ae82d4cd238bf293f0a22295b66eb89ffced7"}, + {file = "google_crc32c-1.5.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:759ce4851a4bb15ecabae28f4d2e18983c244eddd767f560165563bf9aefbc8d"}, + {file = "google_crc32c-1.5.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f13cae8cc389a440def0c8c52057f37359014ccbc9dc1f0827936bcd367c6100"}, + {file = "google_crc32c-1.5.0-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e560628513ed34759456a416bf86b54b2476c59144a9138165c9a1575801d0d9"}, + {file = "google_crc32c-1.5.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e1674e4307fa3024fc897ca774e9c7562c957af85df55efe2988ed9056dc4e57"}, + {file = "google_crc32c-1.5.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:278d2ed7c16cfc075c91378c4f47924c0625f5fc84b2d50d921b18b7975bd210"}, + {file = "google_crc32c-1.5.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:d5280312b9af0976231f9e317c20e4a61cd2f9629b7bfea6a693d1878a264ebd"}, + {file = "google_crc32c-1.5.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:8b87e1a59c38f275c0e3676fc2ab6d59eccecfd460be267ac360cc31f7bcde96"}, + {file = "google_crc32c-1.5.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:7c074fece789b5034b9b1404a1f8208fc2d4c6ce9decdd16e8220c5a793e6f61"}, + {file = "google_crc32c-1.5.0-cp39-cp39-win32.whl", hash = "sha256:7f57f14606cd1dd0f0de396e1e53824c371e9544a822648cd76c034d209b559c"}, + {file = "google_crc32c-1.5.0-cp39-cp39-win_amd64.whl", hash = "sha256:a2355cba1f4ad8b6988a4ca3feed5bff33f6af2d7f134852cf279c2aebfde541"}, + {file = "google_crc32c-1.5.0-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:f314013e7dcd5cf45ab1945d92e713eec788166262ae8deb2cfacd53def27325"}, + {file = "google_crc32c-1.5.0-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3b747a674c20a67343cb61d43fdd9207ce5da6a99f629c6e2541aa0e89215bcd"}, + {file = "google_crc32c-1.5.0-pp37-pypy37_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8f24ed114432de109aa9fd317278518a5af2d31ac2ea6b952b2f7782b43da091"}, + {file = "google_crc32c-1.5.0-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b8667b48e7a7ef66afba2c81e1094ef526388d35b873966d8a9a447974ed9178"}, + {file = "google_crc32c-1.5.0-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:1c7abdac90433b09bad6c43a43af253e688c9cfc1c86d332aed13f9a7c7f65e2"}, + {file = "google_crc32c-1.5.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:6f998db4e71b645350b9ac28a2167e6632c239963ca9da411523bb439c5c514d"}, + {file = "google_crc32c-1.5.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9c99616c853bb585301df6de07ca2cadad344fd1ada6d62bb30aec05219c45d2"}, + {file = "google_crc32c-1.5.0-pp38-pypy38_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2ad40e31093a4af319dadf503b2467ccdc8f67c72e4bcba97f8c10cb078207b5"}, + {file = "google_crc32c-1.5.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cd67cf24a553339d5062eff51013780a00d6f97a39ca062781d06b3a73b15462"}, + {file = "google_crc32c-1.5.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:398af5e3ba9cf768787eef45c803ff9614cc3e22a5b2f7d7ae116df8b11e3314"}, + {file = "google_crc32c-1.5.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:b1f8133c9a275df5613a451e73f36c2aea4fe13c5c8997e22cf355ebd7bd0728"}, + {file = "google_crc32c-1.5.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9ba053c5f50430a3fcfd36f75aff9caeba0440b2d076afdb79a318d6ca245f88"}, + {file = "google_crc32c-1.5.0-pp39-pypy39_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:272d3892a1e1a2dbc39cc5cde96834c236d5327e2122d3aaa19f6614531bb6eb"}, + {file = "google_crc32c-1.5.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:635f5d4dd18758a1fbd1049a8e8d2fee4ffed124462d837d1a02a0e009c3ab31"}, + {file = "google_crc32c-1.5.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:c672d99a345849301784604bfeaeba4db0c7aae50b95be04dd651fd2a7310b93"}, +] + +[package.extras] +testing = ["pytest"] + +[[package]] +name = "google-resumable-media" +version = "2.7.0" +description = "Utilities for Google Media Downloads and Resumable Uploads" +optional = false +python-versions = ">= 3.7" +files = [ + {file = "google-resumable-media-2.7.0.tar.gz", hash = "sha256:5f18f5fa9836f4b083162064a1c2c98c17239bfda9ca50ad970ccf905f3e625b"}, + {file = "google_resumable_media-2.7.0-py2.py3-none-any.whl", hash = "sha256:79543cfe433b63fd81c0844b7803aba1bb8950b47bedf7d980c38fa123937e08"}, +] + +[package.dependencies] +google-crc32c = ">=1.0,<2.0dev" + +[package.extras] +aiohttp = ["aiohttp (>=3.6.2,<4.0.0dev)", "google-auth (>=1.22.0,<2.0dev)"] +requests = ["requests (>=2.18.0,<3.0.0dev)"] + +[[package]] +name = "googleapis-common-protos" +version = "1.62.0" +description = "Common protobufs used in Google APIs" +optional = false +python-versions = ">=3.7" +files = [ + {file = "googleapis-common-protos-1.62.0.tar.gz", hash = "sha256:83f0ece9f94e5672cced82f592d2a5edf527a96ed1794f0bab36d5735c996277"}, + {file = "googleapis_common_protos-1.62.0-py2.py3-none-any.whl", hash = "sha256:4750113612205514f9f6aa4cb00d523a94f3e8c06c5ad2fee466387dc4875f07"}, +] + +[package.dependencies] +protobuf = ">=3.19.5,<3.20.0 || >3.20.0,<3.20.1 || >3.20.1,<4.21.1 || >4.21.1,<4.21.2 || >4.21.2,<4.21.3 || >4.21.3,<4.21.4 || >4.21.4,<4.21.5 || >4.21.5,<5.0.0.dev0" + +[package.extras] +grpc = ["grpcio (>=1.44.0,<2.0.0.dev0)"] + [[package]] name = "greenlet" version = "3.0.3" @@ -2743,6 +2954,8 @@ files = [ {file = "psycopg2-2.9.9-cp310-cp310-win_amd64.whl", hash = "sha256:426f9f29bde126913a20a96ff8ce7d73fd8a216cfb323b1f04da402d452853c3"}, {file = "psycopg2-2.9.9-cp311-cp311-win32.whl", hash = "sha256:ade01303ccf7ae12c356a5e10911c9e1c51136003a9a1d92f7aa9d010fb98372"}, {file = "psycopg2-2.9.9-cp311-cp311-win_amd64.whl", hash = "sha256:121081ea2e76729acfb0673ff33755e8703d45e926e416cb59bae3a86c6a4981"}, + {file = "psycopg2-2.9.9-cp312-cp312-win32.whl", hash = "sha256:d735786acc7dd25815e89cc4ad529a43af779db2e25aa7c626de864127e5a024"}, + {file = "psycopg2-2.9.9-cp312-cp312-win_amd64.whl", hash = "sha256:a7653d00b732afb6fc597e29c50ad28087dcb4fbfb28e86092277a559ae4e693"}, {file = "psycopg2-2.9.9-cp37-cp37m-win32.whl", hash = "sha256:5e0d98cade4f0e0304d7d6f25bbfbc5bd186e07b38eac65379309c4ca3193efa"}, {file = "psycopg2-2.9.9-cp37-cp37m-win_amd64.whl", hash = "sha256:7e2dacf8b009a1c1e843b5213a87f7c544b2b042476ed7755be813eaf4e8347a"}, {file = "psycopg2-2.9.9-cp38-cp38-win32.whl", hash = "sha256:ff432630e510709564c01dafdbe996cb552e0b9f3f065eb89bdce5bd31fabf4c"}, @@ -2763,6 +2976,20 @@ files = [ {file = "pyasn1-0.5.1.tar.gz", hash = "sha256:6d391a96e59b23130a5cfa74d6fd7f388dbbe26cc8f1edf39fdddf08d9d6676c"}, ] +[[package]] +name = "pyasn1-modules" +version = "0.3.0" +description = "A collection of ASN.1-based protocols modules" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7" +files = [ + {file = "pyasn1_modules-0.3.0-py2.py3-none-any.whl", hash = "sha256:d3ccd6ed470d9ffbc716be08bd90efbd44d0734bc9303818f7336070984a162d"}, + {file = "pyasn1_modules-0.3.0.tar.gz", hash = "sha256:5bd01446b736eb9d31512a30d46c1ac3395d676c6f3cafa4c03eb54b9925631c"}, +] + +[package.dependencies] +pyasn1 = ">=0.4.6,<0.6.0" + [[package]] name = "pycocotools" version = "2.0.7" @@ -3033,6 +3260,7 @@ files = [ {file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"}, {file = "PyYAML-6.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28"}, {file = "PyYAML-6.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9"}, + {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a08c6f0fe150303c1c6b71ebcd7213c2858041a7e01975da3a99aed1e7a378ef"}, {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0"}, {file = "PyYAML-6.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4"}, {file = "PyYAML-6.0.1-cp312-cp312-win32.whl", hash = "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54"}, @@ -3333,6 +3561,20 @@ files = [ {file = "rpds_py-0.17.1.tar.gz", hash = "sha256:0210b2668f24c078307260bf88bdac9d6f1093635df5123789bfee4d8d7fc8e7"}, ] +[[package]] +name = "rsa" +version = "4.9" +description = "Pure-Python RSA implementation" +optional = false +python-versions = ">=3.6,<4" +files = [ + {file = "rsa-4.9-py3-none-any.whl", hash = "sha256:90260d9058e514786967344d0ef75fa8727eed8a7d2e43ce9f4bcf1b536174f7"}, + {file = "rsa-4.9.tar.gz", hash = "sha256:e38464a49c6c85d7f1351b0126661487a7e0a14a50f1675ec50eb34d4f20ef21"}, +] + +[package.dependencies] +pyasn1 = ">=0.1.3" + [[package]] name = "ruamel-yaml" version = "0.18.5" @@ -3361,30 +3603,50 @@ files = [ {file = "ruamel.yaml.clib-0.2.8-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:b42169467c42b692c19cf539c38d4602069d8c1505e97b86387fcf7afb766e1d"}, {file = "ruamel.yaml.clib-0.2.8-cp310-cp310-macosx_13_0_arm64.whl", hash = "sha256:07238db9cbdf8fc1e9de2489a4f68474e70dffcb32232db7c08fa61ca0c7c462"}, {file = "ruamel.yaml.clib-0.2.8-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:fff3573c2db359f091e1589c3d7c5fc2f86f5bdb6f24252c2d8e539d4e45f412"}, + {file = "ruamel.yaml.clib-0.2.8-cp310-cp310-manylinux_2_24_aarch64.whl", hash = "sha256:aa2267c6a303eb483de8d02db2871afb5c5fc15618d894300b88958f729ad74f"}, + {file = "ruamel.yaml.clib-0.2.8-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:840f0c7f194986a63d2c2465ca63af8ccbbc90ab1c6001b1978f05119b5e7334"}, + {file = "ruamel.yaml.clib-0.2.8-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:024cfe1fc7c7f4e1aff4a81e718109e13409767e4f871443cbff3dba3578203d"}, {file = "ruamel.yaml.clib-0.2.8-cp310-cp310-win32.whl", hash = "sha256:c69212f63169ec1cfc9bb44723bf2917cbbd8f6191a00ef3410f5a7fe300722d"}, {file = "ruamel.yaml.clib-0.2.8-cp310-cp310-win_amd64.whl", hash = "sha256:cabddb8d8ead485e255fe80429f833172b4cadf99274db39abc080e068cbcc31"}, {file = "ruamel.yaml.clib-0.2.8-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:bef08cd86169d9eafb3ccb0a39edb11d8e25f3dae2b28f5c52fd997521133069"}, {file = "ruamel.yaml.clib-0.2.8-cp311-cp311-macosx_13_0_arm64.whl", hash = "sha256:b16420e621d26fdfa949a8b4b47ade8810c56002f5389970db4ddda51dbff248"}, {file = "ruamel.yaml.clib-0.2.8-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:25c515e350e5b739842fc3228d662413ef28f295791af5e5110b543cf0b57d9b"}, + {file = "ruamel.yaml.clib-0.2.8-cp311-cp311-manylinux_2_24_aarch64.whl", hash = "sha256:1707814f0d9791df063f8c19bb51b0d1278b8e9a2353abbb676c2f685dee6afe"}, + {file = "ruamel.yaml.clib-0.2.8-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:46d378daaac94f454b3a0e3d8d78cafd78a026b1d71443f4966c696b48a6d899"}, + {file = "ruamel.yaml.clib-0.2.8-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:09b055c05697b38ecacb7ac50bdab2240bfca1a0c4872b0fd309bb07dc9aa3a9"}, {file = "ruamel.yaml.clib-0.2.8-cp311-cp311-win32.whl", hash = "sha256:53a300ed9cea38cf5a2a9b069058137c2ca1ce658a874b79baceb8f892f915a7"}, {file = "ruamel.yaml.clib-0.2.8-cp311-cp311-win_amd64.whl", hash = "sha256:c2a72e9109ea74e511e29032f3b670835f8a59bbdc9ce692c5b4ed91ccf1eedb"}, {file = "ruamel.yaml.clib-0.2.8-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:ebc06178e8821efc9692ea7544aa5644217358490145629914d8020042c24aa1"}, {file = "ruamel.yaml.clib-0.2.8-cp312-cp312-macosx_13_0_arm64.whl", hash = "sha256:edaef1c1200c4b4cb914583150dcaa3bc30e592e907c01117c08b13a07255ec2"}, {file = "ruamel.yaml.clib-0.2.8-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d176b57452ab5b7028ac47e7b3cf644bcfdc8cacfecf7e71759f7f51a59e5c92"}, + {file = "ruamel.yaml.clib-0.2.8-cp312-cp312-manylinux_2_24_aarch64.whl", hash = "sha256:1dc67314e7e1086c9fdf2680b7b6c2be1c0d8e3a8279f2e993ca2a7545fecf62"}, + {file = "ruamel.yaml.clib-0.2.8-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:3213ece08ea033eb159ac52ae052a4899b56ecc124bb80020d9bbceeb50258e9"}, + {file = "ruamel.yaml.clib-0.2.8-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:aab7fd643f71d7946f2ee58cc88c9b7bfc97debd71dcc93e03e2d174628e7e2d"}, + {file = "ruamel.yaml.clib-0.2.8-cp312-cp312-win32.whl", hash = "sha256:5c365d91c88390c8d0a8545df0b5857172824b1c604e867161e6b3d59a827eaa"}, + {file = "ruamel.yaml.clib-0.2.8-cp312-cp312-win_amd64.whl", hash = "sha256:1758ce7d8e1a29d23de54a16ae867abd370f01b5a69e1a3ba75223eaa3ca1a1b"}, {file = "ruamel.yaml.clib-0.2.8-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:a5aa27bad2bb83670b71683aae140a1f52b0857a2deff56ad3f6c13a017a26ed"}, {file = "ruamel.yaml.clib-0.2.8-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:c58ecd827313af6864893e7af0a3bb85fd529f862b6adbefe14643947cfe2942"}, {file = "ruamel.yaml.clib-0.2.8-cp37-cp37m-macosx_12_0_arm64.whl", hash = "sha256:f481f16baec5290e45aebdc2a5168ebc6d35189ae6fea7a58787613a25f6e875"}, + {file = "ruamel.yaml.clib-0.2.8-cp37-cp37m-manylinux_2_24_aarch64.whl", hash = "sha256:77159f5d5b5c14f7c34073862a6b7d34944075d9f93e681638f6d753606c6ce6"}, {file = "ruamel.yaml.clib-0.2.8-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:7f67a1ee819dc4562d444bbafb135832b0b909f81cc90f7aa00260968c9ca1b3"}, + {file = "ruamel.yaml.clib-0.2.8-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:4ecbf9c3e19f9562c7fdd462e8d18dd902a47ca046a2e64dba80699f0b6c09b7"}, + {file = "ruamel.yaml.clib-0.2.8-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:87ea5ff66d8064301a154b3933ae406b0863402a799b16e4a1d24d9fbbcbe0d3"}, {file = "ruamel.yaml.clib-0.2.8-cp37-cp37m-win32.whl", hash = "sha256:75e1ed13e1f9de23c5607fe6bd1aeaae21e523b32d83bb33918245361e9cc51b"}, {file = "ruamel.yaml.clib-0.2.8-cp37-cp37m-win_amd64.whl", hash = "sha256:3f215c5daf6a9d7bbed4a0a4f760f3113b10e82ff4c5c44bec20a68c8014f675"}, {file = "ruamel.yaml.clib-0.2.8-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1b617618914cb00bf5c34d4357c37aa15183fa229b24767259657746c9077615"}, {file = "ruamel.yaml.clib-0.2.8-cp38-cp38-macosx_12_0_arm64.whl", hash = "sha256:a6a9ffd280b71ad062eae53ac1659ad86a17f59a0fdc7699fd9be40525153337"}, + {file = "ruamel.yaml.clib-0.2.8-cp38-cp38-manylinux_2_24_aarch64.whl", hash = "sha256:305889baa4043a09e5b76f8e2a51d4ffba44259f6b4c72dec8ca56207d9c6fe1"}, {file = "ruamel.yaml.clib-0.2.8-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:700e4ebb569e59e16a976857c8798aee258dceac7c7d6b50cab63e080058df91"}, + {file = "ruamel.yaml.clib-0.2.8-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:e2b4c44b60eadec492926a7270abb100ef9f72798e18743939bdbf037aab8c28"}, + {file = "ruamel.yaml.clib-0.2.8-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:e79e5db08739731b0ce4850bed599235d601701d5694c36570a99a0c5ca41a9d"}, {file = "ruamel.yaml.clib-0.2.8-cp38-cp38-win32.whl", hash = "sha256:955eae71ac26c1ab35924203fda6220f84dce57d6d7884f189743e2abe3a9fbe"}, {file = "ruamel.yaml.clib-0.2.8-cp38-cp38-win_amd64.whl", hash = "sha256:56f4252222c067b4ce51ae12cbac231bce32aee1d33fbfc9d17e5b8d6966c312"}, {file = "ruamel.yaml.clib-0.2.8-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:03d1162b6d1df1caa3a4bd27aa51ce17c9afc2046c31b0ad60a0a96ec22f8001"}, {file = "ruamel.yaml.clib-0.2.8-cp39-cp39-macosx_12_0_arm64.whl", hash = "sha256:bba64af9fa9cebe325a62fa398760f5c7206b215201b0ec825005f1b18b9bccf"}, + {file = "ruamel.yaml.clib-0.2.8-cp39-cp39-manylinux_2_24_aarch64.whl", hash = "sha256:a1a45e0bb052edf6a1d3a93baef85319733a888363938e1fc9924cb00c8df24c"}, {file = "ruamel.yaml.clib-0.2.8-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:da09ad1c359a728e112d60116f626cc9f29730ff3e0e7db72b9a2dbc2e4beed5"}, + {file = "ruamel.yaml.clib-0.2.8-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:184565012b60405d93838167f425713180b949e9d8dd0bbc7b49f074407c5a8b"}, + {file = "ruamel.yaml.clib-0.2.8-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a75879bacf2c987c003368cf14bed0ffe99e8e85acfa6c0bfffc21a090f16880"}, {file = "ruamel.yaml.clib-0.2.8-cp39-cp39-win32.whl", hash = "sha256:84b554931e932c46f94ab306913ad7e11bba988104c5cff26d90d03f68258cd5"}, {file = "ruamel.yaml.clib-0.2.8-cp39-cp39-win_amd64.whl", hash = "sha256:25ac8c08322002b06fa1d49d1646181f0b2c72f5cbc15a85e80b4c30a544bb15"}, {file = "ruamel.yaml.clib-0.2.8.tar.gz", hash = "sha256:beb2e0404003de9a4cab9753a8805a8fe9320ee6673136ed7f04255fe60bb512"}, @@ -4053,4 +4315,4 @@ multidict = ">=4.0" [metadata] lock-version = "2.0" python-versions = "^3.10,<3.13" -content-hash = "e35de373388feda7314836a75cf89eec394906188c027b11db1d21d96a623717" +content-hash = "a6a596c9671d180524562680d37c8f0c0fed2d9ae2891854329aedbef30d1aa5" diff --git a/packages/examples/cvat/exchange-oracle/pyproject.toml b/packages/examples/cvat/exchange-oracle/pyproject.toml index ee65c380ef..7581afb8c5 100644 --- a/packages/examples/cvat/exchange-oracle/pyproject.toml +++ b/packages/examples/cvat/exchange-oracle/pyproject.toml @@ -23,6 +23,7 @@ human-protocol-sdk = "^1.1.19" xmltodict = "^0.13.0" datumaro = {git = "https://github.com/cvat-ai/datumaro.git", rev = "ff83c00c2c1bc4b8fdfcc55067fcab0a9b5b6b11"} boto3 = "^1.28.33" +google-cloud-storage = "^2.14.0" [tool.poetry.group.dev.dependencies] diff --git a/packages/examples/cvat/exchange-oracle/src/.env.template b/packages/examples/cvat/exchange-oracle/src/.env.template index ee8dba1feb..0ff4616eb0 100644 --- a/packages/examples/cvat/exchange-oracle/src/.env.template +++ b/packages/examples/cvat/exchange-oracle/src/.env.template @@ -35,10 +35,12 @@ PROCESS_RECORDING_ORACLE_WEBHOOKS_CHUNK_SIZE= TRACK_COMPLETED_PROJECTS_INT= TRACK_COMPLETED_PROJECTS_CHUNK_SIZE= TRACK_COMPLETED_TASKS_INT= -RETRIEVE_ANNOTATIONS_INT= -RETRIEVE_ANNOTATIONS_CHUNK_SIZE= +TRACK_COMPLETED_ESCROWS_INT= +TRACK_COMPLETED_ESCROWS_CHUNK_SIZE= PROCESS_JOB_LAUNCHER_WEBHOOKS_INT= TRACK_CREATING_TASKS_INT= +REJECTED_PROJECTS_CHUNK_SIZE= +ACCEPTED_PROJECTS_CHUNK_SIZE= # CVAT Config @@ -50,12 +52,14 @@ CVAT_INCOMING_WEBHOOKS_URL= CVAT_WEBHOOK_SECRET= CVAT_ORG_SLUG= -# S3 Storage Config +# Storage Config (S3/GCS) +STORAGE_PROVIDER= STORAGE_ENDPOINT_URL= STORAGE_REGION= STORAGE_ACCESS_KEY= STORAGE_SECRET_KEY= +STORAGE_KEY_FILE_PATH= STORAGE_RESULTS_BUCKET_NAME= STORAGE_USE_SSL= @@ -73,5 +77,6 @@ HUMAN_APP_SIGNATURE= # Localhost +LOCALHOST_RECORDING_ORACLE_ADDRESS= LOCALHOST_RECORDING_ORACLE_URL= LOCALHOST_JOB_LAUNCHER_URL= \ No newline at end of file diff --git a/packages/examples/cvat/exchange-oracle/src/chain/escrow.py b/packages/examples/cvat/exchange-oracle/src/chain/escrow.py index 26ef9a7f84..8edb66c1e3 100644 --- a/packages/examples/cvat/exchange-oracle/src/chain/escrow.py +++ b/packages/examples/cvat/exchange-oracle/src/chain/escrow.py @@ -5,6 +5,8 @@ from human_protocol_sdk.escrow import EscrowData, EscrowUtils from human_protocol_sdk.storage import StorageUtils +from src.core.config import Config + def get_escrow(chain_id: int, escrow_address: str) -> EscrowData: escrow = EscrowUtils.get_escrow(ChainId(chain_id), escrow_address) @@ -51,4 +53,7 @@ def get_job_launcher_address(chain_id: int, escrow_address: str) -> str: def get_recording_oracle_address(chain_id: int, escrow_address: str) -> str: + if address := Config.localhost.recording_oracle_address: + return address + return get_escrow(chain_id, escrow_address).recording_oracle diff --git a/packages/examples/cvat/exchange-oracle/src/core/annotation_meta.py b/packages/examples/cvat/exchange-oracle/src/core/annotation_meta.py index 1337c5756e..e19a77efd5 100644 --- a/packages/examples/cvat/exchange-oracle/src/core/annotation_meta.py +++ b/packages/examples/cvat/exchange-oracle/src/core/annotation_meta.py @@ -3,7 +3,7 @@ from pydantic import BaseModel -ANNOTATION_METAFILE_NAME = "annotation_meta.json" +ANNOTATION_RESULTS_METAFILE_NAME = "annotation_meta.json" RESULTING_ANNOTATIONS_FILE = "resulting_annotations.zip" diff --git a/packages/examples/cvat/exchange-oracle/src/core/config.py b/packages/examples/cvat/exchange-oracle/src/core/config.py index ae22cbedaa..00c9226fb4 100644 --- a/packages/examples/cvat/exchange-oracle/src/core/config.py +++ b/packages/examples/cvat/exchange-oracle/src/core/config.py @@ -1,7 +1,9 @@ # pylint: disable=too-few-public-methods,missing-class-docstring """ Project configuration from env vars """ import os +from typing import ClassVar, Optional +from attrs.converters import to_bool from dotenv import load_dotenv from src.utils.logging import parse_log_level @@ -10,12 +12,6 @@ load_dotenv() -def str_to_bool(val: str) -> bool: - from distutils.util import strtobool - - return val is True or strtobool(val) - - class PostgresConfig: port = os.environ.get("PG_PORT", "5432") host = os.environ.get("PG_HOST", "0.0.0.0") @@ -53,6 +49,8 @@ class LocalhostConfig: addr = os.environ.get("LOCALHOST_MUMBAI_ADDR", "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266") job_launcher_url = os.environ.get("LOCALHOST_JOB_LAUNCHER_URL") + + recording_oracle_address = os.environ.get("LOCALHOST_RECORDING_ORACLE_ADDRESS") recording_oracle_url = os.environ.get("LOCALHOST_RECORDING_ORACLE_URL") @@ -75,8 +73,20 @@ class CronConfig: track_assignments_int = int(os.environ.get("TRACK_ASSIGNMENTS_INT", 5)) track_assignments_chunk_size = os.environ.get("TRACK_ASSIGNMENTS_CHUNK_SIZE", 10) - retrieve_annotations_int = int(os.environ.get("RETRIEVE_ANNOTATIONS_INT", 60)) - retrieve_annotations_chunk_size = os.environ.get("RETRIEVE_ANNOTATIONS_CHUNK_SIZE", 5) + track_completed_escrows_int = int( + # backward compatibility + os.environ.get( + "TRACK_COMPLETED_ESCROWS_INT", os.environ.get("RETRIEVE_ANNOTATIONS_INT", 60) + ) + ) + track_completed_escrows_chunk_size = os.environ.get( + # backward compatibility + "TRACK_COMPLETED_ESCROWS_CHUNK_SIZE", + os.environ.get("RETRIEVE_ANNOTATIONS_CHUNK_SIZE", 5), + ) + + rejected_projects_chunk_size = os.environ.get("REJECTED_PROJECTS_CHUNK_SIZE", 20) + accepted_projects_chunk_size = os.environ.get("ACCEPTED_PROJECTS_CHUNK_SIZE", 20) class CvatConfig: @@ -94,31 +104,43 @@ class CvatConfig: class StorageConfig: - endpoint_url = os.environ.get("STORAGE_ENDPOINT_URL", "storage.googleapis.com") - region = os.environ.get("STORAGE_REGION", "") - access_key = os.environ.get("STORAGE_ACCESS_KEY", "") - secret_key = os.environ.get("STORAGE_SECRET_KEY", "") - results_bucket_name = os.environ.get("STORAGE_RESULTS_BUCKET_NAME", "") - secure = str_to_bool(os.environ.get("STORAGE_USE_SSL", "true")) + provider: ClassVar[str] = os.environ["STORAGE_PROVIDER"].lower() + data_bucket_name: ClassVar[str] = ( + os.environ.get("STORAGE_RESULTS_BUCKET_NAME") # backward compatibility + or os.environ["STORAGE_BUCKET_NAME"] + ) + endpoint_url: ClassVar[str] = os.environ[ + "STORAGE_ENDPOINT_URL" + ] # TODO: probably should be optional + region: ClassVar[Optional[str]] = os.environ.get("STORAGE_REGION") + results_dir_suffix: ClassVar[str] = os.environ.get("STORAGE_RESULTS_DIR_SUFFIX", "-results") + secure: ClassVar[bool] = to_bool(os.environ.get("STORAGE_USE_SSL", "true")) + + # S3 specific attributes + access_key: ClassVar[Optional[str]] = os.environ.get("STORAGE_ACCESS_KEY") + secret_key: ClassVar[Optional[str]] = os.environ.get("STORAGE_SECRET_KEY") + + # GCS specific attributes + key_file_path: ClassVar[Optional[str]] = os.environ.get("STORAGE_KEY_FILE_PATH") @classmethod - def provider_endpoint_url(cls): - scheme = "https://" if cls.secure else "http://" + def get_scheme(cls) -> str: + return "https://" if cls.secure else "http://" - return f"{scheme}{cls.endpoint_url}" + @classmethod + def provider_endpoint_url(cls): + return f"{cls.get_scheme()}{cls.endpoint_url}" @classmethod def bucket_url(cls): - scheme = "https://" if cls.secure else "http://" - if is_ipv4(cls.endpoint_url): - return f"{scheme}{cls.endpoint_url}/{cls.results_bucket_name}/" + return f"{cls.get_scheme()}{cls.endpoint_url}/{cls.data_bucket_name}/" else: - return f"{scheme}{cls.results_bucket_name}.{cls.endpoint_url}/" + return f"{cls.get_scheme()}{cls.data_bucket_name}.{cls.endpoint_url}/" class FeaturesConfig: - enable_custom_cloud_host = str_to_bool(os.environ.get("ENABLE_CUSTOM_CLOUD_HOST", "no")) + enable_custom_cloud_host = to_bool(os.environ.get("ENABLE_CUSTOM_CLOUD_HOST", "no")) "Allows using a custom host in manifest bucket urls" default_export_timeout = int(os.environ.get("DEFAULT_EXPORT_TIMEOUT", 60)) @@ -126,7 +148,7 @@ class FeaturesConfig: class CoreConfig: - default_assignment_time = int(os.environ.get("DEFAULT_ASSIGNMENT_TIME", 300)) + default_assignment_time = int(os.environ.get("DEFAULT_ASSIGNMENT_TIME", 1800)) class HumanAppConfig: diff --git a/packages/examples/cvat/exchange-oracle/src/core/manifest.py b/packages/examples/cvat/exchange-oracle/src/core/manifest.py index 390a60c5a6..24cfd750b2 100644 --- a/packages/examples/cvat/exchange-oracle/src/core/manifest.py +++ b/packages/examples/cvat/exchange-oracle/src/core/manifest.py @@ -1,26 +1,126 @@ from decimal import Decimal +from enum import Enum +from typing import Annotated, Any, Dict, List, Literal, Optional, Tuple, Union from pydantic import AnyUrl, BaseModel, Field, root_validator -from src.core.config import Config -from src.core.types import TaskType +from src.core.types import TaskTypes +from src.utils.enums import BetterEnumMeta + + +class BucketProviders(str, Enum): + aws = "AWS" + gcs = "GCS" + + +class BucketUrlBase(BaseModel): + provider: BucketProviders + host_url: str + bucket_name: str + path: str = "" + + +class AwsBucketUrl(BucketUrlBase, BaseModel): + provider: Literal[BucketProviders.aws] + access_key: str = "" # (optional) AWS Access key + secret_key: str = "" # (optional) AWS Secret key + + +class GcsBucketUrl(BucketUrlBase, BaseModel): + provider: Literal[BucketProviders.gcs] + service_account_key: Dict[str, Any] = {} # (optional) Contents of GCS key file + + +BucketUrl = Annotated[Union[AwsBucketUrl, GcsBucketUrl], Field(discriminator="provider")] class DataInfo(BaseModel): - data_url: AnyUrl - "Bucket URL, s3 only, virtual-hosted-style access" + data_url: Union[AnyUrl, BucketUrl] + "Bucket URL, AWS S3 | GCS, virtual-hosted-style access" # https://docs.aws.amazon.com/AmazonS3/latest/userguide/access-bucket-intro.html + points_url: Optional[Union[AnyUrl, BucketUrl]] = None + "A path to an archive with a set of points in COCO Keypoints format, " + "which provides information about all objects on images" -class LabelInfo(BaseModel): - name: str + boxes_url: Optional[Union[AnyUrl, BucketUrl]] = None + "A path to an archive with a set of boxes in COCO Instances format, " + "which provides information about all objects on images" + + +class LabelTypes(str, Enum, metaclass=BetterEnumMeta): + plain = "plain" + skeleton = "skeleton" + + +class LabelInfoBase(BaseModel): + name: str = Field(min_length=1) # https://opencv.github.io/cvat/docs/api_sdk/sdk/reference/models/label/ + type: LabelTypes = LabelTypes.plain + + +class PlainLabelInfo(LabelInfoBase): + type: Literal[LabelTypes.plain] + + +class SkeletonLabelInfo(LabelInfoBase): + type: Literal[LabelTypes.skeleton] + + nodes: List[str] = Field(min_items=1) + """ + A list of node label names (only points are supposed to be nodes). + Example: + [ + "left hand", "torso", "right hand", "head" + ] + """ + + joints: List[Tuple[int, int]] + "A list of node adjacency, e.g. [[0, 1], [1, 2], [1, 3]]" + + @root_validator + @classmethod + def validate_type(cls, values: dict) -> dict: + if values["type"] != LabelTypes.skeleton: + raise ValueError(f"Label type must be {LabelTypes.skeleton}") + + skeleton_name = values["name"] + + existing_names = set() + for node_name in values["nodes"]: + node_name = node_name.strip() + + if not node_name: + raise ValueError(f"Skeleton '{skeleton_name}': point name is empty") + + if node_name.lower() in existing_names: + raise ValueError( + f"Skeleton '{skeleton_name}' point {node_name}: label is duplicated" + ) + + existing_names.add(node_name.lower()) + + nodes_count = len(values["nodes"]) + joints = values["joints"] + for joint_idx, joint in enumerate(joints): + for v in joint: + if not (0 <= v < nodes_count): + raise ValueError( + f"Skeleton '{skeleton_name}' joint #{joint_idx}: invalid value. " + f"Expected a number in the range [0; {nodes_count - 1}]" + ) + + return values + + +LabelInfo = Annotated[Union[PlainLabelInfo, SkeletonLabelInfo], Field(discriminator="type")] + class AnnotationInfo(BaseModel): - type: TaskType + type: TaskTypes - labels: list[LabelInfo] + labels: list[LabelInfo] = Field(min_items=1) "Label declarations with accepted annotation types" description: str = "" @@ -32,15 +132,24 @@ class AnnotationInfo(BaseModel): job_size: int = 10 "Frames per job, validation frames are not included" - max_time: int = Field(default_factory=lambda: Config.core_config.default_assignment_time) + max_time: Optional[int] = None # deprecated, TODO: mark deprecated with pydantic 2.7+ "Maximum time per job (assignment) for an annotator, in seconds" - @root_validator + @root_validator(pre=True) @classmethod - def validate_type(cls, values: dict) -> dict: - if values["type"] == TaskType.image_label_binary: - if len(values["labels"]) != 2: - raise ValueError("Binary classification requires 2 labels") + def _validate_label_type(cls, values: dict[str, Any]) -> dict[str, Any]: + default_label_type = LabelTypes.plain + if values["type"] == TaskTypes.image_skeletons_from_boxes: + default_label_type = LabelTypes.skeleton + + # Add default value for labels, if none provided. + # pydantic can't do this for tagged unions + try: + labels = values["labels"] + for label_info in labels: + label_info["type"] = label_info.get("type", default_label_type) + except KeyError: + pass return values @@ -52,7 +161,7 @@ class ValidationInfo(BaseModel): val_size: int = Field(default=2, gt=0) "Validation frames per job" - gt_url: AnyUrl + gt_url: Union[AnyUrl, BucketUrl] "URL to the archive with Ground Truth annotations, the format is COCO keypoints" @@ -63,3 +172,7 @@ class TaskManifest(BaseModel): job_bounty: Decimal = Field(ge=0) "Assignment bounty, a decimal value in HMT" + + +def parse_manifest(manifest: Any) -> TaskManifest: + return TaskManifest.parse_obj(manifest) diff --git a/packages/examples/cvat/exchange-oracle/src/core/oracle_events.py b/packages/examples/cvat/exchange-oracle/src/core/oracle_events.py index 5ca8eb1c17..334fd2ee80 100644 --- a/packages/examples/cvat/exchange-oracle/src/core/oracle_events.py +++ b/packages/examples/cvat/exchange-oracle/src/core/oracle_events.py @@ -3,16 +3,16 @@ from pydantic import BaseModel from src.core.types import ( - ExchangeOracleEventType, - JobLauncherEventType, + ExchangeOracleEventTypes, + JobLauncherEventTypes, OracleWebhookTypes, - RecordingOracleEventType, + RecordingOracleEventTypes, ) EventTypeTag = Union[ - ExchangeOracleEventType, - JobLauncherEventType, - RecordingOracleEventType, + ExchangeOracleEventTypes, + JobLauncherEventTypes, + RecordingOracleEventTypes, ] @@ -49,12 +49,12 @@ class ExchangeOracleEvent_TaskFinished(OracleEvent): _event_type_map = { - JobLauncherEventType.escrow_created: JobLauncherEvent_EscrowCreated, - JobLauncherEventType.escrow_canceled: JobLauncherEvent_EscrowCanceled, - RecordingOracleEventType.task_completed: RecordingOracleEvent_TaskCompleted, - RecordingOracleEventType.task_rejected: RecordingOracleEvent_TaskRejected, - ExchangeOracleEventType.task_creation_failed: ExchangeOracleEvent_TaskCreationFailed, - ExchangeOracleEventType.task_finished: ExchangeOracleEvent_TaskFinished, + JobLauncherEventTypes.escrow_created: JobLauncherEvent_EscrowCreated, + JobLauncherEventTypes.escrow_canceled: JobLauncherEvent_EscrowCanceled, + RecordingOracleEventTypes.task_completed: RecordingOracleEvent_TaskCompleted, + RecordingOracleEventTypes.task_rejected: RecordingOracleEvent_TaskRejected, + ExchangeOracleEventTypes.task_creation_failed: ExchangeOracleEvent_TaskCreationFailed, + ExchangeOracleEventTypes.task_finished: ExchangeOracleEvent_TaskFinished, } @@ -84,9 +84,9 @@ def parse_event( event_data: Optional[dict] = None, ) -> OracleEvent: sender_events_mapping = { - OracleWebhookTypes.job_launcher: JobLauncherEventType, - OracleWebhookTypes.recording_oracle: RecordingOracleEventType, - OracleWebhookTypes.exchange_oracle: ExchangeOracleEventType, + OracleWebhookTypes.job_launcher: JobLauncherEventTypes, + OracleWebhookTypes.recording_oracle: RecordingOracleEventTypes, + OracleWebhookTypes.exchange_oracle: ExchangeOracleEventTypes, } sender_events = sender_events_mapping.get(sender) diff --git a/packages/examples/cvat/exchange-oracle/src/core/storage.py b/packages/examples/cvat/exchange-oracle/src/core/storage.py new file mode 100644 index 0000000000..b934b865c0 --- /dev/null +++ b/packages/examples/cvat/exchange-oracle/src/core/storage.py @@ -0,0 +1,10 @@ +from src.core.config import Config +from src.core.types import Networks + + +def compose_data_bucket_filename(escrow_address: str, chain_id: Networks, filename: str) -> str: + return f"{escrow_address}@{chain_id}/{filename}" + + +def compose_results_bucket_filename(escrow_address: str, chain_id: Networks, filename: str) -> str: + return f"{escrow_address}@{chain_id}{Config.storage_config.results_dir_suffix}/{filename}" diff --git a/packages/examples/cvat/exchange-oracle/src/core/tasks/__init__.py b/packages/examples/cvat/exchange-oracle/src/core/tasks/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/examples/cvat/exchange-oracle/src/core/tasks/boxes_from_points.py b/packages/examples/cvat/exchange-oracle/src/core/tasks/boxes_from_points.py new file mode 100644 index 0000000000..c9320473b6 --- /dev/null +++ b/packages/examples/cvat/exchange-oracle/src/core/tasks/boxes_from_points.py @@ -0,0 +1,92 @@ +import os +from pathlib import Path +from tempfile import TemporaryDirectory +from typing import Dict, Sequence + +import attrs +import datumaro as dm +from attrs import frozen +from datumaro.util import dump_json, parse_json + +BboxPointMapping = Dict[int, int] + + +@frozen +class RoiInfo: + point_id: int + original_image_key: int + point_x: int + point_y: int + roi_x: int + roi_y: int + roi_w: int + roi_h: int + + def asdict(self) -> dict: + return attrs.asdict(self, recurse=False) + + +RoiInfos = Sequence[RoiInfo] + +RoiFilenames = Dict[int, str] + + +class TaskMetaLayout: + GT_FILENAME = "gt.json" + POINTS_FILENAME = "points.json" + BBOX_POINT_MAPPING_FILENAME = "bbox_point_mapping.json" + ROI_INFO_FILENAME = "rois.json" + + ROI_FILENAMES_FILENAME = "roi_filenames.json" + # this is separated from the general roi info to make name mangling more "optional" + + +class TaskMetaSerializer: + GT_DATASET_FORMAT = "coco_instances" + POINTS_DATASET_FORMAT = "coco_person_keypoints" + + def serialize_gt_annotations(self, gt_dataset: dm.Dataset) -> bytes: + with TemporaryDirectory() as temp_dir: + gt_dataset_dir = os.path.join(temp_dir, "gt_dataset") + gt_dataset.export(gt_dataset_dir, self.GT_DATASET_FORMAT) + return (Path(gt_dataset_dir) / "annotations" / "instances_default.json").read_bytes() + + def serialize_bbox_point_mapping(self, bbox_point_mapping: BboxPointMapping) -> bytes: + return dump_json({str(k): str(v) for k, v in bbox_point_mapping.items()}) + + def serialize_roi_info(self, rois_info: RoiInfos) -> bytes: + return dump_json([roi_info.asdict() for roi_info in rois_info]) + + def serialize_roi_filenames(self, roi_filenames: RoiFilenames) -> bytes: + return dump_json({str(k): v for k, v in roi_filenames.items()}) + + def parse_gt_annotations(self, gt_dataset_data: bytes) -> dm.Dataset: + with TemporaryDirectory() as temp_dir: + annotations_filename = os.path.join(temp_dir, "annotations.json") + with open(annotations_filename, "wb") as f: + f.write(gt_dataset_data) + + dataset = dm.Dataset.import_from(annotations_filename, format=self.GT_DATASET_FORMAT) + dataset.init_cache() + return dataset + + def parse_points_annotations(self, points_dataset_data: bytes) -> dm.Dataset: + with TemporaryDirectory() as temp_dir: + annotations_filename = os.path.join(temp_dir, "annotations.json") + with open(annotations_filename, "wb") as f: + f.write(points_dataset_data) + + dataset = dm.Dataset.import_from( + annotations_filename, format=self.POINTS_DATASET_FORMAT + ) + dataset.init_cache() + return dataset + + def parse_bbox_point_mapping(self, bbox_point_mapping_data: bytes) -> BboxPointMapping: + return {int(k): int(v) for k, v in parse_json(bbox_point_mapping_data).items()} + + def parse_roi_info(self, rois_info_data: bytes) -> RoiInfos: + return [RoiInfo(**roi_info) for roi_info in parse_json(rois_info_data)] + + def parse_roi_filenames(self, roi_filenames_data: bytes) -> RoiFilenames: + return {int(k): v for k, v in parse_json(roi_filenames_data).items()} diff --git a/packages/examples/cvat/exchange-oracle/src/core/tasks/skeletons_from_boxes.py b/packages/examples/cvat/exchange-oracle/src/core/tasks/skeletons_from_boxes.py new file mode 100644 index 0000000000..d0d1832482 --- /dev/null +++ b/packages/examples/cvat/exchange-oracle/src/core/tasks/skeletons_from_boxes.py @@ -0,0 +1,127 @@ +import os +from pathlib import Path +from tempfile import TemporaryDirectory +from typing import Dict, Sequence, Tuple + +import attrs +import datumaro as dm +from attrs import frozen +from datumaro.util import dump_json, parse_json + +DEFAULT_ASSIGNMENT_SIZE_MULTIPLIER = 2 * 3 # tile grid size + +SkeletonBboxMapping = Dict[int, int] + + +@frozen(kw_only=True) +class RoiInfo: + original_image_key: int + bbox_id: int + bbox_x: int + bbox_y: int + bbox_label: int + + # RoI is centered on the bbox center + # Coordinates can be out of image boundaries. + # In this case RoI includes extra margins to be centered on bbox center + roi_x: int + roi_y: int + roi_w: int + roi_h: int + + def asdict(self) -> dict: + return attrs.asdict(self, recurse=False) + + +RoiInfos = Sequence[RoiInfo] + +RoiFilenames = Dict[int, str] + +PointLabelsMapping = Dict[Tuple[str, str], str] +"(skeleton, point) -> job point name" + + +class TaskMetaLayout: + GT_FILENAME = "gt.json" + BOXES_FILENAME = "boxes.json" + POINT_LABELS_FILENAME = "point_labels.json" + SKELETON_BBOX_MAPPING_FILENAME = "skeleton_bbox_mapping.json" + ROI_INFO_FILENAME = "rois.json" + + ROI_FILENAMES_FILENAME = "roi_filenames.json" + # this is separated from the general roi info to make name mangling more "optional" + + +class TaskMetaSerializer: + GT_DATASET_FORMAT = "coco_person_keypoints" + BBOX_DATASET_FORMAT = "coco_instances" + + def serialize_gt_annotations(self, gt_dataset: dm.Dataset) -> bytes: + with TemporaryDirectory() as temp_dir: + gt_dataset_dir = os.path.join(temp_dir, "gt_dataset") + gt_dataset.export(gt_dataset_dir, self.GT_DATASET_FORMAT) + return ( + Path(gt_dataset_dir) / "annotations" / "person_keypoints_default.json" + ).read_bytes() + + def serialize_bbox_annotations(self, bbox_dataset: dm.Dataset) -> bytes: + with TemporaryDirectory() as temp_dir: + bbox_dataset_dir = os.path.join(temp_dir, "bbox_dataset") + bbox_dataset.export(bbox_dataset_dir, self.BBOX_DATASET_FORMAT) + return (Path(bbox_dataset_dir) / "annotations" / "instances_default.json").read_bytes() + + def serialize_skeleton_bbox_mapping(self, skeleton_bbox_mapping: SkeletonBboxMapping) -> bytes: + return dump_json({str(k): str(v) for k, v in skeleton_bbox_mapping.items()}) + + def serialize_roi_info(self, rois_info: RoiInfos) -> bytes: + return dump_json([roi_info.asdict() for roi_info in rois_info]) + + def serialize_roi_filenames(self, roi_filenames: RoiFilenames) -> bytes: + return dump_json({str(k): v for k, v in roi_filenames.items()}) + + def serialize_point_labels(self, point_labels: PointLabelsMapping) -> bytes: + return dump_json( + [ + { + "skeleton_label": k[0], + "point_label": k[1], + "job_point_label": v, + } + for k, v in point_labels.items() + ] + ) + + def parse_gt_annotations(self, gt_dataset_data: bytes) -> dm.Dataset: + with TemporaryDirectory() as temp_dir: + annotations_filename = os.path.join(temp_dir, "annotations.json") + with open(annotations_filename, "wb") as f: + f.write(gt_dataset_data) + + dataset = dm.Dataset.import_from(annotations_filename, format=self.GT_DATASET_FORMAT) + dataset.init_cache() + return dataset + + def parse_bbox_annotations(self, bbox_dataset_data: bytes) -> dm.Dataset: + with TemporaryDirectory() as temp_dir: + annotations_filename = os.path.join(temp_dir, "annotations.json") + with open(annotations_filename, "wb") as f: + f.write(bbox_dataset_data) + + dataset = dm.Dataset.import_from(annotations_filename, format=self.BBOX_DATASET_FORMAT) + dataset.init_cache() + return dataset + + def parse_skeleton_bbox_mapping(self, skeleton_bbox_mapping_data: bytes) -> SkeletonBboxMapping: + return {int(k): int(v) for k, v in parse_json(skeleton_bbox_mapping_data).items()} + + def parse_roi_info(self, rois_info_data: bytes) -> RoiInfos: + return [RoiInfo(**roi_info) for roi_info in parse_json(rois_info_data)] + + def parse_roi_filenames(self, roi_filenames_data: bytes) -> RoiFilenames: + return {int(k): v for k, v in parse_json(roi_filenames_data).items()} + + def parse_point_labels(self, point_labels_data: bytes) -> PointLabelsMapping: + return { + (v["skeleton_label"], v["point_label"]): v["job_point_label"] + for v in parse_json(point_labels_data) + } diff --git a/packages/examples/cvat/exchange-oracle/src/core/types.py b/packages/examples/cvat/exchange-oracle/src/core/types.py index d7d151c7b9..eda4e80b00 100644 --- a/packages/examples/cvat/exchange-oracle/src/core/types.py +++ b/packages/examples/cvat/exchange-oracle/src/core/types.py @@ -24,7 +24,7 @@ class ProjectStatuses(str, Enum, metaclass=BetterEnumMeta): recorded = "recorded" -class TaskStatus(str, Enum, metaclass=BetterEnumMeta): +class TaskStatuses(str, Enum, metaclass=BetterEnumMeta): annotation = "annotation" completed = "completed" @@ -36,40 +36,37 @@ class JobStatuses(str, Enum, metaclass=BetterEnumMeta): completed = "completed" -class TaskType(str, Enum, metaclass=BetterEnumMeta): +class TaskTypes(str, Enum, metaclass=BetterEnumMeta): image_label_binary = "IMAGE_LABEL_BINARY" image_points = "IMAGE_POINTS" image_boxes = "IMAGE_BOXES" + image_boxes_from_points = "IMAGE_BOXES_FROM_POINTS" + image_skeletons_from_boxes = "IMAGE_SKELETONS_FROM_BOXES" -class CvatLabelType(str, Enum, metaclass=BetterEnumMeta): +class CvatLabelTypes(str, Enum, metaclass=BetterEnumMeta): tag = "tag" points = "points" rectangle = "rectangle" -class CloudProviders(str, Enum, metaclass=BetterEnumMeta): - aws = "AWS_S3_BUCKET" - gcs = "GOOGLE_CLOUD_STORAGE" - - class OracleWebhookTypes(str, Enum, metaclass=BetterEnumMeta): exchange_oracle = "exchange_oracle" job_launcher = "job_launcher" recording_oracle = "recording_oracle" -class ExchangeOracleEventType(str, Enum, metaclass=BetterEnumMeta): +class ExchangeOracleEventTypes(str, Enum, metaclass=BetterEnumMeta): task_creation_failed = "task_creation_failed" task_finished = "task_finished" -class JobLauncherEventType(str, Enum, metaclass=BetterEnumMeta): +class JobLauncherEventTypes(str, Enum, metaclass=BetterEnumMeta): escrow_created = "escrow_created" escrow_canceled = "escrow_canceled" -class RecordingOracleEventType(str, Enum, metaclass=BetterEnumMeta): +class RecordingOracleEventTypes(str, Enum, metaclass=BetterEnumMeta): task_completed = "task_completed" task_rejected = "task_rejected" @@ -80,11 +77,11 @@ class OracleWebhookStatuses(str, Enum, metaclass=BetterEnumMeta): failed = "failed" -class PlatformType(str, Enum, metaclass=BetterEnumMeta): +class PlatformTypes(str, Enum, metaclass=BetterEnumMeta): CVAT = "cvat" -class AssignmentStatus(str, Enum, metaclass=BetterEnumMeta): +class AssignmentStatuses(str, Enum, metaclass=BetterEnumMeta): created = "created" completed = "completed" expired = "expired" diff --git a/packages/examples/cvat/exchange-oracle/src/crons/__init__.py b/packages/examples/cvat/exchange-oracle/src/crons/__init__.py index 80865b88fc..aefcb8e248 100644 --- a/packages/examples/cvat/exchange-oracle/src/crons/__init__.py +++ b/packages/examples/cvat/exchange-oracle/src/crons/__init__.py @@ -11,8 +11,8 @@ process_outgoing_recording_oracle_webhooks, ) from src.crons.state_trackers import ( - retrieve_annotations, track_assignments, + track_completed_escrows, track_completed_projects, track_completed_tasks, track_task_creation, @@ -54,9 +54,9 @@ def cron_record(): seconds=Config.cron_config.track_completed_tasks_int, ) scheduler.add_job( - retrieve_annotations, + track_completed_escrows, "interval", - seconds=Config.cron_config.retrieve_annotations_int, + seconds=Config.cron_config.track_completed_escrows_int, ) scheduler.add_job( track_task_creation, diff --git a/packages/examples/cvat/exchange-oracle/src/crons/process_job_launcher_webhooks.py b/packages/examples/cvat/exchange-oracle/src/crons/process_job_launcher_webhooks.py index bbe7aedcf3..a3f527fa0b 100644 --- a/packages/examples/cvat/exchange-oracle/src/crons/process_job_launcher_webhooks.py +++ b/packages/examples/cvat/exchange-oracle/src/crons/process_job_launcher_webhooks.py @@ -4,14 +4,14 @@ from human_protocol_sdk.constants import Status as EscrowStatus from sqlalchemy.orm import Session -import src.cvat.tasks as cvat +import src.handlers.job_creation as cvat import src.services.cvat as cvat_db_service import src.services.webhook as oracle_db_service from src.chain.escrow import validate_escrow from src.chain.kvstore import get_job_launcher_url from src.core.config import Config, CronConfig from src.core.oracle_events import ExchangeOracleEvent_TaskCreationFailed -from src.core.types import JobLauncherEventType, OracleWebhookTypes, ProjectStatuses +from src.core.types import JobLauncherEventTypes, OracleWebhookTypes, ProjectStatuses from src.db import SessionLocal from src.db.utils import ForUpdateParams from src.log import ROOT_LOGGER_NAME @@ -66,7 +66,7 @@ def handle_job_launcher_event(webhook: Webhook, *, db_session: Session, logger: assert webhook.type == OracleWebhookTypes.job_launcher match webhook.event_type: - case JobLauncherEventType.escrow_created: + case JobLauncherEventTypes.escrow_created: try: validate_escrow( webhook.chain_id, @@ -108,7 +108,7 @@ def handle_job_launcher_event(webhook: Webhook, *, db_session: Session, logger: raise - case JobLauncherEventType.escrow_canceled: + case JobLauncherEventTypes.escrow_canceled: try: validate_escrow( webhook.chain_id, @@ -188,13 +188,6 @@ def process_outgoing_job_launcher_webhooks(): timestamp=None, # TODO: launcher doesn't support it yet ) - # TODO: remove when field naming is updated in launcher - body["escrowAddress"] = body.pop("escrow_address") - body["chainId"] = body.pop("chain_id") - body["eventType"] = body.pop("event_type") - body["eventData"] = body.pop("event_data") - # ^^^ - _, signature = prepare_signed_message( webhook.escrow_address, webhook.chain_id, diff --git a/packages/examples/cvat/exchange-oracle/src/crons/process_recording_oracle_webhooks.py b/packages/examples/cvat/exchange-oracle/src/crons/process_recording_oracle_webhooks.py index c1b2351289..e44a278f88 100644 --- a/packages/examples/cvat/exchange-oracle/src/crons/process_recording_oracle_webhooks.py +++ b/packages/examples/cvat/exchange-oracle/src/crons/process_recording_oracle_webhooks.py @@ -1,6 +1,7 @@ import logging import httpx +from datumaro.util import take_by from sqlalchemy.orm import Session import src.services.cvat as cvat_db_service @@ -12,8 +13,8 @@ JobStatuses, OracleWebhookTypes, ProjectStatuses, - RecordingOracleEventType, - TaskStatus, + RecordingOracleEventTypes, + TaskStatuses, ) from src.db import SessionLocal from src.db.utils import ForUpdateParams @@ -60,11 +61,12 @@ def handle_recording_oracle_event(webhook: Webhook, *, db_session: Session, logg assert webhook.type == OracleWebhookTypes.recording_oracle match webhook.event_type: - case RecordingOracleEventType.task_completed: - project = cvat_db_service.get_project_by_escrow_address( - db_session, webhook.escrow_address, for_update=True + case RecordingOracleEventTypes.task_completed: + chunk_size = CronConfig.accepted_projects_chunk_size + project_ids = cvat_db_service.get_project_cvat_ids_by_escrow_address( + db_session, webhook.escrow_address ) - if not project: + if not project_ids: logger.error( "Unexpected event {} received for an unknown project, " "ignoring (escrow_address={})".format( @@ -73,54 +75,74 @@ def handle_recording_oracle_event(webhook: Webhook, *, db_session: Session, logg ) return - if project.status != ProjectStatuses.validation: - logger.error( - "Unexpected event {} received for a project in the {} status, " - "ignoring (escrow_address={})".format( - webhook.event_type, project.status, webhook.escrow_address - ) + for ids_chunk in take_by(project_ids, chunk_size): + projects_chunk = cvat_db_service.get_projects_by_cvat_ids( + db_session, ids_chunk, for_update=True, limit=chunk_size ) - return - new_status = ProjectStatuses.recorded - logger.info( - "Changing project status to {} (escrow_address={})".format( - new_status, webhook.escrow_address - ) - ) + for project in projects_chunk: + if project.status != ProjectStatuses.validation: + logger.error( + "Unexpected event {} received for a project in the {} status, " + "ignoring (escrow_address={}, project={})".format( + webhook.event_type, + project.status, + webhook.escrow_address, + project.cvat_id, + ) + ) + return + + new_status = ProjectStatuses.recorded + logger.info( + "Changing project status to {} (escrow_address={}, project={})".format( + new_status, webhook.escrow_address, project.cvat_id + ) + ) - cvat_db_service.update_project_status(db_session, project.id, new_status) + cvat_db_service.update_project_status(db_session, project.id, new_status) - case RecordingOracleEventType.task_rejected: + case RecordingOracleEventTypes.task_rejected: event = RecordingOracleEvent_TaskRejected.parse_obj(webhook.event_data) - project = cvat_db_service.get_project_by_escrow_address( - db_session, webhook.escrow_address, for_update=True - ) - - if project.status != ProjectStatuses.validation: - logger.error( - "Unexpected event {} received for a project in the {} status, " - "ignoring (escrow_address={})".format( - webhook.event_type, project.status, webhook.escrow_address - ) - ) - return - rejected_jobs = cvat_db_service.get_jobs_by_cvat_id(db_session, event.rejected_job_ids) + rejected_project_cvat_ids = set(j.cvat_project_id for j in rejected_jobs) - tasks_to_update = set() - - for job in rejected_jobs: - tasks_to_update.add(job.task.id) - cvat_db_service.update_job_status(db_session, job.id, JobStatuses.new) - - for task_id in tasks_to_update: - cvat_db_service.update_task_status(db_session, task_id, TaskStatus.annotation) + chunk_size = CronConfig.rejected_projects_chunk_size + for chunk_ids in take_by(rejected_project_cvat_ids, chunk_size): + projects_chunk = cvat_db_service.get_projects_by_cvat_ids( + db_session, chunk_ids, for_update=True, limit=chunk_size + ) - cvat_db_service.update_project_status( - db_session, project.id, ProjectStatuses.annotation - ) + for project in projects_chunk: + if project.status != ProjectStatuses.validation: + logger.error( + "Unexpected event {} received for a project in the {} status, " + "ignoring (escrow_address={}, project_id={})".format( + webhook.event_type, + project.status, + webhook.escrow_address, + project.cvat_id, + ) + ) + continue + + rejected_jobs_in_project = [ + j for j in rejected_jobs if j.cvat_project_id == project.cvat_id + ] + tasks_to_update = set() + for job in rejected_jobs_in_project: + tasks_to_update.add(job.task.id) + cvat_db_service.update_job_status(db_session, job.id, JobStatuses.new) + + for task_id in tasks_to_update: + cvat_db_service.update_task_status( + db_session, task_id, TaskStatuses.annotation + ) + + cvat_db_service.update_project_status( + db_session, project.id, ProjectStatuses.annotation + ) case _: assert False, f"Unknown recording oracle event {webhook.event_type}" diff --git a/packages/examples/cvat/exchange-oracle/src/crons/state_trackers.py b/packages/examples/cvat/exchange-oracle/src/crons/state_trackers.py index 8b5d138d0e..f55a36f7a3 100644 --- a/packages/examples/cvat/exchange-oracle/src/crons/state_trackers.py +++ b/packages/examples/cvat/exchange-oracle/src/crons/state_trackers.py @@ -1,28 +1,16 @@ -from typing import Dict, List +from typing import List import src.cvat.api_calls as cvat_api import src.models.cvat as cvat_models -import src.services.cloud.client as cloud_client import src.services.cvat as cvat_service import src.services.webhook as oracle_db_service -from src.chain.escrow import get_escrow_manifest, validate_escrow -from src.core.annotation_meta import RESULTING_ANNOTATIONS_FILE -from src.core.config import CronConfig, StorageConfig -from src.core.oracle_events import ( - ExchangeOracleEvent_TaskCreationFailed, - ExchangeOracleEvent_TaskFinished, -) -from src.core.types import JobStatuses, OracleWebhookTypes, ProjectStatuses, TaskStatus +from src.core.config import CronConfig +from src.core.oracle_events import ExchangeOracleEvent_TaskCreationFailed +from src.core.types import JobStatuses, OracleWebhookTypes, ProjectStatuses, TaskStatuses from src.db import SessionLocal from src.db.utils import ForUpdateParams -from src.handlers.annotation import ( - CVAT_EXPORT_FORMAT_MAPPING, - FileDescriptor, - postprocess_annotations, - prepare_annotation_metafile, -) +from src.handlers.completed_escrows import handle_completed_escrows from src.log import ROOT_LOGGER_NAME -from src.utils.assignments import compose_output_annotation_filename, parse_manifest from src.utils.logging import get_function_logger module_logger = f"{ROOT_LOGGER_NAME}.cron.cvat" @@ -52,7 +40,7 @@ def track_completed_projects() -> None: for project in projects: tasks = cvat_service.get_tasks_by_cvat_project_id(session, project.cvat_id) - if tasks and all(task.status == TaskStatus.completed for task in tasks): + if tasks and all(task.status == TaskStatuses.completed for task in tasks): cvat_service.update_project_status( session, project.id, ProjectStatuses.completed ) @@ -84,7 +72,7 @@ def track_completed_tasks() -> None: logger.debug("Starting cron job") with SessionLocal.begin() as session: tasks = cvat_service.get_tasks_by_status( - session, TaskStatus.annotation, for_update=ForUpdateParams(skip_locked=True) + session, TaskStatuses.annotation, for_update=ForUpdateParams(skip_locked=True) ) completed_task_ids = [] @@ -92,7 +80,7 @@ def track_completed_tasks() -> None: for task in tasks: jobs = cvat_service.get_jobs_by_cvat_task_id(session, task.cvat_id) if jobs and all(job.status == JobStatuses.completed for job in jobs): - cvat_service.update_task_status(session, task.id, TaskStatus.completed) + cvat_service.update_task_status(session, task.id, TaskStatuses.completed) completed_task_ids.append(task.cvat_id) @@ -183,141 +171,13 @@ def track_assignments() -> None: logger.debug("Finishing cron job") -def retrieve_annotations() -> None: - """ - Retrieves and stores completed annotations: - 1. Retrieves annotations from projects with "completed" status - 2. Postprocesses them - 3. Stores annotations in s3 bucket - 4. Prepares a webhook to recording oracle - """ +def track_completed_escrows() -> None: logger = get_function_logger(module_logger) try: logger.debug("Starting cron job") - with SessionLocal.begin() as session: - # Get completed projects from db - projects = cvat_service.get_projects_by_status( - session, - ProjectStatuses.completed, - limit=CronConfig.retrieve_annotations_chunk_size, - for_update=ForUpdateParams(skip_locked=True), - ) - - for project in projects: - # Check if all jobs within the project are completed - if not cvat_service.is_project_completed(session, project.id): - cvat_service.update_project_status( - session, project.id, ProjectStatuses.annotation - ) - continue - validate_escrow(project.chain_id, project.escrow_address) - - manifest = parse_manifest( - get_escrow_manifest(project.chain_id, project.escrow_address) - ) - - logger.debug( - f"Downloading results for the project (escrow_address={project.escrow_address})" - ) - - jobs = cvat_service.get_jobs_by_cvat_project_id(session, project.cvat_id) - - annotation_format = CVAT_EXPORT_FORMAT_MAPPING[project.job_type] - job_annotations: Dict[int, FileDescriptor] = {} - - # Request dataset preparation beforehand - for job in jobs: - cvat_api.request_job_annotations(job.cvat_id, format_name=annotation_format) - cvat_api.request_project_annotations(project.cvat_id, format_name=annotation_format) - - # Collect raw annotations from CVAT, validate and convert them - # into a recording oracle suitable format - for job in jobs: - job_annotations_file = cvat_api.get_job_annotations( - job.cvat_id, format_name=annotation_format - ) - job_assignment = job.latest_assignment - job_annotations[job.cvat_id] = FileDescriptor( - filename="project_{}-task_{}-job_{}-user_{}-assignment_{}.zip".format( - project.cvat_id, - job.cvat_task_id, - job.cvat_id, - job_assignment.user.cvat_id, - job_assignment.id, - ), - file=job_annotations_file, - ) - - project_annotations_file = cvat_api.get_project_annotations( - project.cvat_id, format_name=annotation_format - ) - project_annotations_file_desc = FileDescriptor( - filename=RESULTING_ANNOTATIONS_FILE, - file=project_annotations_file, - ) - - annotation_files: List[FileDescriptor] = [] - annotation_files.append(project_annotations_file_desc) - - annotation_metafile = prepare_annotation_metafile( - jobs=jobs, job_annotations=job_annotations - ) - annotation_files.extend(job_annotations.values()) - postprocess_annotations( - annotation_files, - project_annotations_file_desc, - manifest=manifest, - project_images=cvat_service.get_project_images(session, project.cvat_id), - ) - - annotation_files.append(annotation_metafile) - - storage_client = cloud_client.S3Client( - StorageConfig.provider_endpoint_url(), - access_key=StorageConfig.access_key, - secret_key=StorageConfig.secret_key, - ) - existing_storage_files = set( - f.key - for f in storage_client.list_files( - StorageConfig.results_bucket_name, - path=compose_output_annotation_filename( - project.escrow_address, - project.chain_id, - "", - ), - ) - ) - for file_descriptor in annotation_files: - if file_descriptor.filename in existing_storage_files: - continue - - storage_client.create_file( - StorageConfig.results_bucket_name, - compose_output_annotation_filename( - project.escrow_address, - project.chain_id, - file_descriptor.filename, - ), - file_descriptor.file.read(), - ) - - oracle_db_service.outbox.create_webhook( - session, - project.escrow_address, - project.chain_id, - OracleWebhookTypes.recording_oracle, - event=ExchangeOracleEvent_TaskFinished(), - ) - - cvat_service.update_project_status(session, project.id, ProjectStatuses.validation) - - logger.info( - f"The project (escrow_address={project.escrow_address}) " - "is finished, resulting annotations are processed successfully" - ) + handle_completed_escrows(logger) except Exception as error: logger.exception(error) finally: @@ -353,11 +213,10 @@ def track_task_creation() -> None: failed: List[cvat_models.DataUpload] = [] for upload in uploads: status, reason = cvat_api.get_task_upload_status(upload.task_id) + project = upload.task.project if not status or status == cvat_api.UploadStatus.FAILED: failed.append(upload) - project = upload.task.project - oracle_db_service.outbox.create_webhook( session, escrow_address=project.escrow_address, diff --git a/packages/examples/cvat/exchange-oracle/src/cvat/api_calls.py b/packages/examples/cvat/exchange-oracle/src/cvat/api_calls.py index 9a6fd5dd21..f022e72d5c 100644 --- a/packages/examples/cvat/exchange-oracle/src/cvat/api_calls.py +++ b/packages/examples/cvat/exchange-oracle/src/cvat/api_calls.py @@ -1,11 +1,13 @@ import io +import json import logging import zipfile from datetime import timedelta from enum import Enum from http import HTTPStatus +from io import BytesIO from time import sleep -from typing import List, Optional, Tuple +from typing import Any, Dict, List, Optional, Tuple from cvat_sdk.api_client import ApiClient, Configuration, exceptions, models from cvat_sdk.api_client.api_client import Endpoint @@ -102,21 +104,61 @@ def get_api_client() -> ApiClient: def create_cloudstorage( - provider: str, bucket_host: str, bucket_name: str + provider: str, + bucket_name: str, + *, + credentials: Optional[Dict[str, Any]] = None, + bucket_host: Optional[str] = None, ) -> models.CloudStorageRead: + # credentials: access_key | secret_key | service_account_key + # CVAT credentials: key | secret_key | key_file + def _to_cvat_credentials(credentials: Dict[str, Any]) -> Dict: + cvat_credentials = dict() + for cvat_field, field in { + "key": "access_key", + "secret_key": "secret_key", + "key_file": "service_account_key", + }.items(): + if value := credentials.get(field): + if cvat_field == "key_file": + key_file = BytesIO(json.dumps(value).encode("utf-8")) + key_file.name = "key_file.json" + key_file.seek(0) + cvat_credentials[cvat_field] = key_file + else: + cvat_credentials[cvat_field] = value + return cvat_credentials + + request_kwargs = dict() + + if credentials: + request_kwargs.update(_to_cvat_credentials(credentials)) + credentials_type = ( + models.CredentialsTypeEnum("KEY_SECRET_KEY_PAIR") + if provider == "AWS_S3_BUCKET" + else models.CredentialsTypeEnum("KEY_FILE_PATH") + ) + else: + credentials_type = models.CredentialsTypeEnum("ANONYMOUS_ACCESS") + + if bucket_host: + request_kwargs["specific_attributes"] = f"endpoint_url={bucket_host}" + logger = logging.getLogger("app") + with get_api_client() as api_client: cloud_storage_write_request = models.CloudStorageWriteRequest( provider_type=models.ProviderTypeEnum(provider), resource=bucket_name, display_name=bucket_name, - credentials_type=models.CredentialsTypeEnum("ANONYMOUS_ACCESS"), + credentials_type=credentials_type, description=bucket_name, - specific_attributes=f"endpoint_url={bucket_host}", + **request_kwargs, ) # CloudStorageWriteRequest try: (data, response) = api_client.cloudstorages_api.create( cloud_storage_write_request, + _content_type="multipart/form-data", ) return data @@ -126,16 +168,18 @@ def create_cloudstorage( def create_project( - escrow_address: str, labels: list, *, user_guide: str = "" + name: str, *, labels: Optional[list] = None, user_guide: str = "" ) -> models.ProjectRead: logger = logging.getLogger("app") with get_api_client() as api_client: + kwargs = {} + + if labels is not None: + kwargs["labels"] = labels + try: (project, response) = api_client.projects_api.create( - models.ProjectWriteRequest( - name=escrow_address, - labels=labels, - ) + models.ProjectWriteRequest(name=name, **kwargs) ) if user_guide: api_client.guides_api.create( @@ -234,11 +278,11 @@ def create_cvat_webhook(project_id: int) -> models.WebhookRead: raise -def create_task(project_id: int, escrow_address: str) -> models.TaskRead: +def create_task(project_id: int, name: str) -> models.TaskRead: logger = logging.getLogger("app") with get_api_client() as api_client: task_write_request = models.TaskWriteRequest( - name=escrow_address, + name=name, project_id=project_id, overlap=0, segment_size=Config.cvat_config.cvat_job_segment_size, diff --git a/packages/examples/cvat/exchange-oracle/src/cvat/tasks.py b/packages/examples/cvat/exchange-oracle/src/cvat/tasks.py deleted file mode 100644 index c36e088155..0000000000 --- a/packages/examples/cvat/exchange-oracle/src/cvat/tasks.py +++ /dev/null @@ -1,210 +0,0 @@ -import os -import random -from tempfile import TemporaryDirectory -from typing import List - -import datumaro as dm -from datumaro.util import take_by -from datumaro.util.image import IMAGE_EXTENSIONS - -import src.cvat.api_calls as cvat_api -import src.services.cloud as cloud_service -import src.services.cvat as db_service -from src.chain.escrow import get_escrow_manifest -from src.core.manifest import TaskManifest -from src.core.types import CvatLabelType, TaskStatus, TaskType -from src.db import SessionLocal -from src.utils.assignments import parse_manifest -from src.utils.cloud_storage import compose_bucket_url, parse_bucket_url - -LABEL_TYPE_MAPPING = { - TaskType.image_label_binary: CvatLabelType.tag, - TaskType.image_points: CvatLabelType.points, - TaskType.image_boxes: CvatLabelType.rectangle, -} - -DM_DATASET_FORMAT_MAPPING = { - TaskType.image_label_binary: "cvat_images", - TaskType.image_points: "coco_person_keypoints", - TaskType.image_boxes: "coco_instances", -} - -DM_GT_DATASET_FORMAT_MAPPING = { - # GT uses the same format both for boxes and points - TaskType.image_label_binary: "cvat_images", - TaskType.image_points: "coco_instances", - TaskType.image_boxes: "coco_instances", -} - - -def get_gt_filenames( - gt_file_data: bytes, data_filenames: List[str], *, manifest: TaskManifest -) -> List[str]: - with TemporaryDirectory() as gt_temp_dir: - gt_filename = os.path.join(gt_temp_dir, "gt_annotations.json") - with open(gt_filename, "wb") as f: - f.write(gt_file_data) - - gt_dataset = dm.Dataset.import_from( - gt_filename, - format=DM_GT_DATASET_FORMAT_MAPPING[manifest.annotation.type], - ) - - gt_filenames = set(s.id + s.media.ext for s in gt_dataset) - - known_data_filenames = set(data_filenames) - matched_gt_filenames = gt_filenames.intersection(known_data_filenames) - - if len(gt_filenames) != len(matched_gt_filenames): - missing_gt = gt_filenames - matched_gt_filenames - missing_gt_display_threshold = 10 - remainder = len(missing_gt) - missing_gt_display_threshold - raise Exception( - "Failed to find several validation samples in the dataset files: {}{}".format( - ", ".join(missing_gt[:missing_gt_display_threshold]), - f"(and {remainder} more)" if remainder else "", - ) - ) - - if len(gt_filenames) < manifest.validation.val_size: - raise Exception( - f"Too few validation samples provided ({len(gt_filenames)}), " - f"at least {manifest.validation.val_size} required." - ) - - return matched_gt_filenames - - -def make_job_configuration( - data_filenames: List[str], - gt_filenames: List[str], - *, - manifest: TaskManifest, -) -> List[List[str]]: - # Make job layouts wrt. manifest params, 1 job per task (CVAT can't repeat images in jobs) - gt_filenames_index = set(gt_filenames) - data_filenames = [fn for fn in data_filenames if not fn in gt_filenames_index] - random.shuffle(data_filenames) - - job_layout = [] - for data_samples in take_by(data_filenames, manifest.annotation.job_size): - gt_samples = random.sample(gt_filenames, k=manifest.validation.val_size) - job_samples = list(data_samples) + list(gt_samples) - random.shuffle(job_samples) - job_layout.append(job_samples) - - return job_layout - - -def is_image(path: str) -> bool: - trunk, ext = os.path.splitext(os.path.basename(path)) - return trunk and ext.lower() in IMAGE_EXTENSIONS - - -def filter_image_files(data_filenames: List[str]) -> List[str]: - return list(fn for fn in data_filenames if is_image(fn)) - - -def make_label_configuration(manifest: TaskManifest) -> List[dict]: - return [ - { - "name": label.name, - "type": LABEL_TYPE_MAPPING.get(manifest.annotation.type).value, - } - for label in manifest.annotation.labels - ] - - -def create_task(escrow_address: str, chain_id: int) -> None: - manifest = parse_manifest(get_escrow_manifest(chain_id, escrow_address)) - - parsed_data_bucket_url = parse_bucket_url(manifest.data.data_url) - data_cloud_provider = parsed_data_bucket_url.provider - data_bucket_host = parsed_data_bucket_url.host_url - data_bucket_name = parsed_data_bucket_url.bucket_name - data_bucket_path = parsed_data_bucket_url.path - - # Validate and parse GT - parsed_gt_bucket_url = parse_bucket_url(manifest.validation.gt_url) - gt_bucket_host = parsed_gt_bucket_url.host_url - gt_bucket_name = parsed_gt_bucket_url.bucket_name - gt_filename = parsed_gt_bucket_url.path - - # Register cloud storage on CVAT to pass user dataset - cloud_storage = cvat_api.create_cloudstorage( - data_cloud_provider, data_bucket_host, data_bucket_name - ) - - # Task configuration creation - data_filenames = cloud_service.list_files( - data_bucket_host, - data_bucket_name, - data_bucket_path, - ) - data_filenames = filter_image_files(data_filenames) - - gt_file_data = cloud_service.download_file( - gt_bucket_host, - gt_bucket_name, - gt_filename, - ) - gt_filenames = get_gt_filenames(gt_file_data, data_filenames, manifest=manifest) - - job_configuration = make_job_configuration(data_filenames, gt_filenames, manifest=manifest) - label_configuration = make_label_configuration(manifest) - - # Create a project - project = cvat_api.create_project( - escrow_address, - labels=label_configuration, - user_guide=manifest.annotation.user_guide, - ) - - # Setup webhooks for a project (update:task, update:job) - webhook = cvat_api.create_cvat_webhook(project.id) - - with SessionLocal.begin() as session: - db_service.create_project( - session, - project.id, - cloud_storage.id, - manifest.annotation.type, - escrow_address, - chain_id, - compose_bucket_url( - data_bucket_name, - bucket_host=data_bucket_host, - provider=data_cloud_provider, - ), - cvat_webhook_id=webhook.id, - ) - db_service.add_project_images(session, project.id, data_filenames) - - for job_filenames in job_configuration: - task = cvat_api.create_task(project.id, escrow_address) - - with SessionLocal.begin() as session: - db_service.create_task(session, task.id, project.id, TaskStatus[task.status]) - - # Actual task creation in CVAT takes some time, so it's done in an async process. - # The task will be created in DB once 'update:task' or 'update:job' webhook is received. - cvat_api.put_task_data( - task.id, - cloud_storage.id, - filenames=job_filenames, - sort_images=False, - ) - - with SessionLocal.begin() as session: - db_service.create_data_upload(session, cvat_task_id=task.id) - - -def remove_task(escrow_address: str) -> None: - with SessionLocal.begin() as session: - project = db_service.get_project_by_escrow_address(session, escrow_address) - if project is not None: - if project.cvat_cloudstorage_id: - cvat_api.delete_cloudstorage(project.cvat_cloudstorage_id) - if project.cvat_id: - cvat_api.delete_project(project.cvat_id) - db_service.delete_project(session, project.id) diff --git a/packages/examples/cvat/exchange-oracle/src/endpoints/webhook.py b/packages/examples/cvat/exchange-oracle/src/endpoints/webhook.py index 4c4cf1dbd6..eb4be7b6ee 100644 --- a/packages/examples/cvat/exchange-oracle/src/endpoints/webhook.py +++ b/packages/examples/cvat/exchange-oracle/src/endpoints/webhook.py @@ -19,13 +19,12 @@ async def receive_oracle_webhook( human_signature: Union[str, None] = Header(default=None), ) -> OracleWebhookResponse: try: - # TODO: remove mock + # TODO: remove mock once implemented in launcher if not human_signature: human_signature = "launcher-{}".format(utcnow().timestamp()) sender_type = OracleWebhookTypes.job_launcher else: - # TODO: add allowed sender type checks sender_type = await validate_oracle_webhook_signature(request, human_signature, webhook) with SessionLocal.begin() as session: diff --git a/packages/examples/cvat/exchange-oracle/src/handlers/annotation.py b/packages/examples/cvat/exchange-oracle/src/handlers/annotation.py deleted file mode 100644 index 3913c98ad6..0000000000 --- a/packages/examples/cvat/exchange-oracle/src/handlers/annotation.py +++ /dev/null @@ -1,217 +0,0 @@ -import io -import os -import zipfile -from glob import glob -from tempfile import TemporaryDirectory -from typing import Dict, List, Sequence - -import datumaro as dm -from attrs import define -from defusedxml import ElementTree as ET - -from src.core.annotation_meta import ANNOTATION_METAFILE_NAME, AnnotationMeta, JobMeta -from src.core.manifest import TaskManifest -from src.core.types import TaskType -from src.cvat.tasks import DM_DATASET_FORMAT_MAPPING -from src.models.cvat import Image, Job -from src.utils.zip_archive import extract_zip_archive, write_dir_to_zip_archive - -CVAT_EXPORT_FORMAT_MAPPING = { - TaskType.image_label_binary: "CVAT for images 1.1", - TaskType.image_points: "CVAT for images 1.1", - TaskType.image_boxes: "COCO 1.0", -} - -CVAT_EXPORT_FORMAT_TO_DM_MAPPING = { - "CVAT for images 1.1": "cvat", - "COCO 1.0": "coco_instances", -} - - -@define -class FileDescriptor: - filename: str - file: io.RawIOBase - - -def prepare_annotation_metafile( - jobs: List[Job], job_annotations: Dict[int, FileDescriptor] -) -> FileDescriptor: - """ - Prepares a task/project annotation descriptor file with annotator mapping. - """ - - meta = AnnotationMeta( - jobs=[ - JobMeta( - job_id=job.cvat_id, - annotation_filename=job_annotations[job.cvat_id].filename, - annotator_wallet_address=job.latest_assignment.user_wallet_address, - assignment_id=job.latest_assignment.id, - ) - for job in jobs - ] - ) - - return FileDescriptor(ANNOTATION_METAFILE_NAME, file=io.BytesIO(meta.json().encode())) - - -def flatten_points(input_points: List[dm.Points]) -> List[dm.Points]: - results = [] - - for pts in input_points: - for point_idx in range(len(pts.points) // 2): - point_x = pts.points[2 * point_idx + 0] - point_y = pts.points[2 * point_idx + 1] - results.append(dm.Points([point_x, point_y], label=pts.label)) - - return results - - -def fix_cvat_annotations(dataset_root: str): - for annotation_filename in glob(os.path.join(dataset_root, "**/*.xml"), recursive=True): - with open(annotation_filename, "rb+") as f: - doc = ET.parse(f) - doc_root = doc.getroot() - - if doc_root.find("meta/project"): - # put labels into each task, if needed - # datumaro doesn't support /meta/project/ tag, but works with tasks, - # which is nested in the meta/project/ - labels_element = doc_root.find("meta/project/labels") - if not labels_element: - continue - - for task_element in doc_root.iterfind("meta/project/tasks/task"): - task_element.append(labels_element) - elif job_meta := doc_root.find("meta/job"): - # just rename the job into task for the same reasons - job_meta.tag = "task" - else: - continue - - f.seek(0) - f.truncate() - doc.write(f, encoding="utf-8") - - -def convert_point_arrays_dataset_to_1_point_skeletons( - dataset: dm.Dataset, labels: List[str] -) -> dm.Dataset: - def _get_skeleton_label(original_label: str) -> str: - return original_label + "_sk" - - new_label_cat = dm.LabelCategories.from_iterable( - [_get_skeleton_label(label) for label in labels] - + [(label, _get_skeleton_label(label)) for label in labels] - ) - new_points_cat = dm.PointsCategories.from_iterable( - (new_label_cat.find(_get_skeleton_label(label))[0], [label]) for label in labels - ) - converted_dataset = dm.Dataset( - categories={ - dm.AnnotationType.label: new_label_cat, - dm.AnnotationType.points: new_points_cat, - }, - media_type=dm.Image, - ) - - label_id_map: Dict[int, int] = { - original_id: new_label_cat.find(label.name, parent=_get_skeleton_label(label.name))[0] - for original_id, label in enumerate(dataset.categories()[dm.AnnotationType.label]) - } # old id -> new id - - for sample in dataset: - points = [a for a in sample.annotations if isinstance(a, dm.Points)] - points = flatten_points(points) - - skeletons = [ - dm.Skeleton( - [p.wrap(label=label_id_map[p.label])], - label=new_label_cat.find(_get_skeleton_label(labels[p.label]))[0], - ) - for p in points - ] - - converted_dataset.put(sample.wrap(annotations=skeletons)) - - return converted_dataset - - -def remove_duplicated_gt_frames(dataset: dm.Dataset, known_frames: Sequence[str]): - """ - Removes unknown images from the dataset inplace. - - On project dataset export, CVAT will add GT frames, which repeat in multiple tasks, - with a suffix. We don't need these frames in the resulting dataset, - and we can safely remove them. - """ - if not isinstance(known_frames, set): - known_frames = set(known_frames) - - for sample in list(dataset): - item_image_filename = sample.media.path - - if item_image_filename not in known_frames: - dataset.remove(sample.id, sample.subset) - - -def postprocess_annotations( - annotations: List[FileDescriptor], - merged_annotation: FileDescriptor, - *, - manifest: TaskManifest, - project_images: List[Image], -) -> None: - """ - Processes annotations and updates the files list inplace - """ - - task_type = manifest.annotation.type - - if task_type != TaskType.image_points: - return # CVAT export is fine - - # We need to convert point arrays, which cannot be represented in COCO directly, - # into the 1-point skeletons, compatible with COCO person keypoints, which is the - # required output format - input_format = CVAT_EXPORT_FORMAT_TO_DM_MAPPING[CVAT_EXPORT_FORMAT_MAPPING[task_type]] - resulting_format = DM_DATASET_FORMAT_MAPPING[task_type] - - with TemporaryDirectory() as tempdir: - for ann_descriptor in annotations: - if not zipfile.is_zipfile(ann_descriptor.file): - raise ValueError("Annotation files must be zip files") - ann_descriptor.file.seek(0) - - extract_dir = os.path.join( - tempdir, - os.path.splitext(os.path.basename(ann_descriptor.filename))[0], - ) - extract_zip_archive(ann_descriptor.file, extract_dir) - - fix_cvat_annotations(extract_dir) - dataset = dm.Dataset.import_from(extract_dir, input_format) - - converted_dataset = convert_point_arrays_dataset_to_1_point_skeletons( - dataset, - labels=[label.name for label in manifest.annotation.labels], - ) - - if ann_descriptor.filename == merged_annotation.filename: - remove_duplicated_gt_frames( - converted_dataset, - known_frames=[image.filename for image in project_images], - ) - - export_dir = os.path.join( - tempdir, - os.path.splitext(os.path.basename(ann_descriptor.filename))[0] + "_conv", - ) - converted_dataset.export(export_dir, resulting_format, save_images=False) - - converted_dataset_archive = io.BytesIO() - write_dir_to_zip_archive(export_dir, converted_dataset_archive) - converted_dataset_archive.seek(0) - - ann_descriptor.file = converted_dataset_archive diff --git a/packages/examples/cvat/exchange-oracle/src/handlers/completed_escrows.py b/packages/examples/cvat/exchange-oracle/src/handlers/completed_escrows.py new file mode 100644 index 0000000000..fda29d2162 --- /dev/null +++ b/packages/examples/cvat/exchange-oracle/src/handlers/completed_escrows.py @@ -0,0 +1,352 @@ +import itertools +import logging +from typing import Dict, List, Optional + +from sqlalchemy import exc as db_exc +from sqlalchemy.orm import Session + +import src.cvat.api_calls as cvat_api +import src.models.cvat as cvat_models +import src.services.cloud as cloud_service +import src.services.cvat as cvat_service +import src.services.webhook as oracle_db_service +from src.chain.escrow import get_escrow_manifest, validate_escrow +from src.core.annotation_meta import RESULTING_ANNOTATIONS_FILE +from src.core.config import CronConfig, StorageConfig +from src.core.oracle_events import ExchangeOracleEvent_TaskFinished +from src.core.storage import compose_results_bucket_filename +from src.core.types import OracleWebhookTypes, ProjectStatuses, TaskTypes +from src.db import SessionLocal +from src.db.utils import ForUpdateParams +from src.handlers.job_export import ( + CVAT_EXPORT_FORMAT_MAPPING, + FileDescriptor, + postprocess_annotations, + prepare_annotation_metafile, +) +from src.services.cloud.types import BucketAccessInfo +from src.utils.assignments import parse_manifest +from src.utils.logging import NullLogger + + +class _CompletedEscrowsHandler: + """ + Retrieves and stores completed annotations: + 1. Retrieves annotations from jobs with "completed" status + 2. Processes them + 3. Stores annotations in the oracle bucket + 4. Prepares a webhook to recording oracle + """ + + def __init__(self, logger: Optional[logging.Logger]) -> None: + self.logger = logger or NullLogger() + + def _process_plain_escrows(self): + logger = self.logger + + plain_task_types = [t for t in TaskTypes if not t == TaskTypes.image_skeletons_from_boxes] + with SessionLocal.begin() as session: + completed_projects = cvat_service.get_projects_by_status( + session, + ProjectStatuses.completed, + included_types=plain_task_types, + limit=CronConfig.track_completed_escrows_chunk_size, + for_update=ForUpdateParams(skip_locked=True), + ) + + for project in completed_projects: + # Check if all jobs within the project are completed + if not cvat_service.is_project_completed(session, project.id): + cvat_service.update_project_status( + session, project.id, ProjectStatuses.annotation + ) + continue + + try: + # TODO: such escrows can fill all the queried completed projects + # need to improve handling for such projects + # (e.g. cancel depending on the escrow status) + validate_escrow(project.chain_id, project.escrow_address) + except Exception as e: + logger.error( + "Failed to handle completed project id {} for escrow {}: {}".format( + project.cvat_id, project.escrow_address, e + ) + ) + continue + + manifest = parse_manifest( + get_escrow_manifest(project.chain_id, project.escrow_address) + ) + + logger.debug( + f"Downloading results for the project (escrow_address={project.escrow_address})" + ) + + jobs = cvat_service.get_jobs_by_cvat_project_id(session, project.cvat_id) + + annotation_format = CVAT_EXPORT_FORMAT_MAPPING[project.job_type] + job_annotations: Dict[int, FileDescriptor] = {} + + # Request dataset preparation beforehand + for job in jobs: + cvat_api.request_job_annotations(job.cvat_id, format_name=annotation_format) + cvat_api.request_project_annotations(project.cvat_id, format_name=annotation_format) + + # Collect raw annotations from CVAT, validate and convert them + # into a recording oracle suitable format + for job in jobs: + job_annotations_file = cvat_api.get_job_annotations( + job.cvat_id, format_name=annotation_format + ) + job_assignment = job.latest_assignment + job_annotations[job.cvat_id] = FileDescriptor( + filename="project_{}-task_{}-job_{}-user_{}-assignment_{}.zip".format( + project.cvat_id, + job.cvat_task_id, + job.cvat_id, + job_assignment.user.cvat_id, + job_assignment.id, + ), + file=job_annotations_file, + ) + + project_annotations_file = cvat_api.get_project_annotations( + project.cvat_id, format_name=annotation_format + ) + project_annotations_file_desc = FileDescriptor( + filename=RESULTING_ANNOTATIONS_FILE, + file=project_annotations_file, + ) + + annotation_files: List[FileDescriptor] = [] + annotation_files.append(project_annotations_file_desc) + + annotation_metafile = prepare_annotation_metafile( + jobs=jobs, job_annotations=job_annotations + ) + annotation_files.extend(job_annotations.values()) + postprocess_annotations( + escrow_address=project.escrow_address, + chain_id=project.chain_id, + annotations=annotation_files, + merged_annotation=project_annotations_file_desc, + manifest=manifest, + project_images=cvat_service.get_project_images(session, project.cvat_id), + ) + + annotation_files.append(annotation_metafile) + + storage_info = BucketAccessInfo.parse_obj(StorageConfig) + storage_client = cloud_service.make_client(storage_info) + existing_storage_files = set( + storage_client.list_files( + prefix=compose_results_bucket_filename( + project.escrow_address, + project.chain_id, + "", + ), + ) + ) + for file_descriptor in annotation_files: + if file_descriptor.filename in existing_storage_files: + continue + + storage_client.create_file( + compose_results_bucket_filename( + project.escrow_address, + project.chain_id, + file_descriptor.filename, + ), + file_descriptor.file.read(), + ) + + self._notify_escrow_completed( + project.escrow_address, project.chain_id, db_session=session + ) + + cvat_service.update_project_status(session, project.id, ProjectStatuses.validation) + + logger.info( + f"The project (escrow_address={project.escrow_address}) " + "is completed, resulting annotations are processed successfully" + ) + + def _notify_escrow_completed(self, escrow_address: str, chain_id: int, *, db_session: Session): + oracle_db_service.outbox.create_webhook( + db_session, + escrow_address, + chain_id, + OracleWebhookTypes.recording_oracle, + event=ExchangeOracleEvent_TaskFinished(), + ) + + def _process_skeletons_from_boxes_escrows(self): + logger = self.logger + + # Here we can have several projects per escrow, so the handling is done in project groups + with SessionLocal.begin() as session: + completed_projects = cvat_service.get_projects_by_status( + session, + ProjectStatuses.completed, + included_types=[TaskTypes.image_skeletons_from_boxes], + limit=CronConfig.track_completed_escrows_chunk_size, + ) + + escrows_with_completed_projects = set() + for completed_project in completed_projects: + # Check if all jobs within the project are completed + if not cvat_service.is_project_completed(session, completed_project.id): + cvat_service.update_project_status( + session, completed_project.id, ProjectStatuses.annotation + ) + continue + + try: + # TODO: such escrows can fill all the queried completed projects + # need to improve handling for such projects + # (e.g. cancel depending on the escrow status) + validate_escrow(completed_project.chain_id, completed_project.escrow_address) + except Exception as e: + logger.error( + "Failed to handle completed projects for escrow {}: {}".format( + escrow_address, e + ) + ) + continue + + escrows_with_completed_projects.add( + (completed_project.escrow_address, completed_project.chain_id) + ) + + for escrow_address, chain_id in escrows_with_completed_projects: + # TODO: should throw a db lock exception if lock is not available + # need to skip the escrow in this case. + # Maybe there is a better way that utilizes skip_locked + try: + escrow_projects = cvat_service.get_projects_by_escrow_address( + session, escrow_address, limit=None, for_update=ForUpdateParams(nowait=True) + ) + except db_exc.OperationalError as ex: + if "could not obtain lock on row" in str(ex): + continue + raise + + completed_escrow_projects = [ + p + for p in escrow_projects + if p.status + in [ + ProjectStatuses.completed, + ProjectStatuses.validation, # TODO: think about this list + ] + ] + if len(escrow_projects) != len(completed_escrow_projects): + continue + + manifest = parse_manifest(get_escrow_manifest(chain_id, escrow_address)) + + logger.debug( + f"Downloading results for the escrow (escrow_address={escrow_address})" + ) + + cvat_jobs: List[cvat_models.Job] = list( + itertools.chain.from_iterable( + cvat_service.get_jobs_by_cvat_project_id(session, p.cvat_id) + for p in escrow_projects + ) + ) + + # Request dataset preparation beforehand + annotation_format = CVAT_EXPORT_FORMAT_MAPPING[manifest.annotation.type] + for cvat_job in cvat_jobs: + cvat_api.request_job_annotations( + cvat_job.cvat_id, format_name=annotation_format + ) + + # Collect raw annotations from CVAT, validate and convert them + # into a recording oracle suitable format + job_annotations: Dict[int, FileDescriptor] = {} + for cvat_job in cvat_jobs: + job_annotations_file = cvat_api.get_job_annotations( + cvat_job.cvat_id, format_name=annotation_format + ) + job_assignment = cvat_job.latest_assignment + job_annotations[cvat_job.cvat_id] = FileDescriptor( + filename="project_{}-task_{}-job_{}-user_{}-assignment_{}.zip".format( + cvat_job.cvat_project_id, + cvat_job.cvat_task_id, + cvat_job.cvat_id, + job_assignment.user.cvat_id, + job_assignment.id, + ), + file=job_annotations_file, + ) + + resulting_annotations_file_desc = FileDescriptor( + filename=RESULTING_ANNOTATIONS_FILE, + file=None, + ) + + annotation_files: List[FileDescriptor] = [] + annotation_files.append(resulting_annotations_file_desc) + + annotation_metafile = prepare_annotation_metafile( + jobs=cvat_jobs, job_annotations=job_annotations + ) + annotation_files.extend(job_annotations.values()) + postprocess_annotations( + escrow_address=escrow_address, + chain_id=chain_id, + annotations=annotation_files, + merged_annotation=resulting_annotations_file_desc, + manifest=manifest, + project_images=None, + ) + + annotation_files.append(annotation_metafile) + + storage_info = BucketAccessInfo.parse_obj(StorageConfig) + storage_client = cloud_service.make_client(storage_info) + existing_storage_files = set( + storage_client.list_files( + prefix=compose_results_bucket_filename( + escrow_address, + chain_id, + "", + ), + ) + ) + for file_descriptor in annotation_files: + if file_descriptor.filename in existing_storage_files: + continue + + storage_client.create_file( + compose_results_bucket_filename( + escrow_address, + chain_id, + file_descriptor.filename, + ), + file_descriptor.file.read(), + ) + + self._notify_escrow_completed(escrow_address, chain_id, db_session=session) + + for project in escrow_projects: + cvat_service.update_project_status( + session, project.id, ProjectStatuses.validation + ) + + logger.info( + f"The escrow (escrow_address={project.escrow_address}) " + "is completed, resulting annotations are processed successfully" + ) + + def process(self): + self._process_plain_escrows() + self._process_skeletons_from_boxes_escrows() + + +def handle_completed_escrows(logger: logging.Logger) -> None: + handler = _CompletedEscrowsHandler(logger=logger) + handler.process() diff --git a/packages/examples/cvat/exchange-oracle/src/handlers/cvat_events.py b/packages/examples/cvat/exchange-oracle/src/handlers/cvat_events.py index 1c2f40c290..39a6b972d9 100644 --- a/packages/examples/cvat/exchange-oracle/src/handlers/cvat_events.py +++ b/packages/examples/cvat/exchange-oracle/src/handlers/cvat_events.py @@ -5,7 +5,7 @@ import src.cvat.api_calls as cvat_api import src.models.cvat as models import src.services.cvat as cvat_service -from src.core.types import AssignmentStatus, CvatEventTypes, JobStatuses +from src.core.types import AssignmentStatuses, CvatEventTypes, JobStatuses from src.db import SessionLocal from src.log import ROOT_LOGGER_NAME from src.utils.logging import get_function_logger @@ -29,6 +29,7 @@ def handle_update_job_event(payload: dict) -> None: if "state" in payload.before_update: job_assignments = job.assignments + new_status = JobStatuses(payload.job["state"]) if not job_assignments: logger.warning( @@ -36,7 +37,6 @@ def handle_update_job_event(payload: dict) -> None: "No assignments for this job, ignoring the update" ) else: - new_status = JobStatuses(payload.job["state"]) webhook_time = parse_aware_datetime(payload.job["updated_date"]) webhook_assignee_id = (payload.job["assignee"] or {}).get("id") @@ -60,7 +60,7 @@ def handle_update_job_event(payload: dict) -> None: "Can't find a matching assignment, ignoring the update" ) elif matching_assignment.is_finished: - if matching_assignment.status == AssignmentStatus.created: + if matching_assignment.status == AssignmentStatuses.created: logger.warning( f"Received job #{job.cvat_id} status update: {new_status.value}. " "Assignment is expired, rejecting the update" @@ -78,7 +78,7 @@ def handle_update_job_event(payload: dict) -> None: elif ( new_status == JobStatuses.completed and matching_assignment.id == latest_assignment.id - and matching_assignment.status == AssignmentStatus.created + and matching_assignment.status == AssignmentStatuses.created ): logger.info( f"Received job #{job.cvat_id} status update: {new_status.value}. " diff --git a/packages/examples/cvat/exchange-oracle/src/handlers/job_creation.py b/packages/examples/cvat/exchange-oracle/src/handlers/job_creation.py new file mode 100644 index 0000000000..6f4c1ee236 --- /dev/null +++ b/packages/examples/cvat/exchange-oracle/src/handlers/job_creation.py @@ -0,0 +1,2225 @@ +from __future__ import annotations + +import os +import random +import uuid +from contextlib import ExitStack +from dataclasses import dataclass, field +from itertools import chain, groupby +from logging import Logger +from math import ceil +from tempfile import TemporaryDirectory +from typing import Dict, List, Sequence, Tuple, TypeVar, Union, cast + +import cv2 +import datumaro as dm +import numpy as np +from datumaro.util import take_by +from datumaro.util.annotation_util import BboxCoords, bbox_iou +from datumaro.util.image import IMAGE_EXTENSIONS, decode_image, encode_image + +import src.core.tasks.boxes_from_points as boxes_from_points_task +import src.core.tasks.skeletons_from_boxes as skeletons_from_boxes_task +import src.cvat.api_calls as cvat_api +import src.services.cloud as cloud_service +import src.services.cvat as db_service +from src.chain.escrow import get_escrow_manifest +from src.core.config import Config +from src.core.manifest import TaskManifest +from src.core.storage import compose_data_bucket_filename +from src.core.types import CvatLabelTypes, TaskStatuses, TaskTypes +from src.db import SessionLocal +from src.log import ROOT_LOGGER_NAME +from src.services.cloud import CloudProviders, StorageClient +from src.services.cloud.utils import BucketAccessInfo, compose_bucket_url +from src.utils.annotations import InstanceSegmentsToBbox, ProjectLabels, is_point_in_bbox +from src.utils.assignments import parse_manifest +from src.utils.logging import NullLogger, get_function_logger + +module_logger = f"{ROOT_LOGGER_NAME}.cron.cvat" + +LABEL_TYPE_MAPPING = { + TaskTypes.image_label_binary: CvatLabelTypes.tag, + TaskTypes.image_points: CvatLabelTypes.points, + TaskTypes.image_boxes: CvatLabelTypes.rectangle, + TaskTypes.image_boxes_from_points: CvatLabelTypes.rectangle, + TaskTypes.image_skeletons_from_boxes: CvatLabelTypes.points, +} + +DM_DATASET_FORMAT_MAPPING = { + TaskTypes.image_label_binary: "cvat_images", + TaskTypes.image_points: "coco_person_keypoints", + TaskTypes.image_boxes: "coco_instances", + TaskTypes.image_boxes_from_points: "coco_instances", + TaskTypes.image_skeletons_from_boxes: "coco_person_keypoints", +} + +DM_GT_DATASET_FORMAT_MAPPING = { + # GT uses the same format both for boxes and points + TaskTypes.image_label_binary: "cvat_images", + TaskTypes.image_points: "coco_instances", + TaskTypes.image_boxes: "coco_instances", + TaskTypes.image_boxes_from_points: "coco_instances", + TaskTypes.image_skeletons_from_boxes: "coco_person_keypoints", +} + + +class DatasetValidationError(Exception): + pass + + +class MismatchingAnnotations(DatasetValidationError): + pass + + +class TooFewSamples(DatasetValidationError): + pass + + +class InvalidCategories(DatasetValidationError): + pass + + +class InvalidImageInfo(DatasetValidationError): + pass + + +class InvalidCoordinates(DatasetValidationError): + pass + + +T = TypeVar("T") + + +class _Undefined: + def __bool__(self) -> bool: + return False + + +_unset = _Undefined() + +_MaybeUnset = Union[T, _Undefined] + + +@dataclass +class _ExcludedAnnotationInfo: + message: str + sample_id: str = field(kw_only=True) + sample_subset: str = field(kw_only=True) + + +@dataclass +class _ExcludedAnnotationsInfo: + errors: List[_ExcludedAnnotationInfo] = field(default_factory=list) + + excluded_count: int = 0 + "The number of excluded annotations. Can be different from len(error_messages)" + + total_count: int = 0 + + def add_error(self, message: str, *, sample_id: str, sample_subset: str): + self.errors.append( + _ExcludedAnnotationInfo( + message=message, sample_id=sample_id, sample_subset=sample_subset + ) + ) + + +class BoxesFromPointsTaskBuilder: + def __init__(self, manifest: TaskManifest, escrow_address: str, chain_id: int): + self.exit_stack = ExitStack() + self.manifest = manifest + self.escrow_address = escrow_address + self.chain_id = chain_id + + self.logger: Logger = NullLogger() + + self._input_gt_data: _MaybeUnset[bytes] = _unset + self._input_points_data: _MaybeUnset[bytes] = _unset + + self._data_filenames: _MaybeUnset[Sequence[str]] = _unset + self._gt_dataset: _MaybeUnset[dm.Dataset] = _unset + self._points_dataset: _MaybeUnset[dm.Dataset] = _unset + + self._bbox_point_mapping: _MaybeUnset[boxes_from_points_task.BboxPointMapping] = _unset + "bbox_id -> point_id" + + self._roi_size_estimations: _MaybeUnset[Dict[int, Tuple[float, float]]] = _unset + "label_id -> (rel. w, rel. h)" + + self._rois: _MaybeUnset[boxes_from_points_task.RoiInfos] = _unset + self._roi_filenames: _MaybeUnset[boxes_from_points_task.RoiFilenames] = _unset + + self._job_layout: _MaybeUnset[Sequence[Sequence[str]]] = _unset + "File lists per CVAT job" + + self._label_configuration: _MaybeUnset[Sequence[dict]] = _unset + + self._excluded_points_info: _MaybeUnset[_ExcludedAnnotationsInfo] = _unset + self._excluded_gt_info: _MaybeUnset[_ExcludedAnnotationsInfo] = _unset + + # Configuration / constants + # TODO: consider WebP if produced files are too big + self.roi_file_ext = ".png" # supposed to be lossless and reasonably compressing + "File extension for RoI images, with leading dot (.) included" + + self.sample_error_display_threshold = 5 + "The maximum number of rendered list items in a message" + + self.roi_size_mult = 1.1 + "Additional point ROI size multiplier" + + self.points_format = "coco_person_keypoints" + + self.embed_point_in_roi_image = True + "Put a small point into the extracted RoI images for the original point" + + self.embedded_point_radius = 15 + self.min_embedded_point_radius_percent = 0.005 + self.max_embedded_point_radius_percent = 0.01 + self.embedded_point_color = (0, 255, 255) + self.roi_background_color = (245, 240, 242) # BGR - CVAT background color + + self.oracle_data_bucket = BucketAccessInfo.parse_obj(Config.storage_config) + + self.min_class_samples_for_roi_estimation = 25 + + self.max_class_roi_image_side_threshold = 0.5 + """ + The maximum allowed percent of the image for the estimated class RoI, + before the default RoI is used. Too big RoI estimations reduce the overall + prediction quality, making them unreliable. + """ + + self.max_discarded_threshold = 0.05 + """ + The maximum allowed percent of discarded + GT boxes, points, or samples for successful job launch + """ + + def __enter__(self): + return self + + def __exit__(self, *args, **kwargs): + self.close() + + def close(self): + self.exit_stack.close() + + def set_logger(self, logger: Logger): + # TODO: add escrow info into messages + self.logger = logger + return self + + def _download_input_data(self): + data_bucket = BucketAccessInfo.parse_obj(self.manifest.data.data_url) + gt_bucket = BucketAccessInfo.parse_obj(self.manifest.validation.gt_url) + points_bucket = BucketAccessInfo.parse_obj(self.manifest.data.points_url) + + data_storage_client = self._make_cloud_storage_client(data_bucket) + gt_storage_client = self._make_cloud_storage_client(gt_bucket) + points_storage_client = self._make_cloud_storage_client(points_bucket) + + data_filenames = data_storage_client.list_files(prefix=data_bucket.path) + data_filenames = strip_bucket_prefix(data_filenames, prefix=data_bucket.path) + self._data_filenames = filter_image_files(data_filenames) + + self._input_gt_data = gt_storage_client.download_file(gt_bucket.path) + + self._input_points_data = points_storage_client.download_file(points_bucket.path) + + def _parse_dataset(self, annotation_file_data: bytes, dataset_format: str) -> dm.Dataset: + temp_dir = self.exit_stack.enter_context(TemporaryDirectory()) + + annotation_filename = os.path.join(temp_dir, "annotations.json") + with open(annotation_filename, "wb") as f: + f.write(annotation_file_data) + + return dm.Dataset.import_from(annotation_filename, format=dataset_format) + + def _parse_gt(self): + assert self._input_gt_data is not _unset + + self._gt_dataset = self._parse_dataset( + self._input_gt_data, + dataset_format=DM_GT_DATASET_FORMAT_MAPPING[self.manifest.annotation.type], + ) + + def _parse_points(self): + assert self._input_points_data is not _unset + + self._points_dataset = self._parse_dataset( + self._input_points_data, dataset_format=self.points_format + ) + + def _validate_gt_labels(self): + gt_labels = set( + label.name + for label in self._gt_dataset.categories()[dm.AnnotationType.label] + if not label.parent + ) + manifest_labels = set(label.name for label in self.manifest.annotation.labels) + if gt_labels - manifest_labels: + raise DatasetValidationError( + "GT labels do not match job labels. Unknown labels: {}".format( + self._format_list(list(gt_labels - manifest_labels)), + ) + ) + + self._gt_dataset.transform( + ProjectLabels, dst_labels=[label.name for label in self.manifest.annotation.labels] + ) + self._gt_dataset.init_cache() + + def _validate_gt_filenames(self): + gt_filenames = set(s.id + s.media.ext for s in self._gt_dataset) + + known_data_filenames = set(self._data_filenames) + matched_gt_filenames = gt_filenames.intersection(known_data_filenames) + + if len(gt_filenames) != len(matched_gt_filenames): + extra_gt = list(map(os.path.basename, gt_filenames - matched_gt_filenames)) + + raise MismatchingAnnotations( + "Failed to find several validation samples in the dataset files: {}".format( + self._format_list(extra_gt) + ) + ) + + if len(gt_filenames) < self.manifest.validation.val_size: + raise TooFewSamples( + f"Too few validation samples provided ({len(gt_filenames)}), " + f"at least {self.manifest.validation.val_size} required." + ) + + def _validate_gt_annotations(self): + label_cat: dm.LabelCategories = self._gt_dataset.categories()[dm.AnnotationType.label] + + excluded_gt_info = _ExcludedAnnotationsInfo() + excluded_samples = set() + visited_ids = set() + for gt_sample in self._gt_dataset: + # Could fail on this as well + img_h, img_w = gt_sample.media_as(dm.Image).size + + sample_boxes = [a for a in gt_sample.annotations if isinstance(a, dm.Bbox)] + valid_boxes = [] + for bbox in sample_boxes: + if not ( + (0 <= bbox.x < bbox.x + bbox.w <= img_w) + and (0 <= bbox.y < bbox.y + bbox.h <= img_h) + ): + excluded_gt_info.add_error( + "Sample '{}': GT bbox #{} ({}) - invalid coordinates. " + "The image will be skipped".format( + gt_sample.id, bbox.id, label_cat[bbox.label].name + ), + sample_id=gt_sample.id, + sample_subset=gt_sample.subset, + ) + valid_boxes = [] + break + + if bbox.id in visited_ids: + excluded_gt_info.add_error( + "Sample '{}': GT bbox #{} ({}) skipped - repeated annotation id {}".format( + gt_sample.id, bbox.id, label_cat[bbox.label].name, bbox.id + ), + sample_id=gt_sample.id, + sample_subset=gt_sample.subset, + ) + + valid_boxes.append(bbox) + + excluded_gt_info.excluded_count += len(sample_boxes) - len(valid_boxes) + excluded_gt_info.total_count += len(sample_boxes) + + if len(valid_boxes) != len(sample_boxes): + if not valid_boxes: + excluded_samples.add((gt_sample.id, gt_sample.subset)) + else: + self._gt_dataset.put(gt_sample.wrap(annotations=valid_boxes)) + + for excluded_sample in excluded_samples: + self._gt_dataset.remove(*excluded_sample) + + if excluded_gt_info.excluded_count: + self.logger.warning( + "Some GT boxes were excluded due to the errors found: {}".format( + self._format_list([e.message for e in excluded_gt_info.errors], separator="\n") + ) + ) + + if ( + excluded_gt_info.excluded_count + > excluded_gt_info.total_count * self.max_discarded_threshold + ): + raise TooFewSamples( + "Too many GT boxes discarded, canceling job creation. Errors: {}".format( + self._format_list( + [error_info.message for error_info in excluded_gt_info.errors] + ) + ) + ) + + self._excluded_gt_info = excluded_gt_info + + def _validate_gt(self): + assert self._data_filenames is not _unset + assert self._gt_dataset is not _unset + + self._validate_gt_filenames() + self._validate_gt_labels() + self._validate_gt_annotations() + + def _format_list( + self, items: Sequence[str], *, max_items: int = None, separator: str = ", " + ) -> str: + if max_items is None: + max_items = self.sample_error_display_threshold + + remainder_count = len(items) - max_items + return "{}{}".format( + separator.join(items[:max_items]), + f" (and {remainder_count} more)" if remainder_count > 0 else "", + ) + + def _validate_points_categories(self): + invalid_point_categories_messages = [] + points_dataset_categories = self._points_dataset.categories() + points_dataset_label_cat: dm.LabelCategories = points_dataset_categories[ + dm.AnnotationType.label + ] + for category_id, category in points_dataset_categories[ + dm.AnnotationType.points + ].items.items(): + if len(category.labels) != 1: + invalid_point_categories_messages.append( + "Category '{}' (#{}): {}".format( + points_dataset_label_cat[category_id].name, + category_id, + f"too many skeleton points ({len(category.labels)}), only 1 expected", + ) + ) + + if invalid_point_categories_messages: + raise InvalidCategories( + "Invalid categories in the input point annotations: {}".format( + self._format_list(invalid_point_categories_messages, separator="; ") + ) + ) + + points_labels = set(label.name for label in points_dataset_label_cat if not label.parent) + manifest_labels = set(label.name for label in self.manifest.annotation.labels) + if manifest_labels != points_labels: + raise DatasetValidationError("Point labels do not match job labels") + + self._points_dataset.transform( + ProjectLabels, dst_labels=[label.name for label in self.manifest.annotation.labels] + ) + self._points_dataset.init_cache() + + def _validate_points_filenames(self): + points_filenames = set(sample.id + sample.media.ext for sample in self._points_dataset) + + known_data_filenames = set(self._data_filenames) + matched_points_filenames = points_filenames.intersection(known_data_filenames) + + if len(matched_points_filenames) != len(points_filenames): + extra_point_samples = list( + map(os.path.basename, points_filenames - matched_points_filenames) + ) + + raise MismatchingAnnotations( + "Failed to find several samples in the dataset files: {}".format( + self._format_list(extra_point_samples), + ) + ) + + def _validate_points_annotations(self): + def _validate_skeleton(skeleton: dm.Skeleton, *, sample_bbox: dm.Bbox): + if skeleton.id in visited_ids: + raise DatasetValidationError(f"repeated annotation id ({skeleton.id})") + + if len(skeleton.elements) != 1: + raise DatasetValidationError( + "invalid points count ({}), expected 1".format(len(skeleton.elements)) + ) + + point = skeleton.elements[0] + px, py = point.points[:2] + if not is_point_in_bbox(px, py, sample_bbox): + raise InvalidCoordinates("coordinates are outside image") + + label_cat: dm.LabelCategories = self._points_dataset.categories()[dm.AnnotationType.label] + + excluded_points_info = _ExcludedAnnotationsInfo() + excluded_samples = set() + visited_ids = set() + for sample in self._points_dataset: + # Could fail on this as well + image_h, image_w = sample.image.size + sample_bbox = dm.Bbox(0, 0, w=image_w, h=image_h) + + sample_skeletons = [a for a in sample.annotations if isinstance(a, dm.Skeleton)] + valid_skeletons = [] + for skeleton in sample_skeletons: + # Here 1 skeleton describes 1 point + try: + _validate_skeleton(skeleton, sample_bbox=sample_bbox) + except InvalidCoordinates as error: + excluded_points_info.add_error( + "Sample '{}': point #{} ({}) - {}. " + "The image will be skipped".format( + sample.id, skeleton.id, label_cat[skeleton.label].name, error + ), + sample_id=sample.id, + sample_subset=sample.subset, + ) + valid_skeletons = [] + break + except DatasetValidationError as error: + excluded_points_info.add_error( + "Sample '{}': point #{} ({}) - {}".format( + sample.id, skeleton.id, label_cat[skeleton.label].name, error + ), + sample_id=sample.id, + sample_subset=sample.subset, + ) + + valid_skeletons.append(skeleton) + + excluded_points_info.excluded_count += len(sample_skeletons) - len(valid_skeletons) + excluded_points_info.total_count += len(sample_skeletons) + + if len(valid_skeletons) != len(sample_skeletons): + if not valid_skeletons: + excluded_samples.add((sample.id, sample.subset)) + else: + self._points_dataset.put(sample.wrap(annotations=valid_skeletons)) + + for excluded_sample in excluded_samples: + self._points_dataset.remove(*excluded_sample) + + if excluded_points_info.excluded_count: + self.logger.warning( + "Some points were excluded due to the errors found: {}".format( + self._format_list( + [e.message for e in excluded_points_info.errors], separator="\n" + ) + ) + ) + + if ( + excluded_points_info.excluded_count + > excluded_points_info.total_count * self.max_discarded_threshold + ): + raise TooFewSamples( + "Too many points discarded, canceling job creation. Errors: {}".format( + self._format_list( + [error_info.message for error_info in excluded_points_info.errors] + ) + ) + ) + + self._excluded_points_info = excluded_points_info + + def _validate_points(self): + assert self._data_filenames is not _unset + assert self._points_dataset is not _unset + + self._validate_points_categories() + self._validate_points_filenames() + self._validate_points_annotations() + + @staticmethod + def _is_point_in_bbox(px: float, py: float, bbox: dm.Bbox) -> bool: + return is_point_in_bbox(px, py, bbox) + + def _prepare_gt(self): + assert self._data_filenames is not _unset + assert self._points_dataset is not _unset + assert self._gt_dataset is not _unset + assert [label.name for label in self._gt_dataset.categories()[dm.AnnotationType.label]] == [ + label.name for label in self.manifest.annotation.labels + ] + assert [ + label.name + for label in self._points_dataset.categories()[dm.AnnotationType.label] + if not label.parent + ] == [label.name for label in self.manifest.annotation.labels] + + gt_dataset = dm.Dataset(categories=self._gt_dataset.categories(), media_type=dm.Image) + + gt_label_cat: dm.LabelCategories = self._gt_dataset.categories()[dm.AnnotationType.label] + + excluded_gt_info = self._excluded_gt_info + gt_count_per_class = {} + bbox_point_mapping = {} # bbox id -> point id + for gt_sample in self._gt_dataset: + points_sample = self._points_dataset.get(gt_sample.id, gt_sample.subset) + assert points_sample + + gt_boxes = [a for a in gt_sample.annotations if isinstance(a, dm.Bbox)] + input_skeletons = [a for a in points_sample.annotations if isinstance(a, dm.Skeleton)] + + # Samples without boxes are allowed, so we just skip them without an error + if not gt_boxes: + continue + + matched_boxes = [] + visited_skeletons = set() + for gt_bbox in gt_boxes: + gt_bbox_id = gt_bbox.id + + if len(visited_skeletons) == len(gt_boxes): + # Handle unmatched boxes + excluded_gt_info.add_error( + "Sample '{}': GT bbox #{} ({}) skipped - " + "no matching points found".format( + gt_sample.id, gt_bbox_id, gt_label_cat[gt_bbox.label].name + ), + sample_id=gt_sample.id, + sample_subset=gt_sample.subset, + ) + excluded_gt_info.excluded_count += 1 + continue + + matched_skeletons: List[dm.Skeleton] = [] + for input_skeleton in input_skeletons: + skeleton_id = input_skeleton.id + if skeleton_id in visited_skeletons: + continue + + input_point = input_skeleton.elements[0] + if not self._is_point_in_bbox(*input_point.points[0:2], bbox=gt_bbox): + continue + + if input_skeleton.label != gt_bbox.label: + continue + + matched_skeletons.append(input_skeleton) + visited_skeletons.add(skeleton_id) + + if len(matched_skeletons) > 1: + # Handle ambiguous matches + excluded_gt_info.add_error( + "Sample '{}': GT bbox #{} ({}) skipped - " + "too many matching points ({}) found".format( + gt_sample.id, + gt_bbox_id, + gt_label_cat[gt_bbox.label].name, + len(matched_skeletons), + ), + sample_id=gt_sample.id, + sample_subset=gt_sample.subset, + ) + excluded_gt_info.excluded_count += 1 + continue + elif len(matched_skeletons) == 0: + # Handle unmatched boxes + excluded_gt_info.add_error( + "Sample '{}': GT bbox #{} ({}) skipped - " + "no matching points found".format( + gt_sample.id, + gt_bbox_id, + gt_label_cat[gt_bbox.label].name, + ), + sample_id=gt_sample.id, + sample_subset=gt_sample.subset, + ) + excluded_gt_info.excluded_count += 1 + continue + + gt_count_per_class[gt_bbox.label] = gt_count_per_class.get(gt_bbox.label, 0) + 1 + + matched_boxes.append(gt_bbox) + bbox_point_mapping[gt_bbox_id] = matched_skeletons[0].id + + if not matched_boxes: + continue + + gt_dataset.put(gt_sample.wrap(annotations=matched_boxes)) + + if excluded_gt_info.excluded_count: + self.logger.warning( + "Some GT annotations were excluded due to the errors found: {}".format( + self._format_list([e.message for e in excluded_gt_info.errors], separator="\n") + ) + ) + + if ( + excluded_gt_info.excluded_count + > excluded_gt_info.total_count * self.max_discarded_threshold + ): + raise DatasetValidationError( + "Too many GT boxes discarded ({} out of {}). " + "Please make sure each GT box matches exactly 1 point".format( + excluded_gt_info.total_count - len(bbox_point_mapping), + excluded_gt_info.total_count, + ) + ) + + gt_labels_without_anns = [ + gt_label_cat[label_id] + for label_id, label_count in gt_count_per_class.items() + if not label_count + ] + if gt_labels_without_anns: + raise DatasetValidationError( + "No matching GT boxes/points annotations found for some classes: {}".format( + self._format_list(gt_labels_without_anns) + ) + ) + + self._gt_dataset = gt_dataset + self._bbox_point_mapping = bbox_point_mapping + + def _estimate_roi_sizes(self): + assert self._gt_dataset is not _unset + assert [label.name for label in self._gt_dataset.categories()[dm.AnnotationType.label]] == [ + label.name for label in self.manifest.annotation.labels + ] + + bbox_sizes_per_label = {} + for sample in self._gt_dataset: + image_h, image_w = self._points_dataset.get(sample.id, sample.subset).image.size + + for gt_bbox in sample.annotations: + gt_bbox = cast(dm.Bbox, gt_bbox) + bbox_sizes_per_label.setdefault(gt_bbox.label, []).append( + ( + gt_bbox.w / image_w, + gt_bbox.h / image_h, + ) + ) + + # Consider bbox sides as normally-distributed random variables, estimate max + # For big enough datasets, it should be reasonable approximation + # (due to the central limit theorem). This can work bad for small datasets, + # so we only do this if there are enough class samples. + classes_with_default_roi: dict[int, str] = {} # label_id -> reason + roi_size_estimations_per_label = {} # label id -> (w, h) + default_roi_size = (2, 2) # 2 will yield just the image size after halving + for label_id, label_sizes in bbox_sizes_per_label.items(): + if len(label_sizes) < self.min_class_samples_for_roi_estimation: + estimated_size = default_roi_size + classes_with_default_roi[label_id] = "too few GT provided" + else: + max_bbox = np.max(label_sizes, axis=0) + if np.any(max_bbox > self.max_class_roi_image_side_threshold): + estimated_size = default_roi_size + classes_with_default_roi[label_id] = "estimated RoI is unreliable" + else: + estimated_size = 2 * max_bbox * self.roi_size_mult + + roi_size_estimations_per_label[label_id] = estimated_size + + if classes_with_default_roi: + label_cat = self._gt_dataset.categories()[dm.AnnotationType.label] + labels_by_reason = { + g_reason: list(v[0] for v in g_items) + for g_reason, g_items in groupby( + sorted(classes_with_default_roi.items(), key=lambda v: v[1]), key=lambda v: v[1] + ) + } + self.logger.warning( + "Some classes will use the full image instead of RoI - {}".format( + "; ".join( + "{}: {}".format( + g_reason, + self._format_list([label_cat[label_id].name for label_id in g_labels]), + ) + for g_reason, g_labels in labels_by_reason.items() + ) + ) + ) + + self._roi_size_estimations = roi_size_estimations_per_label + + def _prepare_roi_info(self): + assert self._gt_dataset is not _unset + assert self._roi_size_estimations is not _unset + assert self._points_dataset is not _unset + + rois: List[boxes_from_points_task.RoiInfo] = [] + for sample in self._points_dataset: + for skeleton in sample.annotations: + if not isinstance(skeleton, dm.Skeleton): + continue + + point_label_id = skeleton.label + original_point_x, original_point_y = skeleton.elements[0].points[:2] + original_point_x = int(original_point_x) + original_point_y = int(original_point_y) + + image_h, image_w = sample.image.size + + roi_est_w, roi_est_h = self._roi_size_estimations[point_label_id] + roi_est_w *= image_w + roi_est_h *= image_h + + roi_left = max(0, original_point_x - int(roi_est_w / 2)) + roi_top = max(0, original_point_y - int(roi_est_h / 2)) + roi_right = min(image_w, original_point_x + ceil(roi_est_w / 2)) + roi_bottom = min(image_h, original_point_y + ceil(roi_est_h / 2)) + + roi_w = roi_right - roi_left + roi_h = roi_bottom - roi_top + + new_point_x = original_point_x - roi_left + new_point_y = original_point_y - roi_top + + rois.append( + boxes_from_points_task.RoiInfo( + point_id=skeleton.id, + original_image_key=sample.attributes["id"], + point_x=new_point_x, + point_y=new_point_y, + roi_x=roi_left, + roi_y=roi_top, + roi_w=roi_w, + roi_h=roi_h, + ) + ) + + self._rois = rois + + def _mangle_filenames(self): + """ + Mangle filenames in the dataset to make them less recognizable by annotators + and hide private dataset info + """ + assert self._rois is not _unset + + # TODO: maybe add different names for the same GT images in + # different jobs to make them even less recognizable + self._roi_filenames = { + roi.point_id: str(uuid.uuid4()) + self.roi_file_ext for roi in self._rois + } + + def _prepare_job_layout(self): + # Make job layouts wrt. manifest params + # 1 job per task as CVAT can't repeat images in jobs, but GTs can repeat in the dataset + + assert self._rois is not _unset + assert self._bbox_point_mapping is not _unset + + gt_point_ids = set(self._bbox_point_mapping.values()) + gt_filenames = [self._roi_filenames[point_id] for point_id in gt_point_ids] + + data_filenames = [ + fn for point_id, fn in self._roi_filenames.items() if not point_id in gt_point_ids + ] + random.shuffle(data_filenames) + + job_layout = [] + for data_samples in take_by(data_filenames, self.manifest.annotation.job_size): + gt_samples = random.sample(gt_filenames, k=self.manifest.validation.val_size) + job_samples = list(data_samples) + list(gt_samples) + random.shuffle(job_samples) + job_layout.append(job_samples) + + self._job_layout = job_layout + + def _prepare_label_configuration(self): + self._label_configuration = make_label_configuration(self.manifest) + + def _upload_task_meta(self): + layout = boxes_from_points_task.TaskMetaLayout() + serializer = boxes_from_points_task.TaskMetaSerializer() + + file_list = [] + file_list.append((self._input_points_data, layout.POINTS_FILENAME)) + file_list.append( + ( + serializer.serialize_gt_annotations(self._gt_dataset), + layout.GT_FILENAME, + ) + ) + file_list.append( + ( + serializer.serialize_bbox_point_mapping(self._bbox_point_mapping), + layout.BBOX_POINT_MAPPING_FILENAME, + ) + ) + file_list.append((serializer.serialize_roi_info(self._rois), layout.ROI_INFO_FILENAME)) + file_list.append( + (serializer.serialize_roi_filenames(self._roi_filenames), layout.ROI_FILENAMES_FILENAME) + ) + + storage_client = self._make_cloud_storage_client(self.oracle_data_bucket) + for file_data, filename in file_list: + storage_client.create_file( + compose_data_bucket_filename(self.escrow_address, self.chain_id, filename), + file_data, + ) + + def _extract_roi( + self, source_pixels: np.ndarray, roi_info: boxes_from_points_task.RoiInfo + ) -> np.ndarray: + img_h, img_w, *_ = source_pixels.shape + + roi_pixels = source_pixels[ + max(0, roi_info.roi_y) : min(img_h, roi_info.roi_y + roi_info.roi_h), + max(0, roi_info.roi_x) : min(img_w, roi_info.roi_x + roi_info.roi_w), + ] + + if not ( + (0 <= roi_info.roi_x < roi_info.roi_x + roi_info.roi_w < img_w) + and (0 <= roi_info.roi_y < roi_info.roi_y + roi_info.roi_h < img_h) + ): + # Coords can be outside the original image + # In this case a border should be added to RoI, so that the image was centered on bbox + wrapped_roi_pixels = np.zeros((roi_info.roi_h, roi_info.roi_w, 3), dtype=np.float32) + wrapped_roi_pixels[:, :] = self.roi_background_color + + dst_y = max(-roi_info.roi_y, 0) + dst_x = max(-roi_info.roi_x, 0) + wrapped_roi_pixels[ + dst_y : dst_y + roi_pixels.shape[0], + dst_x : dst_x + roi_pixels.shape[1], + ] = roi_pixels + + roi_pixels = wrapped_roi_pixels + else: + roi_pixels = roi_pixels.copy() + + return roi_pixels + + def _draw_roi_point( + self, roi_pixels: np.ndarray, roi_info: boxes_from_points_task.RoiInfo + ) -> np.ndarray: + center = (roi_info.point_x, roi_info.point_y) + + roi_r = (roi_info.roi_w**2 + roi_info.roi_h**2) ** 0.5 / 2 + point_size = int( + min( + self.max_embedded_point_radius_percent * roi_r, + max(self.embedded_point_radius, self.min_embedded_point_radius_percent * roi_r), + ) + ) + + roi_pixels = roi_pixels.copy() + roi_pixels = cv2.circle( + roi_pixels, + center, + point_size + 1, + (255, 255, 255), + cv2.FILLED, + ) + roi_pixels = cv2.circle( + roi_pixels, + center, + point_size, + self.embedded_point_color, + cv2.FILLED, + ) + + return roi_pixels + + def _extract_and_upload_rois(self): + # TODO: maybe optimize via splitting into separate threads (downloading, uploading, processing) + + # Watch for the memory used, as the whole dataset can be quite big (gigabytes, terabytes) + # Consider also packing RoIs cut into archives + assert self._points_dataset is not _unset + assert self._rois is not _unset + assert self._data_filenames is not _unset + assert self._roi_filenames is not _unset + + src_bucket = BucketAccessInfo.parse_obj(self.manifest.data.data_url) + src_prefix = src_bucket.path + dst_bucket = self.oracle_data_bucket + + src_client = self._make_cloud_storage_client(src_bucket) + dst_client = self._make_cloud_storage_client(dst_bucket) + + image_id_to_filename = { + sample.attributes["id"]: sample.image.path for sample in self._points_dataset + } + + filename_to_sample = {sample.image.path: sample for sample in self._points_dataset} + + _roi_key = lambda e: e.original_image_key + rois_by_image: Dict[str, Sequence[boxes_from_points_task.RoiInfo]] = { + image_id_to_filename[image_id]: list(g) + for image_id, g in groupby(sorted(self._rois, key=_roi_key), key=_roi_key) + } + + for filename in self._data_filenames: + image_roi_infos = rois_by_image.get(filename, []) + if not image_roi_infos: + continue + + image_bytes = src_client.download_file(os.path.join(src_prefix, filename)) + image_pixels = decode_image(image_bytes) + + sample = filename_to_sample[filename] + if tuple(sample.image.size) != tuple(image_pixels.shape[:2]): + # TODO: maybe rois should be regenerated instead + # Option 2: accumulate errors, fail when some threshold is reached + # Option 3: add special handling for cases when image is only rotated (exif etc.) + raise InvalidImageInfo( + f"Sample '{filename}': invalid size provided in the point annotations" + ) + + image_rois = {} + for roi_info in image_roi_infos: + roi_pixels = self._extract_roi(image_pixels, roi_info) + + if self.embed_point_in_roi_image: + roi_pixels = self._draw_roi_point(roi_pixels, roi_info) + + roi_filename = self._roi_filenames[roi_info.point_id] + roi_bytes = encode_image(roi_pixels, os.path.splitext(roi_filename)[-1]) + + image_rois[roi_filename] = roi_bytes + + for roi_filename, roi_bytes in image_rois.items(): + dst_client.create_file( + compose_data_bucket_filename(self.escrow_address, self.chain_id, roi_filename), + roi_bytes, + ) + + def _create_on_cvat(self): + assert self._job_layout is not _unset + assert self._label_configuration is not _unset + + input_data_bucket = BucketAccessInfo.parse_obj(self.manifest.data.data_url) + oracle_bucket = self.oracle_data_bucket + + # Register cloud storage on CVAT to pass user dataset + cloud_storage = cvat_api.create_cloudstorage( + **_make_cvat_cloud_storage_params(oracle_bucket) + ) + + # Create a project + project = cvat_api.create_project( + self.escrow_address, + labels=self._label_configuration, + user_guide=self.manifest.annotation.user_guide, + ) + + # Setup webhooks for a project (update:task, update:job) + webhook = cvat_api.create_cvat_webhook(project.id) + + with SessionLocal.begin() as session: + db_service.create_project( + session, + project.id, + cloud_storage.id, + self.manifest.annotation.type, + self.escrow_address, + self.chain_id, + compose_bucket_url( + input_data_bucket.bucket_name, + bucket_host=input_data_bucket.host_url, + provider=input_data_bucket.provider, + ), + cvat_webhook_id=webhook.id, + ) + db_service.add_project_images( + session, + project.id, + [ + compose_data_bucket_filename(self.escrow_address, self.chain_id, fn) + for fn in self._roi_filenames.values() + ], + ) + + for job_filenames in self._job_layout: + task = cvat_api.create_task(project.id, self.escrow_address) + + with SessionLocal.begin() as session: + db_service.create_task(session, task.id, project.id, TaskStatuses[task.status]) + + # Actual task creation in CVAT takes some time, so it's done in an async process. + # The task will be created in DB once 'update:task' or 'update:job' webhook is received. + cvat_api.put_task_data( + task.id, + cloud_storage.id, + filenames=[ + compose_data_bucket_filename(self.escrow_address, self.chain_id, fn) + for fn in job_filenames + ], + sort_images=False, + ) + + with SessionLocal.begin() as session: + db_service.create_data_upload(session, cvat_task_id=task.id) + + @classmethod + def _make_cloud_storage_client(cls, bucket_info: BucketAccessInfo) -> StorageClient: + return cloud_service.make_client(bucket_info) + + def build(self): + self._download_input_data() + self._parse_gt() + self._parse_points() + self._validate_gt() + self._validate_points() + + # Task configuration creation + self._prepare_gt() + self._estimate_roi_sizes() + self._prepare_roi_info() + self._mangle_filenames() + self._prepare_label_configuration() + self._prepare_job_layout() + + # Data preparation + self._extract_and_upload_rois() + self._upload_task_meta() + + self._create_on_cvat() + + +class SkeletonsFromBoxesTaskBuilder: + @dataclass + class _JobParams: + label_id: int + roi_ids: List[int] + + def __init__(self, manifest: TaskManifest, escrow_address: str, chain_id: int): + self.exit_stack = ExitStack() + self.manifest = manifest + self.escrow_address = escrow_address + self.chain_id = chain_id + + self.logger: Logger = NullLogger() + + self._input_gt_data: _MaybeUnset[bytes] = _unset + self._input_boxes_data: _MaybeUnset[bytes] = _unset + + self._data_filenames: _MaybeUnset[Sequence[str]] = _unset + self._gt_dataset: _MaybeUnset[dm.Dataset] = _unset + self._boxes_dataset: _MaybeUnset[dm.Dataset] = _unset + + self._skeleton_bbox_mapping: _MaybeUnset[ + skeletons_from_boxes_task.SkeletonBboxMapping + ] = _unset + self._roi_infos: _MaybeUnset[skeletons_from_boxes_task.RoiInfos] = _unset + self._roi_filenames: _MaybeUnset[Dict[int, str]] = _unset + self._job_params: _MaybeUnset[List[self._JobParams]] = _unset + + self._excluded_gt_info: _MaybeUnset[_ExcludedAnnotationsInfo] = _unset + self._excluded_boxes_info: _MaybeUnset[_ExcludedAnnotationsInfo] = _unset + + # Configuration / constants + self.job_size_mult = skeletons_from_boxes_task.DEFAULT_ASSIGNMENT_SIZE_MULTIPLIER + "Job size multiplier" + + # TODO: consider WebP if produced files are too big + self.roi_file_ext = ".png" # supposed to be lossless and reasonably compressing + "File extension for RoI images, with leading dot (.) included" + + self.list_display_threshold = 5 + "The maximum number of rendered list items in a message" + + self.roi_size_mult = 1.1 + "Additional point ROI size multiplier" + + self.boxes_format = "coco_instances" + + self.embed_bbox_in_roi_image = True + "Put a bbox into the extracted skeleton RoI images" + + self.embed_tile_border = True + + self.roi_embedded_bbox_color = (0, 255, 255) # BGR + self.roi_background_color = (245, 240, 242) # BGR - CVAT background color + + self.oracle_data_bucket = BucketAccessInfo.parse_obj(Config.storage_config) + + self.min_label_gt_samples = 2 # TODO: find good threshold + + self.max_discarded_threshold = 0.05 + """ + The maximum allowed percent of discarded + GT annotations or samples for successful job launch + """ + + def __enter__(self): + return self + + def __exit__(self, *args, **kwargs): + self.close() + + def close(self): + self.exit_stack.close() + + def set_logger(self, logger: Logger): + # TODO: add escrow info into messages + self.logger = logger + return self + + def _download_input_data(self): + data_bucket = BucketAccessInfo.parse_obj(self.manifest.data.data_url) + gt_bucket = BucketAccessInfo.parse_obj(self.manifest.validation.gt_url) + boxes_bucket = BucketAccessInfo.parse_obj(self.manifest.data.boxes_url) + + data_storage_client = self._make_cloud_storage_client(data_bucket) + gt_storage_client = self._make_cloud_storage_client(gt_bucket) + boxes_storage_client = self._make_cloud_storage_client(boxes_bucket) + + data_filenames = data_storage_client.list_files(prefix=data_bucket.path) + data_filenames = strip_bucket_prefix(data_filenames, prefix=data_bucket.path) + self._data_filenames = filter_image_files(data_filenames) + + self._input_gt_data = gt_storage_client.download_file(gt_bucket.path) + + self._input_boxes_data = boxes_storage_client.download_file(boxes_bucket.path) + + def _parse_dataset(self, annotation_file_data: bytes, dataset_format: str) -> dm.Dataset: + temp_dir = self.exit_stack.enter_context(TemporaryDirectory()) + + annotation_filename = os.path.join(temp_dir, "annotations.json") + with open(annotation_filename, "wb") as f: + f.write(annotation_file_data) + + return dm.Dataset.import_from(annotation_filename, format=dataset_format) + + def _parse_gt(self): + assert self._input_gt_data is not _unset + + self._gt_dataset = self._parse_dataset( + self._input_gt_data, + dataset_format=DM_GT_DATASET_FORMAT_MAPPING[self.manifest.annotation.type], + ) + + def _parse_boxes(self): + assert self._input_boxes_data is not _unset + + self._boxes_dataset = self._parse_dataset( + self._input_boxes_data, dataset_format=self.boxes_format + ) + + def _validate_gt_labels(self): + gt_labels = set( + (label.name, label.parent) + for label in self._gt_dataset.categories()[dm.AnnotationType.label] + ) + + manifest_labels = set() + for skeleton_label in self.manifest.annotation.labels: + manifest_labels.add((skeleton_label.name, "")) + for node_label in skeleton_label.nodes: + manifest_labels.add((node_label, skeleton_label.name)) + + if gt_labels - manifest_labels: + raise DatasetValidationError( + "GT labels do not match job labels. Unknown labels: {}".format( + self._format_list( + [ + label_name if not parent_name else f"{parent_name}.{label_name}" + for label_name, parent_name in gt_labels - manifest_labels + ] + ), + ) + ) + + # Reorder labels to match the manifest + self._gt_dataset.transform( + ProjectLabels, dst_labels=[label.name for label in self.manifest.annotation.labels] + ) + self._gt_dataset.init_cache() + + def _validate_gt_filenames(self): + gt_filenames = set(s.id + s.media.ext for s in self._gt_dataset) + + known_data_filenames = set(self._data_filenames) + matched_gt_filenames = gt_filenames.intersection(known_data_filenames) + + if len(gt_filenames) != len(matched_gt_filenames): + extra_gt = list(map(os.path.basename, gt_filenames - matched_gt_filenames)) + + raise MismatchingAnnotations( + "Failed to find several validation samples in the dataset files: {}".format( + self._format_list(extra_gt) + ) + ) + + if len(gt_filenames) < self.manifest.validation.val_size: + raise TooFewSamples( + f"Too few validation samples provided ({len(gt_filenames)}), " + f"at least {self.manifest.validation.val_size} required." + ) + + def _validate_gt_annotations(self): + def _validate_skeleton(skeleton: dm.Skeleton, *, sample_bbox: dm.Bbox): + if skeleton.id in visited_ids: + raise DatasetValidationError(f"repeated annotation id {skeleton.id}") + + for element in skeleton.elements: + # This is what Datumaro is expected to parse + assert len(element.points) == 2 and len(element.visibility) == 1 + + if element.visibility[0] != dm.Points.Visibility.visible: + continue + + px, py = element.points[:2] + if not is_point_in_bbox(px, py, sample_bbox): + raise InvalidCoordinates("skeleton point is outside the image") + + label_cat: dm.LabelCategories = self._gt_dataset.categories()[dm.AnnotationType.label] + + excluded_gt_info = _ExcludedAnnotationsInfo() + excluded_samples = set() + visited_ids = set() + for gt_sample in self._gt_dataset: + # Could fail on this as well + img_h, img_w = gt_sample.media_as(dm.Image).size + sample_bbox = dm.Bbox(0, 0, w=img_w, h=img_h) + + sample_skeletons = [a for a in gt_sample.annotations if isinstance(a, dm.Skeleton)] + valid_skeletons = [] + for skeleton in sample_skeletons: + try: + _validate_skeleton(skeleton, sample_bbox=sample_bbox) + except InvalidCoordinates as error: + excluded_gt_info.add_error( + "Sample '{}': GT skeleton #{} ({}) - {}. " + "The image will be skipped".format( + gt_sample.id, skeleton.id, label_cat[skeleton.label].name, error + ), + sample_id=gt_sample.id, + sample_subset=gt_sample.subset, + ) + valid_skeletons = [] + break + except DatasetValidationError as error: + excluded_gt_info.add_error( + "Sample '{}': GT skeleton #{} ({}) skipped - {}".format( + gt_sample.id, skeleton.id, label_cat[skeleton.label].name, error + ), + sample_id=gt_sample.id, + sample_subset=gt_sample.subset, + ) + + valid_skeletons.append(skeleton) + visited_ids.add(skeleton.id) + + excluded_gt_info.excluded_count += len(sample_skeletons) - len(valid_skeletons) + excluded_gt_info.total_count += len(sample_skeletons) + + if len(valid_skeletons) != len(sample_skeletons): + if not valid_skeletons: + excluded_samples.add((gt_sample.id, gt_sample.subset)) + else: + # Skeleton boxes can be in the list as well with the same ids / groups + skeleton_ids = set(a.id for a in valid_skeletons) - {0} + self._gt_dataset.put( + gt_sample.wrap( + annotations=[a for a in gt_sample.annotations if a.id in skeleton_ids] + ) + ) + + for excluded_sample in excluded_samples: + self._gt_dataset.remove(*excluded_sample) + + if excluded_gt_info.excluded_count: + self.logger.warning( + "Some GT skeletons were excluded due to the errors found: {}".format( + self._format_list([e.message for e in excluded_gt_info.errors], separator="\n") + ) + ) + + if ( + excluded_gt_info.excluded_count + > excluded_gt_info.total_count * self.max_discarded_threshold + ): + raise TooFewSamples( + "Too many GT skeletons discarded, canceling job creation. Errors: {}".format( + self._format_list( + [error_info.message for error_info in excluded_gt_info.errors] + ) + ) + ) + + self._excluded_gt_info = excluded_gt_info + + def _validate_gt(self): + assert self._data_filenames is not _unset + assert self._gt_dataset is not _unset + + self._validate_gt_filenames() + self._validate_gt_labels() + self._validate_gt_annotations() + + def _validate_boxes_categories(self): + boxes_dataset_categories = self._boxes_dataset.categories() + boxes_dataset_label_cat: dm.LabelCategories = boxes_dataset_categories[ + dm.AnnotationType.label + ] + + boxes_labels = set(label.name for label in boxes_dataset_label_cat if not label.parent) + manifest_labels = set(label.name for label in self.manifest.annotation.labels) + if manifest_labels != boxes_labels: + raise DatasetValidationError("Bbox labels do not match job labels") + + # Reorder labels to match the manifest + self._boxes_dataset.transform( + ProjectLabels, dst_labels=[label.name for label in self.manifest.annotation.labels] + ) + self._boxes_dataset.init_cache() + + def _validate_boxes_filenames(self): + boxes_filenames = set(sample.id + sample.media.ext for sample in self._boxes_dataset) + + known_data_filenames = set(self._data_filenames) + matched_boxes_filenames = boxes_filenames.intersection(known_data_filenames) + + if len(matched_boxes_filenames) != len(boxes_filenames): + extra_bbox_samples = list( + map(os.path.basename, boxes_filenames - matched_boxes_filenames) + ) + + raise MismatchingAnnotations( + "Failed to find several samples in the dataset files: {}".format( + self._format_list(extra_bbox_samples), + ) + ) + + def _validate_boxes_annotations(self): + # Convert possible polygons and masks into boxes + self._boxes_dataset.transform(InstanceSegmentsToBbox) + self._boxes_dataset.init_cache() + + excluded_boxes_info = _ExcludedAnnotationsInfo() + + label_cat: dm.LabelCategories = self._boxes_dataset.categories()[dm.AnnotationType.label] + + visited_ids = set() + for sample in self._boxes_dataset: + # Could fail on this as well + image_h, image_w = sample.media_as(dm.Image).size + + sample_boxes = [a for a in sample.annotations if isinstance(a, dm.Bbox)] + valid_boxes = [] + for bbox in sample_boxes: + if not ( + (0 <= bbox.x < bbox.x + bbox.w <= image_w) + and (0 <= bbox.y < bbox.y + bbox.h <= image_h) + ): + excluded_boxes_info.add_error( + "Sample '{}': bbox #{} ({}) skipped - invalid coordinates".format( + sample.id, bbox.id, label_cat[bbox.label].name + ), + sample_id=sample.id, + sample_subset=sample.subset, + ) + + if bbox.id in visited_ids: + excluded_boxes_info.add_error( + "Sample '{}': bbox #{} ({}) skipped - repeated annotation id {}".format( + sample.id, bbox.id, label_cat[bbox.label].name, bbox.id + ), + sample_id=sample.id, + sample_subset=sample.subset, + ) + + valid_boxes.append(bbox) + visited_ids.add(bbox.id) + + excluded_boxes_info.excluded_count += len(sample_boxes) - len(valid_boxes) + excluded_boxes_info.total_count += len(sample_boxes) + + if len(valid_boxes) != len(sample.annotations): + self._boxes_dataset.put(sample.wrap(annotations=valid_boxes)) + + if ( + excluded_boxes_info.excluded_count + > excluded_boxes_info.total_count * self.max_discarded_threshold + ): + raise TooFewSamples( + "Too many boxes discarded, canceling job creation. Errors: {}".format( + self._format_list( + [error_info.message for error_info in excluded_boxes_info.errors] + ) + ) + ) + + excluded_samples = set((e.sample_id, e.sample_subset) for e in excluded_boxes_info.errors) + for excluded_sample in excluded_samples: + self._boxes_dataset.remove(*excluded_sample) + + if excluded_samples: + self.logger.warning( + "Some boxes were excluded due to the errors found: {}".format( + self._format_list( + [e.message for e in excluded_boxes_info.errors], separator="\n" + ) + ) + ) + + self._excluded_boxes_info = excluded_boxes_info + + def _validate_boxes(self): + assert self._data_filenames is not _unset + assert self._boxes_dataset is not _unset + + self._validate_boxes_categories() + self._validate_boxes_filenames() + self._validate_boxes_annotations() + + def _format_list( + self, items: Sequence[str], *, max_items: int = None, separator: str = ", " + ) -> str: + if max_items is None: + max_items = self.list_display_threshold + + remainder_count = len(items) - max_items + return "{}{}".format( + separator.join(items[:max_items]), + f" (and {remainder_count} more)" if remainder_count > 0 else "", + ) + + def _match_boxes(self, a: BboxCoords, b: BboxCoords): + return bbox_iou(a, b) > 0 + + def _prepare_gt(self): + assert self._data_filenames is not _unset + assert self._boxes_dataset is not _unset + assert self._gt_dataset is not _unset + assert [ + label.name + for label in self._gt_dataset.categories()[dm.AnnotationType.label] + if not label.parent + ] == [label.name for label in self.manifest.annotation.labels] + assert [ + label.name + for label in self._boxes_dataset.categories()[dm.AnnotationType.label] + if not label.parent + ] == [label.name for label in self.manifest.annotation.labels] + + updated_gt_dataset = dm.Dataset( + categories=self._gt_dataset.categories(), media_type=dm.Image + ) + + gt_label_cat: dm.LabelCategories = self._gt_dataset.categories()[dm.AnnotationType.label] + + excluded_gt_info = self._excluded_gt_info + gt_count_per_class = {} + skeleton_bbox_mapping = {} # skeleton id -> bbox id + for gt_sample in self._gt_dataset: + boxes_sample = self._boxes_dataset.get(gt_sample.id, gt_sample.subset) + # Samples could be discarded, so we just skip them without an error + if not boxes_sample: + continue + + gt_skeletons = [a for a in gt_sample.annotations if isinstance(a, dm.Skeleton)] + input_boxes = [a for a in boxes_sample.annotations if isinstance(a, dm.Bbox)] + + # Samples without boxes are allowed, so we just skip them without an error + if not gt_skeletons: + continue + + # Find unambiguous gt skeleton - input bbox pairs + matched_skeletons = [] + visited_skeletons = set() + for gt_skeleton in gt_skeletons: + gt_skeleton_id = gt_skeleton.id + + if len(visited_skeletons) == len(gt_skeletons): + # Handle unmatched boxes + excluded_gt_info.add_error( + "Sample '{}': GT skeleton #{} ({}) skipped - " + "no matching boxes found".format( + gt_sample.id, gt_skeleton_id, gt_label_cat[gt_skeleton.label].name + ), + sample_id=gt_sample.id, + sample_subset=gt_sample.subset, + ) + excluded_gt_info.excluded_count += 1 + continue + + if all( + v != dm.Points.Visibility.visible + for p in gt_skeleton.elements + for v in p.visibility + ): + # Handle fully hidden skeletons + excluded_gt_info.add_error( + "Sample '{}': GT skeleton #{} ({}) skipped - " + "no visible points".format( + gt_sample.id, gt_skeleton_id, gt_label_cat[gt_skeleton.label].name + ), + sample_id=gt_sample.id, + sample_subset=gt_sample.subset, + ) + excluded_gt_info.excluded_count += 1 + continue + + matched_boxes: List[dm.Bbox] = [] + for input_bbox in input_boxes: + skeleton_id = gt_skeleton.id + if skeleton_id in visited_skeletons: + continue + + gt_skeleton_bbox = gt_skeleton.get_bbox() + if not self._match_boxes(input_bbox.get_bbox(), gt_skeleton_bbox): + continue + + if input_bbox.label != gt_skeleton.label: + continue + + matched_boxes.append(input_bbox) + visited_skeletons.add(skeleton_id) + + if len(matched_boxes) > 1: + # Handle ambiguous matches + excluded_gt_info.add_error( + "Sample '{}': GT skeleton #{} ({}) skipped - " + "too many matching boxes ({}) found".format( + gt_sample.id, + gt_skeleton_id, + gt_label_cat[gt_skeleton.label].name, + len(matched_boxes), + ), + sample_id=gt_sample.id, + sample_subset=gt_sample.subset, + ) + excluded_gt_info.excluded_count += 1 + continue + elif len(matched_boxes) == 0: + # Handle unmatched boxes + excluded_gt_info.add_error( + "Sample '{}': GT skeleton #{} ({}) skipped - " + "no matching boxes found".format( + gt_sample.id, + gt_skeleton_id, + gt_label_cat[gt_skeleton.label].name, + ), + sample_id=gt_sample.id, + sample_subset=gt_sample.subset, + ) + excluded_gt_info.excluded_count += 1 + continue + + # TODO: maybe check if the top match is good enough + # (high thresholds may lead to matching issues for small objects) + + gt_count_per_class[gt_skeleton.label] = ( + gt_count_per_class.get(gt_skeleton.label, 0) + 1 + ) + + matched_skeletons.append(gt_skeleton) + skeleton_bbox_mapping[gt_skeleton_id] = matched_boxes[0].id + + if not matched_skeletons: + continue + + updated_gt_dataset.put(gt_sample.wrap(annotations=matched_skeletons)) + + if excluded_gt_info.excluded_count: + self.logger.warning( + "Some GT annotations were excluded due to the errors found: {}".format( + self._format_list([e.message for e in excluded_gt_info.errors], separator="\n") + ) + ) + + if ( + len(skeleton_bbox_mapping) + < (1 - self.max_discarded_threshold) * excluded_gt_info.total_count + ): + raise DatasetValidationError( + "Too many GT skeletons discarded ({} out of {}). " + "Please make sure each GT skeleton matches exactly 1 bbox " + "and has at least 1 visible point".format( + excluded_gt_info.total_count - len(skeleton_bbox_mapping), + excluded_gt_info.total_count, + ) + ) + + labels_with_few_gt = [ + gt_label_cat[label_id] + for label_id, label_count in gt_count_per_class.items() + if label_count < self.min_label_gt_samples + ] + if labels_with_few_gt: + raise DatasetValidationError( + "Too few matching GT boxes/points annotations found for some classes: {}".format( + self._format_list(labels_with_few_gt) + ) + ) + + self._gt_dataset = updated_gt_dataset + self._skeleton_bbox_mapping = skeleton_bbox_mapping + + def _prepare_roi_infos(self): + assert self._gt_dataset is not _unset + assert self._boxes_dataset is not _unset + + rois: List[skeletons_from_boxes_task.RoiInfo] = [] + for sample in self._boxes_dataset: + for bbox in sample.annotations: + if not isinstance(bbox, dm.Bbox): + continue + + # RoI is centered on bbox center + original_bbox_cx = int(bbox.x + bbox.w / 2) + original_bbox_cy = int(bbox.y + bbox.h / 2) + + roi_w = ceil(bbox.w * self.roi_size_mult) + roi_h = ceil(bbox.h * self.roi_size_mult) + + roi_x = original_bbox_cx - int(roi_w / 2) + roi_y = original_bbox_cy - int(roi_h / 2) + + new_bbox_x = bbox.x - roi_x + new_bbox_y = bbox.y - roi_y + + rois.append( + skeletons_from_boxes_task.RoiInfo( + original_image_key=sample.attributes["id"], + bbox_id=bbox.id, + bbox_label=bbox.label, + bbox_x=new_bbox_x, + bbox_y=new_bbox_y, + roi_x=roi_x, + roi_y=roi_y, + roi_w=roi_w, + roi_h=roi_h, + ) + ) + + self._roi_infos = rois + + def _mangle_filenames(self): + """ + Mangle filenames in the dataset to make them less recognizable by annotators + and hide private dataset info + """ + assert self._roi_infos is not _unset + + # TODO: maybe add different names for the same GT images in + # different jobs to make them even less recognizable + self._roi_filenames = { + roi_info.bbox_id: str(uuid.uuid4()) + self.roi_file_ext for roi_info in self._roi_infos + } + + def _prepare_job_params(self): + assert self._roi_infos is not _unset + assert self._skeleton_bbox_mapping is not _unset + + # Make job layouts wrt. manifest params + # 1 job per task, 1 task for each point label + # + # Unlike other task types, here we use a grid of RoIs, + # so the absolute job size numbers from manifest are multiplied by the job size multiplier. + # Then, we add a percent of job tiles for validation, keeping the requested ratio. + gt_ratio = self.manifest.validation.val_size / (self.manifest.annotation.job_size or 1) + job_size_mult = self.job_size_mult + + job_params: List[self._JobParams] = [] + + roi_info_by_id = {roi_info.bbox_id: roi_info for roi_info in self._roi_infos} + for label_id, _ in enumerate(self.manifest.annotation.labels): + label_gt_roi_ids = set( + roi_id + for roi_id in self._skeleton_bbox_mapping.values() + if roi_info_by_id[roi_id].bbox_label == label_id + ) + + label_data_roi_ids = [ + roi_info.bbox_id + for roi_info in self._roi_infos + if roi_info.bbox_label == label_id + if roi_info.bbox_id not in label_gt_roi_ids + ] + random.shuffle(label_data_roi_ids) + + for job_data_roi_ids in take_by( + label_data_roi_ids, int(job_size_mult * self.manifest.annotation.job_size) + ): + job_gt_count = max( + self.manifest.validation.val_size, int(gt_ratio * len(job_data_roi_ids)) + ) + job_gt_count = min(len(label_gt_roi_ids), job_gt_count) + + job_gt_roi_ids = random.sample(label_gt_roi_ids, k=job_gt_count) + + job_roi_ids = list(job_data_roi_ids) + list(job_gt_roi_ids) + random.shuffle(job_roi_ids) + + job_params.append(self._JobParams(label_id=label_id, roi_ids=job_roi_ids)) + + self._job_params = job_params + + def _prepare_job_labels(self): + self.point_labels = {} + + for skeleton_label in self.manifest.annotation.labels: + for point_name in skeleton_label.nodes: + self.point_labels[(skeleton_label.name, point_name)] = point_name + + def _upload_task_meta(self): + layout = skeletons_from_boxes_task.TaskMetaLayout() + serializer = skeletons_from_boxes_task.TaskMetaSerializer() + + file_list = [] + file_list.append( + (serializer.serialize_bbox_annotations(self._boxes_dataset), layout.BOXES_FILENAME) + ) + file_list.append( + ( + serializer.serialize_gt_annotations(self._gt_dataset), + layout.GT_FILENAME, + ) + ) + file_list.append( + ( + serializer.serialize_skeleton_bbox_mapping(self._skeleton_bbox_mapping), + layout.SKELETON_BBOX_MAPPING_FILENAME, + ) + ) + file_list.append((serializer.serialize_roi_info(self._roi_infos), layout.ROI_INFO_FILENAME)) + file_list.append( + (serializer.serialize_roi_filenames(self._roi_filenames), layout.ROI_FILENAMES_FILENAME) + ) + file_list.append( + (serializer.serialize_point_labels(self.point_labels), layout.POINT_LABELS_FILENAME) + ) + + storage_client = self._make_cloud_storage_client(self.oracle_data_bucket) + for file_data, filename in file_list: + storage_client.create_file( + compose_data_bucket_filename(self.escrow_address, self.chain_id, filename), + file_data, + ) + + def _extract_roi( + self, source_pixels: np.ndarray, roi_info: skeletons_from_boxes_task.RoiInfo + ) -> np.ndarray: + img_h, img_w, *_ = source_pixels.shape + + roi_pixels = source_pixels[ + max(0, roi_info.roi_y) : min(img_h, roi_info.roi_y + roi_info.roi_h), + max(0, roi_info.roi_x) : min(img_w, roi_info.roi_x + roi_info.roi_w), + ] + + if not ( + (0 <= roi_info.roi_x < roi_info.roi_x + roi_info.roi_w < img_w) + and (0 <= roi_info.roi_y < roi_info.roi_y + roi_info.roi_h < img_h) + ): + # Coords can be outside the original image + # In this case a border should be added to RoI, so that the image was centered on bbox + wrapped_roi_pixels = np.zeros((roi_info.roi_h, roi_info.roi_w, 3), dtype=np.float32) + wrapped_roi_pixels[:, :] = self.roi_background_color + + dst_y = max(-roi_info.roi_y, 0) + dst_x = max(-roi_info.roi_x, 0) + wrapped_roi_pixels[ + dst_y : dst_y + roi_pixels.shape[0], + dst_x : dst_x + roi_pixels.shape[1], + ] = roi_pixels + + roi_pixels = wrapped_roi_pixels + else: + roi_pixels = roi_pixels.copy() + + return roi_pixels + + def _draw_roi_bbox(self, roi_image: np.ndarray, bbox: dm.Bbox) -> np.ndarray: + roi_cy = roi_image.shape[0] // 2 + roi_cx = roi_image.shape[1] // 2 + return cv2.rectangle( + roi_image, + tuple(map(int, (roi_cx - bbox.w / 2, roi_cy - bbox.h / 2))), + tuple(map(int, (roi_cx + bbox.w / 2, roi_cy + bbox.h / 2))), + self.roi_embedded_bbox_color, + 2, # TODO: maybe improve line thickness + cv2.LINE_4, + ) + + def _extract_and_upload_rois(self): + assert self._roi_filenames is not _unset + assert self._roi_infos is not _unset + + src_bucket = BucketAccessInfo.parse_obj(self.manifest.data.data_url) + src_prefix = src_bucket.path + dst_bucket = self.oracle_data_bucket + + src_client = self._make_cloud_storage_client(src_bucket) + dst_client = self._make_cloud_storage_client(dst_bucket) + + image_id_to_filename = { + sample.attributes["id"]: sample.image.path for sample in self._boxes_dataset + } + + filename_to_sample = {sample.image.path: sample for sample in self._boxes_dataset} + + _roi_info_key = lambda e: e.original_image_key + roi_info_by_image: Dict[str, Sequence[skeletons_from_boxes_task.RoiInfo]] = { + image_id_to_filename[image_id]: list(g) + for image_id, g in groupby( + sorted(self._roi_infos, key=_roi_info_key), key=_roi_info_key + ) + } + + bbox_by_id = { + bbox.id: bbox + for sample in self._boxes_dataset + for bbox in sample.annotations + if isinstance(bbox, dm.Bbox) + } + + for filename in self._data_filenames: + image_roi_infos = roi_info_by_image.get(filename, []) + if not image_roi_infos: + continue + + image_bytes = src_client.download_file(os.path.join(src_prefix, filename)) + image_pixels = decode_image(image_bytes) + + sample = filename_to_sample[filename] + if tuple(sample.image.size) != tuple(image_pixels.shape[:2]): + # TODO: maybe rois should be regenerated instead + # Option 2: accumulate errors, fail when some threshold is reached + # Option 3: add special handling for cases when image is only rotated (exif etc.) + raise InvalidImageInfo( + f"Sample '{filename}': invalid size provided in the point annotations" + ) + + for roi_info in image_roi_infos: + roi_pixels = self._extract_roi(image_pixels, roi_info) + + if self.embed_bbox_in_roi_image: + roi_pixels = self._draw_roi_bbox(roi_pixels, bbox_by_id[roi_info.bbox_id]) + + filename = self._roi_filenames[roi_info.bbox_id] + roi_bytes = encode_image(roi_pixels, os.path.splitext(filename)[-1]) + + dst_client.create_file( + compose_data_bucket_filename(self.escrow_address, self.chain_id, filename), + data=roi_bytes, + ) + + def _create_on_cvat(self): + assert self._job_params is not _unset + assert self.point_labels is not _unset + + _job_params_label_key = lambda ts: ts.label_id + jobs_by_skeleton_label = { + skeleton_label_id: list(g) + for skeleton_label_id, g in groupby( + sorted(self._job_params, key=_job_params_label_key), key=_job_params_label_key + ) + } + + label_specs_by_skeleton = { + skeleton_label_id: [ + { + "name": self.point_labels[(skeleton_label.name, skeleton_point)], + "type": "points", + } + for skeleton_point in skeleton_label.nodes + ] + for skeleton_label_id, skeleton_label in enumerate(self.manifest.annotation.labels) + } + + input_data_bucket = BucketAccessInfo.parse_obj(self.manifest.data.data_url) + oracle_bucket = self.oracle_data_bucket + + # Register cloud storage on CVAT to pass user dataset + cloud_storage = cvat_api.create_cloudstorage( + **_make_cvat_cloud_storage_params(oracle_bucket) + ) + + for skeleton_label_id, skeleton_label_jobs in jobs_by_skeleton_label.items(): + # Each skeleton point uses the same file layout in jobs + skeleton_label_filenames = [] + for skeleton_label_job in skeleton_label_jobs: + skeleton_label_filenames.append( + [ + compose_data_bucket_filename( + self.escrow_address, self.chain_id, self._roi_filenames[roi_id] + ) + for roi_id in skeleton_label_job.roi_ids + ] + ) + + for point_label_spec in label_specs_by_skeleton[skeleton_label_id]: + # Create a project for each point label. + # CVAT doesn't support tasks with different labels in a project. + project = cvat_api.create_project( + name="{} ({} {})".format( + self.escrow_address, + self.manifest.annotation.labels[skeleton_label_id].name, + point_label_spec["name"], + ), + user_guide=self.manifest.annotation.user_guide, + labels=[point_label_spec], + # TODO: improve guide handling - split for different points + ) + + # Setup webhooks for a project (update:task, update:job) + webhook = cvat_api.create_cvat_webhook(project.id) + + with SessionLocal.begin() as session: + db_service.create_project( + session, + project.id, + cloud_storage.id, + self.manifest.annotation.type, + self.escrow_address, + self.chain_id, + compose_bucket_url( + input_data_bucket.bucket_name, + bucket_host=input_data_bucket.host_url, + provider=input_data_bucket.provider, + ), + cvat_webhook_id=webhook.id, + ) + db_service.add_project_images( + session, + project.id, + list(set(chain.from_iterable(skeleton_label_filenames))), + ) + + for point_label_filenames in skeleton_label_filenames: + task = cvat_api.create_task(project.id, name=project.name) + + with SessionLocal.begin() as session: + db_service.create_task( + session, task.id, project.id, TaskStatuses[task.status] + ) + + # Actual task creation in CVAT takes some time, so it's done in an async process. + # The task will be created in DB once 'update:task' or 'update:job' webhook is received. + cvat_api.put_task_data( + task.id, + cloud_storage.id, + filenames=point_label_filenames, + sort_images=False, + ) + + with SessionLocal.begin() as session: + db_service.create_data_upload(session, cvat_task_id=task.id) + + @classmethod + def _make_cloud_storage_client(cls, bucket_info: BucketAccessInfo) -> StorageClient: + return cloud_service.make_client(bucket_info) + + def build(self): + self._download_input_data() + self._parse_gt() + self._parse_boxes() + self._validate_gt() + self._validate_boxes() + + # Task configuration creation + self._prepare_gt() + self._prepare_roi_infos() + self._prepare_job_params() + self._mangle_filenames() + self._prepare_job_labels() + + # Data preparation + self._extract_and_upload_rois() + self._upload_task_meta() + + self._create_on_cvat() + + +def get_gt_filenames( + gt_file_data: bytes, data_filenames: List[str], *, manifest: TaskManifest +) -> List[str]: + with TemporaryDirectory() as gt_temp_dir: + gt_filename = os.path.join(gt_temp_dir, "gt_annotations.json") + with open(gt_filename, "wb") as f: + f.write(gt_file_data) + + gt_dataset = dm.Dataset.import_from( + gt_filename, + format=DM_GT_DATASET_FORMAT_MAPPING[manifest.annotation.type], + ) + + gt_filenames = set(s.id + s.media.ext for s in gt_dataset) + + known_data_filenames = set(data_filenames) + matched_gt_filenames = gt_filenames.intersection(known_data_filenames) + + if len(gt_filenames) != len(matched_gt_filenames): + missing_gt = gt_filenames - matched_gt_filenames + missing_gt_display_threshold = 10 + remainder = len(missing_gt) - missing_gt_display_threshold + raise DatasetValidationError( + "Failed to find several validation samples in the dataset files: {}{}".format( + ", ".join(missing_gt[:missing_gt_display_threshold]), + f"(and {remainder} more)" if remainder else "", + ) + ) + + if len(gt_filenames) < manifest.validation.val_size: + raise TooFewSamples( + f"Too few validation samples provided ({len(gt_filenames)}), " + f"at least {manifest.validation.val_size} required." + ) + + return matched_gt_filenames + + +def make_job_configuration( + data_filenames: List[str], + gt_filenames: List[str], + *, + manifest: TaskManifest, +) -> List[List[str]]: + # Make job layouts wrt. manifest params, 1 job per task (CVAT can't repeat images in jobs) + gt_filenames_index = set(gt_filenames) + data_filenames = [fn for fn in data_filenames if not fn in gt_filenames_index] + random.shuffle(data_filenames) + + job_layout = [] + for data_samples in take_by(data_filenames, manifest.annotation.job_size): + gt_samples = random.sample(gt_filenames, k=manifest.validation.val_size) + job_samples = list(data_samples) + list(gt_samples) + random.shuffle(job_samples) + job_layout.append(job_samples) + + return job_layout + + +def is_image(path: str) -> bool: + trunk, ext = os.path.splitext(os.path.basename(path)) + return trunk and ext.lower() in IMAGE_EXTENSIONS + + +def filter_image_files(data_filenames: List[str]) -> List[str]: + return list(fn for fn in data_filenames if is_image(fn)) + + +def strip_bucket_prefix(data_filenames: List[str], prefix: str) -> List[str]: + return list(os.path.relpath(fn, prefix) for fn in data_filenames) + + +def make_label_configuration(manifest: TaskManifest) -> List[dict]: + return [ + { + "name": label.name, + "type": LABEL_TYPE_MAPPING[manifest.annotation.type].value, + } + for label in manifest.annotation.labels + ] + + +def _make_cvat_cloud_storage_params(bucket_info: BucketAccessInfo) -> Dict: + CLOUD_PROVIDER_TO_CVAT_CLOUD_PROVIDER = { + CloudProviders.aws: "AWS_S3_BUCKET", + CloudProviders.gcs: "GOOGLE_CLOUD_STORAGE", + } + + params = { + "provider": CLOUD_PROVIDER_TO_CVAT_CLOUD_PROVIDER[bucket_info.provider], + "bucket_name": bucket_info.bucket_name, + "bucket_host": bucket_info.host_url, + } + + if bucket_info.credentials: + params["credentials"] = bucket_info.credentials.to_dict() + + return params + + +def create_task(escrow_address: str, chain_id: int) -> None: + logger = get_function_logger(module_logger) + + manifest = parse_manifest(get_escrow_manifest(chain_id, escrow_address)) + + if manifest.annotation.type in [ + TaskTypes.image_boxes, + TaskTypes.image_points, + TaskTypes.image_label_binary, + ]: + data_bucket = BucketAccessInfo.parse_obj(manifest.data.data_url) + gt_bucket = BucketAccessInfo.parse_obj(manifest.validation.gt_url) + + data_bucket_client = cloud_service.make_client(data_bucket) + gt_bucket_client = cloud_service.make_client(gt_bucket) + + # Task configuration creation + data_filenames = data_bucket_client.list_files(prefix=data_bucket.path) + data_filenames = strip_bucket_prefix(data_filenames, prefix=data_bucket.path) + data_filenames = filter_image_files(data_filenames) + + gt_file_data = gt_bucket_client.download_file(gt_bucket.path) + + # Validate and parse GT + gt_filenames = get_gt_filenames(gt_file_data, data_filenames, manifest=manifest) + + job_configuration = make_job_configuration(data_filenames, gt_filenames, manifest=manifest) + label_configuration = make_label_configuration(manifest) + + # Register cloud storage on CVAT to pass user dataset + cloud_storage = cvat_api.create_cloudstorage(**_make_cvat_cloud_storage_params(data_bucket)) + + # Create a project + project = cvat_api.create_project( + escrow_address, + labels=label_configuration, + user_guide=manifest.annotation.user_guide, + ) + + # Setup webhooks for a project (update:task, update:job) + webhook = cvat_api.create_cvat_webhook(project.id) + + with SessionLocal.begin() as session: + db_service.create_project( + session, + project.id, + cloud_storage.id, + manifest.annotation.type, + escrow_address, + chain_id, + compose_bucket_url( + data_bucket.bucket_name, + bucket_host=data_bucket.host_url, + provider=data_bucket.provider, + ), + cvat_webhook_id=webhook.id, + ) + db_service.add_project_images(session, project.id, data_filenames) + + for job_filenames in job_configuration: + task = cvat_api.create_task(project.id, escrow_address) + + with SessionLocal.begin() as session: + db_service.create_task(session, task.id, project.id, TaskStatuses[task.status]) + + # Actual task creation in CVAT takes some time, so it's done in an async process. + # The task will be created in DB once 'update:task' or 'update:job' webhook is received. + cvat_api.put_task_data( + task.id, + cloud_storage.id, + filenames=[os.path.join(data_bucket.path, fn) for fn in job_filenames], + sort_images=False, + ) + + with SessionLocal.begin() as session: + db_service.create_data_upload(session, cvat_task_id=task.id) + + elif manifest.annotation.type in [TaskTypes.image_boxes_from_points]: + with BoxesFromPointsTaskBuilder(manifest, escrow_address, chain_id) as task_builder: + task_builder.set_logger(logger) + task_builder.build() + + elif manifest.annotation.type in [TaskTypes.image_skeletons_from_boxes]: + with SkeletonsFromBoxesTaskBuilder(manifest, escrow_address, chain_id) as task_builder: + task_builder.set_logger(logger) + task_builder.build() + + else: + raise Exception(f"Unsupported task type {manifest.annotation.type}") + + +def remove_task(escrow_address: str) -> None: + with SessionLocal.begin() as session: + project = db_service.get_project_by_escrow_address(session, escrow_address) + if project is not None: + if project.cvat_cloudstorage_id: + cvat_api.delete_cloudstorage(project.cvat_cloudstorage_id) + if project.cvat_id: + cvat_api.delete_project(project.cvat_id) + db_service.delete_project(session, project.id) diff --git a/packages/examples/cvat/exchange-oracle/src/handlers/job_export.py b/packages/examples/cvat/exchange-oracle/src/handlers/job_export.py new file mode 100644 index 0000000000..1f494b8cc5 --- /dev/null +++ b/packages/examples/cvat/exchange-oracle/src/handlers/job_export.py @@ -0,0 +1,601 @@ +import io +import os +import zipfile +from dataclasses import dataclass +from tempfile import TemporaryDirectory +from typing import Dict, List, Optional, Type + +import datumaro as dm +from datumaro.components.dataset import Dataset + +import src.core.tasks.boxes_from_points as boxes_from_points_task +import src.core.tasks.skeletons_from_boxes as skeletons_from_boxes_task +import src.utils.annotations as annotation_utils +from src.core.annotation_meta import ANNOTATION_RESULTS_METAFILE_NAME, AnnotationMeta, JobMeta +from src.core.config import Config +from src.core.manifest import TaskManifest +from src.core.storage import compose_data_bucket_filename +from src.core.types import TaskTypes +from src.handlers.job_creation import DM_DATASET_FORMAT_MAPPING +from src.models.cvat import Image, Job +from src.services.cloud import make_client as make_cloud_client +from src.services.cloud.utils import BucketAccessInfo +from src.utils.zip_archive import extract_zip_archive, write_dir_to_zip_archive + +CVAT_EXPORT_FORMAT_MAPPING = { + TaskTypes.image_label_binary: "CVAT for images 1.1", + TaskTypes.image_points: "CVAT for images 1.1", + TaskTypes.image_boxes: "COCO 1.0", + TaskTypes.image_boxes_from_points: "COCO 1.0", + TaskTypes.image_skeletons_from_boxes: "CVAT for images 1.1", +} + +CVAT_EXPORT_FORMAT_TO_DM_MAPPING = { + "CVAT for images 1.1": "cvat", + "COCO 1.0": "coco_instances", +} + + +@dataclass +class FileDescriptor: + filename: str + file: Optional[io.RawIOBase] + + +def prepare_annotation_metafile( + jobs: List[Job], job_annotations: Dict[int, FileDescriptor] +) -> FileDescriptor: + """ + Prepares a task/project annotation descriptor file with annotator mapping. + """ + + meta = AnnotationMeta( + jobs=[ + JobMeta( + job_id=job.cvat_id, + annotation_filename=job_annotations[job.cvat_id].filename, + annotator_wallet_address=job.latest_assignment.user_wallet_address, + assignment_id=job.latest_assignment.id, + ) + for job in jobs + ] + ) + + return FileDescriptor(ANNOTATION_RESULTS_METAFILE_NAME, file=io.BytesIO(meta.json().encode())) + + +class _TaskProcessor: + def __init__( + self, + escrow_address: str, + chain_id: int, + annotations: List[FileDescriptor], + merged_annotation: FileDescriptor, + *, + manifest: TaskManifest, + project_images: List[Image], + ): + self.escrow_address = escrow_address + self.chain_id = chain_id + self.annotation_files = annotations + self.merged_annotation_file = merged_annotation + self.manifest = manifest + self.project_images = project_images + + self.input_format = CVAT_EXPORT_FORMAT_TO_DM_MAPPING[ + CVAT_EXPORT_FORMAT_MAPPING[manifest.annotation.type] + ] + self.output_format = DM_DATASET_FORMAT_MAPPING[manifest.annotation.type] + + def _is_merged_dataset(self, ann_descriptor: FileDescriptor) -> bool: + return ann_descriptor == self.merged_annotation_file + + def process(self): + with TemporaryDirectory() as tempdir: + for ann_descriptor in self.annotation_files: + if not zipfile.is_zipfile(ann_descriptor.file): + raise ValueError("Annotation files must be zip files") + ann_descriptor.file.seek(0) + + extract_dir = os.path.join( + tempdir, + os.path.splitext(os.path.basename(ann_descriptor.filename))[0], + ) + extract_zip_archive(ann_descriptor.file, extract_dir) + + export_dir = os.path.join( + tempdir, + os.path.splitext(os.path.basename(ann_descriptor.filename))[0] + "_conv", + ) + + self._process_annotation_file(ann_descriptor, extract_dir, export_dir) + + converted_dataset_archive = io.BytesIO() + write_dir_to_zip_archive(export_dir, converted_dataset_archive) + converted_dataset_archive.seek(0) + + ann_descriptor.file = converted_dataset_archive + + def _process_annotation_file( + self, ann_descriptor: FileDescriptor, input_dir: str, output_dir: str + ): + input_dataset = self._parse_dataset(ann_descriptor, input_dir) + output_dataset = self._process_dataset(input_dataset, ann_descriptor=ann_descriptor) + self._export_dataset(output_dataset, output_dir) + + def _parse_dataset(self, ann_descriptor: FileDescriptor, dataset_dir: str) -> dm.Dataset: + return dm.Dataset.import_from(dataset_dir, self.input_format) + + def _export_dataset(self, dataset: dm.Dataset, output_dir: str): + dataset.export(output_dir, self.output_format, save_images=False) + + def _process_dataset( + self, dataset: dm.Dataset, *, ann_descriptor: FileDescriptor + ) -> dm.Dataset: + dataset = dataset.transform( + "map_subsets", mapping=[(sn, dm.DEFAULT_SUBSET_NAME) for sn in dataset.subsets()] + ) + + # TODO: remove complete duplicates in annotations + + if self._is_merged_dataset(ann_descriptor): + dataset = self._process_merged_dataset(dataset) + + return dataset + + def _process_merged_dataset(self, input_dataset: dm.Dataset) -> dm.Dataset: + return annotation_utils.remove_duplicated_gt_frames( + input_dataset, + known_frames=[image.filename for image in self.project_images], + ) + + +class _LabelsTaskProcessor(_TaskProcessor): + pass + + +class _BoxesTaskProcessor(_TaskProcessor): + pass + + +class _PointsTaskProcessor(_TaskProcessor): + def _parse_dataset(self, ann_descriptor: FileDescriptor, dataset_dir: str) -> Dataset: + annotation_utils.prepare_cvat_annotations_for_dm(dataset_dir) + return super()._parse_dataset(ann_descriptor, dataset_dir) + + def _process_dataset(self, dataset: Dataset, *, ann_descriptor: FileDescriptor) -> Dataset: + # We need to convert point arrays, which cannot be represented in COCO directly, + # into the 1-point skeletons, compatible with COCO person keypoints, which is the + # required output format + dataset = annotation_utils.convert_point_arrays_dataset_to_1_point_skeletons( + dataset, + labels=[label.name for label in self.manifest.annotation.labels], + ) + + return super()._process_dataset(dataset, ann_descriptor=ann_descriptor) + + +class _BoxesFromPointsTaskProcessor(_TaskProcessor): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + roi_filenames, roi_infos, points_dataset = self._download_task_meta() + + self.points_dataset = points_dataset + self.original_key_to_sample = {sample.attributes["id"]: sample for sample in points_dataset} + + roi_info_by_id = {roi_info.point_id: roi_info for roi_info in roi_infos} + + self.roi_name_to_roi_info: Dict[str, boxes_from_points_task.RoiInfo] = { + os.path.splitext(roi_filename)[0]: roi_info_by_id[roi_id] + for roi_id, roi_filename in roi_filenames.items() + } + + def _download_task_meta(self): + layout = boxes_from_points_task.TaskMetaLayout() + serializer = boxes_from_points_task.TaskMetaSerializer() + + oracle_data_bucket = BucketAccessInfo.parse_obj(Config.storage_config) + storage_client = make_cloud_client(oracle_data_bucket) + + roi_filenames = serializer.parse_roi_filenames( + storage_client.download_file( + compose_data_bucket_filename( + self.escrow_address, self.chain_id, layout.ROI_FILENAMES_FILENAME + ), + ) + ) + + rois = serializer.parse_roi_info( + storage_client.download_file( + compose_data_bucket_filename( + self.escrow_address, self.chain_id, layout.ROI_INFO_FILENAME + ), + ) + ) + + points_dataset = serializer.parse_points_annotations( + storage_client.download_file( + compose_data_bucket_filename( + self.escrow_address, self.chain_id, layout.POINTS_FILENAME + ), + ) + ) + + return roi_filenames, rois, points_dataset + + def _process_merged_dataset(self, input_dataset: Dataset) -> Dataset: + point_roi_dataset = super()._process_merged_dataset(input_dataset) + + merged_sample_dataset = dm.Dataset( + media_type=dm.Image, + categories={ + dm.AnnotationType.label: dm.LabelCategories.from_iterable( + [label.name for label in self.manifest.annotation.labels] + ) + }, + ) + + for roi_sample in point_roi_dataset: + roi_info = self.roi_name_to_roi_info[os.path.basename(roi_sample.id)] + original_sample = self.original_key_to_sample[roi_info.original_image_key] + + merged_sample = merged_sample_dataset.get(original_sample.id) + if not merged_sample: + merged_sample = original_sample.wrap(annotations=[]) + merged_sample_dataset.put(merged_sample) + + image_h, image_w = merged_sample.image.size + + old_point = next( + skeleton + for skeleton in original_sample.annotations + if skeleton.id == roi_info.point_id + if isinstance(skeleton, dm.Skeleton) + ).elements[0] + old_x, old_y = old_point.points[:2] + + merged_sample.annotations.extend( + annotation_utils.shift_ann( + roi_ann, + offset_x=old_x - roi_info.point_x, + offset_y=old_y - roi_info.point_y, + img_w=image_w, + img_h=image_h, + ) + for roi_ann in roi_sample.annotations + if isinstance(roi_ann, dm.Bbox) + ) + + return merged_sample_dataset + + +class _SkeletonsFromBoxesTaskProcessor(_TaskProcessor): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + roi_filenames, roi_infos, boxes_dataset, job_label_mapping = self._download_task_meta() + + self.boxes_dataset = boxes_dataset + self.original_key_to_sample = {sample.attributes["id"]: sample for sample in boxes_dataset} + + roi_info_by_id = {roi_info.bbox_id: roi_info for roi_info in roi_infos} + + self.roi_name_to_roi_info: Dict[str, skeletons_from_boxes_task.RoiInfo] = { + os.path.splitext(roi_filename)[0]: roi_info_by_id[roi_id] + for roi_id, roi_filename in roi_filenames.items() + } + + self.job_label_mapping = job_label_mapping + + def _download_task_meta(self): + layout = skeletons_from_boxes_task.TaskMetaLayout() + serializer = skeletons_from_boxes_task.TaskMetaSerializer() + + oracle_data_bucket = BucketAccessInfo.parse_obj(Config.storage_config) + storage_client = make_cloud_client(oracle_data_bucket) + + roi_filenames = serializer.parse_roi_filenames( + storage_client.download_file( + compose_data_bucket_filename( + self.escrow_address, self.chain_id, layout.ROI_FILENAMES_FILENAME + ), + ) + ) + + rois = serializer.parse_roi_info( + storage_client.download_file( + compose_data_bucket_filename( + self.escrow_address, self.chain_id, layout.ROI_INFO_FILENAME + ), + ) + ) + + boxes_dataset = serializer.parse_bbox_annotations( + storage_client.download_file( + compose_data_bucket_filename( + self.escrow_address, self.chain_id, layout.BOXES_FILENAME + ), + ) + ) + + job_label_mapping = serializer.parse_point_labels( + storage_client.download_file( + compose_data_bucket_filename( + self.escrow_address, self.chain_id, layout.POINT_LABELS_FILENAME + ), + ) + ) + + return roi_filenames, rois, boxes_dataset, job_label_mapping + + def _init_merged_dataset(self): + label_cat = dm.LabelCategories() + points_cat = dm.PointsCategories() + + # Maintain label order for simplicity + for manifest_skeleton in self.manifest.annotation.labels: + label_cat.add(manifest_skeleton.name) + + for manifest_skeleton in self.manifest.annotation.labels: + points_cat.add( + label_cat.find(manifest_skeleton.name)[0], + labels=manifest_skeleton.nodes, + joints=manifest_skeleton.joints, + ) + + for manifest_skeleton_point in manifest_skeleton.nodes: + label_cat.add(manifest_skeleton_point, parent=manifest_skeleton.name) + + self.job_point_label_id_to_merged_label_id = { + (label_cat.find(skeleton_label)[0], job_point_label): label_cat.find( + point_label, parent=skeleton_label + )[0] + for (skeleton_label, point_label), job_point_label in self.job_label_mapping.items() + } + + self.merged_dataset = dm.Dataset( + categories={ + dm.AnnotationType.label: label_cat, + dm.AnnotationType.points: points_cat, + }, + media_type=dm.Image, + ) + + bbox_label_to_merged = { + bbox_label_id: label_cat.find(bbox_label.name)[0] + for bbox_label_id, bbox_label in enumerate( + self.boxes_dataset.categories()[dm.AnnotationType.label] + ) + } + self.bbox_label_to_merged = bbox_label_to_merged + + for bbox_sample in self.boxes_dataset: + self.merged_dataset.put(bbox_sample.wrap(annotations=[])) + + def _parse_dataset(self, ann_descriptor: FileDescriptor, dataset_dir: str) -> Dataset: + annotation_utils.prepare_cvat_annotations_for_dm(dataset_dir) + return super()._parse_dataset(ann_descriptor, dataset_dir) + + def _process_dataset(self, dataset: Dataset, *, ann_descriptor: FileDescriptor) -> Dataset: + dataset = super()._process_dataset(dataset, ann_descriptor=ann_descriptor) + + job_label_cat = dataset.categories()[dm.AnnotationType.label] + merged_skeleton_id = None + merged_skeleton_name = None + + merged_label_cat = self.merged_dataset.categories()[dm.AnnotationType.label] + merged_points_cat = self.merged_dataset.categories()[dm.AnnotationType.points] + + # We need to convert point arrays, which cannot be represented in COCO directly, + # into the skeletons, compatible with COCO person keypoints, which is the + # required output format + converted_job_dataset = annotation_utils.convert_point_arrays_dataset_to_1_point_skeletons( + dataset, labels=[label.name for label in job_label_cat] + ) + + # Accumulate points into the merged dataset + add boxes + for job_sample in dataset: + roi_info = self.roi_name_to_roi_info[os.path.basename(job_sample.id)] + + if merged_skeleton_name is None: + merged_skeleton_id = self.bbox_label_to_merged[roi_info.bbox_label] + merged_skeleton_name = self.merged_dataset.categories()[dm.AnnotationType.label][ + merged_skeleton_id + ].name + + bbox_sample = self.original_key_to_sample[roi_info.original_image_key] + assert bbox_sample + + skeleton_sample = self.merged_dataset.get(bbox_sample.id) + assert skeleton_sample + + image_h, image_w = bbox_sample.image.size + + old_bbox = next( + bbox + for bbox in bbox_sample.annotations + if bbox.id == roi_info.bbox_id + if isinstance(bbox, dm.Bbox) + ) + offset_x = old_bbox.x - roi_info.bbox_x + offset_y = old_bbox.y - roi_info.bbox_y + + merged_skeleton = next( + ( + ann + for ann in skeleton_sample.annotations + if isinstance(ann, dm.Skeleton) + if ann.id == roi_info.bbox_id and isinstance(ann, dm.Skeleton) + ), + None, + ) + if not merged_skeleton: + merged_skeleton = dm.Skeleton( + elements=[ + dm.Points( + [0, 0], + visibility=[dm.Points.Visibility.hidden], + label=merged_label_cat.find( + point_label, + parent=merged_label_cat[ + self.bbox_label_to_merged[old_bbox.label] + ].name, + )[0], + ) + for point_label in merged_points_cat[ + self.bbox_label_to_merged[old_bbox.label] + ].labels + ], + label=self.bbox_label_to_merged[old_bbox.label], + id=old_bbox.id, + ) + skeleton_sample.annotations.append(merged_skeleton) + + # TODO: think about discarding invalid annotations (points) + # e.g. everything beyond the bbox, duplicates, and extra points. + # For now, just take the first one available, as only 1 must be put by annotators + job_sample_points = annotation_utils.flatten_points( + [p for p in job_sample.annotations if isinstance(p, dm.Points)] + ) + if not job_sample_points: + continue + + job_point = job_sample_points[0] + + skeleton_point_idx, skeleton_point = next( + (p_idx, p) + for p_idx, p in enumerate(merged_skeleton.elements) + if p.label + == self.job_point_label_id_to_merged_label_id[ + (roi_info.bbox_label, job_label_cat[job_point.label].name) + ] + ) + + skeleton_point = skeleton_point.wrap( + points=job_point.points[:2], visibility=[job_point.visibility[0]] + ) + + skeleton_point = annotation_utils.shift_ann( + skeleton_point, offset_x=offset_x, offset_y=offset_y, img_h=image_h, img_w=image_w + ) + + merged_skeleton.elements[skeleton_point_idx] = skeleton_point + + # Append skeleton bbox + converted_sample = converted_job_dataset.get(job_sample.id, subset=job_sample.subset) + assert converted_sample + + skeleton_bbox = annotation_utils.shift_ann( + old_bbox, + offset_x=-offset_x, + offset_y=-offset_y, + img_w=roi_info.roi_w, + img_h=roi_info.roi_h, + ) + + # Join annotations into a group for correct distance comparison + skeleton_group = 1 + converted_skeleton = next( + s for s in converted_sample.annotations if isinstance(s, dm.Skeleton) + ) + converted_skeleton.group = skeleton_group + skeleton_bbox.group = skeleton_group + skeleton_bbox.label = converted_skeleton.label + converted_job_dataset.put( + converted_sample.wrap(annotations=converted_sample.annotations + [skeleton_bbox]) + ) + + # Rename the job skeleton and point to the original names + assert merged_skeleton_name + converted_label_cat: dm.LabelCategories = converted_job_dataset.categories()[ + dm.AnnotationType.label + ] + converted_points_cat: dm.PointsCategories = converted_job_dataset.categories()[ + dm.AnnotationType.points + ] + + assert len(converted_label_cat) == 2 + converted_skeleton_id, converted_skeleton_label = next( + (i, c) for i, c in enumerate(converted_label_cat) if not c.parent + ) + converted_point_label = next(c for c in converted_label_cat if c.parent) + + converted_skeleton_label.name = merged_skeleton_name + converted_point_label.parent = merged_skeleton_name + + merged_point_label_id = self.job_point_label_id_to_merged_label_id[ + (merged_skeleton_id, converted_point_label.name) + ] + converted_point_label.name = self.merged_dataset.categories()[dm.AnnotationType.label][ + merged_point_label_id + ].name + + converted_points_cat[converted_skeleton_id].labels = [converted_point_label.name] + + converted_label_cat._reindex() + + return converted_job_dataset + + def _process_merged_dataset(self, merged_dataset: Dataset) -> Dataset: + return merged_dataset + + def process(self): + assert self.merged_annotation_file.file is None + + # Accumulate points from separate job annotations, then export the resulting dataset + self._init_merged_dataset() + + self.annotation_files = [ + fd for fd in self.annotation_files if not self._is_merged_dataset(fd) + ] + super().process() + self.annotation_files.append(self.merged_annotation_file) + + with TemporaryDirectory() as tempdir: + export_dir = os.path.join( + tempdir, + os.path.splitext(os.path.basename(self.merged_annotation_file.filename))[0] + + "_conv", + ) + + merged_dataset = self._process_merged_dataset(self.merged_dataset) + self._export_dataset(merged_dataset, export_dir) + + converted_dataset_archive = io.BytesIO() + write_dir_to_zip_archive(export_dir, converted_dataset_archive) + converted_dataset_archive.seek(0) + + self.merged_annotation_file.file = converted_dataset_archive + + +def postprocess_annotations( + escrow_address: str, + chain_id: int, + annotations: List[FileDescriptor], + merged_annotation: FileDescriptor, + *, + manifest: TaskManifest, + project_images: List[Image], +) -> None: + """ + Processes annotations and updates the files list inplace + """ + processor_classes: Dict[TaskTypes, Type[_TaskProcessor]] = { + TaskTypes.image_label_binary: _LabelsTaskProcessor, + TaskTypes.image_boxes: _BoxesTaskProcessor, + TaskTypes.image_points: _PointsTaskProcessor, + TaskTypes.image_boxes_from_points: _BoxesFromPointsTaskProcessor, + TaskTypes.image_skeletons_from_boxes: _SkeletonsFromBoxesTaskProcessor, + } + + task_type = manifest.annotation.type + processor = processor_classes[task_type]( + escrow_address=escrow_address, + chain_id=chain_id, + annotations=annotations, + merged_annotation=merged_annotation, + manifest=manifest, + project_images=project_images, + ) + processor.process() diff --git a/packages/examples/cvat/exchange-oracle/src/models/cvat.py b/packages/examples/cvat/exchange-oracle/src/models/cvat.py index 2ffed4ccd6..d6193f457a 100644 --- a/packages/examples/cvat/exchange-oracle/src/models/cvat.py +++ b/packages/examples/cvat/exchange-oracle/src/models/cvat.py @@ -8,12 +8,12 @@ from sqlalchemy.sql import func from src.core.types import ( - AssignmentStatus, + AssignmentStatuses, JobStatuses, Networks, ProjectStatuses, - TaskStatus, - TaskType, + TaskStatuses, + TaskTypes, ) from src.db import Base from src.utils.time import utcnow @@ -25,8 +25,10 @@ class Project(Base): cvat_id = Column(Integer, unique=True, index=True, nullable=False) cvat_cloudstorage_id = Column(Integer, index=True, nullable=False) status = Column(String, Enum(ProjectStatuses), nullable=False) - job_type = Column(String, Enum(TaskType), nullable=False) - escrow_address = Column(String(42), unique=True, nullable=False) + job_type = Column(String, Enum(TaskTypes), nullable=False) + escrow_address = Column( + String(42), unique=False, nullable=False + ) # TODO: extract into a separate model chain_id = Column(Integer, Enum(Networks), nullable=False) bucket_url = Column(String, nullable=False) created_at = Column(DateTime(timezone=True), server_default=func.now()) @@ -62,7 +64,7 @@ class Task(Base): ForeignKey("projects.cvat_id", ondelete="CASCADE"), nullable=False, ) - status = Column(String, Enum(TaskStatus), nullable=False) + status = Column(String, Enum(TaskStatuses), nullable=False) created_at = Column(DateTime(timezone=True), server_default=func.now()) updated_at = Column(DateTime(timezone=True), onupdate=func.now()) @@ -157,8 +159,8 @@ class Assignment(Base): cvat_job_id = Column(Integer, ForeignKey("jobs.cvat_id", ondelete="CASCADE"), nullable=False) status = Column( String, - Enum(AssignmentStatus), - server_default=AssignmentStatus.created.value, + Enum(AssignmentStatuses), + server_default=AssignmentStatuses.created.value, nullable=False, ) @@ -170,7 +172,7 @@ def is_finished(self) -> bool: return ( self.completed_at or utcnow() > self.expires_at - or self.status != AssignmentStatus.created + or self.status != AssignmentStatuses.created ) def __repr__(self): diff --git a/packages/examples/cvat/exchange-oracle/src/schemas/exchange.py b/packages/examples/cvat/exchange-oracle/src/schemas/exchange.py index 96398c1bbf..db156e7f8c 100644 --- a/packages/examples/cvat/exchange-oracle/src/schemas/exchange.py +++ b/packages/examples/cvat/exchange-oracle/src/schemas/exchange.py @@ -3,7 +3,7 @@ from pydantic import AnyUrl, BaseModel, Field -from src.core.types import PlatformType, ProjectStatuses, TaskType +from src.core.types import PlatformTypes, ProjectStatuses, TaskTypes class AssignmentResponse(BaseModel): @@ -17,11 +17,11 @@ class TaskResponse(BaseModel): escrow_address: str title: str description: str - platform: PlatformType + platform: PlatformTypes job_bounty: str job_size: int job_time_limit: int - job_type: TaskType + job_type: TaskTypes assignment: Optional[AssignmentResponse] = None status: ProjectStatuses diff --git a/packages/examples/cvat/exchange-oracle/src/schemas/webhook.py b/packages/examples/cvat/exchange-oracle/src/schemas/webhook.py index ac3e7afcf4..79cedafdcb 100644 --- a/packages/examples/cvat/exchange-oracle/src/schemas/webhook.py +++ b/packages/examples/cvat/exchange-oracle/src/schemas/webhook.py @@ -4,7 +4,7 @@ from pydantic import BaseModel, validator from src.chain.web3 import validate_address -from src.core.types import JobLauncherEventType, Networks +from src.core.types import JobLauncherEventTypes, Networks class OracleWebhook(BaseModel): @@ -24,7 +24,7 @@ class Config: "example": { "escrow_address": "0x199c44cfa6a84554ac01f3e3b01d7cfce38a75eb", "chain_id": 80001, - "event_type": JobLauncherEventType.escrow_created.value, + "event_type": JobLauncherEventTypes.escrow_created.value, "event_data": {}, } } diff --git a/packages/examples/cvat/exchange-oracle/src/services/cloud/__init__.py b/packages/examples/cvat/exchange-oracle/src/services/cloud/__init__.py index eb128cc100..e492c74156 100644 --- a/packages/examples/cvat/exchange-oracle/src/services/cloud/__init__.py +++ b/packages/examples/cvat/exchange-oracle/src/services/cloud/__init__.py @@ -1,13 +1,3 @@ -from typing import Optional - -from src.services.cloud.client import S3Client - - -def download_file(bucket_host: str, bucket_name: str, filename: str) -> bytes: - client = S3Client(bucket_host) - return client.download_fileobj(bucket_name, filename) - - -def list_files(bucket_host: str, bucket_name: str, path: Optional[str] = None) -> list[str]: - client = S3Client(bucket_host) - return [f.key for f in client.list_files(bucket_name, path=path)] +from src.services.cloud.client import StorageClient +from src.services.cloud.types import BucketAccessInfo, BucketCredentials, CloudProviders +from src.services.cloud.utils import make_client diff --git a/packages/examples/cvat/exchange-oracle/src/services/cloud/client.py b/packages/examples/cvat/exchange-oracle/src/services/cloud/client.py index 2c6101a573..5bb92d77e3 100644 --- a/packages/examples/cvat/exchange-oracle/src/services/cloud/client.py +++ b/packages/examples/cvat/exchange-oracle/src/services/cloud/client.py @@ -1,61 +1,37 @@ -# Copyright (C) 2022 CVAT.ai Corporation -# -# SPDX-License-Identifier: MIT - -from io import BytesIO +from abc import ABCMeta, abstractmethod from typing import List, Optional - -import boto3 -from botocore.exceptions import ClientError -from botocore.handlers import disable_signing +from urllib.parse import unquote -class S3Client: +class StorageClient(metaclass=ABCMeta): def __init__( self, - endpoint_url: str, - *, - access_key: Optional[str] = None, - secret_key: Optional[str] = None, + bucket: Optional[str] = None, ) -> None: - s3 = boto3.resource( - "s3", - **(dict(aws_access_key_id=access_key) if access_key else {}), - **(dict(aws_secret_access_key=secret_key) if secret_key else {}), - endpoint_url=endpoint_url, - ) - - self.resource = s3 - self.client = s3.meta.client - - if not access_key and not secret_key: - self.client.meta.events.register("choose-signer.s3.*", disable_signing) - - def create_file(self, bucket: str, filename: str, data: bytes = b""): - self.client.put_object(Body=data, Bucket=bucket, Key=filename) - - def remove_file(self, bucket: str, filename: str): - self.client.delete_object(Bucket=bucket, Key=filename) - - def file_exists(self, bucket: str, filename: str) -> bool: - try: - self.client.head_object(Bucket=bucket, Key=filename) - return True - except ClientError as e: - if e.response["Error"]["Code"] == "404": - return False - else: - raise - - def download_fileobj(self, bucket: str, key: str) -> bytes: - with BytesIO() as data: - self.client.download_fileobj(Bucket=bucket, Key=key, Fileobj=data) - return data.getvalue() - - def list_files(self, bucket: str, path: Optional[str] = None) -> List: - objects = self.resource.Bucket(bucket).objects - if path: - objects = objects.filter(Prefix=path.strip("/\\") + "/") - else: - objects = objects.all() - return list(objects) + self._bucket = unquote(bucket) if bucket else None + + @abstractmethod + def create_file(self, key: str, data: bytes = b"", *, bucket: Optional[str] = None): + ... + + @abstractmethod + def remove_file(self, key: str, *, bucket: Optional[str] = None): + ... + + @abstractmethod + def file_exists(self, key: str, *, bucket: Optional[str] = None) -> bool: + ... + + @abstractmethod + def download_file(self, key: str, *, bucket: Optional[str] = None) -> bytes: + ... + + @abstractmethod + def list_files( + self, *, bucket: Optional[str] = None, prefix: Optional[str] = None + ) -> List[str]: + ... + + @staticmethod + def normalize_prefix(prefix: Optional[str]) -> Optional[str]: + return unquote(prefix).strip("/\\") + "/" if prefix else prefix diff --git a/packages/examples/cvat/exchange-oracle/src/services/cloud/gcs.py b/packages/examples/cvat/exchange-oracle/src/services/cloud/gcs.py new file mode 100644 index 0000000000..36611b363f --- /dev/null +++ b/packages/examples/cvat/exchange-oracle/src/services/cloud/gcs.py @@ -0,0 +1,70 @@ +from io import BytesIO +from typing import Dict, List, Optional +from urllib.parse import unquote + +from google.cloud import storage + +from src.services.cloud.client import StorageClient + +DEFAULT_GCS_HOST = "storage.googleapis.com" + + +class GcsClient(StorageClient): + def __init__( + self, + *, + bucket: Optional[str] = None, + service_account_key: Optional[Dict] = None, + ) -> None: + super().__init__(bucket) + + if service_account_key: + self.client = storage.Client.from_service_account_info(service_account_key) + else: + self.client = storage.Client.create_anonymous_client() + + def create_file(self, key: str, data: bytes = b"", *, bucket: Optional[str] = None) -> None: + bucket = unquote(bucket) if bucket else self._bucket + bucket_client = self.client.get_bucket(bucket) + bucket_client.blob(unquote(key)).upload_from_string(data) + + def remove_file(self, key: str, *, bucket: Optional[str] = None) -> None: + bucket = unquote(bucket) if bucket else self._bucket + bucket_client = self.client.get_bucket(bucket) + bucket_client.delete_blob(unquote(key)) + + def file_exists(self, key: str, *, bucket: Optional[str] = None) -> bool: + bucket = unquote(bucket) if bucket else self._bucket + bucket_client = self.client.get_bucket(bucket) + return bucket_client.blob(unquote(key)).exists() + + def download_file(self, key: str, *, bucket: Optional[str] = None) -> bytes: + bucket = unquote(bucket) if bucket else self._bucket + bucket_client = self.client.get_bucket(bucket) + blob = bucket_client.blob(unquote(key)) + + with BytesIO() as data: + self.client.download_blob_to_file(blob, data) + return data.getvalue() + + def list_files( + self, *, bucket: Optional[str] = None, prefix: Optional[str] = None + ) -> List[str]: + bucket = unquote(bucket) if bucket else self._bucket + prefix = self.normalize_prefix(prefix) + + return [ + blob.name + for blob in self.client.list_blobs( + bucket_or_name=bucket, + fields="items(name)", + **( + { + "prefix": prefix, + "delimiter": "/", + } + if prefix + else {} + ), + ) + ] diff --git a/packages/examples/cvat/exchange-oracle/src/services/cloud/s3.py b/packages/examples/cvat/exchange-oracle/src/services/cloud/s3.py new file mode 100644 index 0000000000..e8e608ce99 --- /dev/null +++ b/packages/examples/cvat/exchange-oracle/src/services/cloud/s3.py @@ -0,0 +1,71 @@ +from io import BytesIO +from typing import List, Optional +from urllib.parse import unquote + +import boto3 +from botocore.exceptions import ClientError +from botocore.handlers import disable_signing + +from src.services.cloud.client import StorageClient + +DEFAULT_S3_HOST = "s3.amazonaws.com" + + +class S3Client(StorageClient): + def __init__( + self, + *, + bucket: Optional[str] = None, + access_key: Optional[str] = None, + secret_key: Optional[str] = None, + endpoint_url: Optional[str] = None, + ) -> None: + super().__init__(bucket) + session = boto3.Session( + **(dict(aws_access_key_id=access_key) if access_key else {}), + **(dict(aws_secret_access_key=secret_key) if secret_key else {}), + ) + s3 = session.resource( + "s3", **({"endpoint_url": unquote(endpoint_url)} if endpoint_url else {}) + ) + self.resource = s3 + self.client = s3.meta.client + + if not access_key and not secret_key: + self.client.meta.events.register("choose-signer.s3.*", disable_signing) + + def create_file(self, key: str, data: bytes = b"", *, bucket: Optional[str] = None): + bucket = unquote(bucket) if bucket else self._bucket + self.client.put_object(Body=data, Bucket=bucket, Key=unquote(key)) + + def remove_file(self, key: str, *, bucket: Optional[str] = None): + bucket = unquote(bucket) if bucket else self._bucket + self.client.delete_object(Bucket=bucket, Key=unquote(key)) + + def file_exists(self, key: str, *, bucket: Optional[str] = None) -> bool: + bucket = unquote(bucket) if bucket else self._bucket + try: + self.client.head_object(Bucket=bucket, Key=unquote(key)) + return True + except ClientError as e: + if e.response["Error"]["Code"] == "404": + return False + else: + raise + + def download_file(self, key: str, *, bucket: Optional[str] = None) -> bytes: + bucket = unquote(bucket) if bucket else self._bucket + with BytesIO() as data: + self.client.download_fileobj(Bucket=bucket, Key=unquote(key), Fileobj=data) + return data.getvalue() + + def list_files( + self, *, bucket: Optional[str] = None, prefix: Optional[str] = None + ) -> List[str]: + bucket = unquote(bucket) if bucket else self._bucket + objects = self.resource.Bucket(bucket).objects + if prefix: + objects = objects.filter(Prefix=self.normalize_prefix(prefix)) + else: + objects = objects.all() + return [file_info.key for file_info in objects] diff --git a/packages/examples/cvat/exchange-oracle/src/services/cloud/types.py b/packages/examples/cvat/exchange-oracle/src/services/cloud/types.py new file mode 100644 index 0000000000..5f5b0db3ad --- /dev/null +++ b/packages/examples/cvat/exchange-oracle/src/services/cloud/types.py @@ -0,0 +1,187 @@ +from __future__ import annotations + +import json +from dataclasses import asdict, dataclass, is_dataclass +from enum import Enum, auto +from inspect import isclass +from typing import Dict, Optional, Type, Union +from urllib.parse import urlparse + +from src.core import manifest +from src.core.config import Config, StorageConfig +from src.services.cloud.gcs import DEFAULT_GCS_HOST +from src.services.cloud.s3 import DEFAULT_S3_HOST +from src.utils.enums import BetterEnumMeta +from src.utils.net import is_ipv4 + + +class CloudProviders(Enum, metaclass=BetterEnumMeta): + aws = auto() + gcs = auto() + + @classmethod + def from_str(cls, provider: str) -> CloudProviders: + try: + return cls[provider.lower()] + except KeyError: + raise ValueError( + f"The '{provider}' is not supported. " + f"List with supported providers: {', '.join(x.name for x in cls)}" + ) + + +class BucketCredentials: + def to_dict(self) -> Dict: + if not is_dataclass(self): + raise NotImplementedError + + return asdict(self) + + @classmethod + def from_storage_config(cls, config: Type[StorageConfig]) -> Optional[BucketCredentials]: + credentials = None + + if (config.access_key or config.secret_key) and config.provider.lower() != "aws": + raise ValueError( + "Invalid storage configuration. The access_key/secret_key pair" + f"cannot be specified with {config.provider} provider" + ) + elif ( + bool(config.access_key) ^ bool(config.secret_key) + ) and config.provider.lower() == "aws": + raise ValueError( + "Invalid storage configuration. " + "Either none or both access_key and secret_key must be specified for an AWS storage" + ) + + if config.key_file_path and config.provider.lower() != "gcs": + raise ValueError( + "Invalid storage configuration. The key_file_path" + f"cannot be specified with {config.provider} provider" + ) + + if config.access_key and config.secret_key: + credentials = S3BucketCredentials(config.access_key, config.secret_key) + elif config.key_file_path: + with open(config.key_file_path, "rb") as f: + credentials = GcsBucketCredentials(json.load(f)) + + return credentials + + +@dataclass +class GcsBucketCredentials(BucketCredentials): + service_account_key: Dict + + +@dataclass +class S3BucketCredentials(BucketCredentials): + access_key: str + secret_key: str + + +@dataclass +class BucketAccessInfo: + provider: CloudProviders + host_url: str + bucket_name: str + path: Optional[str] = None + credentials: Optional[BucketCredentials] = None + + @classmethod + def from_url(cls, url: str) -> BucketAccessInfo: + parsed_url = urlparse(url) + + if parsed_url.netloc.endswith(DEFAULT_S3_HOST): + # AWS S3 bucket + return BucketAccessInfo( + provider=CloudProviders.aws, + host_url=f"https://{DEFAULT_S3_HOST}", + bucket_name=parsed_url.netloc.split(".")[0], + path=parsed_url.path.lstrip("/"), + ) + elif parsed_url.netloc.endswith(DEFAULT_GCS_HOST): + # Google Cloud Storage (GCS) bucket + # Virtual hosted-style is expected: + # https://BUCKET_NAME.storage.googleapis.com/OBJECT_NAME + return BucketAccessInfo( + provider=CloudProviders.gcs, + bucket_name=parsed_url.netloc[: -len(f".{DEFAULT_GCS_HOST}")], + host_url=f"{parsed_url.scheme}://{DEFAULT_GCS_HOST}", + path=parsed_url.path.lstrip("/"), + ) + elif Config.features.enable_custom_cloud_host: + if is_ipv4(parsed_url.netloc): + host = parsed_url.netloc + bucket_name, path = parsed_url.path.lstrip("/").split("/", maxsplit=1) + else: + host = parsed_url.netloc.partition(".")[2] + bucket_name = parsed_url.netloc.split(".")[0] + path = parsed_url.path.lstrip("/") + + return BucketAccessInfo( + provider=CloudProviders.aws, + host_url=f"{parsed_url.scheme}://{host}", + bucket_name=bucket_name, + path=path, + ) + else: + raise ValueError(f"{parsed_url.netloc} cloud provider is not supported.") + + @classmethod + def _from_dict(cls, data: Dict) -> BucketAccessInfo: + for required_field in ( + "provider", + "bucket_name", + ): + if required_field not in data: + raise ValueError( + f"The {required_field} is required and is not " + "specified in the bucket configuration" + ) + + provider = CloudProviders.from_str(data["provider"]) + data["provider"] = provider + + if provider == CloudProviders.aws: + access_key = data.pop("access_key", None) + secret_key = data.pop("secret_key", None) + if bool(access_key) ^ bool(secret_key): + raise ValueError("access_key and secret_key can only be used together") + + data["credentials"] = S3BucketCredentials(access_key, secret_key) + + elif provider == CloudProviders.gcs and ( + service_account_key := data.pop("service_account_key", None) + ): + data["credentials"] = GcsBucketCredentials(service_account_key) + + return BucketAccessInfo(**data) + + @classmethod + def from_storage_config(cls, config: Type[StorageConfig]) -> BucketAccessInfo: + credentials = BucketCredentials.from_storage_config(config) + + return BucketAccessInfo( + provider=CloudProviders.from_str(config.provider), + host_url=config.provider_endpoint_url(), + bucket_name=config.data_bucket_name, + credentials=credentials, + ) + + @classmethod + def from_bucket_url(cls, bucket_url: manifest.BucketUrl) -> BucketAccessInfo: + return cls._from_dict(bucket_url.dict()) + + @classmethod + def parse_obj( + cls, data: Union[str, Type[StorageConfig], manifest.BucketUrl] + ) -> BucketAccessInfo: + if isinstance(data, manifest.BucketUrlBase): + return cls.from_bucket_url(data) + elif isinstance(data, str): + return cls.from_url(data) + elif isclass(data) and issubclass(data, StorageConfig): + return cls.from_storage_config(data) + + raise TypeError(f"Unsupported data type ({type(data)}) was provided") diff --git a/packages/examples/cvat/exchange-oracle/src/services/cloud/utils.py b/packages/examples/cvat/exchange-oracle/src/services/cloud/utils.py new file mode 100644 index 0000000000..a9f821d174 --- /dev/null +++ b/packages/examples/cvat/exchange-oracle/src/services/cloud/utils.py @@ -0,0 +1,44 @@ +from typing import Optional + +from src.services.cloud.client import StorageClient +from src.services.cloud.gcs import DEFAULT_GCS_HOST, GcsClient +from src.services.cloud.s3 import DEFAULT_S3_HOST, S3Client +from src.services.cloud.types import BucketAccessInfo, CloudProviders + + +def compose_bucket_url( + bucket_name: str, provider: CloudProviders, *, bucket_host: Optional[str] = None +) -> str: + match provider: + case CloudProviders.aws: + return f"https://{bucket_name}.{bucket_host or DEFAULT_S3_HOST}/" + case CloudProviders.gcs: + return f"https://{bucket_name}.{bucket_host or DEFAULT_GCS_HOST}/" + + +def make_client( + bucket_info: BucketAccessInfo, +) -> StorageClient: + client_kwargs = { + "bucket": bucket_info.bucket_name, + } + + match bucket_info.provider: + case CloudProviders.aws: + client_type = S3Client + + if bucket_info.credentials: + client_kwargs["access_key"] = bucket_info.credentials.access_key + client_kwargs["secret_key"] = bucket_info.credentials.secret_key + + if bucket_info.host_url: + client_kwargs["endpoint_url"] = bucket_info.host_url + case CloudProviders.gcs: + client_type = GcsClient + + if bucket_info.credentials: + client_kwargs["service_account_key"] = bucket_info.credentials.service_account_key + case _: + raise ValueError(f"Unsupported cloud provider ({bucket_info.provider}) was provided") + + return client_type(**client_kwargs) diff --git a/packages/examples/cvat/exchange-oracle/src/services/cvat.py b/packages/examples/cvat/exchange-oracle/src/services/cvat.py index fd78b5ecd8..c467244b69 100644 --- a/packages/examples/cvat/exchange-oracle/src/services/cvat.py +++ b/packages/examples/cvat/exchange-oracle/src/services/cvat.py @@ -1,11 +1,12 @@ +import itertools import uuid from datetime import datetime -from typing import List, Optional, Union +from typing import List, Optional, Sequence, Union from sqlalchemy import delete, insert, update from sqlalchemy.orm import Session -from src.core.types import AssignmentStatus, JobStatuses, ProjectStatuses, TaskStatus +from src.core.types import AssignmentStatuses, JobStatuses, ProjectStatuses, TaskStatuses, TaskTypes from src.db.utils import ForUpdateParams from src.db.utils import maybe_for_update as _maybe_for_update from src.models.cvat import Assignment, DataUpload, Image, Job, Project, Task, User @@ -63,6 +64,27 @@ def get_project_by_id( ) +def get_projects_by_cvat_ids( + session: Session, + project_cvat_ids: Sequence[int], + *, + for_update: Union[bool, ForUpdateParams] = False, + status_in: Optional[List[ProjectStatuses]] = None, + limit: int = 5, +) -> List[Project]: + if status_in: + status_filter_arg = [Project.status.in_(s.value for s in status_in)] + else: + status_filter_arg = [] + + return ( + _maybe_for_update(session.query(Project), enable=for_update) + .where(Project.cvat_id.in_(project_cvat_ids), *status_filter_arg) + .limit(limit) + .all() + ) + + def get_project_by_escrow_address( session: Session, escrow_address: str, *, for_update: Union[bool, ForUpdateParams] = False ) -> Optional[Project]: @@ -73,19 +95,49 @@ def get_project_by_escrow_address( ) +def get_projects_by_escrow_address( + session: Session, + escrow_address: str, + *, + for_update: Union[bool, ForUpdateParams] = False, + limit: Optional[int] = 5, +) -> List[Project]: + projects = _maybe_for_update(session.query(Project), enable=for_update).where( + Project.escrow_address == escrow_address + ) + + if limit is not None: + projects = projects.limit(limit) + + return projects.all() + + +def get_project_cvat_ids_by_escrow_address( + session: Session, + escrow_address: str, +) -> List[int]: + projects = session.query(Project).where(Project.escrow_address == escrow_address) + + return list(itertools.chain.from_iterable(projects.values(Project.cvat_id))) + + def get_projects_by_status( session: Session, status: ProjectStatuses, *, + included_types: Optional[Sequence[TaskTypes]] = None, limit: int = 5, for_update: Union[bool, ForUpdateParams] = False, ) -> List[Project]: - projects = ( - _maybe_for_update(session.query(Project), enable=for_update) - .where(Project.status == status.value) - .limit(limit) - .all() + projects = _maybe_for_update(session.query(Project), enable=for_update).where( + Project.status == status.value ) + + if included_types is not None: + projects = projects.where(Project.job_type.in_([t.value for t in included_types])) + + projects = projects.limit(limit).all() + return projects @@ -98,7 +150,7 @@ def get_available_projects( (Project.status == ProjectStatuses.annotation.value) & Project.jobs.any( (Job.status == JobStatuses.new) - & ~Job.assignments.any(Assignment.status == AssignmentStatus.created.value) + & ~Job.assignments.any(Assignment.status == AssignmentStatuses.created.value) ) ) .distinct() @@ -122,9 +174,9 @@ def get_projects_by_assignee( (Assignment.user_wallet_address == wallet_address) & Assignment.status.in_( [ - AssignmentStatus.created, - AssignmentStatus.completed, - AssignmentStatus.canceled, + AssignmentStatuses.created, + AssignmentStatuses.completed, + AssignmentStatuses.canceled, ] ) ) @@ -156,7 +208,7 @@ def is_project_completed(session: Session, project_id: str) -> bool: # Task -def create_task(session: Session, cvat_id: int, cvat_project_id: int, status: TaskStatus) -> str: +def create_task(session: Session, cvat_id: int, cvat_project_id: int, status: TaskStatuses) -> str: """ Create a task from CVAT. """ @@ -192,7 +244,7 @@ def get_tasks_by_cvat_id( def get_tasks_by_status( - session: Session, status: TaskStatus, *, for_update: Union[bool, ForUpdateParams] = False + session: Session, status: TaskStatuses, *, for_update: Union[bool, ForUpdateParams] = False ) -> List[Task]: return ( _maybe_for_update(session.query(Task), enable=for_update) @@ -201,7 +253,7 @@ def get_tasks_by_status( ) -def update_task_status(session: Session, task_id: int, status: TaskStatus) -> None: +def update_task_status(session: Session, task_id: int, status: TaskStatuses) -> None: upd = update(Task).where(Task.id == task_id).values(status=status.value) session.execute(upd) @@ -405,7 +457,7 @@ def get_unprocessed_expired_assignments( return ( _maybe_for_update(session.query(Assignment), enable=for_update) .where( - (Assignment.status == AssignmentStatus.created.value) + (Assignment.status == AssignmentStatuses.created.value) & (Assignment.completed_at == None) & (Assignment.expires_at <= utcnow()) ) @@ -420,7 +472,7 @@ def get_active_assignments( return ( _maybe_for_update(session.query(Assignment), enable=for_update) .where( - (Assignment.status == AssignmentStatus.created.value) + (Assignment.status == AssignmentStatuses.created.value) & (Assignment.completed_at == None) & (Assignment.expires_at <= utcnow()) ) @@ -433,7 +485,7 @@ def update_assignment( session: Session, id: str, *, - status: AssignmentStatus, + status: AssignmentStatuses, completed_at: Optional[datetime] = None, ): statement = ( @@ -445,11 +497,11 @@ def update_assignment( def cancel_assignment(session: Session, assignment_id: str): - update_assignment(session, assignment_id, status=AssignmentStatus.canceled) + update_assignment(session, assignment_id, status=AssignmentStatuses.canceled) def expire_assignment(session: Session, assignment_id: str): - update_assignment(session, assignment_id, status=AssignmentStatus.expired) + update_assignment(session, assignment_id, status=AssignmentStatuses.expired) def complete_assignment(session: Session, assignment_id: str, completed_at: datetime): @@ -457,7 +509,7 @@ def complete_assignment(session: Session, assignment_id: str, completed_at: date session, assignment_id, completed_at=completed_at, - status=AssignmentStatus.completed, + status=AssignmentStatuses.completed, ) diff --git a/packages/examples/cvat/exchange-oracle/src/services/exchange.py b/packages/examples/cvat/exchange-oracle/src/services/exchange.py index dcb5b7586b..20f17c0423 100644 --- a/packages/examples/cvat/exchange-oracle/src/services/exchange.py +++ b/packages/examples/cvat/exchange-oracle/src/services/exchange.py @@ -5,10 +5,14 @@ import src.models.cvat as models import src.services.cvat as cvat_service from src.chain.escrow import get_escrow_manifest -from src.core.types import AssignmentStatus, JobStatuses, PlatformType, ProjectStatuses +from src.core.types import AssignmentStatuses, JobStatuses, PlatformTypes, ProjectStatuses from src.db import SessionLocal from src.schemas import exchange as service_api -from src.utils.assignments import compose_assignment_url, parse_manifest +from src.utils.assignments import ( + compose_assignment_url, + get_default_assignment_timeout, + parse_manifest, +) from src.utils.requests import get_or_404 from src.utils.time import utcnow @@ -31,6 +35,7 @@ def serialize_task( assignment_url=compose_assignment_url( task_id=assignment.job.cvat_task_id, job_id=assignment.cvat_job_id, + project=project, ), started_at=assignment.created_at, finishes_at=assignment.expires_at, @@ -42,10 +47,11 @@ def serialize_task( title=f"Task {project.escrow_address[:10]}", description=manifest.annotation.description, job_bounty=manifest.job_bounty, - job_time_limit=manifest.annotation.max_time, + job_time_limit=manifest.annotation.max_time + or get_default_assignment_timeout(manifest.annotation.type), job_size=manifest.annotation.job_size + manifest.validation.val_size, job_type=project.job_type, - platform=PlatformType.CVAT, + platform=PlatformTypes.CVAT, assignment=serialized_assignment, status=project.status, ) @@ -79,7 +85,7 @@ def get_tasks_by_assignee( wallet_address=wallet_address, cvat_projects=[p.cvat_id for p in cvat_projects], ) - if assignment.status == AssignmentStatus.created + if assignment.status == AssignmentStatuses.created } for project in cvat_projects: @@ -158,7 +164,11 @@ def create_assignment(project_id: int, wallet_address: str) -> Optional[str]: session, wallet_address=user.wallet_address, cvat_job_id=unassigned_job.cvat_id, - expires_at=now + timedelta(seconds=manifest.annotation.max_time), + expires_at=now + + timedelta( + seconds=manifest.annotation.max_time + or get_default_assignment_timeout(manifest.annotation.type) + ), ) cvat_api.clear_job_annotations(unassigned_job.cvat_id) diff --git a/packages/examples/cvat/exchange-oracle/src/services/webhook.py b/packages/examples/cvat/exchange-oracle/src/services/webhook.py index 28c239aab6..7610cf9dab 100644 --- a/packages/examples/cvat/exchange-oracle/src/services/webhook.py +++ b/packages/examples/cvat/exchange-oracle/src/services/webhook.py @@ -17,14 +17,14 @@ from src.utils.time import utcnow -class OracleWebhookDirectionTag(str, Enum, metaclass=BetterEnumMeta): +class OracleWebhookDirectionTags(str, Enum, metaclass=BetterEnumMeta): incoming = "incoming" outgoing = "outgoing" @define class OracleWebhookQueue: - direction: OracleWebhookDirectionTag + direction: OracleWebhookDirectionTags default_sender: Optional[OracleWebhookTypes] = None def create_webhook( @@ -47,7 +47,7 @@ def create_webhook( ), f"'event' and 'event_type' cannot be used together. Please use only one of the fields" if event_type: - if self.direction == OracleWebhookDirectionTag.incoming: + if self.direction == OracleWebhookDirectionTags.incoming: sender = type else: assert self.default_sender @@ -57,9 +57,9 @@ def create_webhook( event_type = event.get_type() event_data = event.dict() - if self.direction == OracleWebhookDirectionTag.incoming and not signature: + if self.direction == OracleWebhookDirectionTags.incoming and not signature: raise ValueError("Webhook signature must be specified for incoming events") - elif self.direction == OracleWebhookDirectionTag.outgoing and signature: + elif self.direction == OracleWebhookDirectionTags.outgoing and signature: raise ValueError("Webhook signature must not be specified for outgoing events") if signature: @@ -144,8 +144,8 @@ def handle_webhook_fail(self, session: Session, webhook_id: str) -> None: session.execute(upd) -inbox = OracleWebhookQueue(direction=OracleWebhookDirectionTag.incoming) +inbox = OracleWebhookQueue(direction=OracleWebhookDirectionTags.incoming) outbox = OracleWebhookQueue( - direction=OracleWebhookDirectionTag.outgoing, + direction=OracleWebhookDirectionTags.outgoing, default_sender=OracleWebhookTypes.exchange_oracle, ) diff --git a/packages/examples/cvat/exchange-oracle/src/utils/annotations.py b/packages/examples/cvat/exchange-oracle/src/utils/annotations.py new file mode 100644 index 0000000000..2dbf33d88c --- /dev/null +++ b/packages/examples/cvat/exchange-oracle/src/utils/annotations.py @@ -0,0 +1,388 @@ +import os +from copy import deepcopy +from glob import glob +from typing import Dict, Iterable, List, Optional, Sequence, Tuple, TypeVar, Union + +import datumaro as dm +import numpy as np +from datumaro.util import filter_dict, mask_tools +from datumaro.util.annotation_util import find_group_leader, find_instances, max_bbox +from defusedxml import ElementTree as ET + + +def flatten_points(input_points: Sequence[dm.Points]) -> List[dm.Points]: + results = [] + + for pts in input_points: + for point_idx in range(len(pts.points) // 2): + point_x = pts.points[2 * point_idx + 0] + point_y = pts.points[2 * point_idx + 1] + + point_v = pts.visibility[point_idx] + if pts.attributes.get("outside") is True: + point_v = dm.Points.Visibility.absent + elif point_v == dm.Points.Visibility.visible and pts.attributes.get("occluded") is True: + point_v = dm.Points.Visibility.hidden + + results.append( + dm.Points( + [point_x, point_y], + visibility=[point_v], + label=pts.label, + attributes=filter_dict(pts.attributes, exclude_keys=["occluded", "outside"]), + ) + ) + + return results + + +def prepare_cvat_annotations_for_dm(dataset_root: str): + """ + Fixes project/job annotations from CVAT exported in the CVAT format + to make them readable by Datumaro. + + Datumaro doesn't support the 'meta/project' and 'meta/job' keys, + but does support 'meta/task'. The key formats are the same, + only the name is different. The key is needed to parse labels correctly. + """ + + for annotation_filename in glob(os.path.join(dataset_root, "**/*.xml"), recursive=True): + with open(annotation_filename, "rb+") as f: + doc = ET.parse(f) + doc_root = doc.getroot() + + if doc_root.find("meta/project"): + # put labels into each task, if needed + # datumaro doesn't support /meta/project/ tag, but works with tasks, + # which is nested in the meta/project/ + labels_element = doc_root.find("meta/project/labels") + if not labels_element: + continue + + for task_element in doc_root.iterfind("meta/project/tasks/task"): + task_element.append(labels_element) + elif job_meta := doc_root.find("meta/job"): + # just rename the job into task for the same reasons + job_meta.tag = "task" + else: + continue + + f.seek(0) + f.truncate() + doc.write(f, encoding="utf-8") + + +def convert_point_arrays_dataset_to_1_point_skeletons( + dataset: dm.Dataset, labels: Sequence[str] +) -> dm.Dataset: + """ + In the COCO Person Keypoints format, we can only represent points inside skeletons. + The function converts annotations from points to 1-point skeletons in the dataset. + """ + + def _get_skeleton_label(original_label: str) -> str: + return original_label + "_sk" + + new_label_cat = dm.LabelCategories.from_iterable( + [_get_skeleton_label(label) for label in labels] + + [(label, _get_skeleton_label(label)) for label in labels] + ) + new_points_cat = dm.PointsCategories.from_iterable( + (new_label_cat.find(_get_skeleton_label(label))[0], [label]) for label in labels + ) + converted_dataset = dm.Dataset( + categories={ + dm.AnnotationType.label: new_label_cat, + dm.AnnotationType.points: new_points_cat, + }, + media_type=dm.Image, + ) + + label_id_map: Dict[int, int] = { + original_id: new_label_cat.find(label.name, parent=_get_skeleton_label(label.name))[0] + for original_id, label in enumerate(dataset.categories()[dm.AnnotationType.label]) + } # old id -> new id + + for sample in dataset: + points = [a for a in sample.annotations if isinstance(a, dm.Points)] + points = flatten_points(points) + + skeletons = [ + dm.Skeleton( + [p.wrap(label=label_id_map[p.label])], + label=new_label_cat.find(_get_skeleton_label(labels[p.label]))[0], + ) + for p in points + ] + + converted_dataset.put(sample.wrap(annotations=skeletons)) + + return converted_dataset + + +def remove_duplicated_gt_frames(dataset: dm.Dataset, known_frames: Sequence[str]) -> dm.Dataset: + """ + Removes unknown images from the dataset inplace. + + On project dataset export, CVAT will add GT frames, which repeat in multiple tasks, + with a suffix. We don't need these frames in the resulting dataset, + and we can safely remove them. + """ + if not isinstance(known_frames, set): + known_frames = set(known_frames) + + for sample in list(dataset): + item_image_filename = sample.media.path + + if item_image_filename not in known_frames: + dataset.remove(sample.id, sample.subset) + + return dataset + + +T = TypeVar("T", bound=dm.Annotation) + + +def shift_ann(ann: T, offset_x: float, offset_y: float, *, img_w: int, img_h: int) -> T: + "Shift annotation coordinates with clipping to the image size" + + if isinstance(ann, dm.Bbox): + shifted_ann = ann.wrap( + x=offset_x + ann.x, + y=offset_y + ann.y, + ) + elif isinstance(ann, dm.Points): + shifted_ann = ann.wrap( + points=np.clip( + np.reshape(ann.points, (-1, 2)) + (offset_x, offset_y), + 0, + [img_w, img_h], + ).flat + ) + elif isinstance(ann, dm.Skeleton): + shifted_ann = ann.wrap( + elements=[ + point.wrap( + points=np.clip( + np.reshape(point.points, (-1, 2)) + (offset_x, offset_y), + 0, + [img_w, img_h], + ).flat + ) + for point in ann.elements + ] + ) + else: + assert False, f"Unsupported annotation type '{ann.type}'" + + return shifted_ann + + +class ProjectLabels(dm.ItemTransform): + """ + Changes the order of labels in the dataset from the existing + to the desired one, removes unknown labels and adds new labels. + Updates or removes the corresponding annotations.|n + |n + Labels are matched by names (case dependent). Parent labels are only kept + if they are present in the resulting set of labels. If new labels are + added, and the dataset has mask colors defined, new labels will obtain + generated colors.|n + |n + Useful for merging similar datasets, whose labels need to be aligned.|n + |n + Examples:|n + |s|s- Align the source dataset labels to [person, cat, dog]:|n + + |s|s.. code-block:: + + |s|s|s|s%(prog)s -l person -l cat -l dog + """ + + @classmethod + def build_cmdline_parser(cls, **kwargs): + parser = super().build_cmdline_parser(**kwargs) + parser.add_argument( + "-l", + "--label", + action="append", + dest="dst_labels", + help="Label name (repeatable, ordered)", + ) + return parser + + def __init__( + self, + extractor: dm.IExtractor, + dst_labels: Union[Iterable[Union[str, Tuple[str, str]]], dm.LabelCategories], + ): + super().__init__(extractor) + + self._categories = {} + + src_categories = self._extractor.categories() + + src_label_cat: Optional[dm.LabelCategories] = src_categories.get(dm.AnnotationType.label) + src_point_cat: Optional[dm.PointsCategories] = src_categories.get(dm.AnnotationType.points) + + if isinstance(dst_labels, dm.LabelCategories): + dst_label_cat = deepcopy(dst_labels) + else: + dst_labels = list(dst_labels) + + if src_label_cat: + dst_label_cat = dm.LabelCategories(attributes=deepcopy(src_label_cat.attributes)) + + for dst_label in dst_labels: + assert isinstance(dst_label, str) or isinstance(dst_label, tuple) + + dst_parent = "" + if isinstance(dst_label, tuple): + dst_label, dst_parent = dst_label + + src_label = src_label_cat.find(dst_label, dst_parent)[1] + if src_label is not None: + dst_label_cat.add( + dst_label, src_label.parent, deepcopy(src_label.attributes) + ) + else: + dst_label_cat.add(dst_label, dst_parent) + else: + dst_label_cat = dm.LabelCategories.from_iterable(dst_labels) + + for label in dst_label_cat: + if label.parent not in dst_label_cat: + label.parent = "" + + if src_point_cat: + # Copy nested labels + for skeleton_label_id, skeleton_spec in src_point_cat.items.items(): + skeleton_label = src_label_cat[skeleton_label_id] + for child_label_name in skeleton_spec.labels: + if ( + skeleton_label.name in dst_label_cat + and not dst_label_cat.find(child_label_name, parent=skeleton_label.name)[1] + ): + dst_label_cat.add( + child_label_name, + parent=skeleton_label.name, + attributes=skeleton_label.attributes, + ) + + self._categories[dm.AnnotationType.label] = dst_label_cat + + self._make_label_id_map(src_label_cat, dst_label_cat) + + src_mask_cat = src_categories.get(dm.AnnotationType.mask) + if src_mask_cat is not None: + assert src_label_cat is not None + dst_mask_cat = dm.MaskCategories(attributes=deepcopy(src_mask_cat.attributes)) + for old_id, old_color in src_mask_cat.colormap.items(): + new_id = self._map_id(old_id) + if new_id is not None and new_id not in dst_mask_cat: + dst_mask_cat.colormap[new_id] = deepcopy(old_color) + + # Generate new colors for new labels, keep old untouched + existing_colors = set(dst_mask_cat.colormap.values()) + color_bank = iter( + mask_tools.generate_colormap(len(dst_label_cat), include_background=False).values() + ) + for new_id, new_label in enumerate(dst_label_cat): + if new_label.name in src_label_cat: + continue + if new_id in dst_mask_cat: + continue + + color = next(color_bank) + while color in existing_colors: + color = next(color_bank) + + dst_mask_cat.colormap[new_id] = color + + self._categories[dm.AnnotationType.mask] = dst_mask_cat + + if src_point_cat is not None: + assert src_label_cat is not None + dst_point_cat = dm.PointsCategories(attributes=deepcopy(src_point_cat.attributes)) + for old_id, old_cat in src_point_cat.items.items(): + new_id = self._map_id(old_id) + if new_id is not None and new_id not in dst_point_cat: + dst_point_cat.items[new_id] = deepcopy(old_cat) + + self._categories[dm.AnnotationType.points] = dst_point_cat + + def _make_label_id_map(self, src_label_cat, dst_label_cat): + id_mapping = { + src_id: dst_label_cat.find(src_label_cat[src_id].name, src_label_cat[src_id].parent)[0] + for src_id in range(len(src_label_cat or ())) + } + self._map_id = lambda src_id: id_mapping.get(src_id, None) + + def categories(self): + return self._categories + + def transform_item(self, item): + annotations = [] + for ann in item.annotations: + if getattr(ann, "label", None) is not None: + conv_label = self._map_id(ann.label) + if conv_label is None: + continue + + ann = ann.wrap(label=conv_label) + else: + ann = ann.wrap() + + if ann and isinstance(ann, dm.Skeleton): + ann = ann.wrap(elements=[e.wrap(label=self._map_id(e.label)) for e in ann.elements]) + + annotations.append(ann) + + return item.wrap(annotations=annotations) + + +def is_point_in_bbox(px: float, py: float, bbox: dm.Bbox) -> bool: + return (bbox.x <= px <= bbox.x + bbox.w) and (bbox.y <= py <= bbox.y + bbox.h) + + +class InstanceSegmentsToBbox(dm.ItemTransform): + """ + Replaces instance segments (masks, polygons) with a single ("head") bbox. + A group of annotations with the same group id is considered an "instance". + The largest annotation in the group is considered the group "head". + If there is a bbox in a group, it's used as the group "head". + The resulting bbox takes properties from that "head" annotation. + """ + + def transform_item(self, item): + annotations = [] + segments = [] + for ann in item.annotations: + if ann.type in [ + dm.AnnotationType.polygon, + dm.AnnotationType.mask, + dm.AnnotationType.bbox, + ]: + segments.append(ann) + else: + annotations.append(ann) + + if not segments: + return item + + instances = find_instances(segments) + for instance_annotations in instances: + instance_leader = find_group_leader(instance_annotations) + instance_bbox = max_bbox(instance_annotations) + instance_bbox_ann = dm.Bbox( + *instance_bbox, + label=instance_leader.label, + z_order=instance_leader.z_order, + id=instance_leader.id, + attributes=instance_leader.attributes, + group=instance_leader.group, + ) + + annotations.append(instance_bbox_ann) + + return self.wrap_item(item, annotations=annotations) diff --git a/packages/examples/cvat/exchange-oracle/src/utils/assignments.py b/packages/examples/cvat/exchange-oracle/src/utils/assignments.py index 29b3adcc1e..cbfa9b4a71 100644 --- a/packages/examples/cvat/exchange-oracle/src/utils/assignments.py +++ b/packages/examples/cvat/exchange-oracle/src/utils/assignments.py @@ -1,19 +1,31 @@ from urllib.parse import urljoin from src.core.config import Config -from src.core.manifest import TaskManifest -from src.core.types import Networks +from src.core.manifest import TaskManifest, TaskTypes +from src.core.manifest import parse_manifest as _parse_manifest +from src.core.tasks import skeletons_from_boxes +from src.models.cvat import Project def parse_manifest(manifest: dict) -> TaskManifest: - return TaskManifest.parse_obj(manifest) + return _parse_manifest(manifest) -def compose_assignment_url(task_id, job_id) -> str: - return urljoin(Config.cvat_config.cvat_url, f"/tasks/{task_id}/jobs/{job_id}") +def compose_assignment_url(task_id: int, job_id: int, *, project: Project) -> str: + query_params = "" + if project.job_type in [ + TaskTypes.image_skeletons_from_boxes, + TaskTypes.image_boxes_from_points, + ]: + query_params = "?defaultWorkspace=single_shape" + return urljoin(Config.cvat_config.cvat_url, f"/tasks/{task_id}/jobs/{job_id}{query_params}") -def compose_output_annotation_filename( - escrow_address: str, chain_id: Networks, filename: str -) -> str: - return f"{escrow_address}@{chain_id}/{filename}" + +def get_default_assignment_timeout(task_type: TaskTypes) -> int: + timeout_seconds = Config.core_config.default_assignment_time + + if task_type == TaskTypes.image_skeletons_from_boxes: + timeout_seconds *= skeletons_from_boxes.DEFAULT_ASSIGNMENT_SIZE_MULTIPLIER + + return timeout_seconds diff --git a/packages/examples/cvat/exchange-oracle/src/utils/cloud_storage.py b/packages/examples/cvat/exchange-oracle/src/utils/cloud_storage.py deleted file mode 100644 index 3418fde5bc..0000000000 --- a/packages/examples/cvat/exchange-oracle/src/utils/cloud_storage.py +++ /dev/null @@ -1,64 +0,0 @@ -from dataclasses import dataclass -from typing import Optional -from urllib.parse import urlparse - -from src.core.config import Config -from src.core.types import CloudProviders -from src.utils.net import is_ipv4 - - -@dataclass -class ParsedBucketUrl: - provider: str - host_url: str - bucket_name: str - path: str - - -DEFAULT_S3_HOST = "s3.amazonaws.com" - - -def parse_bucket_url(data_url: str) -> ParsedBucketUrl: - parsed_url = urlparse(data_url) - - if parsed_url.netloc.endswith(DEFAULT_S3_HOST): - # AWS S3 bucket - return ParsedBucketUrl( - provider=CloudProviders.aws.value, - host_url=f"https://{DEFAULT_S3_HOST}", - bucket_name=parsed_url.netloc.split(".")[0], - path=parsed_url.path.lstrip("/"), - ) - # elif parsed_url.netloc.endswith("storage.googleapis.com"): - # # Google Cloud Storage (GCS) bucket - # return ParsedBucketUrl( - # provider=CloudProviders.gcs.value, - # bucket_name=parsed_url.netloc.split(".")[0], - # ) - elif Config.features.enable_custom_cloud_host: - if is_ipv4(parsed_url.netloc): - host = parsed_url.netloc - bucket_name, path = parsed_url.path.lstrip("/").split("/", maxsplit=1) - else: - host = parsed_url.netloc.partition(".")[2] - bucket_name = parsed_url.netloc.split(".")[0] - path = parsed_url.path.lstrip("/") - - return ParsedBucketUrl( - provider=CloudProviders.aws.value, - host_url=f"{parsed_url.scheme}://{host}", - bucket_name=bucket_name, - path=path, - ) - else: - raise ValueError(f"{parsed_url.netloc} cloud provider is not supported by CVAT") - - -def compose_bucket_url( - bucket_name: str, provider: CloudProviders, *, bucket_host: Optional[str] = None -) -> str: - match provider: - case CloudProviders.aws.value: - return f"https://{bucket_name}.{bucket_host or 's3.amazonaws.com'}/" - case CloudProviders.gcs.value: - return f"https://{bucket_name}.{bucket_host or 'storage.googleapis.com'}/" diff --git a/packages/examples/cvat/exchange-oracle/src/utils/logging.py b/packages/examples/cvat/exchange-oracle/src/utils/logging.py index d9ce28d024..146f6c219b 100644 --- a/packages/examples/cvat/exchange-oracle/src/utils/logging.py +++ b/packages/examples/cvat/exchange-oracle/src/utils/logging.py @@ -20,3 +20,9 @@ def get_function_logger( function_name = current_function_name(depth=2) return parent_logger.getChild(function_name) + + +class NullLogger(logging.Logger): + def __init__(self, name: str = "", level=0) -> None: + super().__init__(name, level) + self.disabled = True diff --git a/packages/examples/cvat/exchange-oracle/src/utils/webhooks.py b/packages/examples/cvat/exchange-oracle/src/utils/webhooks.py index 31678b9af1..acb534c265 100644 --- a/packages/examples/cvat/exchange-oracle/src/utils/webhooks.py +++ b/packages/examples/cvat/exchange-oracle/src/utils/webhooks.py @@ -20,7 +20,10 @@ def prepare_outgoing_webhook_body( event = parse_event(OracleWebhookTypes.exchange_oracle, event_type, event_data) body["event_type"] = event_type + body["event_data"] = event.dict() + if not body["event_data"]: + body.pop("event_data") return body diff --git a/packages/examples/cvat/exchange-oracle/tests/api/test_cvat_webhook_api.py b/packages/examples/cvat/exchange-oracle/tests/api/test_cvat_webhook_api.py index 59a106419f..2feb6b8300 100644 --- a/packages/examples/cvat/exchange-oracle/tests/api/test_cvat_webhook_api.py +++ b/packages/examples/cvat/exchange-oracle/tests/api/test_cvat_webhook_api.py @@ -3,7 +3,7 @@ from fastapi.testclient import TestClient -from src.core.types import AssignmentStatus, JobStatuses +from src.core.types import AssignmentStatuses, JobStatuses from tests.utils.setup_cvat import ( add_asignment_to_db, @@ -109,7 +109,7 @@ def test_incoming_webhook_200_update_expired_assignmets(client: TestClient) -> N (job, asignees) = get_cvat_job_from_db(1) assert job.status == JobStatuses.new.value - assert asignees[0].status == AssignmentStatus.expired.value + assert asignees[0].status == AssignmentStatuses.expired.value def test_incoming_webhook_200_update(client: TestClient) -> None: @@ -152,7 +152,7 @@ def test_incoming_webhook_200_update(client: TestClient) -> None: (job, asignees) = get_cvat_job_from_db(1) assert job.status == JobStatuses.completed.value - assert asignees[0].status == AssignmentStatus.completed.value + assert asignees[0].status == AssignmentStatuses.completed.value data = { diff --git a/packages/examples/cvat/exchange-oracle/tests/api/test_exchange_api.py b/packages/examples/cvat/exchange-oracle/tests/api/test_exchange_api.py index 4d36dcf465..809e523c9e 100644 --- a/packages/examples/cvat/exchange-oracle/tests/api/test_exchange_api.py +++ b/packages/examples/cvat/exchange-oracle/tests/api/test_exchange_api.py @@ -5,7 +5,7 @@ from fastapi.testclient import TestClient -from src.core.types import AssignmentStatus +from src.core.types import AssignmentStatuses from src.db import SessionLocal from src.models.cvat import Assignment, User @@ -317,7 +317,7 @@ def test_create_assignment_200(client: TestClient) -> None: assert db_assignment.cvat_job_id == cvat_job_1.cvat_id assert db_assignment.user_wallet_address == user_address - assert db_assignment.status == AssignmentStatus.created + assert db_assignment.status == AssignmentStatuses.created assert response.json()["assignment"] session.close() diff --git a/packages/examples/cvat/exchange-oracle/tests/api/test_webhook_api.py b/packages/examples/cvat/exchange-oracle/tests/api/test_webhook_api.py index 56958c1f32..4d62b8fa2c 100644 --- a/packages/examples/cvat/exchange-oracle/tests/api/test_webhook_api.py +++ b/packages/examples/cvat/exchange-oracle/tests/api/test_webhook_api.py @@ -4,7 +4,7 @@ from sqlalchemy.sql import select from web3 import HTTPProvider, Web3 -from src.core.types import JobLauncherEventType, Networks +from src.core.types import JobLauncherEventTypes, Networks from src.db import SessionLocal from src.models.webhook import Webhook @@ -23,7 +23,7 @@ def test_incoming_webhook_200(client: TestClient) -> None: mock_get_web3.return_value = Web3(HTTPProvider(Networks.localhost)) mock_escrow = Mock() mock_escrow.launcher = JOB_LAUNCHER - mock_escrow.recordingOracle = RECORDING_ORACLE_ADDRESS + mock_escrow.recording_oracle = RECORDING_ORACLE_ADDRESS mock_get_escrow.return_value = mock_escrow response = client.post( @@ -45,7 +45,7 @@ def test_incoming_webhook_200(client: TestClient) -> None: ) assert webhook.escrow_address == escrow_address assert webhook.chain_id == 80001 - assert webhook.event_type == JobLauncherEventType.escrow_created.value + assert webhook.event_type == JobLauncherEventTypes.escrow_created.value assert webhook.event_data == {} assert webhook.direction == "incoming" assert webhook.signature == WEBHOOK_MESSAGE_SIGNED @@ -130,7 +130,7 @@ def test_incoming_webhook_401(client: TestClient) -> None: mock_get_web3.return_value = Web3(HTTPProvider(Networks.localhost)) mock_escrow = Mock() mock_escrow.launcher = escrow_address - mock_escrow.recordingOracle = RECORDING_ORACLE_ADDRESS + mock_escrow.recording_oracle = RECORDING_ORACLE_ADDRESS mock_get_escrow.return_value = mock_escrow response = client.post( "/oracle-webhook", diff --git a/packages/examples/cvat/exchange-oracle/tests/integration/chain/test_escrow.py b/packages/examples/cvat/exchange-oracle/tests/integration/chain/test_escrow.py index 31682030ed..c3c60b931d 100644 --- a/packages/examples/cvat/exchange-oracle/tests/integration/chain/test_escrow.py +++ b/packages/examples/cvat/exchange-oracle/tests/integration/chain/test_escrow.py @@ -14,7 +14,7 @@ ) from tests.utils.constants import ( - DEFAULT_URL, + DEFAULT_MANIFEST_URL, ESCROW_ADDRESS, FACTORY_ADDRESS, JOB_LAUNCHER_ADDRESS, @@ -41,7 +41,7 @@ def setUp(self): token=TOKEN_ADDRESS, total_funded_amount=1000, created_at="", - manifest_url=DEFAULT_URL, + manifest_url=DEFAULT_MANIFEST_URL, recording_oracle=RECORDING_ORACLE_ADDRESS, ) @@ -55,9 +55,7 @@ def test_validate_escrow_invalid_address(self): with self.assertRaises(EscrowClientError) as error: validate_escrow(chain_id, "invalid_address") - self.assertEqual( - f"Invalid escrow address: invalid_address", str(error.exception) - ) + self.assertEqual(f"Invalid escrow address: invalid_address", str(error.exception)) def test_validate_escrow_invalid_status(self): with patch("src.chain.escrow.EscrowUtils.get_escrow") as mock_function: @@ -94,9 +92,7 @@ def test_get_escrow_manifest(self): def test_get_escrow_manifest_invalid_address(self): with self.assertRaises(EscrowClientError) as error: get_escrow_manifest(chain_id, "invalid_address") - self.assertEqual( - f"Invalid escrow address: invalid_address", str(error.exception) - ) + self.assertEqual(f"Invalid escrow address: invalid_address", str(error.exception)) def test_get_job_launcher_address(self): with patch("src.chain.escrow.EscrowUtils.get_escrow") as mock_function: @@ -108,9 +104,7 @@ def test_get_job_launcher_address(self): def test_get_job_launcher_address_invalid_address(self): with self.assertRaises(EscrowClientError) as error: get_job_launcher_address(chain_id, "invalid_address") - self.assertEqual( - f"Invalid escrow address: invalid_address", str(error.exception) - ) + self.assertEqual(f"Invalid escrow address: invalid_address", str(error.exception)) def test_get_job_launcher_address_invalid_chain_id(self): with self.assertRaises(ValueError) as error: @@ -122,26 +116,20 @@ def test_get_job_launcher_address_empty_escrow(self): mock_function.return_value = None with self.assertRaises(Exception) as error: get_job_launcher_address(chain_id, escrow_address) - self.assertEqual( - f"Can't find escrow {ESCROW_ADDRESS}", str(error.exception) - ) + self.assertEqual(f"Can't find escrow {ESCROW_ADDRESS}", str(error.exception)) def test_get_recording_oracle_address(self): with patch("src.chain.escrow.EscrowUtils.get_escrow") as mock_function: - self.escrow_data.recordingOracle = RECORDING_ORACLE_ADDRESS + self.escrow_data.recording_oracle = RECORDING_ORACLE_ADDRESS mock_function.return_value = self.escrow_data - recording_oracle_address = get_recording_oracle_address( - chain_id, escrow_address - ) + recording_oracle_address = get_recording_oracle_address(chain_id, escrow_address) self.assertIsInstance(recording_oracle_address, str) self.assertEqual(recording_oracle_address, RECORDING_ORACLE_ADDRESS) def test_get_recording_oracle_address_invalid_address(self): with self.assertRaises(EscrowClientError) as error: get_recording_oracle_address(chain_id, "invalid_address") - self.assertEqual( - f"Invalid escrow address: invalid_address", str(error.exception) - ) + self.assertEqual(f"Invalid escrow address: invalid_address", str(error.exception)) def test_get_recording_oracle_address_invalid_chain_id(self): with self.assertRaises(ValueError) as error: @@ -153,6 +141,4 @@ def test_get_recording_oracle_address_empty_escrow(self): mock_function.return_value = None with self.assertRaises(Exception) as error: get_recording_oracle_address(chain_id, escrow_address) - self.assertEqual( - f"Can't find escrow {ESCROW_ADDRESS}", str(error.exception) - ) + self.assertEqual(f"Can't find escrow {ESCROW_ADDRESS}", str(error.exception)) diff --git a/packages/examples/cvat/exchange-oracle/tests/integration/chain/test_kvstore.py b/packages/examples/cvat/exchange-oracle/tests/integration/chain/test_kvstore.py index 5c3e2be8b8..3d8b5e0976 100644 --- a/packages/examples/cvat/exchange-oracle/tests/integration/chain/test_kvstore.py +++ b/packages/examples/cvat/exchange-oracle/tests/integration/chain/test_kvstore.py @@ -8,7 +8,7 @@ from src.chain.kvstore import get_job_launcher_url, get_recording_oracle_url from tests.utils.constants import ( - DEFAULT_URL, + DEFAULT_MANIFEST_URL, ESCROW_ADDRESS, FACTORY_ADDRESS, JOB_LAUNCHER_ADDRESS, @@ -36,7 +36,7 @@ def setUp(self): token=TOKEN_ADDRESS, total_funded_amount=1000, created_at="", - manifest_url=DEFAULT_URL, + manifest_url=DEFAULT_MANIFEST_URL, recording_oracle=RECORDING_ORACLE_ADDRESS, ) @@ -45,16 +45,14 @@ def test_get_job_launcher_url(self): "src.chain.kvstore.StakingUtils.get_leader" ) as mock_leader: mock_escrow.return_value = self.escrow_data - mock_leader.return_value = MagicMock(webhook_url=DEFAULT_URL) + mock_leader.return_value = MagicMock(webhook_url=DEFAULT_MANIFEST_URL) recording_url = get_job_launcher_url(self.w3.eth.chain_id, escrow_address) - self.assertEqual(recording_url, DEFAULT_URL) + self.assertEqual(recording_url, DEFAULT_MANIFEST_URL) def test_get_job_launcher_url_invalid_escrow(self): with self.assertRaises(EscrowClientError) as error: get_job_launcher_url(self.w3.eth.chain_id, "invalid_address") - self.assertEqual( - f"Invalid escrow address: invalid_address", str(error.exception) - ) + self.assertEqual(f"Invalid escrow address: invalid_address", str(error.exception)) def test_get_job_launcher_url_invalid_recording_address(self): with patch("src.chain.kvstore.get_escrow") as mock_escrow, patch( @@ -69,29 +67,23 @@ def test_get_recording_oracle_url(self): with patch("src.chain.kvstore.get_escrow") as mock_escrow, patch( "src.chain.kvstore.StakingUtils.get_leader" ) as mock_leader: - self.escrow_data.recordingOracle = RECORDING_ORACLE_ADDRESS + self.escrow_data.recording_oracle = RECORDING_ORACLE_ADDRESS mock_escrow.return_value = self.escrow_data - mock_leader.return_value = MagicMock(webhook_url=DEFAULT_URL) - recording_url = get_recording_oracle_url( - self.w3.eth.chain_id, escrow_address - ) - self.assertEqual(recording_url, DEFAULT_URL) + mock_leader.return_value = MagicMock(webhook_url=DEFAULT_MANIFEST_URL) + recording_url = get_recording_oracle_url(self.w3.eth.chain_id, escrow_address) + self.assertEqual(recording_url, DEFAULT_MANIFEST_URL) def test_get_recording_oracle_url_invalid_escrow(self): with self.assertRaises(EscrowClientError) as error: get_recording_oracle_url(self.w3.eth.chain_id, "invalid_address") - self.assertEqual( - f"Invalid escrow address: invalid_address", str(error.exception) - ) + self.assertEqual(f"Invalid escrow address: invalid_address", str(error.exception)) def test_get_recording_oracle_url_invalid_recording_address(self): with patch("src.chain.kvstore.get_escrow") as mock_escrow, patch( "src.chain.kvstore.StakingUtils.get_leader" ) as mock_leader: - self.escrow_data.recordingOracle = RECORDING_ORACLE_ADDRESS + self.escrow_data.recording_oracle = RECORDING_ORACLE_ADDRESS mock_escrow.return_value = self.escrow_data mock_leader.return_value = MagicMock(webhook_url="") - recording_url = get_recording_oracle_url( - self.w3.eth.chain_id, escrow_address - ) + recording_url = get_recording_oracle_url(self.w3.eth.chain_id, escrow_address) self.assertEqual(recording_url, "") diff --git a/packages/examples/cvat/exchange-oracle/tests/integration/cron/state_trackers/test_track_assignments.py b/packages/examples/cvat/exchange-oracle/tests/integration/cron/state_trackers/test_track_assignments.py index 10a5b8b772..446482bfae 100644 --- a/packages/examples/cvat/exchange-oracle/tests/integration/cron/state_trackers/test_track_assignments.py +++ b/packages/examples/cvat/exchange-oracle/tests/integration/cron/state_trackers/test_track_assignments.py @@ -7,12 +7,12 @@ from sqlalchemy.sql import select from src.core.types import ( - AssignmentStatus, + AssignmentStatuses, JobStatuses, Networks, ProjectStatuses, - TaskStatus, - TaskType, + TaskStatuses, + TaskTypes, ) from src.crons.state_trackers import track_assignments from src.db import SessionLocal @@ -67,8 +67,8 @@ def test_track_expired_assignments(self): db_assignments = sorted( self.session.query(Assignment).all(), key=lambda assignment: assignment.user.cvat_id ) - self.assertEqual(db_assignments[0].status, AssignmentStatus.created.value) - self.assertEqual(db_assignments[1].status, AssignmentStatus.created.value) + self.assertEqual(db_assignments[0].status, AssignmentStatuses.created.value) + self.assertEqual(db_assignments[1].status, AssignmentStatuses.created.value) with patch("src.crons.state_trackers.cvat_api.update_job_assignee") as mock_cvat_api: track_assignments() @@ -79,13 +79,13 @@ def test_track_expired_assignments(self): db_assignments = sorted( self.session.query(Assignment).all(), key=lambda assignment: assignment.user.cvat_id ) - self.assertEqual(db_assignments[0].status, AssignmentStatus.created.value) - self.assertEqual(db_assignments[1].status, AssignmentStatus.expired.value) + self.assertEqual(db_assignments[0].status, AssignmentStatuses.created.value) + self.assertEqual(db_assignments[1].status, AssignmentStatuses.expired.value) # TODO: # Fix src/crons/state_trackers.py # Where in `cvat_service.get_active_assignments()` return value will be empty - # because it actually looking for the expired assignments + # because it actually looking for the expired assignments # def test_track_canceled_assignments(self): # (_, _, cvat_job) = create_project_task_and_job( diff --git a/packages/examples/cvat/exchange-oracle/tests/integration/cron/state_trackers/test_retrieve_annotations.py b/packages/examples/cvat/exchange-oracle/tests/integration/cron/state_trackers/test_track_completed_escrows.py similarity index 71% rename from packages/examples/cvat/exchange-oracle/tests/integration/cron/state_trackers/test_retrieve_annotations.py rename to packages/examples/cvat/exchange-oracle/tests/integration/cron/state_trackers/test_track_completed_escrows.py index 600a30a624..0259a18976 100644 --- a/packages/examples/cvat/exchange-oracle/tests/integration/cron/state_trackers/test_retrieve_annotations.py +++ b/packages/examples/cvat/exchange-oracle/tests/integration/cron/state_trackers/test_track_completed_escrows.py @@ -1,21 +1,27 @@ +import io import json +import os import unittest import uuid +import zipfile from datetime import datetime, timedelta -from io import RawIOBase +from glob import glob +from tempfile import TemporaryDirectory from unittest.mock import Mock, patch +import datumaro as dm + from src.core.types import ( - ExchangeOracleEventType, + ExchangeOracleEventTypes, JobStatuses, Networks, ProjectStatuses, - TaskStatus, - TaskType, + TaskStatuses, + TaskTypes, ) -from src.crons.state_trackers import retrieve_annotations +from src.crons.state_trackers import track_completed_escrows from src.db import SessionLocal -from src.models.cvat import Assignment, Job, Project, Task, User +from src.models.cvat import Assignment, Image, Job, Project, Task, User from src.models.webhook import Webhook @@ -35,19 +41,27 @@ def test_retrieve_annotations(self): cvat_id=cvat_project_id, cvat_cloudstorage_id=1, status=ProjectStatuses.completed.value, - job_type=TaskType.image_label_binary.value, + job_type=TaskTypes.image_label_binary.value, escrow_address=escrow_address, chain_id=Networks.localhost.value, bucket_url="https://test.storage.googleapis.com/", ) self.session.add(cvat_project) + project_images = ["sample1.jpg", "sample2.png"] + for image_filename in project_images: + self.session.add( + Image( + id=str(uuid.uuid4()), cvat_project_id=cvat_project_id, filename=image_filename + ) + ) + cvat_task_id = 1 cvat_task = Task( id=str(uuid.uuid4()), cvat_id=cvat_task_id, cvat_project_id=cvat_project_id, - status=TaskStatus.completed.value, + status=TaskStatuses.completed.value, ) self.session.add(cvat_task) @@ -85,17 +99,39 @@ def test_retrieve_annotations(self): with ( open("tests/utils/manifest.json") as data, - patch("src.crons.state_trackers.get_escrow_manifest") as mock_get_manifest, - patch("src.crons.state_trackers.cvat_api"), - patch("src.crons.state_trackers.validate_escrow"), - patch("src.crons.state_trackers.cloud_client.S3Client") as mock_S3Client, + patch("src.handlers.completed_escrows.get_escrow_manifest") as mock_get_manifest, + patch("src.handlers.completed_escrows.validate_escrow"), + patch("src.handlers.completed_escrows.cvat_api") as mock_cvat_api, + patch("src.handlers.completed_escrows.cloud_service") as mock_cloud_service, ): manifest = json.load(data) mock_get_manifest.return_value = manifest - mock_create_file = Mock() - mock_S3Client.return_value.create_file = mock_create_file - retrieve_annotations() + dummy_zip_file = io.BytesIO() + with zipfile.ZipFile(dummy_zip_file, "w") as archive, TemporaryDirectory() as tempdir: + mock_dataset = dm.Dataset( + media_type=dm.Image, + categories={ + dm.AnnotationType.label: dm.LabelCategories.from_iterable(["cat", "dog"]) + }, + ) + for image_filename in project_images: + mock_dataset.put(dm.DatasetItem(id=os.path.splitext(image_filename)[0])) + mock_dataset.export(tempdir, format="coco_instances") + + for filename in list(glob(os.path.join(tempdir, "**/*"), recursive=True)): + archive.write(filename, os.path.relpath(filename, tempdir)) + dummy_zip_file.seek(0) + + mock_cvat_api.get_job_annotations.return_value = dummy_zip_file + mock_cvat_api.get_project_annotations.return_value = dummy_zip_file + + mock_storage_client = Mock() + mock_storage_client.create_file = Mock() + mock_storage_client.list_files = Mock(return_value=[]) + mock_cloud_service.make_client = Mock(return_value=mock_storage_client) + + track_completed_escrows() webhook = ( self.session.query(Webhook) @@ -103,7 +139,7 @@ def test_retrieve_annotations(self): .first() ) self.assertIsNotNone(webhook) - self.assertEqual(webhook.event_type, ExchangeOracleEventType.task_finished) + self.assertEqual(webhook.event_type, ExchangeOracleEventTypes.task_finished) db_project = self.session.query(Project).filter_by(id=project_id).first() self.assertEqual(db_project.status, ProjectStatuses.validation) @@ -117,7 +153,7 @@ def test_retrieve_annotations_unfinished_jobs(self): cvat_id=cvat_project_id, cvat_cloudstorage_id=1, status=ProjectStatuses.completed.value, - job_type=TaskType.image_label_binary.value, + job_type=TaskTypes.image_label_binary.value, escrow_address=escrow_address, chain_id=Networks.localhost.value, bucket_url="https://test.storage.googleapis.com/", @@ -129,7 +165,7 @@ def test_retrieve_annotations_unfinished_jobs(self): id=str(uuid.uuid4()), cvat_id=cvat_task_id, cvat_project_id=cvat_project_id, - status=TaskStatus.completed.value, + status=TaskStatuses.completed.value, ) self.session.add(cvat_task) @@ -143,7 +179,7 @@ def test_retrieve_annotations_unfinished_jobs(self): self.session.add(cvat_job) self.session.commit() - retrieve_annotations() + track_completed_escrows() self.session.commit() db_project = self.session.query(Project).filter_by(id=project_id).first() @@ -160,7 +196,7 @@ def test_retrieve_annotations_error_getting_annotations(self, mock_annotations): cvat_id=cvat_project_id, cvat_cloudstorage_id=1, status=ProjectStatuses.completed.value, - job_type=TaskType.image_label_binary.value, + job_type=TaskTypes.image_label_binary.value, escrow_address=escrow_address, chain_id=Networks.localhost.value, bucket_url="https://test.storage.googleapis.com/", @@ -172,7 +208,7 @@ def test_retrieve_annotations_error_getting_annotations(self, mock_annotations): id=str(uuid.uuid4()), cvat_id=cvat_task_id, cvat_project_id=cvat_project_id, - status=TaskStatus.completed.value, + status=TaskStatuses.completed.value, ) self.session.add(cvat_task) @@ -210,19 +246,25 @@ def test_retrieve_annotations_error_getting_annotations(self, mock_annotations): with ( open("tests/utils/manifest.json") as data, - patch("src.crons.state_trackers.get_escrow_manifest") as mock_get_manifest, - patch("src.crons.state_trackers.cvat_api"), - patch("src.crons.state_trackers.cvat_api.get_job_annotations") as mock_annotations, - patch("src.crons.state_trackers.validate_escrow"), - patch("src.crons.state_trackers.cloud_client.S3Client") as mock_S3Client, + patch("src.handlers.completed_escrows.get_escrow_manifest") as mock_get_manifest, + patch("src.handlers.completed_escrows.validate_escrow"), + patch("src.handlers.completed_escrows.cvat_api"), + patch( + "src.handlers.completed_escrows.cvat_api.get_job_annotations" + ) as mock_annotations, + patch("src.handlers.completed_escrows.cloud_service") as mock_cloud_service, ): manifest = json.load(data) mock_get_manifest.return_value = manifest + mock_create_file = Mock() - mock_S3Client.return_value.create_file = mock_create_file + mock_storage_client = Mock() + mock_storage_client.create_file = mock_create_file + mock_cloud_service.make_client = Mock(return_value=mock_storage_client) + mock_annotations.side_effect = Exception("Connection error") - retrieve_annotations() + track_completed_escrows() webhook = ( self.session.query(Webhook) @@ -244,7 +286,7 @@ def test_retrieve_annotations_error_uploading_files(self): cvat_id=cvat_project_id, cvat_cloudstorage_id=1, status=ProjectStatuses.completed.value, - job_type=TaskType.image_label_binary.value, + job_type=TaskTypes.image_label_binary.value, escrow_address=escrow_address, chain_id=Networks.localhost.value, bucket_url="https://test.storage.googleapis.com/", @@ -256,7 +298,7 @@ def test_retrieve_annotations_error_uploading_files(self): id=str(uuid.uuid4()), cvat_id=cvat_task_id, cvat_project_id=cvat_project_id, - status=TaskStatus.completed.value, + status=TaskStatuses.completed.value, ) self.session.add(cvat_task) @@ -294,14 +336,14 @@ def test_retrieve_annotations_error_uploading_files(self): with ( open("tests/utils/manifest.json") as data, - patch("src.crons.state_trackers.get_escrow_manifest") as mock_get_manifest, - patch("src.crons.state_trackers.cvat_api"), - patch("src.crons.state_trackers.validate_escrow"), + patch("src.handlers.completed_escrows.get_escrow_manifest") as mock_get_manifest, + patch("src.handlers.completed_escrows.validate_escrow"), + patch("src.handlers.completed_escrows.cvat_api"), ): manifest = json.load(data) mock_get_manifest.return_value = manifest - retrieve_annotations() + track_completed_escrows() webhook = ( self.session.query(Webhook) diff --git a/packages/examples/cvat/exchange-oracle/tests/integration/cron/state_trackers/test_track_completed_projects.py b/packages/examples/cvat/exchange-oracle/tests/integration/cron/state_trackers/test_track_completed_projects.py index 698b533a78..489215e0db 100644 --- a/packages/examples/cvat/exchange-oracle/tests/integration/cron/state_trackers/test_track_completed_projects.py +++ b/packages/examples/cvat/exchange-oracle/tests/integration/cron/state_trackers/test_track_completed_projects.py @@ -3,7 +3,7 @@ from sqlalchemy.sql import select -from src.core.types import Networks, ProjectStatuses, TaskStatus, TaskType +from src.core.types import Networks, ProjectStatuses, TaskStatuses, TaskTypes from src.crons.state_trackers import track_completed_projects from src.db import SessionLocal from src.models.cvat import Project, Task @@ -23,7 +23,7 @@ def test_track_completed_projects(self): cvat_id=1, cvat_cloudstorage_id=1, status=ProjectStatuses.annotation.value, - job_type=TaskType.image_label_binary.value, + job_type=TaskTypes.image_label_binary.value, escrow_address="0x86e83d346041E8806e352681f3F14549C0d2BC67", chain_id=Networks.localhost.value, bucket_url="https://test.storage.googleapis.com/", @@ -33,7 +33,7 @@ def test_track_completed_projects(self): id=str(uuid.uuid4()), cvat_id=1, cvat_project_id=1, - status=TaskStatus.completed.value, + status=TaskStatuses.completed.value, ) self.session.add(project) @@ -55,7 +55,7 @@ def test_track_completed_projects_with_unfinished_task(self): cvat_id=1, cvat_cloudstorage_id=1, status=ProjectStatuses.annotation.value, - job_type=TaskType.image_label_binary.value, + job_type=TaskTypes.image_label_binary.value, escrow_address="0x86e83d346041E8806e352681f3F14549C0d2BC67", chain_id=Networks.localhost.value, bucket_url="https://test.storage.googleapis.com/", @@ -65,13 +65,13 @@ def test_track_completed_projects_with_unfinished_task(self): id=str(uuid.uuid4()), cvat_id=1, cvat_project_id=1, - status=TaskStatus.completed.value, + status=TaskStatuses.completed.value, ) task_2 = Task( id=str(uuid.uuid4()), cvat_id=2, cvat_project_id=1, - status=TaskStatus.annotation.value, + status=TaskStatuses.annotation.value, ) self.session.add(project) diff --git a/packages/examples/cvat/exchange-oracle/tests/integration/cron/state_trackers/test_track_completed_tasks.py b/packages/examples/cvat/exchange-oracle/tests/integration/cron/state_trackers/test_track_completed_tasks.py index c665c8a260..4619c5b7cf 100644 --- a/packages/examples/cvat/exchange-oracle/tests/integration/cron/state_trackers/test_track_completed_tasks.py +++ b/packages/examples/cvat/exchange-oracle/tests/integration/cron/state_trackers/test_track_completed_tasks.py @@ -3,7 +3,7 @@ from sqlalchemy.sql import select -from src.core.types import JobStatuses, Networks, ProjectStatuses, TaskStatus, TaskType +from src.core.types import JobStatuses, Networks, ProjectStatuses, TaskStatuses, TaskTypes from src.crons.state_trackers import track_completed_tasks from src.db import SessionLocal from src.models.cvat import Job, Project, Task @@ -22,7 +22,7 @@ def test_track_completed_tasks(self): cvat_id=1, cvat_cloudstorage_id=1, status=ProjectStatuses.annotation.value, - job_type=TaskType.image_label_binary.value, + job_type=TaskTypes.image_label_binary.value, escrow_address="0x86e83d346041E8806e352681f3F14549C0d2BC67", chain_id=Networks.localhost.value, bucket_url="https://test.storage.googleapis.com/", @@ -33,7 +33,7 @@ def test_track_completed_tasks(self): id=task_id, cvat_id=1, cvat_project_id=1, - status=TaskStatus.annotation.value, + status=TaskStatuses.annotation.value, ) job = Job( @@ -55,7 +55,7 @@ def test_track_completed_tasks(self): self.session.execute(select(Task).where(Task.id == task_id)).scalars().first() ) - self.assertEqual(updated_task.status, TaskStatus.completed.value) + self.assertEqual(updated_task.status, TaskStatuses.completed.value) def test_track_completed_tasks_with_unfinished_job(self): project = Project( @@ -63,7 +63,7 @@ def test_track_completed_tasks_with_unfinished_job(self): cvat_id=1, cvat_cloudstorage_id=1, status=ProjectStatuses.annotation.value, - job_type=TaskType.image_label_binary.value, + job_type=TaskTypes.image_label_binary.value, escrow_address="0x86e83d346041E8806e352681f3F14549C0d2BC67", chain_id=Networks.localhost.value, bucket_url="https://test.storage.googleapis.com/", @@ -74,7 +74,7 @@ def test_track_completed_tasks_with_unfinished_job(self): id=task_id, cvat_id=1, cvat_project_id=1, - status=TaskStatus.annotation.value, + status=TaskStatuses.annotation.value, ) job_1 = Job( @@ -104,4 +104,4 @@ def test_track_completed_tasks_with_unfinished_job(self): self.session.execute(select(Task).where(Task.id == task_id)).scalars().first() ) - self.assertEqual(updated_task.status, TaskStatus.annotation.value) + self.assertEqual(updated_task.status, TaskStatuses.annotation.value) diff --git a/packages/examples/cvat/exchange-oracle/tests/integration/cron/state_trackers/test_track_task_creation.py b/packages/examples/cvat/exchange-oracle/tests/integration/cron/state_trackers/test_track_task_creation.py index 3c98f4b1fb..a140355d7d 100644 --- a/packages/examples/cvat/exchange-oracle/tests/integration/cron/state_trackers/test_track_task_creation.py +++ b/packages/examples/cvat/exchange-oracle/tests/integration/cron/state_trackers/test_track_task_creation.py @@ -3,7 +3,7 @@ from unittest.mock import Mock, patch import src.cvat.api_calls as cvat_api -from src.core.types import ExchangeOracleEventType, JobStatuses +from src.core.types import ExchangeOracleEventTypes, JobStatuses from src.crons.state_trackers import track_task_creation from src.db import SessionLocal from src.models.cvat import DataUpload, Job @@ -81,34 +81,31 @@ def test_track_track_completed_task_creation(self): data_upload = self.session.query(DataUpload).filter_by(id=upload_id).first() self.assertIsNone(data_upload) - # TODO: - # Fix "local variable 'project' referenced before assignment" error in src/crons/state_trackers.py and uncomment this test case - - # def test_track_track_completed_task_creation_error(self): - # escrow_address = "0x86e83d346041E8806e352681f3F14549C0d2BC67" - # (_, cvat_task, cvat_job) = create_project_task_and_job(self.session, escrow_address, 1) - # upload = DataUpload( - # id=str(uuid.uuid4()), - # task_id=cvat_task.cvat_id, - # ) - # self.session.add(upload) - # self.session.commit() - - # with ( - # patch( - # "src.crons.state_trackers.cvat_api.get_task_upload_status" - # ) as mock_get_task_upload_status, - # patch( - # "src.crons.state_trackers.cvat_api.fetch_task_jobs", - # side_effect=cvat_api.exceptions.ApiException("Error"), - # ), - # ): - # mock_get_task_upload_status.return_value = (cvat_api.UploadStatus.FINISHED, None) - - # track_task_creation() - - # self.session.commit() - - # webhook = self.session.query(Webhook).filter_by(escrow_address=escrow_address).first() - # self.assertIsNotNone(webhook) - # self.assertEqual(webhook.event_type, ExchangeOracleEventType.task_creation_failed) + def test_track_track_completed_task_creation_error(self): + escrow_address = "0x86e83d346041E8806e352681f3F14549C0d2BC67" + (_, cvat_task, cvat_job) = create_project_task_and_job(self.session, escrow_address, 1) + upload = DataUpload( + id=str(uuid.uuid4()), + task_id=cvat_task.cvat_id, + ) + self.session.add(upload) + self.session.commit() + + with ( + patch( + "src.crons.state_trackers.cvat_api.get_task_upload_status" + ) as mock_get_task_upload_status, + patch( + "src.crons.state_trackers.cvat_api.fetch_task_jobs", + side_effect=cvat_api.exceptions.ApiException("Error"), + ), + ): + mock_get_task_upload_status.return_value = (cvat_api.UploadStatus.FINISHED, None) + + track_task_creation() + + self.session.commit() + + webhook = self.session.query(Webhook).filter_by(escrow_address=escrow_address).first() + self.assertIsNotNone(webhook) + self.assertEqual(webhook.event_type, ExchangeOracleEventTypes.task_creation_failed) diff --git a/packages/examples/cvat/exchange-oracle/tests/integration/cron/test_process_job_launcher_webhooks.py b/packages/examples/cvat/exchange-oracle/tests/integration/cron/test_process_job_launcher_webhooks.py index fb01365b4b..848965aa01 100644 --- a/packages/examples/cvat/exchange-oracle/tests/integration/cron/test_process_job_launcher_webhooks.py +++ b/packages/examples/cvat/exchange-oracle/tests/integration/cron/test_process_job_launcher_webhooks.py @@ -7,13 +7,15 @@ from sqlalchemy.sql import select from src.core.types import ( - ExchangeOracleEventType, - JobLauncherEventType, + ExchangeOracleEventTypes, + JobLauncherEventTypes, + JobStatuses, Networks, OracleWebhookStatuses, OracleWebhookTypes, ProjectStatuses, - TaskType, + TaskStatuses, + TaskTypes, ) from src.crons.process_job_launcher_webhooks import ( process_incoming_job_launcher_webhooks, @@ -22,9 +24,9 @@ from src.db import SessionLocal from src.models.cvat import Job, Project, Task from src.models.webhook import Webhook -from src.services.webhook import OracleWebhookDirectionTag +from src.services.webhook import OracleWebhookDirectionTags -from tests.utils.constants import DEFAULT_URL, JOB_LAUNCHER_ADDRESS +from tests.utils.constants import DEFAULT_MANIFEST_URL, JOB_LAUNCHER_ADDRESS escrow_address = "0x86e83d346041E8806e352681f3F14549C0d2BC67" chain_id = Networks.localhost.value @@ -46,8 +48,8 @@ def test_process_incoming_job_launcher_webhooks_escrow_created_type(self): chain_id=chain_id, type=OracleWebhookTypes.job_launcher.value, status=OracleWebhookStatuses.pending.value, - event_type=JobLauncherEventType.escrow_created.value, - direction=OracleWebhookDirectionTag.incoming, + event_type=JobLauncherEventTypes.escrow_created.value, + direction=OracleWebhookDirectionTags.incoming, ) self.session.add(webhook) @@ -55,10 +57,10 @@ def test_process_incoming_job_launcher_webhooks_escrow_created_type(self): with ( patch("src.chain.escrow.get_escrow") as mock_escrow, open("tests/utils/manifest.json") as data, - patch("src.cvat.tasks.get_escrow_manifest") as mock_get_manifest, - patch("src.cvat.tasks.cvat_api") as mock_cvat_api, - patch("src.cvat.tasks.cloud_service") as mock_cloud_service, - patch("src.cvat.tasks.get_gt_filenames") as mock_gt_filenames, + patch("src.handlers.job_creation.get_escrow_manifest") as mock_get_manifest, + patch("src.handlers.job_creation.cvat_api") as mock_cvat_api, + patch("src.handlers.job_creation.cloud_service.make_client") as mock_make_cloud_client, + patch("src.handlers.job_creation.get_gt_filenames") as mock_gt_filenames, ): manifest = json.load(data) mock_get_manifest.return_value = manifest @@ -75,14 +77,15 @@ def test_process_incoming_job_launcher_webhooks_escrow_created_type(self): "image2.jpg", ] mock_gt_filenames.return_value = filenames - mock_cloud_service.list_files.return_value = filenames + + mock_cloud_client = Mock() + mock_cloud_client.list_files.return_value = filenames + mock_make_cloud_client.return_value = mock_cloud_client process_incoming_job_launcher_webhooks() updated_webhook = ( - self.session.execute(select(Webhook).where(Webhook.id == webhok_id)) - .scalars() - .first() + self.session.execute(select(Webhook).where(Webhook.id == webhok_id)).scalars().first() ) self.assertEqual(updated_webhook.status, OracleWebhookStatuses.completed.value) @@ -106,8 +109,8 @@ def test_process_incoming_job_launcher_webhooks_escrow_created_type_invalid_escr chain_id=chain_id, type=OracleWebhookTypes.job_launcher.value, status=OracleWebhookStatuses.pending.value, - event_type=JobLauncherEventType.escrow_created.value, - direction=OracleWebhookDirectionTag.incoming, + event_type=JobLauncherEventTypes.escrow_created.value, + direction=OracleWebhookDirectionTags.incoming, ) self.session.add(webhook) @@ -137,8 +140,8 @@ def test_process_incoming_job_launcher_webhooks_escrow_created_type_exceed_max_r chain_id=chain_id, type=OracleWebhookTypes.job_launcher.value, status=OracleWebhookStatuses.pending.value, - event_type=JobLauncherEventType.escrow_created.value, - direction=OracleWebhookDirectionTag.incoming, + event_type=JobLauncherEventTypes.escrow_created.value, + direction=OracleWebhookDirectionTags.incoming, attempts=5, ) @@ -169,7 +172,7 @@ def test_process_incoming_job_launcher_webhooks_escrow_created_type_exceed_max_r ) self.assertEqual(new_webhook.status, OracleWebhookStatuses.pending.value) - self.assertEqual(new_webhook.event_type, ExchangeOracleEventType.task_creation_failed) + self.assertEqual(new_webhook.event_type, ExchangeOracleEventTypes.task_creation_failed) self.assertEqual(new_webhook.attempts, 0) def test_process_incoming_job_launcher_webhooks_escrow_created_type_remove_when_error( @@ -183,8 +186,8 @@ def test_process_incoming_job_launcher_webhooks_escrow_created_type_remove_when_ chain_id=chain_id, type=OracleWebhookTypes.job_launcher.value, status=OracleWebhookStatuses.pending.value, - event_type=JobLauncherEventType.escrow_created.value, - direction=OracleWebhookDirectionTag.incoming, + event_type=JobLauncherEventTypes.escrow_created.value, + direction=OracleWebhookDirectionTags.incoming, ) self.session.add(webhook) @@ -192,12 +195,12 @@ def test_process_incoming_job_launcher_webhooks_escrow_created_type_remove_when_ with ( patch("src.chain.escrow.get_escrow") as mock_escrow, open("tests/utils/manifest.json") as data, - patch("src.cvat.tasks.get_escrow_manifest") as mock_get_manifest, - patch("src.cvat.tasks.cvat_api") as mock_cvat_api, - patch("src.cvat.tasks.cloud_service"), - patch("src.cvat.tasks.get_gt_filenames"), + patch("src.handlers.job_creation.get_escrow_manifest") as mock_get_manifest, + patch("src.handlers.job_creation.cvat_api") as mock_cvat_api, + patch("src.handlers.job_creation.cloud_service"), + patch("src.handlers.job_creation.get_gt_filenames"), patch( - "src.cvat.tasks.db_service.add_project_images", + "src.handlers.job_creation.db_service.add_project_images", side_effect=Exception("Error"), ), ): @@ -215,9 +218,7 @@ def test_process_incoming_job_launcher_webhooks_escrow_created_type_remove_when_ process_incoming_job_launcher_webhooks() updated_webhook = ( - self.session.execute(select(Webhook).where(Webhook.id == webhok_id)) - .scalars() - .first() + self.session.execute(select(Webhook).where(Webhook.id == webhok_id)).scalars().first() ) self.assertEqual(updated_webhook.status, OracleWebhookStatuses.pending.value) @@ -237,7 +238,7 @@ def test_process_incoming_job_launcher_webhooks_escrow_canceled_type(self): cvat_id=1, cvat_cloudstorage_id=1, status=ProjectStatuses.completed.value, - job_type=TaskType.image_label_binary.value, + job_type=TaskTypes.image_label_binary.value, escrow_address=escrow_address, chain_id=Networks.localhost.value, bucket_url="https://test.storage.googleapis.com/", @@ -252,8 +253,8 @@ def test_process_incoming_job_launcher_webhooks_escrow_canceled_type(self): chain_id=chain_id, type=OracleWebhookTypes.job_launcher.value, status=OracleWebhookStatuses.pending.value, - event_type=JobLauncherEventType.escrow_canceled.value, - direction=OracleWebhookDirectionTag.incoming, + event_type=JobLauncherEventTypes.escrow_canceled.value, + direction=OracleWebhookDirectionTags.incoming, ) self.session.add(webhook) @@ -267,9 +268,7 @@ def test_process_incoming_job_launcher_webhooks_escrow_canceled_type(self): process_incoming_job_launcher_webhooks() updated_webhook = ( - self.session.execute(select(Webhook).where(Webhook.id == webhok_id)) - .scalars() - .first() + self.session.execute(select(Webhook).where(Webhook.id == webhok_id)).scalars().first() ) self.assertEqual(updated_webhook.status, OracleWebhookStatuses.completed.value) @@ -291,7 +290,7 @@ def test_process_incoming_job_launcher_webhooks_escrow_canceled_type_invalid_sta cvat_id=1, cvat_cloudstorage_id=1, status=ProjectStatuses.annotation.value, - job_type=TaskType.image_label_binary.value, + job_type=TaskTypes.image_label_binary.value, escrow_address=escrow_address, chain_id=Networks.localhost.value, bucket_url="https://test.storage.googleapis.com/", @@ -306,8 +305,8 @@ def test_process_incoming_job_launcher_webhooks_escrow_canceled_type_invalid_sta chain_id=chain_id, type=OracleWebhookTypes.job_launcher.value, status=OracleWebhookStatuses.pending.value, - event_type=JobLauncherEventType.escrow_canceled.value, - direction=OracleWebhookDirectionTag.incoming, + event_type=JobLauncherEventTypes.escrow_canceled.value, + direction=OracleWebhookDirectionTags.incoming, ) self.session.add(webhook) @@ -320,9 +319,7 @@ def test_process_incoming_job_launcher_webhooks_escrow_canceled_type_invalid_sta process_incoming_job_launcher_webhooks() updated_webhook = ( - self.session.execute(select(Webhook).where(Webhook.id == webhok_id)) - .scalars() - .first() + self.session.execute(select(Webhook).where(Webhook.id == webhok_id)).scalars().first() ) self.assertEqual(updated_webhook.status, OracleWebhookStatuses.pending.value) @@ -344,7 +341,7 @@ def test_process_incoming_job_launcher_webhooks_escrow_canceled_type_invalid_bal cvat_id=1, cvat_cloudstorage_id=1, status=ProjectStatuses.annotation.value, - job_type=TaskType.image_label_binary.value, + job_type=TaskTypes.image_label_binary.value, escrow_address=escrow_address, chain_id=Networks.localhost.value, bucket_url="https://test.storage.googleapis.com/", @@ -359,8 +356,8 @@ def test_process_incoming_job_launcher_webhooks_escrow_canceled_type_invalid_bal chain_id=chain_id, type=OracleWebhookTypes.job_launcher.value, status=OracleWebhookStatuses.pending.value, - event_type=JobLauncherEventType.escrow_canceled.value, - direction=OracleWebhookDirectionTag.incoming, + event_type=JobLauncherEventTypes.escrow_canceled.value, + direction=OracleWebhookDirectionTags.incoming, ) self.session.add(webhook) @@ -374,9 +371,7 @@ def test_process_incoming_job_launcher_webhooks_escrow_canceled_type_invalid_bal process_incoming_job_launcher_webhooks() updated_webhook = ( - self.session.execute(select(Webhook).where(Webhook.id == webhok_id)) - .scalars() - .first() + self.session.execute(select(Webhook).where(Webhook.id == webhok_id)).scalars().first() ) self.assertEqual(updated_webhook.status, OracleWebhookStatuses.pending.value) @@ -400,8 +395,8 @@ def test_process_outgoing_job_launcher_webhooks(self): chain_id=chain_id, type=OracleWebhookTypes.job_launcher.value, status=OracleWebhookStatuses.pending.value, - event_type=ExchangeOracleEventType.task_finished.value, - direction=OracleWebhookDirectionTag.outgoing, + event_type=ExchangeOracleEventTypes.task_finished.value, + direction=OracleWebhookDirectionTags.outgoing, ) self.session.add(webhook) @@ -416,7 +411,7 @@ def test_process_outgoing_job_launcher_webhooks(self): mock_escrow_data = Mock() mock_escrow_data.launcher = JOB_LAUNCHER_ADDRESS mock_escrow.return_value = mock_escrow_data - mock_leader.return_value = MagicMock(webhook_url=DEFAULT_URL) + mock_leader.return_value = MagicMock(webhook_url=DEFAULT_MANIFEST_URL) mock_response = MagicMock() mock_response.raise_for_status.return_value = None mock_httpx_post.return_value = mock_response @@ -424,9 +419,7 @@ def test_process_outgoing_job_launcher_webhooks(self): process_outgoing_job_launcher_webhooks() updated_webhook = ( - self.session.execute(select(Webhook).where(Webhook.id == webhok_id)) - .scalars() - .first() + self.session.execute(select(Webhook).where(Webhook.id == webhok_id)).scalars().first() ) self.assertEqual(updated_webhook.status, OracleWebhookStatuses.completed.value) @@ -444,8 +437,8 @@ def test_process_outgoing_job_launcher_webhooks_invalid_type(self): chain_id=chain_id, type=OracleWebhookTypes.job_launcher.value, status=OracleWebhookStatuses.pending.value, - event_type=JobLauncherEventType.escrow_created.value, - direction=OracleWebhookDirectionTag.outgoing, + event_type=JobLauncherEventTypes.escrow_created.value, + direction=OracleWebhookDirectionTags.outgoing, ) self.session.add(webhook) @@ -454,9 +447,7 @@ def test_process_outgoing_job_launcher_webhooks_invalid_type(self): process_outgoing_job_launcher_webhooks() updated_webhook = ( - self.session.execute(select(Webhook).where(Webhook.id == webhok_id)) - .scalars() - .first() + self.session.execute(select(Webhook).where(Webhook.id == webhok_id)).scalars().first() ) self.assertEqual(updated_webhook.status, OracleWebhookStatuses.pending.value) diff --git a/packages/examples/cvat/exchange-oracle/tests/integration/cron/test_process_recording_oracle_webhooks.py b/packages/examples/cvat/exchange-oracle/tests/integration/cron/test_process_recording_oracle_webhooks.py index 7ca1cbe360..f3dbdc35f2 100644 --- a/packages/examples/cvat/exchange-oracle/tests/integration/cron/test_process_recording_oracle_webhooks.py +++ b/packages/examples/cvat/exchange-oracle/tests/integration/cron/test_process_recording_oracle_webhooks.py @@ -6,15 +6,15 @@ from sqlalchemy.sql import select from src.core.types import ( - ExchangeOracleEventType, + ExchangeOracleEventTypes, JobStatuses, Networks, OracleWebhookStatuses, OracleWebhookTypes, ProjectStatuses, - RecordingOracleEventType, - TaskStatus, - TaskType, + RecordingOracleEventTypes, + TaskStatuses, + TaskTypes, ) from src.crons.process_recording_oracle_webhooks import ( process_incoming_recording_oracle_webhooks, @@ -23,9 +23,9 @@ from src.db import SessionLocal from src.models.cvat import Job, Project, Task from src.models.webhook import Webhook -from src.services.webhook import OracleWebhookDirectionTag +from src.services.webhook import OracleWebhookDirectionTags -from tests.utils.constants import DEFAULT_URL, RECORDING_ORACLE_ADDRESS +from tests.utils.constants import DEFAULT_MANIFEST_URL, RECORDING_ORACLE_ADDRESS escrow_address = "0x86e83d346041E8806e352681f3F14549C0d2BC67" chain_id = Networks.localhost.value @@ -45,7 +45,7 @@ def test_process_incoming_recording_oracle_webhooks_task_completed_type(self): cvat_id=1, cvat_cloudstorage_id=1, status=ProjectStatuses.validation.value, - job_type=TaskType.image_label_binary.value, + job_type=TaskTypes.image_label_binary.value, escrow_address=escrow_address, chain_id=Networks.localhost.value, bucket_url="https://test.storage.googleapis.com/", @@ -60,8 +60,8 @@ def test_process_incoming_recording_oracle_webhooks_task_completed_type(self): chain_id=chain_id, type=OracleWebhookTypes.recording_oracle.value, status=OracleWebhookStatuses.pending.value, - event_type=RecordingOracleEventType.task_completed.value, - direction=OracleWebhookDirectionTag.incoming, + event_type=RecordingOracleEventTypes.task_completed.value, + direction=OracleWebhookDirectionTags.incoming, ) self.session.add(webhook) @@ -70,9 +70,7 @@ def test_process_incoming_recording_oracle_webhooks_task_completed_type(self): process_incoming_recording_oracle_webhooks() updated_webhook = ( - self.session.execute(select(Webhook).where(Webhook.id == webhok_id)) - .scalars() - .first() + self.session.execute(select(Webhook).where(Webhook.id == webhok_id)).scalars().first() ) self.assertEqual(updated_webhook.status, OracleWebhookStatuses.completed.value) @@ -90,7 +88,7 @@ def test_process_incoming_recording_oracle_webhooks_task_completed_type_invalid_ cvat_id=1, cvat_cloudstorage_id=1, status=ProjectStatuses.completed.value, - job_type=TaskType.image_label_binary.value, + job_type=TaskTypes.image_label_binary.value, escrow_address=escrow_address, chain_id=Networks.localhost.value, bucket_url="https://test.storage.googleapis.com/", @@ -105,8 +103,8 @@ def test_process_incoming_recording_oracle_webhooks_task_completed_type_invalid_ chain_id=chain_id, type=OracleWebhookTypes.recording_oracle.value, status=OracleWebhookStatuses.pending.value, - event_type=RecordingOracleEventType.task_completed.value, - direction=OracleWebhookDirectionTag.incoming, + event_type=RecordingOracleEventTypes.task_completed.value, + direction=OracleWebhookDirectionTags.incoming, ) self.session.add(webhook) @@ -115,9 +113,7 @@ def test_process_incoming_recording_oracle_webhooks_task_completed_type_invalid_ process_incoming_recording_oracle_webhooks() updated_webhook = ( - self.session.execute(select(Webhook).where(Webhook.id == webhok_id)) - .scalars() - .first() + self.session.execute(select(Webhook).where(Webhook.id == webhok_id)).scalars().first() ) self.assertEqual(updated_webhook.status, OracleWebhookStatuses.completed.value) @@ -136,7 +132,7 @@ def test_process_incoming_recording_oracle_webhooks_task_task_rejected_type(self cvat_id=cvat_id, cvat_cloudstorage_id=1, status=ProjectStatuses.validation.value, - job_type=TaskType.image_label_binary.value, + job_type=TaskTypes.image_label_binary.value, escrow_address=escrow_address, chain_id=Networks.localhost.value, bucket_url="https://test.storage.googleapis.com/", @@ -147,7 +143,7 @@ def test_process_incoming_recording_oracle_webhooks_task_task_rejected_type(self id=task_id, cvat_id=cvat_id, cvat_project_id=cvat_project.cvat_id, - status=TaskStatus.completed.value, + status=TaskStatuses.completed.value, ) self.session.add(cvat_task) @@ -169,9 +165,9 @@ def test_process_incoming_recording_oracle_webhooks_task_task_rejected_type(self chain_id=chain_id, type=OracleWebhookTypes.recording_oracle.value, status=OracleWebhookStatuses.pending.value, - event_type=RecordingOracleEventType.task_rejected.value, + event_type=RecordingOracleEventTypes.task_rejected.value, event_data={"rejected_job_ids": [cvat_id]}, - direction=OracleWebhookDirectionTag.incoming, + direction=OracleWebhookDirectionTags.incoming, ) self.session.add(webhook) @@ -180,9 +176,7 @@ def test_process_incoming_recording_oracle_webhooks_task_task_rejected_type(self process_incoming_recording_oracle_webhooks() updated_webhook = ( - self.session.execute(select(Webhook).where(Webhook.id == webhok_id)) - .scalars() - .first() + self.session.execute(select(Webhook).where(Webhook.id == webhok_id)).scalars().first() ) self.assertEqual(updated_webhook.status, OracleWebhookStatuses.completed.value) @@ -193,7 +187,7 @@ def test_process_incoming_recording_oracle_webhooks_task_task_rejected_type(self db_task = self.session.query(Task).filter_by(id=task_id).first() - self.assertEqual(db_task.status, TaskStatus.annotation.value) + self.assertEqual(db_task.status, TaskStatuses.annotation.value) db_job = self.session.query(Job).filter_by(id=job_id).first() @@ -210,7 +204,7 @@ def test_process_incoming_recording_oracle_webhooks_task_task_rejected_type_inva cvat_id=cvat_id, cvat_cloudstorage_id=1, status=ProjectStatuses.completed.value, - job_type=TaskType.image_label_binary.value, + job_type=TaskTypes.image_label_binary.value, escrow_address=escrow_address, chain_id=Networks.localhost.value, bucket_url="https://test.storage.googleapis.com/", @@ -225,9 +219,9 @@ def test_process_incoming_recording_oracle_webhooks_task_task_rejected_type_inva chain_id=chain_id, type=OracleWebhookTypes.recording_oracle.value, status=OracleWebhookStatuses.pending.value, - event_type=RecordingOracleEventType.task_rejected.value, + event_type=RecordingOracleEventTypes.task_rejected.value, event_data={"rejected_job_ids": [cvat_id]}, - direction=OracleWebhookDirectionTag.incoming, + direction=OracleWebhookDirectionTags.incoming, ) self.session.add(webhook) @@ -236,9 +230,7 @@ def test_process_incoming_recording_oracle_webhooks_task_task_rejected_type_inva process_incoming_recording_oracle_webhooks() updated_webhook = ( - self.session.execute(select(Webhook).where(Webhook.id == webhok_id)) - .scalars() - .first() + self.session.execute(select(Webhook).where(Webhook.id == webhok_id)).scalars().first() ) self.assertEqual(updated_webhook.status, OracleWebhookStatuses.completed.value) @@ -259,8 +251,8 @@ def test_process_outgoing_recording_oracle_webhooks(self): chain_id=chain_id, type=OracleWebhookTypes.recording_oracle.value, status=OracleWebhookStatuses.pending.value, - event_type=ExchangeOracleEventType.task_finished.value, - direction=OracleWebhookDirectionTag.outgoing, + event_type=ExchangeOracleEventTypes.task_finished.value, + direction=OracleWebhookDirectionTags.outgoing, ) self.session.add(webhook) @@ -273,9 +265,9 @@ def test_process_outgoing_recording_oracle_webhooks(self): w3 = Mock() w3.eth.chain_id = ChainId.LOCALHOST.value mock_escrow_data = Mock() - mock_escrow_data.recordingOracle = RECORDING_ORACLE_ADDRESS + mock_escrow_data.recording_oracle = RECORDING_ORACLE_ADDRESS mock_escrow.return_value = mock_escrow_data - mock_leader.return_value = MagicMock(webhook_url=DEFAULT_URL) + mock_leader.return_value = MagicMock(webhook_url=DEFAULT_MANIFEST_URL) mock_response = MagicMock() mock_response.raise_for_status.return_value = None mock_httpx_post.return_value = mock_response @@ -283,9 +275,7 @@ def test_process_outgoing_recording_oracle_webhooks(self): process_outgoing_recording_oracle_webhooks() updated_webhook = ( - self.session.execute(select(Webhook).where(Webhook.id == webhok_id)) - .scalars() - .first() + self.session.execute(select(Webhook).where(Webhook.id == webhok_id)).scalars().first() ) self.assertEqual(updated_webhook.status, OracleWebhookStatuses.completed.value) @@ -303,8 +293,8 @@ def test_process_outgoing_recording_oracle_webhooks_invalid_type(self): chain_id=chain_id, type=OracleWebhookTypes.recording_oracle.value, status=OracleWebhookStatuses.pending.value, - event_type=RecordingOracleEventType.task_completed.value, - direction=OracleWebhookDirectionTag.outgoing, + event_type=RecordingOracleEventTypes.task_completed.value, + direction=OracleWebhookDirectionTags.outgoing, ) self.session.add(webhook) @@ -313,9 +303,7 @@ def test_process_outgoing_recording_oracle_webhooks_invalid_type(self): process_outgoing_recording_oracle_webhooks() updated_webhook = ( - self.session.execute(select(Webhook).where(Webhook.id == webhok_id)) - .scalars() - .first() + self.session.execute(select(Webhook).where(Webhook.id == webhok_id)).scalars().first() ) self.assertEqual(updated_webhook.status, OracleWebhookStatuses.pending.value) diff --git a/packages/examples/cvat/exchange-oracle/tests/integration/services/test_cvat.py b/packages/examples/cvat/exchange-oracle/tests/integration/services/test_cvat.py index f6c1855087..b511849451 100644 --- a/packages/examples/cvat/exchange-oracle/tests/integration/services/test_cvat.py +++ b/packages/examples/cvat/exchange-oracle/tests/integration/services/test_cvat.py @@ -7,12 +7,12 @@ import src.services.cvat as cvat_service from src.core.types import ( - AssignmentStatus, + AssignmentStatuses, JobStatuses, Networks, ProjectStatuses, - TaskStatus, - TaskType, + TaskStatuses, + TaskTypes, ) from src.db import SessionLocal from src.models.cvat import Assignment, DataUpload, Image, Job, Project, Task, User @@ -33,7 +33,7 @@ def tearDown(self): def test_create_project(self): cvat_id = 1 - job_type = TaskType.image_label_binary.value + job_type = TaskTypes.image_label_binary.value escrow_address = "0x86e83d346041E8806e352681f3F14549C0d2BC67" chain_id = Networks.localhost.value cvat_cloudstorage_id = 1 @@ -61,7 +61,7 @@ def test_create_project(self): def test_create_duplicated_project(self): cvat_id = 1 - job_type = TaskType.image_label_binary.value + job_type = TaskTypes.image_label_binary.value escrow_address = "0x86e83d346041E8806e352681f3F14549C0d2BC67" chain_id = Networks.localhost.value cvat_cloudstorage_id = 1 @@ -91,7 +91,7 @@ def test_create_duplicated_project(self): def test_create_project_none_cvat_id(self): cvat_cloudstorage_id = 1 - job_type = TaskType.image_label_binary.value + job_type = TaskTypes.image_label_binary.value escrow_address = "0x86e83d346041E8806e352681f3F14549C0d2BC67" chain_id = Networks.localhost.value bucket_url = "https://test.storage.googleapis.com/" @@ -109,7 +109,7 @@ def test_create_project_none_cvat_id(self): def test_create_project_none_cvat_cloudstorage_id(self): cvat_id = 1 - job_type = TaskType.image_label_binary.value + job_type = TaskTypes.image_label_binary.value escrow_address = "0x86e83d346041E8806e352681f3F14549C0d2BC67" chain_id = Networks.localhost.value bucket_url = "https://test.storage.googleapis.com/" @@ -146,7 +146,7 @@ def test_create_project_none_job_type(self): def test_create_project_none_escrow_address(self): cvat_id = 1 cvat_cloudstorage_id = 1 - job_type = TaskType.image_label_binary.value + job_type = TaskTypes.image_label_binary.value chain_id = Networks.localhost.value bucket_url = "https://test.storage.googleapis.com/" cvat_service.create_project( @@ -163,7 +163,7 @@ def test_create_project_none_escrow_address(self): def test_create_project_none_chain_id(self): cvat_id = 1 - job_type = TaskType.image_label_binary.value + job_type = TaskTypes.image_label_binary.value escrow_address = "0x86e83d346041E8806e352681f3F14549C0d2BC67" cvat_cloudstorage_id = 1 bucket_url = "https://test.storage.googleapis.com/" @@ -183,7 +183,7 @@ def test_create_project_none_chain_id(self): def test_create_project_none_bucket_url(self): cvat_id = 1 cvat_cloudstorage_id = 1 - job_type = TaskType.image_label_binary.value + job_type = TaskTypes.image_label_binary.value escrow_address = "0x86e83d346041E8806e352681f3F14549C0d2BC67" chain_id = Networks.localhost.value cvat_service.create_project( @@ -201,7 +201,7 @@ def test_create_project_none_bucket_url(self): def test_get_project_by_id(self): cvat_id = 1 cvat_cloudstorage_id = 1 - job_type = TaskType.image_label_binary.value + job_type = TaskTypes.image_label_binary.value escrow_address = "0x86e83d346041E8806e352681f3F14549C0d2BC67" chain_id = Networks.localhost.value bucket_url = "https://test.storage.googleapis.com/" @@ -250,7 +250,7 @@ def test_get_project_by_id(self): def test_get_project_by_escrow_address(self): cvat_id = 1 cvat_cloudstorage_id = 1 - job_type = TaskType.image_label_binary.value + job_type = TaskTypes.image_label_binary.value escrow_address = "0x86e83d346041E8806e352681f3F14549C0d2BC67" chain_id = Networks.localhost.value bucket_url = "https://test.storage.googleapis.com/" @@ -282,7 +282,7 @@ def test_get_project_by_escrow_address(self): def test_get_projects_by_status(self): cvat_id = 1 cvat_cloudstorage_id = 1 - job_type = TaskType.image_label_binary.value + job_type = TaskTypes.image_label_binary.value escrow_address = "0x86e83d346041E8806e352681f3F14549C0d2BC67" chain_id = Networks.localhost.value bucket_url = "https://test.storage.googleapis.com/" @@ -298,7 +298,7 @@ def test_get_projects_by_status(self): cvat_id = 2 cvat_cloudstorage_id = 2 - job_type = TaskType.image_label_binary.value + job_type = TaskTypes.image_label_binary.value escrow_address = "0x86e83d346041E8806e352681f3F14549C0d2BC68" bucket_url = "https://test2.storage.googleapis.com/" cvat_service.create_project( @@ -313,7 +313,7 @@ def test_get_projects_by_status(self): cvat_id = 3 cvat_cloudstorage_id = 3 - job_type = TaskType.image_label_binary.value + job_type = TaskTypes.image_label_binary.value escrow_address = "0x86e83d346041E8806e352681f3F14549C0d2BC69" bucket_url = "https://test3.storage.googleapis.com/" cvat_service.create_project( @@ -424,7 +424,7 @@ def test_get_projects_by_assignee(self): def test_update_project_status(self): cvat_id = 1 cvat_cloudstorage_id = 1 - job_type = TaskType.image_label_binary.value + job_type = TaskTypes.image_label_binary.value escrow_address = "0x86e83d346041E8806e352681f3F14549C0d2BC67" chain_id = Networks.localhost.value bucket_url = "https://test.storage.googleapis.com/" @@ -523,7 +523,7 @@ def test_create_task(self): self.session, "0x86e83d346041E8806e352681f3F14549C0d2BC67", cvat_id ) - status = TaskStatus.annotation + status = TaskStatuses.annotation task_id = cvat_service.create_task(self.session, cvat_id, cvat_project.cvat_id, status) @@ -541,7 +541,7 @@ def test_create_task_duplicated_cvat_id(self): self.session, "0x86e83d346041E8806e352681f3F14549C0d2BC67", cvat_id ) - status = TaskStatus.annotation + status = TaskStatuses.annotation cvat_service.create_task(self.session, cvat_id, cvat_project.cvat_id, status) self.session.commit() @@ -551,17 +551,17 @@ def test_create_task_duplicated_cvat_id(self): self.session.commit() def test_create_tas_without_project(self): - cvat_service.create_task(self.session, 123, 123, TaskStatus.annotation) + cvat_service.create_task(self.session, 123, 123, TaskStatuses.annotation) with self.assertRaises(IntegrityError): self.session.commit() def test_create_task_none_cvat_id(self): - cvat_service.create_task(self.session, None, 123, TaskStatus.annotation) + cvat_service.create_task(self.session, None, 123, TaskStatuses.annotation) with self.assertRaises(IntegrityError): self.session.commit() def test_create_task_none_cvat_project_id(self): - cvat_service.create_task(self.session, 123, None, TaskStatus.annotation) + cvat_service.create_task(self.session, 123, None, TaskStatuses.annotation) with self.assertRaises(IntegrityError): self.session.commit() @@ -569,7 +569,7 @@ def test_get_task_by_id(self): cvat_project = create_project(self.session, "0x86e83d346041E8806e352681f3F14549C0d2BC67", 1) task_id = cvat_service.create_task( - self.session, 1, cvat_project.cvat_id, TaskStatus.annotation + self.session, 1, cvat_project.cvat_id, TaskStatuses.annotation ) task = cvat_service.get_task_by_id(self.session, task_id) @@ -577,7 +577,7 @@ def test_get_task_by_id(self): self.assertEqual(task.id, task_id) self.assertEqual(task.cvat_id, cvat_project.cvat_id) self.assertEqual(task.cvat_project_id, cvat_project.cvat_id) - self.assertEqual(task.status, TaskStatus.annotation.value) + self.assertEqual(task.status, TaskStatuses.annotation.value) task = cvat_service.get_task_by_id(self.session, "dummy_id") @@ -586,9 +586,9 @@ def test_get_task_by_id(self): def test_get_tasks_by_cvat_id(self): cvat_project = create_project(self.session, "0x86e83d346041E8806e352681f3F14549C0d2BC67", 1) - cvat_service.create_task(self.session, 1, cvat_project.cvat_id, TaskStatus.annotation) - cvat_service.create_task(self.session, 2, cvat_project.cvat_id, TaskStatus.annotation) - cvat_service.create_task(self.session, 3, cvat_project.cvat_id, TaskStatus.annotation) + cvat_service.create_task(self.session, 1, cvat_project.cvat_id, TaskStatuses.annotation) + cvat_service.create_task(self.session, 2, cvat_project.cvat_id, TaskStatuses.annotation) + cvat_service.create_task(self.session, 3, cvat_project.cvat_id, TaskStatuses.annotation) tasks = cvat_service.get_tasks_by_cvat_id(self.session, [1, 2]) @@ -605,15 +605,15 @@ def test_get_tasks_by_cvat_id(self): def test_get_tasks_by_status(self): cvat_project = create_project(self.session, "0x86e83d346041E8806e352681f3F14549C0d2BC67", 1) - cvat_service.create_task(self.session, 1, cvat_project.cvat_id, TaskStatus.annotation) - cvat_service.create_task(self.session, 2, cvat_project.cvat_id, TaskStatus.annotation) - cvat_service.create_task(self.session, 3, cvat_project.cvat_id, TaskStatus.completed) + cvat_service.create_task(self.session, 1, cvat_project.cvat_id, TaskStatuses.annotation) + cvat_service.create_task(self.session, 2, cvat_project.cvat_id, TaskStatuses.annotation) + cvat_service.create_task(self.session, 3, cvat_project.cvat_id, TaskStatuses.completed) - tasks = cvat_service.get_tasks_by_status(self.session, TaskStatus.annotation) + tasks = cvat_service.get_tasks_by_status(self.session, TaskStatuses.annotation) self.assertEqual(len(tasks), 2) - tasks = cvat_service.get_tasks_by_status(self.session, TaskStatus.completed) + tasks = cvat_service.get_tasks_by_status(self.session, TaskStatuses.completed) self.assertEqual(len(tasks), 1) @@ -621,10 +621,10 @@ def test_update_task_status(self): cvat_project = create_project(self.session, "0x86e83d346041E8806e352681f3F14549C0d2BC67", 1) task_id = cvat_service.create_task( - self.session, 1, cvat_project.cvat_id, TaskStatus.annotation + self.session, 1, cvat_project.cvat_id, TaskStatuses.annotation ) - cvat_service.update_task_status(self.session, task_id, TaskStatus.completed) + cvat_service.update_task_status(self.session, task_id, TaskStatuses.completed) task = cvat_service.get_task_by_id(self.session, task_id) @@ -632,20 +632,20 @@ def test_update_task_status(self): self.assertEqual(task.id, task_id) self.assertEqual(task.cvat_id, cvat_project.cvat_id) self.assertEqual(task.cvat_project_id, cvat_project.cvat_id) - self.assertEqual(task.status, TaskStatus.completed.value) + self.assertEqual(task.status, TaskStatuses.completed.value) def test_get_tasks_by_cvat_project_id(self): cvat_project = create_project(self.session, "0x86e83d346041E8806e352681f3F14549C0d2BC67", 1) - cvat_service.create_task(self.session, 1, cvat_project.cvat_id, TaskStatus.annotation) - cvat_service.create_task(self.session, 2, cvat_project.cvat_id, TaskStatus.annotation) - cvat_service.create_task(self.session, 3, cvat_project.cvat_id, TaskStatus.completed) + cvat_service.create_task(self.session, 1, cvat_project.cvat_id, TaskStatuses.annotation) + cvat_service.create_task(self.session, 2, cvat_project.cvat_id, TaskStatuses.annotation) + cvat_service.create_task(self.session, 3, cvat_project.cvat_id, TaskStatuses.completed) cvat_project_2 = create_project( self.session, "0x86e83d346041E8806e352681f3F14549C0d2BC68", 2 ) - cvat_service.create_task(self.session, 4, cvat_project_2.cvat_id, TaskStatus.annotation) + cvat_service.create_task(self.session, 4, cvat_project_2.cvat_id, TaskStatuses.annotation) tasks = cvat_service.get_tasks_by_cvat_project_id(self.session, cvat_project.cvat_id) @@ -1200,7 +1200,7 @@ def test_create_assignment(self): self.assertIsNotNone(assignment) self.assertEqual(assignment.user_wallet_address, wallet_address) self.assertEqual(assignment.cvat_job_id, cvat_job.cvat_id) - self.assertEqual(assignment.status, AssignmentStatus.created.value) + self.assertEqual(assignment.status, AssignmentStatuses.created.value) def test_create_assignment_invalid_address(self): (_, _, cvat_job) = create_project_task_and_job( @@ -1386,13 +1386,13 @@ def test_update_assignment(self): self.session.commit() cvat_service.update_assignment( - self.session, assignment.id, status=AssignmentStatus.completed + self.session, assignment.id, status=AssignmentStatuses.completed ) db_assignment = self.session.query(Assignment).filter_by(id=assignment.id).first() self.assertEqual(db_assignment.id, assignment.id) - self.assertEqual(db_assignment.status, AssignmentStatus.completed) + self.assertEqual(db_assignment.status, AssignmentStatuses.completed) def test_cancel_assignment(self): (_, _, cvat_job) = create_project_task_and_job( @@ -1420,7 +1420,7 @@ def test_cancel_assignment(self): db_assignment = self.session.query(Assignment).filter_by(id=assignment.id).first() self.assertEqual(db_assignment.id, assignment.id) - self.assertEqual(db_assignment.status, AssignmentStatus.canceled) + self.assertEqual(db_assignment.status, AssignmentStatuses.canceled) def test_expire_assignment(self): (_, _, cvat_job) = create_project_task_and_job( @@ -1448,7 +1448,7 @@ def test_expire_assignment(self): db_assignment = self.session.query(Assignment).filter_by(id=assignment.id).first() self.assertEqual(db_assignment.id, assignment.id) - self.assertEqual(db_assignment.status, AssignmentStatus.expired) + self.assertEqual(db_assignment.status, AssignmentStatuses.expired) def test_complete_assignment(self): (_, _, cvat_job) = create_project_task_and_job( @@ -1476,7 +1476,7 @@ def test_complete_assignment(self): db_assignment = self.session.query(Assignment).filter_by(id=assignment.id).first() self.assertEqual(db_assignment.id, assignment.id) - self.assertEqual(db_assignment.status, AssignmentStatus.completed) + self.assertEqual(db_assignment.status, AssignmentStatuses.completed) self.assertEqual(db_assignment.completed_at, completed_date) def test_test_add_project_images(self): diff --git a/packages/examples/cvat/exchange-oracle/tests/integration/services/test_exchange.py b/packages/examples/cvat/exchange-oracle/tests/integration/services/test_exchange.py index 60859f2927..b339ceef34 100644 --- a/packages/examples/cvat/exchange-oracle/tests/integration/services/test_exchange.py +++ b/packages/examples/cvat/exchange-oracle/tests/integration/services/test_exchange.py @@ -8,7 +8,7 @@ from pydantic import ValidationError import src.services.cvat as cvat_service -from src.core.types import AssignmentStatus, PlatformType, ProjectStatuses +from src.core.types import AssignmentStatuses, PlatformTypes, ProjectStatuses from src.db import SessionLocal from src.models.cvat import Assignment, User from src.schemas import exchange as service_api @@ -53,7 +53,7 @@ def test_serialize_task(self): self.assertIsInstance(data.job_time_limit, int) self.assertIsInstance(data.job_size, int) self.assertEqual(data.job_type, cvat_project.job_type) - self.assertEqual(data.platform, PlatformType.CVAT) + self.assertEqual(data.platform, PlatformTypes.CVAT) self.assertEqual(data.status, cvat_project.status) self.assertIsNone(data.assignment) self.assertIsInstance(data, service_api.TaskResponse) @@ -100,7 +100,7 @@ def test_serialize_task_with_assignment(self): self.assertIsInstance(data.job_time_limit, int) self.assertIsInstance(data.job_size, int) self.assertEqual(data.job_type, cvat_project.job_type) - self.assertEqual(data.platform, PlatformType.CVAT) + self.assertEqual(data.platform, PlatformTypes.CVAT) self.assertEqual(data.status, cvat_project.status) self.assertIsNotNone(data.assignment) self.assertIsInstance(data.assignment.assignment_url, str) @@ -225,7 +225,7 @@ def test_create_assignment(self): self.assertEqual(assignment.cvat_job_id, cvat_job_1.cvat_id) self.assertEqual(assignment.user_wallet_address, user_address) - self.assertEqual(assignment.status, AssignmentStatus.created) + self.assertEqual(assignment.status, AssignmentStatuses.created) def test_create_assignment_invalid_user_address(self): cvat_project_1, _, _ = create_project_task_and_job( diff --git a/packages/examples/cvat/exchange-oracle/tests/integration/services/test_webhook.py b/packages/examples/cvat/exchange-oracle/tests/integration/services/test_webhook.py index 6d35d7fb1f..baa0a7cd6b 100644 --- a/packages/examples/cvat/exchange-oracle/tests/integration/services/test_webhook.py +++ b/packages/examples/cvat/exchange-oracle/tests/integration/services/test_webhook.py @@ -6,12 +6,12 @@ import src.services.webhook as webhook_service from src.core.oracle_events import ExchangeOracleEvent_TaskFinished from src.core.types import ( - ExchangeOracleEventType, - JobLauncherEventType, + ExchangeOracleEventTypes, + JobLauncherEventTypes, Networks, OracleWebhookStatuses, OracleWebhookTypes, - RecordingOracleEventType, + RecordingOracleEventTypes, ) from src.db import SessionLocal from src.models.webhook import Webhook @@ -35,7 +35,7 @@ def test_create_incoming_webhook(self): chain_id=chain_id, signature=signature, type=OracleWebhookTypes.job_launcher, - event_type=JobLauncherEventType.escrow_created.value, + event_type=JobLauncherEventTypes.escrow_created.value, ) webhook = self.session.query(Webhook).filter_by(id=webhook_id).first() @@ -45,7 +45,7 @@ def test_create_incoming_webhook(self): self.assertEqual(webhook.signature, signature) self.assertEqual(webhook.attempts, 0) self.assertEqual(webhook.type, OracleWebhookTypes.job_launcher.value) - self.assertEqual(webhook.event_type, JobLauncherEventType.escrow_created.value) + self.assertEqual(webhook.event_type, JobLauncherEventTypes.escrow_created.value) self.assertEqual(webhook.event_data, None) self.assertEqual(webhook.status, OracleWebhookStatuses.pending.value) @@ -58,7 +58,7 @@ def test_create_incoming_webhook_none_escrow_address(self): chain_id=chain_id, signature=signature, type=OracleWebhookTypes.job_launcher, - event_type=JobLauncherEventType.escrow_created.value, + event_type=JobLauncherEventTypes.escrow_created.value, ) with self.assertRaises(IntegrityError): self.session.commit() @@ -72,7 +72,7 @@ def test_create_incoming_webhook_none_chain_id(self): chain_id=None, signature=signature, type=OracleWebhookTypes.job_launcher, - event_type=JobLauncherEventType.escrow_created.value, + event_type=JobLauncherEventTypes.escrow_created.value, ) with self.assertRaises(IntegrityError): self.session.commit() @@ -103,7 +103,7 @@ def test_create_incoming_webhook_none_signature(self): escrow_address=escrow_address, chain_id=chain_id, type=OracleWebhookTypes.job_launcher, - event_type=JobLauncherEventType.escrow_created.value, + event_type=JobLauncherEventTypes.escrow_created.value, ) self.assertEqual( str(error.exception), "Webhook signature must be specified for incoming events" @@ -127,7 +127,7 @@ def test_create_outgoing_webhook(self): self.assertEqual(webhook.chain_id, chain_id) self.assertEqual(webhook.attempts, 0) self.assertEqual(webhook.type, OracleWebhookTypes.exchange_oracle.value) - self.assertEqual(webhook.event_type, ExchangeOracleEventType.task_finished.value) + self.assertEqual(webhook.event_type, ExchangeOracleEventTypes.task_finished.value) self.assertEqual(webhook.event_data, {}) self.assertEqual(webhook.status, OracleWebhookStatuses.pending.value) @@ -198,8 +198,8 @@ def test_get_pending_webhooks(self): chain_id=chain_id, type=OracleWebhookTypes.job_launcher.value, status=OracleWebhookStatuses.pending.value, - event_type=JobLauncherEventType.escrow_created.value, - direction=webhook_service.OracleWebhookDirectionTag.incoming, + event_type=JobLauncherEventTypes.escrow_created.value, + direction=webhook_service.OracleWebhookDirectionTags.incoming, ) webhook2_id = str(uuid.uuid4()) webhook2 = Webhook( @@ -209,8 +209,8 @@ def test_get_pending_webhooks(self): chain_id=chain_id, type=OracleWebhookTypes.job_launcher.value, status=OracleWebhookStatuses.pending.value, - event_type=JobLauncherEventType.escrow_created.value, - direction=webhook_service.OracleWebhookDirectionTag.incoming, + event_type=JobLauncherEventTypes.escrow_created.value, + direction=webhook_service.OracleWebhookDirectionTags.incoming, ) webhook3_id = str(uuid.uuid4()) webhook3 = Webhook( @@ -220,8 +220,8 @@ def test_get_pending_webhooks(self): chain_id=chain_id, type=OracleWebhookTypes.job_launcher.value, status=OracleWebhookStatuses.completed.value, - event_type=JobLauncherEventType.escrow_created.value, - direction=webhook_service.OracleWebhookDirectionTag.incoming, + event_type=JobLauncherEventTypes.escrow_created.value, + direction=webhook_service.OracleWebhookDirectionTags.incoming, ) webhook4_id = str(uuid.uuid4()) @@ -232,8 +232,8 @@ def test_get_pending_webhooks(self): chain_id=chain_id, type=OracleWebhookTypes.recording_oracle.value, status=OracleWebhookStatuses.pending.value, - event_type=RecordingOracleEventType.task_completed.value, - direction=webhook_service.OracleWebhookDirectionTag.incoming, + event_type=RecordingOracleEventTypes.task_completed.value, + direction=webhook_service.OracleWebhookDirectionTags.incoming, ) webhook5_id = str(uuid.uuid4()) webhook5 = Webhook( @@ -242,8 +242,8 @@ def test_get_pending_webhooks(self): chain_id=chain_id, type=OracleWebhookTypes.job_launcher.value, status=OracleWebhookStatuses.pending.value, - event_type=JobLauncherEventType.escrow_created.value, - direction=webhook_service.OracleWebhookDirectionTag.outgoing, + event_type=JobLauncherEventTypes.escrow_created.value, + direction=webhook_service.OracleWebhookDirectionTags.outgoing, ) self.session.add(webhook1) @@ -283,7 +283,7 @@ def test_update_webhook_status(self): chain_id=chain_id, signature=signature, type=OracleWebhookTypes.job_launcher, - event_type=JobLauncherEventType.escrow_created.value, + event_type=JobLauncherEventTypes.escrow_created.value, ) webhook_service.inbox.update_webhook_status( @@ -310,7 +310,7 @@ def test_handle_webhook_success(self): chain_id=chain_id, signature=signature, type=OracleWebhookTypes.job_launcher, - event_type=JobLauncherEventType.escrow_created.value, + event_type=JobLauncherEventTypes.escrow_created.value, ) webhook_service.inbox.handle_webhook_success(self.session, webhook_id) @@ -335,7 +335,7 @@ def test_handle_webhook_fail(self): chain_id=chain_id, signature=signature, type=OracleWebhookTypes.job_launcher, - event_type=JobLauncherEventType.escrow_created.value, + event_type=JobLauncherEventTypes.escrow_created.value, ) webhook_service.inbox.handle_webhook_fail(self.session, webhook_id) diff --git a/packages/examples/cvat/exchange-oracle/tests/utils/constants.py b/packages/examples/cvat/exchange-oracle/tests/utils/constants.py index 33e16d146e..2c3655e57f 100644 --- a/packages/examples/cvat/exchange-oracle/tests/utils/constants.py +++ b/packages/examples/cvat/exchange-oracle/tests/utils/constants.py @@ -5,6 +5,7 @@ RECORDING_ORACLE_PRIV = "59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d" RECORDING_ORACLE_FEE = 10 +REPUTATION_ORACLE_WEBHOOK_URL = "http://localhost:5001/webhook/cvat" REPUTATION_ORACLE_ADDRESS = "0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC" REPUTATION_ORACLE_FEE = 10 @@ -17,7 +18,7 @@ TOKEN_ADDRESS = "0x976EA74026E726554dB657fA54763abd0C3a0aa9" FACTORY_ADDRESS = "0x14dC79964da2C08b23698B3D3cc7Ca32193d9955" -DEFAULT_URL = "http://host.docker.internal:9000/manifests/manifest.json" +DEFAULT_MANIFEST_URL = "http://host.docker.internal:9000/manifests/manifest.json" DEFAULT_HASH = "test" SIGNATURE = "0xa0c5626301e3c198cb91356e492890c0c28db8c37044846134939246911a693c4d7116d04aa4bc40a41077493868b8dd533d30980f6addb28d1b3610a84cb4091c" diff --git a/packages/examples/cvat/exchange-oracle/tests/utils/db_helper.py b/packages/examples/cvat/exchange-oracle/tests/utils/db_helper.py index e315357dc5..9133b92031 100644 --- a/packages/examples/cvat/exchange-oracle/tests/utils/db_helper.py +++ b/packages/examples/cvat/exchange-oracle/tests/utils/db_helper.py @@ -1,6 +1,6 @@ import uuid -from src.core.types import JobStatuses, Networks, ProjectStatuses, TaskStatus, TaskType +from src.core.types import JobStatuses, Networks, ProjectStatuses, TaskStatuses, TaskTypes from src.db import SessionLocal from src.models.cvat import Job, Project, Task @@ -11,7 +11,7 @@ def create_project(session: SessionLocal, escrow_address: str, cvat_id: int) -> cvat_id=cvat_id, cvat_cloudstorage_id=1, status=ProjectStatuses.annotation.value, - job_type=TaskType.image_label_binary.value, + job_type=TaskTypes.image_label_binary.value, escrow_address=escrow_address, chain_id=Networks.localhost.value, bucket_url="https://test.storage.googleapis.com/", @@ -27,7 +27,7 @@ def create_project_and_task(session: SessionLocal, escrow_address: str, cvat_id: id=str(uuid.uuid4()), cvat_id=cvat_id, cvat_project_id=cvat_id, - status=TaskStatus.annotation.value, + status=TaskStatuses.annotation.value, ) session.add(cvat_task) diff --git a/packages/examples/cvat/recording-oracle/alembic/versions/a0c5c3a4c13f_add_gt_stats.py b/packages/examples/cvat/recording-oracle/alembic/versions/a0c5c3a4c13f_add_gt_stats.py new file mode 100644 index 0000000000..eb6471554d --- /dev/null +++ b/packages/examples/cvat/recording-oracle/alembic/versions/a0c5c3a4c13f_add_gt_stats.py @@ -0,0 +1,36 @@ +"""add_gt_stats + +Revision ID: a0c5c3a4c13f +Revises: ca93dce1a618 +Create Date: 2024-03-08 11:34:02.458845 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = 'a0c5c3a4c13f' +down_revision = 'ca93dce1a618' +branch_labels = None +depends_on = None + + +def upgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + op.create_table('gt_stats', + sa.Column('task_id', sa.String(), nullable=False), + sa.Column('gt_key', sa.String(), nullable=False), + sa.Column('failed_attempts', sa.Integer(), nullable=False), + sa.ForeignKeyConstraint(['task_id'], ['tasks.id'], ondelete='CASCADE'), + sa.PrimaryKeyConstraint('task_id', 'gt_key') + ) + op.create_index(op.f('ix_gt_stats_gt_key'), 'gt_stats', ['gt_key'], unique=False) + # ### end Alembic commands ### + + +def downgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + op.drop_index(op.f('ix_gt_stats_gt_key'), table_name='gt_stats') + op.drop_table('gt_stats') + # ### end Alembic commands ### diff --git a/packages/examples/cvat/recording-oracle/docker-compose.test.yml b/packages/examples/cvat/recording-oracle/docker-compose.test.yml index c35631b195..0d009890e6 100644 --- a/packages/examples/cvat/recording-oracle/docker-compose.test.yml +++ b/packages/examples/cvat/recording-oracle/docker-compose.test.yml @@ -80,11 +80,18 @@ services: PG_PASSWORD: 'test' PG_DB: 'recording_oracle_test' WEB3_HTTP_PROVIDER_URI: 'http://blockchain-node:8545' - ENDPOINT_URL: 'host.docker.internal:9000' - ACCESS_KEY: 'dev' - SECRET_KEY: 'devdevdev' - RESULTS_BUCKET_NAME: 'results' - USE_SSL: False + STORAGE_ENDPOINT_URL: 'host.docker.internal:9000' + STORAGE_ACCESS_KEY: 'dev' + STORAGE_SECRET_KEY: 'devdevdev' + STORAGE_RESULTS_BUCKET_NAME: 'results' + STORAGE_PROVIDER: 'aws' + STORAGE_USE_SSL: False + EXCHANGE_ORACLE_STORAGE_ENDPOINT_URL: 'host.docker.internal:9000' + EXCHANGE_ORACLE_STORAGE_ACCESS_KEY: 'dev' + EXCHANGE_ORACLE_STORAGE_SECRET_KEY: 'devdevdev' + EXCHANGE_ORACLE_STORAGE_RESULTS_BUCKET_NAME: 'results' + EXCHANGE_ORACLE_STORAGE_USE_SSL: False + EXCHANGE_ORACLE_STORAGE_PROVIDER: 'aws' depends_on: postgres: condition: service_started diff --git a/packages/examples/cvat/recording-oracle/poetry.lock b/packages/examples/cvat/recording-oracle/poetry.lock index d5086474ac..2ee416d598 100644 --- a/packages/examples/cvat/recording-oracle/poetry.lock +++ b/packages/examples/cvat/recording-oracle/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.6.1 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.7.1 and should not be changed by hand. [[package]] name = "aiohttp" @@ -481,6 +481,17 @@ urllib3 = {version = ">=1.25.4,<2.1", markers = "python_version >= \"3.10\""} [package.extras] crt = ["awscrt (==0.19.19)"] +[[package]] +name = "cachetools" +version = "5.3.2" +description = "Extensible memoizing collections and decorators" +optional = false +python-versions = ">=3.7" +files = [ + {file = "cachetools-5.3.2-py3-none-any.whl", hash = "sha256:861f35a13a451f94e301ce2bec7cac63e881232ccce7ed67fab9b5df4d3beaa1"}, + {file = "cachetools-5.3.2.tar.gz", hash = "sha256:086ee420196f7b2ab9ca2db2520aca326318b68fe5ba8bc4d49cca91add450f2"}, +] + [[package]] name = "certifi" version = "2023.11.17" @@ -1388,6 +1399,206 @@ files = [ {file = "frozenlist-1.4.1.tar.gz", hash = "sha256:c037a86e8513059a2613aaba4d817bb90b9d9b6b69aace3ce9c877e8c8ed402b"}, ] +[[package]] +name = "google-api-core" +version = "2.17.1" +description = "Google API client core library" +optional = false +python-versions = ">=3.7" +files = [ + {file = "google-api-core-2.17.1.tar.gz", hash = "sha256:9df18a1f87ee0df0bc4eea2770ebc4228392d8cc4066655b320e2cfccb15db95"}, + {file = "google_api_core-2.17.1-py3-none-any.whl", hash = "sha256:610c5b90092c360736baccf17bd3efbcb30dd380e7a6dc28a71059edb8bd0d8e"}, +] + +[package.dependencies] +google-auth = ">=2.14.1,<3.0.dev0" +googleapis-common-protos = ">=1.56.2,<2.0.dev0" +protobuf = ">=3.19.5,<3.20.0 || >3.20.0,<3.20.1 || >3.20.1,<4.21.0 || >4.21.0,<4.21.1 || >4.21.1,<4.21.2 || >4.21.2,<4.21.3 || >4.21.3,<4.21.4 || >4.21.4,<4.21.5 || >4.21.5,<5.0.0.dev0" +requests = ">=2.18.0,<3.0.0.dev0" + +[package.extras] +grpc = ["grpcio (>=1.33.2,<2.0dev)", "grpcio (>=1.49.1,<2.0dev)", "grpcio-status (>=1.33.2,<2.0.dev0)", "grpcio-status (>=1.49.1,<2.0.dev0)"] +grpcgcp = ["grpcio-gcp (>=0.2.2,<1.0.dev0)"] +grpcio-gcp = ["grpcio-gcp (>=0.2.2,<1.0.dev0)"] + +[[package]] +name = "google-auth" +version = "2.28.0" +description = "Google Authentication Library" +optional = false +python-versions = ">=3.7" +files = [ + {file = "google-auth-2.28.0.tar.gz", hash = "sha256:3cfc1b6e4e64797584fb53fc9bd0b7afa9b7c0dba2004fa7dcc9349e58cc3195"}, + {file = "google_auth-2.28.0-py2.py3-none-any.whl", hash = "sha256:7634d29dcd1e101f5226a23cbc4a0c6cda6394253bf80e281d9c5c6797869c53"}, +] + +[package.dependencies] +cachetools = ">=2.0.0,<6.0" +pyasn1-modules = ">=0.2.1" +rsa = ">=3.1.4,<5" + +[package.extras] +aiohttp = ["aiohttp (>=3.6.2,<4.0.0.dev0)", "requests (>=2.20.0,<3.0.0.dev0)"] +enterprise-cert = ["cryptography (==36.0.2)", "pyopenssl (==22.0.0)"] +pyopenssl = ["cryptography (>=38.0.3)", "pyopenssl (>=20.0.0)"] +reauth = ["pyu2f (>=0.1.5)"] +requests = ["requests (>=2.20.0,<3.0.0.dev0)"] + +[[package]] +name = "google-cloud-core" +version = "2.4.1" +description = "Google Cloud API client core library" +optional = false +python-versions = ">=3.7" +files = [ + {file = "google-cloud-core-2.4.1.tar.gz", hash = "sha256:9b7749272a812bde58fff28868d0c5e2f585b82f37e09a1f6ed2d4d10f134073"}, + {file = "google_cloud_core-2.4.1-py2.py3-none-any.whl", hash = "sha256:a9e6a4422b9ac5c29f79a0ede9485473338e2ce78d91f2370c01e730eab22e61"}, +] + +[package.dependencies] +google-api-core = ">=1.31.6,<2.0.dev0 || >2.3.0,<3.0.0dev" +google-auth = ">=1.25.0,<3.0dev" + +[package.extras] +grpc = ["grpcio (>=1.38.0,<2.0dev)", "grpcio-status (>=1.38.0,<2.0.dev0)"] + +[[package]] +name = "google-cloud-storage" +version = "2.14.0" +description = "Google Cloud Storage API client library" +optional = false +python-versions = ">=3.7" +files = [ + {file = "google-cloud-storage-2.14.0.tar.gz", hash = "sha256:2d23fcf59b55e7b45336729c148bb1c464468c69d5efbaee30f7201dd90eb97e"}, + {file = "google_cloud_storage-2.14.0-py2.py3-none-any.whl", hash = "sha256:8641243bbf2a2042c16a6399551fbb13f062cbc9a2de38d6c0bb5426962e9dbd"}, +] + +[package.dependencies] +google-api-core = ">=1.31.5,<2.0.dev0 || >2.3.0,<3.0.0dev" +google-auth = ">=2.23.3,<3.0dev" +google-cloud-core = ">=2.3.0,<3.0dev" +google-crc32c = ">=1.0,<2.0dev" +google-resumable-media = ">=2.6.0" +requests = ">=2.18.0,<3.0.0dev" + +[package.extras] +protobuf = ["protobuf (<5.0.0dev)"] + +[[package]] +name = "google-crc32c" +version = "1.5.0" +description = "A python wrapper of the C library 'Google CRC32C'" +optional = false +python-versions = ">=3.7" +files = [ + {file = "google-crc32c-1.5.0.tar.gz", hash = "sha256:89284716bc6a5a415d4eaa11b1726d2d60a0cd12aadf5439828353662ede9dd7"}, + {file = "google_crc32c-1.5.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:596d1f98fc70232fcb6590c439f43b350cb762fb5d61ce7b0e9db4539654cc13"}, + {file = "google_crc32c-1.5.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:be82c3c8cfb15b30f36768797a640e800513793d6ae1724aaaafe5bf86f8f346"}, + {file = "google_crc32c-1.5.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:461665ff58895f508e2866824a47bdee72497b091c730071f2b7575d5762ab65"}, + {file = "google_crc32c-1.5.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e2096eddb4e7c7bdae4bd69ad364e55e07b8316653234a56552d9c988bd2d61b"}, + {file = "google_crc32c-1.5.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:116a7c3c616dd14a3de8c64a965828b197e5f2d121fedd2f8c5585c547e87b02"}, + {file = "google_crc32c-1.5.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:5829b792bf5822fd0a6f6eb34c5f81dd074f01d570ed7f36aa101d6fc7a0a6e4"}, + {file = "google_crc32c-1.5.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:64e52e2b3970bd891309c113b54cf0e4384762c934d5ae56e283f9a0afcd953e"}, + {file = "google_crc32c-1.5.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:02ebb8bf46c13e36998aeaad1de9b48f4caf545e91d14041270d9dca767b780c"}, + {file = "google_crc32c-1.5.0-cp310-cp310-win32.whl", hash = "sha256:2e920d506ec85eb4ba50cd4228c2bec05642894d4c73c59b3a2fe20346bd00ee"}, + {file = "google_crc32c-1.5.0-cp310-cp310-win_amd64.whl", hash = "sha256:07eb3c611ce363c51a933bf6bd7f8e3878a51d124acfc89452a75120bc436289"}, + {file = "google_crc32c-1.5.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:cae0274952c079886567f3f4f685bcaf5708f0a23a5f5216fdab71f81a6c0273"}, + {file = "google_crc32c-1.5.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1034d91442ead5a95b5aaef90dbfaca8633b0247d1e41621d1e9f9db88c36298"}, + {file = "google_crc32c-1.5.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7c42c70cd1d362284289c6273adda4c6af8039a8ae12dc451dcd61cdabb8ab57"}, + {file = "google_crc32c-1.5.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8485b340a6a9e76c62a7dce3c98e5f102c9219f4cfbf896a00cf48caf078d438"}, + {file = "google_crc32c-1.5.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:77e2fd3057c9d78e225fa0a2160f96b64a824de17840351b26825b0848022906"}, + {file = "google_crc32c-1.5.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:f583edb943cf2e09c60441b910d6a20b4d9d626c75a36c8fcac01a6c96c01183"}, + {file = "google_crc32c-1.5.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:a1fd716e7a01f8e717490fbe2e431d2905ab8aa598b9b12f8d10abebb36b04dd"}, + {file = "google_crc32c-1.5.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:72218785ce41b9cfd2fc1d6a017dc1ff7acfc4c17d01053265c41a2c0cc39b8c"}, + {file = "google_crc32c-1.5.0-cp311-cp311-win32.whl", hash = "sha256:66741ef4ee08ea0b2cc3c86916ab66b6aef03768525627fd6a1b34968b4e3709"}, + {file = "google_crc32c-1.5.0-cp311-cp311-win_amd64.whl", hash = "sha256:ba1eb1843304b1e5537e1fca632fa894d6f6deca8d6389636ee5b4797affb968"}, + {file = "google_crc32c-1.5.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:98cb4d057f285bd80d8778ebc4fde6b4d509ac3f331758fb1528b733215443ae"}, + {file = "google_crc32c-1.5.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fd8536e902db7e365f49e7d9029283403974ccf29b13fc7028b97e2295b33556"}, + {file = "google_crc32c-1.5.0-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:19e0a019d2c4dcc5e598cd4a4bc7b008546b0358bd322537c74ad47a5386884f"}, + {file = "google_crc32c-1.5.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:02c65b9817512edc6a4ae7c7e987fea799d2e0ee40c53ec573a692bee24de876"}, + {file = "google_crc32c-1.5.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:6ac08d24c1f16bd2bf5eca8eaf8304812f44af5cfe5062006ec676e7e1d50afc"}, + {file = "google_crc32c-1.5.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:3359fc442a743e870f4588fcf5dcbc1bf929df1fad8fb9905cd94e5edb02e84c"}, + {file = "google_crc32c-1.5.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:1e986b206dae4476f41bcec1faa057851f3889503a70e1bdb2378d406223994a"}, + {file = "google_crc32c-1.5.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:de06adc872bcd8c2a4e0dc51250e9e65ef2ca91be023b9d13ebd67c2ba552e1e"}, + {file = "google_crc32c-1.5.0-cp37-cp37m-win32.whl", hash = "sha256:d3515f198eaa2f0ed49f8819d5732d70698c3fa37384146079b3799b97667a94"}, + {file = "google_crc32c-1.5.0-cp37-cp37m-win_amd64.whl", hash = "sha256:67b741654b851abafb7bc625b6d1cdd520a379074e64b6a128e3b688c3c04740"}, + {file = "google_crc32c-1.5.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:c02ec1c5856179f171e032a31d6f8bf84e5a75c45c33b2e20a3de353b266ebd8"}, + {file = "google_crc32c-1.5.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:edfedb64740750e1a3b16152620220f51d58ff1b4abceb339ca92e934775c27a"}, + {file = "google_crc32c-1.5.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:84e6e8cd997930fc66d5bb4fde61e2b62ba19d62b7abd7a69920406f9ecca946"}, + {file = "google_crc32c-1.5.0-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:024894d9d3cfbc5943f8f230e23950cd4906b2fe004c72e29b209420a1e6b05a"}, + {file = "google_crc32c-1.5.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:998679bf62b7fb599d2878aa3ed06b9ce688b8974893e7223c60db155f26bd8d"}, + {file = "google_crc32c-1.5.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:83c681c526a3439b5cf94f7420471705bbf96262f49a6fe546a6db5f687a3d4a"}, + {file = "google_crc32c-1.5.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:4c6fdd4fccbec90cc8a01fc00773fcd5fa28db683c116ee3cb35cd5da9ef6c37"}, + {file = "google_crc32c-1.5.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:5ae44e10a8e3407dbe138984f21e536583f2bba1be9491239f942c2464ac0894"}, + {file = "google_crc32c-1.5.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:37933ec6e693e51a5b07505bd05de57eee12f3e8c32b07da7e73669398e6630a"}, + {file = "google_crc32c-1.5.0-cp38-cp38-win32.whl", hash = "sha256:fe70e325aa68fa4b5edf7d1a4b6f691eb04bbccac0ace68e34820d283b5f80d4"}, + {file = "google_crc32c-1.5.0-cp38-cp38-win_amd64.whl", hash = "sha256:74dea7751d98034887dbd821b7aae3e1d36eda111d6ca36c206c44478035709c"}, + {file = "google_crc32c-1.5.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:c6c777a480337ac14f38564ac88ae82d4cd238bf293f0a22295b66eb89ffced7"}, + {file = "google_crc32c-1.5.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:759ce4851a4bb15ecabae28f4d2e18983c244eddd767f560165563bf9aefbc8d"}, + {file = "google_crc32c-1.5.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f13cae8cc389a440def0c8c52057f37359014ccbc9dc1f0827936bcd367c6100"}, + {file = "google_crc32c-1.5.0-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e560628513ed34759456a416bf86b54b2476c59144a9138165c9a1575801d0d9"}, + {file = "google_crc32c-1.5.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e1674e4307fa3024fc897ca774e9c7562c957af85df55efe2988ed9056dc4e57"}, + {file = "google_crc32c-1.5.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:278d2ed7c16cfc075c91378c4f47924c0625f5fc84b2d50d921b18b7975bd210"}, + {file = "google_crc32c-1.5.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:d5280312b9af0976231f9e317c20e4a61cd2f9629b7bfea6a693d1878a264ebd"}, + {file = "google_crc32c-1.5.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:8b87e1a59c38f275c0e3676fc2ab6d59eccecfd460be267ac360cc31f7bcde96"}, + {file = "google_crc32c-1.5.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:7c074fece789b5034b9b1404a1f8208fc2d4c6ce9decdd16e8220c5a793e6f61"}, + {file = "google_crc32c-1.5.0-cp39-cp39-win32.whl", hash = "sha256:7f57f14606cd1dd0f0de396e1e53824c371e9544a822648cd76c034d209b559c"}, + {file = "google_crc32c-1.5.0-cp39-cp39-win_amd64.whl", hash = "sha256:a2355cba1f4ad8b6988a4ca3feed5bff33f6af2d7f134852cf279c2aebfde541"}, + {file = "google_crc32c-1.5.0-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:f314013e7dcd5cf45ab1945d92e713eec788166262ae8deb2cfacd53def27325"}, + {file = "google_crc32c-1.5.0-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3b747a674c20a67343cb61d43fdd9207ce5da6a99f629c6e2541aa0e89215bcd"}, + {file = "google_crc32c-1.5.0-pp37-pypy37_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8f24ed114432de109aa9fd317278518a5af2d31ac2ea6b952b2f7782b43da091"}, + {file = "google_crc32c-1.5.0-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b8667b48e7a7ef66afba2c81e1094ef526388d35b873966d8a9a447974ed9178"}, + {file = "google_crc32c-1.5.0-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:1c7abdac90433b09bad6c43a43af253e688c9cfc1c86d332aed13f9a7c7f65e2"}, + {file = "google_crc32c-1.5.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:6f998db4e71b645350b9ac28a2167e6632c239963ca9da411523bb439c5c514d"}, + {file = "google_crc32c-1.5.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9c99616c853bb585301df6de07ca2cadad344fd1ada6d62bb30aec05219c45d2"}, + {file = "google_crc32c-1.5.0-pp38-pypy38_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2ad40e31093a4af319dadf503b2467ccdc8f67c72e4bcba97f8c10cb078207b5"}, + {file = "google_crc32c-1.5.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cd67cf24a553339d5062eff51013780a00d6f97a39ca062781d06b3a73b15462"}, + {file = "google_crc32c-1.5.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:398af5e3ba9cf768787eef45c803ff9614cc3e22a5b2f7d7ae116df8b11e3314"}, + {file = "google_crc32c-1.5.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:b1f8133c9a275df5613a451e73f36c2aea4fe13c5c8997e22cf355ebd7bd0728"}, + {file = "google_crc32c-1.5.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9ba053c5f50430a3fcfd36f75aff9caeba0440b2d076afdb79a318d6ca245f88"}, + {file = "google_crc32c-1.5.0-pp39-pypy39_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:272d3892a1e1a2dbc39cc5cde96834c236d5327e2122d3aaa19f6614531bb6eb"}, + {file = "google_crc32c-1.5.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:635f5d4dd18758a1fbd1049a8e8d2fee4ffed124462d837d1a02a0e009c3ab31"}, + {file = "google_crc32c-1.5.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:c672d99a345849301784604bfeaeba4db0c7aae50b95be04dd651fd2a7310b93"}, +] + +[package.extras] +testing = ["pytest"] + +[[package]] +name = "google-resumable-media" +version = "2.7.0" +description = "Utilities for Google Media Downloads and Resumable Uploads" +optional = false +python-versions = ">= 3.7" +files = [ + {file = "google-resumable-media-2.7.0.tar.gz", hash = "sha256:5f18f5fa9836f4b083162064a1c2c98c17239bfda9ca50ad970ccf905f3e625b"}, + {file = "google_resumable_media-2.7.0-py2.py3-none-any.whl", hash = "sha256:79543cfe433b63fd81c0844b7803aba1bb8950b47bedf7d980c38fa123937e08"}, +] + +[package.dependencies] +google-crc32c = ">=1.0,<2.0dev" + +[package.extras] +aiohttp = ["aiohttp (>=3.6.2,<4.0.0dev)", "google-auth (>=1.22.0,<2.0dev)"] +requests = ["requests (>=2.18.0,<3.0.0dev)"] + +[[package]] +name = "googleapis-common-protos" +version = "1.62.0" +description = "Common protobufs used in Google APIs" +optional = false +python-versions = ">=3.7" +files = [ + {file = "googleapis-common-protos-1.62.0.tar.gz", hash = "sha256:83f0ece9f94e5672cced82f592d2a5edf527a96ed1794f0bab36d5735c996277"}, + {file = "googleapis_common_protos-1.62.0-py2.py3-none-any.whl", hash = "sha256:4750113612205514f9f6aa4cb00d523a94f3e8c06c5ad2fee466387dc4875f07"}, +] + +[package.dependencies] +protobuf = ">=3.19.5,<3.20.0 || >3.20.0,<3.20.1 || >3.20.1,<4.21.1 || >4.21.1,<4.21.2 || >4.21.2,<4.21.3 || >4.21.3,<4.21.4 || >4.21.4,<4.21.5 || >4.21.5,<5.0.0.dev0" + +[package.extras] +grpc = ["grpcio (>=1.44.0,<2.0.0.dev0)"] + [[package]] name = "greenlet" version = "3.0.3" @@ -2739,6 +2950,8 @@ files = [ {file = "psycopg2-2.9.9-cp310-cp310-win_amd64.whl", hash = "sha256:426f9f29bde126913a20a96ff8ce7d73fd8a216cfb323b1f04da402d452853c3"}, {file = "psycopg2-2.9.9-cp311-cp311-win32.whl", hash = "sha256:ade01303ccf7ae12c356a5e10911c9e1c51136003a9a1d92f7aa9d010fb98372"}, {file = "psycopg2-2.9.9-cp311-cp311-win_amd64.whl", hash = "sha256:121081ea2e76729acfb0673ff33755e8703d45e926e416cb59bae3a86c6a4981"}, + {file = "psycopg2-2.9.9-cp312-cp312-win32.whl", hash = "sha256:d735786acc7dd25815e89cc4ad529a43af779db2e25aa7c626de864127e5a024"}, + {file = "psycopg2-2.9.9-cp312-cp312-win_amd64.whl", hash = "sha256:a7653d00b732afb6fc597e29c50ad28087dcb4fbfb28e86092277a559ae4e693"}, {file = "psycopg2-2.9.9-cp37-cp37m-win32.whl", hash = "sha256:5e0d98cade4f0e0304d7d6f25bbfbc5bd186e07b38eac65379309c4ca3193efa"}, {file = "psycopg2-2.9.9-cp37-cp37m-win_amd64.whl", hash = "sha256:7e2dacf8b009a1c1e843b5213a87f7c544b2b042476ed7755be813eaf4e8347a"}, {file = "psycopg2-2.9.9-cp38-cp38-win32.whl", hash = "sha256:ff432630e510709564c01dafdbe996cb552e0b9f3f065eb89bdce5bd31fabf4c"}, @@ -2759,6 +2972,20 @@ files = [ {file = "pyasn1-0.5.1.tar.gz", hash = "sha256:6d391a96e59b23130a5cfa74d6fd7f388dbbe26cc8f1edf39fdddf08d9d6676c"}, ] +[[package]] +name = "pyasn1-modules" +version = "0.3.0" +description = "A collection of ASN.1-based protocols modules" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7" +files = [ + {file = "pyasn1_modules-0.3.0-py2.py3-none-any.whl", hash = "sha256:d3ccd6ed470d9ffbc716be08bd90efbd44d0734bc9303818f7336070984a162d"}, + {file = "pyasn1_modules-0.3.0.tar.gz", hash = "sha256:5bd01446b736eb9d31512a30d46c1ac3395d676c6f3cafa4c03eb54b9925631c"}, +] + +[package.dependencies] +pyasn1 = ">=0.4.6,<0.6.0" + [[package]] name = "pycocotools" version = "2.0.7" @@ -3029,6 +3256,7 @@ files = [ {file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"}, {file = "PyYAML-6.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28"}, {file = "PyYAML-6.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9"}, + {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a08c6f0fe150303c1c6b71ebcd7213c2858041a7e01975da3a99aed1e7a378ef"}, {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0"}, {file = "PyYAML-6.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4"}, {file = "PyYAML-6.0.1-cp312-cp312-win32.whl", hash = "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54"}, @@ -3329,6 +3557,20 @@ files = [ {file = "rpds_py-0.17.1.tar.gz", hash = "sha256:0210b2668f24c078307260bf88bdac9d6f1093635df5123789bfee4d8d7fc8e7"}, ] +[[package]] +name = "rsa" +version = "4.9" +description = "Pure-Python RSA implementation" +optional = false +python-versions = ">=3.6,<4" +files = [ + {file = "rsa-4.9-py3-none-any.whl", hash = "sha256:90260d9058e514786967344d0ef75fa8727eed8a7d2e43ce9f4bcf1b536174f7"}, + {file = "rsa-4.9.tar.gz", hash = "sha256:e38464a49c6c85d7f1351b0126661487a7e0a14a50f1675ec50eb34d4f20ef21"}, +] + +[package.dependencies] +pyasn1 = ">=0.1.3" + [[package]] name = "ruamel-yaml" version = "0.18.5" @@ -3357,30 +3599,50 @@ files = [ {file = "ruamel.yaml.clib-0.2.8-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:b42169467c42b692c19cf539c38d4602069d8c1505e97b86387fcf7afb766e1d"}, {file = "ruamel.yaml.clib-0.2.8-cp310-cp310-macosx_13_0_arm64.whl", hash = "sha256:07238db9cbdf8fc1e9de2489a4f68474e70dffcb32232db7c08fa61ca0c7c462"}, {file = "ruamel.yaml.clib-0.2.8-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:fff3573c2db359f091e1589c3d7c5fc2f86f5bdb6f24252c2d8e539d4e45f412"}, + {file = "ruamel.yaml.clib-0.2.8-cp310-cp310-manylinux_2_24_aarch64.whl", hash = "sha256:aa2267c6a303eb483de8d02db2871afb5c5fc15618d894300b88958f729ad74f"}, + {file = "ruamel.yaml.clib-0.2.8-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:840f0c7f194986a63d2c2465ca63af8ccbbc90ab1c6001b1978f05119b5e7334"}, + {file = "ruamel.yaml.clib-0.2.8-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:024cfe1fc7c7f4e1aff4a81e718109e13409767e4f871443cbff3dba3578203d"}, {file = "ruamel.yaml.clib-0.2.8-cp310-cp310-win32.whl", hash = "sha256:c69212f63169ec1cfc9bb44723bf2917cbbd8f6191a00ef3410f5a7fe300722d"}, {file = "ruamel.yaml.clib-0.2.8-cp310-cp310-win_amd64.whl", hash = "sha256:cabddb8d8ead485e255fe80429f833172b4cadf99274db39abc080e068cbcc31"}, {file = "ruamel.yaml.clib-0.2.8-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:bef08cd86169d9eafb3ccb0a39edb11d8e25f3dae2b28f5c52fd997521133069"}, {file = "ruamel.yaml.clib-0.2.8-cp311-cp311-macosx_13_0_arm64.whl", hash = "sha256:b16420e621d26fdfa949a8b4b47ade8810c56002f5389970db4ddda51dbff248"}, {file = "ruamel.yaml.clib-0.2.8-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:25c515e350e5b739842fc3228d662413ef28f295791af5e5110b543cf0b57d9b"}, + {file = "ruamel.yaml.clib-0.2.8-cp311-cp311-manylinux_2_24_aarch64.whl", hash = "sha256:1707814f0d9791df063f8c19bb51b0d1278b8e9a2353abbb676c2f685dee6afe"}, + {file = "ruamel.yaml.clib-0.2.8-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:46d378daaac94f454b3a0e3d8d78cafd78a026b1d71443f4966c696b48a6d899"}, + {file = "ruamel.yaml.clib-0.2.8-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:09b055c05697b38ecacb7ac50bdab2240bfca1a0c4872b0fd309bb07dc9aa3a9"}, {file = "ruamel.yaml.clib-0.2.8-cp311-cp311-win32.whl", hash = "sha256:53a300ed9cea38cf5a2a9b069058137c2ca1ce658a874b79baceb8f892f915a7"}, {file = "ruamel.yaml.clib-0.2.8-cp311-cp311-win_amd64.whl", hash = "sha256:c2a72e9109ea74e511e29032f3b670835f8a59bbdc9ce692c5b4ed91ccf1eedb"}, {file = "ruamel.yaml.clib-0.2.8-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:ebc06178e8821efc9692ea7544aa5644217358490145629914d8020042c24aa1"}, {file = "ruamel.yaml.clib-0.2.8-cp312-cp312-macosx_13_0_arm64.whl", hash = "sha256:edaef1c1200c4b4cb914583150dcaa3bc30e592e907c01117c08b13a07255ec2"}, {file = "ruamel.yaml.clib-0.2.8-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d176b57452ab5b7028ac47e7b3cf644bcfdc8cacfecf7e71759f7f51a59e5c92"}, + {file = "ruamel.yaml.clib-0.2.8-cp312-cp312-manylinux_2_24_aarch64.whl", hash = "sha256:1dc67314e7e1086c9fdf2680b7b6c2be1c0d8e3a8279f2e993ca2a7545fecf62"}, + {file = "ruamel.yaml.clib-0.2.8-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:3213ece08ea033eb159ac52ae052a4899b56ecc124bb80020d9bbceeb50258e9"}, + {file = "ruamel.yaml.clib-0.2.8-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:aab7fd643f71d7946f2ee58cc88c9b7bfc97debd71dcc93e03e2d174628e7e2d"}, + {file = "ruamel.yaml.clib-0.2.8-cp312-cp312-win32.whl", hash = "sha256:5c365d91c88390c8d0a8545df0b5857172824b1c604e867161e6b3d59a827eaa"}, + {file = "ruamel.yaml.clib-0.2.8-cp312-cp312-win_amd64.whl", hash = "sha256:1758ce7d8e1a29d23de54a16ae867abd370f01b5a69e1a3ba75223eaa3ca1a1b"}, {file = "ruamel.yaml.clib-0.2.8-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:a5aa27bad2bb83670b71683aae140a1f52b0857a2deff56ad3f6c13a017a26ed"}, {file = "ruamel.yaml.clib-0.2.8-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:c58ecd827313af6864893e7af0a3bb85fd529f862b6adbefe14643947cfe2942"}, {file = "ruamel.yaml.clib-0.2.8-cp37-cp37m-macosx_12_0_arm64.whl", hash = "sha256:f481f16baec5290e45aebdc2a5168ebc6d35189ae6fea7a58787613a25f6e875"}, + {file = "ruamel.yaml.clib-0.2.8-cp37-cp37m-manylinux_2_24_aarch64.whl", hash = "sha256:77159f5d5b5c14f7c34073862a6b7d34944075d9f93e681638f6d753606c6ce6"}, {file = "ruamel.yaml.clib-0.2.8-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:7f67a1ee819dc4562d444bbafb135832b0b909f81cc90f7aa00260968c9ca1b3"}, + {file = "ruamel.yaml.clib-0.2.8-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:4ecbf9c3e19f9562c7fdd462e8d18dd902a47ca046a2e64dba80699f0b6c09b7"}, + {file = "ruamel.yaml.clib-0.2.8-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:87ea5ff66d8064301a154b3933ae406b0863402a799b16e4a1d24d9fbbcbe0d3"}, {file = "ruamel.yaml.clib-0.2.8-cp37-cp37m-win32.whl", hash = "sha256:75e1ed13e1f9de23c5607fe6bd1aeaae21e523b32d83bb33918245361e9cc51b"}, {file = "ruamel.yaml.clib-0.2.8-cp37-cp37m-win_amd64.whl", hash = "sha256:3f215c5daf6a9d7bbed4a0a4f760f3113b10e82ff4c5c44bec20a68c8014f675"}, {file = "ruamel.yaml.clib-0.2.8-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1b617618914cb00bf5c34d4357c37aa15183fa229b24767259657746c9077615"}, {file = "ruamel.yaml.clib-0.2.8-cp38-cp38-macosx_12_0_arm64.whl", hash = "sha256:a6a9ffd280b71ad062eae53ac1659ad86a17f59a0fdc7699fd9be40525153337"}, + {file = "ruamel.yaml.clib-0.2.8-cp38-cp38-manylinux_2_24_aarch64.whl", hash = "sha256:305889baa4043a09e5b76f8e2a51d4ffba44259f6b4c72dec8ca56207d9c6fe1"}, {file = "ruamel.yaml.clib-0.2.8-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:700e4ebb569e59e16a976857c8798aee258dceac7c7d6b50cab63e080058df91"}, + {file = "ruamel.yaml.clib-0.2.8-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:e2b4c44b60eadec492926a7270abb100ef9f72798e18743939bdbf037aab8c28"}, + {file = "ruamel.yaml.clib-0.2.8-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:e79e5db08739731b0ce4850bed599235d601701d5694c36570a99a0c5ca41a9d"}, {file = "ruamel.yaml.clib-0.2.8-cp38-cp38-win32.whl", hash = "sha256:955eae71ac26c1ab35924203fda6220f84dce57d6d7884f189743e2abe3a9fbe"}, {file = "ruamel.yaml.clib-0.2.8-cp38-cp38-win_amd64.whl", hash = "sha256:56f4252222c067b4ce51ae12cbac231bce32aee1d33fbfc9d17e5b8d6966c312"}, {file = "ruamel.yaml.clib-0.2.8-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:03d1162b6d1df1caa3a4bd27aa51ce17c9afc2046c31b0ad60a0a96ec22f8001"}, {file = "ruamel.yaml.clib-0.2.8-cp39-cp39-macosx_12_0_arm64.whl", hash = "sha256:bba64af9fa9cebe325a62fa398760f5c7206b215201b0ec825005f1b18b9bccf"}, + {file = "ruamel.yaml.clib-0.2.8-cp39-cp39-manylinux_2_24_aarch64.whl", hash = "sha256:a1a45e0bb052edf6a1d3a93baef85319733a888363938e1fc9924cb00c8df24c"}, {file = "ruamel.yaml.clib-0.2.8-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:da09ad1c359a728e112d60116f626cc9f29730ff3e0e7db72b9a2dbc2e4beed5"}, + {file = "ruamel.yaml.clib-0.2.8-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:184565012b60405d93838167f425713180b949e9d8dd0bbc7b49f074407c5a8b"}, + {file = "ruamel.yaml.clib-0.2.8-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a75879bacf2c987c003368cf14bed0ffe99e8e85acfa6c0bfffc21a090f16880"}, {file = "ruamel.yaml.clib-0.2.8-cp39-cp39-win32.whl", hash = "sha256:84b554931e932c46f94ab306913ad7e11bba988104c5cff26d90d03f68258cd5"}, {file = "ruamel.yaml.clib-0.2.8-cp39-cp39-win_amd64.whl", hash = "sha256:25ac8c08322002b06fa1d49d1646181f0b2c72f5cbc15a85e80b4c30a544bb15"}, {file = "ruamel.yaml.clib-0.2.8.tar.gz", hash = "sha256:beb2e0404003de9a4cab9753a8805a8fe9320ee6673136ed7f04255fe60bb512"}, @@ -3968,4 +4230,4 @@ multidict = ">=4.0" [metadata] lock-version = "2.0" python-versions = "^3.10, <3.13" -content-hash = "ec6d9842af954ffe6ba6e3c67ef593182bb3deb283a0efa994a12b42607fd36f" +content-hash = "45d001b7f9df8a4a2eba103888e31fab0cefb61662d3465cad9836e7dc844fe5" diff --git a/packages/examples/cvat/recording-oracle/pyproject.toml b/packages/examples/cvat/recording-oracle/pyproject.toml index d3402bce81..45acb90a7d 100644 --- a/packages/examples/cvat/recording-oracle/pyproject.toml +++ b/packages/examples/cvat/recording-oracle/pyproject.toml @@ -20,6 +20,7 @@ alembic = "^1.11.1" httpx = "^0.24.1" numpy = "^1.25.2" boto3 = "^1.28.40" +google-cloud-storage = "^2.14.0" datumaro = {git = "https://github.com/cvat-ai/datumaro.git", rev = "ff83c00c2c1bc4b8fdfcc55067fcab0a9b5b6b11"} [tool.poetry.group.dev.dependencies] diff --git a/packages/examples/cvat/recording-oracle/src/.env.template b/packages/examples/cvat/recording-oracle/src/.env.template index 4308688299..fde9f58ef4 100644 --- a/packages/examples/cvat/recording-oracle/src/.env.template +++ b/packages/examples/cvat/recording-oracle/src/.env.template @@ -35,21 +35,25 @@ PROCESS_REPUTATION_ORACLE_WEBHOOKS_CHUNK_SIZE= # Storage +STORAGE_PROVIDER= STORAGE_ENDPOINT_URL= STORAGE_REGION= STORAGE_ACCESS_KEY= STORAGE_SECRET_KEY= STORAGE_RESULTS_BUCKET_NAME= STORAGE_USE_SSL= +STORAGE_KEY_FILE_PATH= # Exchange Oracle Storage +EXCHANGE_ORACLE_STORAGE_PROVIDER= EXCHANGE_ORACLE_STORAGE_ENDPOINT_URL= EXCHANGE_ORACLE_STORAGE_REGION= EXCHANGE_ORACLE_STORAGE_ACCESS_KEY= EXCHANGE_ORACLE_STORAGE_SECRET_KEY= EXCHANGE_ORACLE_STORAGE_RESULTS_BUCKET_NAME= EXCHANGE_ORACLE_STORAGE_USE_SSL= +EXCHANGE_ORACLE_STORAGE_KEY_FILE_PATH= # Localhost @@ -58,4 +62,12 @@ LOCALHOST_REPUTATION_ORACLE_URL= # Features -ENABLE_CUSTOM_CLOUD_HOST= \ No newline at end of file +ENABLE_CUSTOM_CLOUD_HOST= + +# Validation + +DEFAULT_POINT_VALIDITY_RELATIVE_RADIUS= +DEFAULT_OKS_SIGMA= +GT_FAILURE_THRESHOLD= +GT_BAN_THRESHOLD= +UNVERIFIABLE_ASSIGNMENTS_THRESHOLD= diff --git a/packages/examples/cvat/recording-oracle/src/core/annotation_meta.py b/packages/examples/cvat/recording-oracle/src/core/annotation_meta.py index 1337c5756e..e19a77efd5 100644 --- a/packages/examples/cvat/recording-oracle/src/core/annotation_meta.py +++ b/packages/examples/cvat/recording-oracle/src/core/annotation_meta.py @@ -3,7 +3,7 @@ from pydantic import BaseModel -ANNOTATION_METAFILE_NAME = "annotation_meta.json" +ANNOTATION_RESULTS_METAFILE_NAME = "annotation_meta.json" RESULTING_ANNOTATIONS_FILE = "resulting_annotations.zip" diff --git a/packages/examples/cvat/recording-oracle/src/core/config.py b/packages/examples/cvat/recording-oracle/src/core/config.py index 3353b5e1b3..9d9dde45e3 100644 --- a/packages/examples/cvat/recording-oracle/src/core/config.py +++ b/packages/examples/cvat/recording-oracle/src/core/config.py @@ -1,7 +1,9 @@ # pylint: disable=too-few-public-methods,missing-class-docstring """ Project configuration from env vars """ import os +from typing import ClassVar, Optional +from attrs.converters import to_bool from dotenv import load_dotenv from src.utils.logging import parse_log_level @@ -10,12 +12,6 @@ load_dotenv() -def str_to_bool(val: str) -> bool: - from distutils.util import strtobool - - return val is True or strtobool(val) - - class Postgres: port = os.environ.get("PG_PORT", "5434") host = os.environ.get("PG_HOST", "0.0.0.0") @@ -71,62 +67,102 @@ class CronConfig: ) -class StorageConfig: - endpoint_url = os.environ.get("STORAGE_ENDPOINT_URL", "storage.googleapis.com") - region = os.environ.get("STORAGE_REGION", "") - access_key = os.environ.get("STORAGE_ACCESS_KEY", "") - secret_key = os.environ.get("STORAGE_SECRET_KEY", "") - results_bucket_name = os.environ.get("STORAGE_RESULTS_BUCKET_NAME", "") - secure = str_to_bool(os.environ.get("STORAGE_USE_SSL", "true")) +class IStorageConfig: + provider: ClassVar[str] + data_bucket_name: ClassVar[str] + secure: ClassVar[bool] + endpoint_url: ClassVar[str] # TODO: probably should be optional + region: ClassVar[Optional[str]] + # AWS S3 specific attributes + access_key: ClassVar[Optional[str]] + secret_key: ClassVar[Optional[str]] + # GCS specific attributes + key_file_path: ClassVar[Optional[str]] @classmethod - def provider_endpoint_url(cls): - scheme = "https://" if cls.secure else "http://" - - return f"{scheme}{cls.endpoint_url}" + def get_scheme(cls) -> str: + return "https://" if cls.secure else "http://" @classmethod - def bucket_url(cls): - scheme = "https://" if cls.secure else "http://" + def provider_endpoint_url(cls) -> str: + return f"{cls.get_scheme()}{cls.endpoint_url}" + @classmethod + def bucket_url(cls) -> str: if is_ipv4(cls.endpoint_url): - return f"{scheme}{cls.endpoint_url}/{cls.results_bucket_name}/" + return f"{cls.get_scheme()}{cls.endpoint_url}/{cls.data_bucket_name}/" else: - return f"{scheme}{cls.results_bucket_name}.{cls.endpoint_url}/" + return f"{cls.get_scheme()}{cls.data_bucket_name}.{cls.endpoint_url}/" -class ExchangeOracleStorageConfig: - endpoint_url = os.environ.get("EXCHANGE_ORACLE_STORAGE_ENDPOINT_URL", "storage.googleapis.com") - region = os.environ.get("EXCHANGE_ORACLE_STORAGE_REGION", "") - access_key = os.environ.get("EXCHANGE_ORACLE_STORAGE_ACCESS_KEY", "") - secret_key = os.environ.get("EXCHANGE_ORACLE_STORAGE_SECRET_KEY", "") - results_bucket_name = os.environ.get("EXCHANGE_ORACLE_STORAGE_RESULTS_BUCKET_NAME", "") - secure = str_to_bool(os.environ.get("EXCHANGE_ORACLE_STORAGE_USE_SSL", "true")) +class StorageConfig(IStorageConfig): + provider = os.environ["STORAGE_PROVIDER"].lower() + endpoint_url = os.environ["STORAGE_ENDPOINT_URL"] # TODO: probably should be optional + region = os.environ.get("STORAGE_REGION") + data_bucket_name = os.environ["STORAGE_RESULTS_BUCKET_NAME"] + secure = to_bool(os.environ.get("STORAGE_USE_SSL", "true")) - @classmethod - def provider_endpoint_url(cls): - scheme = "https://" if cls.secure else "http://" + # AWS S3 specific attributes + access_key = os.environ.get("STORAGE_ACCESS_KEY") + secret_key = os.environ.get("STORAGE_SECRET_KEY") - return f"{scheme}{cls.endpoint_url}" + # GCS specific attributes + key_file_path = os.environ.get("STORAGE_KEY_FILE_PATH") - @classmethod - def bucket_url(cls): - scheme = "https://" if cls.secure else "http://" - if is_ipv4(cls.endpoint_url): - return f"{scheme}{cls.endpoint_url}/{cls.results_bucket_name}/" - else: - return f"{scheme}{cls.results_bucket_name}.{cls.endpoint_url}/" +class ExchangeOracleStorageConfig(IStorageConfig): + # common attributes + provider = os.environ["EXCHANGE_ORACLE_STORAGE_PROVIDER"].lower() + endpoint_url = os.environ[ + "EXCHANGE_ORACLE_STORAGE_ENDPOINT_URL" + ] # TODO: probably should be optional + region = os.environ.get("EXCHANGE_ORACLE_STORAGE_REGION") + data_bucket_name = os.environ["EXCHANGE_ORACLE_STORAGE_RESULTS_BUCKET_NAME"] + results_dir_suffix = os.environ.get("STORAGE_RESULTS_DIR_SUFFIX", "-results") + secure = to_bool(os.environ.get("EXCHANGE_ORACLE_STORAGE_USE_SSL", "true")) + # AWS S3 specific attributes + access_key = os.environ.get("EXCHANGE_ORACLE_STORAGE_ACCESS_KEY") + secret_key = os.environ.get("EXCHANGE_ORACLE_STORAGE_SECRET_KEY") + # GCS specific attributes + key_file_path = os.environ.get("EXCHANGE_ORACLE_STORAGE_KEY_FILE_PATH") class FeaturesConfig: - enable_custom_cloud_host = str_to_bool(os.environ.get("ENABLE_CUSTOM_CLOUD_HOST", "no")) + enable_custom_cloud_host = to_bool(os.environ.get("ENABLE_CUSTOM_CLOUD_HOST", "no")) "Allows using a custom host in manifest bucket urls" + +class ValidationConfig: default_point_validity_relative_radius = float( os.environ.get("DEFAULT_POINT_VALIDITY_RELATIVE_RADIUS", 0.8) ) + default_oks_sigma = float( + os.environ.get("DEFAULT_OKS_SIGMA", 0.1) # average value for COCO points + ) + "Default OKS sigma for GT skeleton points validation. Valid range is (0; 1]" + + gt_failure_threshold = float(os.environ.get("GT_FAILURE_THRESHOLD", 0.9)) + """ + The maximum allowed fraction of failed assignments per GT sample, + before it's considered failed for the current validation iteration. + v = 0 -> any GT failure leads to image failure + v = 1 -> any GT failures do not lead to image failure + """ + + gt_ban_threshold = int(os.environ.get("GT_BAN_THRESHOLD", 3)) + """ + The maximum allowed number of failures per GT sample before it's excluded from validation + """ + + unverifiable_assignments_threshold = float( + os.environ.get("UNVERIFIABLE_ASSIGNMENTS_THRESHOLD", 0.1) + ) + """ + The maximum allowed fraction of jobs with insufficient GT available for validation. + Each such job will be accepted "blindly", as we can't validate the annotations. + """ + class Config: port = int(os.environ.get("PORT", 8000)) @@ -146,3 +182,4 @@ class Config: exchange_oracle_storage_config = ExchangeOracleStorageConfig features = FeaturesConfig + validation = ValidationConfig diff --git a/packages/examples/cvat/recording-oracle/src/core/manifest.py b/packages/examples/cvat/recording-oracle/src/core/manifest.py index 00bd265b82..24cfd750b2 100644 --- a/packages/examples/cvat/recording-oracle/src/core/manifest.py +++ b/packages/examples/cvat/recording-oracle/src/core/manifest.py @@ -1,27 +1,126 @@ from decimal import Decimal -from typing import Optional +from enum import Enum +from typing import Annotated, Any, Dict, List, Literal, Optional, Tuple, Union from pydantic import AnyUrl, BaseModel, Field, root_validator -from src.core.config import Config -from src.core.types import TaskType +from src.core.types import TaskTypes +from src.utils.enums import BetterEnumMeta + + +class BucketProviders(str, Enum): + aws = "AWS" + gcs = "GCS" + + +class BucketUrlBase(BaseModel): + provider: BucketProviders + host_url: str + bucket_name: str + path: str = "" + + +class AwsBucketUrl(BucketUrlBase, BaseModel): + provider: Literal[BucketProviders.aws] + access_key: str = "" # (optional) AWS Access key + secret_key: str = "" # (optional) AWS Secret key + + +class GcsBucketUrl(BucketUrlBase, BaseModel): + provider: Literal[BucketProviders.gcs] + service_account_key: Dict[str, Any] = {} # (optional) Contents of GCS key file + + +BucketUrl = Annotated[Union[AwsBucketUrl, GcsBucketUrl], Field(discriminator="provider")] class DataInfo(BaseModel): - data_url: AnyUrl - "Bucket URL, s3 only, virtual-hosted-style access" + data_url: Union[AnyUrl, BucketUrl] + "Bucket URL, AWS S3 | GCS, virtual-hosted-style access" # https://docs.aws.amazon.com/AmazonS3/latest/userguide/access-bucket-intro.html + points_url: Optional[Union[AnyUrl, BucketUrl]] = None + "A path to an archive with a set of points in COCO Keypoints format, " + "which provides information about all objects on images" -class LabelInfo(BaseModel): - name: str + boxes_url: Optional[Union[AnyUrl, BucketUrl]] = None + "A path to an archive with a set of boxes in COCO Instances format, " + "which provides information about all objects on images" + + +class LabelTypes(str, Enum, metaclass=BetterEnumMeta): + plain = "plain" + skeleton = "skeleton" + + +class LabelInfoBase(BaseModel): + name: str = Field(min_length=1) # https://opencv.github.io/cvat/docs/api_sdk/sdk/reference/models/label/ + type: LabelTypes = LabelTypes.plain + + +class PlainLabelInfo(LabelInfoBase): + type: Literal[LabelTypes.plain] + + +class SkeletonLabelInfo(LabelInfoBase): + type: Literal[LabelTypes.skeleton] + + nodes: List[str] = Field(min_items=1) + """ + A list of node label names (only points are supposed to be nodes). + Example: + [ + "left hand", "torso", "right hand", "head" + ] + """ + + joints: List[Tuple[int, int]] + "A list of node adjacency, e.g. [[0, 1], [1, 2], [1, 3]]" + + @root_validator + @classmethod + def validate_type(cls, values: dict) -> dict: + if values["type"] != LabelTypes.skeleton: + raise ValueError(f"Label type must be {LabelTypes.skeleton}") + + skeleton_name = values["name"] + + existing_names = set() + for node_name in values["nodes"]: + node_name = node_name.strip() + + if not node_name: + raise ValueError(f"Skeleton '{skeleton_name}': point name is empty") + + if node_name.lower() in existing_names: + raise ValueError( + f"Skeleton '{skeleton_name}' point {node_name}: label is duplicated" + ) + + existing_names.add(node_name.lower()) + + nodes_count = len(values["nodes"]) + joints = values["joints"] + for joint_idx, joint in enumerate(joints): + for v in joint: + if not (0 <= v < nodes_count): + raise ValueError( + f"Skeleton '{skeleton_name}' joint #{joint_idx}: invalid value. " + f"Expected a number in the range [0; {nodes_count - 1}]" + ) + + return values + + +LabelInfo = Annotated[Union[PlainLabelInfo, SkeletonLabelInfo], Field(discriminator="type")] + class AnnotationInfo(BaseModel): - type: TaskType + type: TaskTypes - labels: list[LabelInfo] + labels: list[LabelInfo] = Field(min_items=1) "Label declarations with accepted annotation types" description: str = "" @@ -33,15 +132,24 @@ class AnnotationInfo(BaseModel): job_size: int = 10 "Frames per job, validation frames are not included" - max_time: Optional[int] + max_time: Optional[int] = None # deprecated, TODO: mark deprecated with pydantic 2.7+ "Maximum time per job (assignment) for an annotator, in seconds" - @root_validator + @root_validator(pre=True) @classmethod - def validate_type(cls, values: dict) -> dict: - if values["type"] == TaskType.image_label_binary: - if len(values["labels"]) != 2: - raise ValueError("Binary classification requires 2 labels") + def _validate_label_type(cls, values: dict[str, Any]) -> dict[str, Any]: + default_label_type = LabelTypes.plain + if values["type"] == TaskTypes.image_skeletons_from_boxes: + default_label_type = LabelTypes.skeleton + + # Add default value for labels, if none provided. + # pydantic can't do this for tagged unions + try: + labels = values["labels"] + for label_info in labels: + label_info["type"] = label_info.get("type", default_label_type) + except KeyError: + pass return values @@ -53,7 +161,7 @@ class ValidationInfo(BaseModel): val_size: int = Field(default=2, gt=0) "Validation frames per job" - gt_url: AnyUrl + gt_url: Union[AnyUrl, BucketUrl] "URL to the archive with Ground Truth annotations, the format is COCO keypoints" @@ -64,3 +172,7 @@ class TaskManifest(BaseModel): job_bounty: Decimal = Field(ge=0) "Assignment bounty, a decimal value in HMT" + + +def parse_manifest(manifest: Any) -> TaskManifest: + return TaskManifest.parse_obj(manifest) diff --git a/packages/examples/cvat/recording-oracle/src/core/oracle_events.py b/packages/examples/cvat/recording-oracle/src/core/oracle_events.py index 8075abd8ee..519152e2de 100644 --- a/packages/examples/cvat/recording-oracle/src/core/oracle_events.py +++ b/packages/examples/cvat/recording-oracle/src/core/oracle_events.py @@ -2,11 +2,11 @@ from pydantic import BaseModel -from src.core.types import ExchangeOracleEventType, OracleWebhookTypes, RecordingOracleEventType +from src.core.types import ExchangeOracleEventTypes, OracleWebhookTypes, RecordingOracleEventTypes EventTypeTag = Union[ - ExchangeOracleEventType, - RecordingOracleEventType, + ExchangeOracleEventTypes, + RecordingOracleEventTypes, ] @@ -43,10 +43,10 @@ class ExchangeOracleEvent_TaskFinished(OracleEvent): _event_type_map = { - RecordingOracleEventType.task_completed: RecordingOracleEvent_TaskCompleted, - RecordingOracleEventType.task_rejected: RecordingOracleEvent_TaskRejected, - ExchangeOracleEventType.task_creation_failed: ExchangeOracleEvent_TaskCreationFailed, - ExchangeOracleEventType.task_finished: ExchangeOracleEvent_TaskFinished, + RecordingOracleEventTypes.task_completed: RecordingOracleEvent_TaskCompleted, + RecordingOracleEventTypes.task_rejected: RecordingOracleEvent_TaskRejected, + ExchangeOracleEventTypes.task_creation_failed: ExchangeOracleEvent_TaskCreationFailed, + ExchangeOracleEventTypes.task_finished: ExchangeOracleEvent_TaskFinished, } @@ -72,8 +72,8 @@ def parse_event( sender: OracleWebhookTypes, event_type: str, event_data: Optional[dict] = None ) -> OracleEvent: sender_events_mapping = { - OracleWebhookTypes.recording_oracle: RecordingOracleEventType, - OracleWebhookTypes.exchange_oracle: ExchangeOracleEventType, + OracleWebhookTypes.recording_oracle: RecordingOracleEventTypes, + OracleWebhookTypes.exchange_oracle: ExchangeOracleEventTypes, } sender_events = sender_events_mapping.get(sender) diff --git a/packages/examples/cvat/recording-oracle/src/core/storage.py b/packages/examples/cvat/recording-oracle/src/core/storage.py new file mode 100644 index 0000000000..6c5c53dbe2 --- /dev/null +++ b/packages/examples/cvat/recording-oracle/src/core/storage.py @@ -0,0 +1,10 @@ +from src.core.config import Config +from src.core.types import Networks + + +def compose_data_bucket_filename(escrow_address: str, chain_id: Networks, filename: str) -> str: + return f"{escrow_address}@{chain_id}/{filename}" + + +def compose_results_bucket_filename(escrow_address: str, chain_id: Networks, filename: str) -> str: + return f"{escrow_address}@{chain_id}{Config.exchange_oracle_storage_config.results_dir_suffix}/{filename}" diff --git a/packages/examples/cvat/recording-oracle/src/core/tasks/__init__.py b/packages/examples/cvat/recording-oracle/src/core/tasks/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/examples/cvat/recording-oracle/src/core/tasks/boxes_from_points.py b/packages/examples/cvat/recording-oracle/src/core/tasks/boxes_from_points.py new file mode 100644 index 0000000000..c9320473b6 --- /dev/null +++ b/packages/examples/cvat/recording-oracle/src/core/tasks/boxes_from_points.py @@ -0,0 +1,92 @@ +import os +from pathlib import Path +from tempfile import TemporaryDirectory +from typing import Dict, Sequence + +import attrs +import datumaro as dm +from attrs import frozen +from datumaro.util import dump_json, parse_json + +BboxPointMapping = Dict[int, int] + + +@frozen +class RoiInfo: + point_id: int + original_image_key: int + point_x: int + point_y: int + roi_x: int + roi_y: int + roi_w: int + roi_h: int + + def asdict(self) -> dict: + return attrs.asdict(self, recurse=False) + + +RoiInfos = Sequence[RoiInfo] + +RoiFilenames = Dict[int, str] + + +class TaskMetaLayout: + GT_FILENAME = "gt.json" + POINTS_FILENAME = "points.json" + BBOX_POINT_MAPPING_FILENAME = "bbox_point_mapping.json" + ROI_INFO_FILENAME = "rois.json" + + ROI_FILENAMES_FILENAME = "roi_filenames.json" + # this is separated from the general roi info to make name mangling more "optional" + + +class TaskMetaSerializer: + GT_DATASET_FORMAT = "coco_instances" + POINTS_DATASET_FORMAT = "coco_person_keypoints" + + def serialize_gt_annotations(self, gt_dataset: dm.Dataset) -> bytes: + with TemporaryDirectory() as temp_dir: + gt_dataset_dir = os.path.join(temp_dir, "gt_dataset") + gt_dataset.export(gt_dataset_dir, self.GT_DATASET_FORMAT) + return (Path(gt_dataset_dir) / "annotations" / "instances_default.json").read_bytes() + + def serialize_bbox_point_mapping(self, bbox_point_mapping: BboxPointMapping) -> bytes: + return dump_json({str(k): str(v) for k, v in bbox_point_mapping.items()}) + + def serialize_roi_info(self, rois_info: RoiInfos) -> bytes: + return dump_json([roi_info.asdict() for roi_info in rois_info]) + + def serialize_roi_filenames(self, roi_filenames: RoiFilenames) -> bytes: + return dump_json({str(k): v for k, v in roi_filenames.items()}) + + def parse_gt_annotations(self, gt_dataset_data: bytes) -> dm.Dataset: + with TemporaryDirectory() as temp_dir: + annotations_filename = os.path.join(temp_dir, "annotations.json") + with open(annotations_filename, "wb") as f: + f.write(gt_dataset_data) + + dataset = dm.Dataset.import_from(annotations_filename, format=self.GT_DATASET_FORMAT) + dataset.init_cache() + return dataset + + def parse_points_annotations(self, points_dataset_data: bytes) -> dm.Dataset: + with TemporaryDirectory() as temp_dir: + annotations_filename = os.path.join(temp_dir, "annotations.json") + with open(annotations_filename, "wb") as f: + f.write(points_dataset_data) + + dataset = dm.Dataset.import_from( + annotations_filename, format=self.POINTS_DATASET_FORMAT + ) + dataset.init_cache() + return dataset + + def parse_bbox_point_mapping(self, bbox_point_mapping_data: bytes) -> BboxPointMapping: + return {int(k): int(v) for k, v in parse_json(bbox_point_mapping_data).items()} + + def parse_roi_info(self, rois_info_data: bytes) -> RoiInfos: + return [RoiInfo(**roi_info) for roi_info in parse_json(rois_info_data)] + + def parse_roi_filenames(self, roi_filenames_data: bytes) -> RoiFilenames: + return {int(k): v for k, v in parse_json(roi_filenames_data).items()} diff --git a/packages/examples/cvat/recording-oracle/src/core/tasks/skeletons_from_boxes.py b/packages/examples/cvat/recording-oracle/src/core/tasks/skeletons_from_boxes.py new file mode 100644 index 0000000000..b6b680c19a --- /dev/null +++ b/packages/examples/cvat/recording-oracle/src/core/tasks/skeletons_from_boxes.py @@ -0,0 +1,126 @@ +import os +from pathlib import Path +from tempfile import TemporaryDirectory +from typing import Dict, Sequence, Tuple + +import attrs +import datumaro as dm +from attrs import frozen +from datumaro.util import dump_json, parse_json + +SkeletonBboxMapping = Dict[int, int] + + +# TODO: migrate to pydantic +@frozen(kw_only=True) +class RoiInfo: + original_image_key: int + bbox_id: int + bbox_x: int + bbox_y: int + bbox_label: int + + # RoI is centered on the bbox center + # Coordinates can be out of image boundaries. + # In this case RoI includes extra margins to be centered on bbox center + roi_x: int + roi_y: int + roi_w: int + roi_h: int + + def asdict(self) -> dict: + return attrs.asdict(self, recurse=False) + + +RoiInfos = Sequence[RoiInfo] + +RoiFilenames = Dict[int, str] + +PointLabelsMapping = Dict[Tuple[str, str], str] +"(skeleton, point) -> job point name" + + +class TaskMetaLayout: + GT_FILENAME = "gt.json" + BOXES_FILENAME = "boxes.json" + POINT_LABELS_FILENAME = "point_labels.json" + SKELETON_BBOX_MAPPING_FILENAME = "skeleton_bbox_mapping.json" + ROI_INFO_FILENAME = "rois.json" + + ROI_FILENAMES_FILENAME = "roi_filenames.json" + # this is separated from the general roi info to make name mangling more "optional" + + +class TaskMetaSerializer: + GT_DATASET_FORMAT = "coco_person_keypoints" + BBOX_DATASET_FORMAT = "coco_instances" + + def serialize_gt_annotations(self, gt_dataset: dm.Dataset) -> bytes: + with TemporaryDirectory() as temp_dir: + gt_dataset_dir = os.path.join(temp_dir, "gt_dataset") + gt_dataset.export(gt_dataset_dir, self.GT_DATASET_FORMAT) + return ( + Path(gt_dataset_dir) / "annotations" / "person_keypoints_default.json" + ).read_bytes() + + def serialize_bbox_annotations(self, bbox_dataset: dm.Dataset) -> bytes: + with TemporaryDirectory() as temp_dir: + bbox_dataset_dir = os.path.join(temp_dir, "bbox_dataset") + bbox_dataset.export(bbox_dataset_dir, self.BBOX_DATASET_FORMAT) + return (Path(bbox_dataset_dir) / "annotations" / "instances_default.json").read_bytes() + + def serialize_skeleton_bbox_mapping(self, skeleton_bbox_mapping: SkeletonBboxMapping) -> bytes: + return dump_json({str(k): str(v) for k, v in skeleton_bbox_mapping.items()}) + + def serialize_roi_info(self, rois_info: RoiInfos) -> bytes: + return dump_json([roi_info.asdict() for roi_info in rois_info]) + + def serialize_roi_filenames(self, roi_filenames: RoiFilenames) -> bytes: + return dump_json({str(k): v for k, v in roi_filenames.items()}) + + def serialize_point_labels(self, point_labels: PointLabelsMapping) -> bytes: + return dump_json( + [ + { + "skeleton_label": k[0], + "point_label": k[1], + "job_point_label": v, + } + for k, v in point_labels.items() + ] + ) + + def parse_gt_annotations(self, gt_dataset_data: bytes) -> dm.Dataset: + with TemporaryDirectory() as temp_dir: + annotations_filename = os.path.join(temp_dir, "annotations.json") + with open(annotations_filename, "wb") as f: + f.write(gt_dataset_data) + + dataset = dm.Dataset.import_from(annotations_filename, format=self.GT_DATASET_FORMAT) + dataset.init_cache() + return dataset + + def parse_bbox_annotations(self, bbox_dataset_data: bytes) -> dm.Dataset: + with TemporaryDirectory() as temp_dir: + annotations_filename = os.path.join(temp_dir, "annotations.json") + with open(annotations_filename, "wb") as f: + f.write(bbox_dataset_data) + + dataset = dm.Dataset.import_from(annotations_filename, format=self.BBOX_DATASET_FORMAT) + dataset.init_cache() + return dataset + + def parse_skeleton_bbox_mapping(self, skeleton_bbox_mapping_data: bytes) -> SkeletonBboxMapping: + return {int(k): int(v) for k, v in parse_json(skeleton_bbox_mapping_data).items()} + + def parse_roi_info(self, rois_info_data: bytes) -> RoiInfos: + return [RoiInfo(**roi_info) for roi_info in parse_json(rois_info_data)] + + def parse_roi_filenames(self, roi_filenames_data: bytes) -> RoiFilenames: + return {int(k): v for k, v in parse_json(roi_filenames_data).items()} + + def parse_point_labels(self, point_labels_data: bytes) -> PointLabelsMapping: + return { + (v["skeleton_label"], v["point_label"]): v["job_point_label"] + for v in parse_json(point_labels_data) + } diff --git a/packages/examples/cvat/recording-oracle/src/core/types.py b/packages/examples/cvat/recording-oracle/src/core/types.py index 90ff668752..3ed9792c9c 100644 --- a/packages/examples/cvat/recording-oracle/src/core/types.py +++ b/packages/examples/cvat/recording-oracle/src/core/types.py @@ -10,10 +10,12 @@ class Networks(int, Enum): localhost = Config.localhost.chain_id -class TaskType(str, Enum, metaclass=BetterEnumMeta): +class TaskTypes(str, Enum, metaclass=BetterEnumMeta): image_label_binary = "IMAGE_LABEL_BINARY" image_points = "IMAGE_POINTS" image_boxes = "IMAGE_BOXES" + image_boxes_from_points = "IMAGE_BOXES_FROM_POINTS" + image_skeletons_from_boxes = "IMAGE_SKELETONS_FROM_BOXES" class OracleWebhookTypes(str, Enum): @@ -28,16 +30,11 @@ class OracleWebhookStatuses(str, Enum): failed = "failed" -class ExchangeOracleEventType(str, Enum, metaclass=BetterEnumMeta): +class ExchangeOracleEventTypes(str, Enum, metaclass=BetterEnumMeta): task_creation_failed = "task_creation_failed" task_finished = "task_finished" -class RecordingOracleEventType(str, Enum, metaclass=BetterEnumMeta): +class RecordingOracleEventTypes(str, Enum, metaclass=BetterEnumMeta): task_completed = "task_completed" task_rejected = "task_rejected" - - -class CloudProviders(str, Enum, metaclass=BetterEnumMeta): - aws = "AWS_S3_BUCKET" - gcs = "GOOGLE_CLOUD_STORAGE" diff --git a/packages/examples/cvat/recording-oracle/src/core/validation_errors.py b/packages/examples/cvat/recording-oracle/src/core/validation_errors.py new file mode 100644 index 0000000000..b2d4b68a47 --- /dev/null +++ b/packages/examples/cvat/recording-oracle/src/core/validation_errors.py @@ -0,0 +1,15 @@ +class DatasetValidationError(Exception): + pass + + +class TooFewGtError(DatasetValidationError): + def __str__(self) -> str: + return ( + "Too many GT images were excluded. " + "Please check that all the GT images have correct annotations corresponding " + "to the dataset specification." + ) + + +class LowAccuracyError(DatasetValidationError): + pass diff --git a/packages/examples/cvat/recording-oracle/src/core/validation_results.py b/packages/examples/cvat/recording-oracle/src/core/validation_results.py new file mode 100644 index 0000000000..8d78bfc7c8 --- /dev/null +++ b/packages/examples/cvat/recording-oracle/src/core/validation_results.py @@ -0,0 +1,17 @@ +from dataclasses import dataclass +from typing import Dict + +from src.core.validation_errors import DatasetValidationError +from src.core.validation_meta import ValidationMeta + + +@dataclass +class ValidationSuccess: + validation_meta: ValidationMeta + resulting_annotations: bytes + average_quality: float + + +@dataclass +class ValidationFailure: + rejected_jobs: Dict[int, DatasetValidationError] diff --git a/packages/examples/cvat/recording-oracle/src/crons/process_exchange_oracle_webhooks.py b/packages/examples/cvat/recording-oracle/src/crons/process_exchange_oracle_webhooks.py index 710001cc8c..86d1097ece 100644 --- a/packages/examples/cvat/recording-oracle/src/crons/process_exchange_oracle_webhooks.py +++ b/packages/examples/cvat/recording-oracle/src/crons/process_exchange_oracle_webhooks.py @@ -1,38 +1,19 @@ -import io import logging -import os from typing import Dict import httpx from sqlalchemy.orm import Session -import src.chain.escrow as escrow -import src.core.annotation_meta as annotation -import src.core.validation_meta as validation -import src.services.cloud.client as cloud_client import src.services.webhook as oracle_db_service from src.chain.kvstore import get_exchange_oracle_url from src.core.config import Config -from src.core.oracle_events import ( - RecordingOracleEvent_TaskCompleted, - RecordingOracleEvent_TaskRejected, -) -from src.core.types import ExchangeOracleEventType, OracleWebhookTypes +from src.core.types import ExchangeOracleEventTypes, OracleWebhookTypes from src.db import SessionLocal from src.db.utils import ForUpdateParams -from src.handlers.process_intermediate_results import ( - ValidationSuccess, - parse_annotation_metafile, - process_intermediate_results, - serialize_validation_meta, -) +from src.handlers.validation import validate_results from src.log import ROOT_LOGGER_NAME from src.models.webhook import Webhook -from src.services.cloud import download_file -from src.utils.assignments import compute_resulting_annotations_hash, parse_manifest -from src.utils.cloud_storage import parse_bucket_url from src.utils.logging import get_function_logger -from src.utils.storage import compose_bucket_filename from src.utils.webhooks import prepare_outgoing_webhook_body, prepare_signed_message module_logger_name = f"{ROOT_LOGGER_NAME}.cron.webhook" @@ -81,148 +62,18 @@ def handle_exchange_oracle_event(webhook: Webhook, *, db_session: Session, logge assert webhook.type == OracleWebhookTypes.exchange_oracle match webhook.event_type: - case ExchangeOracleEventType.task_finished: + case ExchangeOracleEventTypes.task_finished: logger.debug( f"Received a task finish event for escrow_address={webhook.escrow_address}. " "Validating the results" ) - escrow.validate_escrow(webhook.chain_id, webhook.escrow_address) - - manifest = parse_manifest( - escrow.get_escrow_manifest(webhook.chain_id, webhook.escrow_address) - ) - - excor_bucket_host = Config.exchange_oracle_storage_config.provider_endpoint_url() - excor_bucket_name = Config.exchange_oracle_storage_config.results_bucket_name - - excor_annotation_meta_path = compose_bucket_filename( - webhook.escrow_address, - webhook.chain_id, - annotation.ANNOTATION_METAFILE_NAME, - ) - annotation_metafile_data = download_file( - excor_bucket_host, excor_bucket_name, excor_annotation_meta_path - ) - annotation_meta = parse_annotation_metafile(io.BytesIO(annotation_metafile_data)) - - job_annotations: Dict[int, bytes] = {} - for job_meta in annotation_meta.jobs: - job_filename = compose_bucket_filename( - webhook.escrow_address, - webhook.chain_id, - job_meta.annotation_filename, - ) - job_annotations[job_meta.job_id] = download_file( - excor_bucket_host, excor_bucket_name, job_filename - ) - - parsed_gt_bucket_url = parse_bucket_url(manifest.validation.gt_url) - gt_bucket_host = parsed_gt_bucket_url.host_url - gt_bucket_name = parsed_gt_bucket_url.bucket_name - gt_filename = parsed_gt_bucket_url.path - gt_file_data = download_file( - gt_bucket_host, - gt_bucket_name, - gt_filename, - ) - - excor_merged_annotation_path = compose_bucket_filename( - webhook.escrow_address, - webhook.chain_id, - annotation.RESULTING_ANNOTATIONS_FILE, - ) - merged_annotations = download_file( - excor_bucket_host, excor_bucket_name, excor_merged_annotation_path - ) - - validation_results = process_intermediate_results( - db_session, + validate_results( escrow_address=webhook.escrow_address, chain_id=webhook.chain_id, - meta=annotation_meta, - job_annotations={k: io.BytesIO(v) for k, v in job_annotations.items()}, - merged_annotations=io.BytesIO(merged_annotations), - gt_annotations=io.BytesIO(gt_file_data), - manifest=manifest, - logger=logger, + db_session=db_session, ) - if isinstance(validation_results, ValidationSuccess): - logger.info( - f"Validation for escrow_address={webhook.escrow_address} successful, " - f"average annotation quality is {validation_results.average_quality:.2f}" - ) - - recor_merged_annotations_path = compose_bucket_filename( - webhook.escrow_address, - webhook.chain_id, - validation.RESULTING_ANNOTATIONS_FILE, - ) - - recor_validation_meta_path = compose_bucket_filename( - webhook.escrow_address, - webhook.chain_id, - validation.VALIDATION_METAFILE_NAME, - ) - validation_metafile = serialize_validation_meta(validation_results.validation_meta) - - storage_client = cloud_client.S3Client( - Config.storage_config.provider_endpoint_url(), - access_key=Config.storage_config.access_key, - secret_key=Config.storage_config.secret_key, - ) - - # TODO: add encryption - storage_client.create_file( - Config.storage_config.results_bucket_name, - recor_merged_annotations_path, - validation_results.resulting_annotations, - ) - storage_client.create_file( - Config.storage_config.results_bucket_name, - recor_validation_meta_path, - validation_metafile, - ) - - escrow.store_results( - webhook.chain_id, - webhook.escrow_address, - Config.storage_config.bucket_url() - + os.path.dirname(recor_merged_annotations_path), - compute_resulting_annotations_hash(validation_results.resulting_annotations), - ) - - oracle_db_service.outbox.create_webhook( - db_session, - webhook.escrow_address, - webhook.chain_id, - OracleWebhookTypes.reputation_oracle, - event=RecordingOracleEvent_TaskCompleted(), - ) - oracle_db_service.outbox.create_webhook( - db_session, - webhook.escrow_address, - webhook.chain_id, - OracleWebhookTypes.exchange_oracle, - event=RecordingOracleEvent_TaskCompleted(), - ) - else: - logger.info( - f"Validation for escrow_address={webhook.escrow_address} failed, " - f"rejected {len(validation_results.rejected_job_ids)} jobs" - ) - - oracle_db_service.outbox.create_webhook( - db_session, - webhook.escrow_address, - webhook.chain_id, - OracleWebhookTypes.exchange_oracle, - event=RecordingOracleEvent_TaskRejected( - rejected_job_ids=validation_results.rejected_job_ids - ), - ) - case _: assert False, f"Unknown exchange oracle event {webhook.event_type}" diff --git a/packages/examples/cvat/recording-oracle/src/crons/process_reputation_oracle_webhooks.py b/packages/examples/cvat/recording-oracle/src/crons/process_reputation_oracle_webhooks.py index 5fb551ee3b..863adbef9a 100644 --- a/packages/examples/cvat/recording-oracle/src/crons/process_reputation_oracle_webhooks.py +++ b/packages/examples/cvat/recording-oracle/src/crons/process_reputation_oracle_webhooks.py @@ -48,17 +48,6 @@ def process_outgoing_reputation_oracle_webhooks(): timestamp=None, # TODO: reputation oracle doesn't support ) - # FIXME: For a sake of compatability with the current - # version of Reputation Oracle keep this - body["escrowAddress"] = body.pop("escrow_address") - body["chainId"] = body.pop("chain_id") - body["eventType"] = body.pop("event_type") - - # TODO: remove compatibility code - # vvv - body.pop("event_data") - # ^^^ - _, signature = prepare_signed_message( webhook.escrow_address, webhook.chain_id, diff --git a/packages/examples/cvat/recording-oracle/src/handlers/process_intermediate_results.py b/packages/examples/cvat/recording-oracle/src/handlers/process_intermediate_results.py index ad004673b7..f0eb281817 100644 --- a/packages/examples/cvat/recording-oracle/src/handlers/process_intermediate_results.py +++ b/packages/examples/cvat/recording-oracle/src/handlers/process_intermediate_results.py @@ -1,120 +1,916 @@ +from __future__ import annotations + import io import logging import os +from dataclasses import dataclass, field from pathlib import Path from tempfile import TemporaryDirectory -from typing import Dict, List, Type, Union +from typing import Dict, NamedTuple, Optional, Set, Type, TypeVar, Union import datumaro as dm import numpy as np -from attrs import define from sqlalchemy.orm import Session +import src.core.tasks.boxes_from_points as boxes_from_points_task +import src.core.tasks.skeletons_from_boxes as skeletons_from_boxes_task import src.services.validation as db_service from src.core.annotation_meta import AnnotationMeta +from src.core.config import Config from src.core.manifest import TaskManifest -from src.core.types import TaskType +from src.core.storage import compose_data_bucket_filename +from src.core.types import TaskTypes +from src.core.validation_errors import DatasetValidationError, LowAccuracyError from src.core.validation_meta import JobMeta, ResultMeta, ValidationMeta +from src.core.validation_results import ValidationFailure, ValidationSuccess +from src.db.utils import ForUpdateParams +from src.services.cloud import make_client as make_cloud_client +from src.services.cloud.utils import BucketAccessInfo +from src.utils.annotations import ProjectLabels, shift_ann from src.utils.zip_archive import extract_zip_archive, write_dir_to_zip_archive from src.validation.dataset_comparison import ( BboxDatasetComparator, DatasetComparator, PointsDatasetComparator, + SkeletonDatasetComparator, + TooFewGtError, ) - -@define -class ValidationSuccess: - validation_meta: ValidationMeta - resulting_annotations: bytes - average_quality: float - - -@define -class ValidationFailure: - rejected_job_ids: List[int] - - DM_DATASET_FORMAT_MAPPING = { - TaskType.image_label_binary: "cvat_images", - TaskType.image_points: "coco_person_keypoints", - TaskType.image_boxes: "coco_instances", + TaskTypes.image_label_binary: "cvat_images", + TaskTypes.image_points: "coco_person_keypoints", + TaskTypes.image_boxes: "coco_instances", + TaskTypes.image_boxes_from_points: "coco_instances", + TaskTypes.image_skeletons_from_boxes: "coco_person_keypoints", } DM_GT_DATASET_FORMAT_MAPPING = { - TaskType.image_label_binary: "cvat_images", - TaskType.image_points: "coco_instances", # we compare points against boxes - TaskType.image_boxes: "coco_instances", + TaskTypes.image_label_binary: "cvat_images", + TaskTypes.image_points: "coco_instances", # we compare points against boxes + TaskTypes.image_boxes: "coco_instances", + TaskTypes.image_boxes_from_points: "coco_instances", + TaskTypes.image_skeletons_from_boxes: "coco_person_keypoints", } -DATASET_COMPARATOR_TYPE_MAP: Dict[TaskType, Type[DatasetComparator]] = { +DATASET_COMPARATOR_TYPE_MAP: Dict[TaskTypes, Type[DatasetComparator]] = { # TaskType.image_label_binary: TagDatasetComparator, # TODO: implement if support is needed - TaskType.image_boxes: BboxDatasetComparator, - TaskType.image_points: PointsDatasetComparator, + TaskTypes.image_boxes: BboxDatasetComparator, + TaskTypes.image_points: PointsDatasetComparator, + TaskTypes.image_boxes_from_points: BboxDatasetComparator, + TaskTypes.image_skeletons_from_boxes: SkeletonDatasetComparator, } +_JobResults = Dict[int, float] + +_RejectedJobs = Dict[int, DatasetValidationError] + +_FailedGtAttempts = Dict[str, int] +"gt key -> attempts" + + +@dataclass +class _UpdatedFailedGtInfo: + failed_jobs: Set[int] = field(default_factory=set) + occurrences: int = 0 + + +_UpdatedFailedGtStats = Dict[str, _UpdatedFailedGtInfo] + + +@dataclass +class _ValidationResult: + job_results: _JobResults + rejected_jobs: _RejectedJobs + updated_merged_dataset: io.BytesIO + updated_gt_stats: _UpdatedFailedGtStats + + +T = TypeVar("T") -def process_intermediate_results( - session: Session, - *, - escrow_address: str, - chain_id: int, - meta: AnnotationMeta, - job_annotations: Dict[int, io.RawIOBase], - gt_annotations: io.RawIOBase, - merged_annotations: io.RawIOBase, - manifest: TaskManifest, - logger: logging.Logger, -) -> Union[ValidationSuccess, ValidationFailure]: - # validate - task_type = manifest.annotation.type - dataset_format = DM_DATASET_FORMAT_MAPPING[task_type] - job_results: Dict[int, float] = {} - rejected_job_ids: List[int] = [] +class _TaskValidator: + def __init__( + self, + escrow_address: str, + chain_id: int, + manifest: TaskManifest, + *, + job_annotations: Dict[int, io.IOBase], + merged_annotations: io.IOBase, + gt_stats: Optional[_FailedGtAttempts] = None, + ): + self.escrow_address = escrow_address + self.chain_id = chain_id + self.manifest = manifest - with TemporaryDirectory() as tempdir: - tempdir = Path(tempdir) + self._initial_gt_attempts: _FailedGtAttempts = gt_stats or {} + self._job_annotations: Dict[int, io.IOBase] = job_annotations + self._merged_annotations: io.IOBase = merged_annotations + + self._updated_merged_dataset_archive: Optional[io.IOBase] = None + self._updated_gt_stats: Optional[_UpdatedFailedGtStats] = None + self._job_results: Optional[_JobResults] = None + self._rejected_jobs: Optional[_RejectedJobs] = None + + self._temp_dir: Optional[Path] = None + self._gt_dataset: Optional[dm.Dataset] = None + + def _require_field(self, field: Optional[T]) -> T: + assert field is not None + return field + + def _get_gt_weight(self, failed_attempts: int) -> float: + ban_threshold = Config.validation.gt_ban_threshold + + weight = 1 + if ban_threshold < failed_attempts: + weight = 0 + + return weight + + def _get_gt_weights(self) -> Dict[str, float]: + weights = {} + + ban_threshold = Config.validation.gt_ban_threshold + if not ban_threshold: + return weights + + for gt_key, attempts in self._initial_gt_attempts.items(): + sample_id = self._gt_key_to_sample_id(gt_key) + weights[sample_id] = self._get_gt_weight(attempts) + + return weights + + def _gt_key_to_sample_id(self, gt_key: str) -> str: + return gt_key + + def _parse_gt(self): + tempdir = self._require_field(self._temp_dir) + manifest = self._require_field(self.manifest) + + bucket_info = BucketAccessInfo.parse_obj(self.manifest.validation.gt_url) + bucket_client = make_cloud_client(bucket_info) + + gt_annotations = io.BytesIO(bucket_client.download_file(bucket_info.path)) gt_dataset_path = tempdir / "gt.json" gt_dataset_path.write_bytes(gt_annotations.read()) - gt_dataset = dm.Dataset.import_from( - os.fspath(gt_dataset_path), format=DM_GT_DATASET_FORMAT_MAPPING[task_type] + self._gt_dataset = dm.Dataset.import_from( + os.fspath(gt_dataset_path), + format=DM_GT_DATASET_FORMAT_MAPPING[manifest.annotation.type], ) - comparator = DATASET_COMPARATOR_TYPE_MAP[task_type]( - min_similarity_threshold=manifest.validation.min_quality + def _load_job_dataset(self, job_id: int, job_dataset_path: Path) -> dm.Dataset: + manifest = self._require_field(self.manifest) + + return dm.Dataset.import_from( + os.fspath(job_dataset_path), + format=DM_DATASET_FORMAT_MAPPING[manifest.annotation.type], + ) + + def _validate_jobs(self): + tempdir = self._require_field(self._temp_dir) + manifest = self._require_field(self.manifest) + gt_dataset = self._require_field(self._gt_dataset) + job_annotations = self._require_field(self._job_annotations) + + job_results: _JobResults = {} + rejected_jobs: _RejectedJobs = {} + updated_gt_stats: _UpdatedFailedGtStats = {} + + comparator = DATASET_COMPARATOR_TYPE_MAP[manifest.annotation.type]( + min_similarity_threshold=manifest.validation.min_quality, + gt_weights=self._get_gt_weights(), ) for job_cvat_id, job_annotations_file in job_annotations.items(): job_dataset_path = tempdir / str(job_cvat_id) extract_zip_archive(job_annotations_file, job_dataset_path) - job_dataset = dm.Dataset.import_from(os.fspath(job_dataset_path), format=dataset_format) + job_dataset = self._load_job_dataset(job_cvat_id, job_dataset_path) + + try: + job_mean_accuracy = comparator.compare(gt_dataset, job_dataset) + except TooFewGtError as e: + rejected_jobs[job_cvat_id] = e + continue - job_mean_accuracy = comparator.compare(gt_dataset, job_dataset) job_results[job_cvat_id] = job_mean_accuracy + for gt_sample in gt_dataset: + updated_gt_stats.setdefault(gt_sample.id, _UpdatedFailedGtInfo()).occurrences += 1 + + for sample_id in comparator.failed_gts: + updated_gt_stats[sample_id].failed_jobs.add(job_cvat_id) + if job_mean_accuracy < manifest.validation.min_quality: - rejected_job_ids.append(job_cvat_id) + rejected_jobs[job_cvat_id] = LowAccuracyError() + + self._updated_gt_stats = updated_gt_stats + self._job_results = job_results + self._rejected_jobs = rejected_jobs + + def _prepare_merged_dataset(self): + tempdir = self._require_field(self._temp_dir) + manifest = self._require_field(self.manifest) + merged_annotations = self._require_field(self._merged_annotations) + gt_dataset = self._require_field(self._gt_dataset) - merged_dataset_path = tempdir / str("merged") - merged_dataset_format = DM_DATASET_FORMAT_MAPPING[task_type] + merged_dataset_path = tempdir / "merged" + merged_dataset_format = DM_DATASET_FORMAT_MAPPING[manifest.annotation.type] extract_zip_archive(merged_annotations, merged_dataset_path) merged_dataset = dm.Dataset.import_from( os.fspath(merged_dataset_path), format=merged_dataset_format ) - put_gt_into_merged_dataset(gt_dataset, merged_dataset, manifest=manifest) + self._put_gt_into_merged_dataset(gt_dataset, merged_dataset, manifest=manifest) - updated_merged_dataset_path = tempdir / str("merged_updated") + updated_merged_dataset_path = tempdir / "merged_updated" merged_dataset.export(updated_merged_dataset_path, merged_dataset_format, save_media=False) updated_merged_dataset_archive = io.BytesIO() write_dir_to_zip_archive(updated_merged_dataset_path, updated_merged_dataset_archive) updated_merged_dataset_archive.seek(0) + self._updated_merged_dataset_archive = updated_merged_dataset_archive + + @classmethod + def _put_gt_into_merged_dataset( + cls, gt_dataset: dm.Dataset, merged_dataset: dm.Dataset, *, manifest: TaskManifest + ): + """ + Updates the merged dataset inplace, writing GT annotations corresponding to the task type. + """ + + match manifest.annotation.type: + case TaskTypes.image_boxes.value: + merged_dataset.update(gt_dataset) + case TaskTypes.image_points.value: + for sample in gt_dataset: + annotations = [ + # Put a point in the center of each GT bbox + # Not ideal, but it's the target for now + dm.Points( + [bbox.x + bbox.w / 2, bbox.y + bbox.h / 2], + label=bbox.label, + attributes=bbox.attributes, + ) + for bbox in sample.annotations + if isinstance(bbox, dm.Bbox) + ] + merged_dataset.put(sample.wrap(annotations=annotations)) + case TaskTypes.image_label_binary.value: + merged_dataset.update(gt_dataset) + case TaskTypes.image_boxes_from_points: + merged_dataset.update(gt_dataset) + case TaskTypes.image_skeletons_from_boxes: + # The original behavior is broken for skeletons + gt_dataset = dm.Dataset(gt_dataset) + gt_dataset = gt_dataset.transform( + ProjectLabels, dst_labels=merged_dataset.categories()[dm.AnnotationType.label] + ) + merged_dataset.update(gt_dataset) + case _: + assert False, f"Unknown task type {manifest.annotation.type}" + + def validate(self) -> _ValidationResult: + with TemporaryDirectory() as tempdir: + self._temp_dir = Path(tempdir) + + self._parse_gt() + self._validate_jobs() + self._prepare_merged_dataset() + + return _ValidationResult( + job_results=self._require_field(self._job_results), + rejected_jobs=self._require_field(self._rejected_jobs), + updated_merged_dataset=self._require_field(self._updated_merged_dataset_archive), + updated_gt_stats=self._require_field(self._updated_gt_stats), + ) + + +class _TaskValidatorWithPerJobGt(_TaskValidator): + def _make_gt_dataset_for_job(self, job_id: int, job_dataset: dm.Dataset) -> dm.Dataset: + raise NotImplementedError + + def _get_gt_weights(self, *, job_cvat_id: int, job_gt_dataset: dm.Dataset) -> Dict[str, float]: + weights = {} + + ban_threshold = Config.validation.gt_ban_threshold + if not ban_threshold: + return weights + + for gt_key, attempts in self._initial_gt_attempts.items(): + sample_id = self._gt_key_to_sample_id( + gt_key, job_cvat_id=job_cvat_id, job_gt_dataset=job_gt_dataset + ) + if not sample_id: + continue + + weights[sample_id] = self._get_gt_weight(attempts) + + return weights + + def _gt_key_to_sample_id( + self, gt_key: str, *, job_cvat_id: int, job_gt_dataset: dm.Dataset + ) -> Optional[str]: + return gt_key + + def _update_gt_stats( + self, + updated_gt_stats: _UpdatedFailedGtStats, + *, + job_cvat_id: int, + job_gt_dataset: dm.Dataset, + failed_gts: set[str], + ): + for gt_sample in job_gt_dataset: + updated_gt_stats.setdefault(gt_sample.id, _UpdatedFailedGtInfo()).occurrences += 1 + + for sample_id in failed_gts: + updated_gt_stats[sample_id].failed_jobs.add(job_cvat_id) + + return updated_gt_stats + + def _validate_jobs(self): + tempdir = self._require_field(self._temp_dir) + manifest = self._require_field(self.manifest) + job_annotations = self._require_field(self._job_annotations) + + job_results: _JobResults = {} + rejected_jobs: _RejectedJobs = {} + updated_gt_stats: _UpdatedFailedGtStats = {} + + for job_cvat_id, job_annotations_file in job_annotations.items(): + job_dataset_path = tempdir / str(job_cvat_id) + extract_zip_archive(job_annotations_file, job_dataset_path) + + job_dataset = self._load_job_dataset(job_cvat_id, job_dataset_path) + job_gt_dataset = self._make_gt_dataset_for_job(job_cvat_id, job_dataset) + + comparator = DATASET_COMPARATOR_TYPE_MAP[manifest.annotation.type]( + min_similarity_threshold=manifest.validation.min_quality, + gt_weights=self._get_gt_weights( + job_cvat_id=job_cvat_id, job_gt_dataset=job_gt_dataset + ), + ) + + try: + job_mean_accuracy = comparator.compare(job_gt_dataset, job_dataset) + except TooFewGtError as e: + rejected_jobs[job_cvat_id] = e + continue + + job_results[job_cvat_id] = job_mean_accuracy + + updated_gt_stats = self._update_gt_stats( + updated_gt_stats, + job_cvat_id=job_cvat_id, + job_gt_dataset=job_gt_dataset, + failed_gts=comparator.failed_gts, + ) + + if job_mean_accuracy < manifest.validation.min_quality: + rejected_jobs[job_cvat_id] = LowAccuracyError() + + self._updated_gt_stats = updated_gt_stats + self._job_results = job_results + self._rejected_jobs = rejected_jobs + + +class _BoxesFromPointsValidator(_TaskValidatorWithPerJobGt): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + ( + boxes_to_points_mapping, + roi_filenames, + roi_infos, + gt_dataset, + points_dataset, + ) = self._download_task_meta() + + self._gt_dataset = gt_dataset + self._points_dataset = points_dataset + + point_key_to_sample = { + skeleton.id: sample + for sample in points_dataset + for skeleton in sample.annotations + if isinstance(skeleton, dm.Skeleton) + } + + self.bbox_key_to_sample = { + bbox.id: sample + for sample in gt_dataset + for bbox in sample.annotations + if isinstance(bbox, dm.Bbox) + } + + self._point_key_to_bbox_key = {v: k for k, v in boxes_to_points_mapping.items()} + self._roi_info_by_id = {roi_info.point_id: roi_info for roi_info in roi_infos} + self._roi_name_to_roi_info: Dict[str, boxes_from_points_task.RoiInfo] = { + os.path.splitext(roi_filename)[0]: self._roi_info_by_id[roi_id] + for roi_id, roi_filename in roi_filenames.items() + } + + self._point_offset_by_roi_id = {} + "Offset from new to old coords, (dx, dy)" + + for roi_info in roi_infos: + point_sample = point_key_to_sample[roi_info.point_id] + old_point = next( + skeleton + for skeleton in point_sample.annotations + if skeleton.id == roi_info.point_id + if isinstance(skeleton, dm.Skeleton) + ).elements[0] + old_x, old_y = old_point.points[:2] + offset_x = old_x - roi_info.point_x + offset_y = old_y - roi_info.point_y + self._point_offset_by_roi_id[roi_info.point_id] = (offset_x, offset_y) + + def _parse_gt(self): + pass # handled by _download_task_meta() + + def _download_task_meta(self): + layout = boxes_from_points_task.TaskMetaLayout() + serializer = boxes_from_points_task.TaskMetaSerializer() + + oracle_data_bucket = BucketAccessInfo.parse_obj(Config.exchange_oracle_storage_config) + storage_client = make_cloud_client(oracle_data_bucket) + + boxes_to_points_mapping = serializer.parse_bbox_point_mapping( + storage_client.download_file( + compose_data_bucket_filename( + self.escrow_address, self.chain_id, layout.BBOX_POINT_MAPPING_FILENAME + ), + ) + ) + + roi_filenames = serializer.parse_roi_filenames( + storage_client.download_file( + compose_data_bucket_filename( + self.escrow_address, self.chain_id, layout.ROI_FILENAMES_FILENAME + ), + ) + ) + + rois = serializer.parse_roi_info( + storage_client.download_file( + compose_data_bucket_filename( + self.escrow_address, self.chain_id, layout.ROI_INFO_FILENAME + ), + ) + ) + + gt_dataset = serializer.parse_gt_annotations( + storage_client.download_file( + compose_data_bucket_filename( + self.escrow_address, self.chain_id, layout.GT_FILENAME + ), + ) + ) + + points_dataset = serializer.parse_points_annotations( + storage_client.download_file( + compose_data_bucket_filename( + self.escrow_address, self.chain_id, layout.POINTS_FILENAME + ), + ) + ) + + return boxes_to_points_mapping, roi_filenames, rois, gt_dataset, points_dataset + + def _make_gt_dataset_for_job(self, job_id: int, job_dataset: dm.Dataset) -> dm.Dataset: + job_gt_dataset = dm.Dataset(categories=self._gt_dataset.categories(), media_type=dm.Image) + + for job_sample in job_dataset: + roi_info = self._roi_name_to_roi_info[os.path.basename(job_sample.id)] + + point_bbox_key = self._point_key_to_bbox_key.get(roi_info.point_id, None) + if point_bbox_key is None: + continue # roi is not from GT set + + bbox_sample = self.bbox_key_to_sample[point_bbox_key] + + bbox = next(bbox for bbox in bbox_sample.annotations if bbox.id == point_bbox_key) + roi_shift_x, roi_shift_y = self._point_offset_by_roi_id[roi_info.point_id] + + bbox_in_roi_coords = shift_ann( + bbox, + offset_x=-roi_shift_x, + offset_y=-roi_shift_y, + img_w=roi_info.roi_w, + img_h=roi_info.roi_h, + ) + + job_gt_dataset.put(job_sample.wrap(annotations=[bbox_in_roi_coords])) + + return job_gt_dataset + + +class _SkeletonsFromBoxesValidator(_TaskValidatorWithPerJobGt): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + ( + roi_filenames, + roi_infos, + boxes_dataset, + job_label_mapping, + gt_dataset, + skeletons_to_boxes_mapping, + ) = self._download_task_meta() + + self._boxes_dataset = boxes_dataset + self._original_key_to_sample = {sample.attributes["id"]: sample for sample in boxes_dataset} + + self._job_label_mapping = job_label_mapping + + self._gt_dataset = gt_dataset + + self._bbox_key_to_sample = { + bbox.id: sample + for sample in boxes_dataset + for bbox in sample.annotations + if isinstance(bbox, dm.Bbox) + } + + self._skeleton_key_to_sample = { + skeleton.id: sample + for sample in gt_dataset + for skeleton in sample.annotations + if isinstance(skeleton, dm.Skeleton) + } + + self._bbox_key_to_skeleton_key = {v: k for k, v in skeletons_to_boxes_mapping.items()} + self._roi_info_by_id = {roi_info.bbox_id: roi_info for roi_info in roi_infos} + self._roi_name_to_roi_info: Dict[str, skeletons_from_boxes_task.RoiInfo] = { + os.path.splitext(roi_filename)[0]: self._roi_info_by_id[roi_id] + for roi_id, roi_filename in roi_filenames.items() + } + + self._bbox_offset_by_roi_id = {} + "Offset from old to new coords, (dx, dy)" + + for roi_info in roi_infos: + bbox_sample = self._bbox_key_to_sample[roi_info.bbox_id] + + old_bbox = next( + bbox + for bbox in bbox_sample.annotations + if bbox.id == roi_info.bbox_id + if isinstance(bbox, dm.Bbox) + ) + offset_x = roi_info.bbox_x - old_bbox.x + offset_y = roi_info.bbox_y - old_bbox.y + + self._bbox_offset_by_roi_id[roi_info.bbox_id] = (offset_x, offset_y) + + def _parse_gt(self): + pass # handled by _download_task_meta() + + def _download_task_meta(self): + layout = skeletons_from_boxes_task.TaskMetaLayout() + serializer = skeletons_from_boxes_task.TaskMetaSerializer() + + oracle_data_bucket = BucketAccessInfo.parse_obj(Config.exchange_oracle_storage_config) + storage_client = make_cloud_client(oracle_data_bucket) + + roi_filenames = serializer.parse_roi_filenames( + storage_client.download_file( + compose_data_bucket_filename( + self.escrow_address, self.chain_id, layout.ROI_FILENAMES_FILENAME + ), + ) + ) + + rois = serializer.parse_roi_info( + storage_client.download_file( + compose_data_bucket_filename( + self.escrow_address, self.chain_id, layout.ROI_INFO_FILENAME + ), + ) + ) + + boxes_dataset = serializer.parse_bbox_annotations( + storage_client.download_file( + compose_data_bucket_filename( + self.escrow_address, self.chain_id, layout.BOXES_FILENAME + ), + ) + ) + + job_label_mapping = serializer.parse_point_labels( + storage_client.download_file( + compose_data_bucket_filename( + self.escrow_address, self.chain_id, layout.POINT_LABELS_FILENAME + ), + ) + ) + + gt_dataset = serializer.parse_gt_annotations( + storage_client.download_file( + compose_data_bucket_filename( + self.escrow_address, self.chain_id, layout.GT_FILENAME + ), + ) + ) + + skeletons_to_boxes_mapping = serializer.parse_skeleton_bbox_mapping( + storage_client.download_file( + compose_data_bucket_filename( + self.escrow_address, self.chain_id, layout.SKELETON_BBOX_MAPPING_FILENAME + ), + ) + ) + + return ( + roi_filenames, + rois, + boxes_dataset, + job_label_mapping, + gt_dataset, + skeletons_to_boxes_mapping, + ) + + def _load_job_dataset(self, job_id: int, job_dataset_path: Path) -> dm.Dataset: + job_dataset = super()._load_job_dataset(job_id=job_id, job_dataset_path=job_dataset_path) + + cat = job_dataset.categories() + updated_dataset = dm.Dataset(categories=cat, media_type=job_dataset.media_type()) + + job_label_cat: dm.LabelCategories = cat[dm.AnnotationType.label] + assert len(job_label_cat) == 2 + job_skeleton_label_id = next(i for i, c in enumerate(job_label_cat) if not c.parent) + job_point_label_id = next(i for i, c in enumerate(job_label_cat) if c.parent) + + for job_sample in job_dataset: + updated_annotations = job_sample.annotations.copy() + + if not job_sample.annotations: + skeleton = dm.Skeleton( + label=job_skeleton_label_id, + elements=[ + dm.Points( + [0, 0], + visibility=[dm.Points.Visibility.absent], + label=job_point_label_id, + ) + ], + ) + + roi_info = self._roi_name_to_roi_info[os.path.basename(job_sample.id)] + bbox_sample = self._bbox_key_to_sample[roi_info.bbox_id] + bbox = next( + bbox + for bbox in bbox_sample.annotations + if bbox.id == roi_info.bbox_id + if isinstance(bbox, dm.Bbox) + ) + + roi_shift_x, roi_shift_y = self._bbox_offset_by_roi_id[roi_info.bbox_id] + converted_bbox = shift_ann( + bbox, + offset_x=roi_shift_x, + offset_y=roi_shift_y, + img_w=roi_info.roi_w, + img_h=roi_info.roi_h, + ) + + skeleton.group = 1 + converted_bbox.group = skeleton.group + + updated_annotations = [skeleton, converted_bbox] + + updated_dataset.put(job_sample.wrap(annotations=updated_annotations)) + + return updated_dataset + + def _make_gt_dataset_for_job(self, job_id: int, job_dataset: dm.Dataset) -> dm.Dataset: + job_label_cat: dm.LabelCategories = job_dataset.categories()[dm.AnnotationType.label] + assert len(job_label_cat) == 2 + job_skeleton_label_id, job_skeleton_label = next( + (i, c) for i, c in enumerate(job_label_cat) if not c.parent + ) + job_point_label_id, job_point_label = next( + (i, c) for i, c in enumerate(job_label_cat) if c.parent + ) + + gt_label_cat = self._gt_dataset.categories()[dm.AnnotationType.label] + gt_point_label_id = gt_label_cat.find(job_point_label.name, parent=job_skeleton_label.name)[ + 0 + ] + + job_gt_dataset = dm.Dataset(categories=job_dataset.categories(), media_type=dm.Image) + for job_sample in job_dataset: + roi_info = self._roi_name_to_roi_info[os.path.basename(job_sample.id)] + + gt_skeleton_key = self._bbox_key_to_skeleton_key.get(roi_info.bbox_id, None) + if gt_skeleton_key is None: + continue # roi is not from GT set + + bbox_sample = self._bbox_key_to_sample[roi_info.bbox_id] + bbox = next( + bbox + for bbox in bbox_sample.annotations + if bbox.id == roi_info.bbox_id + if isinstance(bbox, dm.Bbox) + ) + + gt_sample = self._skeleton_key_to_sample[gt_skeleton_key] + gt_skeleton = next( + skeleton + for skeleton in gt_sample.annotations + if skeleton.id == gt_skeleton_key + if isinstance(skeleton, dm.Skeleton) + ) + + roi_shift_x, roi_shift_y = self._bbox_offset_by_roi_id[roi_info.bbox_id] + converted_gt_skeleton = shift_ann( + gt_skeleton, + offset_x=roi_shift_x, + offset_y=roi_shift_y, + img_w=roi_info.roi_w, + img_h=roi_info.roi_h, + ) + converted_bbox = shift_ann( + bbox, + offset_x=roi_shift_x, + offset_y=roi_shift_y, + img_w=roi_info.roi_w, + img_h=roi_info.roi_h, + ) + + # Join annotations into a group for correct distance comparison + skeleton_group = 1 + converted_bbox.group = skeleton_group + converted_gt_skeleton.group = skeleton_group + + # Convert labels + converted_gt_skeleton.label = job_skeleton_label_id + converted_gt_skeleton.elements = [ + p.wrap(label=job_point_label_id) + for p in converted_gt_skeleton.elements + if p.label == gt_point_label_id + ] + + job_gt_dataset.put(job_sample.wrap(annotations=[converted_gt_skeleton, converted_bbox])) + + return job_gt_dataset + + @dataclass + class _GtKey: + sample_id: str + skeleton_id: int + point_id: int + + _GT_KEY_SEPARATOR = ":" + + def _parse_gt_key(self, raw_gt_key: str) -> _GtKey: + # Assume "sample_id:skeleton_id:point_id" + sample_id, skeleton_id, point_id = raw_gt_key.rsplit(self._GT_KEY_SEPARATOR, maxsplit=2) + + return self._GtKey( + sample_id=sample_id, skeleton_id=int(skeleton_id), point_id=int(point_id) + ) + + def _serialize_gt_key(self, parsed_gt_key: _GtKey) -> str: + # Assume "sample_id:skeleton_id:point_id" + return self._GT_KEY_SEPARATOR.join( + [parsed_gt_key.sample_id, str(parsed_gt_key.skeleton_id), str(parsed_gt_key.point_id)] + ) + + class _LabelId(NamedTuple): + skeleton_id: int + point_id: int + + def _get_gt_dataset_label_id(self, job_gt_dataset: dm.Dataset) -> _LabelId: + label_cat: dm.LabelCategories = job_gt_dataset.categories()[dm.AnnotationType.label] + assert len(label_cat) == 2 + job_skeleton_label = next(l for l in label_cat if not l.parent) + job_point_label = next(l for l in label_cat if l.parent) + + return self._LabelId( + *next( + (skeleton_id, point_id) + for skeleton_id, skeleton_label in enumerate(self.manifest.annotation.labels) + for point_id, point_name in enumerate(skeleton_label.nodes) + if skeleton_label.name == job_skeleton_label.name + if point_name == job_point_label.name + ) + ) + + def _gt_key_to_sample_id( + self, gt_key: str, *, job_cvat_id: int, job_gt_dataset: dm.Dataset + ) -> Optional[str]: + parsed_gt_key = self._parse_gt_key(gt_key) + job_label_id = self._get_gt_dataset_label_id(job_gt_dataset) + if (parsed_gt_key.skeleton_id, parsed_gt_key.point_id) != job_label_id: + return None + + return parsed_gt_key.sample_id + + def _update_gt_stats( + self, + updated_gt_stats: _UpdatedFailedGtStats, + *, + job_cvat_id: int, + job_gt_dataset: dm.Dataset, + failed_gts: set[str], + ): + job_label_id = self._get_gt_dataset_label_id(job_gt_dataset) + + for gt_sample in job_gt_dataset: + raw_gt_key = self._serialize_gt_key( + self._GtKey( + sample_id=gt_sample.id, + skeleton_id=job_label_id.skeleton_id, + point_id=job_label_id.point_id, + ) + ) + updated_gt_stats.setdefault(raw_gt_key, _UpdatedFailedGtInfo()).occurrences += 1 + + for gt_sample_id in failed_gts: + raw_gt_key = self._serialize_gt_key( + self._GtKey( + sample_id=gt_sample_id, + skeleton_id=job_label_id.skeleton_id, + point_id=job_label_id.point_id, + ) + ) + updated_gt_stats[raw_gt_key].failed_jobs.add(job_cvat_id) + + return updated_gt_stats + + +def _compute_gt_stats_update( + initial_gt_stats: _FailedGtAttempts, validation_gt_stats: _UpdatedFailedGtStats +) -> _FailedGtAttempts: + updated_gt_stats = initial_gt_stats.copy() + + for gt_key, gt_info in validation_gt_stats.items(): + if gt_info.occurrences * Config.validation.gt_failure_threshold < len(gt_info.failed_jobs): + updated_gt_stats[gt_key] = updated_gt_stats.get(gt_key, 0) + 1 + + return updated_gt_stats + + +def process_intermediate_results( + session: Session, + *, + escrow_address: str, + chain_id: int, + meta: AnnotationMeta, + job_annotations: Dict[int, io.RawIOBase], + merged_annotations: io.RawIOBase, + manifest: TaskManifest, + logger: logging.Logger, +) -> Union[ValidationSuccess, ValidationFailure]: + # actually validate jobs + + task_type = manifest.annotation.type + if task_type in [TaskTypes.image_label_binary, TaskTypes.image_boxes, TaskTypes.image_points]: + validator_type = _TaskValidator + elif task_type == TaskTypes.image_boxes_from_points: + validator_type = _BoxesFromPointsValidator + elif task_type == TaskTypes.image_skeletons_from_boxes: + validator_type = _SkeletonsFromBoxesValidator + else: + raise Exception(f"Unknown task type {task_type}") + + task = db_service.get_task_by_escrow_address( + session, + escrow_address, + for_update=ForUpdateParams( + nowait=True + ), # should not happen, but waiting should not block processing + ) + if not task: + task_id = db_service.create_task(session, escrow_address=escrow_address, chain_id=chain_id) + task = db_service.get_task_by_id(session, task_id, for_update=True) + + initial_gt_stats = { + gt_image_stat.gt_key: gt_image_stat.failed_attempts + for gt_image_stat in db_service.get_task_gt_stats(session, task.id) + } + + validator = validator_type( + escrow_address=escrow_address, + chain_id=chain_id, + manifest=manifest, + job_annotations=job_annotations, + merged_annotations=merged_annotations, + gt_stats=initial_gt_stats, + ) + + validation_result = validator.validate() + job_results = validation_result.job_results + rejected_jobs = validation_result.rejected_jobs + updated_merged_dataset_archive = validation_result.updated_merged_dataset + if logger.isEnabledFor(logging.DEBUG): logger.debug( "Task validation results for escrow_address=%s: %s", @@ -122,10 +918,11 @@ def process_intermediate_results( ", ".join(f"{k}: {v:.2f}" for k, v in job_results.items()), ) - task = db_service.get_task_by_escrow_address(session, escrow_address, for_update=True) - if not task: - task_id = db_service.create_task(session, escrow_address=escrow_address, chain_id=chain_id) - task = db_service.get_task_by_id(session, task_id, for_update=True) + if validation_result.updated_gt_stats: + updated_gt_stats = _compute_gt_stats_update( + initial_gt_stats, validation_result.updated_gt_stats + ) + db_service.update_gt_stats(session, task.id, updated_gt_stats) job_final_result_ids: Dict[int, str] = {} for job_meta in meta.jobs: @@ -134,11 +931,11 @@ def process_intermediate_results( job_id = db_service.create_job(session, task_id=task.id, job_cvat_id=job_meta.job_id) job = db_service.get_job_by_id(session, job_id) - validation_result = db_service.get_validation_result_by_assignment_id( + assignment_validation_result = db_service.get_validation_result_by_assignment_id( session, job_meta.assignment_id ) - if not validation_result: - validation_result_id = db_service.create_validation_result( + if not assignment_validation_result: + assignment_validation_result_id = db_service.create_validation_result( session, job_id=job.id, annotator_wallet_address=job_meta.annotator_wallet_address, @@ -146,14 +943,35 @@ def process_intermediate_results( assignment_id=job_meta.assignment_id, ) else: - validation_result_id = validation_result.id - - job_final_result_ids[job.id] = validation_result_id + assignment_validation_result_id = assignment_validation_result.id - if rejected_job_ids: - return ValidationFailure(rejected_job_ids) + job_final_result_ids[job.id] = assignment_validation_result_id task_jobs = task.jobs + + should_complete = False + total_jobs = len(task_jobs) + unverifiable_jobs_count = len( + [v for v in rejected_jobs.values() if isinstance(v, TooFewGtError)] + ) + if total_jobs * Config.validation.unverifiable_assignments_threshold < unverifiable_jobs_count: + logger.info( + "Validation for escrow_address={}: " + "too many assignments have insufficient GT for validation ({} of {} ({:.2f}%)), " + "stopping annotation".format( + escrow_address, + unverifiable_jobs_count, + total_jobs, + unverifiable_jobs_count / total_jobs * 100, + ) + ) + should_complete = True + elif not rejected_jobs: + should_complete = True + + if not should_complete: + return ValidationFailure(rejected_jobs) + task_validation_results = db_service.get_task_validation_results(session, task.id) job_id_to_meta_id = {job.id: i for i, job in enumerate(task_jobs)} @@ -186,36 +1004,6 @@ def process_intermediate_results( ) -def put_gt_into_merged_dataset( - gt_dataset: dm.Dataset, merged_dataset: dm.Dataset, *, manifest: TaskManifest -): - """ - Updates the merged dataset inplace, writing GT annotations corresponding to the task type. - """ - - match manifest.annotation.type: - case TaskType.image_boxes.value: - merged_dataset.update(gt_dataset) - case TaskType.image_points.value: - for sample in gt_dataset: - annotations = [ - # Put a point in the center of each GT bbox - # Not ideal, but it's the target for now - dm.Points( - [bbox.x + bbox.w / 2, bbox.y + bbox.h / 2], - label=bbox.label, - attributes=bbox.attributes, - ) - for bbox in sample.annotations - if isinstance(bbox, dm.Bbox) - ] - merged_dataset.put(sample.wrap(annotations=annotations)) - case TaskType.image_label_binary.value: - merged_dataset.update(gt_dataset) - case _: - assert False, f"Unknown task type {manifest.annotation.type}" - - def parse_annotation_metafile(metafile: io.RawIOBase) -> AnnotationMeta: return AnnotationMeta.parse_raw(metafile.read()) diff --git a/packages/examples/cvat/recording-oracle/src/handlers/validation.py b/packages/examples/cvat/recording-oracle/src/handlers/validation.py new file mode 100644 index 0000000000..b46c3ff13e --- /dev/null +++ b/packages/examples/cvat/recording-oracle/src/handlers/validation.py @@ -0,0 +1,224 @@ +import io +import os +from collections import Counter +from logging import Logger +from typing import Dict, Optional, Union + +from sqlalchemy.orm import Session + +import src.chain.escrow as escrow +import src.core.annotation_meta as annotation +import src.core.validation_meta as validation +import src.services.webhook as oracle_db_service +from src.core.config import Config +from src.core.manifest import TaskManifest +from src.core.oracle_events import ( + RecordingOracleEvent_TaskCompleted, + RecordingOracleEvent_TaskRejected, +) +from src.core.storage import ( + compose_results_bucket_filename as compose_annotation_results_bucket_filename, +) +from src.core.types import OracleWebhookTypes +from src.core.validation_errors import TooFewGtError +from src.core.validation_results import ValidationFailure, ValidationSuccess +from src.handlers.process_intermediate_results import ( + parse_annotation_metafile, + process_intermediate_results, + serialize_validation_meta, +) +from src.log import ROOT_LOGGER_NAME +from src.services.cloud import make_client as make_cloud_client +from src.services.cloud.utils import BucketAccessInfo +from src.core.manifest import parse_manifest +from src.utils.assignments import compute_resulting_annotations_hash +from src.utils.logging import NullLogger, get_function_logger + +module_logger_name = f"{ROOT_LOGGER_NAME}.cron.webhook" + + +class _TaskValidator: + def __init__( + self, escrow_address: str, chain_id: int, manifest: TaskManifest, db_session: Session + ) -> None: + self.escrow_address = escrow_address + self.chain_id = chain_id + self.manifest = manifest + self.db_session = db_session + self.logger: Logger = NullLogger() + + self.data_bucket = BucketAccessInfo.parse_obj(Config.exchange_oracle_storage_config) + + self.annotation_meta: Optional[annotation.AnnotationMeta] = None + self.job_annotations: Optional[Dict[int, bytes]] = None + self.merged_annotations: Optional[bytes] = None + + def set_logger(self, logger: Logger): + self.logger = logger + + def _download_results_meta(self): + data_bucket_client = make_cloud_client(self.data_bucket) + + annotation_meta_path = compose_annotation_results_bucket_filename( + self.escrow_address, + self.chain_id, + annotation.ANNOTATION_RESULTS_METAFILE_NAME, + ) + annotation_metafile_data = data_bucket_client.download_file(annotation_meta_path) + self.annotation_meta = parse_annotation_metafile(io.BytesIO(annotation_metafile_data)) + + def _download_annotations(self): + assert self.annotation_meta is not None + + data_bucket_client = make_cloud_client(self.data_bucket) + + job_annotations = {} + for job_meta in self.annotation_meta.jobs: + job_filename = compose_annotation_results_bucket_filename( + self.escrow_address, + self.chain_id, + job_meta.annotation_filename, + ) + job_annotations[job_meta.job_id] = data_bucket_client.download_file(job_filename) + + excor_merged_annotation_path = compose_annotation_results_bucket_filename( + self.escrow_address, + self.chain_id, + annotation.RESULTING_ANNOTATIONS_FILE, + ) + merged_annotations = data_bucket_client.download_file(excor_merged_annotation_path) + + self.job_annotations = job_annotations + self.merged_annotations = merged_annotations + + def _download_results(self): + self._download_results_meta() + self._download_annotations() + + ValidationResult = Union[ValidationSuccess, ValidationFailure] + + def _process_annotation_results(self) -> ValidationResult: + assert self.annotation_meta is not None + assert self.job_annotations is not None + assert self.merged_annotations is not None + + # TODO: refactor further + return process_intermediate_results( + session=self.db_session, + escrow_address=self.escrow_address, + chain_id=self.chain_id, + meta=self.annotation_meta, + job_annotations={k: io.BytesIO(v) for k, v in self.job_annotations.items()}, + merged_annotations=io.BytesIO(self.merged_annotations), + manifest=self.manifest, + logger=self.logger, + ) + + def validate(self): + self._download_results() + + validation_result = self._process_annotation_results() + + self._handle_validation_result(validation_result) + + def _compose_validation_results_bucket_filename(self, filename: str) -> str: + return f"{self.escrow_address}@{self.chain_id}/{filename}" + + def _handle_validation_result(self, validation_result: ValidationResult): + logger = self.logger + escrow_address = self.escrow_address + chain_id = self.chain_id + db_session = self.db_session + + if isinstance(validation_result, ValidationSuccess): + logger.info( + f"Validation for escrow_address={escrow_address}: successful, " + f"average annotation quality is {validation_result.average_quality:.2f}" + ) + + recor_merged_annotations_path = self._compose_validation_results_bucket_filename( + validation.RESULTING_ANNOTATIONS_FILE, + ) + + recor_validation_meta_path = self._compose_validation_results_bucket_filename( + validation.VALIDATION_METAFILE_NAME, + ) + validation_metafile = serialize_validation_meta(validation_result.validation_meta) + + storage_client = make_cloud_client(BucketAccessInfo.parse_obj(Config.storage_config)) + + # TODO: add encryption + storage_client.create_file( + recor_merged_annotations_path, + validation_result.resulting_annotations, + ) + storage_client.create_file( + recor_validation_meta_path, + validation_metafile, + ) + + escrow.store_results( + chain_id, + escrow_address, + Config.storage_config.bucket_url() + os.path.dirname(recor_merged_annotations_path), + compute_resulting_annotations_hash(validation_result.resulting_annotations), + ) + + oracle_db_service.outbox.create_webhook( + db_session, + escrow_address, + chain_id, + OracleWebhookTypes.reputation_oracle, + event=RecordingOracleEvent_TaskCompleted(), + ) + oracle_db_service.outbox.create_webhook( + db_session, + escrow_address, + chain_id, + OracleWebhookTypes.exchange_oracle, + event=RecordingOracleEvent_TaskCompleted(), + ) + else: + error_type_counts = Counter( + type(e).__name__ for e in validation_result.rejected_jobs.values() + ) + logger.info( + f"Validation for escrow_address={escrow_address}: failed, " + f"rejected {len(validation_result.rejected_jobs)} jobs. " + f"Problems: {dict(error_type_counts)}" + ) + + oracle_db_service.outbox.create_webhook( + db_session, + escrow_address, + chain_id, + OracleWebhookTypes.exchange_oracle, + event=RecordingOracleEvent_TaskRejected( + # TODO: update wrt. M2 API changes, send reason + rejected_job_ids=list( + jid + for jid, reason in validation_result.rejected_jobs.items() + if not isinstance( + reason, TooFewGtError + ) # prevent such jobs from reannotation, can also be handled in ExcOr + ) + ), + ) + + +def validate_results( + escrow_address: str, + chain_id: int, + db_session: Session, +): + logger = get_function_logger(module_logger_name) + + escrow.validate_escrow(chain_id=chain_id, escrow_address=escrow_address) + + manifest = parse_manifest(escrow.get_escrow_manifest(chain_id, escrow_address)) + + validator = _TaskValidator( + escrow_address=escrow_address, chain_id=chain_id, manifest=manifest, db_session=db_session + ) + validator.set_logger(logger) + validator.validate() diff --git a/packages/examples/cvat/recording-oracle/src/models/validation.py b/packages/examples/cvat/recording-oracle/src/models/validation.py index 7945ac28da..aa75e93c2d 100644 --- a/packages/examples/cvat/recording-oracle/src/models/validation.py +++ b/packages/examples/cvat/recording-oracle/src/models/validation.py @@ -22,6 +22,9 @@ class Task(Base): jobs: Mapped[List["Job"]] = relationship( back_populates="task", cascade="all, delete", passive_deletes=True ) + gt_stats: Mapped[List["GtStats"]] = relationship( + back_populates="task", cascade="all, delete", passive_deletes=True + ) class Job(Base): @@ -45,3 +48,19 @@ class ValidationResult(Base): annotation_quality = Column(Float, nullable=False) job: Mapped["Job"] = relationship(back_populates="validation_results") + + +class GtStats(Base): + __tablename__ = "gt_stats" + + # A composite primary key is used + task_id = Column( + String, ForeignKey("tasks.id", ondelete="CASCADE"), primary_key=True, nullable=False + ) + + # TODO: think how to store this better + gt_key = Column(String, index=True, primary_key=True, nullable=False) + + failed_attempts = Column(Integer, default=0, nullable=False) + + task: Mapped["Task"] = relationship(back_populates="gt_stats") diff --git a/packages/examples/cvat/recording-oracle/src/schemas/agreement.py b/packages/examples/cvat/recording-oracle/src/schemas/agreement.py deleted file mode 100644 index 9088ddf120..0000000000 --- a/packages/examples/cvat/recording-oracle/src/schemas/agreement.py +++ /dev/null @@ -1,35 +0,0 @@ -from typing import Generic, Mapping, Optional, Sequence, TypeVar - -from pydantic import BaseModel - -T = TypeVar("T") - - -class ImageLabelBinaryFinalResult(BaseModel): - url: str - label: str - label_counts: Mapping[str, int] - score: float - - -class AgreementEstimate(BaseModel): - score: float - interval: Optional[tuple[float, float]] - alpha: Optional[float] - - -class ResultDataset(BaseModel, Generic[T]): - dataset_scores: Mapping[str, AgreementEstimate] - data_points: Sequence[T] - - -class WorkerPerformanceResult(BaseModel): - worker_id: str - consensus_annotations: int - total_annotations: int - score: float - - -class ImageLabelBinaryJobResults(BaseModel): - dataset: ResultDataset[ImageLabelBinaryFinalResult] - worker_performance: Sequence[WorkerPerformanceResult] diff --git a/packages/examples/cvat/recording-oracle/src/schemas/webhook.py b/packages/examples/cvat/recording-oracle/src/schemas/webhook.py index e47a129847..cf898de268 100644 --- a/packages/examples/cvat/recording-oracle/src/schemas/webhook.py +++ b/packages/examples/cvat/recording-oracle/src/schemas/webhook.py @@ -4,7 +4,7 @@ from pydantic import BaseModel, validator from src.chain.web3 import validate_address -from src.core.types import ExchangeOracleEventType, Networks +from src.core.types import ExchangeOracleEventTypes, Networks class OracleWebhook(BaseModel): @@ -24,7 +24,7 @@ class Config: "example": { "escrow_address": "0x199c44cfa6a84554ac01f3e3b01d7cfce38a75eb", "chain_id": 80001, - "event_type": ExchangeOracleEventType.task_finished.value, + "event_type": ExchangeOracleEventTypes.task_finished.value, "event_data": {}, } } diff --git a/packages/examples/cvat/recording-oracle/src/services/cloud/__init__.py b/packages/examples/cvat/recording-oracle/src/services/cloud/__init__.py index eb128cc100..e492c74156 100644 --- a/packages/examples/cvat/recording-oracle/src/services/cloud/__init__.py +++ b/packages/examples/cvat/recording-oracle/src/services/cloud/__init__.py @@ -1,13 +1,3 @@ -from typing import Optional - -from src.services.cloud.client import S3Client - - -def download_file(bucket_host: str, bucket_name: str, filename: str) -> bytes: - client = S3Client(bucket_host) - return client.download_fileobj(bucket_name, filename) - - -def list_files(bucket_host: str, bucket_name: str, path: Optional[str] = None) -> list[str]: - client = S3Client(bucket_host) - return [f.key for f in client.list_files(bucket_name, path=path)] +from src.services.cloud.client import StorageClient +from src.services.cloud.types import BucketAccessInfo, BucketCredentials, CloudProviders +from src.services.cloud.utils import make_client diff --git a/packages/examples/cvat/recording-oracle/src/services/cloud/client.py b/packages/examples/cvat/recording-oracle/src/services/cloud/client.py index 2c6101a573..5bb92d77e3 100644 --- a/packages/examples/cvat/recording-oracle/src/services/cloud/client.py +++ b/packages/examples/cvat/recording-oracle/src/services/cloud/client.py @@ -1,61 +1,37 @@ -# Copyright (C) 2022 CVAT.ai Corporation -# -# SPDX-License-Identifier: MIT - -from io import BytesIO +from abc import ABCMeta, abstractmethod from typing import List, Optional - -import boto3 -from botocore.exceptions import ClientError -from botocore.handlers import disable_signing +from urllib.parse import unquote -class S3Client: +class StorageClient(metaclass=ABCMeta): def __init__( self, - endpoint_url: str, - *, - access_key: Optional[str] = None, - secret_key: Optional[str] = None, + bucket: Optional[str] = None, ) -> None: - s3 = boto3.resource( - "s3", - **(dict(aws_access_key_id=access_key) if access_key else {}), - **(dict(aws_secret_access_key=secret_key) if secret_key else {}), - endpoint_url=endpoint_url, - ) - - self.resource = s3 - self.client = s3.meta.client - - if not access_key and not secret_key: - self.client.meta.events.register("choose-signer.s3.*", disable_signing) - - def create_file(self, bucket: str, filename: str, data: bytes = b""): - self.client.put_object(Body=data, Bucket=bucket, Key=filename) - - def remove_file(self, bucket: str, filename: str): - self.client.delete_object(Bucket=bucket, Key=filename) - - def file_exists(self, bucket: str, filename: str) -> bool: - try: - self.client.head_object(Bucket=bucket, Key=filename) - return True - except ClientError as e: - if e.response["Error"]["Code"] == "404": - return False - else: - raise - - def download_fileobj(self, bucket: str, key: str) -> bytes: - with BytesIO() as data: - self.client.download_fileobj(Bucket=bucket, Key=key, Fileobj=data) - return data.getvalue() - - def list_files(self, bucket: str, path: Optional[str] = None) -> List: - objects = self.resource.Bucket(bucket).objects - if path: - objects = objects.filter(Prefix=path.strip("/\\") + "/") - else: - objects = objects.all() - return list(objects) + self._bucket = unquote(bucket) if bucket else None + + @abstractmethod + def create_file(self, key: str, data: bytes = b"", *, bucket: Optional[str] = None): + ... + + @abstractmethod + def remove_file(self, key: str, *, bucket: Optional[str] = None): + ... + + @abstractmethod + def file_exists(self, key: str, *, bucket: Optional[str] = None) -> bool: + ... + + @abstractmethod + def download_file(self, key: str, *, bucket: Optional[str] = None) -> bytes: + ... + + @abstractmethod + def list_files( + self, *, bucket: Optional[str] = None, prefix: Optional[str] = None + ) -> List[str]: + ... + + @staticmethod + def normalize_prefix(prefix: Optional[str]) -> Optional[str]: + return unquote(prefix).strip("/\\") + "/" if prefix else prefix diff --git a/packages/examples/cvat/recording-oracle/src/services/cloud/gcs.py b/packages/examples/cvat/recording-oracle/src/services/cloud/gcs.py new file mode 100644 index 0000000000..36611b363f --- /dev/null +++ b/packages/examples/cvat/recording-oracle/src/services/cloud/gcs.py @@ -0,0 +1,70 @@ +from io import BytesIO +from typing import Dict, List, Optional +from urllib.parse import unquote + +from google.cloud import storage + +from src.services.cloud.client import StorageClient + +DEFAULT_GCS_HOST = "storage.googleapis.com" + + +class GcsClient(StorageClient): + def __init__( + self, + *, + bucket: Optional[str] = None, + service_account_key: Optional[Dict] = None, + ) -> None: + super().__init__(bucket) + + if service_account_key: + self.client = storage.Client.from_service_account_info(service_account_key) + else: + self.client = storage.Client.create_anonymous_client() + + def create_file(self, key: str, data: bytes = b"", *, bucket: Optional[str] = None) -> None: + bucket = unquote(bucket) if bucket else self._bucket + bucket_client = self.client.get_bucket(bucket) + bucket_client.blob(unquote(key)).upload_from_string(data) + + def remove_file(self, key: str, *, bucket: Optional[str] = None) -> None: + bucket = unquote(bucket) if bucket else self._bucket + bucket_client = self.client.get_bucket(bucket) + bucket_client.delete_blob(unquote(key)) + + def file_exists(self, key: str, *, bucket: Optional[str] = None) -> bool: + bucket = unquote(bucket) if bucket else self._bucket + bucket_client = self.client.get_bucket(bucket) + return bucket_client.blob(unquote(key)).exists() + + def download_file(self, key: str, *, bucket: Optional[str] = None) -> bytes: + bucket = unquote(bucket) if bucket else self._bucket + bucket_client = self.client.get_bucket(bucket) + blob = bucket_client.blob(unquote(key)) + + with BytesIO() as data: + self.client.download_blob_to_file(blob, data) + return data.getvalue() + + def list_files( + self, *, bucket: Optional[str] = None, prefix: Optional[str] = None + ) -> List[str]: + bucket = unquote(bucket) if bucket else self._bucket + prefix = self.normalize_prefix(prefix) + + return [ + blob.name + for blob in self.client.list_blobs( + bucket_or_name=bucket, + fields="items(name)", + **( + { + "prefix": prefix, + "delimiter": "/", + } + if prefix + else {} + ), + ) + ] diff --git a/packages/examples/cvat/recording-oracle/src/services/cloud/s3.py b/packages/examples/cvat/recording-oracle/src/services/cloud/s3.py new file mode 100644 index 0000000000..e8e608ce99 --- /dev/null +++ b/packages/examples/cvat/recording-oracle/src/services/cloud/s3.py @@ -0,0 +1,71 @@ +from io import BytesIO +from typing import List, Optional +from urllib.parse import unquote + +import boto3 +from botocore.exceptions import ClientError +from botocore.handlers import disable_signing + +from src.services.cloud.client import StorageClient + +DEFAULT_S3_HOST = "s3.amazonaws.com" + + +class S3Client(StorageClient): + def __init__( + self, + *, + bucket: Optional[str] = None, + access_key: Optional[str] = None, + secret_key: Optional[str] = None, + endpoint_url: Optional[str] = None, + ) -> None: + super().__init__(bucket) + session = boto3.Session( + **(dict(aws_access_key_id=access_key) if access_key else {}), + **(dict(aws_secret_access_key=secret_key) if secret_key else {}), + ) + s3 = session.resource( + "s3", **({"endpoint_url": unquote(endpoint_url)} if endpoint_url else {}) + ) + self.resource = s3 + self.client = s3.meta.client + + if not access_key and not secret_key: + self.client.meta.events.register("choose-signer.s3.*", disable_signing) + + def create_file(self, key: str, data: bytes = b"", *, bucket: Optional[str] = None): + bucket = unquote(bucket) if bucket else self._bucket + self.client.put_object(Body=data, Bucket=bucket, Key=unquote(key)) + + def remove_file(self, key: str, *, bucket: Optional[str] = None): + bucket = unquote(bucket) if bucket else self._bucket + self.client.delete_object(Bucket=bucket, Key=unquote(key)) + + def file_exists(self, key: str, *, bucket: Optional[str] = None) -> bool: + bucket = unquote(bucket) if bucket else self._bucket + try: + self.client.head_object(Bucket=bucket, Key=unquote(key)) + return True + except ClientError as e: + if e.response["Error"]["Code"] == "404": + return False + else: + raise + + def download_file(self, key: str, *, bucket: Optional[str] = None) -> bytes: + bucket = unquote(bucket) if bucket else self._bucket + with BytesIO() as data: + self.client.download_fileobj(Bucket=bucket, Key=unquote(key), Fileobj=data) + return data.getvalue() + + def list_files( + self, *, bucket: Optional[str] = None, prefix: Optional[str] = None + ) -> List[str]: + bucket = unquote(bucket) if bucket else self._bucket + objects = self.resource.Bucket(bucket).objects + if prefix: + objects = objects.filter(Prefix=self.normalize_prefix(prefix)) + else: + objects = objects.all() + return [file_info.key for file_info in objects] diff --git a/packages/examples/cvat/recording-oracle/src/services/cloud/types.py b/packages/examples/cvat/recording-oracle/src/services/cloud/types.py new file mode 100644 index 0000000000..ec8cd9df9e --- /dev/null +++ b/packages/examples/cvat/recording-oracle/src/services/cloud/types.py @@ -0,0 +1,187 @@ +from __future__ import annotations + +import json +from dataclasses import asdict, dataclass, is_dataclass +from enum import Enum, auto +from inspect import isclass +from typing import Dict, Optional, Type, Union +from urllib.parse import urlparse + +from src.core import manifest +from src.core.config import Config, IStorageConfig +from src.services.cloud.gcs import DEFAULT_GCS_HOST +from src.services.cloud.s3 import DEFAULT_S3_HOST +from src.utils.enums import BetterEnumMeta +from src.utils.net import is_ipv4 + + +class CloudProviders(Enum, metaclass=BetterEnumMeta): + aws = auto() + gcs = auto() + + @classmethod + def from_str(cls, provider: str) -> CloudProviders: + try: + return cls[provider.lower()] + except KeyError: + raise ValueError( + f"The '{provider}' is not supported. " + f"List with supported providers: {', '.join(x.name for x in cls)}" + ) + + +class BucketCredentials: + def to_dict(self) -> Dict: + if not is_dataclass(self): + raise NotImplementedError + + return asdict(self) + + @classmethod + def from_storage_config(cls, config: Type[IStorageConfig]) -> Optional[BucketCredentials]: + credentials = None + + if (config.access_key or config.secret_key) and config.provider.lower() != "aws": + raise ValueError( + "Invalid storage configuration. The access_key/secret_key pair" + f"cannot be specified with {config.provider} provider" + ) + elif ( + bool(config.access_key) ^ bool(config.secret_key) + ) and config.provider.lower() == "aws": + raise ValueError( + "Invalid storage configuration. " + "Either none or both access_key and secret_key must be specified for an AWS storage" + ) + + if config.key_file_path and config.provider.lower() != "gcs": + raise ValueError( + "Invalid storage configuration. The key_file_path" + f"cannot be specified with {config.provider} provider" + ) + + if config.access_key and config.secret_key: + credentials = S3BucketCredentials(config.access_key, config.secret_key) + elif config.key_file_path: + with open(config.key_file_path, "rb") as f: + credentials = GcsBucketCredentials(json.load(f)) + + return credentials + + +@dataclass +class GcsBucketCredentials(BucketCredentials): + service_account_key: Dict + + +@dataclass +class S3BucketCredentials(BucketCredentials): + access_key: str + secret_key: str + + +@dataclass +class BucketAccessInfo: + provider: CloudProviders + host_url: str + bucket_name: str + path: Optional[str] = None + credentials: Optional[BucketCredentials] = None + + @classmethod + def from_url(cls, url: str) -> BucketAccessInfo: + parsed_url = urlparse(url) + + if parsed_url.netloc.endswith(DEFAULT_S3_HOST): + # AWS S3 bucket + return BucketAccessInfo( + provider=CloudProviders.aws, + host_url=f"https://{DEFAULT_S3_HOST}", + bucket_name=parsed_url.netloc.split(".")[0], + path=parsed_url.path.lstrip("/"), + ) + elif parsed_url.netloc.endswith(DEFAULT_GCS_HOST): + # Google Cloud Storage (GCS) bucket + # Virtual hosted-style is expected: + # https://BUCKET_NAME.storage.googleapis.com/OBJECT_NAME + return BucketAccessInfo( + provider=CloudProviders.gcs, + bucket_name=parsed_url.netloc[: -len(f".{DEFAULT_GCS_HOST}")], + host_url=f"{parsed_url.scheme}://{DEFAULT_GCS_HOST}", + path=parsed_url.path.lstrip("/"), + ) + elif Config.features.enable_custom_cloud_host: + if is_ipv4(parsed_url.netloc): + host = parsed_url.netloc + bucket_name, path = parsed_url.path.lstrip("/").split("/", maxsplit=1) + else: + host = parsed_url.netloc.partition(".")[2] + bucket_name = parsed_url.netloc.split(".")[0] + path = parsed_url.path.lstrip("/") + + return BucketAccessInfo( + provider=CloudProviders.aws, + host_url=f"{parsed_url.scheme}://{host}", + bucket_name=bucket_name, + path=path, + ) + else: + raise ValueError(f"{parsed_url.netloc} cloud provider is not supported.") + + @classmethod + def _from_dict(cls, data: Dict) -> BucketAccessInfo: + for required_field in ( + "provider", + "bucket_name", + ): + if required_field not in data: + raise ValueError( + f"The {required_field} is required and is not " + "specified in the bucket configuration" + ) + + provider = CloudProviders.from_str(data["provider"]) + data["provider"] = provider + + if provider == CloudProviders.aws: + access_key = data.pop("access_key", None) + secret_key = data.pop("secret_key", None) + if bool(access_key) ^ bool(secret_key): + raise ValueError("access_key and secret_key can only be used together") + + data["credentials"] = S3BucketCredentials(access_key, secret_key) + + elif provider == CloudProviders.gcs and ( + service_account_key := data.pop("service_account_key", None) + ): + data["credentials"] = GcsBucketCredentials(service_account_key) + + return BucketAccessInfo(**data) + + @classmethod + def from_storage_config(cls, config: Type[IStorageConfig]) -> BucketAccessInfo: + credentials = BucketCredentials.from_storage_config(config) + + return BucketAccessInfo( + provider=CloudProviders.from_str(config.provider), + host_url=config.provider_endpoint_url(), + bucket_name=config.data_bucket_name, + credentials=credentials, + ) + + @classmethod + def from_bucket_url(cls, bucket_url: manifest.BucketUrl) -> BucketAccessInfo: + return cls._from_dict(bucket_url.dict()) + + @classmethod + def parse_obj( + cls, data: Union[str, Type[IStorageConfig], manifest.BucketUrl] + ) -> BucketAccessInfo: + if isinstance(data, manifest.BucketUrlBase): + return cls.from_bucket_url(data) + elif isinstance(data, str): + return cls.from_url(data) + elif isclass(data) and issubclass(data, IStorageConfig): + return cls.from_storage_config(data) + + raise TypeError(f"Unsupported data type ({type(data)}) was provided") diff --git a/packages/examples/cvat/recording-oracle/src/services/cloud/utils.py b/packages/examples/cvat/recording-oracle/src/services/cloud/utils.py new file mode 100644 index 0000000000..a9f821d174 --- /dev/null +++ b/packages/examples/cvat/recording-oracle/src/services/cloud/utils.py @@ -0,0 +1,44 @@ +from typing import Optional + +from src.services.cloud.client import StorageClient +from src.services.cloud.gcs import DEFAULT_GCS_HOST, GcsClient +from src.services.cloud.s3 import DEFAULT_S3_HOST, S3Client +from src.services.cloud.types import BucketAccessInfo, CloudProviders + + +def compose_bucket_url( + bucket_name: str, provider: CloudProviders, *, bucket_host: Optional[str] = None +) -> str: + match provider: + case CloudProviders.aws: + return f"https://{bucket_name}.{bucket_host or DEFAULT_S3_HOST}/" + case CloudProviders.gcs: + return f"https://{bucket_name}.{bucket_host or DEFAULT_GCS_HOST}/" + + +def make_client( + bucket_info: BucketAccessInfo, +) -> StorageClient: + client_kwargs = { + "bucket": bucket_info.bucket_name, + } + + match bucket_info.provider: + case CloudProviders.aws: + client_type = S3Client + + if bucket_info.credentials: + client_kwargs["access_key"] = bucket_info.credentials.access_key + client_kwargs["secret_key"] = bucket_info.credentials.secret_key + + if bucket_info.host_url: + client_kwargs["endpoint_url"] = bucket_info.host_url + case CloudProviders.gcs: + client_type = GcsClient + + if bucket_info.credentials: + client_kwargs["service_account_key"] = bucket_info.credentials.service_account_key + case _: + raise ValueError(f"Unsupported cloud provider ({bucket_info.provider}) was provided") + + return client_type(**client_kwargs) diff --git a/packages/examples/cvat/recording-oracle/src/services/validation.py b/packages/examples/cvat/recording-oracle/src/services/validation.py index c1aa577219..f4a054e7fc 100644 --- a/packages/examples/cvat/recording-oracle/src/services/validation.py +++ b/packages/examples/cvat/recording-oracle/src/services/validation.py @@ -1,11 +1,12 @@ import uuid -from typing import List, Optional, Union +from typing import Dict, List, Optional, Union from sqlalchemy.orm import Session +from src.db import engine as db_engine from src.db.utils import ForUpdateParams from src.db.utils import maybe_for_update as _maybe_for_update -from src.models.validation import Job, Task, ValidationResult +from src.models.validation import GtStats, Job, Task, ValidationResult def create_task(session: Session, escrow_address: str, chain_id: int) -> str: @@ -99,3 +100,40 @@ def get_validation_result_by_assignment_id( .where(ValidationResult.assignment_id == assignment_id) .first() ) + + +def get_task_gt_stats( + session: Session, task_id: str, *, for_update: Union[bool, ForUpdateParams] = False +) -> List[GtStats]: + return ( + _maybe_for_update(session.query(GtStats), enable=for_update) + .where(GtStats.task_id == task_id) + .all() + ) + + +def update_gt_stats(session: Session, task_id: str, values: Dict[str, int]): + # Read more about upsert: + # https://docs.sqlalchemy.org/en/20/orm/queryguide/dml.html#orm-upsert-statements + + if db_engine.driver != "psycopg2": + raise NotImplementedError + + from sqlalchemy.dialects.postgresql import insert as psql_insert + from sqlalchemy.inspection import inspect + + statement = psql_insert(GtStats).values( + [ + dict( + task_id=task_id, + gt_key=gt_key, + failed_attempts=failed_attempts, + ) + for gt_key, failed_attempts in values.items() + ], + ) + statement = statement.on_conflict_do_update( + index_elements=inspect(GtStats).primary_key, set_=statement.excluded + ) + + session.execute(statement) diff --git a/packages/examples/cvat/recording-oracle/src/services/webhook.py b/packages/examples/cvat/recording-oracle/src/services/webhook.py index 9d64c4edc4..e14af05321 100644 --- a/packages/examples/cvat/recording-oracle/src/services/webhook.py +++ b/packages/examples/cvat/recording-oracle/src/services/webhook.py @@ -18,14 +18,14 @@ from src.utils.time import utcnow -class OracleWebhookDirectionTag(str, Enum, metaclass=BetterEnumMeta): +class OracleWebhookDirectionTags(str, Enum, metaclass=BetterEnumMeta): incoming = "incoming" outgoing = "outgoing" @define class OracleWebhookQueue: - direction: OracleWebhookDirectionTag + direction: OracleWebhookDirectionTags default_sender: Optional[OracleWebhookTypes] = None def create_webhook( @@ -48,7 +48,7 @@ def create_webhook( ), f"'event' and 'event_type' cannot be used together. Please use only one of the fields" if event_type: - if self.direction == OracleWebhookDirectionTag.incoming: + if self.direction == OracleWebhookDirectionTags.incoming: sender = type else: assert self.default_sender @@ -58,9 +58,9 @@ def create_webhook( event_type = event.get_type() event_data = event.dict() - if self.direction == OracleWebhookDirectionTag.incoming and not signature: + if self.direction == OracleWebhookDirectionTags.incoming and not signature: raise ValueError("Webhook signature must be specified for incoming events") - elif self.direction == OracleWebhookDirectionTag.outgoing and signature: + elif self.direction == OracleWebhookDirectionTags.outgoing and signature: raise ValueError("Webhook signature must not be specified for outgoing events") if signature: @@ -142,8 +142,8 @@ def handle_webhook_fail(self, session: Session, webhook_id: str) -> None: session.execute(upd) -inbox = OracleWebhookQueue(direction=OracleWebhookDirectionTag.incoming) +inbox = OracleWebhookQueue(direction=OracleWebhookDirectionTags.incoming) outbox = OracleWebhookQueue( - direction=OracleWebhookDirectionTag.outgoing, + direction=OracleWebhookDirectionTags.outgoing, default_sender=OracleWebhookTypes.recording_oracle, ) diff --git a/packages/examples/cvat/recording-oracle/src/utils/annotations.py b/packages/examples/cvat/recording-oracle/src/utils/annotations.py new file mode 100644 index 0000000000..75ec29ec15 --- /dev/null +++ b/packages/examples/cvat/recording-oracle/src/utils/annotations.py @@ -0,0 +1,206 @@ +from copy import deepcopy +from typing import Iterable, Optional, Tuple, Union + +import datumaro as dm +import numpy as np +from datumaro.util import mask_tools + + +def shift_ann( + ann: dm.Annotation, offset_x: float, offset_y: float, *, img_w: int, img_h: int +) -> dm.Annotation: + "Shift annotation coordinates with clipping to the image size" + + if isinstance(ann, dm.Bbox): + shifted_ann = ann.wrap( + x=offset_x + ann.x, + y=offset_y + ann.y, + ) + elif isinstance(ann, dm.Points): + shifted_ann = ann.wrap( + points=np.clip( + np.reshape(ann.points, (-1, 2)) + (offset_x, offset_y), + 0, + [img_w, img_h], + ).flat + ) + elif isinstance(ann, dm.Skeleton): + shifted_ann = ann.wrap( + elements=[ + point.wrap( + points=np.clip( + np.reshape(point.points, (-1, 2)) + (offset_x, offset_y), + 0, + [img_w, img_h], + ).flat + ) + for point in ann.elements + ] + ) + else: + assert False, f"Unsupported annotation type '{ann.type}'" + + return shifted_ann + + +class ProjectLabels(dm.ItemTransform): + """ + Changes the order of labels in the dataset from the existing + to the desired one, removes unknown labels and adds new labels. + Updates or removes the corresponding annotations.|n + |n + Labels are matched by names (case dependent). Parent labels are only kept + if they are present in the resulting set of labels. If new labels are + added, and the dataset has mask colors defined, new labels will obtain + generated colors.|n + |n + Useful for merging similar datasets, whose labels need to be aligned.|n + |n + Examples:|n + |s|s- Align the source dataset labels to [person, cat, dog]:|n + + |s|s.. code-block:: + + |s|s|s|s%(prog)s -l person -l cat -l dog + """ + + @classmethod + def build_cmdline_parser(cls, **kwargs): + parser = super().build_cmdline_parser(**kwargs) + parser.add_argument( + "-l", + "--label", + action="append", + dest="dst_labels", + help="Label name (repeatable, ordered)", + ) + return parser + + def __init__( + self, + extractor: dm.IExtractor, + dst_labels: Union[Iterable[Union[str, Tuple[str, str]]], dm.LabelCategories], + ): + super().__init__(extractor) + + self._categories = {} + + src_categories = self._extractor.categories() + + src_label_cat: Optional[dm.LabelCategories] = src_categories.get(dm.AnnotationType.label) + src_point_cat: Optional[dm.PointsCategories] = src_categories.get(dm.AnnotationType.points) + + if isinstance(dst_labels, dm.LabelCategories): + dst_label_cat = deepcopy(dst_labels) + else: + dst_labels = list(dst_labels) + + if src_label_cat: + dst_label_cat = dm.LabelCategories(attributes=deepcopy(src_label_cat.attributes)) + + for dst_label in dst_labels: + assert isinstance(dst_label, str) or isinstance(dst_label, tuple) + + dst_parent = "" + if isinstance(dst_label, tuple): + dst_label, dst_parent = dst_label + + src_label = src_label_cat.find(dst_label, dst_parent)[1] + if src_label is not None: + dst_label_cat.add( + dst_label, src_label.parent, deepcopy(src_label.attributes) + ) + else: + dst_label_cat.add(dst_label, dst_parent) + else: + dst_label_cat = dm.LabelCategories.from_iterable(dst_labels) + + for label in dst_label_cat: + if label.parent not in dst_label_cat: + label.parent = "" + + if src_point_cat: + # Copy nested labels + for skeleton_label_id, skeleton_spec in src_point_cat.items.items(): + skeleton_label = src_label_cat[skeleton_label_id] + for child_label_name in skeleton_spec.labels: + if ( + skeleton_label.name in dst_label_cat + and not dst_label_cat.find(child_label_name, parent=skeleton_label.name)[1] + ): + dst_label_cat.add( + child_label_name, + parent=skeleton_label.name, + attributes=skeleton_label.attributes, + ) + + self._categories[dm.AnnotationType.label] = dst_label_cat + + self._make_label_id_map(src_label_cat, dst_label_cat) + + src_mask_cat = src_categories.get(dm.AnnotationType.mask) + if src_mask_cat is not None: + assert src_label_cat is not None + dst_mask_cat = dm.MaskCategories(attributes=deepcopy(src_mask_cat.attributes)) + for old_id, old_color in src_mask_cat.colormap.items(): + new_id = self._map_id(old_id) + if new_id is not None and new_id not in dst_mask_cat: + dst_mask_cat.colormap[new_id] = deepcopy(old_color) + + # Generate new colors for new labels, keep old untouched + existing_colors = set(dst_mask_cat.colormap.values()) + color_bank = iter( + mask_tools.generate_colormap(len(dst_label_cat), include_background=False).values() + ) + for new_id, new_label in enumerate(dst_label_cat): + if new_label.name in src_label_cat: + continue + if new_id in dst_mask_cat: + continue + + color = next(color_bank) + while color in existing_colors: + color = next(color_bank) + + dst_mask_cat.colormap[new_id] = color + + self._categories[dm.AnnotationType.mask] = dst_mask_cat + + if src_point_cat is not None: + assert src_label_cat is not None + dst_point_cat = dm.PointsCategories(attributes=deepcopy(src_point_cat.attributes)) + for old_id, old_cat in src_point_cat.items.items(): + new_id = self._map_id(old_id) + if new_id is not None and new_id not in dst_point_cat: + dst_point_cat.items[new_id] = deepcopy(old_cat) + + self._categories[dm.AnnotationType.points] = dst_point_cat + + def _make_label_id_map(self, src_label_cat, dst_label_cat): + id_mapping = { + src_id: dst_label_cat.find(src_label_cat[src_id].name, src_label_cat[src_id].parent)[0] + for src_id in range(len(src_label_cat or ())) + } + self._map_id = lambda src_id: id_mapping.get(src_id, None) + + def categories(self): + return self._categories + + def transform_item(self, item): + annotations = [] + for ann in item.annotations: + if getattr(ann, "label", None) is not None: + conv_label = self._map_id(ann.label) + if conv_label is None: + continue + + ann = ann.wrap(label=conv_label) + else: + ann = ann.wrap() + + if ann and isinstance(ann, dm.Skeleton): + ann = ann.wrap(elements=[e.wrap(label=self._map_id(e.label)) for e in ann.elements]) + + annotations.append(ann) + + return item.wrap(annotations=annotations) diff --git a/packages/examples/cvat/recording-oracle/src/utils/assignments.py b/packages/examples/cvat/recording-oracle/src/utils/assignments.py index dc3da37e50..1453425d8d 100644 --- a/packages/examples/cvat/recording-oracle/src/utils/assignments.py +++ b/packages/examples/cvat/recording-oracle/src/utils/assignments.py @@ -1,11 +1,5 @@ from hashlib import sha256 -from src.core.manifest import TaskManifest - - -def parse_manifest(manifest: dict) -> TaskManifest: - return TaskManifest.parse_obj(manifest) - def compute_resulting_annotations_hash(data: bytes) -> str: return sha256(data, usedforsecurity=False).hexdigest() diff --git a/packages/examples/cvat/recording-oracle/src/utils/cloud_storage.py b/packages/examples/cvat/recording-oracle/src/utils/cloud_storage.py deleted file mode 100644 index 3418fde5bc..0000000000 --- a/packages/examples/cvat/recording-oracle/src/utils/cloud_storage.py +++ /dev/null @@ -1,64 +0,0 @@ -from dataclasses import dataclass -from typing import Optional -from urllib.parse import urlparse - -from src.core.config import Config -from src.core.types import CloudProviders -from src.utils.net import is_ipv4 - - -@dataclass -class ParsedBucketUrl: - provider: str - host_url: str - bucket_name: str - path: str - - -DEFAULT_S3_HOST = "s3.amazonaws.com" - - -def parse_bucket_url(data_url: str) -> ParsedBucketUrl: - parsed_url = urlparse(data_url) - - if parsed_url.netloc.endswith(DEFAULT_S3_HOST): - # AWS S3 bucket - return ParsedBucketUrl( - provider=CloudProviders.aws.value, - host_url=f"https://{DEFAULT_S3_HOST}", - bucket_name=parsed_url.netloc.split(".")[0], - path=parsed_url.path.lstrip("/"), - ) - # elif parsed_url.netloc.endswith("storage.googleapis.com"): - # # Google Cloud Storage (GCS) bucket - # return ParsedBucketUrl( - # provider=CloudProviders.gcs.value, - # bucket_name=parsed_url.netloc.split(".")[0], - # ) - elif Config.features.enable_custom_cloud_host: - if is_ipv4(parsed_url.netloc): - host = parsed_url.netloc - bucket_name, path = parsed_url.path.lstrip("/").split("/", maxsplit=1) - else: - host = parsed_url.netloc.partition(".")[2] - bucket_name = parsed_url.netloc.split(".")[0] - path = parsed_url.path.lstrip("/") - - return ParsedBucketUrl( - provider=CloudProviders.aws.value, - host_url=f"{parsed_url.scheme}://{host}", - bucket_name=bucket_name, - path=path, - ) - else: - raise ValueError(f"{parsed_url.netloc} cloud provider is not supported by CVAT") - - -def compose_bucket_url( - bucket_name: str, provider: CloudProviders, *, bucket_host: Optional[str] = None -) -> str: - match provider: - case CloudProviders.aws.value: - return f"https://{bucket_name}.{bucket_host or 's3.amazonaws.com'}/" - case CloudProviders.gcs.value: - return f"https://{bucket_name}.{bucket_host or 'storage.googleapis.com'}/" diff --git a/packages/examples/cvat/recording-oracle/src/utils/logging.py b/packages/examples/cvat/recording-oracle/src/utils/logging.py index d9ce28d024..146f6c219b 100644 --- a/packages/examples/cvat/recording-oracle/src/utils/logging.py +++ b/packages/examples/cvat/recording-oracle/src/utils/logging.py @@ -20,3 +20,9 @@ def get_function_logger( function_name = current_function_name(depth=2) return parent_logger.getChild(function_name) + + +class NullLogger(logging.Logger): + def __init__(self, name: str = "", level=0) -> None: + super().__init__(name, level) + self.disabled = True diff --git a/packages/examples/cvat/recording-oracle/src/utils/storage.py b/packages/examples/cvat/recording-oracle/src/utils/storage.py deleted file mode 100644 index 2ce5d70eec..0000000000 --- a/packages/examples/cvat/recording-oracle/src/utils/storage.py +++ /dev/null @@ -1,5 +0,0 @@ -from src.core.types import Networks - - -def compose_bucket_filename(escrow_address: str, chain_id: Networks, filename: str) -> str: - return f"{escrow_address}@{chain_id}/{filename}" diff --git a/packages/examples/cvat/recording-oracle/src/utils/webhooks.py b/packages/examples/cvat/recording-oracle/src/utils/webhooks.py index bef1bcc459..e6d39202a3 100644 --- a/packages/examples/cvat/recording-oracle/src/utils/webhooks.py +++ b/packages/examples/cvat/recording-oracle/src/utils/webhooks.py @@ -20,7 +20,10 @@ def prepare_outgoing_webhook_body( event = parse_event(OracleWebhookTypes.recording_oracle, event_type, event_data) body["event_type"] = event_type + body["event_data"] = event.dict() + if not body["event_data"]: + body.pop("event_data") return body diff --git a/packages/examples/cvat/recording-oracle/src/validation/annotation_matching.py b/packages/examples/cvat/recording-oracle/src/validation/annotation_matching.py index d5ab648c15..fa3a965f7f 100644 --- a/packages/examples/cvat/recording-oracle/src/validation/annotation_matching.py +++ b/packages/examples/cvat/recording-oracle/src/validation/annotation_matching.py @@ -52,7 +52,7 @@ def point_to_bbox_cmp( bbox: Bbox, point: Point, *, - rel_sigma: float = Config.features.default_point_validity_relative_radius, + rel_sigma: float = Config.validation.default_point_validity_relative_radius, ) -> float: """ Checks that the point is within the axis-aligned bbox, @@ -103,6 +103,8 @@ def match_annotations( for a, _ in itertools.zip_longest(a_anns, range(max_ann_count), fillvalue=None) ] ) + + distances[~np.isfinite(distances)] = 1 distances[distances > 1 - min_similarity] = 1 if a_anns and b_anns: diff --git a/packages/examples/cvat/recording-oracle/src/validation/dataset_comparison.py b/packages/examples/cvat/recording-oracle/src/validation/dataset_comparison.py index 9ecb640060..6a570dc663 100644 --- a/packages/examples/cvat/recording-oracle/src/validation/dataset_comparison.py +++ b/packages/examples/cvat/recording-oracle/src/validation/dataset_comparison.py @@ -1,48 +1,71 @@ +from __future__ import annotations + import itertools -from typing import Any, Callable, Dict, Tuple +from abc import ABCMeta, abstractmethod +from typing import Callable, Dict, Optional, Sequence, Set, Tuple, Union import datumaro as dm import numpy as np from attrs import define, field +from datumaro.util.annotation_util import BboxCoords + +from src.core.config import Config +from src.core.validation_errors import TooFewGtError + +from .annotation_matching import ( + Bbox, + MatchResult, + Point, + bbox_iou, + match_annotations, + point_to_bbox_cmp, +) -from .annotation_matching import Bbox, Point, bbox_iou, match_annotations, point_to_bbox_cmp + +class SimilarityFunction(metaclass=ABCMeta): + "A function to compute similarity between 2 annotations" + + def __call__(self, gt_ann: dm.Annotation, ds_ann: dm.Annotation) -> float: + ... -class CachedSimilarityFunction: - def __call__(self, gt_ann: Any, ds_ann: Any) -> float: +class CachedSimilarityFunction(SimilarityFunction): + def __init__( + self, sim_fn: Callable, *, cache: Optional[Dict[Tuple[int, int], float]] = None + ) -> None: + self.cache: Dict[Tuple[int, int], float] = cache or {} + self.sim_fn = sim_fn + + def __call__(self, gt_ann: dm.Annotation, ds_ann: dm.Annotation) -> float: key = ( id(gt_ann), id(ds_ann), - ) # make sure the boxes have stable ids before calling this! - cached_value = self._memo.get(key) + ) # make sure the annotations have stable ids before calling this + cached_value = self.cache.get(key) if cached_value is None: - cached_value = self._inner(gt_ann, ds_ann) - self._memo[key] = cached_value + cached_value = self.sim_fn(gt_ann, ds_ann) + self.cache[key] = cached_value return cached_value def clear_cache(self): - self._memo.clear() - - def __init__(self, inner: Callable) -> None: - self._memo: Dict[Tuple[int, int], float] = {} - self._inner = inner + self.cache.clear() @define -class DatasetComparator: - min_similarity_threshold: float - - def compare(self, gt_dataset: dm.Dataset, ds_dataset: dm.Dataset) -> float: - ... +class DatasetComparator(metaclass=ABCMeta): + _min_similarity_threshold: float + _gt_weights: Dict[str, float] = field(factory=dict) + failed_gts: Set[str] = field(factory=set, init=False) + "Recorded list of failed GT samples, available after compare() call" -class BboxDatasetComparator(DatasetComparator): def compare(self, gt_dataset: dm.Dataset, ds_dataset: dm.Dataset) -> float: - similarity_fn = CachedSimilarityFunction(bbox_iou) - - all_similarities = [] + dataset_similarities = [] + dataset_total_anns_to_compare = 0 + dataset_failed_gts = set() + dataset_excluded_gts_count = 0 for ds_sample in ds_dataset: gt_sample = gt_dataset.get(ds_sample.id) @@ -50,77 +73,311 @@ def compare(self, gt_dataset: dm.Dataset, ds_dataset: dm.Dataset) -> float: if not gt_sample: continue - ds_boxes = [ - Bbox(a.x, a.y, a.w, a.h, a.label) - for a in ds_sample.annotations - if isinstance(a, dm.Bbox) - ] - gt_boxes = [ - Bbox(a.x, a.y, a.w, a.h, a.label) - for a in gt_sample.annotations - if isinstance(a, dm.Bbox) - ] - - matching_result = match_annotations( - gt_boxes, - ds_boxes, - similarity=similarity_fn, - min_similarity=self.min_similarity_threshold, + sample_weight = self._gt_weights.get(gt_sample.id, 1) + if not sample_weight: + dataset_excluded_gts_count += 1 + continue + + sample_similarity_threshold = self._min_similarity_threshold * sample_weight + matching_result, similarity_fn = self.compare_sample_annotations( + gt_sample, ds_sample, similarity_threshold=sample_similarity_threshold ) - for gt_bbox, ds_bbox in itertools.chain( + sample_similarities = [] + sample_total_anns_to_compare = 0 + for gt_ann, ds_ann in itertools.chain( matching_result.matches, matching_result.mispred, zip(matching_result.a_extra, itertools.repeat(None)), zip(itertools.repeat(None), matching_result.b_extra), ): - sim = similarity_fn(gt_bbox, ds_bbox) if gt_bbox and ds_bbox else 0 - all_similarities.append(sim) + sim = similarity_fn(gt_ann, ds_ann) if gt_ann and ds_ann else 0 + sample_similarities.append(sim) + sample_total_anns_to_compare += (gt_ann is not None) + (ds_ann is not None) + + dataset_similarities.extend(sample_similarities) + dataset_total_anns_to_compare += sample_total_anns_to_compare + + sample_accuracy = 0 + if sample_total_anns_to_compare: + sample_accuracy = 2 * np.sum(sample_similarities) / sample_total_anns_to_compare + + if sample_accuracy < sample_similarity_threshold: + dataset_failed_gts.add(gt_sample.id) + + if dataset_excluded_gts_count == len(gt_dataset): + raise TooFewGtError() + + dataset_accuracy = 0 + if dataset_total_anns_to_compare: + dataset_accuracy = 2 * np.sum(dataset_similarities) / dataset_total_anns_to_compare - return np.mean(all_similarities) if all_similarities else 0 + self.failed_gts = dataset_failed_gts + + return dataset_accuracy + + @abstractmethod + def compare_sample_annotations( + self, gt_sample: dm.DatasetItem, ds_sample: dm.DatasetItem, *, similarity_threshold: float + ) -> Tuple[MatchResult, SimilarityFunction]: + ... + + +class BboxDatasetComparator(DatasetComparator): + def compare_sample_annotations( + self, gt_sample: dm.DatasetItem, ds_sample: dm.DatasetItem, *, similarity_threshold: float + ) -> Tuple[MatchResult, SimilarityFunction]: + similarity_fn = CachedSimilarityFunction(bbox_iou) + + ds_boxes = [ + Bbox(a.x, a.y, a.w, a.h, a.label) + for a in ds_sample.annotations + if isinstance(a, dm.Bbox) + ] + gt_boxes = [ + Bbox(a.x, a.y, a.w, a.h, a.label) + for a in gt_sample.annotations + if isinstance(a, dm.Bbox) + ] + + matching_result = match_annotations( + gt_boxes, + ds_boxes, + similarity=similarity_fn, + min_similarity=similarity_threshold, + ) + + return matching_result, similarity_fn class PointsDatasetComparator(DatasetComparator): - def compare(self, gt_dataset: dm.Dataset, ds_dataset: dm.Dataset) -> float: + def compare_sample_annotations( + self, gt_sample: dm.DatasetItem, ds_sample: dm.DatasetItem, *, similarity_threshold: float + ) -> Tuple[MatchResult, SimilarityFunction]: similarity_fn = CachedSimilarityFunction(point_to_bbox_cmp) - all_similarities = [] + ds_points = [ + Point( + a.elements[0].points[0], + a.elements[0].points[1], + a.elements[0].label, + ) + for a in ds_sample.annotations + if isinstance(a, dm.Skeleton) + ] + gt_boxes = [ + Bbox(a.x, a.y, a.w, a.h, a.label) + for a in gt_sample.annotations + if isinstance(a, dm.Bbox) + ] - for ds_sample in ds_dataset: - gt_sample = gt_dataset.get(ds_sample.id) + matching_result = match_annotations( + gt_boxes, + ds_points, + similarity=similarity_fn, + min_similarity=similarity_threshold, + ) - if not gt_sample: - continue + return matching_result, similarity_fn + + +_SkeletonInfo = list[str] - ds_points = [ - Point( - a.elements[0].points[0], - a.elements[0].points[1], - a.elements[0].label, + +@define +class SkeletonDatasetComparator(DatasetComparator): + _skeleton_info: Dict[int, _SkeletonInfo] = field(factory=dict, init=False) + _categories: Optional[dm.CategoriesInfo] = field(default=None, init=False) + + # TODO: find better strategy for sigma estimation + _oks_sigma: float = Config.validation.default_oks_sigma + + def compare(self, gt_dataset: dm.Dataset, ds_dataset: dm.Dataset) -> float: + self._categories = gt_dataset.categories() + return super().compare(gt_dataset, ds_dataset) + + def compare_sample_annotations( + self, gt_sample: dm.DatasetItem, ds_sample: dm.DatasetItem, *, similarity_threshold: float + ) -> Tuple[MatchResult, SimilarityFunction]: + return self._match_skeletons( + gt_sample, ds_sample, similarity_threshold=similarity_threshold + ) + + def _get_skeleton_info(self, skeleton_label_id: int) -> _SkeletonInfo: + label_cat: dm.LabelCategories = self._categories[dm.AnnotationType.label] + skeleton_info = self._skeleton_info.get(skeleton_label_id) + + if skeleton_info is None: + skeleton_label_name = label_cat[skeleton_label_id].name + + # Build a sorted list of sublabels to arrange skeleton points during comparison + skeleton_info = sorted( + idx for idx, label in enumerate(label_cat) if label.parent == skeleton_label_name + ) + self._skeleton_info[skeleton_label_id] = skeleton_info + + return skeleton_info + + def _match_skeletons( + self, item_a: dm.DatasetItem, item_b: dm.DatasetItem, *, similarity_threshold: float + ) -> Tuple[MatchResult, SimilarityFunction]: + a_skeletons = [a for a in item_a.annotations if isinstance(a, dm.Skeleton)] + b_skeletons = [a for a in item_b.annotations if isinstance(a, dm.Skeleton)] + + # Convert skeletons to point lists for comparison + # This is required to compute correct per-instance distance + # It is assumed that labels are the same in the datasets + skeleton_infos = {} + points_map = {} + skeleton_map = {} + a_points = [] + b_points = [] + for source, source_points in [(a_skeletons, a_points), (b_skeletons, b_points)]: + for skeleton in source: + skeleton_info = skeleton_infos.setdefault( + skeleton.label, self._get_skeleton_info(skeleton.label) + ) + + # Merge skeleton points into a single list + # The list is ordered by skeleton_info + skeleton_points = [ + next((p for p in skeleton.elements if p.label == sublabel), None) + for sublabel in skeleton_info + ] + + # Build a single Points object for further comparisons + merged_points = dm.Points() + merged_points.points = np.ravel( + [p.points if p else [0, 0] for p in skeleton_points] + ) + merged_points.visibility = np.ravel( + [p.visibility if p else [dm.Points.Visibility.absent] for p in skeleton_points] ) - for a in ds_sample.annotations - if isinstance(a, dm.Skeleton) - ] - gt_boxes = [ - Bbox(a.x, a.y, a.w, a.h, a.label) - for a in gt_sample.annotations - if isinstance(a, dm.Bbox) - ] - - matching_result = match_annotations( - gt_boxes, - ds_points, - similarity=similarity_fn, - min_similarity=self.min_similarity_threshold, + merged_points.label = skeleton.label + # no per-point attributes currently in CVAT + + points_map[id(merged_points)] = skeleton + skeleton_map[id(skeleton)] = merged_points + source_points.append(merged_points) + + instance_map = {} + for source in [item_a.annotations, item_b.annotations]: + for instance_group in dm.ops.find_instances(source): + instance_bbox = self._instance_bbox(instance_group) + + instance_group = [ + skeleton_map[id(a)] if isinstance(a, dm.Skeleton) else a + for a in instance_group + if not isinstance(a, dm.Skeleton) or skeleton_map[id(a)] is not None + ] + for ann in instance_group: + instance_map[id(ann)] = [instance_group, instance_bbox] + + keypoints_matcher = self._KeypointsMatcher(instance_map=instance_map, sigma=self._oks_sigma) + keypoints_similarity = CachedSimilarityFunction(keypoints_matcher.distance) + matching_result = match_annotations( + a_points, + b_points, + similarity=keypoints_similarity, + min_similarity=similarity_threshold, + ) + + distances = keypoints_similarity.cache + + matched, mismatched, a_extra, b_extra = matching_result + + matched = [(points_map[id(p_a)], points_map[id(p_b)]) for (p_a, p_b) in matched] + mismatched = [(points_map[id(p_a)], points_map[id(p_b)]) for (p_a, p_b) in mismatched] + a_extra = [points_map[id(p_a)] for p_a in a_extra] + b_extra = [points_map[id(p_b)] for p_b in b_extra] + + # Map points back to skeletons + for p_a_id, p_b_id in list(distances.keys()): + dist = distances.pop((p_a_id, p_b_id)) + distances[(id(points_map[p_a_id]), id(points_map[p_b_id]))] = dist + + similarity_fn = CachedSimilarityFunction(None) + similarity_fn.cache.update(distances) + + return MatchResult(matched, mismatched, a_extra, b_extra), similarity_fn + + def _instance_bbox( + self, instance_anns: Sequence[dm.Annotation] + ) -> Tuple[float, float, float, float]: + return dm.ops.max_bbox( + a.get_bbox() if isinstance(a, dm.Skeleton) else a + for a in instance_anns + if hasattr(a, "get_bbox") and not a.attributes.get("outside", False) + ) + + @define(kw_only=True) + class _KeypointsMatcher(dm.ops.PointsMatcher): + def distance(self, a: dm.Points, b: dm.Points) -> float: + a_bbox = self.instance_map[id(a)][1] + b_bbox = self.instance_map[id(b)][1] + if dm.ops.bbox_iou(a_bbox, b_bbox) <= 0: + return 0 + + bbox = dm.ops.mean_bbox([a_bbox, b_bbox]) + return self._compute_oks( + a, + b, + sigma=self.sigma, + bbox=bbox, + visibility_a=[v == dm.Points.Visibility.visible for v in a.visibility], + visibility_b=[v == dm.Points.Visibility.visible for v in b.visibility], ) - for gt_bbox, ds_point in itertools.chain( - matching_result.matches, - matching_result.mispred, - zip(matching_result.a_extra, itertools.repeat(None)), - zip(itertools.repeat(None), matching_result.b_extra), - ): - sim = similarity_fn(gt_bbox, ds_point) if gt_bbox and ds_point else 0 - all_similarities.append(sim) + @classmethod + def _compute_oks( + cls, + a: dm.Points, + b: dm.Points, + *, + sigma: Union[float, np.ndarray] = 0.1, + bbox: Optional[BboxCoords] = None, + scale: Union[None, float, np.ndarray] = None, + visibility_a: Union[None, bool, np.ndarray] = None, + visibility_b: Union[None, bool, np.ndarray] = None, + ) -> float: + """ + Computes Object Keypoint Similarity metric for a pair of point sets. + https://cocodataset.org/#keypoints-eval + """ + + p1 = np.array(a.points).reshape((-1, 2)) + p2 = np.array(b.points).reshape((-1, 2)) + if len(p1) != len(p2): + return 0 + + if visibility_a is None: + visibility_a = np.full(len(p1), True) + else: + visibility_a = np.asarray(visibility_a, dtype=bool) - return np.mean(all_similarities) if all_similarities else 0 + if visibility_b is None: + visibility_b = np.full(len(p2), True) + else: + visibility_b = np.asarray(visibility_b, dtype=bool) + + if not scale: + if bbox is None: + bbox = dm.ops.mean_bbox([a, b]) + scale = bbox[2] * bbox[3] + + total_vis = np.sum(visibility_a | visibility_b, dtype=float) + if not total_vis: + # We treat this situation as match. It's possible to use alternative approaches, + # such as add weight for occluded points. Our current annotation approach + # doesn't allow to distinguish between 'occluded' and 'absent' points. + return 1.0 + + dists = np.linalg.norm(p1 - p2, axis=1) + return ( + np.sum( + visibility_a + * visibility_b + * np.exp(-(dists**2) / (2 * scale * ((2 * sigma) ** 2))) + ) + / total_vis + ) diff --git a/packages/examples/cvat/recording-oracle/tests/integration/chain/test_escrow.py b/packages/examples/cvat/recording-oracle/tests/integration/chain/test_escrow.py index b3f1cc7920..4e5531db53 100644 --- a/packages/examples/cvat/recording-oracle/tests/integration/chain/test_escrow.py +++ b/packages/examples/cvat/recording-oracle/tests/integration/chain/test_escrow.py @@ -19,7 +19,7 @@ from tests.utils.constants import ( DEFAULT_GAS_PAYER_PRIV, DEFAULT_HASH, - DEFAULT_URL, + DEFAULT_MANIFEST_URL, REPUTATION_ORACLE_ADDRESS, ) from tests.utils.setup_escrow import ( @@ -69,9 +69,7 @@ def test_validate_escrow_without_funds(self): validate_escrow(-1, "", allow_no_funds=False) # should not throw an exception - validate_escrow( - self.network_config.chain_id, self.escrow_address, allow_no_funds=True - ) + validate_escrow(self.network_config.chain_id, self.escrow_address, allow_no_funds=True) def test_validate_escrow_invalid_status(self): escrow_address = create_escrow(self.w3) @@ -85,9 +83,7 @@ def test_validate_escrow_invalid_status(self): with patch("src.chain.escrow.get_escrow") as mock_get_escrow: mock_get_escrow.return_value = self.escrow("Partial", 0.95) - with pytest.raises( - ValueError, match="Escrow is not in any of the accepted states" - ): + with pytest.raises(ValueError, match="Escrow is not in any of the accepted states"): validate_escrow(self.w3.eth.chain_id, escrow_address) def test_get_escrow_manifest(self): @@ -97,9 +93,7 @@ def test_get_escrow_manifest(self): mock_download.return_value = json.dumps({"title": "test"}).encode() mock_get_escrow.return_value = self.escrow() - manifest = get_escrow_manifest( - self.network_config.chain_id, self.escrow_address - ) + manifest = get_escrow_manifest(self.network_config.chain_id, self.escrow_address) self.assertIsInstance(manifest, dict) self.assertIsNotNone(manifest) @@ -108,22 +102,18 @@ def test_store_results(self): with patch("src.chain.escrow.get_web3") as mock_function: mock_function.return_value = self.w3 results = store_results( - self.w3.eth.chain_id, escrow_address, DEFAULT_URL, DEFAULT_HASH + self.w3.eth.chain_id, escrow_address, DEFAULT_MANIFEST_URL, DEFAULT_HASH ) self.assertIsNone(results) - intermediate_results_url = get_intermediate_results_url( - self.w3, escrow_address - ) - self.assertEqual(intermediate_results_url, DEFAULT_URL) + intermediate_results_url = get_intermediate_results_url(self.w3, escrow_address) + self.assertEqual(intermediate_results_url, DEFAULT_MANIFEST_URL) def test_store_results_invalid_url(self): escrow_address = create_escrow(self.w3) with patch("src.chain.escrow.get_web3") as mock_function: mock_function.return_value = self.w3 with self.assertRaises(EscrowClientError) as error: - store_results( - self.w3.eth.chain_id, escrow_address, "invalid_url", DEFAULT_HASH - ) + store_results(self.w3.eth.chain_id, escrow_address, "invalid_url", DEFAULT_HASH) self.assertEqual(f"Invalid URL: invalid_url", str(error.exception)) def test_store_results_invalid_hash(self): @@ -131,7 +121,7 @@ def test_store_results_invalid_hash(self): with patch("src.chain.escrow.get_web3") as mock_function: mock_function.return_value = self.w3 with self.assertRaises(EscrowClientError) as error: - store_results(self.w3.eth.chain_id, escrow_address, DEFAULT_URL, "") + store_results(self.w3.eth.chain_id, escrow_address, DEFAULT_MANIFEST_URL, "") self.assertEqual(f"Invalid empty hash", str(error.exception)) def test_get_reputation_oracle_address(self): @@ -143,9 +133,7 @@ def test_get_reputation_oracle_address(self): mock_escrow = MagicMock() mock_escrow.reputation_oracle = REPUTATION_ORACLE_ADDRESS mock_get_escrow.return_value = mock_escrow - address = get_reputation_oracle_address( - self.w3.eth.chain_id, escrow_address - ) + address = get_reputation_oracle_address(self.w3.eth.chain_id, escrow_address) self.assertIsInstance(address, str) self.assertIsNotNone(address) @@ -157,6 +145,4 @@ def test_get_reputation_oracle_address_invalid_address(self): def test_get_reputation_oracle_address_invalid_chain_id(self): with pytest.raises(Exception, match="Can't find escrow"): - get_reputation_oracle_address( - 1, "0x1234567890123456789012345678901234567890" - ) + get_reputation_oracle_address(1, "0x1234567890123456789012345678901234567890") diff --git a/packages/examples/cvat/recording-oracle/tests/integration/chain/test_kvstore.py b/packages/examples/cvat/recording-oracle/tests/integration/chain/test_kvstore.py index 4f99224538..ec88c3f3b7 100644 --- a/packages/examples/cvat/recording-oracle/tests/integration/chain/test_kvstore.py +++ b/packages/examples/cvat/recording-oracle/tests/integration/chain/test_kvstore.py @@ -9,7 +9,11 @@ from src.chain.kvstore import get_reputation_oracle_url, get_role_by_address -from tests.utils.constants import DEFAULT_GAS_PAYER_PRIV, DEFAULT_URL, REPUTATION_ORACLE_ADDRESS +from tests.utils.constants import ( + DEFAULT_GAS_PAYER_PRIV, + DEFAULT_MANIFEST_URL, + REPUTATION_ORACLE_ADDRESS, +) from tests.utils.setup_escrow import create_escrow from tests.utils.setup_kvstore import store_kvstore_value @@ -28,7 +32,7 @@ def setUp(self): def test_get_reputation_oracle_url(self): escrow_address = create_escrow(self.w3) - store_kvstore_value("webhook_url", DEFAULT_URL) + store_kvstore_value("webhook_url", DEFAULT_MANIFEST_URL) with ( patch("src.chain.kvstore.get_web3") as mock_get_web3, @@ -39,12 +43,10 @@ def test_get_reputation_oracle_url(self): mock_escrow = Mock() mock_escrow.reputationOracle = REPUTATION_ORACLE_ADDRESS mock_get_escrow.return_value = mock_escrow - mock_leader.return_value = MagicMock(webhook_url=DEFAULT_URL) + mock_leader.return_value = MagicMock(webhook_url=DEFAULT_MANIFEST_URL) - reputation_url = get_reputation_oracle_url( - self.w3.eth.chain_id, escrow_address - ) - self.assertEqual(reputation_url, DEFAULT_URL) + reputation_url = get_reputation_oracle_url(self.w3.eth.chain_id, escrow_address) + self.assertEqual(reputation_url, DEFAULT_MANIFEST_URL) def test_get_reputation_oracle_url_invalid_escrow(self): with patch("src.chain.kvstore.get_web3") as mock_function: @@ -62,7 +64,7 @@ def test_get_reputation_oracle_url_invalid_address(self): ): mock_get_web3.return_value = self.w3 mock_escrow = Mock() - mock_escrow.reputationOracle = REPUTATION_ORACLE_ADDRESS + mock_escrow.reputation_oracle = REPUTATION_ORACLE_ADDRESS mock_get_escrow.return_value = mock_escrow mock_leader.return_value = MagicMock(webhook_url="") @@ -75,9 +77,7 @@ def test_get_role_by_address(self): store_kvstore_value("role", "Reputation Oracle") with patch("src.chain.kvstore.get_web3") as mock_function: mock_function.return_value = self.w3 - reputation_url = get_role_by_address( - self.w3.eth.chain_id, REPUTATION_ORACLE_ADDRESS - ) + reputation_url = get_role_by_address(self.w3.eth.chain_id, REPUTATION_ORACLE_ADDRESS) self.assertEqual(reputation_url, "Reputation Oracle") def test_get_role_by_address_invalid_escrow(self): @@ -92,7 +92,5 @@ def test_get_role_by_address_invalid_address(self): store_kvstore_value("role", "") with patch("src.chain.kvstore.get_web3") as mock_function: mock_function.return_value = self.w3 - reputation_url = get_role_by_address( - self.w3.eth.chain_id, REPUTATION_ORACLE_ADDRESS - ) + reputation_url = get_role_by_address(self.w3.eth.chain_id, REPUTATION_ORACLE_ADDRESS) self.assertEqual(reputation_url, "") diff --git a/packages/examples/cvat/recording-oracle/tests/integration/cron/test_process_exchange_oracle_webhooks.py b/packages/examples/cvat/recording-oracle/tests/integration/cron/test_process_exchange_oracle_webhooks.py index 045d9a7064..f94eb61eba 100644 --- a/packages/examples/cvat/recording-oracle/tests/integration/cron/test_process_exchange_oracle_webhooks.py +++ b/packages/examples/cvat/recording-oracle/tests/integration/cron/test_process_exchange_oracle_webhooks.py @@ -11,7 +11,7 @@ from src.core.config import StorageConfig from src.core.types import ( - ExchangeOracleEventType, + ExchangeOracleEventTypes, Networks, OracleWebhookStatuses, OracleWebhookTypes, @@ -22,7 +22,7 @@ ) from src.db import SessionLocal from src.models.webhook import Webhook -from src.services.webhook import OracleWebhookDirectionTag +from src.services.webhook import OracleWebhookDirectionTags from src.utils.logging import get_function_logger from tests.utils.constants import DEFAULT_GAS_PAYER_PRIV, RECORDING_ORACLE_FEE, SIGNATURE @@ -47,13 +47,13 @@ def tearDown(self): def make_webhook(self, escrow_address): return Webhook( id=str(uuid.uuid4()), - direction=OracleWebhookDirectionTag.incoming.value, + direction=OracleWebhookDirectionTags.incoming.value, signature=SIGNATURE, escrow_address=escrow_address, chain_id=Networks.localhost.value, type=OracleWebhookTypes.exchange_oracle.value, status=OracleWebhookStatuses.pending.value, - event_type=ExchangeOracleEventType.task_finished.value, + event_type=ExchangeOracleEventTypes.task_finished.value, ) def test_process_exchange_oracle_webhook(self): diff --git a/packages/examples/cvat/recording-oracle/tests/integration/cron/test_process_reputation_oracle_webhooks.py b/packages/examples/cvat/recording-oracle/tests/integration/cron/test_process_reputation_oracle_webhooks.py index 06b0b86bf7..a31c512f85 100644 --- a/packages/examples/cvat/recording-oracle/tests/integration/cron/test_process_reputation_oracle_webhooks.py +++ b/packages/examples/cvat/recording-oracle/tests/integration/cron/test_process_reputation_oracle_webhooks.py @@ -11,12 +11,12 @@ Networks, OracleWebhookStatuses, OracleWebhookTypes, - RecordingOracleEventType, + RecordingOracleEventTypes, ) from src.crons.process_reputation_oracle_webhooks import process_outgoing_reputation_oracle_webhooks from src.db import SessionLocal from src.models.webhook import Webhook -from src.services.webhook import OracleWebhookDirectionTag +from src.services.webhook import OracleWebhookDirectionTags from tests.utils.constants import DEFAULT_GAS_PAYER_PRIV, SIGNATURE from tests.utils.setup_escrow import create_escrow @@ -41,13 +41,13 @@ def tearDown(self): def get_webhook(self, escrow_address, chain_id, event_data): return Webhook( id=str(uuid.uuid4()), - direction=OracleWebhookDirectionTag.outgoing.value, + direction=OracleWebhookDirectionTags.outgoing.value, signature=SIGNATURE, escrow_address=escrow_address, chain_id=chain_id, type=OracleWebhookTypes.reputation_oracle.value, status=OracleWebhookStatuses.pending.value, - event_type=RecordingOracleEventType.task_completed, + event_type=RecordingOracleEventTypes.task_completed, event_data=event_data, ) @@ -89,9 +89,9 @@ def test_process_reputation_oracle_webhooks(self): expected_url, headers={"human-signature": SIGNATURE}, json={ - "escrowAddress": escrow_address, - "chainId": chain_id, - "eventType": RecordingOracleEventType.task_completed.value, + "escrow_address": escrow_address, + "chain_id": chain_id, + "event_type": RecordingOracleEventTypes.task_completed.value, }, ) self.assertEqual(updated_webhook.status, OracleWebhookStatuses.completed.value) diff --git a/packages/examples/cvat/recording-oracle/tests/integration/endpoints/test_webhook.py b/packages/examples/cvat/recording-oracle/tests/integration/endpoints/test_webhook.py index 22f10e617c..13d19f0687 100644 --- a/packages/examples/cvat/recording-oracle/tests/integration/endpoints/test_webhook.py +++ b/packages/examples/cvat/recording-oracle/tests/integration/endpoints/test_webhook.py @@ -5,7 +5,7 @@ from fastapi.testclient import TestClient from src.chain.web3 import sign_message -from src.core.types import ExchangeOracleEventType, Networks +from src.core.types import ExchangeOracleEventTypes, Networks from src.db import SessionLocal from src.endpoints.webhook import router from src.models.webhook import Webhook @@ -32,7 +32,7 @@ def test_receive_oracle_webhook_client(self, mock_get_escrow): escrow_address = "0x" + "".join([str(random.randint(0, 9)) for _ in range(40)]) chain_id = Networks.localhost - event_type = ExchangeOracleEventType.task_finished.value + event_type = ExchangeOracleEventTypes.task_finished.value message = { "escrow_address": escrow_address, @@ -49,9 +49,7 @@ def test_receive_oracle_webhook_client(self, mock_get_escrow): assert response.status_code == 200 response_body = response.json() - webhook = ( - self.session.query(Webhook).where(Webhook.id == response_body["id"]).one() - ) + webhook = self.session.query(Webhook).where(Webhook.id == response_body["id"]).one() assert webhook is not None assert webhook.escrow_address == escrow_address assert webhook.chain_id == chain_id diff --git a/packages/examples/cvat/recording-oracle/tests/integration/services/cloud/test_client_service.py b/packages/examples/cvat/recording-oracle/tests/integration/services/cloud/test_client_service.py index a71ec3d0d4..75cf94eec5 100644 --- a/packages/examples/cvat/recording-oracle/tests/integration/services/cloud/test_client_service.py +++ b/packages/examples/cvat/recording-oracle/tests/integration/services/cloud/test_client_service.py @@ -4,7 +4,7 @@ import pytest from botocore.exceptions import ClientError, EndpointConnectionError -from src.services.cloud import S3Client +from src.services.cloud.s3 import S3Client class ServiceIntegrationTest(unittest.TestCase): @@ -25,49 +25,56 @@ def tearDown(self): self.client.delete_bucket(Bucket=self.bucket_name) def test_file_operations(self): - client = S3Client(self.url, access_key=self.access_key, secret_key=self.secret) + client = S3Client( + endpoint_url=self.url, + bucket=self.bucket_name, + access_key=self.access_key, + secret_key=self.secret, + ) - assert len(client.list_files(self.bucket_name)) == 0 + assert len(client.list_files()) == 0 file_name = "test_file" data = "this is a test".encode("utf-8") - assert not client.file_exists(self.bucket_name, file_name) - client.create_file(self.bucket_name, file_name, data) - assert client.file_exists(self.bucket_name, file_name) - assert len(client.list_files(self.bucket_name)) == 1 + assert not client.file_exists(file_name) + client.create_file(file_name, data) + assert client.file_exists(file_name) + assert len(client.list_files()) == 1 - file_content = client.download_fileobj(bucket=self.bucket_name, key=file_name) + file_content = client.download_file(key=file_name) assert file_content == data - client.remove_file(self.bucket_name, file_name) - assert not client.file_exists(self.bucket_name, file_name) + client.remove_file(file_name) + assert not client.file_exists(file_name) def test_degenerate_file_operations(self): - client = S3Client(self.url, access_key=self.access_key, secret_key=self.secret) + client = S3Client(endpoint_url=self.url, access_key=self.access_key, secret_key=self.secret) invalid_bucket = "non-existent-bucket" invalid_file = "non-existent-file" with pytest.raises(ClientError): - client.download_fileobj(bucket=invalid_bucket, key=invalid_file) + client.download_file(invalid_file, bucket=invalid_bucket) with pytest.raises(ClientError): - client.download_fileobj(bucket=self.bucket_name, key=invalid_file) + client.download_file(invalid_file, bucket=self.bucket_name) with pytest.raises(ClientError): - client.create_file(bucket=invalid_bucket, filename=invalid_file) + client.create_file(invalid_file, bucket=invalid_bucket) with pytest.raises(ClientError): client.list_files(bucket=invalid_bucket) - client.remove_file(bucket=self.bucket_name, filename=invalid_file) + client.remove_file(invalid_file, bucket=self.bucket_name) def test_degenerate_client(self): with pytest.raises(EndpointConnectionError): invalid_client = S3Client( - "http://not.an.url:1234", access_key=self.access_key, secret_key=self.secret + endpoint_url="http://not.an.url:1234", + access_key=self.access_key, + secret_key=self.secret, ) - invalid_client.create_file(self.bucket_name, "test.txt") + invalid_client.create_file("test.txt", bucket=self.bucket_name) with pytest.raises(ValueError): - S3Client("nonsense-stuff") + S3Client(endpoint_url="nonsense-stuff") diff --git a/packages/examples/cvat/recording-oracle/tests/integration/services/test_webhook_service.py b/packages/examples/cvat/recording-oracle/tests/integration/services/test_webhook_service.py index 124c1ec953..a7cf4673d4 100644 --- a/packages/examples/cvat/recording-oracle/tests/integration/services/test_webhook_service.py +++ b/packages/examples/cvat/recording-oracle/tests/integration/services/test_webhook_service.py @@ -7,7 +7,7 @@ from src.core.types import Networks, OracleWebhookStatuses, OracleWebhookTypes from src.db import SessionLocal from src.models.webhook import Webhook -from src.services.webhook import OracleWebhookDirectionTag, inbox +from src.services.webhook import OracleWebhookDirectionTags, inbox class ServiceIntegrationTest(unittest.TestCase): @@ -30,7 +30,7 @@ def dummy_webhook(self, oracle_webhook_type: OracleWebhookTypes, status: OracleW address = "0x" + "".join([str(random.randint(0, 9)) for _ in range(40)]) return Webhook( id=str(uuid.uuid4()), - direction=OracleWebhookDirectionTag.incoming.value, + direction=OracleWebhookDirectionTags.incoming.value, signature=f"signature-{uuid.uuid4()}", escrow_address=address, chain_id=Networks.polygon_mainnet.value, diff --git a/packages/examples/cvat/recording-oracle/tests/utils/constants.py b/packages/examples/cvat/recording-oracle/tests/utils/constants.py index 07dde21a4a..900ab8574d 100644 --- a/packages/examples/cvat/recording-oracle/tests/utils/constants.py +++ b/packages/examples/cvat/recording-oracle/tests/utils/constants.py @@ -4,6 +4,7 @@ RECORDING_ORACLE_ADDRESS = "0x70997970C51812dc3A010C7d01b50e0d17dc79C8" RECORDING_ORACLE_FEE = 10 +REPUTATION_ORACLE_WEBHOOK_URL = "http://localhost:5001/webhook/cvat" REPUTATION_ORACLE_ADDRESS = "0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC" REPUTATION_ORACLE_PRIV = "5de4111afa1a4b94908f83103eb1f1706367c2e68ca870fc3fb9a804cdab365a" REPUTATION_ORACLE_FEE = 10 @@ -11,7 +12,7 @@ EXCHANGE_ORACLE_ADDRESS = "0x90F79bf6EB2c4f870365E785982E1f101E93b906" EXCHANGE_ORACLE_FEE = 10 -DEFAULT_URL = "http://host.docker.internal:9000/manifests/manifest.json" +DEFAULT_MANIFEST_URL = "http://host.docker.internal:9000/manifests/manifest.json" DEFAULT_HASH = "test" SIGNATURE = "0xa0c5626301e3c198cb91356e492890c0c28db8c37044846134939246911a693c4d7116d04aa4bc40a41077493868b8dd533d30980f6addb28d1b3610a84cb4091c" diff --git a/packages/examples/cvat/recording-oracle/tests/utils/setup_escrow.py b/packages/examples/cvat/recording-oracle/tests/utils/setup_escrow.py index a225373349..afc161a1ed 100644 --- a/packages/examples/cvat/recording-oracle/tests/utils/setup_escrow.py +++ b/packages/examples/cvat/recording-oracle/tests/utils/setup_escrow.py @@ -7,7 +7,7 @@ from tests.utils.constants import ( DEFAULT_HASH, - DEFAULT_URL, + DEFAULT_MANIFEST_URL, EXCHANGE_ORACLE_ADDRESS, EXCHANGE_ORACLE_FEE, JOB_REQUESTER_ID, @@ -36,7 +36,7 @@ def create_escrow(web3: Web3): recording_oracle_fee=RECORDING_ORACLE_FEE, reputation_oracle_address=REPUTATION_ORACLE_ADDRESS, reputation_oracle_fee=REPUTATION_ORACLE_FEE, - manifest_url=DEFAULT_URL, + manifest_url=DEFAULT_MANIFEST_URL, hash=DEFAULT_HASH, ), ) @@ -50,7 +50,9 @@ def fund_escrow(web3: Web3, escrow_address: str): def bulk_payout(web3: Web3, escrow_address: str, recipient: str, amount: Decimal): escrow_client = EscrowClient(web3) - escrow_client.bulk_payout(escrow_address, [recipient], [amount], DEFAULT_URL, DEFAULT_HASH, 1) + escrow_client.bulk_payout( + escrow_address, [recipient], [amount], DEFAULT_MANIFEST_URL, DEFAULT_HASH, 1 + ) def get_intermediate_results_url(web3: Web3, escrow_address: str): From 980bde75734872e17f7e15e4b3d154c4e276b9cb Mon Sep 17 00:00:00 2001 From: Dzeranov Date: Thu, 28 Mar 2024 18:38:53 +0300 Subject: [PATCH 12/66] [Job Launcher] Multiple typos fixed (#1781) * Fixed typo in `/webhook/process` endpoint * Fixed typo in `handleWebhook` method --- .../src/modules/cron-job/cron.job.controller.ts | 2 +- .../server/src/modules/webhook/webhook.service.ts | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/apps/job-launcher/server/src/modules/cron-job/cron.job.controller.ts b/packages/apps/job-launcher/server/src/modules/cron-job/cron.job.controller.ts index d6d91c6fe0..73935d7ceb 100644 --- a/packages/apps/job-launcher/server/src/modules/cron-job/cron.job.controller.ts +++ b/packages/apps/job-launcher/server/src/modules/cron-job/cron.job.controller.ts @@ -109,7 +109,7 @@ export class CronJobController { }) @ApiBearerAuth() @UseGuards(CronAuthGuard) - @Get('/wehbhook/process') + @Get('/webhook/process') public async processPendingWebhooks(): Promise { await this.cronJobService.processPendingWebhooks(); return; diff --git a/packages/apps/job-launcher/server/src/modules/webhook/webhook.service.ts b/packages/apps/job-launcher/server/src/modules/webhook/webhook.service.ts index 2210a67c3f..1a8ad6bd40 100644 --- a/packages/apps/job-launcher/server/src/modules/webhook/webhook.service.ts +++ b/packages/apps/job-launcher/server/src/modules/webhook/webhook.service.ts @@ -146,23 +146,23 @@ export class WebhookService { this.webhookRepository.updateOne(webhookEntity); } - public async handleWebhook(wehbook: WebhookDataDto): Promise { - switch (wehbook.eventType) { + public async handleWebhook(webhook: WebhookDataDto): Promise { + switch (webhook.eventType) { case EventType.ESCROW_COMPLETED: - await this.jobService.completeJob(wehbook); + await this.jobService.completeJob(webhook); break; case EventType.TASK_CREATION_FAILED: - await this.jobService.escrowFailedWebhook(wehbook); + await this.jobService.escrowFailedWebhook(webhook); break; case EventType.ESCROW_FAILED: - await this.jobService.escrowFailedWebhook(wehbook); + await this.jobService.escrowFailedWebhook(webhook); break; default: throw new BadRequestException( - `Invalid webhook event type: ${wehbook.eventType}`, + `Invalid webhook event type: ${webhook.eventType}`, ); } } From 3cbb38d184a3d824b23de37f7b54f844da7c5e6a Mon Sep 17 00:00:00 2001 From: Maxim Zhiltsov Date: Thu, 28 Mar 2024 18:52:59 +0300 Subject: [PATCH 13/66] [CVAT-M2] Updates for the annotation experiment (#1780) * Fix job event handling when no assignments exist * Implement boxes from points task creation * Refactor cloud storage api * Implement job downloading * Estimate max bbox * Little refactoring * Copy some shared code from excor to recor * Refactor task creation and export * Implement boxes from points validation * Integrate SDK updates * Fix escrow access * [Exchange/Recoring oracles] Add GCS support * Remove local dev mocks * Remove some extra changes * Update tests * Update some tests * Remove extra launcher change * Refactor code * Add google-cloud-storage dependency * Fix exception classes * Set env variables in docker-compose.test.yml * [CVAT] Points to boxes task (#1560) * Fix job event handling when no assignments exist * Implement boxes from points task creation * Refactor cloud storage api * Implement job downloading * Estimate max bbox * Little refactoring * Copy some shared code from excor to recor * Refactor task creation and export * Implement boxes from points validation * Integrate SDK updates * Fix escrow access * Remove local dev mocks * Remove some extra changes * Update tests * Update some tests * Remove extra launcher change * Use virtual hosted bucket style * Fix creating CS in CVAT * Fix tests * Add basic implementation for skeletons from boxes * Refactor and fix some errors * Implement job uploading * Implement downloading * Fix extra gt boxes in merged results from excor * Implement skeleton matching * Fix labels in merged annotations * Update project events handling in excor * Improve oks sigma comment * Remove local testing assets * Fix label mapping * Add extra skeleton validations in manifest * Update .env template * Update several comments * Fix failing tests * Update enum name * Add more annotation validations for skeletons * Extend input annotations validation * Fix quality computation, refactor * Unify assignment accuracy checks between different job types * Refactor GT downloading for validation * Add gcs support - fixes (#1) * Update code formatting * Update poetry lock * Refactor * Use dict for gcs file contents * Update class name * Add backward compatibility for data bucket env var * Remove extra changes * Remove extra import * Fix enum name * Update bucket access info parsing * Fix error type in cloud provider parsing * Update tests * Add job annotation mode param into assignment links * Add CD trigger for experimental cvat oracles * Update code formatting * Fix test * Format code * Fix bucket uses * Fix result sending * Remove extra changes * Fix extra code * Remove unused code * Align enum naming convention * Use relative paths in bucket in task creation * Improve error messages * Improve skeleton and bbox validation * Fix escrow manifest downloading * Update tests * Fix tests * Implement general image bans * Fix import * Make default gt ban threshold more strict * Fix comparison for absent points * Fix comparison for omitted points in jobs * Finish escrows with too many unverifiable assignments * Check if an increased healthcheck interval will fix unhealthy container (#1700) * Fix validations for boxes from points task creation * Clean code * disable roi estimation for unreliable cases * Fix manifest parsing * Remove m0 launcher stubs * Remove m0 rep or stubs * Make max assignment time optional in manifest * Make label type fully optional in manifest * Fix test * Enable and fix a disabled m1 task creation test * Don't fail on too many unused GT in boxes from points * Make joints optional in skeletons from boxes manifests * Review gt exclusion errors * Fix RecOr tests --------- Co-authored-by: maya Co-authored-by: Ivan Co-authored-by: Dzeranov --- .../cvat/exchange-oracle/src/core/manifest.py | 2 +- .../src/handlers/job_creation.py | 18 +++++++++--------- .../cvat/recording-oracle/src/core/manifest.py | 2 +- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/packages/examples/cvat/exchange-oracle/src/core/manifest.py b/packages/examples/cvat/exchange-oracle/src/core/manifest.py index 24cfd750b2..c8a4c2260d 100644 --- a/packages/examples/cvat/exchange-oracle/src/core/manifest.py +++ b/packages/examples/cvat/exchange-oracle/src/core/manifest.py @@ -76,7 +76,7 @@ class SkeletonLabelInfo(LabelInfoBase): ] """ - joints: List[Tuple[int, int]] + joints: Optional[List[Tuple[int, int]]] = Field(default_factory=list) "A list of node adjacency, e.g. [[0, 1], [1, 2], [1, 3]]" @root_validator diff --git a/packages/examples/cvat/exchange-oracle/src/handlers/job_creation.py b/packages/examples/cvat/exchange-oracle/src/handlers/job_creation.py index 6f4c1ee236..041818ed71 100644 --- a/packages/examples/cvat/exchange-oracle/src/handlers/job_creation.py +++ b/packages/examples/cvat/exchange-oracle/src/handlers/job_creation.py @@ -352,7 +352,7 @@ def _validate_gt_annotations(self): if ( excluded_gt_info.excluded_count - > excluded_gt_info.total_count * self.max_discarded_threshold + > ceil(excluded_gt_info.total_count * self.max_discarded_threshold) ): raise TooFewSamples( "Too many GT boxes discarded, canceling job creation. Errors: {}".format( @@ -512,7 +512,7 @@ def _validate_skeleton(skeleton: dm.Skeleton, *, sample_bbox: dm.Bbox): if ( excluded_points_info.excluded_count - > excluded_points_info.total_count * self.max_discarded_threshold + > ceil(excluded_points_info.total_count * self.max_discarded_threshold) ): raise TooFewSamples( "Too many points discarded, canceling job creation. Errors: {}".format( @@ -614,7 +614,7 @@ def _prepare_gt(self): sample_id=gt_sample.id, sample_subset=gt_sample.subset, ) - excluded_gt_info.excluded_count += 1 + # Don't need to count as excluded, because it's not an error in annotations continue elif len(matched_skeletons) == 0: # Handle unmatched boxes @@ -650,7 +650,7 @@ def _prepare_gt(self): if ( excluded_gt_info.excluded_count - > excluded_gt_info.total_count * self.max_discarded_threshold + > ceil(excluded_gt_info.total_count * self.max_discarded_threshold) ): raise DatasetValidationError( "Too many GT boxes discarded ({} out of {}). " @@ -1327,7 +1327,7 @@ def _validate_skeleton(skeleton: dm.Skeleton, *, sample_bbox: dm.Bbox): if ( excluded_gt_info.excluded_count - > excluded_gt_info.total_count * self.max_discarded_threshold + > ceil(excluded_gt_info.total_count * self.max_discarded_threshold) ): raise TooFewSamples( "Too many GT skeletons discarded, canceling job creation. Errors: {}".format( @@ -1430,7 +1430,7 @@ def _validate_boxes_annotations(self): if ( excluded_boxes_info.excluded_count - > excluded_boxes_info.total_count * self.max_discarded_threshold + > ceil(excluded_boxes_info.total_count * self.max_discarded_threshold) ): raise TooFewSamples( "Too many boxes discarded, canceling job creation. Errors: {}".format( @@ -1580,7 +1580,7 @@ def _prepare_gt(self): sample_id=gt_sample.id, sample_subset=gt_sample.subset, ) - excluded_gt_info.excluded_count += 1 + # Don't need to count as excluded, because it's not an error in annotations continue elif len(matched_boxes) == 0: # Handle unmatched boxes @@ -1620,8 +1620,8 @@ def _prepare_gt(self): ) if ( - len(skeleton_bbox_mapping) - < (1 - self.max_discarded_threshold) * excluded_gt_info.total_count + excluded_gt_info.excluded_count + > ceil(self.max_discarded_threshold * excluded_gt_info.total_count) ): raise DatasetValidationError( "Too many GT skeletons discarded ({} out of {}). " diff --git a/packages/examples/cvat/recording-oracle/src/core/manifest.py b/packages/examples/cvat/recording-oracle/src/core/manifest.py index 24cfd750b2..c8a4c2260d 100644 --- a/packages/examples/cvat/recording-oracle/src/core/manifest.py +++ b/packages/examples/cvat/recording-oracle/src/core/manifest.py @@ -76,7 +76,7 @@ class SkeletonLabelInfo(LabelInfoBase): ] """ - joints: List[Tuple[int, int]] + joints: Optional[List[Tuple[int, int]]] = Field(default_factory=list) "A list of node adjacency, e.g. [[0, 1], [1, 2], [1, 3]]" @root_validator From b9109e421b603d8bbc718059e8707ae92acf8af7 Mon Sep 17 00:00:00 2001 From: Dzeranov Date: Fri, 29 Mar 2024 13:13:36 +0300 Subject: [PATCH 14/66] Changed unique index for webhooks table (#1786) Enabled signature for CVAT oracles Check not only for 201 status but for 200 too when webhook is being sent --- .../1711705135695-updateWebhookIndex.ts | 23 +++++++++++++++++++ .../src/modules/cron-job/cron-job.service.ts | 4 +--- .../server/src/modules/job/job.service.ts | 2 +- .../src/modules/webhook/webhook.entity.ts | 2 +- .../src/modules/webhook/webhook.service.ts | 2 +- 5 files changed, 27 insertions(+), 6 deletions(-) create mode 100644 packages/apps/job-launcher/server/src/database/migrations/1711705135695-updateWebhookIndex.ts diff --git a/packages/apps/job-launcher/server/src/database/migrations/1711705135695-updateWebhookIndex.ts b/packages/apps/job-launcher/server/src/database/migrations/1711705135695-updateWebhookIndex.ts new file mode 100644 index 0000000000..737f289b3b --- /dev/null +++ b/packages/apps/job-launcher/server/src/database/migrations/1711705135695-updateWebhookIndex.ts @@ -0,0 +1,23 @@ +import { MigrationInterface, QueryRunner } from 'typeorm'; + +export class UpdateWebhookIndex1711705135695 implements MigrationInterface { + name = 'UpdateWebhookIndex1711705135695'; + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + DROP INDEX "hmt"."IDX_7449312cababf4bb89c681e986" + `); + await queryRunner.query(` + CREATE UNIQUE INDEX "IDX_012a8481fc9980fcc49f3f0dc2" ON "hmt"."webhook" ("chain_id", "escrow_address", "event_type") + `); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + DROP INDEX "hmt"."IDX_012a8481fc9980fcc49f3f0dc2" + `); + await queryRunner.query(` + CREATE UNIQUE INDEX "IDX_7449312cababf4bb89c681e986" ON "hmt"."webhook" ("chain_id", "escrow_address") + `); + } +} diff --git a/packages/apps/job-launcher/server/src/modules/cron-job/cron-job.service.ts b/packages/apps/job-launcher/server/src/modules/cron-job/cron-job.service.ts index 846f9ec74a..ffd3c63b36 100644 --- a/packages/apps/job-launcher/server/src/modules/cron-job/cron-job.service.ts +++ b/packages/apps/job-launcher/server/src/modules/cron-job/cron-job.service.ts @@ -218,9 +218,7 @@ export class CronJobService { chainId: jobEntity.chainId, eventType: EventType.ESCROW_CANCELED, oracleType, - hasSignature: - (manifest as FortuneManifestDto).requestType === - JobRequestType.FORTUNE, + hasSignature: true, }); await this.webhookRepository.createUnique(webhookEntity); } diff --git a/packages/apps/job-launcher/server/src/modules/job/job.service.ts b/packages/apps/job-launcher/server/src/modules/job/job.service.ts index 21de196a45..535df1142b 100644 --- a/packages/apps/job-launcher/server/src/modules/job/job.service.ts +++ b/packages/apps/job-launcher/server/src/modules/job/job.service.ts @@ -815,7 +815,7 @@ export class JobService { chainId: jobEntity.chainId, eventType: EventType.ESCROW_CREATED, oracleType: oracleType, - hasSignature: oracleType === OracleType.FORTUNE, + hasSignature: oracleType !== OracleType.HCAPTCHA ? true : false, }); await this.webhookRepository.createUnique(webhookEntity); diff --git a/packages/apps/job-launcher/server/src/modules/webhook/webhook.entity.ts b/packages/apps/job-launcher/server/src/modules/webhook/webhook.entity.ts index 424fb59f55..d376b0e5bf 100644 --- a/packages/apps/job-launcher/server/src/modules/webhook/webhook.entity.ts +++ b/packages/apps/job-launcher/server/src/modules/webhook/webhook.entity.ts @@ -10,7 +10,7 @@ import { import { ChainId } from '@human-protocol/sdk'; @Entity({ schema: NS, name: 'webhook' }) -@Index(['chainId', 'escrowAddress'], { unique: true }) +@Index(['chainId', 'escrowAddress', 'eventType'], { unique: true }) export class WebhookEntity extends BaseEntity { @Column({ type: 'int' }) public chainId: ChainId; diff --git a/packages/apps/job-launcher/server/src/modules/webhook/webhook.service.ts b/packages/apps/job-launcher/server/src/modules/webhook/webhook.service.ts index 1a8ad6bd40..2abfacac91 100644 --- a/packages/apps/job-launcher/server/src/modules/webhook/webhook.service.ts +++ b/packages/apps/job-launcher/server/src/modules/webhook/webhook.service.ts @@ -89,7 +89,7 @@ export class WebhookService { ); // Check if the request was successful. - if (status !== HttpStatus.CREATED) { + if (status !== HttpStatus.CREATED && status !== HttpStatus.OK) { this.logger.log(ErrorWebhook.NotSent, WebhookService.name); throw new NotFoundException(ErrorWebhook.NotSent); } From 42e8a58f649ed834deab253946c46f46ce4e2a5e Mon Sep 17 00:00:00 2001 From: Maxim Zhiltsov Date: Fri, 29 Mar 2024 14:34:33 +0300 Subject: [PATCH 15/66] [CVAT-M2] Fix several problems (#1787) * Fix project cancellation for skeletons from boxes jobs * Remove m0 launcher compatibility stub in webhook receiving of exchange oracle * Fix reported assignment size --- .../crons/process_job_launcher_webhooks.py | 41 ++++++++++--------- .../exchange-oracle/src/endpoints/webhook.py | 10 +---- .../exchange-oracle/src/services/exchange.py | 3 +- .../exchange-oracle/src/utils/assignments.py | 9 ++++ 4 files changed, 33 insertions(+), 30 deletions(-) diff --git a/packages/examples/cvat/exchange-oracle/src/crons/process_job_launcher_webhooks.py b/packages/examples/cvat/exchange-oracle/src/crons/process_job_launcher_webhooks.py index a3f527fa0b..1e3fcfc7be 100644 --- a/packages/examples/cvat/exchange-oracle/src/crons/process_job_launcher_webhooks.py +++ b/packages/examples/cvat/exchange-oracle/src/crons/process_job_launcher_webhooks.py @@ -116,10 +116,10 @@ def handle_job_launcher_event(webhook: Webhook, *, db_session: Session, logger: accepted_states=[EscrowStatus.Pending, EscrowStatus.Cancelled], ) - project = cvat_db_service.get_project_by_escrow_address( - db_session, webhook.escrow_address, for_update=True + projects = cvat_db_service.get_projects_by_escrow_address( + db_session, webhook.escrow_address, for_update=True, limit=None ) - if not project: + if not projects: logger.error( "Received escrow cancel event " f"(escrow_address={webhook.escrow_address}). " @@ -127,24 +127,25 @@ def handle_job_launcher_event(webhook: Webhook, *, db_session: Session, logger: ) return - if project.status in [ - ProjectStatuses.canceled, - ProjectStatuses.recorded, - ]: - logger.error( - "Received escrow cancel event " - f"(escrow_address={webhook.escrow_address}). " - "The project already finished, ignoring" + for project in projects: + if project.status in [ + ProjectStatuses.canceled, + ProjectStatuses.recorded, + ]: + logger.error( + "Received escrow cancel event " + f"(escrow_address={webhook.escrow_address}). " + "The project is already finished, ignoring" + ) + continue + + logger.info( + f"Received escrow cancel event (escrow_address={webhook.escrow_address}). " + "Canceling the project" + ) + cvat_db_service.update_project_status( + db_session, project.id, ProjectStatuses.canceled ) - return - - logger.info( - f"Received escrow cancel event (escrow_address={webhook.escrow_address}). " - "Canceling the project" - ) - cvat_db_service.update_project_status( - db_session, project.id, ProjectStatuses.canceled - ) except Exception as ex: raise diff --git a/packages/examples/cvat/exchange-oracle/src/endpoints/webhook.py b/packages/examples/cvat/exchange-oracle/src/endpoints/webhook.py index eb4be7b6ee..a770ce7c1f 100644 --- a/packages/examples/cvat/exchange-oracle/src/endpoints/webhook.py +++ b/packages/examples/cvat/exchange-oracle/src/endpoints/webhook.py @@ -3,10 +3,8 @@ from fastapi import APIRouter, Header, HTTPException, Request import src.services.webhook as oracle_db_service -from src.core.types import OracleWebhookTypes from src.db import SessionLocal from src.schemas.webhook import OracleWebhook, OracleWebhookResponse -from src.utils.time import utcnow from src.validators.signature import validate_oracle_webhook_signature router = APIRouter() @@ -19,13 +17,7 @@ async def receive_oracle_webhook( human_signature: Union[str, None] = Header(default=None), ) -> OracleWebhookResponse: try: - # TODO: remove mock once implemented in launcher - if not human_signature: - human_signature = "launcher-{}".format(utcnow().timestamp()) - sender_type = OracleWebhookTypes.job_launcher - - else: - sender_type = await validate_oracle_webhook_signature(request, human_signature, webhook) + sender_type = await validate_oracle_webhook_signature(request, human_signature, webhook) with SessionLocal.begin() as session: webhook_id = oracle_db_service.inbox.create_webhook( diff --git a/packages/examples/cvat/exchange-oracle/src/services/exchange.py b/packages/examples/cvat/exchange-oracle/src/services/exchange.py index 20f17c0423..5b1f41bce7 100644 --- a/packages/examples/cvat/exchange-oracle/src/services/exchange.py +++ b/packages/examples/cvat/exchange-oracle/src/services/exchange.py @@ -10,6 +10,7 @@ from src.schemas import exchange as service_api from src.utils.assignments import ( compose_assignment_url, + get_default_assignment_size, get_default_assignment_timeout, parse_manifest, ) @@ -49,7 +50,7 @@ def serialize_task( job_bounty=manifest.job_bounty, job_time_limit=manifest.annotation.max_time or get_default_assignment_timeout(manifest.annotation.type), - job_size=manifest.annotation.job_size + manifest.validation.val_size, + job_size=get_default_assignment_size(manifest), job_type=project.job_type, platform=PlatformTypes.CVAT, assignment=serialized_assignment, diff --git a/packages/examples/cvat/exchange-oracle/src/utils/assignments.py b/packages/examples/cvat/exchange-oracle/src/utils/assignments.py index cbfa9b4a71..f19ddb1483 100644 --- a/packages/examples/cvat/exchange-oracle/src/utils/assignments.py +++ b/packages/examples/cvat/exchange-oracle/src/utils/assignments.py @@ -29,3 +29,12 @@ def get_default_assignment_timeout(task_type: TaskTypes) -> int: timeout_seconds *= skeletons_from_boxes.DEFAULT_ASSIGNMENT_SIZE_MULTIPLIER return timeout_seconds + + +def get_default_assignment_size(manifest: TaskManifest) -> int: + job_size = manifest.annotation.job_size + manifest.validation.val_size + + if job_size == TaskTypes.image_skeletons_from_boxes: + job_size *= skeletons_from_boxes.DEFAULT_ASSIGNMENT_SIZE_MULTIPLIER + + return job_size From fa43dc84e70c82c489b37eb53e39e09f3b2b1547 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Francisco=20L=C3=B3pez?= <50665615+flopez7@users.noreply.github.com> Date: Mon, 1 Apr 2024 11:07:27 +0200 Subject: [PATCH 16/66] Set reputation address to lowercase before fetching graphql (#1773) --- packages/sdk/typescript/human-protocol-sdk/src/operator.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/sdk/typescript/human-protocol-sdk/src/operator.ts b/packages/sdk/typescript/human-protocol-sdk/src/operator.ts index 63067827cd..7038f51883 100644 --- a/packages/sdk/typescript/human-protocol-sdk/src/operator.ts +++ b/packages/sdk/typescript/human-protocol-sdk/src/operator.ts @@ -134,7 +134,7 @@ export class OperatorUtils { const { reputationNetwork } = await gqlFetch<{ reputationNetwork: IReputationNetwork; }>(networkData.subgraphUrl, GET_REPUTATION_NETWORK_QUERY(role), { - address: address, + address: address.toLowerCase(), role: role, }); From 5770adaf814b157b59b24d16054ad0d8604ed3e3 Mon Sep 17 00:00:00 2001 From: Dzeranov Date: Mon, 1 Apr 2024 16:29:20 +0300 Subject: [PATCH 17/66] [Job Laucnher] Changed config service (#1709) * Changed the way of working with env variables * Config services splitted into different files * Remove `publicKey` from `auth.modules.ts` * Fixed tests, implemented configs * Added server config * Resolved comments --------- Co-authored-by: Eugene Voronov --- .../apps/job-launcher/server/src/app-init.ts | 11 +- .../job-launcher/server/src/app.module.ts | 4 +- .../src/common/config/auth-config.service.ts | 52 +++++++ .../server/src/common/config/config.module.ts | 41 +++++ .../src/common/config/cvat-config.service.ts | 16 ++ .../common/config/database-config.service.ts | 28 ++++ .../server/src/common/config/env-schema.ts | 70 +++++++++ .../server/src/common/config/env.ts | 132 ---------------- .../server/src/common/config/index.ts | 3 +- .../src/common/config/pgp-config.service.ts | 16 ++ .../src/common/config/s3-config.service.ts | 25 +++ .../server/src/common/config/s3.ts | 13 -- .../common/config/sendgrid-config.service.ts | 22 +++ .../common/config/server-config.service.ts | 28 ++++ .../common/config/stripe-config.service.ts | 25 +++ .../src/common/config/web3-config.service.ts | 46 ++++++ .../server/src/common/guards/cron.auth.ts | 7 +- .../server/src/database/database.module.ts | 49 +++--- packages/apps/job-launcher/server/src/main.ts | 8 +- .../server/src/modules/auth/auth.dto.ts | 1 - .../server/src/modules/auth/auth.module.ts | 16 +- .../src/modules/auth/auth.service.spec.ts | 4 + .../server/src/modules/auth/auth.service.ts | 96 ++++-------- .../src/modules/auth/strategy/jwt.http.ts | 7 +- .../modules/cron-job/cron-job.service.spec.ts | 10 ++ .../modules/encryption/encryption.module.ts | 13 +- .../server/src/modules/job/job.repository.ts | 13 +- .../src/modules/job/job.service.spec.ts | 70 ++++----- .../server/src/modules/job/job.service.ts | 144 ++++++++---------- .../modules/payment/payment.service.spec.ts | 2 + .../src/modules/payment/payment.service.ts | 34 ++--- .../modules/sendgrid/sendgrid.service.spec.ts | 12 +- .../src/modules/sendgrid/sendgrid.service.ts | 21 +-- .../src/modules/storage/storage.module.ts | 4 +- .../modules/storage/storage.service.spec.ts | 7 +- .../src/modules/storage/storage.service.ts | 40 ++--- .../src/modules/web3/web3.service.spec.ts | 8 +- .../server/src/modules/web3/web3.service.ts | 19 +-- .../webhook/webhook.controller.spec.ts | 4 + .../src/modules/webhook/webhook.repository.ts | 13 +- .../modules/webhook/webhook.service.spec.ts | 4 + .../src/modules/webhook/webhook.service.ts | 22 +-- 42 files changed, 638 insertions(+), 522 deletions(-) create mode 100644 packages/apps/job-launcher/server/src/common/config/auth-config.service.ts create mode 100644 packages/apps/job-launcher/server/src/common/config/config.module.ts create mode 100644 packages/apps/job-launcher/server/src/common/config/cvat-config.service.ts create mode 100644 packages/apps/job-launcher/server/src/common/config/database-config.service.ts create mode 100644 packages/apps/job-launcher/server/src/common/config/env-schema.ts delete mode 100644 packages/apps/job-launcher/server/src/common/config/env.ts create mode 100644 packages/apps/job-launcher/server/src/common/config/pgp-config.service.ts create mode 100644 packages/apps/job-launcher/server/src/common/config/s3-config.service.ts delete mode 100644 packages/apps/job-launcher/server/src/common/config/s3.ts create mode 100644 packages/apps/job-launcher/server/src/common/config/sendgrid-config.service.ts create mode 100644 packages/apps/job-launcher/server/src/common/config/server-config.service.ts create mode 100644 packages/apps/job-launcher/server/src/common/config/stripe-config.service.ts create mode 100644 packages/apps/job-launcher/server/src/common/config/web3-config.service.ts diff --git a/packages/apps/job-launcher/server/src/app-init.ts b/packages/apps/job-launcher/server/src/app-init.ts index d7bb554819..656b7ba392 100644 --- a/packages/apps/job-launcher/server/src/app-init.ts +++ b/packages/apps/job-launcher/server/src/app-init.ts @@ -7,22 +7,19 @@ import helmet from 'helmet'; import cookieParser from 'cookie-parser'; import { AppModule } from './app.module'; -import { ConfigNames } from './common/config'; +import { ServerConfigService } from './common/config/server-config.service'; + export default async function init(app: any) { const configService: ConfigService = app.get(ConfigService); + const serverConfigService = new ServerConfigService(configService); useContainer(app.select(AppModule), { fallbackOnErrors: true }); app.use(cookieParser()); - const sessionSecret = configService.get( - ConfigNames.SESSION_SECRET, - 'session-secret', - ); - app.use( session({ - secret: sessionSecret, + secret: serverConfigService.sessionSecret, resave: false, saveUninitialized: false, }), diff --git a/packages/apps/job-launcher/server/src/app.module.ts b/packages/apps/job-launcher/server/src/app.module.ts index 63dc7309a4..ef8085b1cc 100644 --- a/packages/apps/job-launcher/server/src/app.module.ts +++ b/packages/apps/job-launcher/server/src/app.module.ts @@ -12,7 +12,7 @@ import { UserModule } from './modules/user/user.module'; import { JobModule } from './modules/job/job.module'; import { PaymentModule } from './modules/payment/payment.module'; import { Web3Module } from './modules/web3/web3.module'; -import { envValidator } from './common/config'; +import { envValidator } from './common/config/env-schema'; import { ServeStaticModule } from '@nestjs/serve-static'; import { join } from 'path'; import { StorageModule } from './modules/storage/storage.module'; @@ -20,6 +20,7 @@ import { CronJobModule } from './modules/cron-job/cron-job.module'; import { SnakeCaseInterceptor } from './common/interceptors/snake-case'; import { DatabaseExceptionFilter } from './common/exceptions/database.filter'; import { WebhookModule } from './modules/webhook/webhook.module'; +import { EnvConfigModule } from './common/config/config.module'; @Module({ providers: [ @@ -64,6 +65,7 @@ import { WebhookModule } from './modules/webhook/webhook.module'; ), }), CronJobModule, + EnvConfigModule, ], controllers: [AppController], }) diff --git a/packages/apps/job-launcher/server/src/common/config/auth-config.service.ts b/packages/apps/job-launcher/server/src/common/config/auth-config.service.ts new file mode 100644 index 0000000000..1ac4d7d81f --- /dev/null +++ b/packages/apps/job-launcher/server/src/common/config/auth-config.service.ts @@ -0,0 +1,52 @@ +import { Injectable } from '@nestjs/common'; +import { ConfigService } from '@nestjs/config'; + +@Injectable() +export class AuthConfigService { + constructor(private configService: ConfigService) {} + get jwtPrivateKey(): string { + return this.configService.get('JWT_PRIVATE_KEY', ''); + } + get jwtPublicKey(): string { + return this.configService.get('JWT_PUBLIC_KEY', ''); + } + get accessTokenExpiresIn(): number { + return +this.configService.get( + 'JWT_ACCESS_TOKEN_EXPIRES_IN', + 300000, + ); + } + get refreshTokenExpiresIn(): number { + return +this.configService.get('REFRESH_TOKEN_EXPIRES_IN', 3600000); + } + get verifyEmailTokenExpiresIn(): number { + return +this.configService.get( + 'VERIFY_EMAIL_TOKEN_EXPIRES_IN', + 1800000, + ); + } + get forgotPasswordExpiresIn(): number { + return +this.configService.get( + 'FORGOT_PASSWORD_TOKEN_EXPIRES_IN', + 1800000, + ); + } + get apiKeyIterations(): number { + return +this.configService.get('APIKEY_ITERATIONS', 1000); + } + get apiKeyLength(): number { + return +this.configService.get('APIKEY_KEY_LENGTH', 64); + } + get hCaptchaSiteKey(): string { + return this.configService.get('HCAPTCHA_SITE_KEY', ''); + } + get hCaptchaSecret(): string { + return this.configService.get('HCAPTCHA_SECRET', ''); + } + get hCaptchaExchangeURL(): string { + return this.configService.get( + 'HCAPTCHA_EXCHANGE_URL', + 'https://foundation-exchange.hmt.ai', + ); + } +} diff --git a/packages/apps/job-launcher/server/src/common/config/config.module.ts b/packages/apps/job-launcher/server/src/common/config/config.module.ts new file mode 100644 index 0000000000..8c77835370 --- /dev/null +++ b/packages/apps/job-launcher/server/src/common/config/config.module.ts @@ -0,0 +1,41 @@ +import { Module, Global } from '@nestjs/common'; +import { ConfigService } from '@nestjs/config'; + +import { AuthConfigService } from './auth-config.service'; +import { ServerConfigService } from './server-config.service'; +import { CvatConfigService } from './cvat-config.service'; +import { DatabaseConfigService } from './database-config.service'; +import { PGPConfigService } from './pgp-config.service'; +import { S3ConfigService } from './s3-config.service'; +import { SendgridConfigService } from './sendgrid-config.service'; +import { StripeConfigService } from './stripe-config.service'; +import { Web3ConfigService } from './web3-config.service'; + +@Global() +@Module({ + providers: [ + ConfigService, + ServerConfigService, + AuthConfigService, + DatabaseConfigService, + Web3ConfigService, + S3ConfigService, + StripeConfigService, + SendgridConfigService, + CvatConfigService, + PGPConfigService, + ], + exports: [ + ConfigService, + ServerConfigService, + AuthConfigService, + DatabaseConfigService, + Web3ConfigService, + S3ConfigService, + StripeConfigService, + SendgridConfigService, + CvatConfigService, + PGPConfigService, + ], +}) +export class EnvConfigModule {} diff --git a/packages/apps/job-launcher/server/src/common/config/cvat-config.service.ts b/packages/apps/job-launcher/server/src/common/config/cvat-config.service.ts new file mode 100644 index 0000000000..f4a1356cf9 --- /dev/null +++ b/packages/apps/job-launcher/server/src/common/config/cvat-config.service.ts @@ -0,0 +1,16 @@ +import { Injectable } from '@nestjs/common'; +import { ConfigService } from '@nestjs/config'; + +@Injectable() +export class CvatConfigService { + constructor(private configService: ConfigService) {} + get jobSize(): number { + return +this.configService.get('CVAT_JOB_SIZE', 10); + } + get maxTime(): number { + return +this.configService.get('CVAT_MAX_TIME', 300); + } + get valSize(): number { + return +this.configService.get('CVAT_VAL_SIZE', 2); + } +} diff --git a/packages/apps/job-launcher/server/src/common/config/database-config.service.ts b/packages/apps/job-launcher/server/src/common/config/database-config.service.ts new file mode 100644 index 0000000000..52149cd525 --- /dev/null +++ b/packages/apps/job-launcher/server/src/common/config/database-config.service.ts @@ -0,0 +1,28 @@ +import { Injectable } from '@nestjs/common'; +import { ConfigService } from '@nestjs/config'; + +@Injectable() +export class DatabaseConfigService { + constructor(private configService: ConfigService) {} + get host(): string { + return this.configService.get('POSTGRES_HOST', '127.0.0.1'); + } + get port(): number { + return +this.configService.get('POSTGRES_PORT', 5432); + } + get user(): string { + return this.configService.get('POSTGRES_USER', 'operator'); + } + get password(): string { + return this.configService.get('POSTGRES_PASSWORD', 'qwerty'); + } + get database(): string { + return this.configService.get('POSTGRES_DATABASE', 'job-launcher'); + } + get ssl(): boolean { + return this.configService.get('POSTGRES_SSL', 'false') === 'true'; + } + get logging(): string { + return this.configService.get('POSTGRES_LOGGING', ''); + } +} diff --git a/packages/apps/job-launcher/server/src/common/config/env-schema.ts b/packages/apps/job-launcher/server/src/common/config/env-schema.ts new file mode 100644 index 0000000000..f20dbf75b7 --- /dev/null +++ b/packages/apps/job-launcher/server/src/common/config/env-schema.ts @@ -0,0 +1,70 @@ +import * as Joi from 'joi'; + +export const envValidator = Joi.object({ + // General + NODE_ENV: Joi.string(), + HOST: Joi.string(), + PORT: Joi.string(), + FE_URL: Joi.string(), + SESSION_SECRET: Joi.string(), + MAX_RETRY_COUNT: Joi.number(), + // Auth + JWT_PRIVATE_KEY: Joi.string().required(), + JWT_PUBLIC_KEY: Joi.string().required(), + JWT_ACCESS_TOKEN_EXPIRES_IN: Joi.number(), + REFRESH_TOKEN_EXPIRES_IN: Joi.number(), + VERIFY_EMAIL_TOKEN_EXPIRES_IN: Joi.number(), + FORGOT_PASSWORD_TOKEN_EXPIRES_IN: Joi.number(), + // Database + POSTGRES_HOST: Joi.string(), + POSTGRES_USER: Joi.string(), + POSTGRES_PASSWORD: Joi.string(), + POSTGRES_DATABASE: Joi.string(), + POSTGRES_PORT: Joi.string(), + POSTGRES_SSL: Joi.string(), + POSTGRES_LOGGING: Joi.string(), + // Web3 + WEB3_ENV: Joi.string(), + WEB3_PRIVATE_KEY: Joi.string().required(), + GAS_PRICE_MULTIPLIER: Joi.number(), + REPUTATION_ORACLE_ADDRESS: Joi.string().required(), + FORTUNE_EXCHANGE_ORACLE_ADDRESS: Joi.string().required(), + FORTUNE_RECORDING_ORACLE_ADDRESS: Joi.string().required(), + CVAT_EXCHANGE_ORACLE_ADDRESS: Joi.string().required(), + CVAT_RECORDING_ORACLE_ADDRESS: Joi.string().required(), + HCAPTCHA_RECORDING_ORACLE_URI: Joi.string().required(), + HCAPTCHA_REPUTATION_ORACLE_URI: Joi.string().required(), + HCAPTCHA_ORACLE_ADDRESS: Joi.string().required(), + HCAPTCHA_SITE_KEY: Joi.string().required(), + // HCAPTCHA_SECRET: Joi.string().required(), + HCAPTCHA_EXCHANGE_URL: Joi.string().description('hcaptcha exchange url'), + // S3 + S3_ENDPOINT: Joi.string(), + S3_PORT: Joi.string(), + S3_ACCESS_KEY: Joi.string().required(), + S3_SECRET_KEY: Joi.string().required(), + S3_BUCKET: Joi.string(), + S3_USE_SSL: Joi.string(), + // Stripe + STRIPE_SECRET_KEY: Joi.string().required(), + STRIPE_API_VERSION: Joi.string(), + STRIPE_APP_NAME: Joi.string(), + STRIPE_APP_VERSION: Joi.string(), + STRIPE_APP_INFO_URL: Joi.string(), + // SendGrid + SENDGRID_API_KEY: Joi.string().required(), + SENDGRID_FROM_EMAIL: Joi.string(), + SENDGRID_FROM_NAME: Joi.string(), + // CVAT + CVAT_JOB_SIZE: Joi.string(), + CVAT_VAL_SIZE: Joi.string(), + //PGP + PGP_ENCRYPT: Joi.boolean(), + PGP_PRIVATE_KEY: Joi.string().optional(), + PGP_PASSPHRASE: Joi.string().optional(), + // APIKey + APIKEY_ITERATIONS: Joi.number(), + APIKEY_KEY_LENGTH: Joi.number(), + // Cron Job Secret + CRON_SECRET: Joi.string().required(), +}); diff --git a/packages/apps/job-launcher/server/src/common/config/env.ts b/packages/apps/job-launcher/server/src/common/config/env.ts deleted file mode 100644 index f4f89e7822..0000000000 --- a/packages/apps/job-launcher/server/src/common/config/env.ts +++ /dev/null @@ -1,132 +0,0 @@ -import * as Joi from 'joi'; - -export const ConfigNames = { - NODE_ENV: 'NODE_ENV', - HOST: 'HOST', - PORT: 'PORT', - FE_URL: 'FE_URL', - SESSION_SECRET: 'SESSION_SECRET', - MAX_RETRY_COUNT: 'MAX_RETRY_COUNT', - JWT_PRIVATE_KEY: 'JWT_PRIVATE_KEY', - JWT_PUBLIC_KEY: 'JWT_PUBLIC_KEY', - JWT_ACCESS_TOKEN_EXPIRES_IN: 'JWT_ACCESS_TOKEN_EXPIRES_IN', - REFRESH_TOKEN_EXPIRES_IN: 'REFRESH_TOKEN_EXPIRES_IN', - VERIFY_EMAIL_TOKEN_EXPIRES_IN: 'VERIFY_EMAIL_TOKEN_EXPIRES_IN', - FORGOT_PASSWORD_TOKEN_EXPIRES_IN: 'FORGOT_PASSWORD_TOKEN_EXPIRES_IN', - POSTGRES_HOST: 'POSTGRES_HOST', - POSTGRES_USER: 'POSTGRES_USER', - POSTGRES_PASSWORD: 'POSTGRES_PASSWORD', - POSTGRES_DATABASE: 'POSTGRES_DATABASE', - POSTGRES_PORT: 'POSTGRES_PORT', - POSTGRES_SSL: 'POSTGRES_SSL', - POSTGRES_LOGGING: 'POSTGRES_LOGGING', - WEB3_ENV: 'WEB3_ENV', - WEB3_PRIVATE_KEY: 'WEB3_PRIVATE_KEY', - GAS_PRICE_MULTIPLIER: 'GAS_PRICE_MULTIPLIER', - PGP_ENCRYPT: 'PGP_ENCRYPT', - PGP_PRIVATE_KEY: 'PGP_PRIVATE_KEY', - PGP_PASSPHRASE: 'PGP_PASSPHRASE', - REPUTATION_ORACLE_ADDRESS: 'REPUTATION_ORACLE_ADDRESS', - FORTUNE_EXCHANGE_ORACLE_ADDRESS: 'FORTUNE_EXCHANGE_ORACLE_ADDRESS', - FORTUNE_RECORDING_ORACLE_ADDRESS: 'FORTUNE_RECORDING_ORACLE_ADDRESS', - CVAT_EXCHANGE_ORACLE_ADDRESS: 'CVAT_EXCHANGE_ORACLE_ADDRESS', - CVAT_RECORDING_ORACLE_ADDRESS: 'CVAT_RECORDING_ORACLE_ADDRESS', - HCAPTCHA_RECORDING_ORACLE_URI: 'HCAPTCHA_RECORDING_ORACLE_URI', - HCAPTCHA_REPUTATION_ORACLE_URI: 'HCAPTCHA_REPUTATION_ORACLE_URI', - HCAPTCHA_ORACLE_ADDRESS: 'HCAPTCHA_ORACLE_ADDRESS', - HCAPTCHA_SITE_KEY: 'HCAPTCHA_SITE_KEY', - // HCAPTCHA_SECRET: 'HCAPTCHA_SECRET', - HCAPTCHA_EXCHANGE_URL: 'HCAPTCHA_EXCHANGE_URL', - S3_ENDPOINT: 'S3_ENDPOINT', - S3_PORT: 'S3_PORT', - S3_ACCESS_KEY: 'S3_ACCESS_KEY', - S3_SECRET_KEY: 'S3_SECRET_KEY', - S3_BUCKET: 'S3_BUCKET', - S3_USE_SSL: 'S3_USE_SSL', - STRIPE_SECRET_KEY: 'STRIPE_SECRET_KEY', - STRIPE_API_VERSION: 'STRIPE_API_VERSION', - STRIPE_APP_NAME: 'STRIPE_APP_NAME', - STRIPE_APP_VERSION: 'STRIPE_APP_VERSION', - STRIPE_APP_INFO_URL: 'STRIPE_APP_INFO_URL', - SENDGRID_API_KEY: 'SENDGRID_API_KEY', - SENDGRID_FROM_EMAIL: 'SENDGRID_FROM_EMAIL', - SENDGRID_FROM_NAME: 'SENDGRID_FROM_NAME', - CVAT_JOB_SIZE: 'CVAT_JOB_SIZE', - CVAT_MAX_TIME: 'CVAT_MAX_TIME', - CVAT_VAL_SIZE: 'CVAT_VAL_SIZE', - APIKEY_ITERATIONS: 'APIKEY_ITERATIONS', - APIKEY_KEY_LENGTH: 'APIKEY_KEY_LENGTH', - CRON_SECRET: 'CRON_SECRET', -}; - -export const envValidator = Joi.object({ - // General - NODE_ENV: Joi.string().default('development'), - HOST: Joi.string().default('localhost'), - PORT: Joi.string().default(5000), - FE_URL: Joi.string().default('http://localhost:3005'), - SESSION_SECRET: Joi.string().default('session_key'), - MAX_RETRY_COUNT: Joi.number().default(5), - // Auth - JWT_PRIVATE_KEY: Joi.string().required(), - JWT_PUBLIC_KEY: Joi.string().required(), - JWT_ACCESS_TOKEN_EXPIRES_IN: Joi.number().default(300000), - REFRESH_TOKEN_EXPIRES_IN: Joi.number().default(3600000), - VERIFY_EMAIL_TOKEN_EXPIRES_IN: Joi.number().default(1800000), - FORGOT_PASSWORD_TOKEN_EXPIRES_IN: Joi.number().default(1800000), - // Database - POSTGRES_HOST: Joi.string().default('127.0.0.1'), - POSTGRES_USER: Joi.string().default('operator'), - POSTGRES_PASSWORD: Joi.string().default('qwerty'), - POSTGRES_DATABASE: Joi.string().default('job-launcher'), - POSTGRES_PORT: Joi.string().default('5432'), - POSTGRES_SSL: Joi.string().default('false'), - POSTGRES_LOGGING: Joi.string(), - // Web3 - WEB3_ENV: Joi.string().default('testnet'), - WEB3_PRIVATE_KEY: Joi.string().required(), - GAS_PRICE_MULTIPLIER: Joi.number().default(null), - REPUTATION_ORACLE_ADDRESS: Joi.string().required(), - FORTUNE_EXCHANGE_ORACLE_ADDRESS: Joi.string().required(), - FORTUNE_RECORDING_ORACLE_ADDRESS: Joi.string().required(), - CVAT_EXCHANGE_ORACLE_ADDRESS: Joi.string().required(), - CVAT_RECORDING_ORACLE_ADDRESS: Joi.string().required(), - HCAPTCHA_RECORDING_ORACLE_URI: Joi.string().required(), - HCAPTCHA_REPUTATION_ORACLE_URI: Joi.string().required(), - HCAPTCHA_ORACLE_ADDRESS: Joi.string().required(), - HCAPTCHA_SITE_KEY: Joi.string().required(), - // HCAPTCHA_SECRET: Joi.string().required(), - HCAPTCHA_EXCHANGE_URL: Joi.string() - .default('https://foundation-exchange.hmt.ai') - .description('hcaptcha exchange url'), - // S3 - S3_ENDPOINT: Joi.string().default('127.0.0.1'), - S3_PORT: Joi.string().default(9000), - S3_ACCESS_KEY: Joi.string().required(), - S3_SECRET_KEY: Joi.string().required(), - S3_BUCKET: Joi.string().default('launcher'), - S3_USE_SSL: Joi.string().default('false'), - // Stripe - STRIPE_SECRET_KEY: Joi.string().required(), - STRIPE_API_VERSION: Joi.string().default('2022-11-15'), - STRIPE_APP_NAME: Joi.string().default('Fortune'), - STRIPE_APP_VERSION: Joi.string().default('0.0.1'), - STRIPE_APP_INFO_URL: Joi.string().default('https://hmt.ai'), - // SendGrid - SENDGRID_API_KEY: Joi.string().required(), - SENDGRID_FROM_EMAIL: Joi.string().default('job-launcher@hmt.ai'), - SENDGRID_FROM_NAME: Joi.string().default('Human Protocol Job Launcher'), - // CVAT - CVAT_JOB_SIZE: Joi.string().default('10'), - CVAT_MAX_TIME: Joi.string().default('300'), - CVAT_VAL_SIZE: Joi.string().default('2'), - //PGP - PGP_ENCRYPT: Joi.boolean().default(false), - PGP_PRIVATE_KEY: Joi.string().optional(), - PGP_PASSPHRASE: Joi.string().optional(), - // APIKey - APIKEY_ITERATIONS: Joi.number().default(1000), - APIKEY_KEY_LENGTH: Joi.number().default(64), - // Cron Job Secret - CRON_SECRET: Joi.string().required(), -}); diff --git a/packages/apps/job-launcher/server/src/common/config/index.ts b/packages/apps/job-launcher/server/src/common/config/index.ts index 20c54299ce..8b28b497be 100644 --- a/packages/apps/job-launcher/server/src/common/config/index.ts +++ b/packages/apps/job-launcher/server/src/common/config/index.ts @@ -1,3 +1,2 @@ -export * from './env'; +export * from './env-schema'; export * from './networks'; -export * from './s3'; diff --git a/packages/apps/job-launcher/server/src/common/config/pgp-config.service.ts b/packages/apps/job-launcher/server/src/common/config/pgp-config.service.ts new file mode 100644 index 0000000000..a6ec43a0df --- /dev/null +++ b/packages/apps/job-launcher/server/src/common/config/pgp-config.service.ts @@ -0,0 +1,16 @@ +import { Injectable } from '@nestjs/common'; +import { ConfigService } from '@nestjs/config'; + +@Injectable() +export class PGPConfigService { + constructor(private configService: ConfigService) {} + get encrypt(): boolean { + return this.configService.get('PGP_ENCRYPT', 'false') === 'true'; + } + get privateKey(): string { + return this.configService.get('PGP_PRIVATE_KEY', ''); + } + get passphrase(): string { + return this.configService.get('PGP_PASSPHRASE', ''); + } +} diff --git a/packages/apps/job-launcher/server/src/common/config/s3-config.service.ts b/packages/apps/job-launcher/server/src/common/config/s3-config.service.ts new file mode 100644 index 0000000000..054003c0d5 --- /dev/null +++ b/packages/apps/job-launcher/server/src/common/config/s3-config.service.ts @@ -0,0 +1,25 @@ +import { Injectable } from '@nestjs/common'; +import { ConfigService } from '@nestjs/config'; + +@Injectable() +export class S3ConfigService { + constructor(private configService: ConfigService) {} + get endpoint(): string { + return this.configService.get('S3_ENDPOINT', '127.0.0.1'); + } + get port(): number { + return +this.configService.get('S3_PORT', 9000); + } + get accessKey(): string { + return this.configService.get('S3_ACCESS_KEY', ''); + } + get secretKey(): string { + return this.configService.get('S3_SECRET_KEY', ''); + } + get bucket(): string { + return this.configService.get('S3_BUCKET', 'launcher'); + } + get useSSL(): boolean { + return this.configService.get('S3_USE_SSL', 'false') === 'true'; + } +} diff --git a/packages/apps/job-launcher/server/src/common/config/s3.ts b/packages/apps/job-launcher/server/src/common/config/s3.ts deleted file mode 100644 index 2389bffabb..0000000000 --- a/packages/apps/job-launcher/server/src/common/config/s3.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { ConfigType, registerAs } from '@nestjs/config'; - -export const s3Config = registerAs('s3', () => ({ - endPoint: process.env.S3_ENDPOINT || '127.0.0.1', - port: +(process.env.S3_PORT || 9000), - accessKey: process.env.S3_ACCESS_KEY || '', - secretKey: process.env.S3_SECRET_KEY || '', - bucket: process.env.S3_BUCKET || '', - useSSL: process.env.S3_USE_SSL === 'true', -})); - -export const s3ConfigKey = s3Config.KEY; -export type S3ConfigType = ConfigType; diff --git a/packages/apps/job-launcher/server/src/common/config/sendgrid-config.service.ts b/packages/apps/job-launcher/server/src/common/config/sendgrid-config.service.ts new file mode 100644 index 0000000000..2583a39142 --- /dev/null +++ b/packages/apps/job-launcher/server/src/common/config/sendgrid-config.service.ts @@ -0,0 +1,22 @@ +import { Injectable } from '@nestjs/common'; +import { ConfigService } from '@nestjs/config'; + +@Injectable() +export class SendgridConfigService { + constructor(private configService: ConfigService) {} + get apiKey(): string { + return this.configService.get('SENDGRID_API_KEY', ''); + } + get fromEmail(): string { + return this.configService.get( + 'SENDGRID_FROM_EMAIL', + 'job-launcher@hmt.ai', + ); + } + get fromName(): string { + return this.configService.get( + 'SENDGRID_FROM_NAME', + 'Human Protocol Job Launcher', + ); + } +} diff --git a/packages/apps/job-launcher/server/src/common/config/server-config.service.ts b/packages/apps/job-launcher/server/src/common/config/server-config.service.ts new file mode 100644 index 0000000000..2f6f8bf2c0 --- /dev/null +++ b/packages/apps/job-launcher/server/src/common/config/server-config.service.ts @@ -0,0 +1,28 @@ +import { Injectable } from '@nestjs/common'; +import { ConfigService } from '@nestjs/config'; + +@Injectable() +export class ServerConfigService { + constructor(private configService: ConfigService) {} + get nodeEnv(): string { + return this.configService.get('NODE_ENV', 'development'); + } + get host(): string { + return this.configService.get('HOST', 'localhost'); + } + get port(): number { + return +this.configService.get('PORT', 5000); + } + get feURL(): string { + return this.configService.get('FE_URL', 'http://localhost:3005'); + } + get sessionSecret(): string { + return this.configService.get('SESSION_SECRET', 'session_key'); + } + get maxRetryCount(): number { + return +this.configService.get('MAX_RETRY_COUNT', 5); + } + get cronSecret(): string { + return this.configService.get('CRON_SECRET', ''); + } +} diff --git a/packages/apps/job-launcher/server/src/common/config/stripe-config.service.ts b/packages/apps/job-launcher/server/src/common/config/stripe-config.service.ts new file mode 100644 index 0000000000..9f44d6ae8d --- /dev/null +++ b/packages/apps/job-launcher/server/src/common/config/stripe-config.service.ts @@ -0,0 +1,25 @@ +import { Injectable } from '@nestjs/common'; +import { ConfigService } from '@nestjs/config'; + +@Injectable() +export class StripeConfigService { + constructor(private configService: ConfigService) {} + get secretKey(): string { + return this.configService.get('STRIPE_SECRET_KEY', '127.0.0.1'); + } + get apiVersion(): string { + return this.configService.get('STRIPE_API_VERSION', '2022-11-15'); + } + get appName(): string { + return this.configService.get('STRIPE_APP_NAME', 'Fortune'); + } + get appVersion(): string { + return this.configService.get('STRIPE_APP_VERSION', '0.0.1'); + } + get appInfoURL(): string { + return this.configService.get( + 'STRIPE_APP_INFO_URL', + 'https://hmt.ai', + ); + } +} diff --git a/packages/apps/job-launcher/server/src/common/config/web3-config.service.ts b/packages/apps/job-launcher/server/src/common/config/web3-config.service.ts new file mode 100644 index 0000000000..91488efd34 --- /dev/null +++ b/packages/apps/job-launcher/server/src/common/config/web3-config.service.ts @@ -0,0 +1,46 @@ +import { Injectable } from '@nestjs/common'; +import { ConfigService } from '@nestjs/config'; + +@Injectable() +export class Web3ConfigService { + constructor(private configService: ConfigService) {} + get env(): string { + return this.configService.get('WEB3_ENV', 'testnet'); + } + get privateKey(): string { + return this.configService.get('WEB3_PRIVATE_KEY', ''); + } + get gasPriceMultiplier(): number { + return +this.configService.get('GAS_PRICE_MULTIPLIER', 1); + } + get reputationOracleAddress(): string { + return this.configService.get('REPUTATION_ORACLE_ADDRESS', ''); + } + get fortuneExchangeOracleAddress(): string { + return this.configService.get( + 'FORTUNE_EXCHANGE_ORACLE_ADDRESS', + '', + ); + } + get fortuneRecordingOracleAddress(): string { + return this.configService.get( + 'FORTUNE_RECORDING_ORACLE_ADDRESS', + '', + ); + } + get cvatExchangeOracleAddress(): string { + return this.configService.get('CVAT_EXCHANGE_ORACLE_ADDRESS', ''); + } + get cvatRecordingOracleAddress(): string { + return this.configService.get('CVAT_RECORDING_ORACLE_ADDRESS', ''); + } + get hCaptchaRecordingOracleURI(): string { + return this.configService.get('HCAPTCHA_RECORDING_ORACLE_URI', ''); + } + get hCaptchaReputationOracleURI(): string { + return this.configService.get('HCAPTCHA_REPUTATION_ORACLE_URI', ''); + } + get hCaptchaOracleAddress(): string { + return this.configService.get('HCAPTCHA_ORACLE_ADDRESS', ''); + } +} diff --git a/packages/apps/job-launcher/server/src/common/guards/cron.auth.ts b/packages/apps/job-launcher/server/src/common/guards/cron.auth.ts index c0a48cb30a..f59facb459 100644 --- a/packages/apps/job-launcher/server/src/common/guards/cron.auth.ts +++ b/packages/apps/job-launcher/server/src/common/guards/cron.auth.ts @@ -4,19 +4,18 @@ import { Injectable, UnauthorizedException, } from '@nestjs/common'; -import { ConfigService } from '@nestjs/config'; -import { ConfigNames } from '../config'; +import { ServerConfigService } from '../config/server-config.service'; @Injectable() export class CronAuthGuard implements CanActivate { - constructor(private readonly configService: ConfigService) {} + constructor(private readonly serverConfigService: ServerConfigService) {} public async canActivate(context: ExecutionContext): Promise { const request = context.switchToHttp().getRequest(); if ( request.headers['authorization'] === - `Bearer ${this.configService.get(ConfigNames.CRON_SECRET)}` + `Bearer ${this.serverConfigService.cronSecret}` ) { return true; } diff --git a/packages/apps/job-launcher/server/src/database/database.module.ts b/packages/apps/job-launcher/server/src/database/database.module.ts index a83e71dba6..8e53982e0a 100644 --- a/packages/apps/job-launcher/server/src/database/database.module.ts +++ b/packages/apps/job-launcher/server/src/database/database.module.ts @@ -1,5 +1,4 @@ import { Module } from '@nestjs/common'; -import { ConfigModule, ConfigService } from '@nestjs/config'; import { TypeOrmModule } from '@nestjs/typeorm'; import * as path from 'path'; import { SnakeNamingStrategy } from 'typeorm-naming-strategies'; @@ -10,7 +9,8 @@ import { UserEntity } from '../modules/user/user.entity'; import { TypeOrmLoggerModule, TypeOrmLoggerService } from './typeorm'; import { JobEntity } from '../modules/job/job.entity'; import { PaymentEntity } from '../modules/payment/payment.entity'; -import { ConfigNames } from '../common/config'; +import { ServerConfigService } from '../common/config/server-config.service'; +import { DatabaseConfigService } from '../common/config/database-config.service'; import { ApiKeyEntity } from '../modules/auth/apikey.entity'; import { WebhookEntity } from '../modules/webhook/webhook.entity'; import { LoggerOptions } from 'typeorm'; @@ -19,15 +19,18 @@ import { CronJobEntity } from '../modules/cron-job/cron-job.entity'; @Module({ imports: [ TypeOrmModule.forRootAsync({ - imports: [TypeOrmLoggerModule, ConfigModule], - inject: [TypeOrmLoggerService, ConfigService], + imports: [TypeOrmLoggerModule], + inject: [ + TypeOrmLoggerService, + DatabaseConfigService, + ServerConfigService, + ], useFactory: ( typeOrmLoggerService: TypeOrmLoggerService, - configService: ConfigService, + databaseConfigService: DatabaseConfigService, + serverConfigService: ServerConfigService, ) => { - const loggerOptions = configService - .get(ConfigNames.POSTGRES_LOGGING) - ?.split(', '); + const loggerOptions = databaseConfigService.logging?.split(', '); typeOrmLoggerService.setOptions( loggerOptions && loggerOptions[0] === 'all' ? 'all' @@ -59,30 +62,14 @@ import { CronJobEntity } from '../modules/cron-job/cron-job.entity'; migrations: [path.join(__dirname, '/migrations/**/*{.ts,.js}')], //"migrations": ["dist/migrations/*{.ts,.js}"], logger: typeOrmLoggerService, - host: configService.get( - ConfigNames.POSTGRES_HOST, - 'localhost', - ), - port: configService.get(ConfigNames.POSTGRES_PORT, 5432), - username: configService.get( - ConfigNames.POSTGRES_USER, - 'operator', - ), - password: configService.get( - ConfigNames.POSTGRES_PASSWORD, - 'qwerty', - ), - database: configService.get( - ConfigNames.POSTGRES_DATABASE, - 'job-launcher', - ), - keepConnectionAlive: - configService.get(ConfigNames.NODE_ENV) === 'test', + host: databaseConfigService.host, + port: databaseConfigService.port, + username: databaseConfigService.user, + password: databaseConfigService.password, + database: databaseConfigService.database, + keepConnectionAlive: serverConfigService.nodeEnv === 'test', migrationsRun: false, - ssl: - configService - .get(ConfigNames.POSTGRES_SSL) - ?.toLowerCase() === 'true', + ssl: databaseConfigService.ssl, }; }, }), diff --git a/packages/apps/job-launcher/server/src/main.ts b/packages/apps/job-launcher/server/src/main.ts index e199c52137..c527d623d6 100644 --- a/packages/apps/job-launcher/server/src/main.ts +++ b/packages/apps/job-launcher/server/src/main.ts @@ -3,7 +3,7 @@ import { NestFactory } from '@nestjs/core'; import { ConfigService } from '@nestjs/config'; import { AppModule } from './app.module'; -import { ConfigNames } from './common/config'; +import { ServerConfigService } from './common/config/server-config.service'; import init from './app-init'; async function bootstrap() { @@ -13,9 +13,11 @@ async function bootstrap() { await init(app); const configService: ConfigService = app.get(ConfigService); + const serverConfigService = new ServerConfigService(configService); + + const host = serverConfigService.host; + const port = serverConfigService.port; - const host = configService.get(ConfigNames.HOST, 'localhost'); - const port = +configService.get(ConfigNames.PORT, '5000'); await app.listen(port, host, async () => { console.info(`API server is running on http://${host}:${port}`); }); diff --git a/packages/apps/job-launcher/server/src/modules/auth/auth.dto.ts b/packages/apps/job-launcher/server/src/modules/auth/auth.dto.ts index 50f0804c6c..ad745d83f4 100644 --- a/packages/apps/job-launcher/server/src/modules/auth/auth.dto.ts +++ b/packages/apps/job-launcher/server/src/modules/auth/auth.dto.ts @@ -2,7 +2,6 @@ import { ApiProperty } from '@nestjs/swagger'; import { Transform } from 'class-transformer'; import { IsEmail, IsString, IsUUID } from 'class-validator'; import { IsPassword } from '../../common/validators'; -import { UserEntity } from '../user/user.entity'; export class ForgotPasswordDto { @ApiProperty() diff --git a/packages/apps/job-launcher/server/src/modules/auth/auth.module.ts b/packages/apps/job-launcher/server/src/modules/auth/auth.module.ts index dd318b5e7c..73962fab03 100644 --- a/packages/apps/job-launcher/server/src/modules/auth/auth.module.ts +++ b/packages/apps/job-launcher/server/src/modules/auth/auth.module.ts @@ -1,6 +1,5 @@ import { Module } from '@nestjs/common'; import { JwtModule } from '@nestjs/jwt'; -import { ConfigService, ConfigModule } from '@nestjs/config'; import { TypeOrmModule } from '@nestjs/typeorm'; import { UserModule } from '../user/user.module'; @@ -9,7 +8,7 @@ import { AuthService } from './auth.service'; import { AuthJwtController } from './auth.controller'; import { TokenEntity } from './token.entity'; import { TokenRepository } from './token.repository'; -import { ConfigNames } from '../../common/config'; +import { AuthConfigService } from '../../common/config/auth-config.service'; import { SendGridModule } from '../sendgrid/sendgrid.module'; import { ApiKeyRepository } from './apikey.repository'; import { ApiKeyEntity } from './apikey.entity'; @@ -19,18 +18,13 @@ import { UserRepository } from '../user/user.repository'; @Module({ imports: [ UserModule, - ConfigModule, JwtModule.registerAsync({ - inject: [ConfigService], - imports: [ConfigModule], - useFactory: (configService: ConfigService) => ({ - privateKey: configService.get(ConfigNames.JWT_PRIVATE_KEY), + inject: [AuthConfigService], + useFactory: (authConfigService: AuthConfigService) => ({ + privateKey: authConfigService.jwtPrivateKey, signOptions: { algorithm: 'ES256', - expiresIn: configService.get( - ConfigNames.JWT_ACCESS_TOKEN_EXPIRES_IN, - 3600, - ), + expiresIn: authConfigService.accessTokenExpiresIn, }, }), }), diff --git a/packages/apps/job-launcher/server/src/modules/auth/auth.service.spec.ts b/packages/apps/job-launcher/server/src/modules/auth/auth.service.spec.ts index 93d01edf27..038536a547 100644 --- a/packages/apps/job-launcher/server/src/modules/auth/auth.service.spec.ts +++ b/packages/apps/job-launcher/server/src/modules/auth/auth.service.spec.ts @@ -25,6 +25,8 @@ import { SendGridService } from '../sendgrid/sendgrid.service'; import { SENDGRID_TEMPLATES, SERVICE_NAME } from '../../common/constants'; import { ApiKeyRepository } from './apikey.repository'; import { AuthError } from './auth.error'; +import { ServerConfigService } from '../../common/config/server-config.service'; +import { AuthConfigService } from '../../common/config/auth-config.service'; jest.mock('@human-protocol/sdk'); jest.mock('../../common/utils/hcaptcha', () => ({ @@ -57,6 +59,8 @@ describe('AuthService', () => { providers: [ AuthService, UserService, + ServerConfigService, + AuthConfigService, { provide: JwtService, useValue: { diff --git a/packages/apps/job-launcher/server/src/modules/auth/auth.service.ts b/packages/apps/job-launcher/server/src/modules/auth/auth.service.ts index 51bdcf3332..693236a0af 100644 --- a/packages/apps/job-launcher/server/src/modules/auth/auth.service.ts +++ b/packages/apps/job-launcher/server/src/modules/auth/auth.service.ts @@ -19,8 +19,8 @@ import { import { TokenEntity, TokenType } from './token.entity'; import { TokenRepository } from './token.repository'; -import { ConfigNames } from '../../common/config'; -import { ConfigService } from '@nestjs/config'; +import { AuthConfigService } from '../../common/config/auth-config.service'; +import { ServerConfigService } from '../../common/config/server-config.service'; import { SendGridService } from '../sendgrid/sendgrid.service'; import { SENDGRID_TEMPLATES, SERVICE_NAME } from '../../common/constants'; @@ -35,64 +35,24 @@ import { AuthError } from './auth.error'; @Injectable() export class AuthService { private readonly logger = new Logger(AuthService.name); - private readonly refreshTokenExpiresIn: number; - private readonly accessTokenExpiresIn: number; - private readonly verifyEmailTokenExpiresIn: number; - private readonly forgotPasswordTokenExpiresIn: number; - private readonly feURL: string; - private readonly iterations: number; - private readonly keyLength: number; - constructor( private readonly jwtService: JwtService, private readonly userService: UserService, private readonly tokenRepository: TokenRepository, - private readonly configService: ConfigService, + private readonly serverConfigService: ServerConfigService, + private readonly authConfigService: AuthConfigService, private readonly sendgridService: SendGridService, private readonly apiKeyRepository: ApiKeyRepository, private readonly userRepository: UserRepository, - ) { - this.refreshTokenExpiresIn = this.configService.get( - ConfigNames.REFRESH_TOKEN_EXPIRES_IN, - 3600000, - ); - - this.accessTokenExpiresIn = this.configService.get( - ConfigNames.JWT_ACCESS_TOKEN_EXPIRES_IN, - 300000, - ); - - this.verifyEmailTokenExpiresIn = this.configService.get( - ConfigNames.VERIFY_EMAIL_TOKEN_EXPIRES_IN, - 1800000, - ); - - this.forgotPasswordTokenExpiresIn = this.configService.get( - ConfigNames.FORGOT_PASSWORD_TOKEN_EXPIRES_IN, - 1800000, - ); - - this.feURL = this.configService.get( - ConfigNames.FE_URL, - 'http://localhost:3005', - ); - this.iterations = this.configService.get( - ConfigNames.APIKEY_ITERATIONS, - 1000, - ); - this.keyLength = this.configService.get( - ConfigNames.APIKEY_KEY_LENGTH, - 64, - ); - } + ) {} public async signin(data: SignInDto, ip?: string): Promise { // if ( // !( // await verifyToken( - // this.configService.get(ConfigNames.HCAPTCHA_EXCHANGE_URL)!, - // this.configService.get(ConfigNames.HCAPTCHA_SITE_KEY)!, - // this.configService.get(ConfigNames.HCAPTCHA_SECRET)!, + // this.authConfigService.hCaptchaExchangeURL, + // this.authConfigService.hCaptchaSiteKey, + // this.authConfigService.hCaptchaSecret, // data.hCaptchaToken, // ip, // ) @@ -116,9 +76,9 @@ export class AuthService { // if ( // !( // await verifyToken( - // this.configService.get(ConfigNames.HCAPTCHA_SITE_KEY)!, - // this.configService.get(ConfigNames.HCAPTCHA_EXCHANGE_URL)!, - // this.configService.get(ConfigNames.HCAPTCHA_SECRET)!, + // this.authConfigService.hCaptchaExchangeURL, + // this.authConfigService.hCaptchaSiteKey, + // this.authConfigService.hCaptchaSecret, // data.hCaptchaToken, // ip, // ) @@ -133,7 +93,7 @@ export class AuthService { tokenEntity.user = userEntity; const date = new Date(); tokenEntity.expiresAt = new Date( - date.getTime() + this.verifyEmailTokenExpiresIn, + date.getTime() + this.authConfigService.verifyEmailTokenExpiresIn, ); await this.tokenRepository.createUnique(tokenEntity); @@ -144,7 +104,7 @@ export class AuthService { to: data.email, dynamicTemplateData: { service_name: SERVICE_NAME, - url: `${this.feURL}/verify?token=${tokenEntity.uuid}`, + url: `${this.serverConfigService.feURL}/verify?token=${tokenEntity.uuid}`, }, }, ], @@ -185,7 +145,7 @@ export class AuthService { status: userEntity.status, }, { - expiresIn: this.accessTokenExpiresIn, + expiresIn: this.authConfigService.accessTokenExpiresIn, }, ); @@ -198,7 +158,7 @@ export class AuthService { newRefreshTokenEntity.type = TokenType.REFRESH; const date = new Date(); newRefreshTokenEntity.expiresAt = new Date( - date.getTime() + this.refreshTokenExpiresIn, + date.getTime() + this.authConfigService.refreshTokenExpiresIn, ); await this.tokenRepository.createUnique(newRefreshTokenEntity); @@ -231,7 +191,7 @@ export class AuthService { tokenEntity.user = userEntity; const date = new Date(); tokenEntity.expiresAt = new Date( - date.getTime() + this.forgotPasswordTokenExpiresIn, + date.getTime() + this.authConfigService.forgotPasswordExpiresIn, ); await this.tokenRepository.createUnique(tokenEntity); @@ -242,7 +202,7 @@ export class AuthService { to: data.email, dynamicTemplateData: { service_name: SERVICE_NAME, - url: `${this.feURL}/reset-password?token=${tokenEntity.uuid}`, + url: `${this.serverConfigService.feURL}/reset-password?token=${tokenEntity.uuid}`, }, }, ], @@ -257,9 +217,9 @@ export class AuthService { // if ( // !( // await verifyToken( - // this.configService.get(ConfigNames.HCAPTCHA_EXCHANGE_URL)!, - // this.configService.get(ConfigNames.HCAPTCHA_SITE_KEY)!, - // this.configService.get(ConfigNames.HCAPTCHA_SECRET)!, + // this.authConfigService.hCaptchaExchangeURL, + // this.authConfigService.hCaptchaSiteKey, + // this.authConfigService.hCaptchaSecret, // data.hCaptchaToken, // ip, // ) @@ -338,7 +298,7 @@ export class AuthService { tokenEntity.user = userEntity; const date = new Date(); tokenEntity.expiresAt = new Date( - date.getTime() + this.verifyEmailTokenExpiresIn, + date.getTime() + this.authConfigService.verifyEmailTokenExpiresIn, ); await this.tokenRepository.createUnique(tokenEntity); @@ -349,7 +309,7 @@ export class AuthService { to: data.email, dynamicTemplateData: { service_name: SERVICE_NAME, - url: `${this.feURL}/verify?token=${tokenEntity.uuid}`, + url: `${this.serverConfigService.feURL}/verify?token=${tokenEntity.uuid}`, }, }, ], @@ -363,8 +323,8 @@ export class AuthService { const hashedAPIKey = await generateHash( apiKey, salt, - this.iterations, - this.keyLength, + this.authConfigService.apiKeyIterations, + this.authConfigService.apiKeyLength, ); let apiKeyEntity = await this.apiKeyRepository.findAPIKeyByUserId(userId); @@ -393,8 +353,8 @@ export class AuthService { const hash = await generateHash( apiKey, apiKeyEntity.salt, - this.iterations, - this.keyLength, + this.authConfigService.apiKeyIterations, + this.authConfigService.apiKeyLength, ); return hash === apiKeyEntity.hashedAPIKey; @@ -413,8 +373,8 @@ export class AuthService { const hash = await generateHash( apiKey, apiKeyEntity.salt, - this.iterations, - this.keyLength, + this.authConfigService.apiKeyIterations, + this.authConfigService.apiKeyLength, ); const isValid = crypto.timingSafeEqual( diff --git a/packages/apps/job-launcher/server/src/modules/auth/strategy/jwt.http.ts b/packages/apps/job-launcher/server/src/modules/auth/strategy/jwt.http.ts index 35190d5539..4c21bc650b 100644 --- a/packages/apps/job-launcher/server/src/modules/auth/strategy/jwt.http.ts +++ b/packages/apps/job-launcher/server/src/modules/auth/strategy/jwt.http.ts @@ -1,24 +1,23 @@ import { ExtractJwt, Strategy } from 'passport-jwt'; import { PassportStrategy } from '@nestjs/passport'; import { Injectable, Req, UnauthorizedException } from '@nestjs/common'; -import { ConfigService } from '@nestjs/config'; import { UserEntity } from '../../user/user.entity'; import { RESEND_EMAIL_VERIFICATION_PATH } from '../../../common/constants'; import { UserStatus } from '../../../common/enums/user'; -import { ConfigNames } from '../../../common/config'; +import { AuthConfigService } from '../../../common/config/auth-config.service'; import { UserRepository } from '../../user/user.repository'; @Injectable() export class JwtHttpStrategy extends PassportStrategy(Strategy, 'jwt-http') { constructor( private readonly userRepository: UserRepository, - private readonly configService: ConfigService, + private readonly authConfigService: AuthConfigService, ) { super({ jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), ignoreExpiration: false, - secretOrKey: configService.get(ConfigNames.JWT_PUBLIC_KEY, ''), + secretOrKey: authConfigService.jwtPublicKey, passReqToCallback: true, }); } diff --git a/packages/apps/job-launcher/server/src/modules/cron-job/cron-job.service.spec.ts b/packages/apps/job-launcher/server/src/modules/cron-job/cron-job.service.spec.ts index b1e5f4d0c9..ca5ffb4eab 100644 --- a/packages/apps/job-launcher/server/src/modules/cron-job/cron-job.service.spec.ts +++ b/packages/apps/job-launcher/server/src/modules/cron-job/cron-job.service.spec.ts @@ -38,6 +38,11 @@ import { WebhookEntity } from '../webhook/webhook.entity'; import { WebhookStatus } from '../../common/enums/webhook'; import { WebhookRepository } from '../webhook/webhook.repository'; import { HttpService } from '@nestjs/axios'; +import { ServerConfigService } from '../../common/config/server-config.service'; +import { AuthConfigService } from '../../common/config/auth-config.service'; +import { Web3ConfigService } from '../../common/config/web3-config.service'; +import { CvatConfigService } from '../../common/config/cvat-config.service'; +import { PGPConfigService } from '../../common/config/pgp-config.service'; jest.mock('@human-protocol/sdk', () => ({ ...jest.requireActual('@human-protocol/sdk'), @@ -95,6 +100,11 @@ describe('CronJobService', () => { JobService, WebhookService, Encryption, + ServerConfigService, + AuthConfigService, + Web3ConfigService, + CvatConfigService, + PGPConfigService, { provide: JobRepository, useValue: createMock() }, { provide: PaymentRepository, diff --git a/packages/apps/job-launcher/server/src/modules/encryption/encryption.module.ts b/packages/apps/job-launcher/server/src/modules/encryption/encryption.module.ts index 60ce616d6a..43d5e961f6 100644 --- a/packages/apps/job-launcher/server/src/modules/encryption/encryption.module.ts +++ b/packages/apps/job-launcher/server/src/modules/encryption/encryption.module.ts @@ -1,18 +1,19 @@ import { Module, Global, Provider } from '@nestjs/common'; import { Encryption } from '@human-protocol/sdk'; -import { ConfigModule, ConfigService } from '@nestjs/config'; +import { ConfigModule } from '@nestjs/config'; +import { PGPConfigService } from '../../common/config/pgp-config.service'; const encryptionProvider: Provider = { provide: Encryption, - useFactory: async (configService: ConfigService) => { - if (!configService.get('PGP_ENCRYPT')) { + useFactory: async (pgpConfigService: PGPConfigService) => { + if (!pgpConfigService.encrypt) { return null; } - const privateKey = configService.get('PGP_PRIVATE_KEY')!; - const passPhrase = configService.get('PGP_PASSPHRASE'); + const privateKey = pgpConfigService.privateKey; + const passPhrase = pgpConfigService.passphrase; return await Encryption.build(privateKey, passPhrase); }, - inject: [ConfigService], + inject: [PGPConfigService], }; @Global() diff --git a/packages/apps/job-launcher/server/src/modules/job/job.repository.ts b/packages/apps/job-launcher/server/src/modules/job/job.repository.ts index c4657f299a..83eb0ef7e1 100644 --- a/packages/apps/job-launcher/server/src/modules/job/job.repository.ts +++ b/packages/apps/job-launcher/server/src/modules/job/job.repository.ts @@ -1,8 +1,6 @@ import { ChainId } from '@human-protocol/sdk'; import { Injectable } from '@nestjs/common'; -import { ConfigService } from '@nestjs/config'; -import { ConfigNames } from '../../common/config'; -import { DEFAULT_MAX_RETRY_COUNT } from '../../common/constants'; +import { ServerConfigService } from '../../common/config/server-config.service'; import { SortDirection } from '../../common/enums/collection'; import { DataSource, In, LessThanOrEqual } from 'typeorm'; import { JobStatus, JobStatusFilter } from '../../common/enums/job'; @@ -13,7 +11,7 @@ import { BaseRepository } from '../../database/base.repository'; export class JobRepository extends BaseRepository { constructor( private dataSource: DataSource, - public readonly configService: ConfigService, + public readonly serverConfigService: ServerConfigService, ) { super(JobEntity, dataSource); } @@ -46,12 +44,7 @@ export class JobRepository extends BaseRepository { return this.find({ where: { status: status, - retriesCount: LessThanOrEqual( - this.configService.get( - ConfigNames.MAX_RETRY_COUNT, - DEFAULT_MAX_RETRY_COUNT, - ), - ), + retriesCount: LessThanOrEqual(this.serverConfigService.maxRetryCount), waitUntil: LessThanOrEqual(new Date()), }, order: { diff --git a/packages/apps/job-launcher/server/src/modules/job/job.service.spec.ts b/packages/apps/job-launcher/server/src/modules/job/job.service.spec.ts index b894c30415..f591f96e23 100644 --- a/packages/apps/job-launcher/server/src/modules/job/job.service.spec.ts +++ b/packages/apps/job-launcher/server/src/modules/job/job.service.spec.ts @@ -68,7 +68,6 @@ import { MOCK_CVAT_DATA, MOCK_CVAT_LABELS, MOCK_CVAT_JOB_SIZE, - MOCK_CVAT_MAX_TIME, MOCK_CVAT_VAL_SIZE, MOCK_HCAPTCHA_SITE_KEY, MOCK_HCAPTCHA_IMAGE_LABEL, @@ -113,6 +112,12 @@ import { import { WebhookService } from '../webhook/webhook.service'; import { CronJobService } from '../cron-job/cron-job.service'; import { AWSRegions, StorageProviders } from '../../common/enums/storage'; +import { ServerConfigService } from '../../common/config/server-config.service'; +import { AuthConfigService } from '../../common/config/auth-config.service'; +import { Web3ConfigService } from '../../common/config/web3-config.service'; +import { CvatConfigService } from '../../common/config/cvat-config.service'; +import { PGPConfigService } from '../../common/config/pgp-config.service'; +import { S3ConfigService } from '../../common/config/s3-config.service'; const rate = 1.5; jest.mock('@human-protocol/sdk', () => ({ @@ -171,7 +176,7 @@ describe('JobService', () => { storageService: StorageService, webhookRepository: WebhookRepository; - let encrypt = true; + let encrypt = 'true'; const signerMock = { address: MOCK_ADDRESS, @@ -214,8 +219,6 @@ describe('JobService', () => { return MOCK_HCAPTCHA_SITE_KEY; case 'CVAT_JOB_SIZE': return MOCK_CVAT_JOB_SIZE; - case 'CVAT_MAX_TIME': - return MOCK_CVAT_MAX_TIME; case 'CVAT_VAL_SIZE': return MOCK_CVAT_VAL_SIZE; case 'HCAPTCHA_REPUTATION_ORACLE_URI': @@ -232,6 +235,12 @@ describe('JobService', () => { providers: [ JobService, Encryption, + ServerConfigService, + AuthConfigService, + Web3ConfigService, + CvatConfigService, + PGPConfigService, + S3ConfigService, { provide: Web3Service, useValue: { @@ -490,7 +499,7 @@ describe('JobService', () => { await expect( jobService.createJob(userId, JobRequestType.FORTUNE, fortuneJobDto), - ).rejects.toThrowError(ErrorWeb3.InvalidChainId); + ).rejects.toThrow(ErrorWeb3.InvalidChainId); }); it('should throw an exception for insufficient user balance', async () => { @@ -507,7 +516,7 @@ describe('JobService', () => { await expect( jobService.createJob(userId, JobRequestType.FORTUNE, fortuneJobDto), - ).rejects.toThrowError(ErrorJob.NotEnoughFunds); + ).rejects.toThrow(ErrorJob.NotEnoughFunds); }); }); @@ -927,9 +936,9 @@ describe('JobService', () => { advanced: {}, }; - await expect( - jobService.createHCaptchaManifest(jobDto), - ).rejects.toThrowError(BadRequestException); + await expect(jobService.createHCaptchaManifest(jobDto)).rejects.toThrow( + BadRequestException, + ); }); }); @@ -1065,7 +1074,7 @@ describe('JobService', () => { JobRequestType.IMAGE_POINTS, imageLabelBinaryJobDto, ), - ).rejects.toThrowError(ErrorBucket.InvalidProvider); + ).rejects.toThrow(ErrorBucket.InvalidProvider); expect(paymentService.getUserBalance).toHaveBeenCalledWith(userId); }); @@ -1105,7 +1114,7 @@ describe('JobService', () => { JobRequestType.IMAGE_POINTS, imageLabelBinaryJobDto, ), - ).rejects.toThrowError(ErrorBucket.InvalidRegion); + ).rejects.toThrow(ErrorBucket.InvalidRegion); expect(paymentService.getUserBalance).toHaveBeenCalledWith(userId); }); @@ -1144,7 +1153,7 @@ describe('JobService', () => { JobRequestType.IMAGE_POINTS, imageLabelBinaryJobDto, ), - ).rejects.toThrowError(ErrorBucket.EmptyRegion); + ).rejects.toThrow(ErrorBucket.EmptyRegion); expect(paymentService.getUserBalance).toHaveBeenCalledWith(userId); }); @@ -1183,7 +1192,7 @@ describe('JobService', () => { JobRequestType.IMAGE_POINTS, imageLabelBinaryJobDto, ), - ).rejects.toThrowError(ErrorBucket.EmptyBucket); + ).rejects.toThrow(ErrorBucket.EmptyBucket); expect(paymentService.getUserBalance).toHaveBeenCalledWith(userId); }); @@ -1237,7 +1246,7 @@ describe('JobService', () => { JobRequestType.IMAGE_POINTS, imageLabelBinaryJobDto, ), - ).rejects.toThrowError(ErrorWeb3.InvalidChainId); + ).rejects.toThrow(ErrorWeb3.InvalidChainId); }); it('should throw an exception for insufficient user balance', async () => { @@ -1259,7 +1268,7 @@ describe('JobService', () => { JobRequestType.IMAGE_POINTS, imageLabelBinaryJobDto, ), - ).rejects.toThrowError(ErrorJob.NotEnoughFunds); + ).rejects.toThrow(ErrorJob.NotEnoughFunds); }); }); @@ -1404,7 +1413,7 @@ describe('JobService', () => { await expect( jobService.createJob(userId, JobRequestType.HCAPTCHA, hCaptchaJobDto), - ).rejects.toThrowError(ErrorJob.NotEnoughFunds); + ).rejects.toThrow(ErrorJob.NotEnoughFunds); }); }); @@ -1854,9 +1863,7 @@ describe('JobService', () => { chainId, fortuneManifestParams, ), - ).rejects.toThrowError( - new BadGatewayException(ErrorBucket.UnableSaveFile), - ); + ).rejects.toThrow(new BadGatewayException(ErrorBucket.UnableSaveFile)); expect(storageService.uploadFile).toHaveBeenCalled(); expect( @@ -1880,7 +1887,7 @@ describe('JobService', () => { chainId, fortuneManifestParams, ), - ).rejects.toThrowError(new Error(errorMessage)); + ).rejects.toThrow(new Error(errorMessage)); expect(storageService.uploadFile).toHaveBeenCalled(); expect( @@ -1952,6 +1959,7 @@ describe('JobService', () => { ]); expect(storageService.uploadFile).toHaveBeenCalled(); + expect( JSON.parse( await encryption.decrypt( @@ -1972,9 +1980,7 @@ describe('JobService', () => { chainId, manifest, ), - ).rejects.toThrowError( - new BadGatewayException(ErrorBucket.UnableSaveFile), - ); + ).rejects.toThrow(new BadGatewayException(ErrorBucket.UnableSaveFile)); expect(storageService.uploadFile).toHaveBeenCalled(); expect( @@ -1998,7 +2004,7 @@ describe('JobService', () => { chainId, manifest, ), - ).rejects.toThrowError(new Error(errorMessage)); + ).rejects.toThrow(new Error(errorMessage)); expect(storageService.uploadFile).toHaveBeenCalled(); expect( JSON.parse( @@ -2030,7 +2036,7 @@ describe('JobService', () => { (KVStoreClient.build as any).mockImplementation(() => ({ getPublicKey: jest.fn().mockResolvedValue(MOCK_PGP_PUBLIC_KEY), })); - encrypt = false; + encrypt = 'false'; }); afterEach(() => { @@ -2038,7 +2044,7 @@ describe('JobService', () => { }); afterAll(() => { - encrypt = true; + encrypt = 'true'; }); it('should save the manifest and return the manifest URL and hash', async () => { @@ -2079,9 +2085,7 @@ describe('JobService', () => { chainId, fortuneManifestParams, ), - ).rejects.toThrowError( - new BadGatewayException(ErrorBucket.UnableSaveFile), - ); + ).rejects.toThrow(new BadGatewayException(ErrorBucket.UnableSaveFile)); expect(storageService.uploadFile).toHaveBeenCalled(); expect((storageService.uploadFile as any).mock.calls[0][0]).toEqual( @@ -2101,7 +2105,7 @@ describe('JobService', () => { chainId, fortuneManifestParams, ), - ).rejects.toThrowError(new Error(errorMessage)); + ).rejects.toThrow(new Error(errorMessage)); expect(storageService.uploadFile).toHaveBeenCalled(); expect((storageService.uploadFile as any).mock.calls[0][0]).toEqual( @@ -2209,7 +2213,7 @@ describe('JobService', () => { await expect( jobService.getResult(MOCK_USER_ID, MOCK_JOB_ID), - ).rejects.toThrowError(new NotFoundException(ErrorJob.ResultNotFound)); + ).rejects.toThrow(new NotFoundException(ErrorJob.ResultNotFound)); expect(storageService.download).toHaveBeenCalledWith(MOCK_FILE_URL); expect(storageService.download).toHaveBeenCalledTimes(1); }); @@ -2250,9 +2254,7 @@ describe('JobService', () => { await expect( jobService.getResult(MOCK_USER_ID, MOCK_JOB_ID), - ).rejects.toThrowError( - new NotFoundException(ErrorJob.ResultValidationFailed), - ); + ).rejects.toThrow(new NotFoundException(ErrorJob.ResultValidationFailed)); expect(storageService.download).toHaveBeenCalledWith(MOCK_FILE_URL); expect(storageService.download).toHaveBeenCalledTimes(1); diff --git a/packages/apps/job-launcher/server/src/modules/job/job.service.ts b/packages/apps/job-launcher/server/src/modules/job/job.service.ts index 535df1142b..acab0bc649 100644 --- a/packages/apps/job-launcher/server/src/modules/job/job.service.ts +++ b/packages/apps/job-launcher/server/src/modules/job/job.service.ts @@ -21,10 +21,13 @@ import { NotFoundException, ValidationError, } from '@nestjs/common'; -import { ConfigService } from '@nestjs/config'; import { validate } from 'class-validator'; import { ethers } from 'ethers'; -import { ConfigNames } from '../../common/config'; +import { AuthConfigService } from '../../common/config/auth-config.service'; +import { ServerConfigService } from '../../common/config/server-config.service'; +import { CvatConfigService } from '../../common/config/cvat-config.service'; +import { PGPConfigService } from '../../common/config/pgp-config.service'; +import { Web3ConfigService } from '../../common/config/web3-config.service'; import { ErrorBucket, ErrorEscrow, @@ -76,7 +79,6 @@ import { JobRepository } from './job.repository'; import { RoutingProtocolService } from './routing-protocol.service'; import { CANCEL_JOB_STATUSES, - DEFAULT_MAX_RETRY_COUNT, HCAPTCHA_BOUNDING_BOX_MAX_POINTS, HCAPTCHA_BOUNDING_BOX_MIN_POINTS, HCAPTCHA_IMMO_MAX_LENGTH, @@ -130,7 +132,11 @@ export class JobService { public readonly webhookRepository: WebhookRepository, private readonly paymentService: PaymentService, private readonly paymentRepository: PaymentRepository, - public readonly configService: ConfigService, + public readonly serverConfigService: ServerConfigService, + public readonly authConfigService: AuthConfigService, + public readonly web3ConfigService: Web3ConfigService, + public readonly cvatConfigService: CvatConfigService, + public readonly pgpConfigService: PGPConfigService, private readonly routingProtocolService: RoutingProtocolService, private readonly storageService: StorageService, @Inject(Encryption) private readonly encryption: Encryption, @@ -159,15 +165,11 @@ export class JobService { description: dto.requesterDescription, user_guide: dto.userGuide, type: requestType, - job_size: Number( - this.configService.get(ConfigNames.CVAT_JOB_SIZE)!, - ), + job_size: this.cvatConfigService.jobSize, }, validation: { min_quality: dto.minQuality, - val_size: Number( - this.configService.get(ConfigNames.CVAT_VAL_SIZE)!, - ), + val_size: this.cvatConfigService.valSize, gt_url: generateBucketUrl(dto.groundTruth, requestType), }, job_bounty: await this.calculateJobBounty(elementsCount, tokenFundAmount), @@ -202,12 +204,8 @@ export class JobService { ), public_results: true, oracle_stake: HCAPTCHA_ORACLE_STAKE, - repo_uri: this.configService.get( - ConfigNames.HCAPTCHA_REPUTATION_ORACLE_URI, - )!, - ro_uri: this.configService.get( - ConfigNames.HCAPTCHA_RECORDING_ORACLE_URI, - )!, + repo_uri: this.web3ConfigService.hCaptchaReputationOracleURI, + ro_uri: this.web3ConfigService.hCaptchaRecordingOracleURI, }; let groundTruthsData; @@ -351,7 +349,7 @@ export class JobService { restrictedAudience.sitekey = [ { - [this.configService.get(ConfigNames.HCAPTCHA_SITE_KEY)!]: { + [this.authConfigService.hCaptchaSiteKey]: { score: 1, }, }, @@ -500,90 +498,60 @@ export class JobService { private getOraclesSpecificActions: Record = { [JobRequestType.HCAPTCHA]: { getOracleAddresses: (): OracleAddresses => { - const exchangeOracle = this.configService.get( - ConfigNames.HCAPTCHA_ORACLE_ADDRESS, - )!; - const recordingOracle = this.configService.get( - ConfigNames.HCAPTCHA_ORACLE_ADDRESS, - )!; - const reputationOracle = this.configService.get( - ConfigNames.HCAPTCHA_ORACLE_ADDRESS, - )!; + const exchangeOracle = this.web3ConfigService.hCaptchaOracleAddress; + const recordingOracle = this.web3ConfigService.hCaptchaOracleAddress; + const reputationOracle = this.web3ConfigService.hCaptchaOracleAddress; return { exchangeOracle, recordingOracle, reputationOracle }; }, }, [JobRequestType.FORTUNE]: { getOracleAddresses: (): OracleAddresses => { - const exchangeOracle = this.configService.get( - ConfigNames.FORTUNE_EXCHANGE_ORACLE_ADDRESS, - )!; - const recordingOracle = this.configService.get( - ConfigNames.FORTUNE_RECORDING_ORACLE_ADDRESS, - )!; - const reputationOracle = this.configService.get( - ConfigNames.REPUTATION_ORACLE_ADDRESS, - )!; + const exchangeOracle = + this.web3ConfigService.fortuneExchangeOracleAddress; + const recordingOracle = + this.web3ConfigService.fortuneRecordingOracleAddress; + const reputationOracle = this.web3ConfigService.reputationOracleAddress; return { exchangeOracle, recordingOracle, reputationOracle }; }, }, [JobRequestType.IMAGE_BOXES]: { getOracleAddresses: (): OracleAddresses => { - const exchangeOracle = this.configService.get( - ConfigNames.CVAT_EXCHANGE_ORACLE_ADDRESS, - )!; - const recordingOracle = this.configService.get( - ConfigNames.CVAT_RECORDING_ORACLE_ADDRESS, - )!; - const reputationOracle = this.configService.get( - ConfigNames.REPUTATION_ORACLE_ADDRESS, - )!; + const exchangeOracle = this.web3ConfigService.cvatExchangeOracleAddress; + const recordingOracle = + this.web3ConfigService.cvatRecordingOracleAddress; + const reputationOracle = this.web3ConfigService.reputationOracleAddress; return { exchangeOracle, recordingOracle, reputationOracle }; }, }, [JobRequestType.IMAGE_POINTS]: { getOracleAddresses: (): OracleAddresses => { - const exchangeOracle = this.configService.get( - ConfigNames.CVAT_EXCHANGE_ORACLE_ADDRESS, - )!; - const recordingOracle = this.configService.get( - ConfigNames.CVAT_RECORDING_ORACLE_ADDRESS, - )!; - const reputationOracle = this.configService.get( - ConfigNames.REPUTATION_ORACLE_ADDRESS, - )!; + const exchangeOracle = this.web3ConfigService.cvatExchangeOracleAddress; + const recordingOracle = + this.web3ConfigService.cvatRecordingOracleAddress; + const reputationOracle = this.web3ConfigService.reputationOracleAddress; return { exchangeOracle, recordingOracle, reputationOracle }; }, }, [JobRequestType.IMAGE_BOXES_FROM_POINTS]: { getOracleAddresses: (): OracleAddresses => { - const exchangeOracle = this.configService.get( - ConfigNames.CVAT_EXCHANGE_ORACLE_ADDRESS, - )!; - const recordingOracle = this.configService.get( - ConfigNames.CVAT_RECORDING_ORACLE_ADDRESS, - )!; - const reputationOracle = this.configService.get( - ConfigNames.REPUTATION_ORACLE_ADDRESS, - )!; + const exchangeOracle = this.web3ConfigService.cvatExchangeOracleAddress; + const recordingOracle = + this.web3ConfigService.cvatRecordingOracleAddress; + const reputationOracle = this.web3ConfigService.reputationOracleAddress; return { exchangeOracle, recordingOracle, reputationOracle }; }, }, [JobRequestType.IMAGE_SKELETONS_FROM_BOXES]: { getOracleAddresses: (): OracleAddresses => { - const exchangeOracle = this.configService.get( - ConfigNames.CVAT_EXCHANGE_ORACLE_ADDRESS, - )!; - const recordingOracle = this.configService.get( - ConfigNames.CVAT_RECORDING_ORACLE_ADDRESS, - )!; - const reputationOracle = this.configService.get( - ConfigNames.REPUTATION_ORACLE_ADDRESS, - )!; + const exchangeOracle = this.web3ConfigService.cvatExchangeOracleAddress; + const recordingOracle = + this.web3ConfigService.cvatRecordingOracleAddress; + const reputationOracle = this.web3ConfigService.reputationOracleAddress; return { exchangeOracle, recordingOracle, reputationOracle }; }, @@ -703,10 +671,7 @@ export class JobService { fundAmount: number, ): Promise { const totalJobs = Math.ceil( - div( - elementsCount, - Number(this.configService.get(ConfigNames.CVAT_JOB_SIZE)!), - ), + div(elementsCount, this.cvatConfigService.jobSize), ); return ethers.formatEther( @@ -852,13 +817,32 @@ export class JobService { await this.jobRepository.updateOne(jobEntity); } + private getOracleAddresses(manifest: any) { + let exchangeOracle; + let recordingOracle; + const oracleType = this.getOracleType(manifest); + if (oracleType === OracleType.FORTUNE) { + exchangeOracle = this.web3ConfigService.fortuneExchangeOracleAddress; + recordingOracle = this.web3ConfigService.fortuneRecordingOracleAddress; + } else if (oracleType === OracleType.HCAPTCHA) { + exchangeOracle = this.web3ConfigService.hCaptchaOracleAddress; + recordingOracle = this.web3ConfigService.hCaptchaOracleAddress; + } else { + exchangeOracle = this.web3ConfigService.cvatExchangeOracleAddress; + recordingOracle = this.web3ConfigService.cvatRecordingOracleAddress; + } + const reputationOracle = this.web3ConfigService.reputationOracleAddress; + + return { exchangeOracle, recordingOracle, reputationOracle }; + } + public async uploadManifest( requestType: JobRequestType, chainId: ChainId, data: any, ): Promise { let manifestFile = data; - if (this.configService.get(ConfigNames.PGP_ENCRYPT) as boolean) { + if (this.pgpConfigService.encrypt) { const { getOracleAddresses } = this.getOraclesSpecificActions[requestType]; @@ -1097,13 +1081,7 @@ export class JobService { } public handleProcessJobFailure = async (jobEntity: JobEntity) => { - if ( - jobEntity.retriesCount < - this.configService.get( - ConfigNames.MAX_RETRY_COUNT, - DEFAULT_MAX_RETRY_COUNT, - ) - ) { + if (jobEntity.retriesCount < this.serverConfigService.maxRetryCount) { jobEntity.retriesCount += 1; } else { jobEntity.status = JobStatus.FAILED; diff --git a/packages/apps/job-launcher/server/src/modules/payment/payment.service.spec.ts b/packages/apps/job-launcher/server/src/modules/payment/payment.service.spec.ts index 18048016fb..715260a67e 100644 --- a/packages/apps/job-launcher/server/src/modules/payment/payment.service.spec.ts +++ b/packages/apps/job-launcher/server/src/modules/payment/payment.service.spec.ts @@ -33,6 +33,7 @@ import { PaymentEntity } from './payment.entity'; import { verifySignature } from '../../common/utils/signature'; import { ConflictException } from '@nestjs/common'; import { DatabaseError } from '../../database/database.error'; +import { StripeConfigService } from '../../common/config/stripe-config.service'; jest.mock('@human-protocol/sdk'); @@ -77,6 +78,7 @@ describe('PaymentService', () => { const moduleRef = await Test.createTestingModule({ providers: [ PaymentService, + StripeConfigService, { provide: PaymentRepository, useValue: createMock(), diff --git a/packages/apps/job-launcher/server/src/modules/payment/payment.service.ts b/packages/apps/job-launcher/server/src/modules/payment/payment.service.ts index 0aa1ad868d..aca930ed34 100644 --- a/packages/apps/job-launcher/server/src/modules/payment/payment.service.ts +++ b/packages/apps/job-launcher/server/src/modules/payment/payment.service.ts @@ -5,7 +5,6 @@ import { Logger, NotFoundException, } from '@nestjs/common'; -import { ConfigService } from '@nestjs/config'; import Stripe from 'stripe'; import { ethers } from 'ethers'; import { ErrorPayment } from '../../common/constants/errors'; @@ -25,7 +24,8 @@ import { TokenId, } from '../../common/enums/payment'; import { TX_CONFIRMATION_TRESHOLD } from '../../common/constants'; -import { ConfigNames, networkMap } from '../../common/config'; +import { networkMap } from '../../common/config'; +import { StripeConfigService } from '../../common/config/stripe-config.service'; import { HMToken, HMToken__factory, @@ -45,30 +45,16 @@ export class PaymentService { constructor( private readonly web3Service: Web3Service, private readonly paymentRepository: PaymentRepository, - private configService: ConfigService, + private stripeConfigService: StripeConfigService, ) { - this.stripe = new Stripe( - this.configService.get(ConfigNames.STRIPE_SECRET_KEY, ''), - { - apiVersion: this.configService.get( - ConfigNames.STRIPE_API_VERSION, - '', - ), - appInfo: { - name: this.configService.get( - ConfigNames.STRIPE_APP_NAME, - 'Fortune', - ), - version: this.configService.get( - ConfigNames.STRIPE_APP_VERSION, - ), - url: this.configService.get( - ConfigNames.STRIPE_APP_INFO_URL, - '', - ), - }, + this.stripe = new Stripe(this.stripeConfigService.secretKey, { + apiVersion: this.stripeConfigService.apiVersion as any, + appInfo: { + name: this.stripeConfigService.appName, + version: this.stripeConfigService.appVersion, + url: this.stripeConfigService.appInfoURL, }, - ); + }); } public async createFiatPayment( diff --git a/packages/apps/job-launcher/server/src/modules/sendgrid/sendgrid.service.spec.ts b/packages/apps/job-launcher/server/src/modules/sendgrid/sendgrid.service.spec.ts index c26f8b7a25..900667a757 100644 --- a/packages/apps/job-launcher/server/src/modules/sendgrid/sendgrid.service.spec.ts +++ b/packages/apps/job-launcher/server/src/modules/sendgrid/sendgrid.service.spec.ts @@ -8,6 +8,7 @@ import { MOCK_SENDGRID_FROM_EMAIL, MOCK_SENDGRID_FROM_NAME, } from '../../../test/constants'; +import { SendgridConfigService } from '../../common/config/sendgrid-config.service'; describe('SendGridService', () => { let sendGridService: SendGridService; @@ -36,6 +37,7 @@ describe('SendGridService', () => { const app = await Test.createTestingModule({ providers: [ SendGridService, + SendgridConfigService, { provide: MailService, useValue: mockMailService, @@ -107,11 +109,6 @@ describe('SendGridService', () => { describe('constructor', () => { it('should initialize SendGridService with valid API key', () => { - sendGridService = new SendGridService( - mailService, - mockConfigService as any, - ); - expect(mailService.setApiKey).toHaveBeenCalledWith(MOCK_SENDGRID_API_KEY); expect(sendGridService['defaultFromEmail']).toEqual( MOCK_SENDGRID_FROM_EMAIL, @@ -122,15 +119,14 @@ describe('SendGridService', () => { }); it('should throw an error with invalid API key', async () => { - const invalidApiKey = 'invalid-api-key'; - mockConfigService.get = jest.fn().mockReturnValue(invalidApiKey); + mockConfigService.get = jest.fn().mockReturnValue('invalid-api-key'); expect(() => { sendGridService = new SendGridService( mailService, mockConfigService as any, ); - }).toThrowError(ErrorSendGrid.InvalidApiKey); + }).toThrow(ErrorSendGrid.InvalidApiKey); }); }); }); diff --git a/packages/apps/job-launcher/server/src/modules/sendgrid/sendgrid.service.ts b/packages/apps/job-launcher/server/src/modules/sendgrid/sendgrid.service.ts index cd6d2d9340..46ee073b8f 100644 --- a/packages/apps/job-launcher/server/src/modules/sendgrid/sendgrid.service.ts +++ b/packages/apps/job-launcher/server/src/modules/sendgrid/sendgrid.service.ts @@ -1,9 +1,8 @@ import { BadRequestException, Injectable, Logger } from '@nestjs/common'; -import { ConfigService } from '@nestjs/config'; import { MailDataRequired, MailService } from '@sendgrid/mail'; -import { ConfigNames } from '../../common/config'; import { SENDGRID_API_KEY_REGEX } from '../../common/constants'; import { ErrorSendGrid } from '../../common/constants/errors'; +import { SendgridConfigService } from '../../common/config/sendgrid-config.service'; @Injectable() export class SendGridService { @@ -14,12 +13,9 @@ export class SendGridService { constructor( private readonly mailService: MailService, - private readonly configService: ConfigService, + private readonly sendgridConfigService: SendgridConfigService, ) { - const apiKey = this.configService.get( - ConfigNames.SENDGRID_API_KEY, - '', - ); + const apiKey = this.sendgridConfigService.apiKey; if (!SENDGRID_API_KEY_REGEX.test(apiKey)) { throw new Error(ErrorSendGrid.InvalidApiKey); @@ -27,14 +23,8 @@ export class SendGridService { this.mailService.setApiKey(apiKey); - this.defaultFromEmail = this.configService.get( - ConfigNames.SENDGRID_FROM_EMAIL, - '', - ); - this.defaultFromName = this.configService.get( - ConfigNames.SENDGRID_FROM_NAME, - '', - ); + this.defaultFromEmail = this.sendgridConfigService.fromEmail; + this.defaultFromName = this.sendgridConfigService.fromName; } async sendEmail({ @@ -53,7 +43,6 @@ export class SendGridService { personalizations, ...emailData, }); - this.logger.log('Email sent successfully'); return; } catch (error) { diff --git a/packages/apps/job-launcher/server/src/modules/storage/storage.module.ts b/packages/apps/job-launcher/server/src/modules/storage/storage.module.ts index 551196f956..a251aae490 100644 --- a/packages/apps/job-launcher/server/src/modules/storage/storage.module.ts +++ b/packages/apps/job-launcher/server/src/modules/storage/storage.module.ts @@ -1,11 +1,9 @@ import { Module } from '@nestjs/common'; import { StorageService } from './storage.service'; -import { ConfigModule } from '@nestjs/config'; -import { s3Config } from '../../common/config'; import { EncryptionModule } from '../encryption/encryption.module'; @Module({ - imports: [ConfigModule.forFeature(s3Config), EncryptionModule], + imports: [EncryptionModule], providers: [StorageService], exports: [StorageService], }) diff --git a/packages/apps/job-launcher/server/src/modules/storage/storage.service.spec.ts b/packages/apps/job-launcher/server/src/modules/storage/storage.service.spec.ts index 7bd0454367..26d23e1bf6 100644 --- a/packages/apps/job-launcher/server/src/modules/storage/storage.service.spec.ts +++ b/packages/apps/job-launcher/server/src/modules/storage/storage.service.spec.ts @@ -6,6 +6,7 @@ import { import { ConfigModule, ConfigService, registerAs } from '@nestjs/config'; import { Test } from '@nestjs/testing'; import { + MOCK_BUCKET_NAME, MOCK_FILE_HASH, MOCK_FILE_URL, MOCK_MANIFEST, @@ -23,6 +24,7 @@ import stringify from 'json-stable-stringify'; import { ErrorBucket } from '../../common/constants/errors'; import { hashString } from '../../common/utils'; import { ContentType } from '../../common/enums/storage'; +import { S3ConfigService } from '../../common/config/s3-config.service'; jest.mock('@human-protocol/sdk', () => ({ ...jest.requireActual('@human-protocol/sdk'), @@ -56,6 +58,8 @@ describe('StorageService', () => { switch (key) { case 'MOCK_PGP_PRIVATE_KEY': return MOCK_PGP_PRIVATE_KEY; + case 'S3_BUCKET': + return MOCK_BUCKET_NAME; } }), }; @@ -75,6 +79,7 @@ describe('StorageService', () => { ], providers: [ StorageService, + S3ConfigService, { provide: Encryption, useValue: await Encryption.build(MOCK_PGP_PRIVATE_KEY), @@ -100,7 +105,7 @@ describe('StorageService', () => { hash: expect.any(String), }); expect(storageService.minioClient.putObject).toHaveBeenCalledWith( - MOCK_S3_BUCKET, + MOCK_BUCKET_NAME, expect.any(String), expect.any(String), { diff --git a/packages/apps/job-launcher/server/src/modules/storage/storage.service.ts b/packages/apps/job-launcher/server/src/modules/storage/storage.service.ts index 0e6f922663..d6f564b6a7 100644 --- a/packages/apps/job-launcher/server/src/modules/storage/storage.service.ts +++ b/packages/apps/job-launcher/server/src/modules/storage/storage.service.ts @@ -5,35 +5,32 @@ import { } from '@human-protocol/sdk'; import { BadRequestException, Inject, Injectable } from '@nestjs/common'; import * as Minio from 'minio'; -import { S3ConfigType, s3ConfigKey } from '../../common/config'; -import { ErrorBucket } from '../../common/constants/errors'; import stringify from 'json-stable-stringify'; +import { ErrorBucket } from '../../common/constants/errors'; import { ContentType, Extension } from '../../common/enums/storage'; import { UploadedFile } from '../../common/interfaces'; -import { ConfigService } from '@nestjs/config'; +import { S3ConfigService } from '../../common/config/s3-config.service'; @Injectable() export class StorageService { public readonly minioClient: Minio.Client; constructor( - @Inject(s3ConfigKey) - private s3Config: S3ConfigType, - public readonly configService: ConfigService, + public readonly s3ConfigService: S3ConfigService, @Inject(Encryption) private readonly encryption: Encryption, ) { this.minioClient = new Minio.Client({ - endPoint: this.s3Config.endPoint, - port: this.s3Config.port, - accessKey: this.s3Config.accessKey, - secretKey: this.s3Config.secretKey, - useSSL: this.s3Config.useSSL, + endPoint: this.s3ConfigService.endpoint, + port: this.s3ConfigService.port, + accessKey: this.s3ConfigService.accessKey, + secretKey: this.s3ConfigService.secretKey, + useSSL: this.s3ConfigService.useSSL, }); } public formatUrl(key: string): string { - return `${this.s3Config.useSSL ? 'https' : 'http'}://${ - this.s3Config.endPoint - }:${this.s3Config.port}/${this.s3Config.bucket}/${key}`; + return `${this.s3ConfigService.useSSL ? 'https' : 'http'}://${ + this.s3ConfigService.endpoint + }:${this.s3ConfigService.port}/${this.s3ConfigService.bucket}/${key}`; } public async download(url: string): Promise { @@ -56,7 +53,7 @@ export class StorageService { data: string | object, hash: string, ): Promise { - if (!(await this.minioClient.bucketExists(this.s3Config.bucket))) { + if (!(await this.minioClient.bucketExists(this.s3ConfigService.bucket))) { throw new BadRequestException(ErrorBucket.NotExist); } @@ -68,10 +65,15 @@ export class StorageService { const key = `s3${hash}${isStringData ? '' : Extension.JSON}`; try { - await this.minioClient.putObject(this.s3Config.bucket, key, content, { - 'Content-Type': contentType, - 'Cache-Control': 'no-store', - }); + await this.minioClient.putObject( + this.s3ConfigService.bucket, + key, + content, + { + 'Content-Type': contentType, + 'Cache-Control': 'no-store', + }, + ); return { url: this.formatUrl(key), hash }; } catch (e) { diff --git a/packages/apps/job-launcher/server/src/modules/web3/web3.service.spec.ts b/packages/apps/job-launcher/server/src/modules/web3/web3.service.spec.ts index 6587cbb674..eb0da32eb2 100644 --- a/packages/apps/job-launcher/server/src/modules/web3/web3.service.spec.ts +++ b/packages/apps/job-launcher/server/src/modules/web3/web3.service.spec.ts @@ -6,6 +6,7 @@ import { ErrorWeb3 } from '../../common/constants/errors'; import { Web3Env } from '../../common/enums/web3'; import { Web3Service } from './web3.service'; import { MOCK_ADDRESS, MOCK_PRIVATE_KEY } from './../../../test/constants'; +import { Web3ConfigService } from '../../common/config/web3-config.service'; describe('Web3Service', () => { let mockConfigService: Partial; @@ -18,7 +19,7 @@ describe('Web3Service', () => { case 'WEB3_PRIVATE_KEY': return MOCK_PRIVATE_KEY; case 'WEB3_ENV': - return 'testnet'; + return Web3Env.TESTNET; default: return defaultValue; } @@ -28,6 +29,7 @@ describe('Web3Service', () => { const moduleRef = await Test.createTestingModule({ providers: [ Web3Service, + Web3ConfigService, { provide: ConfigService, useValue: mockConfigService, @@ -57,13 +59,13 @@ describe('Web3Service', () => { describe('getValidChains', () => { it('should get all valid chainIds on MAINNET', () => { - (web3Service as any).currentWeb3Env = Web3Env.MAINNET; + mockConfigService.get = jest.fn().mockReturnValue(Web3Env.MAINNET); const validChainIds = web3Service.getValidChains(); expect(validChainIds).toBe(MAINNET_CHAIN_IDS); }); it('should get all valid chainIds on TESTNET', () => { - (web3Service as any).currentWeb3Env = Web3Env.TESTNET; + mockConfigService.get = jest.fn().mockReturnValue(Web3Env.TESTNET); const validChainIds = web3Service.getValidChains(); expect(validChainIds).toBe(TESTNET_CHAIN_IDS); }); diff --git a/packages/apps/job-launcher/server/src/modules/web3/web3.service.ts b/packages/apps/job-launcher/server/src/modules/web3/web3.service.ts index 9ad91b9279..32bec90893 100644 --- a/packages/apps/job-launcher/server/src/modules/web3/web3.service.ts +++ b/packages/apps/job-launcher/server/src/modules/web3/web3.service.ts @@ -1,7 +1,7 @@ import { BadRequestException, Injectable, Logger } from '@nestjs/common'; -import { ConfigService } from '@nestjs/config'; import { Wallet, ethers } from 'ethers'; -import { ConfigNames, networks } from '../../common/config'; +import { Web3ConfigService } from '../../common/config/web3-config.service'; +import { networks } from '../../common/config'; import { Web3Env } from '../../common/enums/web3'; import { LOCALHOST_CHAIN_IDS, @@ -16,13 +16,9 @@ export class Web3Service { private signers: { [key: number]: Wallet } = {}; public readonly logger = new Logger(Web3Service.name); public readonly signerAddress: string; - public readonly currentWeb3Env: string; - constructor(private readonly configService: ConfigService) { - this.currentWeb3Env = this.configService.get( - ConfigNames.WEB3_ENV, - ) as string; - const privateKey = this.configService.get(ConfigNames.WEB3_PRIVATE_KEY); + constructor(public readonly web3ConfigService: Web3ConfigService) { + const privateKey = this.web3ConfigService.privateKey; const validChains = this.getValidChains(); const validNetworks = networks.filter((network) => validChains.includes(network.chainId), @@ -48,7 +44,7 @@ export class Web3Service { } public getValidChains(): ChainId[] { - switch (this.currentWeb3Env) { + switch (this.web3ConfigService.env) { case Web3Env.MAINNET: return MAINNET_CHAIN_IDS; case Web3Env.TESTNET: @@ -61,10 +57,7 @@ export class Web3Service { public async calculateGasPrice(chainId: number): Promise { const signer = this.getSigner(chainId); - const multiplier = this.configService.get( - ConfigNames.GAS_PRICE_MULTIPLIER, - 1, - ); + const multiplier = this.web3ConfigService.gasPriceMultiplier; const gasPrice = (await signer.provider?.getFeeData())?.gasPrice; if (gasPrice) { return gasPrice * BigInt(multiplier); diff --git a/packages/apps/job-launcher/server/src/modules/webhook/webhook.controller.spec.ts b/packages/apps/job-launcher/server/src/modules/webhook/webhook.controller.spec.ts index 4f98382b6c..e2f52281e7 100644 --- a/packages/apps/job-launcher/server/src/modules/webhook/webhook.controller.spec.ts +++ b/packages/apps/job-launcher/server/src/modules/webhook/webhook.controller.spec.ts @@ -16,6 +16,8 @@ import { MOCK_MAX_RETRY_COUNT, MOCK_PRIVATE_KEY, } from '../../../test/constants'; +import { ServerConfigService } from '../../common/config/server-config.service'; +import { Web3ConfigService } from '../../common/config/web3-config.service'; jest.mock('@human-protocol/sdk'); @@ -28,6 +30,8 @@ describe('WebhookController', () => { controllers: [WebhookController], providers: [ WebhookService, + ServerConfigService, + Web3ConfigService, { provide: Web3Service, useValue: { diff --git a/packages/apps/job-launcher/server/src/modules/webhook/webhook.repository.ts b/packages/apps/job-launcher/server/src/modules/webhook/webhook.repository.ts index ba6d5a7beb..35d7bc8ef6 100644 --- a/packages/apps/job-launcher/server/src/modules/webhook/webhook.repository.ts +++ b/packages/apps/job-launcher/server/src/modules/webhook/webhook.repository.ts @@ -1,10 +1,8 @@ import { Injectable, Logger } from '@nestjs/common'; -import { ConfigService } from '@nestjs/config'; import { BaseRepository } from '../../database/base.repository'; import { DataSource, LessThanOrEqual } from 'typeorm'; -import { ConfigNames } from '../../common/config'; -import { DEFAULT_MAX_RETRY_COUNT } from '../../common/constants'; +import { ServerConfigService } from '../../common/config/server-config.service'; import { WebhookStatus } from '../../common/enums/webhook'; import { WebhookEntity } from './webhook.entity'; @@ -13,7 +11,7 @@ export class WebhookRepository extends BaseRepository { private readonly logger = new Logger(WebhookRepository.name); constructor( private dataSource: DataSource, - public readonly configService: ConfigService, + public readonly serverConfigService: ServerConfigService, ) { super(WebhookEntity, dataSource); } @@ -22,12 +20,7 @@ export class WebhookRepository extends BaseRepository { return this.find({ where: { status: status, - retriesCount: LessThanOrEqual( - this.configService.get( - ConfigNames.MAX_RETRY_COUNT, - DEFAULT_MAX_RETRY_COUNT, - ), - ), + retriesCount: LessThanOrEqual(this.serverConfigService.maxRetryCount), waitUntil: LessThanOrEqual(new Date()), }, diff --git a/packages/apps/job-launcher/server/src/modules/webhook/webhook.service.spec.ts b/packages/apps/job-launcher/server/src/modules/webhook/webhook.service.spec.ts index ca9025a993..adb08c8714 100644 --- a/packages/apps/job-launcher/server/src/modules/webhook/webhook.service.spec.ts +++ b/packages/apps/job-launcher/server/src/modules/webhook/webhook.service.spec.ts @@ -25,6 +25,8 @@ import { HEADER_SIGNATURE_KEY } from '../../common/constants'; import { JobService } from '../job/job.service'; import { WebhookDataDto } from './webhook.dto'; import { HttpStatus } from '@nestjs/common'; +import { ServerConfigService } from '../../common/config/server-config.service'; +import { Web3ConfigService } from '../../common/config/web3-config.service'; jest.mock('@human-protocol/sdk', () => ({ ...jest.requireActual('@human-protocol/sdk'), @@ -67,6 +69,8 @@ describe('WebhookService', () => { const moduleRef = await Test.createTestingModule({ providers: [ WebhookService, + ServerConfigService, + Web3ConfigService, { provide: Web3Service, useValue: { diff --git a/packages/apps/job-launcher/server/src/modules/webhook/webhook.service.ts b/packages/apps/job-launcher/server/src/modules/webhook/webhook.service.ts index 2abfacac91..f0b4d3ac67 100644 --- a/packages/apps/job-launcher/server/src/modules/webhook/webhook.service.ts +++ b/packages/apps/job-launcher/server/src/modules/webhook/webhook.service.ts @@ -12,15 +12,12 @@ import { KVStoreClient, KVStoreKeys, } from '@human-protocol/sdk'; -import { ConfigService } from '@nestjs/config'; -import { ConfigNames } from '../../common/config'; +import { ServerConfigService } from '../../common/config/server-config.service'; +import { Web3ConfigService } from '../../common/config/web3-config.service'; import { signMessage } from '../../common/utils/signature'; import { WebhookRepository } from './webhook.repository'; import { firstValueFrom } from 'rxjs'; -import { - DEFAULT_MAX_RETRY_COUNT, - HEADER_SIGNATURE_KEY, -} from '../../common/constants'; +import { HEADER_SIGNATURE_KEY } from '../../common/constants'; import { HttpService } from '@nestjs/axios'; import { Web3Service } from '../web3/web3.service'; import { WebhookStatus } from '../../common/enums/webhook'; @@ -40,7 +37,8 @@ export class WebhookService { private readonly web3Service: Web3Service, private readonly webhookRepository: WebhookRepository, private readonly jobService: JobService, - public readonly configService: ConfigService, + public readonly commonConfigSerice: ServerConfigService, + public readonly web3ConfigService: Web3ConfigService, public readonly httpService: HttpService, ) {} @@ -75,7 +73,7 @@ export class WebhookService { if (webhook.hasSignature) { const signedBody = await signMessage( webhookData, - this.configService.get(ConfigNames.WEB3_PRIVATE_KEY)!, + this.web3ConfigService.privateKey, ); config = { @@ -131,13 +129,7 @@ export class WebhookService { * @returns {Promise} - Returns a promise that resolves when the operation is complete. */ public async handleWebhookError(webhookEntity: WebhookEntity): Promise { - if ( - webhookEntity.retriesCount >= - this.configService.get( - ConfigNames.MAX_RETRY_COUNT, - DEFAULT_MAX_RETRY_COUNT, - ) - ) { + if (webhookEntity.retriesCount >= this.commonConfigSerice.maxRetryCount) { webhookEntity.status = WebhookStatus.FAILED; } else { webhookEntity.waitUntil = new Date(); From 67ef7b84a2bc65afefd33aa708e26e78880df833 Mon Sep 17 00:00:00 2001 From: eugenvoronov <104138627+eugenvoronov@users.noreply.github.com> Date: Mon, 1 Apr 2024 18:22:35 +0300 Subject: [PATCH 18/66] [Job Launcher] Implemented get elements count (#1789) * Implemented get elements count for cvat * Slightly changed the way of calculating `jobBounty` for `IMAGE_BOXES_FROM_POINTS` and `IMAGE_SKELETONS_FROM_BOXES` job types * Updated job interface, updated get elements count method, improved calculate job bounty method * Updated job service * Removed env config --------- Co-authored-by: Sergey Dzeranov --- .../src/common/config/cvat-config.service.ts | 6 + .../server/src/common/config/env-schema.ts | 2 + .../server/src/common/constants/errors.ts | 1 + .../server/src/modules/job/job.interface.ts | 14 +- .../src/modules/job/job.service.spec.ts | 165 +++++++++++++++++- .../server/src/modules/job/job.service.ts | 111 +++++++++++- .../job-launcher/server/test/constants.ts | 26 ++- 7 files changed, 307 insertions(+), 18 deletions(-) diff --git a/packages/apps/job-launcher/server/src/common/config/cvat-config.service.ts b/packages/apps/job-launcher/server/src/common/config/cvat-config.service.ts index f4a1356cf9..2136b3954f 100644 --- a/packages/apps/job-launcher/server/src/common/config/cvat-config.service.ts +++ b/packages/apps/job-launcher/server/src/common/config/cvat-config.service.ts @@ -13,4 +13,10 @@ export class CvatConfigService { get valSize(): number { return +this.configService.get('CVAT_VAL_SIZE', 2); } + get skeletonsJobSizeMultiplier(): number { + return +this.configService.get( + 'CVAT_SKELETONS_JOB_SIZE_MULTIPLIER', + 6, + ); + } } diff --git a/packages/apps/job-launcher/server/src/common/config/env-schema.ts b/packages/apps/job-launcher/server/src/common/config/env-schema.ts index f20dbf75b7..549e65ea7a 100644 --- a/packages/apps/job-launcher/server/src/common/config/env-schema.ts +++ b/packages/apps/job-launcher/server/src/common/config/env-schema.ts @@ -57,7 +57,9 @@ export const envValidator = Joi.object({ SENDGRID_FROM_NAME: Joi.string(), // CVAT CVAT_JOB_SIZE: Joi.string(), + CVAT_MAX_TIME: Joi.string(), CVAT_VAL_SIZE: Joi.string(), + CVAT_SKELETONS_JOB_SIZE_MULTIPLIER: Joi.string(), //PGP PGP_ENCRYPT: Joi.boolean(), PGP_PRIVATE_KEY: Joi.string().optional(), diff --git a/packages/apps/job-launcher/server/src/common/constants/errors.ts b/packages/apps/job-launcher/server/src/common/constants/errors.ts index 4db5d482c8..b9fdb4dc8a 100644 --- a/packages/apps/job-launcher/server/src/common/constants/errors.ts +++ b/packages/apps/job-launcher/server/src/common/constants/errors.ts @@ -18,6 +18,7 @@ export enum ErrorJob { HCaptchaInvalidJobType = 'hCaptcha invalid job type', GroundThuthValidationFailed = 'Ground thuth validation failed', ManifestHashNotExist = 'Manifest hash does not exist', + DataNotExist = 'Data does not exist', } /** diff --git a/packages/apps/job-launcher/server/src/modules/job/job.interface.ts b/packages/apps/job-launcher/server/src/modules/job/job.interface.ts index 78c765ad43..821dc31114 100644 --- a/packages/apps/job-launcher/server/src/modules/job/job.interface.ts +++ b/packages/apps/job-launcher/server/src/modules/job/job.interface.ts @@ -1,5 +1,5 @@ import { JobRequestType } from '../../common/enums/job'; -import { CreateJob } from './job.dto'; +import { CreateJob, CvatDataDto, Label } from './job.dto'; export interface RequestAction { calculateFundAmount: (dto: CreateJob, rate: number) => Promise; @@ -10,6 +10,18 @@ export interface RequestAction { ) => Promise; } +export interface ManifestAction { + getElementsCount: ( + requestType: JobRequestType, + data: CvatDataDto, + ) => Promise; + calculateJobBounty: ( + elementsCount: number, + fundAmount: number, + nodesTotal?: number, + ) => Promise; +} + export interface EscrowAction { getTrustedHandlers: () => string[]; } diff --git a/packages/apps/job-launcher/server/src/modules/job/job.service.spec.ts b/packages/apps/job-launcher/server/src/modules/job/job.service.spec.ts index f591f96e23..450288da0f 100644 --- a/packages/apps/job-launcher/server/src/modules/job/job.service.spec.ts +++ b/packages/apps/job-launcher/server/src/modules/job/job.service.spec.ts @@ -65,10 +65,11 @@ import { MOCK_TRANSACTION_HASH, MOCK_USER_ID, MOCK_STORAGE_DATA, - MOCK_CVAT_DATA, + MOCK_CVAT_DATA_DATASET, MOCK_CVAT_LABELS, MOCK_CVAT_JOB_SIZE, MOCK_CVAT_VAL_SIZE, + MOCK_CVAT_SKELETONS_JOB_SIZE_MULTIPLIER, MOCK_HCAPTCHA_SITE_KEY, MOCK_HCAPTCHA_IMAGE_LABEL, MOCK_HCAPTCHA_IMAGE_URL, @@ -76,6 +77,9 @@ import { MOCK_HCAPTCHA_RO_URI, MOCK_BUCKET_FILE, MOCK_MAX_RETRY_COUNT, + MOCK_CVAT_LABELS_WITH_NODES, + MOCK_CVAT_DATA_POINTS, + MOCK_CVAT_DATA_BOXES, } from '../../../test/constants'; import { PaymentService } from '../payment/payment.service'; import { Web3Service } from '../web3/web3.service'; @@ -221,6 +225,8 @@ describe('JobService', () => { return MOCK_CVAT_JOB_SIZE; case 'CVAT_VAL_SIZE': return MOCK_CVAT_VAL_SIZE; + case 'CVAT_SKELETONS_JOB_SIZE_MULTIPLIER': + return MOCK_CVAT_SKELETONS_JOB_SIZE_MULTIPLIER; case 'HCAPTCHA_REPUTATION_ORACLE_URI': return MOCK_HCAPTCHA_REPO_URI; case 'HCAPTCHA_RECORDING_ORACLE_URI': @@ -521,14 +527,14 @@ describe('JobService', () => { }); describe('createCvatManifest', () => { - it('should create a valid CVAT manifest', async () => { + it('should create a valid CVAT manifest for image boxes job type', async () => { const jobBounty = '50'; jest - .spyOn(jobService, 'calculateJobBounty') + .spyOn(jobService, 'calculateCvatJobBounty') .mockResolvedValueOnce(jobBounty); const dto: JobCvatDto = { - data: MOCK_CVAT_DATA, + data: MOCK_CVAT_DATA_DATASET, labels: MOCK_CVAT_LABELS, requesterDescription: MOCK_REQUESTER_DESCRIPTION, userGuide: MOCK_FILE_URL, @@ -552,7 +558,7 @@ describe('JobService', () => { data_url: MOCK_BUCKET_FILE, }, annotation: { - labels: [{ name: 'label1' }, { name: 'label2' }], + labels: MOCK_CVAT_LABELS, description: MOCK_REQUESTER_DESCRIPTION, user_guide: MOCK_FILE_URL, type: requestType, @@ -566,6 +572,146 @@ describe('JobService', () => { job_bounty: jobBounty, }); }); + + it('should create a valid CVAT manifest for image boxes from points job type', async () => { + const jobBounty = '25.0'; + + const data = { + annotations: ['string', 'string', 'string', 'string'], + }; + jest.spyOn(storageService, 'download').mockResolvedValueOnce(data); + + const dto: JobCvatDto = { + data: MOCK_CVAT_DATA_POINTS, + labels: MOCK_CVAT_LABELS, + requesterDescription: MOCK_REQUESTER_DESCRIPTION, + userGuide: MOCK_FILE_URL, + minQuality: 0.8, + groundTruth: MOCK_STORAGE_DATA, + type: JobRequestType.IMAGE_BOXES_FROM_POINTS, + fundAmount: 10, + }; + + const requestType = JobRequestType.IMAGE_BOXES_FROM_POINTS; + const tokenFundAmount = 100; + + const result = await jobService.createCvatManifest( + dto, + requestType, + tokenFundAmount, + ); + + expect(result).toEqual({ + data: { + data_url: MOCK_BUCKET_FILE, + points_url: MOCK_BUCKET_FILE, + }, + annotation: { + labels: MOCK_CVAT_LABELS, + description: MOCK_REQUESTER_DESCRIPTION, + user_guide: MOCK_FILE_URL, + type: requestType, + job_size: 1, + }, + validation: { + min_quality: 0.8, + val_size: 2, + gt_url: MOCK_BUCKET_FILE, + }, + job_bounty: jobBounty, + }); + }); + + it('should create a valid CVAT manifest for image skeletons from boxes job type', async () => { + const jobBounty = '4.0'; + + const data = { + annotations: ['string', 'string', 'string', 'string'], + }; + jest.spyOn(storageService, 'download').mockResolvedValueOnce(data); + + const dto: JobCvatDto = { + data: MOCK_CVAT_DATA_BOXES, + labels: MOCK_CVAT_LABELS_WITH_NODES, + requesterDescription: MOCK_REQUESTER_DESCRIPTION, + userGuide: MOCK_FILE_URL, + minQuality: 0.8, + groundTruth: MOCK_STORAGE_DATA, + type: JobRequestType.IMAGE_SKELETONS_FROM_BOXES, + fundAmount: 10, + }; + + const requestType = JobRequestType.IMAGE_SKELETONS_FROM_BOXES; + const tokenFundAmount = 16; + + const result = await jobService.createCvatManifest( + dto, + requestType, + tokenFundAmount, + ); + + expect(result).toEqual({ + data: { + data_url: MOCK_BUCKET_FILE, + boxes_url: MOCK_BUCKET_FILE, + }, + annotation: { + labels: MOCK_CVAT_LABELS_WITH_NODES, + description: MOCK_REQUESTER_DESCRIPTION, + user_guide: MOCK_FILE_URL, + type: requestType, + job_size: 1, + }, + validation: { + min_quality: 0.8, + val_size: 2, + gt_url: MOCK_BUCKET_FILE, + }, + job_bounty: jobBounty, + }); + }); + + it('should throw an error data not exist for image boxes from points job type', async () => { + const requestType = JobRequestType.IMAGE_BOXES_FROM_POINTS; + + const dto: JobCvatDto = { + data: MOCK_CVAT_DATA_DATASET, // Data without points + labels: MOCK_CVAT_LABELS, + requesterDescription: MOCK_REQUESTER_DESCRIPTION, + userGuide: MOCK_FILE_URL, + minQuality: 0.8, + groundTruth: MOCK_STORAGE_DATA, + type: requestType, + fundAmount: 10, + }; + + const tokenFundAmount = 100; + + expect( + jobService.createCvatManifest(dto, requestType, tokenFundAmount), + ).rejects.toThrow(new ConflictException(ErrorJob.DataNotExist)); + }); + + it('should throw an error data not exist for image skeletons from boxes job type', async () => { + const requestType = JobRequestType.IMAGE_BOXES_FROM_POINTS; + + const dto: JobCvatDto = { + data: MOCK_CVAT_DATA_DATASET, // Data without points + labels: MOCK_CVAT_LABELS, + requesterDescription: MOCK_REQUESTER_DESCRIPTION, + userGuide: MOCK_FILE_URL, + minQuality: 0.8, + groundTruth: MOCK_STORAGE_DATA, + type: requestType, + fundAmount: 10, + }; + + const tokenFundAmount = 100; + + expect( + jobService.createCvatManifest(dto, requestType, tokenFundAmount), + ).rejects.toThrow(new ConflictException(ErrorJob.DataNotExist)); + }); }); describe('createHCaptchaManifest', () => { @@ -942,10 +1088,13 @@ describe('JobService', () => { }); }); - describe('calculateJobBounty', () => { + describe('calculateCvatJobBounty', () => { it('should calculate the job bounty correctly', async () => { const tokenFundAmount = 0.013997056833333334; - const result = await jobService['calculateJobBounty'](6, tokenFundAmount); + const result = await jobService['calculateCvatJobBounty']( + 6, + tokenFundAmount, + ); expect(result).toEqual('0.002332842805555555'); }); @@ -957,7 +1106,7 @@ describe('JobService', () => { const imageLabelBinaryJobDto: JobCvatDto = { chainId: MOCK_CHAIN_ID, - data: MOCK_CVAT_DATA, + data: MOCK_CVAT_DATA_DATASET, labels: MOCK_CVAT_LABELS, requesterDescription: MOCK_REQUESTER_DESCRIPTION, minQuality: 0.95, diff --git a/packages/apps/job-launcher/server/src/modules/job/job.service.ts b/packages/apps/job-launcher/server/src/modules/job/job.service.ts index acab0bc649..abdac137ed 100644 --- a/packages/apps/job-launcher/server/src/modules/job/job.service.ts +++ b/packages/apps/job-launcher/server/src/modules/job/job.service.ts @@ -73,6 +73,8 @@ import { RestrictedAudience, CreateJob, JobQuickLaunchDto, + CvatDataDto, + StorageDataDto, } from './job.dto'; import { JobEntity } from './job.entity'; import { JobRepository } from './job.repository'; @@ -111,6 +113,7 @@ import { WebhookDataDto } from '../webhook/webhook.dto'; import * as crypto from 'crypto'; import { PaymentEntity } from '../payment/payment.entity'; import { + ManifestAction, EscrowAction, OracleAction, OracleAddresses, @@ -147,9 +150,16 @@ export class JobService { requestType: JobRequestType, tokenFundAmount: number, ): Promise { - const elementsCount = ( - await listObjectsInBucket(dto.data.dataset, requestType) - ).length; + const { getElementsCount, calculateJobBounty } = + this.createManifestActions[requestType]; + + const elementsCount = await getElementsCount(requestType, dto.data); + const jobBounty = await calculateJobBounty( + elementsCount, + tokenFundAmount, + dto.labels[0]?.nodes?.length, + ); + return { data: { data_url: generateBucketUrl(dto.data.dataset, requestType), @@ -172,7 +182,7 @@ export class JobService { val_size: this.cvatConfigService.valSize, gt_url: generateBucketUrl(dto.groundTruth, requestType), }, - job_bounty: await this.calculateJobBounty(elementsCount, tokenFundAmount), + job_bounty: jobBounty, }; } @@ -495,6 +505,76 @@ export class JobService { }, }; + private createManifestActions: Record = { + [JobRequestType.HCAPTCHA]: { + getElementsCount: async () => 0, + calculateJobBounty: async () => ethers.formatEther(0), + }, + [JobRequestType.FORTUNE]: { + getElementsCount: async () => 0, + calculateJobBounty: async () => ethers.formatEther(0), + }, + [JobRequestType.IMAGE_BOXES]: { + getElementsCount: async ( + requestType: JobRequestType, + data: CvatDataDto, + ) => (await listObjectsInBucket(data.dataset, requestType)).length, + calculateJobBounty: async ( + elementsCount: number, + fundAmount: number, + ): Promise => + this.calculateCvatJobBounty(elementsCount, fundAmount), + }, + [JobRequestType.IMAGE_POINTS]: { + getElementsCount: async ( + requestType: JobRequestType, + data: CvatDataDto, + ) => (await listObjectsInBucket(data.dataset, requestType)).length, + calculateJobBounty: async ( + elementsCount: number, + fundAmount: number, + ): Promise => + this.calculateCvatJobBounty(elementsCount, fundAmount), + }, + [JobRequestType.IMAGE_BOXES_FROM_POINTS]: { + getElementsCount: async ( + requestType: JobRequestType, + data: CvatDataDto, + ) => this.getCvatElementsCount(requestType, data.points!), + calculateJobBounty: async ( + elementsCount: number, + fundAmount: number, + ): Promise => + this.calculateCvatJobBounty(elementsCount, fundAmount), + }, + [JobRequestType.IMAGE_SKELETONS_FROM_BOXES]: { + getElementsCount: async ( + requestType: JobRequestType, + data: CvatDataDto, + ) => this.getCvatElementsCount(requestType, data.boxes!), + calculateJobBounty: async ( + elementsCount: number, + fundAmount: number, + nodesTotal: number, + ): Promise => { + const cvatDefaultJobSize = Number(this.cvatConfigService.jobSize); + + const jobSizeMultiplier = Number( + this.cvatConfigService.skeletonsJobSizeMultiplier, + ); + const jobSize = jobSizeMultiplier * cvatDefaultJobSize; + + // For each skeleton node CVAT creates a separate project thus increasing amount of jobs + const totalJobs = + Math.ceil(div(elementsCount, jobSize)) * (nodesTotal || 1); + + return ethers.formatEther( + ethers.parseUnits(fundAmount.toString(), 'ether') / BigInt(totalJobs), + ); + }, + }, + }; + private getOraclesSpecificActions: Record = { [JobRequestType.HCAPTCHA]: { getOracleAddresses: (): OracleAddresses => { @@ -666,13 +746,28 @@ export class JobService { return jobEntity.id; } - public async calculateJobBounty( + public async getCvatElementsCount( + requestType: JobRequestType, + storageData: StorageDataDto, + ): Promise { + if (!storageData) { + throw new ConflictException(ErrorJob.DataNotExist); + } + + return ( + await this.storageService.download( + generateBucketUrl(storageData, requestType), + ) + ).annotations?.length; + } + + public async calculateCvatJobBounty( elementsCount: number, fundAmount: number, ): Promise { - const totalJobs = Math.ceil( - div(elementsCount, this.cvatConfigService.jobSize), - ); + const jobSize = Number(this.cvatConfigService.jobSize); + + const totalJobs = Math.ceil(div(elementsCount, jobSize)); return ethers.formatEther( ethers.parseUnits(fundAmount.toString(), 'ether') / BigInt(totalJobs), diff --git a/packages/apps/job-launcher/server/test/constants.ts b/packages/apps/job-launcher/server/test/constants.ts index b7451961aa..3c25edbcfd 100644 --- a/packages/apps/job-launcher/server/test/constants.ts +++ b/packages/apps/job-launcher/server/test/constants.ts @@ -105,6 +105,7 @@ export const MOCK_HCAPTCHA_ORACLE_ADDRESS = export const MOCK_CVAT_JOB_SIZE = '10'; export const MOCK_CVAT_MAX_TIME = '300'; export const MOCK_CVAT_VAL_SIZE = '2'; +export const MOCK_CVAT_SKELETONS_JOB_SIZE_MULTIPLIER = '6'; export const MOCK_HCAPTCHA_SITE_KEY = '1234'; export const MOCK_HCAPTCHA_IMAGE_URL = 'http://mockedFileUrl.test/bucket/img_1.jpg'; @@ -118,9 +119,20 @@ export const MOCK_STORAGE_DATA: StorageDataDto = { bucketName: 'bucket', path: 'folder/test', }; -export const MOCK_CVAT_DATA: CvatDataDto = { +export const MOCK_CVAT_DATA_DATASET: CvatDataDto = { dataset: MOCK_STORAGE_DATA, }; + +export const MOCK_CVAT_DATA_POINTS: CvatDataDto = { + dataset: MOCK_STORAGE_DATA, + points: MOCK_STORAGE_DATA, +}; + +export const MOCK_CVAT_DATA_BOXES: CvatDataDto = { + dataset: MOCK_STORAGE_DATA, + boxes: MOCK_STORAGE_DATA, +}; + export const MOCK_CVAT_LABELS: Label[] = [ { name: 'label1', @@ -129,5 +141,17 @@ export const MOCK_CVAT_LABELS: Label[] = [ name: 'label2', }, ]; + +export const MOCK_CVAT_LABELS_WITH_NODES: Label[] = [ + { + name: 'label1', + nodes: ['node 1', 'node 2', 'node 3', 'node 4'], + }, + { + name: 'label2', + nodes: ['node 1', 'node 2', 'node 3', 'node 4'], + }, +]; + export const MOCK_BUCKET_FILE = 'https://bucket.s3.eu-central-1.amazonaws.com/folder/test'; From 136fb7602cc162c4c9e5229d14994f9e38a21558 Mon Sep 17 00:00:00 2001 From: Dzeranov Date: Mon, 1 Apr 2024 19:58:18 +0300 Subject: [PATCH 19/66] Fixed wrong if statement (#1795) --- .../apps/job-launcher/server/src/modules/job/job.service.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/apps/job-launcher/server/src/modules/job/job.service.ts b/packages/apps/job-launcher/server/src/modules/job/job.service.ts index abdac137ed..043fa1eaae 100644 --- a/packages/apps/job-launcher/server/src/modules/job/job.service.ts +++ b/packages/apps/job-launcher/server/src/modules/job/job.service.ts @@ -1225,8 +1225,8 @@ export class JobService { public async escrowFailedWebhook(dto: WebhookDataDto): Promise { if ( - dto.eventType !== - (EventType.ESCROW_FAILED || EventType.TASK_CREATION_FAILED) + dto.eventType !== EventType.ESCROW_FAILED && + dto.eventType !== EventType.TASK_CREATION_FAILED ) { this.logger.log(ErrorJob.InvalidEventType, JobService.name); throw new BadRequestException(ErrorJob.InvalidEventType); From dd2bda5ba517d90b47b54d53e0add5d3aa881ed7 Mon Sep 17 00:00:00 2001 From: Dzeranov Date: Mon, 1 Apr 2024 21:17:36 +0300 Subject: [PATCH 20/66] Missing `@IsOptional` decorators added in multiple DTOs (#1797) --- .../job-launcher/server/src/modules/webhook/webhook.dto.ts | 1 + .../apps/reputation-oracle/server/src/modules/user/user.dto.ts | 1 + .../server/src/modules/webhook/webhook.dto.ts | 3 ++- 3 files changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/apps/job-launcher/server/src/modules/webhook/webhook.dto.ts b/packages/apps/job-launcher/server/src/modules/webhook/webhook.dto.ts index d1a6210313..98746f8b19 100644 --- a/packages/apps/job-launcher/server/src/modules/webhook/webhook.dto.ts +++ b/packages/apps/job-launcher/server/src/modules/webhook/webhook.dto.ts @@ -56,6 +56,7 @@ export class WebhookDataDto { public eventType: EventType; @ApiPropertyOptional({ name: 'event_data' }) + @IsOptional() @IsObject() public eventData?: EventData; } diff --git a/packages/apps/reputation-oracle/server/src/modules/user/user.dto.ts b/packages/apps/reputation-oracle/server/src/modules/user/user.dto.ts index b5b1c03472..1a0b950fe7 100644 --- a/packages/apps/reputation-oracle/server/src/modules/user/user.dto.ts +++ b/packages/apps/reputation-oracle/server/src/modules/user/user.dto.ts @@ -46,6 +46,7 @@ export class UserUpdateDto { @ApiPropertyOptional({ enum: UserStatus, }) + @IsOptional() @IsEnum(UserStatus) public status?: UserStatus; } diff --git a/packages/apps/reputation-oracle/server/src/modules/webhook/webhook.dto.ts b/packages/apps/reputation-oracle/server/src/modules/webhook/webhook.dto.ts index 1e16685976..25409b56c3 100644 --- a/packages/apps/reputation-oracle/server/src/modules/webhook/webhook.dto.ts +++ b/packages/apps/reputation-oracle/server/src/modules/webhook/webhook.dto.ts @@ -1,5 +1,5 @@ import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; -import { IsEnum, IsObject, IsString } from 'class-validator'; +import { IsEnum, IsObject, IsOptional, IsString } from 'class-validator'; import { EventType } from '../../common/enums'; import { ChainId } from '@human-protocol/sdk'; @@ -17,6 +17,7 @@ export class WebhookDto { public escrowAddress: string; @ApiPropertyOptional({ name: 'event_data' }) + @IsOptional() @IsObject() public eventData?: any; } From 7ab85dec0b5929feb2748afc054db7a9055ccf4d Mon Sep 17 00:00:00 2001 From: eugenvoronov <104138627+eugenvoronov@users.noreply.github.com> Date: Mon, 1 Apr 2024 21:47:37 +0300 Subject: [PATCH 21/66] [Reputation Oracle] Fixed create incoming webhook method (#1798) * Fixed create incoming webhook method * Tests fix --------- Co-authored-by: Sergey Dzeranov --- .../src/modules/webhook/webhook.service.spec.ts | 6 ++++-- .../server/src/modules/webhook/webhook.service.ts | 15 ++++++++------- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/packages/apps/reputation-oracle/server/src/modules/webhook/webhook.service.spec.ts b/packages/apps/reputation-oracle/server/src/modules/webhook/webhook.service.spec.ts index 4623f3101b..41455e3837 100644 --- a/packages/apps/reputation-oracle/server/src/modules/webhook/webhook.service.spec.ts +++ b/packages/apps/reputation-oracle/server/src/modules/webhook/webhook.service.spec.ts @@ -106,7 +106,7 @@ describe('WebhookService', () => { await webhookService.createIncomingWebhook(validDto); - expect(webhookRepository.create).toHaveBeenCalled(); + expect(webhookRepository.createUnique).toHaveBeenCalled(); expect(webhookEntity.status).toBe(WebhookStatus.PENDING); expect(webhookEntity.retriesCount).toBe(0); expect(webhookEntity.waitUntil).toBeInstanceOf(Date); @@ -131,7 +131,9 @@ describe('WebhookService', () => { eventType: EventType.TASK_COMPLETED, }; - jest.spyOn(webhookRepository as any, 'create').mockResolvedValue(null); + jest + .spyOn(webhookRepository as any, 'createUnique') + .mockResolvedValue(null); await expect( webhookService.createIncomingWebhook(validDto), diff --git a/packages/apps/reputation-oracle/server/src/modules/webhook/webhook.service.ts b/packages/apps/reputation-oracle/server/src/modules/webhook/webhook.service.ts index 4610a8db84..d6507e5b88 100644 --- a/packages/apps/reputation-oracle/server/src/modules/webhook/webhook.service.ts +++ b/packages/apps/reputation-oracle/server/src/modules/webhook/webhook.service.ts @@ -41,13 +41,14 @@ export class WebhookService { throw new BadRequestException(ErrorWebhook.InvalidEventType); } - const webhookEntity = await this.webhookRepository.create({ - chainId: dto.chainId, - escrowAddress: dto.escrowAddress, - status: WebhookStatus.PENDING, - waitUntil: new Date(), - retriesCount: 0, - }); + let webhookEntity = new WebhookIncomingEntity(); + webhookEntity.chainId = dto.chainId; + webhookEntity.escrowAddress = dto.escrowAddress; + webhookEntity.status = WebhookStatus.PENDING; + webhookEntity.waitUntil = new Date(); + webhookEntity.retriesCount = 0; + + webhookEntity = await this.webhookRepository.createUnique(webhookEntity); if (!webhookEntity) { this.logger.log(ErrorWebhook.NotCreated, WebhookService.name); From 6a3df1c1a63040d63e178af861eb3ebf6f109504 Mon Sep 17 00:00:00 2001 From: Dzeranov Date: Tue, 2 Apr 2024 10:45:47 +0300 Subject: [PATCH 22/66] [Reputation Oracle] Added handlers for new CVAT job types (#1801) * Added handler for new CVAT job types * Tests fix --- .../server/src/common/constants/index.ts | 2 ++ .../server/src/common/enums/job.ts | 2 ++ .../src/modules/payout/payout.service.spec.ts | 6 ++++++ .../server/src/modules/payout/payout.service.ts | 16 ++++++++++++++++ .../src/modules/reputation/reputation.service.ts | 14 ++++++++++++++ 5 files changed, 40 insertions(+) diff --git a/packages/apps/reputation-oracle/server/src/common/constants/index.ts b/packages/apps/reputation-oracle/server/src/common/constants/index.ts index 257819997b..a51cede170 100644 --- a/packages/apps/reputation-oracle/server/src/common/constants/index.ts +++ b/packages/apps/reputation-oracle/server/src/common/constants/index.ts @@ -20,6 +20,8 @@ export const CVAT_VALIDATION_META_FILENAME = 'validation_meta.json'; export const CVAT_JOB_TYPES = [ JobRequestType.IMAGE_BOXES, JobRequestType.IMAGE_POINTS, + JobRequestType.IMAGE_BOXES_FROM_POINTS, + JobRequestType.IMAGE_SKELETONS_FROM_BOXES, ]; export const HEADER_SIGNATURE_KEY = 'human-signature'; diff --git a/packages/apps/reputation-oracle/server/src/common/enums/job.ts b/packages/apps/reputation-oracle/server/src/common/enums/job.ts index 78d295aa24..444ac787a9 100644 --- a/packages/apps/reputation-oracle/server/src/common/enums/job.ts +++ b/packages/apps/reputation-oracle/server/src/common/enums/job.ts @@ -1,6 +1,8 @@ export enum JobRequestType { IMAGE_BOXES = 'IMAGE_BOXES', IMAGE_POINTS = 'IMAGE_POINTS', + IMAGE_BOXES_FROM_POINTS = 'IMAGE_BOXES_FROM_POINTS', + IMAGE_SKELETONS_FROM_BOXES = 'IMAGE_SKELETONS_FROM_BOXES', FORTUNE = 'FORTUNE', } diff --git a/packages/apps/reputation-oracle/server/src/modules/payout/payout.service.spec.ts b/packages/apps/reputation-oracle/server/src/modules/payout/payout.service.spec.ts index b66be4671d..a80b4de6d6 100644 --- a/packages/apps/reputation-oracle/server/src/modules/payout/payout.service.spec.ts +++ b/packages/apps/reputation-oracle/server/src/modules/payout/payout.service.spec.ts @@ -97,6 +97,12 @@ describe('PayoutService', () => { [JobRequestType.IMAGE_POINTS]: { calculateResults: jest.fn(), }, + [JobRequestType.IMAGE_BOXES_FROM_POINTS]: { + calculateResults: jest.fn(), + }, + [JobRequestType.IMAGE_SKELETONS_FROM_BOXES]: { + calculateResults: jest.fn(), + }, }; }); diff --git a/packages/apps/reputation-oracle/server/src/modules/payout/payout.service.ts b/packages/apps/reputation-oracle/server/src/modules/payout/payout.service.ts index 601fb293fe..a9f6cb2204 100644 --- a/packages/apps/reputation-oracle/server/src/modules/payout/payout.service.ts +++ b/packages/apps/reputation-oracle/server/src/modules/payout/payout.service.ts @@ -106,6 +106,22 @@ export class PayoutService { ): Promise => this.processCvat(manifest, chainId, escrowAddress), }, + [JobRequestType.IMAGE_BOXES_FROM_POINTS]: { + calculateResults: async ( + manifest: CvatManifestDto, + chainId: ChainId, + escrowAddress: string, + ): Promise => + this.processCvat(manifest, chainId, escrowAddress), + }, + [JobRequestType.IMAGE_SKELETONS_FROM_BOXES]: { + calculateResults: async ( + manifest: CvatManifestDto, + chainId: ChainId, + escrowAddress: string, + ): Promise => + this.processCvat(manifest, chainId, escrowAddress), + }, }; /** diff --git a/packages/apps/reputation-oracle/server/src/modules/reputation/reputation.service.ts b/packages/apps/reputation-oracle/server/src/modules/reputation/reputation.service.ts index 24a65d88cf..fc45b576a6 100644 --- a/packages/apps/reputation-oracle/server/src/modules/reputation/reputation.service.ts +++ b/packages/apps/reputation-oracle/server/src/modules/reputation/reputation.service.ts @@ -132,6 +132,20 @@ export class ReputationService { manifest: CvatManifestDto, ): Promise => this.processCvat(chainId, escrowAddress, manifest), }, + [JobRequestType.IMAGE_BOXES_FROM_POINTS]: { + assessWorkerReputationScores: async ( + chainId: ChainId, + escrowAddress: string, + manifest: CvatManifestDto, + ): Promise => this.processCvat(chainId, escrowAddress, manifest), + }, + [JobRequestType.IMAGE_SKELETONS_FROM_BOXES]: { + assessWorkerReputationScores: async ( + chainId: ChainId, + escrowAddress: string, + manifest: CvatManifestDto, + ): Promise => this.processCvat(chainId, escrowAddress, manifest), + }, }; private async processFortune( From d01ac40df57d0394bbc3a328b8ba45e4d67fb2fe Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 2 Apr 2024 09:51:36 +0200 Subject: [PATCH 23/66] Bump hardhat-dependency-compiler from 1.1.3 to 1.1.4 (#1790) Bumps [hardhat-dependency-compiler](https://github.com/ItsNickBarry/hardhat-dependency-compiler) from 1.1.3 to 1.1.4. - [Commits](https://github.com/ItsNickBarry/hardhat-dependency-compiler/compare/v1.1.3...v1.1.4) --- updated-dependencies: - dependency-name: hardhat-dependency-compiler dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- packages/core/package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/core/package.json b/packages/core/package.json index 822177ae47..7e24aa196f 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -68,7 +68,7 @@ "hardhat": "^2.22.1", "hardhat-abi-exporter": "^2.10.1", "hardhat-contract-sizer": "^2.6.1", - "hardhat-dependency-compiler": "^1.1.3", + "hardhat-dependency-compiler": "^1.1.4", "hardhat-gas-reporter": "^2.0.2", "openpgp": "5.10.2", "prettier-plugin-solidity": "^1.3.1", diff --git a/yarn.lock b/yarn.lock index 634973c17a..36f9bb1549 100644 --- a/yarn.lock +++ b/yarn.lock @@ -14516,10 +14516,10 @@ hardhat-contract-sizer@^2.6.1: cli-table3 "^0.6.0" strip-ansi "^6.0.0" -hardhat-dependency-compiler@^1.1.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/hardhat-dependency-compiler/-/hardhat-dependency-compiler-1.1.3.tgz#1e49e23f68878bd713f860c66648a711bc4a4a79" - integrity sha512-bCDqsOxGST6WkbMvj4lPchYWidNSSBm5CFnkyAex1T11cGmr9otZTGl81W6f9pmrtBXbKCvr3OSuNJ6Q394sAw== +hardhat-dependency-compiler@^1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/hardhat-dependency-compiler/-/hardhat-dependency-compiler-1.1.4.tgz#1e405d749ad55c4f3b0f8c908333a34d89f0f15c" + integrity sha512-bEsNwSU2owIcKTRJS1vz/VTI1mILfSYiI/4Zcwee99XC41uQIcDDSvJOdIveeMyO7E50JLg2lZet7oIxTC9Gyg== hardhat-deploy@^0.11.43: version "0.11.45" From a99e0528d2ada10608a373924a95ceafc894a241 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 2 Apr 2024 09:52:13 +0200 Subject: [PATCH 24/66] Bump @stripe/stripe-js from 2.4.0 to 3.1.0 (#1791) Bumps [@stripe/stripe-js](https://github.com/stripe/stripe-js) from 2.4.0 to 3.1.0. - [Release notes](https://github.com/stripe/stripe-js/releases) - [Commits](https://github.com/stripe/stripe-js/compare/v2.4.0...v3.1.0) --- updated-dependencies: - dependency-name: "@stripe/stripe-js" dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- packages/apps/job-launcher/client/package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/apps/job-launcher/client/package.json b/packages/apps/job-launcher/client/package.json index fd06629621..8c517620ce 100644 --- a/packages/apps/job-launcher/client/package.json +++ b/packages/apps/job-launcher/client/package.json @@ -12,7 +12,7 @@ "@mui/x-date-pickers": "^6.19.7", "@reduxjs/toolkit": "^1.9.5", "@stripe/react-stripe-js": "^2.4.0", - "@stripe/stripe-js": "^2.4.0", + "@stripe/stripe-js": "^3.1.0", "axios": "^1.1.3", "dayjs": "^1.11.9", "ethers": "^5.7.2", diff --git a/yarn.lock b/yarn.lock index 36f9bb1549..6489d84c23 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6083,10 +6083,10 @@ dependencies: prop-types "^15.7.2" -"@stripe/stripe-js@^2.4.0": - version "2.4.0" - resolved "https://registry.yarnpkg.com/@stripe/stripe-js/-/stripe-js-2.4.0.tgz#7a7e5b187b9e9bb43073edd946ec3e9a778e61bd" - integrity sha512-WFkQx1mbs2b5+7looI9IV1BLa3bIApuN3ehp9FP58xGg7KL9hCHDECgW3BwO9l9L+xBPVAD7Yjn1EhGe6EDTeA== +"@stripe/stripe-js@^3.1.0": + version "3.1.0" + resolved "https://registry.yarnpkg.com/@stripe/stripe-js/-/stripe-js-3.1.0.tgz#bb0458c5cb5e9a835c18f8d353e8980cc66b9959" + integrity sha512-7+ciE35i8NZ6l4FiO1qFkBoZ64ul6B2ZhBVyygB+e/2EZa2WLUyjoxrP53SagnUW7+/q25nDyDLzQq5F0ebOEw== "@swc/helpers@^0.5.0": version "0.5.7" From 00658d3a07658320550528b8582759786b909273 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 2 Apr 2024 09:52:46 +0200 Subject: [PATCH 25/66] Bump concurrently from 7.6.0 to 8.2.2 (#1792) Bumps [concurrently](https://github.com/open-cli-tools/concurrently) from 7.6.0 to 8.2.2. - [Release notes](https://github.com/open-cli-tools/concurrently/releases) - [Commits](https://github.com/open-cli-tools/concurrently/compare/v7.6.0...v8.2.2) --- updated-dependencies: - dependency-name: concurrently dependency-type: direct:development update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package.json | 2 +- yarn.lock | 40 ++++++++++++++++++++-------------------- 2 files changed, 21 insertions(+), 21 deletions(-) diff --git a/package.json b/package.json index ca047370d0..cc2c0adb08 100644 --- a/package.json +++ b/package.json @@ -50,7 +50,7 @@ "@typescript-eslint/eslint-plugin": "^5.43.0", "@typescript-eslint/parser": "^5.43.0", "@typescript-eslint/utils": "^6.10.0", - "concurrently": "^7.5.0", + "concurrently": "^8.2.2", "dotenv": "^16.3.2", "eslint": "^8.55.0", "eslint-config-prettier": "^9.1.0", diff --git a/yarn.lock b/yarn.lock index 6489d84c23..3af3fad37e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -10469,20 +10469,20 @@ concordance@^5.0.4: semver "^7.3.2" well-known-symbols "^2.0.0" -concurrently@^7.5.0: - version "7.6.0" - resolved "https://registry.yarnpkg.com/concurrently/-/concurrently-7.6.0.tgz#531a6f5f30cf616f355a4afb8f8fcb2bba65a49a" - integrity sha512-BKtRgvcJGeZ4XttiDiNcFiRlxoAeZOseqUvyYRUp/Vtd+9p1ULmeoSqGsDA+2ivdeDFpqrJvGvmI+StKfKl5hw== +concurrently@^8.2.2: + version "8.2.2" + resolved "https://registry.yarnpkg.com/concurrently/-/concurrently-8.2.2.tgz#353141985c198cfa5e4a3ef90082c336b5851784" + integrity sha512-1dP4gpXFhei8IOtlXRE/T/4H88ElHgTiUzh71YUmtjTEHMSRS2Z/fgOxHSxxusGHogsRfxNq1vyAwxSC+EVyDg== dependencies: - chalk "^4.1.0" - date-fns "^2.29.1" + chalk "^4.1.2" + date-fns "^2.30.0" lodash "^4.17.21" - rxjs "^7.0.0" - shell-quote "^1.7.3" - spawn-command "^0.0.2-1" - supports-color "^8.1.0" + rxjs "^7.8.1" + shell-quote "^1.8.1" + spawn-command "0.0.2" + supports-color "^8.1.1" tree-kill "^1.2.2" - yargs "^17.3.1" + yargs "^17.7.2" config-chain@^1.1.11: version "1.1.13" @@ -11112,7 +11112,7 @@ dataloader@^2.2.2: resolved "https://registry.yarnpkg.com/dataloader/-/dataloader-2.2.2.tgz#216dc509b5abe39d43a9b9d97e6e5e473dfbe3e0" integrity sha512-8YnDaaf7N3k/q5HnTJVuzSyLETjoZjVmHc4AeKAzOvKHEFQKcn64OKBfzHYtE9zGjctNM7V9I0MfnUVLpi7M5g== -date-fns@2.30.0, date-fns@^2.29.1: +date-fns@2.30.0, date-fns@^2.30.0: version "2.30.0" resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.30.0.tgz#f367e644839ff57894ec6ac480de40cae4b0f4d0" integrity sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw== @@ -21531,7 +21531,7 @@ run-parallel@^1.1.9: dependencies: queue-microtask "^1.2.2" -rxjs@7.8.1, rxjs@^7.0.0, rxjs@^7.2.0, rxjs@^7.5.5, rxjs@^7.8.1: +rxjs@7.8.1, rxjs@^7.2.0, rxjs@^7.5.5, rxjs@^7.8.1: version "7.8.1" resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-7.8.1.tgz#6f6f3d99ea8044291efd92e7c7fcf562c4057543" integrity sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg== @@ -21883,7 +21883,7 @@ shebang-regex@^3.0.0: resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== -shell-quote@^1.7.3: +shell-quote@^1.8.1: version "1.8.1" resolved "https://registry.yarnpkg.com/shell-quote/-/shell-quote-1.8.1.tgz#6dbf4db75515ad5bac63b4f1894c3a154c766680" integrity sha512-6j1W9l1iAs/4xYBI1SYOVZyFcCis9b4KCLQ8fgAGG07QvzaRLVVRQvAy85yNmmZSjYjg4MWh4gNvlPujU/5LpA== @@ -22265,10 +22265,10 @@ sparse-array@^1.3.1: resolved "https://registry.yarnpkg.com/sparse-array/-/sparse-array-1.3.2.tgz#0e1a8b71706d356bc916fe754ff496d450ec20b0" integrity sha512-ZT711fePGn3+kQyLuv1fpd3rNSkNF8vd5Kv2D+qnOANeyKs3fx6bUMGWRPvgTTcYV64QMqZKZwcuaQSP3AZ0tg== -spawn-command@^0.0.2-1: - version "0.0.2-1" - resolved "https://registry.yarnpkg.com/spawn-command/-/spawn-command-0.0.2-1.tgz#62f5e9466981c1b796dc5929937e11c9c6921bd0" - integrity sha512-n98l9E2RMSJ9ON1AKisHzz7V42VDiBQGY6PB1BwRglz99wpVsSuGzQ+jOi6lFXBGVTCrRpltvjm+/XA+tpeJrg== +spawn-command@0.0.2: + version "0.0.2" + resolved "https://registry.yarnpkg.com/spawn-command/-/spawn-command-0.0.2.tgz#9544e1a43ca045f8531aac1a48cb29bdae62338e" + integrity sha512-zC8zGoGkmc8J9ndvml8Xksr1Amk9qBujgbF0JAIWO7kXr43w0h/0GJNM/Vustixu+YE8N/MTrQ7N31FvHUACxQ== spdx-correct@^3.0.0: version "3.2.0" @@ -22736,7 +22736,7 @@ supertest@^6.1.3, supertest@^6.3.4: methods "^1.1.2" superagent "^8.1.2" -supports-color@8.1.1, supports-color@^8.0.0, supports-color@^8.1.0, supports-color@^8.1.1, supports-color@~8.1.1: +supports-color@8.1.1, supports-color@^8.0.0, supports-color@^8.1.1, supports-color@~8.1.1: version "8.1.1" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-8.1.1.tgz#cd6fc17e28500cff56c1b86c0a7fd4a54a73005c" integrity sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q== @@ -25283,7 +25283,7 @@ yargs@^15.3.1: y18n "^4.0.0" yargs-parser "^18.1.2" -yargs@^17.3.1, yargs@^17.6.2: +yargs@^17.3.1, yargs@^17.6.2, yargs@^17.7.2: version "17.7.2" resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.7.2.tgz#991df39aca675a192b816e1e0363f9d75d2aa269" integrity sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w== From f4a93bf3cdaa19fe8462c117ded704af4fe9cbf0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 2 Apr 2024 09:53:12 +0200 Subject: [PATCH 26/66] Bump @babel/preset-env from 7.24.1 to 7.24.3 (#1793) Bumps [@babel/preset-env](https://github.com/babel/babel/tree/HEAD/packages/babel-preset-env) from 7.24.1 to 7.24.3. - [Release notes](https://github.com/babel/babel/releases) - [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md) - [Commits](https://github.com/babel/babel/commits/v7.24.3/packages/babel-preset-env) --- updated-dependencies: - dependency-name: "@babel/preset-env" dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package.json | 2 +- yarn.lock | 32 ++++++++++++++++---------------- 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/package.json b/package.json index cc2c0adb08..e65f2861c5 100644 --- a/package.json +++ b/package.json @@ -41,7 +41,7 @@ "devDependencies": { "@apollo/client": "^3.7.12", "@babel/core": "^7.23.5", - "@babel/preset-env": "^7.23.9", + "@babel/preset-env": "^7.24.3", "@babel/preset-react": "^7.18.6", "@babel/preset-typescript": "^7.18.6", "@jest/globals": "^29.3.1", diff --git a/yarn.lock b/yarn.lock index 3af3fad37e..cd6864dd34 100644 --- a/yarn.lock +++ b/yarn.lock @@ -649,10 +649,10 @@ dependencies: "@babel/helper-plugin-utils" "^7.24.0" -"@babel/plugin-transform-async-generator-functions@^7.24.1": - version "7.24.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.24.1.tgz#b38009d650b3c419e6708ec5ab4fa5eeffe7b489" - integrity sha512-OTkLJM0OtmzcpOgF7MREERUCdCnCBtBsq3vVFbuq/RKMK0/jdYqdMexWi3zNs7Nzd95ase65MbTGrpFJflOb6A== +"@babel/plugin-transform-async-generator-functions@^7.24.3": + version "7.24.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.24.3.tgz#8fa7ae481b100768cc9842c8617808c5352b8b89" + integrity sha512-Qe26CMYVjpQxJ8zxM1340JFNjZaF+ISWpr1Kt/jGo+ZTUzKkfw/pphEWbRCb+lmSM6k/TOgfYLvmbHkUQ0asIg== dependencies: "@babel/helper-environment-visitor" "^7.22.20" "@babel/helper-plugin-utils" "^7.24.0" @@ -1106,10 +1106,10 @@ "@babel/helper-create-regexp-features-plugin" "^7.22.15" "@babel/helper-plugin-utils" "^7.24.0" -"@babel/preset-env@^7.16.4", "@babel/preset-env@^7.23.9": - version "7.24.1" - resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.24.1.tgz#e63a3f95d9922c07f4a53649b5c2f53f611f2e6c" - integrity sha512-CwCMz1Z28UHLI2iE+cbnWT2epPMV9bzzoBGM6A3mOS22VQd/1TPoWItV7S7iL9TkPmPEf5L/QzurmztyyDN9FA== +"@babel/preset-env@^7.16.4", "@babel/preset-env@^7.24.3": + version "7.24.3" + resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.24.3.tgz#f3f138c844ffeeac372597b29c51b5259e8323a3" + integrity sha512-fSk430k5c2ff8536JcPvPWK4tZDwehWLGlBp0wrsBUjZVdeQV6lePbwKWZaZfK2vnh/1kQX1PzAJWsnBmVgGJA== dependencies: "@babel/compat-data" "^7.24.1" "@babel/helper-compilation-targets" "^7.23.6" @@ -1138,7 +1138,7 @@ "@babel/plugin-syntax-top-level-await" "^7.14.5" "@babel/plugin-syntax-unicode-sets-regex" "^7.18.6" "@babel/plugin-transform-arrow-functions" "^7.24.1" - "@babel/plugin-transform-async-generator-functions" "^7.24.1" + "@babel/plugin-transform-async-generator-functions" "^7.24.3" "@babel/plugin-transform-async-to-generator" "^7.24.1" "@babel/plugin-transform-block-scoped-functions" "^7.24.1" "@babel/plugin-transform-block-scoping" "^7.24.1" @@ -1187,7 +1187,7 @@ "@babel/plugin-transform-unicode-sets-regex" "^7.24.1" "@babel/preset-modules" "0.1.6-no-external-plugins" babel-plugin-polyfill-corejs2 "^0.4.10" - babel-plugin-polyfill-corejs3 "^0.10.1" + babel-plugin-polyfill-corejs3 "^0.10.4" babel-plugin-polyfill-regenerator "^0.6.1" core-js-compat "^3.31.0" semver "^6.3.1" @@ -8901,13 +8901,13 @@ babel-plugin-polyfill-corejs2@^0.4.10: "@babel/helper-define-polyfill-provider" "^0.6.1" semver "^6.3.1" -babel-plugin-polyfill-corejs3@^0.10.1: - version "0.10.1" - resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.10.1.tgz#cd8750e0b7da30ec2f66007b6151792f02e1138e" - integrity sha512-XiFei6VGwM4ii6nKC1VCenGD8Z4bjiNYcrdkM8oqM3pbuemmyb8biMgrDX1ZHSbIuMLXatM6JJ/StPYIuTl6MQ== +babel-plugin-polyfill-corejs3@^0.10.1, babel-plugin-polyfill-corejs3@^0.10.4: + version "0.10.4" + resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.10.4.tgz#789ac82405ad664c20476d0233b485281deb9c77" + integrity sha512-25J6I8NGfa5YkCDogHRID3fVCadIR8/pGl1/spvCkzb6lVn6SR3ojpx9nOn9iEBcUsjY24AmdKm5khcfKdylcg== dependencies: "@babel/helper-define-polyfill-provider" "^0.6.1" - core-js-compat "^3.36.0" + core-js-compat "^3.36.1" babel-plugin-polyfill-regenerator@^0.6.1: version "0.6.1" @@ -10660,7 +10660,7 @@ copyfiles@2.4.1: untildify "^4.0.0" yargs "^16.1.0" -core-js-compat@^3.31.0, core-js-compat@^3.36.0: +core-js-compat@^3.31.0, core-js-compat@^3.36.1: version "3.36.1" resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.36.1.tgz#1818695d72c99c25d621dca94e6883e190cea3c8" integrity sha512-Dk997v9ZCt3X/npqzyGdTlq6t7lDBhZwGvV94PKzDArjp7BTRm7WlDAXYd/OWdeFHO8OChQYRJNJvUCqCbrtKA== From cb93b2981c34f972a8bba8560d0defb3a7572c65 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Francisco=20L=C3=B3pez?= <50665615+flopez7@users.noreply.github.com> Date: Tue, 2 Apr 2024 10:59:15 +0200 Subject: [PATCH 27/66] Fortune local environment (#1802) * add reputation oracle, make sengrid optional and use env for setup scripts * Add setup for fortune oracles * Fix some bugs to run localhost fortune * remove duplicated code * Remove yarn start:watch and fix encryption conditionals --------- Co-authored-by: portuu3 --- .../exchange-oracle/server/package.json | 2 +- .../exchange-oracle/server/test/setup.ts | 13 ++-- .../fortune/recording-oracle/package.json | 2 +- .../src/common/constants/networks.ts | 2 +- .../src/common/enums/webhook.ts | 2 +- .../src/common/utils/webhook.ts | 6 +- .../src/modules/job/job.service.spec.ts | 7 +- .../src/modules/job/job.service.ts | 16 ++--- .../src/modules/storage/storage.service.ts | 2 +- .../webhook/webhook.controller.spec.ts | 4 +- .../src/modules/webhook/webhook.dto.ts | 9 ++- .../modules/webhook/webhook.service.spec.ts | 4 +- .../fortune/recording-oracle/test/setup.ts | 13 ++-- .../server/src/common/constants/index.ts | 1 + .../src/modules/sendgrid/sendgrid.service.ts | 15 ++++- .../apps/job-launcher/server/test/setup.ts | 1 - .../reputation-oracle/server/package.json | 11 ++-- .../server/src/common/config/networks.ts | 6 ++ .../server/src/common/constants/errors.ts | 3 +- .../server/src/common/constants/index.ts | 1 + .../server/src/common/enums/web3.ts | 1 + .../src/modules/cron-job/cron-job.service.ts | 1 - .../src/modules/sendgrid/sendgrid.service.ts | 29 ++++++--- .../modules/storage/storage.service.spec.ts | 10 +-- .../src/modules/storage/storage.service.ts | 2 +- .../src/modules/web3/web3.service.spec.ts | 17 ++--- .../server/src/modules/web3/web3.service.ts | 40 +++++++----- .../reputation-oracle/server/test/setup.ts | 43 ++++++++++-- scripts/Makefile | 17 +++-- scripts/fortune/.env.exco-server | 1 + scripts/fortune/.env.jl-server | 4 +- scripts/fortune/.env.rep-oracle | 65 +++++++++++++++++++ 32 files changed, 253 insertions(+), 97 deletions(-) create mode 100644 scripts/fortune/.env.rep-oracle diff --git a/packages/apps/fortune/exchange-oracle/server/package.json b/packages/apps/fortune/exchange-oracle/server/package.json index c12b0facee..decbb0354b 100644 --- a/packages/apps/fortune/exchange-oracle/server/package.json +++ b/packages/apps/fortune/exchange-oracle/server/package.json @@ -8,7 +8,7 @@ "build": "nest build", "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"", "start": "nest start", - "start:dev": "NODE_ENV=development nest start --watch", + "start:dev": "nest start --watch", "start:debug": "nest start --debug --watch", "start:prod": "node dist/src/main", "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix", diff --git a/packages/apps/fortune/exchange-oracle/server/test/setup.ts b/packages/apps/fortune/exchange-oracle/server/test/setup.ts index 66d80d0557..9f2c592e2e 100644 --- a/packages/apps/fortune/exchange-oracle/server/test/setup.ts +++ b/packages/apps/fortune/exchange-oracle/server/test/setup.ts @@ -1,11 +1,14 @@ import { KVStoreClient, KVStoreKeys, Role } from '@human-protocol/sdk'; import { Wallet, ethers } from 'ethers'; +import * as dotenv from 'dotenv'; +dotenv.config({ path: '.env.local' }); export async function setup(): Promise { - //This private key is generated by hardhat and should be used just for local testing - const privateKey = - '0x5de4111afa1a4b94908f83103eb1f1706367c2e68ca870fc3fb9a804cdab365a'; //0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC - const provider = new ethers.JsonRpcProvider('http://localhost:8545'); + const privateKey = process.env.WEB3_PRIVATE_KEY; + if (!privateKey) { + throw new Error('Private key is empty'); + } + const provider = new ethers.JsonRpcProvider('http://0.0.0.0:8545'); const wallet = new Wallet(privateKey, provider); const kvStoreClient = await KVStoreClient.build(wallet); @@ -14,3 +17,5 @@ export async function setup(): Promise { [Role.ExchangeOracle, '1', 'http://localhost:5001/webhook'], ); } + +setup(); diff --git a/packages/apps/fortune/recording-oracle/package.json b/packages/apps/fortune/recording-oracle/package.json index 8f429b3cba..376a4adf73 100644 --- a/packages/apps/fortune/recording-oracle/package.json +++ b/packages/apps/fortune/recording-oracle/package.json @@ -8,7 +8,7 @@ "build": "nest build", "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"", "start": "nest start", - "start:dev": "NODE_ENV=${NODE_ENV:=development} nest start --watch", + "start:dev": "nest start --watch", "start:debug": "nest start --debug --watch", "start:prod": "node dist/main", "lint": "eslint .", diff --git a/packages/apps/fortune/recording-oracle/src/common/constants/networks.ts b/packages/apps/fortune/recording-oracle/src/common/constants/networks.ts index b17669659a..d8e35c5bb4 100644 --- a/packages/apps/fortune/recording-oracle/src/common/constants/networks.ts +++ b/packages/apps/fortune/recording-oracle/src/common/constants/networks.ts @@ -30,7 +30,7 @@ export const networkMap: NetworkMapDto = { }, localhost: { chainId: ChainId.LOCALHOST, - rpcUrl: 'http://localhost:8545', + rpcUrl: 'http://0.0.0.0:8545/', }, }; diff --git a/packages/apps/fortune/recording-oracle/src/common/enums/webhook.ts b/packages/apps/fortune/recording-oracle/src/common/enums/webhook.ts index 21c8d59661..ee68ccb08f 100644 --- a/packages/apps/fortune/recording-oracle/src/common/enums/webhook.ts +++ b/packages/apps/fortune/recording-oracle/src/common/enums/webhook.ts @@ -1,6 +1,6 @@ export enum EventType { ESCROW_COMPLETED = 'escrow_completed', - ESCROW_RECORDED = 'escrow_recorded', + TASK_COMPLETED = 'task_completed', SUBMISSION_REJECTED = 'submission_rejected', SUBMISSION_IN_REVIEW = 'submission_in_review', } diff --git a/packages/apps/fortune/recording-oracle/src/common/utils/webhook.ts b/packages/apps/fortune/recording-oracle/src/common/utils/webhook.ts index a3ce716c4e..49172fb718 100644 --- a/packages/apps/fortune/recording-oracle/src/common/utils/webhook.ts +++ b/packages/apps/fortune/recording-oracle/src/common/utils/webhook.ts @@ -1,4 +1,4 @@ -import { Logger, NotFoundException } from '@nestjs/common'; +import { HttpStatus, Logger, NotFoundException } from '@nestjs/common'; import { firstValueFrom } from 'rxjs'; import { HttpService } from '@nestjs/axios'; import { ErrorJob } from '../constants/errors'; @@ -16,13 +16,13 @@ export async function sendWebhook( ): Promise { const snake_case_body = CaseConverter.transformToSnakeCase(webhookBody); const signedBody = await signMessage(snake_case_body, privateKey); - const { data } = await firstValueFrom( + const { status } = await firstValueFrom( await httpService.post(webhookUrl, snake_case_body, { headers: { [HEADER_SIGNATURE_KEY]: signedBody }, }), ); - if (!data) { + if (status !== HttpStatus.CREATED) { logger.log(ErrorJob.WebhookWasNotSent, 'JobService'); throw new NotFoundException(ErrorJob.WebhookWasNotSent); } diff --git a/packages/apps/fortune/recording-oracle/src/modules/job/job.service.spec.ts b/packages/apps/fortune/recording-oracle/src/modules/job/job.service.spec.ts index 7928cbabd6..73e9f064d7 100644 --- a/packages/apps/fortune/recording-oracle/src/modules/job/job.service.spec.ts +++ b/packages/apps/fortune/recording-oracle/src/modules/job/job.service.spec.ts @@ -89,7 +89,7 @@ describe('JobService', () => { const httpServicePostMock = jest .fn() - .mockReturnValue(of({ status: 200, data: {} })); + .mockReturnValue(of({ status: 201, data: {} })); beforeEach(async () => { const moduleRef = await Test.createTestingModule({ @@ -113,6 +113,7 @@ describe('JobService', () => { registerAs('server', () => ({ encryptionPrivateKey: MOCK_ENCRYPTION_PRIVATE_KEY, encryptionPassphrase: MOCK_ENCRYPTION_PASSPHRASE, + pgpEncrypt: 'false', })), ), ], @@ -491,7 +492,7 @@ describe('JobService', () => { const expectedBody = { chain_id: jobSolution.chainId, escrow_address: jobSolution.escrowAddress, - event_type: EventType.ESCROW_RECORDED, + event_type: EventType.TASK_COMPLETED, }; expect(result).toEqual('The requested job is completed.'); expect(httpServicePostMock).toHaveBeenCalledWith( @@ -583,7 +584,7 @@ describe('JobService', () => { const expectedBody = { chain_id: jobSolution.chainId, escrow_address: jobSolution.escrowAddress, - event_type: EventType.ESCROW_RECORDED, + event_type: EventType.TASK_COMPLETED, }; expect(result).toEqual('The requested job is completed.'); expect(httpServicePostMock).toHaveBeenCalledWith( diff --git a/packages/apps/fortune/recording-oracle/src/modules/job/job.service.ts b/packages/apps/fortune/recording-oracle/src/modules/job/job.service.ts index 53fc526407..7db1c23825 100644 --- a/packages/apps/fortune/recording-oracle/src/modules/job/job.service.ts +++ b/packages/apps/fortune/recording-oracle/src/modules/job/job.service.ts @@ -181,17 +181,17 @@ export class JobService { jobSolutionUploaded.hash, ); - const reputationOracleAddress = - await escrowClient.getReputationOracleAddress(webhook.escrowAddress); - const reputationOracleWebhook = (await kvstoreClient.get( - reputationOracleAddress, - KVStoreKeys.webhookUrl, - )) as string; - if ( recordingOracleSolutions.filter((solution) => !solution.error).length >= submissionsRequired ) { + const reputationOracleAddress = + await escrowClient.getReputationOracleAddress(webhook.escrowAddress); + const reputationOracleWebhook = (await kvstoreClient.get( + reputationOracleAddress, + KVStoreKeys.webhookUrl, + )) as string; + await sendWebhook( this.httpService, this.logger, @@ -199,7 +199,7 @@ export class JobService { { chainId: webhook.chainId, escrowAddress: webhook.escrowAddress, - eventType: EventType.ESCROW_RECORDED, + eventType: EventType.TASK_COMPLETED, }, this.web3Config.web3PrivateKey, ); diff --git a/packages/apps/fortune/recording-oracle/src/modules/storage/storage.service.ts b/packages/apps/fortune/recording-oracle/src/modules/storage/storage.service.ts index d412f989f9..84feeca176 100644 --- a/packages/apps/fortune/recording-oracle/src/modules/storage/storage.service.ts +++ b/packages/apps/fortune/recording-oracle/src/modules/storage/storage.service.ts @@ -87,7 +87,7 @@ export class StorageService { } let fileToUpload = JSON.stringify(solutions); - if (this.serverConfig.pgpEncrypt as boolean) { + if (this.serverConfig.pgpEncrypt === 'true') { try { const signer = this.web3Service.getSigner(chainId); const escrowClient = await EscrowClient.build(signer); diff --git a/packages/apps/fortune/recording-oracle/src/modules/webhook/webhook.controller.spec.ts b/packages/apps/fortune/recording-oracle/src/modules/webhook/webhook.controller.spec.ts index 72bca40f62..a7b981edde 100644 --- a/packages/apps/fortune/recording-oracle/src/modules/webhook/webhook.controller.spec.ts +++ b/packages/apps/fortune/recording-oracle/src/modules/webhook/webhook.controller.spec.ts @@ -149,7 +149,7 @@ describe('webhookController', () => { const webhook: WebhookDto = { chainId, escrowAddress, - eventType: EventType.ESCROW_RECORDED, + eventType: EventType.TASK_COMPLETED, }; jest.spyOn(webhookService, 'handleWebhook'); @@ -157,7 +157,7 @@ describe('webhookController', () => { await expect( webhookController.processWebhook(MOCK_SIGNATURE, webhook), - ).rejects.toThrow('Invalid webhook event type: escrow_recorded'); + ).rejects.toThrow('Invalid webhook event type: task_completed'); expect(webhookService.handleWebhook).toHaveBeenCalledWith(webhook); }); diff --git a/packages/apps/fortune/recording-oracle/src/modules/webhook/webhook.dto.ts b/packages/apps/fortune/recording-oracle/src/modules/webhook/webhook.dto.ts index 2fb51e9b42..8519f20060 100644 --- a/packages/apps/fortune/recording-oracle/src/modules/webhook/webhook.dto.ts +++ b/packages/apps/fortune/recording-oracle/src/modules/webhook/webhook.dto.ts @@ -1,6 +1,12 @@ import { ChainId } from '@human-protocol/sdk'; import { ApiProperty } from '@nestjs/swagger'; -import { IsArray, IsEnum, IsObject, IsString } from 'class-validator'; +import { + IsArray, + IsEnum, + IsObject, + IsOptional, + IsString, +} from 'class-validator'; import { IsValidEthereumAddress } from '../../common/validators'; import { EventType } from '../../common/enums/webhook'; @@ -54,5 +60,6 @@ export class WebhookDto { name: 'event_data', }) @IsObject() + @IsOptional() public eventData?: EventData; } diff --git a/packages/apps/fortune/recording-oracle/src/modules/webhook/webhook.service.spec.ts b/packages/apps/fortune/recording-oracle/src/modules/webhook/webhook.service.spec.ts index 3671e7dfe5..9c92ddd384 100644 --- a/packages/apps/fortune/recording-oracle/src/modules/webhook/webhook.service.spec.ts +++ b/packages/apps/fortune/recording-oracle/src/modules/webhook/webhook.service.spec.ts @@ -130,11 +130,11 @@ describe('WebhookService', () => { const webhook: WebhookDto = { chainId, escrowAddress, - eventType: EventType.ESCROW_RECORDED, + eventType: EventType.TASK_COMPLETED, }; await expect(webhookService.handleWebhook(webhook)).rejects.toThrow( - 'Invalid webhook event type: escrow_recorded', + 'Invalid webhook event type: task_completed', ); }); }); diff --git a/packages/apps/fortune/recording-oracle/test/setup.ts b/packages/apps/fortune/recording-oracle/test/setup.ts index b6582854bb..cdba2d9bb2 100644 --- a/packages/apps/fortune/recording-oracle/test/setup.ts +++ b/packages/apps/fortune/recording-oracle/test/setup.ts @@ -1,11 +1,14 @@ import { KVStoreClient, KVStoreKeys, Role } from '@human-protocol/sdk'; import { Wallet, ethers } from 'ethers'; +import * as dotenv from 'dotenv'; +dotenv.config({ path: '.env.local' }); export async function setup(): Promise { - //This private key is generated by hardhat and should be used just for local testing - const privateKey = - '0x7c852118294e51e653712a81e05800f419141751be58f605c371e15141b007a6'; //0x90F79bf6EB2c4f870365E785982E1f101E93b906 - const provider = new ethers.JsonRpcProvider('http://localhost:8545'); + const privateKey = process.env.WEB3_PRIVATE_KEY; + if (!privateKey) { + throw new Error('Private key is empty'); + } + const provider = new ethers.JsonRpcProvider('http://0.0.0.0:8545'); const wallet = new Wallet(privateKey, provider); const kvStoreClient = await KVStoreClient.build(wallet); @@ -14,3 +17,5 @@ export async function setup(): Promise { [Role.RecordingOracle, '1', 'http://localhost:5002/webhook'], ); } + +setup(); diff --git a/packages/apps/job-launcher/server/src/common/constants/index.ts b/packages/apps/job-launcher/server/src/common/constants/index.ts index d99c2036ac..cdf79b35a2 100644 --- a/packages/apps/job-launcher/server/src/common/constants/index.ts +++ b/packages/apps/job-launcher/server/src/common/constants/index.ts @@ -25,6 +25,7 @@ export const MAINNET_CHAIN_IDS = [ export const SENDGRID_API_KEY_REGEX = /^SG\.[A-Za-z0-9-_]{22}\.[A-Za-z0-9-_]{43}$/; +export const SENDGRID_API_KEY_DISABLED = 'sendgrid-disabled'; export const HEADER_SIGNATURE_KEY = 'human-signature'; diff --git a/packages/apps/job-launcher/server/src/modules/sendgrid/sendgrid.service.ts b/packages/apps/job-launcher/server/src/modules/sendgrid/sendgrid.service.ts index 46ee073b8f..161a3c71a9 100644 --- a/packages/apps/job-launcher/server/src/modules/sendgrid/sendgrid.service.ts +++ b/packages/apps/job-launcher/server/src/modules/sendgrid/sendgrid.service.ts @@ -1,6 +1,9 @@ import { BadRequestException, Injectable, Logger } from '@nestjs/common'; import { MailDataRequired, MailService } from '@sendgrid/mail'; -import { SENDGRID_API_KEY_REGEX } from '../../common/constants'; +import { + SENDGRID_API_KEY_DISABLED, + SENDGRID_API_KEY_REGEX, +} from '../../common/constants'; import { ErrorSendGrid } from '../../common/constants/errors'; import { SendgridConfigService } from '../../common/config/sendgrid-config.service'; @@ -10,6 +13,7 @@ export class SendGridService { private readonly defaultFromEmail: string; private readonly defaultFromName: string; + private readonly apiKey: string; constructor( private readonly mailService: MailService, @@ -17,6 +21,10 @@ export class SendGridService { ) { const apiKey = this.sendgridConfigService.apiKey; + if (this.apiKey === SENDGRID_API_KEY_DISABLED) { + return; + } + if (!SENDGRID_API_KEY_REGEX.test(apiKey)) { throw new Error(ErrorSendGrid.InvalidApiKey); } @@ -37,6 +45,11 @@ export class SendGridService { ...emailData }: Partial): Promise { try { + if (this.apiKey === SENDGRID_API_KEY_DISABLED) { + this.logger.debug(personalizations); + return; + } + await this.mailService.send({ from, templateId, diff --git a/packages/apps/job-launcher/server/test/setup.ts b/packages/apps/job-launcher/server/test/setup.ts index 782692d86d..56cba97184 100644 --- a/packages/apps/job-launcher/server/test/setup.ts +++ b/packages/apps/job-launcher/server/test/setup.ts @@ -10,7 +10,6 @@ import * as dotenv from 'dotenv'; dotenv.config({ path: '.env.local' }); export async function setup(): Promise { - //This private key is generated by hardhat and should be used just for local testing const privateKey = process.env.WEB3_PRIVATE_KEY; if (!privateKey) { throw new Error('Private key is empty'); diff --git a/packages/apps/reputation-oracle/server/package.json b/packages/apps/reputation-oracle/server/package.json index 498e78682f..c67d641bb1 100644 --- a/packages/apps/reputation-oracle/server/package.json +++ b/packages/apps/reputation-oracle/server/package.json @@ -12,13 +12,14 @@ "start:dev": "nest start --watch", "start:debug": "nest start --debug --watch", "start:prod": "node dist/main", - "migration:create": "typeorm-ts-node-commonjs migration:create", - "migration:generate": "typeorm-ts-node-commonjs migration:generate -d typeorm.config.ts", - "migration:revert": "typeorm-ts-node-commonjs migration:revert -d typeorm.config.ts", - "migration:run": "typeorm-ts-node-commonjs migration:run -d typeorm.config.ts", - "migration:show": "typeorm-ts-node-commonjs migration:show -d typeorm.config.ts", + "migration:create": "yarn build && typeorm-ts-node-commonjs migration:create", + "migration:generate": "yarn build && typeorm-ts-node-commonjs migration:generate -d typeorm.config.ts", + "migration:revert": "yarn build && typeorm-ts-node-commonjs migration:revert -d typeorm.config.ts", + "migration:run": "yarn build && typeorm-ts-node-commonjs migration:run -d typeorm.config.ts", + "migration:show": "yarn build && typeorm-ts-node-commonjs migration:show -d typeorm.config.ts", "docker:db:up": "docker compose up -d && yarn migration:run", "docker:db:down": "docker compose down", + "setup:local": "ts-node ./test/setup.ts", "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix", "test": "jest", "test:watch": "jest --watch", diff --git a/packages/apps/reputation-oracle/server/src/common/config/networks.ts b/packages/apps/reputation-oracle/server/src/common/config/networks.ts index 5564be20eb..8d8b8124e1 100644 --- a/packages/apps/reputation-oracle/server/src/common/config/networks.ts +++ b/packages/apps/reputation-oracle/server/src/common/config/networks.ts @@ -36,6 +36,10 @@ export const networkMap: NetworkMapDto = { chainId: ChainId.BSC_TESTNET, rpcUrl: 'https://data-seed-prebsc-1-s1.binance.org:8545/', }, + localhost: { + chainId: ChainId.LOCALHOST, + rpcUrl: 'http://0.0.0.0:8545', + }, }; export const networks = Object.values(networkMap).map((network) => network); @@ -51,3 +55,5 @@ export const MAINNET_CHAIN_IDS = [ ChainId.POLYGON, ChainId.MOONBEAM, ]; + +export const LOCALHOST_CHAIN_IDS = [ChainId.LOCALHOST]; diff --git a/packages/apps/reputation-oracle/server/src/common/constants/errors.ts b/packages/apps/reputation-oracle/server/src/common/constants/errors.ts index 58cd6b17f9..ac9ba2f9b8 100644 --- a/packages/apps/reputation-oracle/server/src/common/constants/errors.ts +++ b/packages/apps/reputation-oracle/server/src/common/constants/errors.ts @@ -109,8 +109,7 @@ export enum ErrorCronJob { * Represents error messages related to web3. */ export enum ErrorWeb3 { - InvalidTestnetChainId = 'Invalid chain id provided for the testnet environment', - InvalidMainnetChainId = 'Invalid chain id provided for the mainnet environment', + InvalidChainId = 'Invalid chain id provided for the configured environment', GasPriceError = 'Error calculating gas price', } diff --git a/packages/apps/reputation-oracle/server/src/common/constants/index.ts b/packages/apps/reputation-oracle/server/src/common/constants/index.ts index a51cede170..f68da3b9b8 100644 --- a/packages/apps/reputation-oracle/server/src/common/constants/index.ts +++ b/packages/apps/reputation-oracle/server/src/common/constants/index.ts @@ -7,6 +7,7 @@ export const INITIAL_REPUTATION = 0; export const JWT_PREFIX = 'bearer '; export const SENDGRID_API_KEY_REGEX = /^SG\.[A-Za-z0-9-_]{22}\.[A-Za-z0-9-_]{43}$/; +export const SENDGRID_API_KEY_DISABLED = 'sendgrid-disabled'; export const SENDGRID_TEMPLATES = { signup: 'd-ca99cc7410aa4e6dab3e6042d5ecb9a3', diff --git a/packages/apps/reputation-oracle/server/src/common/enums/web3.ts b/packages/apps/reputation-oracle/server/src/common/enums/web3.ts index 17fc5129fd..2d06ef924a 100644 --- a/packages/apps/reputation-oracle/server/src/common/enums/web3.ts +++ b/packages/apps/reputation-oracle/server/src/common/enums/web3.ts @@ -1,6 +1,7 @@ export enum Web3Env { TESTNET = 'testnet', MAINNET = 'mainnet', + LOCALHOST = 'localhost', } export enum SignatureType { diff --git a/packages/apps/reputation-oracle/server/src/modules/cron-job/cron-job.service.ts b/packages/apps/reputation-oracle/server/src/modules/cron-job/cron-job.service.ts index cea2ffe557..6421f62196 100644 --- a/packages/apps/reputation-oracle/server/src/modules/cron-job/cron-job.service.ts +++ b/packages/apps/reputation-oracle/server/src/modules/cron-job/cron-job.service.ts @@ -10,7 +10,6 @@ import { ErrorCronJob, ErrorWebhook } from '../../common/constants/errors'; import { CronJobEntity } from './cron-job.entity'; import { CronJobRepository } from './cron-job.repository'; -import { Cron, CronExpression } from '@nestjs/schedule'; import { WebhookService } from '../webhook/webhook.service'; import { EventType, WebhookStatus } from '../../common/enums/webhook'; import { WebhookRepository } from '../webhook/webhook.repository'; diff --git a/packages/apps/reputation-oracle/server/src/modules/sendgrid/sendgrid.service.ts b/packages/apps/reputation-oracle/server/src/modules/sendgrid/sendgrid.service.ts index a7bf646091..496c79db96 100644 --- a/packages/apps/reputation-oracle/server/src/modules/sendgrid/sendgrid.service.ts +++ b/packages/apps/reputation-oracle/server/src/modules/sendgrid/sendgrid.service.ts @@ -2,7 +2,10 @@ import { BadRequestException, Injectable, Logger } from '@nestjs/common'; import { ConfigService } from '@nestjs/config'; import { MailDataRequired, MailService } from '@sendgrid/mail'; import { ConfigNames } from '../../common/config'; -import { SENDGRID_API_KEY_REGEX } from '../../common/constants'; +import { + SENDGRID_API_KEY_DISABLED, + SENDGRID_API_KEY_REGEX, +} from '../../common/constants'; import { ErrorSendGrid } from '../../common/constants/errors'; @Injectable() @@ -11,22 +14,17 @@ export class SendGridService { private readonly defaultFromEmail: string; private readonly defaultFromName: string; + private readonly apiKey: string; constructor( private readonly mailService: MailService, private readonly configService: ConfigService, ) { - const apiKey = this.configService.get( + this.apiKey = this.configService.get( ConfigNames.SENDGRID_API_KEY, '', ); - if (!SENDGRID_API_KEY_REGEX.test(apiKey)) { - throw new Error(ErrorSendGrid.InvalidApiKey); - } - - this.mailService.setApiKey(apiKey); - this.defaultFromEmail = this.configService.get( ConfigNames.SENDGRID_FROM_EMAIL, '', @@ -35,6 +33,16 @@ export class SendGridService { ConfigNames.SENDGRID_FROM_NAME, '', ); + + if (this.apiKey === SENDGRID_API_KEY_DISABLED) { + return; + } + + if (!SENDGRID_API_KEY_REGEX.test(this.apiKey)) { + throw new Error(ErrorSendGrid.InvalidApiKey); + } + + this.mailService.setApiKey(this.apiKey); } async sendEmail({ @@ -47,6 +55,11 @@ export class SendGridService { ...emailData }: Partial): Promise { try { + if (this.apiKey === SENDGRID_API_KEY_DISABLED) { + this.logger.debug(personalizations); + return; + } + await this.mailService.send({ from, templateId, diff --git a/packages/apps/reputation-oracle/server/src/modules/storage/storage.service.spec.ts b/packages/apps/reputation-oracle/server/src/modules/storage/storage.service.spec.ts index ec3955b07f..e8fcc0e6a0 100644 --- a/packages/apps/reputation-oracle/server/src/modules/storage/storage.service.spec.ts +++ b/packages/apps/reputation-oracle/server/src/modules/storage/storage.service.spec.ts @@ -64,7 +64,7 @@ jest.mock('axios'); describe('StorageService', () => { let storageService: StorageService; - let encrypt = true; + let encrypt = 'true'; const signerMock = { address: '0x1234567890123456789012345678901234567892', @@ -160,7 +160,7 @@ describe('StorageService', () => { describe('without encryption', () => { beforeAll(() => { - encrypt = false; + encrypt = 'false'; }); afterEach(() => { @@ -168,7 +168,7 @@ describe('StorageService', () => { }); afterAll(() => { - encrypt = true; + encrypt = 'true'; }); it('should upload the solutions', async () => { @@ -397,7 +397,7 @@ describe('StorageService', () => { describe('without encryption', () => { beforeAll(() => { - encrypt = false; + encrypt = 'false'; }); afterEach(() => { @@ -405,7 +405,7 @@ describe('StorageService', () => { }); afterAll(() => { - encrypt = true; + encrypt = 'true'; }); it('should copy a file from a valid URL to a bucket', async () => { diff --git a/packages/apps/reputation-oracle/server/src/modules/storage/storage.service.ts b/packages/apps/reputation-oracle/server/src/modules/storage/storage.service.ts index e8ae0a7e46..f829bf59a8 100644 --- a/packages/apps/reputation-oracle/server/src/modules/storage/storage.service.ts +++ b/packages/apps/reputation-oracle/server/src/modules/storage/storage.service.ts @@ -45,7 +45,7 @@ export class StorageService { chainId: ChainId, content: any, ) { - if (!this.configService.get(ConfigNames.PGP_ENCRYPT)) { + if (this.configService.get(ConfigNames.PGP_ENCRYPT) !== 'true') { return content; } diff --git a/packages/apps/reputation-oracle/server/src/modules/web3/web3.service.spec.ts b/packages/apps/reputation-oracle/server/src/modules/web3/web3.service.spec.ts index 258bf3e72f..6ff1706571 100644 --- a/packages/apps/reputation-oracle/server/src/modules/web3/web3.service.spec.ts +++ b/packages/apps/reputation-oracle/server/src/modules/web3/web3.service.spec.ts @@ -1,12 +1,12 @@ +import { ChainId } from '@human-protocol/sdk'; import { ConfigService } from '@nestjs/config'; import { Test } from '@nestjs/testing'; -import { MAINNET_CHAIN_IDS, TESTNET_CHAIN_IDS } from '../../common/config'; -import { Web3Service } from './web3.service'; import { MOCK_ADDRESS, MOCK_PRIVATE_KEY } from '../../../test/constants'; -import { ChainId } from '@human-protocol/sdk'; +import { TESTNET_CHAIN_IDS } from '../../common/config'; import { ErrorWeb3 } from '../../common/constants/errors'; -import { SignatureType, Web3Env } from '../../common/enums/web3'; +import { SignatureType } from '../../common/enums/web3'; import { SignatureBodyDto } from './web3.dto'; +import { Web3Service } from './web3.service'; describe('Web3Service', () => { let mockConfigService: Partial; @@ -51,20 +51,13 @@ describe('Web3Service', () => { const invalidChainId = ChainId.POLYGON; expect(() => web3Service.getSigner(invalidChainId)).toThrow( - ErrorWeb3.InvalidTestnetChainId, + ErrorWeb3.InvalidChainId, ); }); }); describe('getValidChains', () => { - it('should get all valid chainIds on MAINNET', () => { - mockConfigService.get = jest.fn().mockReturnValue(Web3Env.MAINNET); - const validChainIds = web3Service.getValidChains(); - expect(validChainIds).toBe(MAINNET_CHAIN_IDS); - }); - it('should get all valid chainIds on TESTNET', () => { - mockConfigService.get = jest.fn().mockReturnValue(Web3Env.TESTNET); const validChainIds = web3Service.getValidChains(); expect(validChainIds).toBe(TESTNET_CHAIN_IDS); }); diff --git a/packages/apps/reputation-oracle/server/src/modules/web3/web3.service.ts b/packages/apps/reputation-oracle/server/src/modules/web3/web3.service.ts index e4c9601b7b..14e6c67126 100644 --- a/packages/apps/reputation-oracle/server/src/modules/web3/web3.service.ts +++ b/packages/apps/reputation-oracle/server/src/modules/web3/web3.service.ts @@ -3,6 +3,7 @@ import { ConfigService } from '@nestjs/config'; import { Wallet, ethers } from 'ethers'; import { ConfigNames, + LOCALHOST_CHAIN_IDS, MAINNET_CHAIN_IDS, TESTNET_CHAIN_IDS, networks, @@ -15,9 +16,15 @@ import { SignatureBodyDto } from './web3.dto'; @Injectable() export class Web3Service { private signers: { [key: number]: Wallet } = {}; + public readonly signerAddress: string; + public readonly currentWeb3Env: string; public readonly logger = new Logger(Web3Service.name); constructor(private readonly configService: ConfigService) { + this.currentWeb3Env = this.configService.get( + ConfigNames.WEB3_ENV, + ) as string; + const privateKey = this.configService.get(ConfigNames.WEB3_PRIVATE_KEY); const validChains = this.getValidChains(); const validNetworks = networks.filter((network) => @@ -35,27 +42,24 @@ export class Web3Service { } public validateChainId(chainId: number): void { - const currentWeb3Env = this.configService.get(ConfigNames.WEB3_ENV); const validChainIds = this.getValidChains(); - if (!validChainIds.includes(chainId)) { - const errorType = - currentWeb3Env === Web3Env.MAINNET - ? ErrorWeb3.InvalidMainnetChainId - : ErrorWeb3.InvalidTestnetChainId; - this.logger.log(errorType, Web3Service.name); - throw new BadRequestException(errorType); + this.logger.log(ErrorWeb3.InvalidChainId, Web3Service.name); + throw new BadRequestException(ErrorWeb3.InvalidChainId); } } public getValidChains(): ChainId[] { - const currentWeb3Env = this.configService.get(ConfigNames.WEB3_ENV); - const validChainIds = - currentWeb3Env === Web3Env.MAINNET - ? MAINNET_CHAIN_IDS - : TESTNET_CHAIN_IDS; - - return validChainIds; + switch (this.currentWeb3Env) { + case Web3Env.MAINNET: + return MAINNET_CHAIN_IDS; + case Web3Env.TESTNET: + return TESTNET_CHAIN_IDS; + case Web3Env.LOCALHOST: + return LOCALHOST_CHAIN_IDS; + default: + return LOCALHOST_CHAIN_IDS; + } } public async calculateGasPrice(chainId: number): Promise { @@ -65,10 +69,10 @@ export class Web3Service { 1, ); const gasPrice = (await signer.provider?.getFeeData())?.gasPrice; - if (!gasPrice) { - throw new Error(ErrorWeb3.GasPriceError); + if (gasPrice) { + return gasPrice * BigInt(multiplier); } - return gasPrice * BigInt(multiplier); + throw new Error(ErrorWeb3.GasPriceError); } public getOperatorAddress(): string { diff --git a/packages/apps/reputation-oracle/server/test/setup.ts b/packages/apps/reputation-oracle/server/test/setup.ts index 5a34c8a8cc..b3d45d6645 100644 --- a/packages/apps/reputation-oracle/server/test/setup.ts +++ b/packages/apps/reputation-oracle/server/test/setup.ts @@ -1,11 +1,42 @@ import { KVStoreClient, KVStoreKeys, Role } from '@human-protocol/sdk'; import { Wallet, ethers } from 'ethers'; +import * as Minio from 'minio'; +import * as dotenv from 'dotenv'; +dotenv.config({ path: '.env.local' }); export async function setup(): Promise { - //This private key is generated by hardhat and should be used just for local testing - const privateKey = - '0x47e179ec197488593b187f80a00eb0da91f1b9d0b13f8733639f19c30a34926a'; //0x15d34AAf54267DB7D7c367839AAf71A00a2C6A65 - const provider = new ethers.JsonRpcProvider('http://localhost:8545'); + const privateKey = process.env.WEB3_PRIVATE_KEY; + if (!privateKey) { + throw new Error('Private key is empty'); + } + const jwtPublicKey = process.env.JWT_PUBLIC_KEY; + if (!jwtPublicKey) { + throw new Error('JWT public key is empty'); + } + + const minioClient = new Minio.Client({ + endPoint: process.env.S3_ENDPOINT || 'localhost', + port: parseInt(process.env.S3_PORT || '9000', 10), + useSSL: process.env.S3_USE_SSL === 'true', + accessKey: process.env.S3_ACCESS_KEY || 'access-key', + secretKey: process.env.S3_SECRET_KEY || 'secret-key', + }); + const bucket = process.env.S3_BUCKET || 'bucket'; + const fileName = 'public-key.txt'; + const exists = await minioClient.bucketExists(bucket); + if (!exists) { + throw new Error('Bucket does not exists'); + } + try { + await minioClient.putObject(bucket, fileName, jwtPublicKey, { + 'Content-Type': 'application/json', + 'Cache-Control': 'no-store', + }); + } catch (e) { + console.log(e); + } + + const provider = new ethers.JsonRpcProvider('http://0.0.0.0:8545'); const wallet = new Wallet(privateKey, provider); const kvStoreClient = await KVStoreClient.build(wallet); @@ -13,6 +44,10 @@ export async function setup(): Promise { [KVStoreKeys.role, KVStoreKeys.fee, KVStoreKeys.webhookUrl], [Role.ReputationOracle, '1', 'http://localhost:5003/webhook'], ); + await kvStoreClient.setFileUrlAndHash( + `http://localhost:9000/bucket/${fileName}`, + 'jwt_public_key', + ); } setup(); diff --git a/scripts/Makefile b/scripts/Makefile index 4499987023..2ffb246f54 100644 --- a/scripts/Makefile +++ b/scripts/Makefile @@ -22,6 +22,10 @@ create-env-files: cp ./fortune/.env.rec-oracle ../packages/apps/fortune/recording-oracle/.env.local ; \ fi + @if [ ! -f "../packages/apps/reputation-oracle/server/.env.local" ]; then \ + cp ./fortune/.env.rep-oracle ../packages/apps/reputation-oracle/server/.env.local ; \ + fi + hardhat-node: yarn workspace @human-protocol/core local @@ -44,7 +48,7 @@ database: job-launcher-server: rpc-health-check yarn workspace @human-protocol/job-launcher-server setup:local NODE_ENV=local yarn workspace @human-protocol/job-launcher-server migration:run - NODE_ENV=local yarn workspace @human-protocol/job-launcher-server start + NODE_ENV=local yarn workspace @human-protocol/job-launcher-server start:dev job-launcher-client: NODE_ENV=local yarn workspace @human-protocol/job-launcher-client start @@ -52,14 +56,16 @@ job-launcher-client: fortune-exchange-oracle: rpc-health-check yarn workspace @human-protocol/fortune-exchange-oracle-server setup:local NODE_ENV=local yarn workspace @human-protocol/fortune-exchange-oracle-server migration:run - NODE_ENV=local yarn workspace @human-protocol/fortune-exchange-oracle-server start + NODE_ENV=local yarn workspace @human-protocol/fortune-exchange-oracle-server start:dev fortune-recording-oracle: rpc-health-check yarn workspace @human-protocol/fortune-recording-oracle setup:local - NODE_ENV=local yarn workspace @human-protocol/fortune-recording-oracle start + NODE_ENV=local yarn workspace @human-protocol/fortune-recording-oracle start:dev -reputation-oracle: +reputation-oracle: rpc-health-check yarn workspace @human-protocol/reputation-oracle setup:local + NODE_ENV=local yarn workspace @human-protocol/reputation-oracle migration:run + NODE_ENV=local yarn workspace @human-protocol/reputation-oracle start:dev rpc-health-check: @until curl -s -X POST "http://localhost:8545" -H "Content-Type: application/json" --data '{"jsonrpc":"2.0","method":"web3_clientVersion","params":[],"id":1}' | grep -q "result"; do \ @@ -68,11 +74,12 @@ rpc-health-check: fortune: check-core-folders minio @echo "RUNNING FORTUNE..." - @trap 'echo "STOPPING FORTUNE.."; kill -9 $$PID_HARDHAT $$PID_JL_CLIENT $$PID_JL_SERVER $$PID_EXO $$PID_RECO; docker compose -f ./fortune/docker-compose.yml down; exit 0' SIGINT ERR; \ + @trap 'echo "STOPPING FORTUNE.."; kill -9 $$PID_HARDHAT $$PID_JL_CLIENT $$PID_JL_SERVER $$PID_EXO $$PID_RECO $$PID_REPO; docker compose -f ./fortune/docker-compose.yml down; exit 0' SIGINT ERR; \ $(MAKE) hardhat-node & PID_HARDHAT=$$!; \ $(MAKE) job-launcher-client & PID_JL_CLIENT=$$!; \ $(MAKE) job-launcher-server & PID_JL_SERVER=$$!; \ $(MAKE) fortune-exchange-oracle & PID_EXO=$$!; \ $(MAKE) fortune-recording-oracle & PID_RECO=$$!; \ + $(MAKE) reputation-oracle & PID_REPO=$$!; \ $(MAKE) subgraph & \ wait \ No newline at end of file diff --git a/scripts/fortune/.env.exco-server b/scripts/fortune/.env.exco-server index 387769f56d..35a43bb3e6 100644 --- a/scripts/fortune/.env.exco-server +++ b/scripts/fortune/.env.exco-server @@ -20,6 +20,7 @@ S3_SECRET_KEY=secret-key S3_BUCKET=bucket S3_USE_SSL=false +WEB3_ENV=localhost WEB3_PRIVATE_KEY=0x5de4111afa1a4b94908f83103eb1f1706367c2e68ca870fc3fb9a804cdab365a #0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC PGP_ENCRYPT=false diff --git a/scripts/fortune/.env.jl-server b/scripts/fortune/.env.jl-server index 4fd1f43f5e..b15e2ec4e0 100644 --- a/scripts/fortune/.env.jl-server +++ b/scripts/fortune/.env.jl-server @@ -26,7 +26,7 @@ FORTUNE_EXCHANGE_ORACLE_ADDRESS=0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC FORTUNE_RECORDING_ORACLE_ADDRESS=0x90F79bf6EB2c4f870365E785982E1f101E93b906 CVAT_EXCHANGE_ORACLE_ADDRESS=0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC CVAT_RECORDING_ORACLE_ADDRESS=0x90F79bf6EB2c4f870365E785982E1f101E93b906 -REPUTATION_ORACLE_ADDRESS=0x90F79bf6EB2c4f870365E785982E1f101E93b906 +REPUTATION_ORACLE_ADDRESS=0x15d34AAf54267DB7D7c367839AAf71A00a2C6A65 HCAPTCHA_RECORDING_ORACLE_URI=a HCAPTCHA_REPUTATION_ORACLE_URI=a HCAPTCHA_ORACLE_ADDRESS=a @@ -72,7 +72,7 @@ STRIPE_APP_VERSION=1.0.0 STRIPE_APP_INFO_URL=https://github.com/humanprotocol/human-protocol/tree/main/packages/apps/job-launcher/server #Sendgrid -SENDGRID_API_KEY= +SENDGRID_API_KEY=sendgrid-disabled #Cron jobs secret CRON_SECRET=secret \ No newline at end of file diff --git a/scripts/fortune/.env.rep-oracle b/scripts/fortune/.env.rep-oracle new file mode 100644 index 0000000000..74e6069fd3 --- /dev/null +++ b/scripts/fortune/.env.rep-oracle @@ -0,0 +1,65 @@ +# General +NODE_ENV=development +HOST=localhost +PORT=5003 +SESSION_SECRET=test +MAX_RETRY_COUNT=5 +CRON_SECRET=secret + +# Database +POSTGRES_HOST=0.0.0.0 +POSTGRES_USER=default +POSTGRES_PASSWORD=qwerty +POSTGRES_DATABASE=reputation-oracle +POSTGRES_DB=reputation-oracle +POSTGRES_PORT=5432 +POSTGRES_SSL=false +POSTGRES_LOGGING='all' + +# Auth +HASH_SECRET=a328af3fc1dad15342cc3d68936008fa +JWT_PRIVATE_KEY="-----BEGIN EC PARAMETERS----- +BggqhkjOPQMBBw== +-----END EC PARAMETERS----- +-----BEGIN EC PRIVATE KEY----- +MHcCAQEEIHGJVym8HlDFqIq7VWYSzfVG2/sW5qLrihq2g6ssRLQ0oAoGCCqGSM49 +AwEHoUQDQgAEmxypAnb2RpdFhhOfY4JpfKG1SlH7pDsp3Y6apiRBPO7DXgmBrwY3 +cljrl3cdbeEgkp5SSYBGSdoGDsce8NnQdA== +-----END EC PRIVATE KEY-----" +JWT_PUBLIC_KEY="-----BEGIN PUBLIC KEY----- +MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEmxypAnb2RpdFhhOfY4JpfKG1SlH7 +pDsp3Y6apiRBPO7DXgmBrwY3cljrl3cdbeEgkp5SSYBGSdoGDsce8NnQdA== +-----END PUBLIC KEY-----" +JWT_ACCESS_TOKEN_EXPIRES_IN=1000000000 +JWT_REFRESH_TOKEN_EXPIRES_IN=1000000000 + +# S3 +S3_ENDPOINT=localhost +S3_PORT=9000 +S3_ACCESS_KEY=access-key +S3_SECRET_KEY=secret-key +S3_BUCKET=bucket +S3_USE_SSL=false + +# Sendgrid +SENDGRID_API_KEY=sendgrid-disabled +SENDGRID_FROM_EMAIL=reputation-oracle@hmt.ai +SENDGRID_FROM_NAME="Human Protocol Reputation Oracle" + +# Web3 +WEB3_ENV=localhost +WEB3_PRIVATE_KEY=0x47e179ec197488593b187f80a00eb0da91f1b9d0b13f8733639f19c30a34926a #0x15d34AAf54267DB7D7c367839AAf71A00a2C6A65 +GAS_PRICE_MULTIPLIER=1 + +# Reputation Level +REPUTATION_LEVEL_LOW=300 +REPUTATION_LEVEL_HIGH=700 + +# Encryption +# ENCRYPTION_PRIVATE_KEY= +# ENCRYPTION_PASSPHRASE= +PGP_ENCRYPT=false + +# Synaps Kyc +SYNAPS_API_KEY=test +SYNAPS_WEBHOOK_SECRET=test From 08a65d2064b4c2e3f05fa3b8c6ae73a328bd9517 Mon Sep 17 00:00:00 2001 From: portuu3 <61605646+portuu3@users.noreply.github.com> Date: Tue, 2 Apr 2024 12:37:40 +0200 Subject: [PATCH 28/66] fix webhook typo (#1804) --- .../server/src/modules/cron-job/cron.job.controller.ts | 2 +- .../server/src/modules/cron-job/cron.job.controller.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/apps/fortune/exchange-oracle/server/src/modules/cron-job/cron.job.controller.ts b/packages/apps/fortune/exchange-oracle/server/src/modules/cron-job/cron.job.controller.ts index 56529fc8e7..9f9d82d989 100644 --- a/packages/apps/fortune/exchange-oracle/server/src/modules/cron-job/cron.job.controller.ts +++ b/packages/apps/fortune/exchange-oracle/server/src/modules/cron-job/cron.job.controller.ts @@ -27,7 +27,7 @@ export class CronJobController { description: 'Bad Request. Invalid input parameters.', }) @ApiBearerAuth() - @Get('/wehbhook/process') + @Get('/webhook/process') public async processPendingWebhooks(): Promise { await this.cronJobService.processPendingWebhooks(); return; diff --git a/packages/apps/reputation-oracle/server/src/modules/cron-job/cron.job.controller.ts b/packages/apps/reputation-oracle/server/src/modules/cron-job/cron.job.controller.ts index f4ea816285..98439cb3bd 100644 --- a/packages/apps/reputation-oracle/server/src/modules/cron-job/cron.job.controller.ts +++ b/packages/apps/reputation-oracle/server/src/modules/cron-job/cron.job.controller.ts @@ -16,7 +16,7 @@ export class CronJobController { constructor(private readonly cronJobService: CronJobService) {} @Public() - @Get('/wehbhook/pending') + @Get('/webhook/pending') @ApiOperation({ summary: 'Process Pending Cron Job', description: 'Endpoint to process pending cron jobs that triggers payouts.', @@ -33,7 +33,7 @@ export class CronJobController { } @Public() - @Get('/wehbhook/paid') + @Get('/webhook/paid') @ApiOperation({ summary: 'Process Paid Cron Job', description: From 5534c7719f12682e42b700d778cae20c6e4b96f2 Mon Sep 17 00:00:00 2001 From: eugenvoronov <104138627+eugenvoronov@users.noreply.github.com> Date: Tue, 2 Apr 2024 16:17:30 +0300 Subject: [PATCH 29/66] [Job Launcher] Refactored calculate job bounty method (#1806) * Refactored calculate job bounty method * Updated create datset actions * Resolved comments --- .../server/src/modules/job/job.interface.ts | 14 +-- .../src/modules/job/job.service.spec.ts | 31 +++++-- .../server/src/modules/job/job.service.ts | 88 ++++++++----------- 3 files changed, 65 insertions(+), 68 deletions(-) diff --git a/packages/apps/job-launcher/server/src/modules/job/job.interface.ts b/packages/apps/job-launcher/server/src/modules/job/job.interface.ts index 821dc31114..05138bd7cc 100644 --- a/packages/apps/job-launcher/server/src/modules/job/job.interface.ts +++ b/packages/apps/job-launcher/server/src/modules/job/job.interface.ts @@ -10,16 +10,11 @@ export interface RequestAction { ) => Promise; } -export interface ManifestAction { +export interface DatasetAction { getElementsCount: ( requestType: JobRequestType, data: CvatDataDto, ) => Promise; - calculateJobBounty: ( - elementsCount: number, - fundAmount: number, - nodesTotal?: number, - ) => Promise; } export interface EscrowAction { @@ -35,3 +30,10 @@ export interface OracleAddresses { recordingOracle: string; reputationOracle: string; } + +export class CvatCalculateJobBounty { + requestType: JobRequestType; + elementsCount: number; + fundAmount: number; + nodesTotal?: number; +} diff --git a/packages/apps/job-launcher/server/src/modules/job/job.service.spec.ts b/packages/apps/job-launcher/server/src/modules/job/job.service.spec.ts index 450288da0f..87b6ef3cc7 100644 --- a/packages/apps/job-launcher/server/src/modules/job/job.service.spec.ts +++ b/packages/apps/job-launcher/server/src/modules/job/job.service.spec.ts @@ -530,7 +530,7 @@ describe('JobService', () => { it('should create a valid CVAT manifest for image boxes job type', async () => { const jobBounty = '50'; jest - .spyOn(jobService, 'calculateCvatJobBounty') + .spyOn(jobService, 'calculateJobBounty') .mockResolvedValueOnce(jobBounty); const dto: JobCvatDto = { @@ -1088,15 +1088,28 @@ describe('JobService', () => { }); }); - describe('calculateCvatJobBounty', () => { - it('should calculate the job bounty correctly', async () => { - const tokenFundAmount = 0.013997056833333334; - const result = await jobService['calculateCvatJobBounty']( - 6, - tokenFundAmount, - ); + describe('calculateJobBounty', () => { + it('should calculate the job bounty correctly for image boxes from points type', async () => { + const data = { + requestType: JobRequestType.IMAGE_BOXES_FROM_POINTS, + elementsCount: 28883, + fundAmount: 22.918128652290278, + }; + const result = await jobService['calculateJobBounty'](data); + + expect(result).toEqual('0.000793481586133375'); + }); + + it('should calculate the job bounty correctly for image skeletons from boxed type', async () => { + const data = { + requestType: JobRequestType.IMAGE_SKELETONS_FROM_BOXES, + elementsCount: 50, + fundAmount: 22.918128652290278, + nodesTotal: 4, + }; + const result = await jobService['calculateJobBounty'](data); - expect(result).toEqual('0.002332842805555555'); + expect(result).toEqual('0.636614684785841055'); }); }); diff --git a/packages/apps/job-launcher/server/src/modules/job/job.service.ts b/packages/apps/job-launcher/server/src/modules/job/job.service.ts index 043fa1eaae..c2153e23ff 100644 --- a/packages/apps/job-launcher/server/src/modules/job/job.service.ts +++ b/packages/apps/job-launcher/server/src/modules/job/job.service.ts @@ -113,11 +113,12 @@ import { WebhookDataDto } from '../webhook/webhook.dto'; import * as crypto from 'crypto'; import { PaymentEntity } from '../payment/payment.entity'; import { - ManifestAction, + DatasetAction, EscrowAction, OracleAction, OracleAddresses, RequestAction, + CvatCalculateJobBounty, } from './job.interface'; import { WebhookEntity } from '../webhook/webhook.entity'; import { WebhookRepository } from '../webhook/webhook.repository'; @@ -150,15 +151,15 @@ export class JobService { requestType: JobRequestType, tokenFundAmount: number, ): Promise { - const { getElementsCount, calculateJobBounty } = - this.createManifestActions[requestType]; + const { getElementsCount } = this.createDatasetActions[requestType]; const elementsCount = await getElementsCount(requestType, dto.data); - const jobBounty = await calculateJobBounty( + const jobBounty = await this.calculateJobBounty({ + requestType, elementsCount, - tokenFundAmount, - dto.labels[0]?.nodes?.length, - ); + fundAmount: tokenFundAmount, + nodesTotal: dto.labels[0]?.nodes?.length, + }); return { data: { @@ -505,73 +506,36 @@ export class JobService { }, }; - private createManifestActions: Record = { + private createDatasetActions: Record = { [JobRequestType.HCAPTCHA]: { getElementsCount: async () => 0, - calculateJobBounty: async () => ethers.formatEther(0), }, [JobRequestType.FORTUNE]: { getElementsCount: async () => 0, - calculateJobBounty: async () => ethers.formatEther(0), }, [JobRequestType.IMAGE_BOXES]: { getElementsCount: async ( requestType: JobRequestType, data: CvatDataDto, ) => (await listObjectsInBucket(data.dataset, requestType)).length, - calculateJobBounty: async ( - elementsCount: number, - fundAmount: number, - ): Promise => - this.calculateCvatJobBounty(elementsCount, fundAmount), }, [JobRequestType.IMAGE_POINTS]: { getElementsCount: async ( requestType: JobRequestType, data: CvatDataDto, ) => (await listObjectsInBucket(data.dataset, requestType)).length, - calculateJobBounty: async ( - elementsCount: number, - fundAmount: number, - ): Promise => - this.calculateCvatJobBounty(elementsCount, fundAmount), }, [JobRequestType.IMAGE_BOXES_FROM_POINTS]: { getElementsCount: async ( requestType: JobRequestType, data: CvatDataDto, ) => this.getCvatElementsCount(requestType, data.points!), - calculateJobBounty: async ( - elementsCount: number, - fundAmount: number, - ): Promise => - this.calculateCvatJobBounty(elementsCount, fundAmount), }, [JobRequestType.IMAGE_SKELETONS_FROM_BOXES]: { getElementsCount: async ( requestType: JobRequestType, data: CvatDataDto, ) => this.getCvatElementsCount(requestType, data.boxes!), - calculateJobBounty: async ( - elementsCount: number, - fundAmount: number, - nodesTotal: number, - ): Promise => { - const cvatDefaultJobSize = Number(this.cvatConfigService.jobSize); - - const jobSizeMultiplier = Number( - this.cvatConfigService.skeletonsJobSizeMultiplier, - ); - const jobSize = jobSizeMultiplier * cvatDefaultJobSize; - - // For each skeleton node CVAT creates a separate project thus increasing amount of jobs - const totalJobs = - Math.ceil(div(elementsCount, jobSize)) * (nodesTotal || 1); - - return ethers.formatEther( - ethers.parseUnits(fundAmount.toString(), 'ether') / BigInt(totalJobs), - ); - }, }, }; @@ -761,17 +725,35 @@ export class JobService { ).annotations?.length; } - public async calculateCvatJobBounty( - elementsCount: number, - fundAmount: number, + public async calculateJobBounty( + data: CvatCalculateJobBounty, ): Promise { - const jobSize = Number(this.cvatConfigService.jobSize); + const { requestType, elementsCount, fundAmount, nodesTotal } = data; - const totalJobs = Math.ceil(div(elementsCount, jobSize)); + let jobSize = Number(this.cvatConfigService.jobSize); - return ethers.formatEther( - ethers.parseUnits(fundAmount.toString(), 'ether') / BigInt(totalJobs), - ); + if (requestType === JobRequestType.IMAGE_SKELETONS_FROM_BOXES) { + const jobSizeMultiplier = Number( + this.cvatConfigService.skeletonsJobSizeMultiplier, + ); + jobSize *= jobSizeMultiplier; + } + + let totalJobs: number; + + // For each skeleton node CVAT creates a separate project thus increasing amount of jobs + if ( + requestType === JobRequestType.IMAGE_SKELETONS_FROM_BOXES && + nodesTotal + ) { + totalJobs = Math.ceil(elementsCount / jobSize) * nodesTotal; + } else { + totalJobs = Math.ceil(elementsCount / jobSize); + } + + const jobBounty = + ethers.parseUnits(fundAmount.toString(), 'ether') / BigInt(totalJobs); + return ethers.formatEther(jobBounty); } public async createEscrow(jobEntity: JobEntity): Promise { From 473de255469a74df599d2c8384a7c1445f33e293 Mon Sep 17 00:00:00 2001 From: eugenvoronov <104138627+eugenvoronov@users.noreply.github.com> Date: Wed, 3 Apr 2024 13:54:14 +0300 Subject: [PATCH 30/66] [Job Launcher] Updated `getCvatElementsCount` method (#1808) * Updated getCvatElementsCount method * Imlemented generateUrls method, refactored calculateJobBounty method, updated unit tests --- .../server/src/modules/job/job.interface.ts | 49 +++++- .../src/modules/job/job.service.spec.ts | 163 +++++++++++++++--- .../server/src/modules/job/job.service.ts | 120 +++++++++++-- .../job-launcher/server/test/constants.ts | 56 ++++++ 4 files changed, 339 insertions(+), 49 deletions(-) diff --git a/packages/apps/job-launcher/server/src/modules/job/job.interface.ts b/packages/apps/job-launcher/server/src/modules/job/job.interface.ts index 05138bd7cc..1baaac3105 100644 --- a/packages/apps/job-launcher/server/src/modules/job/job.interface.ts +++ b/packages/apps/job-launcher/server/src/modules/job/job.interface.ts @@ -1,5 +1,5 @@ import { JobRequestType } from '../../common/enums/job'; -import { CreateJob, CvatDataDto, Label } from './job.dto'; +import { CreateJob, CvatDataDto, Label, StorageDataDto } from './job.dto'; export interface RequestAction { calculateFundAmount: (dto: CreateJob, rate: number) => Promise; @@ -10,11 +10,16 @@ export interface RequestAction { ) => Promise; } -export interface DatasetAction { +export interface ManifestAction { getElementsCount: ( requestType: JobRequestType, data: CvatDataDto, + gtUrl?: string, ) => Promise; + generateUrls: ( + data: CvatDataDto, + groundTruth: StorageDataDto, + ) => GenerateUrls; } export interface EscrowAction { @@ -31,9 +36,45 @@ export interface OracleAddresses { reputationOracle: string; } -export class CvatCalculateJobBounty { +export interface CvatCalculateJobBounty { requestType: JobRequestType; - elementsCount: number; fundAmount: number; + data: CvatDataDto; + gtUrl: string; nodesTotal?: number; } + +export interface GenerateUrls { + dataUrl: string; + gtUrl: string; + pointsUrl?: string; + boxesUrl?: string; +} + +export interface CvatImageData { + id: number; + width: number; + height: number; + file_name: string; + license: number; + flickr_url: string; + coco_url: string; + date_captured: number; +} + +export interface CvatAnnotationData { + id: number; + image_id: number; + category_id: number; + segmentation: number[]; + area: number; + bbox: [number, number, number, number]; + iscrowd: number; + attributes: { + scale: number; + x: number; + y: number; + }; + keypoints: [number, number, number]; + num_keypoints: number; +} diff --git a/packages/apps/job-launcher/server/src/modules/job/job.service.spec.ts b/packages/apps/job-launcher/server/src/modules/job/job.service.spec.ts index 87b6ef3cc7..7a535d092b 100644 --- a/packages/apps/job-launcher/server/src/modules/job/job.service.spec.ts +++ b/packages/apps/job-launcher/server/src/modules/job/job.service.spec.ts @@ -80,6 +80,8 @@ import { MOCK_CVAT_LABELS_WITH_NODES, MOCK_CVAT_DATA_POINTS, MOCK_CVAT_DATA_BOXES, + MOCK_CVAT_DATA, + MOCK_CVAT_GT, } from '../../../test/constants'; import { PaymentService } from '../payment/payment.service'; import { Web3Service } from '../web3/web3.service'; @@ -526,6 +528,43 @@ describe('JobService', () => { }); }); + describe('getCvatElementsCount', () => { + let storageDatasetMock: StorageDataDto; + + beforeAll(async () => { + storageDatasetMock = { + provider: StorageProviders.AWS, + region: AWSRegions.AP_EAST_1, + bucketName: 'bucket', + path: 'folder/test', + }; + }); + + it('should throw ConflictException if storageData is not provided', async () => { + await expect( + jobService.getCvatElementsCount( + JobRequestType.IMAGE_BOXES_FROM_POINTS, + null as any, + 'some-gt-url', + ), + ).rejects.toThrow(ConflictException); + }); + + it('should calculate the number of CVAT elements correctly', async () => { + jest + .spyOn(storageService, 'download') + .mockResolvedValueOnce(MOCK_CVAT_DATA) + .mockResolvedValueOnce(MOCK_CVAT_GT); + + const result = await jobService.getCvatElementsCount( + JobRequestType.IMAGE_BOXES_FROM_POINTS, + storageDatasetMock, + 'some-gt-url', + ); + expect(result).toBe(2); + }); + }); + describe('createCvatManifest', () => { it('should create a valid CVAT manifest for image boxes job type', async () => { const jobBounty = '50'; @@ -574,12 +613,12 @@ describe('JobService', () => { }); it('should create a valid CVAT manifest for image boxes from points job type', async () => { - const jobBounty = '25.0'; + const jobBounty = '50.0'; - const data = { - annotations: ['string', 'string', 'string', 'string'], - }; - jest.spyOn(storageService, 'download').mockResolvedValueOnce(data); + jest + .spyOn(storageService, 'download') + .mockResolvedValueOnce(MOCK_CVAT_DATA) + .mockResolvedValueOnce(MOCK_CVAT_GT); const dto: JobCvatDto = { data: MOCK_CVAT_DATA_POINTS, @@ -625,10 +664,10 @@ describe('JobService', () => { it('should create a valid CVAT manifest for image skeletons from boxes job type', async () => { const jobBounty = '4.0'; - const data = { - annotations: ['string', 'string', 'string', 'string'], - }; - jest.spyOn(storageService, 'download').mockResolvedValueOnce(data); + jest + .spyOn(storageService, 'download') + .mockResolvedValueOnce(MOCK_CVAT_DATA) + .mockResolvedValueOnce(MOCK_CVAT_GT); const dto: JobCvatDto = { data: MOCK_CVAT_DATA_BOXES, @@ -1090,26 +1129,70 @@ describe('JobService', () => { describe('calculateJobBounty', () => { it('should calculate the job bounty correctly for image boxes from points type', async () => { + const storageDatasetMock: any = { + dataset: { + provider: StorageProviders.AWS, + region: AWSRegions.EU_CENTRAL_1, + bucketName: 'bucket', + path: 'folder/test', + }, + points: { + provider: StorageProviders.AWS, + region: AWSRegions.EU_CENTRAL_1, + bucketName: 'bucket', + path: 'folder/test', + }, + }; + + jest + .spyOn(storageService, 'download') + .mockResolvedValueOnce(MOCK_CVAT_DATA) + .mockResolvedValueOnce(MOCK_CVAT_GT); + const data = { requestType: JobRequestType.IMAGE_BOXES_FROM_POINTS, - elementsCount: 28883, fundAmount: 22.918128652290278, + data: storageDatasetMock, + gtUrl: MOCK_FILE_URL, }; - const result = await jobService['calculateJobBounty'](data); - expect(result).toEqual('0.000793481586133375'); + const result = await jobService['calculateJobBounty'](data); // elementsCount = 2 + + expect(result).toEqual('11.459064326145139'); }); it('should calculate the job bounty correctly for image skeletons from boxed type', async () => { + const storageDatasetMock: any = { + dataset: { + provider: StorageProviders.AWS, + region: AWSRegions.EU_CENTRAL_1, + bucketName: 'bucket', + path: 'folder/test', + }, + boxes: { + provider: StorageProviders.AWS, + region: AWSRegions.EU_CENTRAL_1, + bucketName: 'bucket', + path: 'folder/test', + }, + }; + + jest + .spyOn(storageService, 'download') + .mockResolvedValueOnce(MOCK_CVAT_DATA) + .mockResolvedValueOnce(MOCK_CVAT_GT); + const data = { requestType: JobRequestType.IMAGE_SKELETONS_FROM_BOXES, - elementsCount: 50, fundAmount: 22.918128652290278, + data: storageDatasetMock, + gtUrl: MOCK_FILE_URL, nodesTotal: 4, }; - const result = await jobService['calculateJobBounty'](data); - expect(result).toEqual('0.636614684785841055'); + const result = await jobService['calculateJobBounty'](data); // elementsCount = 2 + + expect(result).toEqual('5.7295321630725695'); }); }); @@ -1205,7 +1288,7 @@ describe('JobService', () => { const userBalance = 25; getUserBalanceMock.mockResolvedValue(userBalance); - const storageDataMock: any = { + const storageDatasetMock: any = { dataset: { provider: StorageProviders.GCS, region: AWSRegions.EU_CENTRAL_1, @@ -1214,14 +1297,21 @@ describe('JobService', () => { }, }; + const storageGtMock: any = { + provider: StorageProviders.GCS, + region: AWSRegions.EU_CENTRAL_1, + bucketName: 'bucket', + path: 'folder/test', + }; + const imageLabelBinaryJobDto: JobCvatDto = { chainId: MOCK_CHAIN_ID, - data: storageDataMock, + data: storageDatasetMock, labels: MOCK_CVAT_LABELS, requesterDescription: MOCK_REQUESTER_DESCRIPTION, minQuality: 0.95, fundAmount: 10, - groundTruth: storageDataMock, + groundTruth: storageGtMock, userGuide: MOCK_FILE_URL, type: JobRequestType.IMAGE_POINTS, }; @@ -1245,7 +1335,7 @@ describe('JobService', () => { const userBalance = 25; getUserBalanceMock.mockResolvedValue(userBalance); - const storageDataMock: any = { + const storageDatasetMock: any = { dataset: { provider: StorageProviders.AWS, region: 'test-region', @@ -1254,14 +1344,21 @@ describe('JobService', () => { }, }; + const storageGtMock: any = { + provider: StorageProviders.AWS, + region: 'test-region', + bucketName: 'bucket', + path: 'folder/test', + }; + const imageLabelBinaryJobDto: JobCvatDto = { chainId: MOCK_CHAIN_ID, - data: storageDataMock, + data: storageDatasetMock, labels: MOCK_CVAT_LABELS, requesterDescription: MOCK_REQUESTER_DESCRIPTION, minQuality: 0.95, fundAmount: 10, - groundTruth: storageDataMock, + groundTruth: storageGtMock, userGuide: MOCK_FILE_URL, type: JobRequestType.IMAGE_POINTS, }; @@ -1285,7 +1382,7 @@ describe('JobService', () => { const userBalance = 25; getUserBalanceMock.mockResolvedValue(userBalance); - const storageDataMock: any = { + const storageDatasetMock: any = { dataset: { provider: StorageProviders.AWS, bucketName: 'bucket', @@ -1293,14 +1390,20 @@ describe('JobService', () => { }, }; + const storageGtMock: any = { + provider: StorageProviders.AWS, + bucketName: 'bucket', + path: 'folder/test', + }; + const imageLabelBinaryJobDto: JobCvatDto = { chainId: MOCK_CHAIN_ID, - data: storageDataMock, + data: storageDatasetMock, labels: MOCK_CVAT_LABELS, requesterDescription: MOCK_REQUESTER_DESCRIPTION, minQuality: 0.95, fundAmount: 10, - groundTruth: storageDataMock, + groundTruth: storageGtMock, userGuide: MOCK_FILE_URL, type: JobRequestType.IMAGE_POINTS, }; @@ -1324,7 +1427,7 @@ describe('JobService', () => { const userBalance = 25; getUserBalanceMock.mockResolvedValue(userBalance); - const storageDataMock: any = { + const storageDatasetMock: any = { dataset: { provider: StorageProviders.AWS, region: AWSRegions.EU_CENTRAL_1, @@ -1332,14 +1435,20 @@ describe('JobService', () => { }, }; + const storageGtMock: any = { + provider: StorageProviders.AWS, + region: AWSRegions.EU_CENTRAL_1, + path: 'folder/test', + }; + const imageLabelBinaryJobDto: JobCvatDto = { chainId: MOCK_CHAIN_ID, - data: storageDataMock, + data: storageDatasetMock, labels: MOCK_CVAT_LABELS, requesterDescription: MOCK_REQUESTER_DESCRIPTION, minQuality: 0.95, fundAmount: 10, - groundTruth: storageDataMock, + groundTruth: storageGtMock, userGuide: MOCK_FILE_URL, type: JobRequestType.IMAGE_POINTS, }; diff --git a/packages/apps/job-launcher/server/src/modules/job/job.service.ts b/packages/apps/job-launcher/server/src/modules/job/job.service.ts index c2153e23ff..28294c4660 100644 --- a/packages/apps/job-launcher/server/src/modules/job/job.service.ts +++ b/packages/apps/job-launcher/server/src/modules/job/job.service.ts @@ -113,12 +113,15 @@ import { WebhookDataDto } from '../webhook/webhook.dto'; import * as crypto from 'crypto'; import { PaymentEntity } from '../payment/payment.entity'; import { - DatasetAction, + ManifestAction, EscrowAction, OracleAction, OracleAddresses, RequestAction, CvatCalculateJobBounty, + CvatImageData, + CvatAnnotationData, + GenerateUrls, } from './job.interface'; import { WebhookEntity } from '../webhook/webhook.entity'; import { WebhookRepository } from '../webhook/webhook.repository'; @@ -151,24 +154,26 @@ export class JobService { requestType: JobRequestType, tokenFundAmount: number, ): Promise { - const { getElementsCount } = this.createDatasetActions[requestType]; + const { generateUrls } = this.createManifestActions[requestType]; + + const urls = generateUrls(dto.data, dto.groundTruth); - const elementsCount = await getElementsCount(requestType, dto.data); const jobBounty = await this.calculateJobBounty({ requestType, - elementsCount, fundAmount: tokenFundAmount, + data: dto.data, + gtUrl: urls.gtUrl, nodesTotal: dto.labels[0]?.nodes?.length, }); return { data: { - data_url: generateBucketUrl(dto.data.dataset, requestType), + data_url: urls.dataUrl, ...(dto.data.points && { - points_url: generateBucketUrl(dto.data.points, requestType), + points_url: urls.pointsUrl, }), ...(dto.data.boxes && { - boxes_url: generateBucketUrl(dto.data.boxes, requestType), + boxes_url: urls.boxesUrl, }), }, annotation: { @@ -181,7 +186,7 @@ export class JobService { validation: { min_quality: dto.minQuality, val_size: this.cvatConfigService.valSize, - gt_url: generateBucketUrl(dto.groundTruth, requestType), + gt_url: urls.gtUrl, }, job_bounty: jobBounty, }; @@ -506,36 +511,94 @@ export class JobService { }, }; - private createDatasetActions: Record = { + private createManifestActions: Record = { [JobRequestType.HCAPTCHA]: { getElementsCount: async () => 0, + generateUrls: () => ({ dataUrl: '', gtUrl: '' }), }, [JobRequestType.FORTUNE]: { getElementsCount: async () => 0, + generateUrls: () => ({ dataUrl: '', gtUrl: '' }), }, [JobRequestType.IMAGE_BOXES]: { getElementsCount: async ( requestType: JobRequestType, data: CvatDataDto, ) => (await listObjectsInBucket(data.dataset, requestType)).length, + generateUrls: ( + data: CvatDataDto, + groundTruth: StorageDataDto, + ): GenerateUrls => { + const requestType = JobRequestType.IMAGE_BOXES; + + return { + dataUrl: generateBucketUrl(data.dataset, requestType), + gtUrl: generateBucketUrl(groundTruth, requestType), + }; + }, }, [JobRequestType.IMAGE_POINTS]: { getElementsCount: async ( requestType: JobRequestType, data: CvatDataDto, ) => (await listObjectsInBucket(data.dataset, requestType)).length, + generateUrls: ( + data: CvatDataDto, + groundTruth: StorageDataDto, + ): GenerateUrls => { + const requestType = JobRequestType.IMAGE_POINTS; + + return { + dataUrl: generateBucketUrl(data.dataset, requestType), + gtUrl: generateBucketUrl(groundTruth, requestType), + }; + }, }, [JobRequestType.IMAGE_BOXES_FROM_POINTS]: { getElementsCount: async ( requestType: JobRequestType, data: CvatDataDto, - ) => this.getCvatElementsCount(requestType, data.points!), + gtUrl: string, + ) => this.getCvatElementsCount(requestType, data.points!, gtUrl), + generateUrls: ( + data: CvatDataDto, + groundTruth: StorageDataDto, + ): GenerateUrls => { + if (!data.points) { + throw new ConflictException(ErrorJob.DataNotExist); + } + + const requestType = JobRequestType.IMAGE_BOXES_FROM_POINTS; + + return { + dataUrl: generateBucketUrl(data.dataset, requestType), + gtUrl: generateBucketUrl(groundTruth, requestType), + pointsUrl: generateBucketUrl(data.points, requestType), + }; + }, }, [JobRequestType.IMAGE_SKELETONS_FROM_BOXES]: { getElementsCount: async ( requestType: JobRequestType, data: CvatDataDto, - ) => this.getCvatElementsCount(requestType, data.boxes!), + gtUrl: string, + ) => this.getCvatElementsCount(requestType, data.boxes!, gtUrl), + generateUrls: ( + data: CvatDataDto, + groundTruth: StorageDataDto, + ): GenerateUrls => { + if (!data.boxes) { + throw new ConflictException(ErrorJob.DataNotExist); + } + + const requestType = JobRequestType.IMAGE_SKELETONS_FROM_BOXES; + + return { + dataUrl: generateBucketUrl(data.dataset, requestType), + gtUrl: generateBucketUrl(groundTruth, requestType), + boxesUrl: generateBucketUrl(data.boxes, requestType), + }; + }, }, }; @@ -713,22 +776,43 @@ export class JobService { public async getCvatElementsCount( requestType: JobRequestType, storageData: StorageDataDto, + gtUrl: string, ): Promise { if (!storageData) { throw new ConflictException(ErrorJob.DataNotExist); } - return ( - await this.storageService.download( - generateBucketUrl(storageData, requestType), - ) - ).annotations?.length; + const data = await this.storageService.download( + generateBucketUrl(storageData, requestType), + ); + const gt = await this.storageService.download(gtUrl); + + let gtEntries = 0; + + gt.images.forEach((gtImage: CvatImageData) => { + const { id } = data.images.find( + (dataImage: CvatImageData) => dataImage.file_name === gtImage.file_name, + ); + + if (id) { + const matchingAnnotations = data.annotations.filter( + (dataAnnotation: CvatAnnotationData) => + dataAnnotation.image_id === id, + ); + gtEntries += matchingAnnotations.length; + } + }); + + return data.annotations.length - gtEntries; } public async calculateJobBounty( - data: CvatCalculateJobBounty, + params: CvatCalculateJobBounty, ): Promise { - const { requestType, elementsCount, fundAmount, nodesTotal } = data; + const { requestType, fundAmount, data, gtUrl, nodesTotal } = params; + + const { getElementsCount } = this.createManifestActions[requestType]; + const elementsCount = await getElementsCount(requestType, data, gtUrl); let jobSize = Number(this.cvatConfigService.jobSize); diff --git a/packages/apps/job-launcher/server/test/constants.ts b/packages/apps/job-launcher/server/test/constants.ts index 3c25edbcfd..c98ef56412 100644 --- a/packages/apps/job-launcher/server/test/constants.ts +++ b/packages/apps/job-launcher/server/test/constants.ts @@ -155,3 +155,59 @@ export const MOCK_CVAT_LABELS_WITH_NODES: Label[] = [ export const MOCK_BUCKET_FILE = 'https://bucket.s3.eu-central-1.amazonaws.com/folder/test'; + +export const MOCK_CVAT_DATA = { + images: [ + { + id: 1, + file_name: '1.jpg', + }, + { + id: 2, + file_name: '2.jpg', + }, + { + id: 3, + file_name: '3.jpg', + }, + { + id: 4, + file_name: '4.jpg', + }, + { + id: 5, + file_name: '5.jpg', + }, + ], + annotations: [ + { + image_id: 1, + }, + { + image_id: 2, + }, + { + image_id: 3, + }, + { + image_id: 4, + }, + { + image_id: 5, + }, + ], +}; + +export const MOCK_CVAT_GT = { + images: [ + { + file_name: '1.jpg', + }, + { + file_name: '2.jpg', + }, + { + file_name: '3.jpg', + }, + ], +}; From 5fa9f9e80df074bc62b1908a4a45b43d03314c15 Mon Sep 17 00:00:00 2001 From: Eric Lee <98655210+leric7@users.noreply.github.com> Date: Wed, 3 Apr 2024 07:38:19 -0400 Subject: [PATCH 31/66] remove crypto package dependency (#1809) * remove crypto package dependency * update yarn lock * fix recording oracle storage service --- .../recording-oracle/src/modules/storage/storage.service.ts | 2 +- packages/sdk/typescript/human-protocol-sdk/package.json | 1 - yarn.lock | 5 ----- 3 files changed, 1 insertion(+), 7 deletions(-) diff --git a/packages/apps/fortune/recording-oracle/src/modules/storage/storage.service.ts b/packages/apps/fortune/recording-oracle/src/modules/storage/storage.service.ts index 84feeca176..c37ea24599 100644 --- a/packages/apps/fortune/recording-oracle/src/modules/storage/storage.service.ts +++ b/packages/apps/fortune/recording-oracle/src/modules/storage/storage.service.ts @@ -87,7 +87,7 @@ export class StorageService { } let fileToUpload = JSON.stringify(solutions); - if (this.serverConfig.pgpEncrypt === 'true') { + if (this.serverConfig.pgpEncrypt.toString() === 'true') { try { const signer = this.web3Service.getSigner(chainId); const escrowClient = await EscrowClient.build(signer); diff --git a/packages/sdk/typescript/human-protocol-sdk/package.json b/packages/sdk/typescript/human-protocol-sdk/package.json index 1d2550bc11..3b3f3651a7 100644 --- a/packages/sdk/typescript/human-protocol-sdk/package.json +++ b/packages/sdk/typescript/human-protocol-sdk/package.json @@ -42,7 +42,6 @@ "@human-protocol/core": "*", "aws-sdk": "^2.1528.0", "axios": "^1.4.0", - "crypto": "^1.0.1", "graphql": "^16.8.1", "graphql-request": "^6.1.0", "graphql-tag": "^2.12.6", diff --git a/yarn.lock b/yarn.lock index cd6864dd34..95c7bfc354 100644 --- a/yarn.lock +++ b/yarn.lock @@ -10882,11 +10882,6 @@ crypto-random-string@^2.0.0: resolved "https://registry.yarnpkg.com/crypto-random-string/-/crypto-random-string-2.0.0.tgz#ef2a7a966ec11083388369baa02ebead229b30d5" integrity sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA== -crypto@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/crypto/-/crypto-1.0.1.tgz#2af1b7cad8175d24c8a1b0778255794a21803037" - integrity sha512-VxBKmeNcqQdiUQUW2Tzq0t377b54N2bMtXO/qiLa+6eRRmmC4qT3D4OnTGoT/U6O9aklQ/jTwbOtRMTTY8G0Ig== - css-color-keywords@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/css-color-keywords/-/css-color-keywords-1.0.0.tgz#fea2616dc676b2962686b3af8dbdbe180b244e05" From 8a610b0d2032404c6c47cc7b3c14fdeb50e9b9f1 Mon Sep 17 00:00:00 2001 From: Maxim Zhiltsov Date: Wed, 3 Apr 2024 18:28:50 +0300 Subject: [PATCH 32/66] [CVAT-M2] Add more logging to recording oracle (#1813) * Add more logs to recording oracle * fix default echo value --- .../examples/cvat/recording-oracle/src/db/__init__.py | 3 +++ .../src/handlers/process_intermediate_results.py | 10 ++++++++++ .../cvat/recording-oracle/src/handlers/validation.py | 3 +-- 3 files changed, 14 insertions(+), 2 deletions(-) diff --git a/packages/examples/cvat/recording-oracle/src/db/__init__.py b/packages/examples/cvat/recording-oracle/src/db/__init__.py index f9fa30dabb..e212da4a47 100644 --- a/packages/examples/cvat/recording-oracle/src/db/__init__.py +++ b/packages/examples/cvat/recording-oracle/src/db/__init__.py @@ -1,3 +1,5 @@ +import logging + import sqlalchemy from sqlalchemy.orm import declarative_base, sessionmaker @@ -6,6 +8,7 @@ DATABASE_URL = Config.postgres_config.connection_url() engine = sqlalchemy.create_engine( DATABASE_URL, + echo="debug" if Config.loglevel <= logging.DEBUG else False, connect_args={"options": "-c lock_timeout={:d}".format(Config.postgres_config.lock_timeout)}, ) SessionLocal = sessionmaker(autocommit=False, bind=engine) diff --git a/packages/examples/cvat/recording-oracle/src/handlers/process_intermediate_results.py b/packages/examples/cvat/recording-oracle/src/handlers/process_intermediate_results.py index f0eb281817..a139d2ddad 100644 --- a/packages/examples/cvat/recording-oracle/src/handlers/process_intermediate_results.py +++ b/packages/examples/cvat/recording-oracle/src/handlers/process_intermediate_results.py @@ -892,6 +892,11 @@ def process_intermediate_results( task_id = db_service.create_task(session, escrow_address=escrow_address, chain_id=chain_id) task = db_service.get_task_by_id(session, task_id, for_update=True) + if logger.isEnabledFor(logging.DEBUG): + logger.debug("process_intermediate_results for escrow %s", escrow_address) + logger.debug("Task id %s", task_id) + logger.debug("Task %s %s", task, getattr(task, "__dict__", None)) + initial_gt_stats = { gt_image_stat.gt_key: gt_image_stat.failed_attempts for gt_image_stat in db_service.get_task_gt_stats(session, task.id) @@ -912,6 +917,7 @@ def process_intermediate_results( updated_merged_dataset_archive = validation_result.updated_merged_dataset if logger.isEnabledFor(logging.DEBUG): + logger.debug("Validation results %s", validation_result) logger.debug( "Task validation results for escrow_address=%s: %s", escrow_address, @@ -922,6 +928,10 @@ def process_intermediate_results( updated_gt_stats = _compute_gt_stats_update( initial_gt_stats, validation_result.updated_gt_stats ) + + if logger.isEnabledFor(logging.DEBUG): + logger.debug("Updating GT stats: %s", updated_gt_stats) + db_service.update_gt_stats(session, task.id, updated_gt_stats) job_final_result_ids: Dict[int, str] = {} diff --git a/packages/examples/cvat/recording-oracle/src/handlers/validation.py b/packages/examples/cvat/recording-oracle/src/handlers/validation.py index b46c3ff13e..e398e91a6f 100644 --- a/packages/examples/cvat/recording-oracle/src/handlers/validation.py +++ b/packages/examples/cvat/recording-oracle/src/handlers/validation.py @@ -11,7 +11,7 @@ import src.core.validation_meta as validation import src.services.webhook as oracle_db_service from src.core.config import Config -from src.core.manifest import TaskManifest +from src.core.manifest import TaskManifest, parse_manifest from src.core.oracle_events import ( RecordingOracleEvent_TaskCompleted, RecordingOracleEvent_TaskRejected, @@ -30,7 +30,6 @@ from src.log import ROOT_LOGGER_NAME from src.services.cloud import make_client as make_cloud_client from src.services.cloud.utils import BucketAccessInfo -from src.core.manifest import parse_manifest from src.utils.assignments import compute_resulting_annotations_hash from src.utils.logging import NullLogger, get_function_logger From dddc76057ab56b11d1f1af175e4daf117f41e127 Mon Sep 17 00:00:00 2001 From: Maxim Zhiltsov Date: Wed, 3 Apr 2024 20:27:51 +0300 Subject: [PATCH 33/66] [CVAT-M2] Fix validation when no GT stats to be updated (#1815) * Fix recording oracle failure when no gt stats to be updated * Add trace loglevel for db queries logging --- packages/examples/cvat/exchange-oracle/src/db/__init__.py | 2 ++ packages/examples/cvat/exchange-oracle/src/utils/logging.py | 4 ++++ packages/examples/cvat/recording-oracle/src/db/__init__.py | 5 ++--- .../cvat/recording-oracle/src/services/validation.py | 3 +++ packages/examples/cvat/recording-oracle/src/utils/logging.py | 4 ++++ 5 files changed, 15 insertions(+), 3 deletions(-) diff --git a/packages/examples/cvat/exchange-oracle/src/db/__init__.py b/packages/examples/cvat/exchange-oracle/src/db/__init__.py index f9fa30dabb..6e9c85cded 100644 --- a/packages/examples/cvat/exchange-oracle/src/db/__init__.py +++ b/packages/examples/cvat/exchange-oracle/src/db/__init__.py @@ -1,11 +1,13 @@ import sqlalchemy from sqlalchemy.orm import declarative_base, sessionmaker +import src.utils.logging from src.core.config import Config DATABASE_URL = Config.postgres_config.connection_url() engine = sqlalchemy.create_engine( DATABASE_URL, + echo="debug" if Config.loglevel <= src.utils.logging.TRACE else False, connect_args={"options": "-c lock_timeout={:d}".format(Config.postgres_config.lock_timeout)}, ) SessionLocal = sessionmaker(autocommit=False, bind=engine) diff --git a/packages/examples/cvat/exchange-oracle/src/utils/logging.py b/packages/examples/cvat/exchange-oracle/src/utils/logging.py index 146f6c219b..be2c5feba3 100644 --- a/packages/examples/cvat/exchange-oracle/src/utils/logging.py +++ b/packages/examples/cvat/exchange-oracle/src/utils/logging.py @@ -6,6 +6,10 @@ LogLevel = NewType("LogLevel", int) +TRACE = 5 +logging.addLevelName(TRACE, "TRACE") + + def parse_log_level(level: str) -> LogLevel: return logging._nameToLevel[level.upper()] diff --git a/packages/examples/cvat/recording-oracle/src/db/__init__.py b/packages/examples/cvat/recording-oracle/src/db/__init__.py index e212da4a47..6e9c85cded 100644 --- a/packages/examples/cvat/recording-oracle/src/db/__init__.py +++ b/packages/examples/cvat/recording-oracle/src/db/__init__.py @@ -1,14 +1,13 @@ -import logging - import sqlalchemy from sqlalchemy.orm import declarative_base, sessionmaker +import src.utils.logging from src.core.config import Config DATABASE_URL = Config.postgres_config.connection_url() engine = sqlalchemy.create_engine( DATABASE_URL, - echo="debug" if Config.loglevel <= logging.DEBUG else False, + echo="debug" if Config.loglevel <= src.utils.logging.TRACE else False, connect_args={"options": "-c lock_timeout={:d}".format(Config.postgres_config.lock_timeout)}, ) SessionLocal = sessionmaker(autocommit=False, bind=engine) diff --git a/packages/examples/cvat/recording-oracle/src/services/validation.py b/packages/examples/cvat/recording-oracle/src/services/validation.py index f4a054e7fc..84146ca806 100644 --- a/packages/examples/cvat/recording-oracle/src/services/validation.py +++ b/packages/examples/cvat/recording-oracle/src/services/validation.py @@ -116,6 +116,9 @@ def update_gt_stats(session: Session, task_id: str, values: Dict[str, int]): # Read more about upsert: # https://docs.sqlalchemy.org/en/20/orm/queryguide/dml.html#orm-upsert-statements + if not values: + return + if db_engine.driver != "psycopg2": raise NotImplementedError diff --git a/packages/examples/cvat/recording-oracle/src/utils/logging.py b/packages/examples/cvat/recording-oracle/src/utils/logging.py index 146f6c219b..be2c5feba3 100644 --- a/packages/examples/cvat/recording-oracle/src/utils/logging.py +++ b/packages/examples/cvat/recording-oracle/src/utils/logging.py @@ -6,6 +6,10 @@ LogLevel = NewType("LogLevel", int) +TRACE = 5 +logging.addLevelName(TRACE, "TRACE") + + def parse_log_level(level: str) -> LogLevel: return logging._nameToLevel[level.upper()] From c40e247d2eb00f994f17050189271bbd7cfd417a Mon Sep 17 00:00:00 2001 From: portuu3 <61605646+portuu3@users.noreply.github.com> Date: Thu, 4 Apr 2024 09:52:07 +0200 Subject: [PATCH 34/66] Reputation Oracle - add hcaptcha token to signin dto (#1816) add hcaptcha token to sign in --- .../reputation-oracle/server/src/modules/auth/auth.dto.ts | 4 ++++ .../server/src/modules/auth/auth.service.spec.ts | 2 ++ packages/apps/reputation-oracle/server/test/constants.ts | 2 ++ 3 files changed, 8 insertions(+) diff --git a/packages/apps/reputation-oracle/server/src/modules/auth/auth.dto.ts b/packages/apps/reputation-oracle/server/src/modules/auth/auth.dto.ts index 581d62fac1..611d9aecdb 100644 --- a/packages/apps/reputation-oracle/server/src/modules/auth/auth.dto.ts +++ b/packages/apps/reputation-oracle/server/src/modules/auth/auth.dto.ts @@ -29,6 +29,10 @@ export class SignInDto { @ApiProperty() @IsString() public password: string; + + @ApiProperty({ name: 'h_captcha_token' }) + @IsString() + public hCaptchaToken: string; } export class RefreshDto { diff --git a/packages/apps/reputation-oracle/server/src/modules/auth/auth.service.spec.ts b/packages/apps/reputation-oracle/server/src/modules/auth/auth.service.spec.ts index 3a34e07bdb..a7aa3ae7d5 100644 --- a/packages/apps/reputation-oracle/server/src/modules/auth/auth.service.spec.ts +++ b/packages/apps/reputation-oracle/server/src/modules/auth/auth.service.spec.ts @@ -15,6 +15,7 @@ import { MOCK_EMAIL, MOCK_EXPIRES_IN, MOCK_HASHED_PASSWORD, + MOCK_HCAPTCHA_TOKEN, MOCK_PASSWORD, MOCK_PRIVATE_KEY, MOCK_REFRESH_TOKEN, @@ -117,6 +118,7 @@ describe('AuthService', () => { const signInDto = { email: MOCK_EMAIL, password: MOCK_PASSWORD, + hCaptchaToken: MOCK_HCAPTCHA_TOKEN, }; const userEntity: Partial = { diff --git a/packages/apps/reputation-oracle/server/test/constants.ts b/packages/apps/reputation-oracle/server/test/constants.ts index 29c8f80b23..3cd1d34d26 100644 --- a/packages/apps/reputation-oracle/server/test/constants.ts +++ b/packages/apps/reputation-oracle/server/test/constants.ts @@ -232,3 +232,5 @@ Fx3dwWk9YaZ4lQD+MHnMYu48TwdE4ZKNcNUaOmWLBbZTgedqqHGLXbiyZAg= export const MOCK_MAX_RETRY_COUNT = 5; export const MOCK_BUCKET_FILE = 'https://bucket.s3.eu-central-1.amazonaws.com/folder/test'; + +export const MOCK_HCAPTCHA_TOKEN = 'test-token'; From 9c53c7da6a6c30a374da049af2175b1e3e20bffd Mon Sep 17 00:00:00 2001 From: portuu3 <61605646+portuu3@users.noreply.github.com> Date: Thu, 4 Apr 2024 12:34:20 +0200 Subject: [PATCH 35/66] Update changelog (#1820) --- docs/sdk/changelog.md | 6 ------ 1 file changed, 6 deletions(-) diff --git a/docs/sdk/changelog.md b/docs/sdk/changelog.md index 9a808f1102..ce35d69a83 100644 --- a/docs/sdk/changelog.md +++ b/docs/sdk/changelog.md @@ -2,14 +2,8 @@ ### Added -- **Add function to get the encryption public key:** get public key function added to KVStore client. - ### Changed -- **Rename set url function of KVStore client:** function renamed to be descriptive. This function sets the url and the hash -- **Rename get url function of KVStore client:** function renamed to be descriptive. This function gets the url and verifies the hash of the content. -- **Convert operators config keys to snake_case:** use snake_case as standard. - ### Deprecated ### Removed From 825ca33d4769397d1be7a3a697e7d55b13d7dbbc Mon Sep 17 00:00:00 2001 From: portuu3 <61605646+portuu3@users.noreply.github.com> Date: Thu, 4 Apr 2024 13:08:24 +0200 Subject: [PATCH 36/66] dependency review update (#1821) --- .github/workflows/ci-dependency-review.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci-dependency-review.yaml b/.github/workflows/ci-dependency-review.yaml index 8caf799d12..f6af4500f8 100644 --- a/.github/workflows/ci-dependency-review.yaml +++ b/.github/workflows/ci-dependency-review.yaml @@ -9,6 +9,6 @@ jobs: runs-on: ubuntu-latest steps: - name: "Checkout Repository" - uses: actions/checkout@v4 + uses: actions/checkout@v4.1.1 - name: "Dependency Review" - uses: actions/dependency-review-action@v4 + uses: actions/dependency-review-action@v4.2.5 From 1a3c01459256de690797ae2573403aca0e403bcb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Francisco=20L=C3=B3pez?= <50665615+flopez7@users.noreply.github.com> Date: Thu, 4 Apr 2024 13:19:15 +0200 Subject: [PATCH 37/66] Convert all addresses to lowercase when calling subgraph (#1822) --- .../human_protocol_sdk/agreement/measures.py | 12 +-- .../human_protocol_sdk/gql/escrow.py | 4 +- .../human_protocol_sdk/gql/hmtoken.py | 4 +- .../human_protocol_sdk/gql/reward.py | 4 +- .../human_protocol_sdk/gql/statistics.py | 8 +- .../operator/operator_utils.py | 15 +++- .../operator/test_operator_utils.py | 85 +++++++++++++++++++ 7 files changed, 107 insertions(+), 25 deletions(-) diff --git a/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/agreement/measures.py b/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/agreement/measures.py index b0ccf3a7a0..a4b938ca89 100644 --- a/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/agreement/measures.py +++ b/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/agreement/measures.py @@ -185,13 +185,11 @@ def _percentage_from_label_counts(label_counts): max_item_agreements = (n_raters * (n_raters - 1)).sum() if max_item_agreements == 0: - warn( - """ + warn(""" All annotations were made by a single annotator, check your data to ensure this is not an error. Returning 1.0 - """ - ) + """) return 1.0 return item_agreements / max_item_agreements @@ -199,13 +197,11 @@ def _percentage_from_label_counts(label_counts): def _kappa(agreement_observed, agreement_expected): if agreement_expected == 1.0: - warn( - """ + warn(""" Annotations contained only a single value, check your data to ensure this is not an error. Returning 1.0. - """ - ) + """) return 1.0 return (agreement_observed - agreement_expected) / (1 - agreement_expected) diff --git a/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/gql/escrow.py b/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/gql/escrow.py index 30e12f382b..168c0dbca9 100644 --- a/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/gql/escrow.py +++ b/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/gql/escrow.py @@ -87,6 +87,4 @@ def get_escrow_query(): }} }} {escrow_fragment} -""".format( - escrow_fragment=escrow_fragment - ) +""".format(escrow_fragment=escrow_fragment) diff --git a/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/gql/hmtoken.py b/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/gql/hmtoken.py index a9bd32c28e..04f2096ad7 100644 --- a/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/gql/hmtoken.py +++ b/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/gql/hmtoken.py @@ -12,6 +12,4 @@ }} }} {holder_fragment} -""".format( - holder_fragment=holder_fragment -) +""".format(holder_fragment=holder_fragment) diff --git a/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/gql/reward.py b/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/gql/reward.py index 3f43bbff9e..09c06278ae 100644 --- a/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/gql/reward.py +++ b/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/gql/reward.py @@ -14,6 +14,4 @@ }} }} {reward_added_event_fragment} -""".format( - reward_added_event_fragment=reward_added_event_fragment -) +""".format(reward_added_event_fragment=reward_added_event_fragment) diff --git a/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/gql/statistics.py b/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/gql/statistics.py index 74da8d6815..4e787baa9a 100644 --- a/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/gql/statistics.py +++ b/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/gql/statistics.py @@ -56,9 +56,7 @@ }} }} {hmtoken_statistics_fragment} -""".format( - hmtoken_statistics_fragment=hmtoken_statistics_fragment -) +""".format(hmtoken_statistics_fragment=hmtoken_statistics_fragment) get_escrow_statistics_query = """ query GetEscrowStatistics {{ @@ -67,9 +65,7 @@ }} }} {escrow_statistics_fragment} -""".format( - escrow_statistics_fragment=escrow_statistics_fragment -) +""".format(escrow_statistics_fragment=escrow_statistics_fragment) def get_event_day_data_query(param: StatisticsParam): diff --git a/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/operator/operator_utils.py b/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/operator/operator_utils.py index 0205ac5ea4..fea376522e 100644 --- a/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/operator/operator_utils.py +++ b/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/operator/operator_utils.py @@ -205,6 +205,9 @@ def get_leaders( ) leaders_raw = leaders_data["data"]["leaders"] + if not leaders_raw: + continue + leaders.extend( [ LeaderData( @@ -271,7 +274,7 @@ def get_leader( leader_data = get_data_from_subgraph( network["subgraph_url"], query=get_leader_query, - params={"address": leader_address}, + params={"address": leader_address.lower()}, ) leader = leader_data["data"]["leader"] @@ -337,8 +340,12 @@ def get_reputation_network_operators( reputation_network_data = get_data_from_subgraph( network["subgraph_url"], query=get_reputation_network_query(role), - params={"address": address, "role": role}, + params={"address": address.lower(), "role": role}, ) + + if not reputation_network_data["data"]["reputationNetwork"]: + return [] + operators = reputation_network_data["data"]["reputationNetwork"]["operators"] return [ @@ -383,6 +390,10 @@ def get_rewards_info(chain_id: ChainId, slasher: str) -> List[RewardData]: query=get_reward_added_events_query, params={"slasherAddress": slasher.lower()}, ) + + if not reward_added_events_data["data"]["rewardAddedEvents"]: + return [] + reward_added_events = reward_added_events_data["data"]["rewardAddedEvents"] return [ diff --git a/packages/sdk/python/human-protocol-sdk/test/human_protocol_sdk/operator/test_operator_utils.py b/packages/sdk/python/human-protocol-sdk/test/human_protocol_sdk/operator/test_operator_utils.py index 31d96c0b70..1e2adbd2bf 100644 --- a/packages/sdk/python/human-protocol-sdk/test/human_protocol_sdk/operator/test_operator_utils.py +++ b/packages/sdk/python/human-protocol-sdk/test/human_protocol_sdk/operator/test_operator_utils.py @@ -73,6 +73,31 @@ def test_get_leaders(self): self.assertEqual(leaders[0].webhook_url, None) self.assertEqual(leaders[0].url, None) + def test_get_leaders_empty_data(self): + filter = LeaderFilter(networks=[ChainId.POLYGON], role="role") + mock_function = MagicMock() + + with patch( + "human_protocol_sdk.operator.operator_utils.get_data_from_subgraph" + ) as mock_function: + mock_function.side_effect = [ + { + "data": { + "leaders": None, + } + } + ] + + leaders = OperatorUtils.get_leaders(filter) + + mock_function.assert_any_call( + NETWORKS[ChainId.POLYGON]["subgraph_url"], + query=get_leaders_query(filter), + params={"role": filter.role}, + ) + + self.assertEqual(leaders, []) + def test_get_leader(self): staker_address = "0x1234567890123456789012345678901234567891" @@ -132,6 +157,26 @@ def test_get_leader(self): self.assertEqual(leader.webhook_url, None) self.assertEqual(leader.url, None) + def test_get_leader_empty_data(self): + staker_address = "0x1234567890123456789012345678901234567891" + + mock_function = MagicMock() + + with patch( + "human_protocol_sdk.operator.operator_utils.get_data_from_subgraph" + ) as mock_function: + mock_function.side_effect = [{"data": {"leader": None}}] + + leader = OperatorUtils.get_leader(ChainId.POLYGON, staker_address) + + mock_function.assert_any_call( + NETWORKS[ChainId.POLYGON]["subgraph_url"], + query=get_leader_query, + params={"address": staker_address}, + ) + + self.assertEqual(leader, None) + def test_get_reputation_network_operators(self): reputation_address = "0x1234567890123456789012345678901234567891" operator_address = "0x1234567890123456789012345678901234567891" @@ -168,6 +213,28 @@ def test_get_reputation_network_operators(self): self.assertEqual(operators[0].address, operator_address) self.assertEqual(operators[0].role, role) + def test_get_reputation_network_operators_empty_data(self): + reputation_address = "0x1234567890123456789012345678901234567891" + + mock_function = MagicMock() + + with patch( + "human_protocol_sdk.operator.operator_utils.get_data_from_subgraph" + ) as mock_function: + mock_function.side_effect = [{"data": {"reputationNetwork": None}}] + + operators = OperatorUtils.get_reputation_network_operators( + ChainId.POLYGON, reputation_address + ) + + mock_function.assert_any_call( + NETWORKS[ChainId.POLYGON]["subgraph_url"], + query=get_reputation_network_query(None), + params={"address": reputation_address, "role": None}, + ) + + self.assertEqual(operators, []) + def test_get_rewards_info(self): slasher = "0x1234567890123456789012345678901234567891" @@ -203,6 +270,24 @@ def test_get_rewards_info(self): self.assertEqual(rewards_info[1].escrow_address.lower(), "escrow2") self.assertEqual(rewards_info[1].amount, 20) + def test_get_rewards_info_empty_data(self): + slasher = "0x1234567890123456789012345678901234567891" + + mock_function = MagicMock() + with patch( + "human_protocol_sdk.operator.operator_utils.get_data_from_subgraph" + ) as mock_function: + mock_function.return_value = {"data": {"rewardAddedEvents": None}} + rewards_info = OperatorUtils.get_rewards_info(ChainId.POLYGON, slasher) + + mock_function.assert_called_once_with( + NETWORKS[ChainId.POLYGON]["subgraph_url"], + query=get_reward_added_events_query, + params={"slasherAddress": slasher}, + ) + + self.assertEqual(rewards_info, []) + if __name__ == "__main__": unittest.main(exit=True) From 8fa8da45558a1e058605dee177823aff3536eb63 Mon Sep 17 00:00:00 2001 From: portuu3 <61605646+portuu3@users.noreply.github.com> Date: Thu, 4 Apr 2024 13:59:43 +0200 Subject: [PATCH 38/66] fix dependency review (#1823) --- package.json | 3 +- .../apps/job-launcher/server/package.json | 2 +- yarn.lock | 1363 ++++++++--------- 3 files changed, 638 insertions(+), 730 deletions(-) diff --git a/package.json b/package.json index e65f2861c5..378c98143c 100644 --- a/package.json +++ b/package.json @@ -73,6 +73,7 @@ "node-fetch": "^2.6.7", "node-forge": "^1.0.0", "qrcode": "^1.5.0", - "semver": "^7.5.2" + "semver": "^7.5.2", + "undici": "^6.11.1" } } \ No newline at end of file diff --git a/packages/apps/job-launcher/server/package.json b/packages/apps/job-launcher/server/package.json index ec01f60a4e..a444cf2218 100644 --- a/packages/apps/job-launcher/server/package.json +++ b/packages/apps/job-launcher/server/package.json @@ -78,7 +78,7 @@ "@types/zxcvbn": "4.4.1", "@typescript-eslint/eslint-plugin": "^5.0.0", "@typescript-eslint/parser": "^5.0.0", - "@vercel/node": "^3.0.24", + "@vercel/node": "^3.0.26", "eslint": "^8.55.0", "eslint-config-prettier": "^9.1.0", "eslint-plugin-prettier": "^5.1.3", diff --git a/yarn.lock b/yarn.lock index 95c7bfc354..3a19466d95 100644 --- a/yarn.lock +++ b/yarn.lock @@ -65,9 +65,9 @@ rxjs "7.8.1" "@apollo/client@^3.7.12": - version "3.9.7" - resolved "https://registry.yarnpkg.com/@apollo/client/-/client-3.9.7.tgz#e2b6f2d0240a6420753fb658a021dfd0637f2a56" - integrity sha512-OEEwt55bkFhqihCT5d75KUxZt50JZ9MuIYwG7VZlyPPIAb9K+qzVWlXWlf3tB5DaV43yXkUSLQfNpdIBFOB55Q== + version "3.9.10" + resolved "https://registry.yarnpkg.com/@apollo/client/-/client-3.9.10.tgz#f381f67f3559cb5f5b66ce9183f84f49616acbe4" + integrity sha512-w8i/Lk1P0vvWZF0Xb00XPonn79/0rgRJ1vopBlVudVuy9QP29/NZXK0rI2xJIN6VrKuEqJZaVGJC+7k23I2sfA== dependencies: "@graphql-typed-document-node/core" "^3.1.1" "@wry/caches" "^1.0.0" @@ -144,7 +144,7 @@ dependencies: tslib "^2.3.1" -"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.10.4", "@babel/code-frame@^7.12.13", "@babel/code-frame@^7.16.7", "@babel/code-frame@^7.18.6", "@babel/code-frame@^7.23.5", "@babel/code-frame@^7.24.1": +"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.10.4", "@babel/code-frame@^7.12.13", "@babel/code-frame@^7.16.7", "@babel/code-frame@^7.18.6", "@babel/code-frame@^7.23.5", "@babel/code-frame@^7.24.1", "@babel/code-frame@^7.24.2": version "7.24.2" resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.24.2.tgz#718b4b19841809a58b29b68cde80bc5e1aa6d9ae" integrity sha512-y5+tLQyV8pg3fsiln67BVLD1P13Eg4lh5RW9mF0zUuvLrv9uIQ4MCL+CRT+FTsBlBjcIan6PGsLcBN0m3ClUyQ== @@ -152,23 +152,23 @@ "@babel/highlight" "^7.24.2" picocolors "^1.0.0" -"@babel/compat-data@^7.22.6", "@babel/compat-data@^7.23.5", "@babel/compat-data@^7.24.1": - version "7.24.1" - resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.24.1.tgz#31c1f66435f2a9c329bb5716a6d6186c516c3742" - integrity sha512-Pc65opHDliVpRHuKfzI+gSA4zcgr65O4cl64fFJIWEEh8JoHIHh0Oez1Eo8Arz8zq/JhgKodQaxEwUPRtZylVA== +"@babel/compat-data@^7.22.6", "@babel/compat-data@^7.23.5", "@babel/compat-data@^7.24.4": + version "7.24.4" + resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.24.4.tgz#6f102372e9094f25d908ca0d34fc74c74606059a" + integrity sha512-vg8Gih2MLK+kOkHJp4gBEIkyaIi00jgWot2D9QOmmfLC8jINSOzmCLta6Bvz/JSBCqnegV0L80jhxkol5GWNfQ== "@babel/core@^7.11.6", "@babel/core@^7.12.3", "@babel/core@^7.16.0", "@babel/core@^7.22.20", "@babel/core@^7.23.5", "@babel/core@^7.23.9": - version "7.24.1" - resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.24.1.tgz#b802f931b6498dcb8fed5a4710881a45abbc2784" - integrity sha512-F82udohVyIgGAY2VVj/g34TpFUG606rumIHjTfVbssPg2zTR7PuuEpZcX8JA6sgBfIYmJrFtWgPvHQuJamVqZQ== + version "7.24.4" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.24.4.tgz#1f758428e88e0d8c563874741bc4ffc4f71a4717" + integrity sha512-MBVlMXP+kkl5394RBLSxxk/iLTeVGuXTV3cIDXavPpMMqnSnt6apKgan/U8O3USWZCWZT/TbgfEpKa4uMgN4Dg== dependencies: "@ampproject/remapping" "^2.2.0" - "@babel/code-frame" "^7.24.1" - "@babel/generator" "^7.24.1" + "@babel/code-frame" "^7.24.2" + "@babel/generator" "^7.24.4" "@babel/helper-compilation-targets" "^7.23.6" "@babel/helper-module-transforms" "^7.23.3" - "@babel/helpers" "^7.24.1" - "@babel/parser" "^7.24.1" + "@babel/helpers" "^7.24.4" + "@babel/parser" "^7.24.4" "@babel/template" "^7.24.0" "@babel/traverse" "^7.24.1" "@babel/types" "^7.24.0" @@ -187,10 +187,10 @@ eslint-visitor-keys "^2.1.0" semver "^6.3.1" -"@babel/generator@^7.24.1", "@babel/generator@^7.7.2": - version "7.24.1" - resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.24.1.tgz#e67e06f68568a4ebf194d1c6014235344f0476d0" - integrity sha512-DfCRfZsBcrPEHUfuBMgbJ1Ut01Y/itOs+hY2nFLgqsqXd52/iSiVq5TITtUasIUgm+IIKdY2/1I7auiQOEeC9A== +"@babel/generator@^7.24.1", "@babel/generator@^7.24.4", "@babel/generator@^7.7.2": + version "7.24.4" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.24.4.tgz#1fc55532b88adf952025d5d2d1e71f946cb1c498" + integrity sha512-Xd6+v6SnjWVx/nus+y0l1sxMOTOMBkyL4+BIdbALyatQnAe/SRVjANeDPSCYaX+i1iJmuGSKf3Z+E+V/va1Hvw== dependencies: "@babel/types" "^7.24.0" "@jridgewell/gen-mapping" "^0.3.5" @@ -222,10 +222,10 @@ lru-cache "^5.1.1" semver "^6.3.1" -"@babel/helper-create-class-features-plugin@^7.18.6", "@babel/helper-create-class-features-plugin@^7.24.1": - version "7.24.1" - resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.24.1.tgz#db58bf57137b623b916e24874ab7188d93d7f68f" - integrity sha512-1yJa9dX9g//V6fDebXoEfEsxkZHk3Hcbm+zLhyu6qVgYFLvmTALTeV+jNU9e5RnYtioBrGEOdoI2joMSNQ/+aA== +"@babel/helper-create-class-features-plugin@^7.18.6", "@babel/helper-create-class-features-plugin@^7.24.1", "@babel/helper-create-class-features-plugin@^7.24.4": + version "7.24.4" + resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.24.4.tgz#c806f73788a6800a5cfbbc04d2df7ee4d927cce3" + integrity sha512-lG75yeuUSVu0pIcbhiYMXBXANHrpUPaOfu7ryAzskCgKUHuAxRQI5ssrtmF0X9UXldPlvT0XM/A4F44OXRt6iQ== dependencies: "@babel/helper-annotate-as-pure" "^7.22.5" "@babel/helper-environment-visitor" "^7.22.20" @@ -284,10 +284,10 @@ dependencies: "@babel/types" "^7.23.0" -"@babel/helper-module-imports@^7.0.0", "@babel/helper-module-imports@^7.16.7", "@babel/helper-module-imports@^7.22.15", "@babel/helper-module-imports@^7.22.5", "@babel/helper-module-imports@^7.24.1": - version "7.24.1" - resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.24.1.tgz#961ea2c12aad6cfc75b8c396c81608a08283027b" - integrity sha512-HfEWzysMyOa7xI5uQHc/OcZf67/jc+xe/RZlznWQHhbb8Pg1SkRdbK4yEi61aY8wxQA7PkSfoojtLQP/Kpe3og== +"@babel/helper-module-imports@^7.0.0", "@babel/helper-module-imports@^7.16.7", "@babel/helper-module-imports@^7.22.15", "@babel/helper-module-imports@^7.22.5", "@babel/helper-module-imports@^7.24.1", "@babel/helper-module-imports@^7.24.3": + version "7.24.3" + resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.24.3.tgz#6ac476e6d168c7c23ff3ba3cf4f7841d46ac8128" + integrity sha512-viKb0F9f2s0BCS22QSF308z/+1YWKV/76mwt61NBzS5izMzDPwdq1pTrzf+Li3npBWX9KdQbkeCt1jSAM7lZqg== dependencies: "@babel/types" "^7.24.0" @@ -377,10 +377,10 @@ "@babel/template" "^7.22.15" "@babel/types" "^7.22.19" -"@babel/helpers@^7.24.1": - version "7.24.1" - resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.24.1.tgz#183e44714b9eba36c3038e442516587b1e0a1a94" - integrity sha512-BpU09QqEe6ZCHuIHFphEFgvNSrubve1FtyMton26ekZ85gRGi6LrTF7zArARp2YvyFxloeiRmtSCq5sjh1WqIg== +"@babel/helpers@^7.24.4": + version "7.24.4" + resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.24.4.tgz#dc00907fd0d95da74563c142ef4cd21f2cb856b6" + integrity sha512-FewdlZbSiwaVGlgT1DPANDuCHaDMiOo+D/IDYRFYjHOuv66xMSJ7fQwwODwRNAPkADIO/z1EoF/l2BCWlWABDw== dependencies: "@babel/template" "^7.24.0" "@babel/traverse" "^7.24.1" @@ -396,10 +396,18 @@ js-tokens "^4.0.0" picocolors "^1.0.0" -"@babel/parser@^7.1.0", "@babel/parser@^7.14.7", "@babel/parser@^7.16.8", "@babel/parser@^7.20.7", "@babel/parser@^7.23.9", "@babel/parser@^7.24.0", "@babel/parser@^7.24.1": - version "7.24.1" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.24.1.tgz#1e416d3627393fab1cb5b0f2f1796a100ae9133a" - integrity sha512-Zo9c7N3xdOIQrNip7Lc9wvRPzlRtovHVE4lkz8WEDr7uYh/GMQhSiIgFxGIArRHYdJE5kxtZjAf8rT0xhdLCzg== +"@babel/parser@^7.1.0", "@babel/parser@^7.14.7", "@babel/parser@^7.16.8", "@babel/parser@^7.20.7", "@babel/parser@^7.23.9", "@babel/parser@^7.24.0", "@babel/parser@^7.24.1", "@babel/parser@^7.24.4": + version "7.24.4" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.24.4.tgz#234487a110d89ad5a3ed4a8a566c36b9453e8c88" + integrity sha512-zTvEBcghmeBma9QIGunWevvBAp4/Qu9Bdq+2k0Ot4fVMD6v3dsC9WOcRSKk7tRRyBM/53yKMJko9xOatGQAwSg== + +"@babel/plugin-bugfix-firefox-class-in-computed-class-key@^7.24.4": + version "7.24.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.24.4.tgz#6125f0158543fb4edf1c22f322f3db67f21cb3e1" + integrity sha512-qpl6vOOEEzTLLcsuqYYo8yDtrTocmu2xkGvgNebvPjT9DTtfFYGmgDqY+rBYXNlqL4s9qLDn6xkrJv4RxAPiTA== + dependencies: + "@babel/helper-environment-visitor" "^7.22.20" + "@babel/helper-plugin-utils" "^7.24.0" "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@^7.24.1": version "7.24.1" @@ -675,10 +683,10 @@ dependencies: "@babel/helper-plugin-utils" "^7.24.0" -"@babel/plugin-transform-block-scoping@^7.24.1": - version "7.24.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.24.1.tgz#27af183d7f6dad890531256c7a45019df768ac1f" - integrity sha512-h71T2QQvDgM2SmT29UYU6ozjMlAt7s7CSs5Hvy8f8cf/GM/Z4a2zMfN+fjVGaieeCrXR3EdQl6C4gQG+OgmbKw== +"@babel/plugin-transform-block-scoping@^7.24.4": + version "7.24.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.24.4.tgz#28f5c010b66fbb8ccdeef853bef1935c434d7012" + integrity sha512-nIFUZIpGKDf9O9ttyRXpHFpKC+X3Y5mtshZONuEUYBomAKoM4y029Jr+uB1bHGPhNmK8YXHevDtKDOLmtRrp6g== dependencies: "@babel/helper-plugin-utils" "^7.24.0" @@ -690,12 +698,12 @@ "@babel/helper-create-class-features-plugin" "^7.24.1" "@babel/helper-plugin-utils" "^7.24.0" -"@babel/plugin-transform-class-static-block@^7.24.1": - version "7.24.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.24.1.tgz#4e37efcca1d9f2fcb908d1bae8b56b4b6e9e1cb6" - integrity sha512-FUHlKCn6J3ERiu8Dv+4eoz7w8+kFLSyeVG4vDAikwADGjUCoHw/JHokyGtr8OR4UjpwPVivyF+h8Q5iv/JmrtA== +"@babel/plugin-transform-class-static-block@^7.24.4": + version "7.24.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.24.4.tgz#1a4653c0cf8ac46441ec406dece6e9bc590356a4" + integrity sha512-B8q7Pz870Hz/q9UgP8InNpY01CSLDSCyqX7zcRuv3FcPl87A2G17lASroHWaCtbdIcbYzOZ7kWmXFKbijMSmFg== dependencies: - "@babel/helper-create-class-features-plugin" "^7.24.1" + "@babel/helper-create-class-features-plugin" "^7.24.4" "@babel/helper-plugin-utils" "^7.24.0" "@babel/plugin-syntax-class-static-block" "^7.14.5" @@ -1018,11 +1026,11 @@ "@babel/helper-plugin-utils" "^7.24.0" "@babel/plugin-transform-runtime@^7.16.4": - version "7.24.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.24.1.tgz#3678311f7193ef7cf62c6f34c6f757d0301bf314" - integrity sha512-yHLX14/T+tO0gjgJroDb8JYjOcQuzVC+Brt4CjHAxq/Ghw4xBVG+N02d1rMEcyUnKUQBL4Yy2gA9R72GK961jQ== + version "7.24.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.24.3.tgz#dc58ad4a31810a890550365cc922e1ff5acb5d7f" + integrity sha512-J0BuRPNlNqlMTRJ72eVptpt9VcInbxO6iP3jaxr+1NPhC0UkKL+6oeX6VXMEYdADnuqmMmsBspt4d5w8Y/TCbQ== dependencies: - "@babel/helper-module-imports" "^7.24.1" + "@babel/helper-module-imports" "^7.24.3" "@babel/helper-plugin-utils" "^7.24.0" babel-plugin-polyfill-corejs2 "^0.4.10" babel-plugin-polyfill-corejs3 "^0.10.1" @@ -1066,12 +1074,12 @@ "@babel/helper-plugin-utils" "^7.24.0" "@babel/plugin-transform-typescript@^7.24.1": - version "7.24.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.24.1.tgz#5c05e28bb76c7dfe7d6c5bed9951324fd2d3ab07" - integrity sha512-liYSESjX2fZ7JyBFkYG78nfvHlMKE6IpNdTVnxmlYUR+j5ZLsitFbaAE+eJSK2zPPkNWNw4mXL51rQ8WrvdK0w== + version "7.24.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.24.4.tgz#03e0492537a4b953e491f53f2bc88245574ebd15" + integrity sha512-79t3CQ8+oBGk/80SQ8MN3Bs3obf83zJ0YZjDmDaEZN8MqhMI760apl5z6a20kFeMXBwJX99VpKT8CKxEBp5H1g== dependencies: "@babel/helper-annotate-as-pure" "^7.22.5" - "@babel/helper-create-class-features-plugin" "^7.24.1" + "@babel/helper-create-class-features-plugin" "^7.24.4" "@babel/helper-plugin-utils" "^7.24.0" "@babel/plugin-syntax-typescript" "^7.24.1" @@ -1107,14 +1115,15 @@ "@babel/helper-plugin-utils" "^7.24.0" "@babel/preset-env@^7.16.4", "@babel/preset-env@^7.24.3": - version "7.24.3" - resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.24.3.tgz#f3f138c844ffeeac372597b29c51b5259e8323a3" - integrity sha512-fSk430k5c2ff8536JcPvPWK4tZDwehWLGlBp0wrsBUjZVdeQV6lePbwKWZaZfK2vnh/1kQX1PzAJWsnBmVgGJA== + version "7.24.4" + resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.24.4.tgz#46dbbcd608771373b88f956ffb67d471dce0d23b" + integrity sha512-7Kl6cSmYkak0FK/FXjSEnLJ1N9T/WA2RkMhu17gZ/dsxKJUuTYNIylahPTzqpLyJN4WhDif8X0XK1R8Wsguo/A== dependencies: - "@babel/compat-data" "^7.24.1" + "@babel/compat-data" "^7.24.4" "@babel/helper-compilation-targets" "^7.23.6" "@babel/helper-plugin-utils" "^7.24.0" "@babel/helper-validator-option" "^7.23.5" + "@babel/plugin-bugfix-firefox-class-in-computed-class-key" "^7.24.4" "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression" "^7.24.1" "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining" "^7.24.1" "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly" "^7.24.1" @@ -1141,9 +1150,9 @@ "@babel/plugin-transform-async-generator-functions" "^7.24.3" "@babel/plugin-transform-async-to-generator" "^7.24.1" "@babel/plugin-transform-block-scoped-functions" "^7.24.1" - "@babel/plugin-transform-block-scoping" "^7.24.1" + "@babel/plugin-transform-block-scoping" "^7.24.4" "@babel/plugin-transform-class-properties" "^7.24.1" - "@babel/plugin-transform-class-static-block" "^7.24.1" + "@babel/plugin-transform-class-static-block" "^7.24.4" "@babel/plugin-transform-classes" "^7.24.1" "@babel/plugin-transform-computed-properties" "^7.24.1" "@babel/plugin-transform-destructuring" "^7.24.1" @@ -1230,17 +1239,17 @@ integrity sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA== "@babel/runtime-corejs3@^7.9.2": - version "7.24.1" - resolved "https://registry.yarnpkg.com/@babel/runtime-corejs3/-/runtime-corejs3-7.24.1.tgz#f39707b213441dec645ce8285ae14f281a5077c6" - integrity sha512-T9ko/35G+Bkl+win48GduaPlhSlOjjE5s1TeiEcD+QpxlLQnoEfb/nO/T+TQqkm+ipFwORn+rB8w14iJ/uD0bg== + version "7.24.4" + resolved "https://registry.yarnpkg.com/@babel/runtime-corejs3/-/runtime-corejs3-7.24.4.tgz#b9ebe728087cfbb22bbaccc6f9a70d69834124a0" + integrity sha512-VOQOexSilscN24VEY810G/PqtpFvx/z6UqDIjIWbDe2368HhDLkYN5TYwaEz/+eRCUkhJ2WaNLLmQAlxzfWj4w== dependencies: core-js-pure "^3.30.2" regenerator-runtime "^0.14.0" "@babel/runtime@^7.0.0", "@babel/runtime@^7.1.2", "@babel/runtime@^7.10.5", "@babel/runtime@^7.12.0", "@babel/runtime@^7.12.1", "@babel/runtime@^7.12.13", "@babel/runtime@^7.12.5", "@babel/runtime@^7.13.10", "@babel/runtime@^7.16.3", "@babel/runtime@^7.17.2", "@babel/runtime@^7.17.9", "@babel/runtime@^7.18.3", "@babel/runtime@^7.18.6", "@babel/runtime@^7.21.0", "@babel/runtime@^7.23.2", "@babel/runtime@^7.23.4", "@babel/runtime@^7.23.8", "@babel/runtime@^7.23.9", "@babel/runtime@^7.5.5", "@babel/runtime@^7.6.2", "@babel/runtime@^7.7.2", "@babel/runtime@^7.8.4", "@babel/runtime@^7.8.7", "@babel/runtime@^7.9.2": - version "7.24.1" - resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.24.1.tgz#431f9a794d173b53720e69a6464abc6f0e2a5c57" - integrity sha512-+BIznRzyqBf+2wCTxcKE3wDjfGeCoVE61KSHGpkzqrLi8qxqFwBeUFyId2cxkTmm55fzDGnm0+yCxaxygrLUnQ== + version "7.24.4" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.24.4.tgz#de795accd698007a66ba44add6cc86542aff1edd" + integrity sha512-dkxf7+hn8mFBwKjs9bvBlArzLVxVbS8usaPUDd5p2a9JCL9tB8OaOVN1isD4+Xyk4ns89/xeOmbQvgdK7IIVdA== dependencies: regenerator-runtime "^0.14.0" @@ -1364,9 +1373,9 @@ "@lezer/highlight" "^1.0.0" "@codemirror/view@^6.0.0", "@codemirror/view@^6.17.0", "@codemirror/view@^6.23.0": - version "6.26.0" - resolved "https://registry.yarnpkg.com/@codemirror/view/-/view-6.26.0.tgz#ab5a85aa8ebfb953cb5534e07d0a3751f9a3869a" - integrity sha512-nSSmzONpqsNzshPOxiKhK203R6BvABepugAe34QfQDbNDslyjkqBuKgrK5ZBvqNXpfxz5iLrlGTmEfhbQyH46A== + version "6.26.1" + resolved "https://registry.yarnpkg.com/@codemirror/view/-/view-6.26.1.tgz#ab99cf35c576bc65f5804ab49db730b65c3fad3f" + integrity sha512-wLw0t3R9AwOSQThdZ5Onw8QQtem5asE7+bPlnzc57eubPqiuJKIzwjMZ+C42vQett+iva+J8VgFV4RYWDBh5FA== dependencies: "@codemirror/state" "^6.4.0" style-mod "^4.1.0" @@ -1493,7 +1502,7 @@ dependencies: "@emotion/memoize" "0.7.4" -"@emotion/is-prop-valid@^1.2.1": +"@emotion/is-prop-valid@^1.2.2": version "1.2.2" resolved "https://registry.yarnpkg.com/@emotion/is-prop-valid/-/is-prop-valid-1.2.2.tgz#d4175076679c6a26faa92b03bb786f9e52612337" integrity sha512-uNsoYd37AFmaCdXlg6EYD1KaPOaRWRByMCYzbKUX4+hhMfrxdVSelShywL4JVaAeM/eHUOSprYBQls+/neX3pw== @@ -1524,10 +1533,10 @@ "@emotion/weak-memoize" "^0.3.1" hoist-non-react-statics "^3.3.1" -"@emotion/serialize@^1.1.2", "@emotion/serialize@^1.1.3": - version "1.1.3" - resolved "https://registry.yarnpkg.com/@emotion/serialize/-/serialize-1.1.3.tgz#84b77bfcfe3b7bb47d326602f640ccfcacd5ffb0" - integrity sha512-iD4D6QVZFDhcbH0RAG1uVu1CwVLMWUkCvAqqlewO/rxf8+87yIBAlt4+AxMiiKPLs5hFc0owNk/sLLAOROw3cA== +"@emotion/serialize@^1.1.2", "@emotion/serialize@^1.1.3", "@emotion/serialize@^1.1.4": + version "1.1.4" + resolved "https://registry.yarnpkg.com/@emotion/serialize/-/serialize-1.1.4.tgz#fc8f6d80c492cfa08801d544a05331d1cc7cd451" + integrity sha512-RIN04MBT8g+FnDwgvIUi8czvr1LU1alUMI05LekWB5DGyTm8cCBMCRpq3GqaiyEDRptEXOyXnvZ58GZYu4kBxQ== dependencies: "@emotion/hash" "^0.9.1" "@emotion/memoize" "^0.8.1" @@ -1541,14 +1550,14 @@ integrity sha512-0QBtGvaqtWi+nx6doRwDdBIzhNdZrXUppvTM4dtZZWEGTXL/XE/yJxLMGlDT1Gt+UHH5IX1n+jkXyytE/av7OA== "@emotion/styled@^11.10.5": - version "11.11.0" - resolved "https://registry.yarnpkg.com/@emotion/styled/-/styled-11.11.0.tgz#26b75e1b5a1b7a629d7c0a8b708fbf5a9cdce346" - integrity sha512-hM5Nnvu9P3midq5aaXj4I+lnSfNi7Pmd4EWk1fOZ3pxookaQTNew6bp4JaCBYM4HVFZF9g7UjJmsUmC2JlxOng== + version "11.11.5" + resolved "https://registry.yarnpkg.com/@emotion/styled/-/styled-11.11.5.tgz#0c5c8febef9d86e8a926e663b2e5488705545dfb" + integrity sha512-/ZjjnaNKvuMPxcIiUkf/9SHoG4Q196DRl1w82hQ3WCsjo1IUR8uaGWrC6a87CrYAW0Kb/pK7hk8BnLgLRi9KoQ== dependencies: "@babel/runtime" "^7.18.3" "@emotion/babel-plugin" "^11.11.0" - "@emotion/is-prop-valid" "^1.2.1" - "@emotion/serialize" "^1.1.2" + "@emotion/is-prop-valid" "^1.2.2" + "@emotion/serialize" "^1.1.4" "@emotion/use-insertion-effect-with-fallbacks" "^1.0.1" "@emotion/utils" "^1.2.1" @@ -2435,11 +2444,6 @@ "@ethersproject/properties" "^5.7.0" "@ethersproject/strings" "^5.7.0" -"@fastify/busboy@^2.0.0": - version "2.1.1" - resolved "https://registry.yarnpkg.com/@fastify/busboy/-/busboy-2.1.1.tgz#b9da6a878a371829a0502c9b6c1c143ef6663f4d" - integrity sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA== - "@float-capital/float-subgraph-uncrashable@^0.0.0-alpha.4": version "0.0.0-internal-testing.5" resolved "https://registry.yarnpkg.com/@float-capital/float-subgraph-uncrashable/-/float-subgraph-uncrashable-0.0.0-internal-testing.5.tgz#060f98440f6e410812766c5b040952d2d02e2b73" @@ -2457,7 +2461,7 @@ dependencies: "@floating-ui/utils" "^0.2.1" -"@floating-ui/dom@^1.0.1", "@floating-ui/dom@^1.5.4", "@floating-ui/dom@^1.6.1": +"@floating-ui/dom@^1.0.1", "@floating-ui/dom@^1.6.1": version "1.6.3" resolved "https://registry.yarnpkg.com/@floating-ui/dom/-/dom-1.6.3.tgz#954e46c1dd3ad48e49db9ada7218b0985cee75ef" integrity sha512-RnDthu3mzPlQ31Ss/BTwQ1zjzIhr3lk1gZB1OC56h/1vEtaXkESrOqL5fQVMfXpwGtRwX+YsZBdyHtJMQnkArw== @@ -2465,14 +2469,7 @@ "@floating-ui/core" "^1.0.0" "@floating-ui/utils" "^0.2.0" -"@floating-ui/react-dom@^2.0.0", "@floating-ui/react-dom@^2.0.2": - version "2.0.5" - resolved "https://registry.yarnpkg.com/@floating-ui/react-dom/-/react-dom-2.0.5.tgz#851522899c34e3e2be1e29f3294f150834936e28" - integrity sha512-UsBK30Bg+s6+nsgblXtZmwHhgS2vmbuQK22qgt2pTQM6M3X6H1+cQcLXqgRY3ihVLcZJE6IvqDQozhsnIVqK/Q== - dependencies: - "@floating-ui/dom" "^1.5.4" - -"@floating-ui/react-dom@^2.0.8": +"@floating-ui/react-dom@^2.0.0", "@floating-ui/react-dom@^2.0.2", "@floating-ui/react-dom@^2.0.8": version "2.0.8" resolved "https://registry.yarnpkg.com/@floating-ui/react-dom/-/react-dom-2.0.8.tgz#afc24f9756d1b433e1fe0d047c24bd4d9cefaa5d" integrity sha512-HOdqOt3R3OGeTKidaLvJKcgg75S6tibQ3Tif4eyd91QnIJWr0NLvoXFpJA/j8HqkFSL68GDca9AuyWEHlhyClw== @@ -2852,9 +2849,9 @@ integrity sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA== "@humanwhocodes/object-schema@^2.0.2": - version "2.0.2" - resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-2.0.2.tgz#d9fae00a2d5cb40f92cfe64b47ad749fbc38f917" - integrity sha512-6EwiSjwWYP7pTckG6I5eyFANjPhmPjUX9JRLUSfNPC7FX7zK9gyZAfUEaECL6ALTpGX5AjnBq3C9XmVWPitNpw== + version "2.0.3" + resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz#4a2868d75d6d6963e423bcf90b7fd1be343409d3" + integrity sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA== "@internationalized/date@^3.5.0", "@internationalized/date@^3.5.2": version "3.5.2" @@ -3379,40 +3376,40 @@ clsx "^2.1.0" prop-types "^15.8.1" -"@mui/core-downloads-tracker@^5.15.14": - version "5.15.14" - resolved "https://registry.yarnpkg.com/@mui/core-downloads-tracker/-/core-downloads-tracker-5.15.14.tgz#f7c57b261904831877220182303761c012d05046" - integrity sha512-on75VMd0XqZfaQW+9pGjSNiqW+ghc5E2ZSLRBXwcXl/C4YzjfyjrLPhrEpKnR9Uym9KXBvxrhoHfPcczYHweyA== +"@mui/core-downloads-tracker@^5.15.15": + version "5.15.15" + resolved "https://registry.yarnpkg.com/@mui/core-downloads-tracker/-/core-downloads-tracker-5.15.15.tgz#2bc2bda50db66c12f10aefec907c48c8f669ef59" + integrity sha512-aXnw29OWQ6I5A47iuWEI6qSSUfH6G/aCsW9KmW3LiFqr7uXZBK4Ks+z8G+qeIub8k0T5CMqlT2q0L+ZJTMrqpg== "@mui/icons-material@^5.15.11": - version "5.15.14" - resolved "https://registry.yarnpkg.com/@mui/icons-material/-/icons-material-5.15.14.tgz#333468c94988d96203946d1cfeb8f4d7e8e7de34" - integrity sha512-vj/51k7MdFmt+XVw94sl30SCvGx6+wJLsNYjZRgxhS6y3UtnWnypMOsm3Kmg8TN+P0dqwsjy4/fX7B1HufJIhw== + version "5.15.15" + resolved "https://registry.yarnpkg.com/@mui/icons-material/-/icons-material-5.15.15.tgz#84ce08225a531d9f5dc5132009d91164b456a0ae" + integrity sha512-kkeU/pe+hABcYDH6Uqy8RmIsr2S/y5bP2rp+Gat4CcRjCcVne6KudS1NrZQhUCRysrTDCAhcbcf9gt+/+pGO2g== dependencies: "@babel/runtime" "^7.23.9" "@mui/lab@^5.0.0-alpha.141": - version "5.0.0-alpha.169" - resolved "https://registry.yarnpkg.com/@mui/lab/-/lab-5.0.0-alpha.169.tgz#85b88b2f06ad78c586cde2b47970653e5fd895eb" - integrity sha512-h6xe1K6ISKUbyxTDgdvql4qoDP6+q8ad5fg9nXQxGLUrIeT2jVrBuT/jRECSTufbnhzP+V5kulvYxaMfM8rEdA== + version "5.0.0-alpha.170" + resolved "https://registry.yarnpkg.com/@mui/lab/-/lab-5.0.0-alpha.170.tgz#4519dfc8d1c51ca54fb9d8b91b95a3733d07be16" + integrity sha512-0bDVECGmrNjd3+bLdcLiwYZ0O4HP5j5WSQm5DV6iA/Z9kr8O6AnvZ1bv9ImQbbX7Gj3pX4o43EKwCutj3EQxQg== dependencies: "@babel/runtime" "^7.23.9" "@mui/base" "5.0.0-beta.40" - "@mui/system" "^5.15.14" + "@mui/system" "^5.15.15" "@mui/types" "^7.2.14" "@mui/utils" "^5.15.14" clsx "^2.1.0" prop-types "^15.8.1" "@mui/material@^5.14.14": - version "5.15.14" - resolved "https://registry.yarnpkg.com/@mui/material/-/material-5.15.14.tgz#a40bd5eccfa9fc925535e1f4d70c6cef77fa3a75" - integrity sha512-kEbRw6fASdQ1SQ7LVdWR5OlWV3y7Y54ZxkLzd6LV5tmz+NpO3MJKZXSfgR0LHMP7meKsPiMm4AuzV0pXDpk/BQ== + version "5.15.15" + resolved "https://registry.yarnpkg.com/@mui/material/-/material-5.15.15.tgz#e3ba35f50b510aa677cec3261abddc2db7b20b59" + integrity sha512-3zvWayJ+E1kzoIsvwyEvkTUKVKt1AjchFFns+JtluHCuvxgKcLSRJTADw37k0doaRtVAsyh8bz9Afqzv+KYrIA== dependencies: "@babel/runtime" "^7.23.9" "@mui/base" "5.0.0-beta.40" - "@mui/core-downloads-tracker" "^5.15.14" - "@mui/system" "^5.15.14" + "@mui/core-downloads-tracker" "^5.15.15" + "@mui/system" "^5.15.15" "@mui/types" "^7.2.14" "@mui/utils" "^5.15.14" "@types/react-transition-group" "^4.4.10" @@ -3441,10 +3438,10 @@ csstype "^3.1.3" prop-types "^15.8.1" -"@mui/system@^5.15.14": - version "5.15.14" - resolved "https://registry.yarnpkg.com/@mui/system/-/system-5.15.14.tgz#8a0c6571077eeb6b5f1ff7aa7ff6a3dc4a14200d" - integrity sha512-auXLXzUaCSSOLqJXmsAaq7P96VPRXg2Rrz6OHNV7lr+kB8lobUF+/N84Vd9C4G/wvCXYPs5TYuuGBRhcGbiBGg== +"@mui/system@^5.15.15": + version "5.15.15" + resolved "https://registry.yarnpkg.com/@mui/system/-/system-5.15.15.tgz#658771b200ce3c4a0f28e58169f02e5e718d1c53" + integrity sha512-aulox6N1dnu5PABsfxVGOZffDVmlxPOVgj56HrUnJE8MCSh8lOvvkd47cebIVQQYAjpwieXQXiDPj5pwM40jTQ== dependencies: "@babel/runtime" "^7.23.9" "@mui/private-theming" "^5.15.14" @@ -3471,9 +3468,9 @@ react-is "^18.2.0" "@mui/x-date-pickers@^6.19.7": - version "6.19.7" - resolved "https://registry.yarnpkg.com/@mui/x-date-pickers/-/x-date-pickers-6.19.7.tgz#05bf07a7ae77af56b7bf3f2b1cb9d95b3325b72c" - integrity sha512-BCTOQjAuyU29Ymd2FJrHHdRh0U6Qve7MsthdrD2jjaMaR8ou455JuxsNTQUGSpiMkGHWOMVq+B8N1EBcSYH63g== + version "6.19.8" + resolved "https://registry.yarnpkg.com/@mui/x-date-pickers/-/x-date-pickers-6.19.8.tgz#3962f08194c7b2cba1b01688179a8835a8a27eae" + integrity sha512-6wgc2DoRTR9/mKesku4CVCKr9yYkY3FI2Oy/wshLTs2rFkw2Z10uxXFHBR9ugEtNPNCQv0qqwldElenYI97wsA== dependencies: "@babel/runtime" "^7.23.2" "@mui/base" "^5.0.0-beta.22" @@ -3525,28 +3522,28 @@ webpack-node-externals "3.0.0" "@nestjs/common@^10.2.7": - version "10.3.4" - resolved "https://registry.yarnpkg.com/@nestjs/common/-/common-10.3.4.tgz#d02ea0e34a38809ad42223856c008e6329e37d81" - integrity sha512-HmehujZhUZjf9TN2o0TyzWYNwEgyRYqZZ5qIcF/mCgIUZ4olIKlazna0kGK56FGlCvviHWNKQM5eTuVeTstIgA== + version "10.3.7" + resolved "https://registry.yarnpkg.com/@nestjs/common/-/common-10.3.7.tgz#38ab5ff92277cf1f26f4749c264524e76962cfff" + integrity sha512-gKFtFzcJznrwsRYjtNZoPAvSOPYdNgxbTYoAyLTpoy393cIKgLmJTHu6ReH8/qIB9AaZLdGaFLkx98W/tFWFUw== dependencies: uid "2.0.2" iterare "1.2.1" tslib "2.6.2" "@nestjs/config@^3.1.1": - version "3.2.0" - resolved "https://registry.yarnpkg.com/@nestjs/config/-/config-3.2.0.tgz#4ca70f88b0636f86741ba192dd51dd2f01eaa7c1" - integrity sha512-BpYRn57shg7CH35KGT6h+hT7ZucB6Qn2B3NBNdvhD4ApU8huS5pX/Wc2e/aO5trIha606Bz2a9t9/vbiuTBTww== + version "3.2.1" + resolved "https://registry.yarnpkg.com/@nestjs/config/-/config-3.2.1.tgz#6b264564e70a8d639734e9a583ad1d2ac5351588" + integrity sha512-tFZyLJKanSAu51ygQ6ZBSpx95pRcwS6qSpJDW6FFgRQzkOaOUXpL8qD8yMNoYoYxuJCxph+waiBaWKgFWxn3sw== dependencies: - dotenv "16.4.1" + dotenv "16.4.5" dotenv-expand "10.0.0" lodash "4.17.21" uuid "9.0.1" "@nestjs/core@^10.2.8": - version "10.3.4" - resolved "https://registry.yarnpkg.com/@nestjs/core/-/core-10.3.4.tgz#ec72f386ffe2f18f8a4c5a8cc5c256c774ab9ee2" - integrity sha512-rF0yebuHmMj+9/CkbjPWWMvlF5x8j5Biw2DRvbl8R8n2X3OdFBN+06x/9xm3/ZssR5tLoB9tsYspFUb+SvnnwA== + version "10.3.7" + resolved "https://registry.yarnpkg.com/@nestjs/core/-/core-10.3.7.tgz#736906ec27bc39b91519506babc231c8ab56ea21" + integrity sha512-hsdlnfiQ3kgqHL5k7js3CU0PV7hBJVi+LfFMgCkoagRxNMf67z0GFGeOV2jk5d65ssB19qdYsDa1MGVuEaoUpg== dependencies: uid "2.0.2" "@nuxtjs/opencollective" "0.3.2" @@ -3574,13 +3571,13 @@ integrity sha512-znJ9Y4S8ZDVY+j4doWAJ8EuuVO7SkQN3yOBmzxbGaXbvcSwFDAdGJ+OMCg52NdzIO4tQoN4pYKx8W6M0ArfFRQ== "@nestjs/platform-express@^10.2.6": - version "10.3.4" - resolved "https://registry.yarnpkg.com/@nestjs/platform-express/-/platform-express-10.3.4.tgz#5b9988fe32c226569c0bab99f68d7af022091358" - integrity sha512-rzUUUZCGYNs/viT9I6W5izJ1+oYCG0ym/dAn31NmYJW9UchxJdX5PCJqWF8iIbys6JgfbdcapMR5t+L7OZsasQ== + version "10.3.7" + resolved "https://registry.yarnpkg.com/@nestjs/platform-express/-/platform-express-10.3.7.tgz#ae3fc59609bdc0ffc5029a6e74d59a5d1e257eef" + integrity sha512-noNJ+PyIxQJLCKfuXz0tcQtlVAynfLIuKy62g70lEZ86UrIqSrZFqvWs/rFUgkbT6J8H7Rmv11hASOnX+7M2rA== dependencies: body-parser "1.20.2" cors "2.8.5" - express "4.18.3" + express "4.19.2" multer "1.4.4-lts.1" tslib "2.6.2" @@ -3603,16 +3600,16 @@ pluralize "8.0.0" "@nestjs/serve-static@^4.0.1": - version "4.0.1" - resolved "https://registry.yarnpkg.com/@nestjs/serve-static/-/serve-static-4.0.1.tgz#287b8b059602705184980b4c8c24b62cc24d26c1" - integrity sha512-AoOrVdAe+WmsceuCcA8nWmKUYmaOsg9pqBCbIj7PS4W3XdikJQMtfxgSIoOlyUksZdhTBFjHqKh0Yhpj6pulwQ== + version "4.0.2" + resolved "https://registry.yarnpkg.com/@nestjs/serve-static/-/serve-static-4.0.2.tgz#f003bbd90922bdc73d0261edacf001dfef174c96" + integrity sha512-cT0vdWN5ar7jDI2NKbhf4LcwJzU4vS5sVpMkVrHuyLcltbrz6JdGi1TfIMMatP2pNiq5Ie/uUdPSFDVaZX/URQ== dependencies: path-to-regexp "0.2.5" "@nestjs/swagger@^7.1.13": - version "7.3.0" - resolved "https://registry.yarnpkg.com/@nestjs/swagger/-/swagger-7.3.0.tgz#0b5e397cc5a592422df9afb24c79af928fea5954" - integrity sha512-zLkfKZ+ioYsIZ3dfv7Bj8YHnZMNAGWFUmx2ZDuLp/fBE4P8BSjB7hldzDueFXsmwaPL90v7lgyd82P+s7KME1Q== + version "7.3.1" + resolved "https://registry.yarnpkg.com/@nestjs/swagger/-/swagger-7.3.1.tgz#353fdd5bd6f23564505117b1c82d7decc145e8fe" + integrity sha512-LUC4mr+5oAleEC/a2j8pNRh1S5xhKXJ1Gal5ZdRjt9XebQgbngXCdW7JTA9WOEcwGtFZN9EnKYdquzH971LZfw== dependencies: "@microsoft/tsdoc" "^0.14.2" "@nestjs/mapped-types" "2.0.5" @@ -3630,9 +3627,9 @@ check-disk-space "3.4.0" "@nestjs/testing@^10.3.1": - version "10.3.4" - resolved "https://registry.yarnpkg.com/@nestjs/testing/-/testing-10.3.4.tgz#9b01e33097ae9a5f4f2ebb86a1438329dc845b83" - integrity sha512-g3NQnRUFBcYF+ySkB7INg5RiV7CNfkP5zwaf3NFo0WjhBrfih9f1jMZ/19blLZ4djN/ngulYks2E3lzROAW8RQ== + version "10.3.7" + resolved "https://registry.yarnpkg.com/@nestjs/testing/-/testing-10.3.7.tgz#b39fbaf011c439a397d42bc16a13bc5191d0a961" + integrity sha512-PmwZXyoCC/m3F3IFgpgD+SNN6cDPQa/vi3YQxFruvfX3cuHq+P6ZFvBB7hwaKKsLlhA0so42LsMm41oFBkdouw== dependencies: tslib "2.6.2" @@ -3724,65 +3721,65 @@ "@nodelib/fs.scandir" "2.1.5" fastq "^1.6.0" -"@nomicfoundation/edr-darwin-arm64@0.3.2": - version "0.3.2" - resolved "https://registry.yarnpkg.com/@nomicfoundation/edr-darwin-arm64/-/edr-darwin-arm64-0.3.2.tgz#be1f696d429aecdf47d292a75958551e7cc34294" - integrity sha512-l6wfSBUUbGJiCENT6272CDI8yoMuf0sZH56H5qz3HnAyVzenkOvmzyF6/lar54m986kdAQqWls4cLvDxiOuLxg== +"@nomicfoundation/edr-darwin-arm64@0.3.3": + version "0.3.3" + resolved "https://registry.yarnpkg.com/@nomicfoundation/edr-darwin-arm64/-/edr-darwin-arm64-0.3.3.tgz#0618dbdf1c832f8e61c77540e7188c13fdd5b658" + integrity sha512-E9VGsUD+1Ga4mn/5ooHsMi8JEfhZbKP6CXN/BhJ8kXbIC10NqTD1RuhCKGRtYq4vqH/3Nfq25Xg8E8RWOF4KBQ== -"@nomicfoundation/edr-darwin-x64@0.3.2": - version "0.3.2" - resolved "https://registry.yarnpkg.com/@nomicfoundation/edr-darwin-x64/-/edr-darwin-x64-0.3.2.tgz#efd2a5a302dc0a806fb2439a7e2c8ed7bd482ff8" - integrity sha512-OboExL7vEw+TRJQl3KkaEKU4K7PTdZPTInZ0fxMAtOpcWp7EKR+dQo68vc/iAOusB3xszHKxt7t+WpisItfdcg== +"@nomicfoundation/edr-darwin-x64@0.3.3": + version "0.3.3" + resolved "https://registry.yarnpkg.com/@nomicfoundation/edr-darwin-x64/-/edr-darwin-x64-0.3.3.tgz#567ee0bca8d019085e8dd95330e7c03f16c66a79" + integrity sha512-vkZXZ1ydPg+Ijb2iyqENA+KCkxGTCUWG5itCSliiA0Li2YE7ujDMGhheEpFp1WVlZadviz0bfk1rZXbCqlirpg== -"@nomicfoundation/edr-linux-arm64-gnu@0.3.2": - version "0.3.2" - resolved "https://registry.yarnpkg.com/@nomicfoundation/edr-linux-arm64-gnu/-/edr-linux-arm64-gnu-0.3.2.tgz#4d7d4f28e13d426b5e3cf9731dbfed573f819245" - integrity sha512-xtEK+1eg++3pHi6405NDXd80S3CGOFEGQIyVGCwjMGQFOLSzBGGCsrb/0GB4J19zd1o/8ftCd/HjZcbVAWWTLQ== +"@nomicfoundation/edr-linux-arm64-gnu@0.3.3": + version "0.3.3" + resolved "https://registry.yarnpkg.com/@nomicfoundation/edr-linux-arm64-gnu/-/edr-linux-arm64-gnu-0.3.3.tgz#3956b4d7a0127e2259351626c92698c4ce6ecf05" + integrity sha512-gdIg0Yj1qqS9wVuywc5B/+DqKylfUGB6/CQn/shMqwAfsAVAVpchkhy66PR+REEx7fh/GkNctxLlENXPeLzDiA== -"@nomicfoundation/edr-linux-arm64-musl@0.3.2": - version "0.3.2" - resolved "https://registry.yarnpkg.com/@nomicfoundation/edr-linux-arm64-musl/-/edr-linux-arm64-musl-0.3.2.tgz#10702ae4db386cc21efef9e6276bb56a09581e0a" - integrity sha512-3cIsskJOXQ1yEVsImmCacY7O03tUTiWrmd54F05PnPFrDLkjbzodQ3b2gUWzfbzUZWl67ZTJd1CvVSzpe7XGzw== +"@nomicfoundation/edr-linux-arm64-musl@0.3.3": + version "0.3.3" + resolved "https://registry.yarnpkg.com/@nomicfoundation/edr-linux-arm64-musl/-/edr-linux-arm64-musl-0.3.3.tgz#139f801939ed467f1719a2ab014993838008eefb" + integrity sha512-AXZ08MFvhNeBZbOBNmz1SJ/DMrMOE2mHEJtaNnsctlxIunjxfrWww4q+WXB34jbr9iaVYYlPsaWe5sueuw6s3Q== -"@nomicfoundation/edr-linux-x64-gnu@0.3.2": - version "0.3.2" - resolved "https://registry.yarnpkg.com/@nomicfoundation/edr-linux-x64-gnu/-/edr-linux-x64-gnu-0.3.2.tgz#a29590bea8d490aa2129f2cb7fab1559ec9d25ee" - integrity sha512-ouPdphHNsyO7wqwa4hwahC5WqBglK/fIvMmhR/SXNZ9qruIpsA8ZZKIURaHMOv/2h2BbNGcyTX9uEk6+5rK/MQ== +"@nomicfoundation/edr-linux-x64-gnu@0.3.3": + version "0.3.3" + resolved "https://registry.yarnpkg.com/@nomicfoundation/edr-linux-x64-gnu/-/edr-linux-x64-gnu-0.3.3.tgz#b5994caa1a8bb4afca5f079ad7dd99edb26c6c45" + integrity sha512-xElOs1U+E6lBLtv1mnJ+E8nr2MxZgKiLo8bZAgBboy9odYtmkDVwhMjtsFKSuZbGxFtsSyGRT4cXw3JAbtUDeA== -"@nomicfoundation/edr-linux-x64-musl@0.3.2": - version "0.3.2" - resolved "https://registry.yarnpkg.com/@nomicfoundation/edr-linux-x64-musl/-/edr-linux-x64-musl-0.3.2.tgz#fe2b4c282c80c792b3a03e04d517d140a927cf92" - integrity sha512-sRhwhiPbkpJMOUwXW1FZw9ks6xWyQhIhM0E8o3TXEXKSPKTE6whQLEk1R37iFITaI36vb6rSwLKTU1cb32gCoA== +"@nomicfoundation/edr-linux-x64-musl@0.3.3": + version "0.3.3" + resolved "https://registry.yarnpkg.com/@nomicfoundation/edr-linux-x64-musl/-/edr-linux-x64-musl-0.3.3.tgz#536c1d1dfd2fc7d7ad6ed6e14ed9a12322d88ba6" + integrity sha512-2Fe6gwm1RAGQ/PfMYiaSba2OrFp8zzYWh+am9lYObOFjV9D+A1zhIzfy0UC74glPks5eV8eY4pBPrVR042m2Nw== -"@nomicfoundation/edr-win32-arm64-msvc@0.3.2": - version "0.3.2" - resolved "https://registry.yarnpkg.com/@nomicfoundation/edr-win32-arm64-msvc/-/edr-win32-arm64-msvc-0.3.2.tgz#6f09cf20f7e7be3ea70f61888cde238d924ec369" - integrity sha512-IEwVealKfumu1HSSnama26yPuQC/uthRPK5IWtFsQUOGwOXaS1r9Bu7cGYH2jBHl3IT/JbxD4xzPq/2pM9uK0A== +"@nomicfoundation/edr-win32-arm64-msvc@0.3.3": + version "0.3.3" + resolved "https://registry.yarnpkg.com/@nomicfoundation/edr-win32-arm64-msvc/-/edr-win32-arm64-msvc-0.3.3.tgz#f71609644d8585c2ec71580bf75c2fd036ee58b0" + integrity sha512-8NHyxIsFrl0ufSQ/ErqF2lKIa/gz1gaaa1a2vKkDEqvqCUcPhBTYhA5NHgTPhLETFTnCFr0z+YbctFCyjh4qrA== -"@nomicfoundation/edr-win32-ia32-msvc@0.3.2": - version "0.3.2" - resolved "https://registry.yarnpkg.com/@nomicfoundation/edr-win32-ia32-msvc/-/edr-win32-ia32-msvc-0.3.2.tgz#ba4cd9744d95f491329efdfbefb527a8c9f5f31d" - integrity sha512-jYMnf6SFgguqROswwdsjJ1wvneD/5c16pVu9OD4DxNqhKNP5bHEw6L2N4DcJ89tpXMpJ6AlOpc0QuwzddiZ3tA== +"@nomicfoundation/edr-win32-ia32-msvc@0.3.3": + version "0.3.3" + resolved "https://registry.yarnpkg.com/@nomicfoundation/edr-win32-ia32-msvc/-/edr-win32-ia32-msvc-0.3.3.tgz#baa5eaacb1fff107d02f0e6a33dee9521fd2bf37" + integrity sha512-0F6hM0kGia4dQVb/kauho9JcP1ozWisY2/She+ISR5ceuhzmAwQJluM0g+0TYDME0LtxBxiMPq/yPiZMQeq31w== -"@nomicfoundation/edr-win32-x64-msvc@0.3.2": - version "0.3.2" - resolved "https://registry.yarnpkg.com/@nomicfoundation/edr-win32-x64-msvc/-/edr-win32-x64-msvc-0.3.2.tgz#331e3cea1618134e8d9f4ccb999086af6362a3af" - integrity sha512-Byn4QuWczRy/DUUQM3WjglgX/jGVUURVFaUsmIhnGg//MPlCLawubBGRqsrMuvaYedlIIJ4I2rgKvZlxdgHrqg== +"@nomicfoundation/edr-win32-x64-msvc@0.3.3": + version "0.3.3" + resolved "https://registry.yarnpkg.com/@nomicfoundation/edr-win32-x64-msvc/-/edr-win32-x64-msvc-0.3.3.tgz#7562e061b2481f87bb1ace30513a2ad38c469836" + integrity sha512-d75q1uaMb6z9i+GQZoblbOfFBvlBnWc+5rB13UWRkCOJSnoYwyFWhGJx5GeM59gC7aIblc5VD9qOAhHuvM9N+w== "@nomicfoundation/edr@^0.3.1": - version "0.3.2" - resolved "https://registry.yarnpkg.com/@nomicfoundation/edr/-/edr-0.3.2.tgz#a81500746f8f8c342606a9be04a9f6176f8e2d54" - integrity sha512-HGWtjibAK1mo4I2A7nJ/fXqe/J9G54OrSPJnnkY2K8TiXotYLShGd9GvHkae3PuFjTJKm6ZgBy7tveJj5yrCfw== + version "0.3.3" + resolved "https://registry.yarnpkg.com/@nomicfoundation/edr/-/edr-0.3.3.tgz#0ed8619ea2ac644bf87cdc09dd1a8f465a859bcc" + integrity sha512-zP+e+3B1nEUx6bW5BPnIzCQbkhmYfdMBJdiVggTqqTfAA82sOkdOG7wsOMcz5qF3fYfx/irNRM1kgc9HVFIbpQ== optionalDependencies: - "@nomicfoundation/edr-darwin-arm64" "0.3.2" - "@nomicfoundation/edr-darwin-x64" "0.3.2" - "@nomicfoundation/edr-linux-arm64-gnu" "0.3.2" - "@nomicfoundation/edr-linux-arm64-musl" "0.3.2" - "@nomicfoundation/edr-linux-x64-gnu" "0.3.2" - "@nomicfoundation/edr-linux-x64-musl" "0.3.2" - "@nomicfoundation/edr-win32-arm64-msvc" "0.3.2" - "@nomicfoundation/edr-win32-ia32-msvc" "0.3.2" - "@nomicfoundation/edr-win32-x64-msvc" "0.3.2" + "@nomicfoundation/edr-darwin-arm64" "0.3.3" + "@nomicfoundation/edr-darwin-x64" "0.3.3" + "@nomicfoundation/edr-linux-arm64-gnu" "0.3.3" + "@nomicfoundation/edr-linux-arm64-musl" "0.3.3" + "@nomicfoundation/edr-linux-x64-gnu" "0.3.3" + "@nomicfoundation/edr-linux-x64-musl" "0.3.3" + "@nomicfoundation/edr-win32-arm64-msvc" "0.3.3" + "@nomicfoundation/edr-win32-ia32-msvc" "0.3.3" + "@nomicfoundation/edr-win32-x64-msvc" "0.3.3" "@nomicfoundation/ethereumjs-common@4.0.4": version "4.0.4" @@ -4219,15 +4216,15 @@ tslib "^2.0.0" "@peculiar/webcrypto@^1.4.0": - version "1.4.5" - resolved "https://registry.yarnpkg.com/@peculiar/webcrypto/-/webcrypto-1.4.5.tgz#424bed6b0d133b772f5cbffd143d0468a90f40a0" - integrity sha512-oDk93QCDGdxFRM8382Zdminzs44dg3M2+E5Np+JWkpqLDyJC9DviMh8F8mEJkYuUcUOGA5jHO5AJJ10MFWdbZw== + version "1.4.6" + resolved "https://registry.yarnpkg.com/@peculiar/webcrypto/-/webcrypto-1.4.6.tgz#607af294c4f205efeeb172aa32cb20024fe4aecf" + integrity sha512-YBcMfqNSwn3SujUJvAaySy5tlYbYm6tVt9SKoXu8BaTdKGROiJDgPR3TXpZdAKUfklzm3lRapJEAltiMQtBgZg== dependencies: "@peculiar/asn1-schema" "^2.3.8" "@peculiar/json-schema" "^1.1.12" pvtsutils "^1.3.5" tslib "^2.6.2" - webcrypto-core "^1.7.8" + webcrypto-core "^1.7.9" "@pkgjs/parseargs@^0.11.0": version "0.11.0" @@ -4779,75 +4776,85 @@ estree-walker "^2.0.2" picomatch "^2.3.1" -"@rollup/rollup-android-arm-eabi@4.13.0": - version "4.13.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.13.0.tgz#b98786c1304b4ff8db3a873180b778649b5dff2b" - integrity sha512-5ZYPOuaAqEH/W3gYsRkxQATBW3Ii1MfaT4EQstTnLKViLi2gLSQmlmtTpGucNP3sXEpOiI5tdGhjdE111ekyEg== - -"@rollup/rollup-android-arm64@4.13.0": - version "4.13.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.13.0.tgz#8833679af11172b1bf1ab7cb3bad84df4caf0c9e" - integrity sha512-BSbaCmn8ZadK3UAQdlauSvtaJjhlDEjS5hEVVIN3A4bbl3X+otyf/kOJV08bYiRxfejP3DXFzO2jz3G20107+Q== - -"@rollup/rollup-darwin-arm64@4.13.0": - version "4.13.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.13.0.tgz#ef02d73e0a95d406e0eb4fd61a53d5d17775659b" - integrity sha512-Ovf2evVaP6sW5Ut0GHyUSOqA6tVKfrTHddtmxGQc1CTQa1Cw3/KMCDEEICZBbyppcwnhMwcDce9ZRxdWRpVd6g== - -"@rollup/rollup-darwin-x64@4.13.0": - version "4.13.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.13.0.tgz#3ce5b9bcf92b3341a5c1c58a3e6bcce0ea9e7455" - integrity sha512-U+Jcxm89UTK592vZ2J9st9ajRv/hrwHdnvyuJpa5A2ngGSVHypigidkQJP+YiGL6JODiUeMzkqQzbCG3At81Gg== - -"@rollup/rollup-linux-arm-gnueabihf@4.13.0": - version "4.13.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.13.0.tgz#3d3d2c018bdd8e037c6bfedd52acfff1c97e4be4" - integrity sha512-8wZidaUJUTIR5T4vRS22VkSMOVooG0F4N+JSwQXWSRiC6yfEsFMLTYRFHvby5mFFuExHa/yAp9juSphQQJAijQ== - -"@rollup/rollup-linux-arm64-gnu@4.13.0": - version "4.13.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.13.0.tgz#5fc8cc978ff396eaa136d7bfe05b5b9138064143" - integrity sha512-Iu0Kno1vrD7zHQDxOmvweqLkAzjxEVqNhUIXBsZ8hu8Oak7/5VTPrxOEZXYC1nmrBVJp0ZcL2E7lSuuOVaE3+w== - -"@rollup/rollup-linux-arm64-musl@4.13.0": - version "4.13.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.13.0.tgz#f2ae7d7bed416ffa26d6b948ac5772b520700eef" - integrity sha512-C31QrW47llgVyrRjIwiOwsHFcaIwmkKi3PCroQY5aVq4H0A5v/vVVAtFsI1nfBngtoRpeREvZOkIhmRwUKkAdw== - -"@rollup/rollup-linux-riscv64-gnu@4.13.0": - version "4.13.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.13.0.tgz#303d57a328ee9a50c85385936f31cf62306d30b6" - integrity sha512-Oq90dtMHvthFOPMl7pt7KmxzX7E71AfyIhh+cPhLY9oko97Zf2C9tt/XJD4RgxhaGeAraAXDtqxvKE1y/j35lA== - -"@rollup/rollup-linux-x64-gnu@4.13.0": - version "4.13.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.13.0.tgz#f672f6508f090fc73f08ba40ff76c20b57424778" - integrity sha512-yUD/8wMffnTKuiIsl6xU+4IA8UNhQ/f1sAnQebmE/lyQ8abjsVyDkyRkWop0kdMhKMprpNIhPmYlCxgHrPoXoA== - -"@rollup/rollup-linux-x64-musl@4.13.0": - version "4.13.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.13.0.tgz#d2f34b1b157f3e7f13925bca3288192a66755a89" - integrity sha512-9RyNqoFNdF0vu/qqX63fKotBh43fJQeYC98hCaf89DYQpv+xu0D8QFSOS0biA7cGuqJFOc1bJ+m2rhhsKcw1hw== - -"@rollup/rollup-win32-arm64-msvc@4.13.0": - version "4.13.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.13.0.tgz#8ffecc980ae4d9899eb2f9c4ae471a8d58d2da6b" - integrity sha512-46ue8ymtm/5PUU6pCvjlic0z82qWkxv54GTJZgHrQUuZnVH+tvvSP0LsozIDsCBFO4VjJ13N68wqrKSeScUKdA== - -"@rollup/rollup-win32-ia32-msvc@4.13.0": - version "4.13.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.13.0.tgz#a7505884f415662e088365b9218b2b03a88fc6f2" - integrity sha512-P5/MqLdLSlqxbeuJ3YDeX37srC8mCflSyTrUsgbU1c/U9j6l2g2GiIdYaGD9QjdMQPMSgYm7hgg0551wHyIluw== - -"@rollup/rollup-win32-x64-msvc@4.13.0": - version "4.13.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.13.0.tgz#6abd79db7ff8d01a58865ba20a63cfd23d9e2a10" - integrity sha512-UKXUQNbO3DOhzLRwHSpa0HnhhCgNODvfoPWv2FCXme8N/ANFfhIPMGuOT+QuKd16+B5yxZ0HdpNlqPvTMS1qfw== +"@rollup/rollup-android-arm-eabi@4.14.0": + version "4.14.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.14.0.tgz#57936f50d0335e2e7bfac496d209606fa516add4" + integrity sha512-jwXtxYbRt1V+CdQSy6Z+uZti7JF5irRKF8hlKfEnF/xJpcNGuuiZMBvuoYM+x9sr9iWGnzrlM0+9hvQ1kgkf1w== + +"@rollup/rollup-android-arm64@4.14.0": + version "4.14.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.14.0.tgz#81bba83b37382a2d0e30ceced06c8d3d85138054" + integrity sha512-fI9nduZhCccjzlsA/OuAwtFGWocxA4gqXGTLvOyiF8d+8o0fZUeSztixkYjcGq1fGZY3Tkq4yRvHPFxU+jdZ9Q== + +"@rollup/rollup-darwin-arm64@4.14.0": + version "4.14.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.14.0.tgz#a371bd723a5c4c4a33376da72abfc3938066842b" + integrity sha512-BcnSPRM76/cD2gQC+rQNGBN6GStBs2pl/FpweW8JYuz5J/IEa0Fr4AtrPv766DB/6b2MZ/AfSIOSGw3nEIP8SA== + +"@rollup/rollup-darwin-x64@4.14.0": + version "4.14.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.14.0.tgz#8baf2fda277c9729125017c65651296282412886" + integrity sha512-LDyFB9GRolGN7XI6955aFeI3wCdCUszFWumWU0deHA8VpR3nWRrjG6GtGjBrQxQKFevnUTHKCfPR4IvrW3kCgQ== + +"@rollup/rollup-linux-arm-gnueabihf@4.14.0": + version "4.14.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.14.0.tgz#822830a8f7388d5b81d04c69415408d3bab1079b" + integrity sha512-ygrGVhQP47mRh0AAD0zl6QqCbNsf0eTo+vgwkY6LunBcg0f2Jv365GXlDUECIyoXp1kKwL5WW6rsO429DBY/bA== + +"@rollup/rollup-linux-arm64-gnu@4.14.0": + version "4.14.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.14.0.tgz#e20fbe1bd4414c7119f9e0bba8ad17a6666c8365" + integrity sha512-x+uJ6MAYRlHGe9wi4HQjxpaKHPM3d3JjqqCkeC5gpnnI6OWovLdXTpfa8trjxPLnWKyBsSi5kne+146GAxFt4A== + +"@rollup/rollup-linux-arm64-musl@4.14.0": + version "4.14.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.14.0.tgz#13f475596a62e1924f13fe1c8cf2c40e09a99b47" + integrity sha512-nrRw8ZTQKg6+Lttwqo6a2VxR9tOroa2m91XbdQ2sUUzHoedXlsyvY1fN4xWdqz8PKmf4orDwejxXHjh7YBGUCA== + +"@rollup/rollup-linux-powerpc64le-gnu@4.14.0": + version "4.14.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.14.0.tgz#6a431c441420d1c510a205e08c6673355a0a2ea9" + integrity sha512-xV0d5jDb4aFu84XKr+lcUJ9y3qpIWhttO3Qev97z8DKLXR62LC3cXT/bMZXrjLF9X+P5oSmJTzAhqwUbY96PnA== + +"@rollup/rollup-linux-riscv64-gnu@4.14.0": + version "4.14.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.14.0.tgz#53d9448962c3f9ed7a1672269655476ea2d67567" + integrity sha512-SDDhBQwZX6LPRoPYjAZWyL27LbcBo7WdBFWJi5PI9RPCzU8ijzkQn7tt8NXiXRiFMJCVpkuMkBf4OxSxVMizAw== + +"@rollup/rollup-linux-s390x-gnu@4.14.0": + version "4.14.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.14.0.tgz#95f0c133b324da3e7e5c7d12855e0eb71d21a946" + integrity sha512-RxB/qez8zIDshNJDufYlTT0ZTVut5eCpAZ3bdXDU9yTxBzui3KhbGjROK2OYTTor7alM7XBhssgoO3CZ0XD3qA== + +"@rollup/rollup-linux-x64-gnu@4.14.0": + version "4.14.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.14.0.tgz#820ada75c68ead1acc486e41238ca0d8f8531478" + integrity sha512-C6y6z2eCNCfhZxT9u+jAM2Fup89ZjiG5pIzZIDycs1IwESviLxwkQcFRGLjnDrP+PT+v5i4YFvlcfAs+LnreXg== + +"@rollup/rollup-linux-x64-musl@4.14.0": + version "4.14.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.14.0.tgz#ca74f22e125efbe94c1148d989ef93329b464443" + integrity sha512-i0QwbHYfnOMYsBEyjxcwGu5SMIi9sImDVjDg087hpzXqhBSosxkE7gyIYFHgfFl4mr7RrXksIBZ4DoLoP4FhJg== + +"@rollup/rollup-win32-arm64-msvc@4.14.0": + version "4.14.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.14.0.tgz#269023332297051d037a9593dcba92c10fef726b" + integrity sha512-Fq52EYb0riNHLBTAcL0cun+rRwyZ10S9vKzhGKKgeD+XbwunszSY0rVMco5KbOsTlwovP2rTOkiII/fQ4ih/zQ== + +"@rollup/rollup-win32-ia32-msvc@4.14.0": + version "4.14.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.14.0.tgz#d7701438daf964011fd7ca33e3f13f3ff5129e7b" + integrity sha512-e/PBHxPdJ00O9p5Ui43+vixSgVf4NlLsmV6QneGERJ3lnjIua/kim6PRFe3iDueT1rQcgSkYP8ZBBXa/h4iPvw== + +"@rollup/rollup-win32-x64-msvc@4.14.0": + version "4.14.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.14.0.tgz#0bb7ac3cd1c3292db1f39afdabfd03ccea3a3d34" + integrity sha512-aGg7iToJjdklmxlUlJh/PaPNa4PmqHfyRMLunbL3eaMO0gp656+q1zOKkpJ/CVe9CryJv6tAN1HDoR8cNGzkag== "@rushstack/eslint-patch@^1.1.0": - version "1.8.0" - resolved "https://registry.yarnpkg.com/@rushstack/eslint-patch/-/eslint-patch-1.8.0.tgz#c5545e6a5d2bd5c26b4021c357177a28698c950e" - integrity sha512-0HejFckBN2W+ucM6cUOlwsByTKt9/+0tWhqUffNIcHqCXkthY/mZ7AuYPK/2IIaGWhdl0h+tICDO0ssLMd6XMQ== + version "1.10.1" + resolved "https://registry.yarnpkg.com/@rushstack/eslint-patch/-/eslint-patch-1.10.1.tgz#7ca168b6937818e9a74b47ac4e2112b2e1a024cf" + integrity sha512-S3Kq8e7LqxkA9s7HKLqXGTGck1uwis5vAXan3FnU5yw1Ec5hsSGnq4s/UCaSqABPOnOTg7zASLyst7+ohgWexg== "@rushstack/node-core-library@4.0.2": version "4.0.2" @@ -4909,9 +4916,9 @@ integrity sha512-TRlP05KY6t3wjLJ74FiirWlEt3xTclnUQM2YdYto1jx5G1o0meMnugIUZXhzm7Bs3rDEDNhz/aDf2KMSZtoCFg== "@scure/base@~1.1.0", "@scure/base@~1.1.2", "@scure/base@~1.1.4": - version "1.1.5" - resolved "https://registry.yarnpkg.com/@scure/base/-/base-1.1.5.tgz#1d85d17269fe97694b9c592552dd9e5e33552157" - integrity sha512-Brj9FiG2W1MRQSTB212YVPRrcbjkv48FoZi/u4l/zds/ieRrqsh7aUf6CLwkAq61oKXr/ZlTzlY66gLIj3TFTQ== + version "1.1.6" + resolved "https://registry.yarnpkg.com/@scure/base/-/base-1.1.6.tgz#8ce5d304b436e4c84f896e0550c83e4d88cb917d" + integrity sha512-ok9AWwhcgYuGG3Zfhyqg+zwl+Wn5uE+dwC0NV/2qQkx4dABbb/bx96vWu8NSj+BNjjSjno+JRYRjle1jV08k3g== "@scure/bip32@1.1.5": version "1.1.5" @@ -5267,9 +5274,9 @@ buffer "~6.0.3" "@solana/web3.js@^1.70.1": - version "1.91.1" - resolved "https://registry.yarnpkg.com/@solana/web3.js/-/web3.js-1.91.1.tgz#d49d2f982b52070be3b987fd8d892fcbddd064b5" - integrity sha512-cPgjZXm688oM9cULvJ8u2VH6Qp5rvptE1N1VODVxn2mAbpZsWrvWNPjmASkMYT/HzyrtqFkPvFdSHg8Xjt7aQA== + version "1.91.4" + resolved "https://registry.yarnpkg.com/@solana/web3.js/-/web3.js-1.91.4.tgz#b80295ce72aa125930dfc5b41b4b4e3f85fd87fa" + integrity sha512-zconqecIcBqEF6JiM4xYF865Xc4aas+iWK5qnu7nwKPq9ilRYcn+2GiwpYXqUqqBUe0XCO17w18KO0F8h+QATg== dependencies: "@babel/runtime" "^7.23.4" "@noble/curves" "^1.2.0" @@ -6014,9 +6021,9 @@ typescript "5.2.2" "@strapi/ui-primitives@^1.13.0", "@strapi/ui-primitives@^1.13.1", "@strapi/ui-primitives@^1.16.0": - version "1.16.0" - resolved "https://registry.yarnpkg.com/@strapi/ui-primitives/-/ui-primitives-1.16.0.tgz#6156c1493c2929945ed6591ca3f340477be4c4b0" - integrity sha512-ATJPrLI9K/Cq9gGlLa93KAXBdc4rkWRg7GOogOeukohy4a8CzcQTK0l7Lp3EeXSZKwcOU5ohDcC43BXB0eSlyg== + version "1.17.0" + resolved "https://registry.yarnpkg.com/@strapi/ui-primitives/-/ui-primitives-1.17.0.tgz#c8d88ceb03d896b7a9a2f00d07a326284aede24d" + integrity sha512-u5ao5RaOqVD7HUDOdUpBqIOtvw57nV7jpSlckFzyP3Uqv3OGgTvno7Wnr0uAA7ppHISxXcFxQTbWzDbN7ycoQA== dependencies: "@radix-ui/number" "^1.0.1" "@radix-ui/primitive" "^1.0.1" @@ -6037,8 +6044,8 @@ "@radix-ui/react-use-layout-effect" "1.0.1" "@radix-ui/react-use-previous" "^1.0.1" "@radix-ui/react-visually-hidden" "^1.0.3" - aria-hidden "^1.2.3" - react-remove-scroll "^2.5.7" + aria-hidden "^1.2.4" + react-remove-scroll "^2.5.9" "@strapi/utils@4.15.5": version "4.15.5" @@ -6084,14 +6091,14 @@ prop-types "^15.7.2" "@stripe/stripe-js@^3.1.0": - version "3.1.0" - resolved "https://registry.yarnpkg.com/@stripe/stripe-js/-/stripe-js-3.1.0.tgz#bb0458c5cb5e9a835c18f8d353e8980cc66b9959" - integrity sha512-7+ciE35i8NZ6l4FiO1qFkBoZ64ul6B2ZhBVyygB+e/2EZa2WLUyjoxrP53SagnUW7+/q25nDyDLzQq5F0ebOEw== + version "3.2.0" + resolved "https://registry.yarnpkg.com/@stripe/stripe-js/-/stripe-js-3.2.0.tgz#b31a1e823610f49856ae2cd2341a98e6d2d0e883" + integrity sha512-dNu+FLX0UrxM0RVgr4YVc5dJdmC6Wx5Grgi53E28K+n+/aIqVrWkonKtAEPcdkabQz6i/1IpNd93s+UUVSOkcg== "@swc/helpers@^0.5.0": - version "0.5.7" - resolved "https://registry.yarnpkg.com/@swc/helpers/-/helpers-0.5.7.tgz#36c05f61b412abcff3616ecc8634623bcc7c9618" - integrity sha512-BVvNZhx362+l2tSwSuyEUV4h7+jk9raNdoTSdLfwTshXJSaGmYKluGRJznziCI3KX02Z19DdsQrdfrpXAU3Hfg== + version "0.5.8" + resolved "https://registry.yarnpkg.com/@swc/helpers/-/helpers-0.5.8.tgz#65d56b1961487fd99795ffd8c68edb7a591571fb" + integrity sha512-lruDGw3pnfM3wmZHeW7JuhkGQaJjPyiKjxeGhdmfoOT53Ic9qb5JLDNaK2HUdl1zLDeX28H221UvKjfdvSLVMg== dependencies: tslib "^2.4.0" @@ -6207,9 +6214,9 @@ path-browserify "^1.0.1" "@tsconfig/node10@^1.0.7": - version "1.0.9" - resolved "https://registry.yarnpkg.com/@tsconfig/node10/-/node10-1.0.9.tgz#df4907fc07a886922637b15e02d4cebc4c0021b2" - integrity sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA== + version "1.0.11" + resolved "https://registry.yarnpkg.com/@tsconfig/node10/-/node10-1.0.11.tgz#6ee46400685f130e278128c7b38b7e031ff5b2f2" + integrity sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw== "@tsconfig/node12@^1.0.7": version "1.0.11" @@ -6338,9 +6345,9 @@ "@types/chai" "*" "@types/chai@*", "@types/chai@^4.3.3", "@types/chai@^4.3.4": - version "4.3.13" - resolved "https://registry.yarnpkg.com/@types/chai/-/chai-4.3.13.tgz#1b152232b93959829842177cbdfc756ed27e9fd9" - integrity sha512-+LxQEbg4BDUf88utmhpUpTyYn1zHao443aGnXIAQak9ZMt9Rtsic0Oig0OS1xyIqdDXc5uMekoC6NaiUlkT/qA== + version "4.3.14" + resolved "https://registry.yarnpkg.com/@types/chai/-/chai-4.3.14.tgz#ae3055ea2be43c91c9fd700a36d67820026d96e6" + integrity sha512-Wj71sXE4Q4AkGdG9Tvq1u/fquNz9EdG4LIJMwVVII7ashjD/8cf8fyIfJAjRr6YcsXnSE8cOGQPq1gqeR8z+3w== "@types/chrome@^0.0.246": version "0.0.246" @@ -6462,9 +6469,9 @@ "@types/estree" "*" "@types/eslint@*": - version "8.56.6" - resolved "https://registry.yarnpkg.com/@types/eslint/-/eslint-8.56.6.tgz#d5dc16cac025d313ee101108ba5714ea10eb3ed0" - integrity sha512-ymwc+qb1XkjT/gfoQwxIeHZ6ixH23A+tCT2ADSA/DPVKzAjwYkTXBMCQ/f6fe4wEa85Lhp26VPeUxI7wMhAi7A== + version "8.56.7" + resolved "https://registry.yarnpkg.com/@types/eslint/-/eslint-8.56.7.tgz#c33b5b5a9cfb66881beb7b5be6c34aa3e81d3366" + integrity sha512-SjDvI/x3zsZnOkYZ3lCt9lOZWZLB2jIlNKz+LBgCtDurK0JZcwucxYHn1w2BJkD34dgX9Tjnak0txtq4WTggEA== dependencies: "@types/estree" "*" "@types/json-schema" "*" @@ -6507,9 +6514,9 @@ integrity sha512-dNKVfHd/jk0SkR/exKGj2ggkB45MAkzvWCaqLUUgkyjITkGNzH8H+yUwr+BLJUBjZOe9w8X3wgmXhZDRg1ED6A== "@types/filesystem@*": - version "0.0.35" - resolved "https://registry.yarnpkg.com/@types/filesystem/-/filesystem-0.0.35.tgz#6d6766626083e2b397c09bdc57092827120db11d" - integrity sha512-1eKvCaIBdrD2mmMgy5dwh564rVvfEhZTWVQQGRNn0Nt4ZEnJ0C8oSUCzvMKRA4lGde5oEVo+q2MrTTbV/GHDCQ== + version "0.0.36" + resolved "https://registry.yarnpkg.com/@types/filesystem/-/filesystem-0.0.36.tgz#7227c2d76bfed1b21819db310816c7821d303857" + integrity sha512-vPDXOZuannb9FZdxgHnqSwAG/jvdGM8Wq+6N4D/d80z+D4HWH+bItqsZaVRQykAn6WEVeEkLm2oQigyHtgb0RA== dependencies: "@types/filewriter" "*" @@ -6730,11 +6737,6 @@ resolved "https://registry.yarnpkg.com/@types/methods/-/methods-1.1.4.tgz#d3b7ac30ac47c91054ea951ce9eed07b1051e547" integrity sha512-ymXWVrDiCxTBE3+RIrrP533E70eA+9qu7zdWoHuOmGujkYtzf4HQF96b8nwHLqhuf4ykX61IGRIB38CC6/sImQ== -"@types/mime@*": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@types/mime/-/mime-3.0.4.tgz#2198ac274de6017b44d941e00261d5bc6a0e0a45" - integrity sha512-iJt33IQnVRkqeqC7PzBHPTC6fDlRNRW8vjrgqtScAhrmMwe8c4Eo7+fUGTa+XdWrpEgpyKWMYmi2dIwMAYRzPw== - "@types/mime@^1": version "1.3.5" resolved "https://registry.yarnpkg.com/@types/mime/-/mime-1.3.5.tgz#1ef302e01cf7d2b5a0fa526790c9123bf1d06690" @@ -6766,9 +6768,9 @@ integrity sha512-nG96G3Wp6acyAgJqGasjODb+acrI7KltPiRxzHPXnP3NgI28bpQDRv53olbqGXbfcgF5aiiHmO3xpwEpS5Ld9g== "@types/node@*", "@types/node@>=13.7.0", "@types/node@>=8.1.0", "@types/node@^20.11.20": - version "20.11.30" - resolved "https://registry.yarnpkg.com/@types/node/-/node-20.11.30.tgz#9c33467fc23167a347e73834f788f4b9f399d66f" - integrity sha512-dHM6ZxwlmuZaRmUPfv1p+KrdD1Dci04FbdEm/9wEMouFqxYoFl5aMkt0VMAUtYRQDyYvD41WJLukhq/ha3YuTw== + version "20.12.4" + resolved "https://registry.yarnpkg.com/@types/node/-/node-20.12.4.tgz#af5921bd75ccdf3a3d8b3fa75bf3d3359268cd11" + integrity sha512-E+Fa9z3wSQpzgYQdYmme5X3OTuejnnTx88A6p6vkkJosR3KBz+HpE3kqNm98VE6cfLFcISx7zW7MsJkH6KwbTw== dependencies: undici-types "~5.26.4" @@ -6856,14 +6858,14 @@ integrity sha512-+68kP9yzs4LMp7VNh8gdzMSPZFL44MLGqiHWvttYJe+6qnuVr4Ek9wSBQoveqY/r+LwjCcU29kNVkidwim+kYA== "@types/prop-types@*", "@types/prop-types@^15.7.11": - version "15.7.11" - resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.11.tgz#2596fb352ee96a1379c657734d4b913a613ad563" - integrity sha512-ga8y9v9uyeiLdpKddhxYQkxNDrfvuPrlFb0N1qnZZByvcElJaXthF1UhvCh9TLWJBEHeNtdnbysW7Y6Uq8CVng== + version "15.7.12" + resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.12.tgz#12bb1e2be27293c1406acb6af1c3f3a1481d98c6" + integrity sha512-5zvhXYtRNRluoE/jAp4GVsSduVUzNWKkOZrCDBWYtE7biZywwdC2AcEzg+cSMLFRfVgeAFqpfNabiPjxFddV1Q== "@types/qs@*", "@types/qs@^6.2.31", "@types/qs@^6.9.7": - version "6.9.13" - resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.9.13.tgz#c7e2406bdc6bd512f8a3651632568c72d43eb1e7" - integrity sha512-iLR+1vTTJ3p0QaOUq6ACbY1mzKTODFDT/XedZI8BksOotFmL4ForwDfRQ/DZeuTHR7/2i4lI1D203gdfxuqTlA== + version "6.9.14" + resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.9.14.tgz#169e142bfe493895287bee382af6039795e9b75b" + integrity sha512-5khscbd3SwWMhFqylJBLQ0zIu7c1K6Vz0uBIt915BI3zV0q1nfjRQD3RqSBcPaO6PHEF4ov/t9y89fSiyThlPA== "@types/range-parser@*": version "1.2.7" @@ -6871,9 +6873,9 @@ integrity sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ== "@types/react-dom@^18.0.0", "@types/react-dom@^18.2.14": - version "18.2.22" - resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-18.2.22.tgz#d332febf0815403de6da8a97e5fe282cbe609bae" - integrity sha512-fHkBXPeNtfvri6gdsMYyW+dW7RXFo6Ad09nLFK0VQWR7yGLai/Cyvyj696gbwYvBnhGtevUG9cET0pmUbMtoPQ== + version "18.2.24" + resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-18.2.24.tgz#8dda8f449ae436a7a6e91efed8035d4ab03ff759" + integrity sha512-cN6upcKd8zkGy4HU9F1+/s98Hrp6D4MOcippK4PoE8OZRngohHZpbJn1GsaDLz87MqvHNoT13nHvNqM9ocRHZg== dependencies: "@types/react" "*" @@ -6897,12 +6899,11 @@ "@types/react" "*" "@types/react@*", "@types/react@16 || 17 || 18", "@types/react@^18.2.43": - version "18.2.67" - resolved "https://registry.yarnpkg.com/@types/react/-/react-18.2.67.tgz#96b7af0b5e79c756f4bdd981de2ca28472c858e5" - integrity sha512-vkIE2vTIMHQ/xL0rgmuoECBCkZFZeHr49HeWSc24AptMbNRo7pwSBvj73rlJJs9fGKj0koS+V7kQB1jHS0uCgw== + version "18.2.74" + resolved "https://registry.yarnpkg.com/@types/react/-/react-18.2.74.tgz#2d52eb80e4e7c4ea8812c89181d6d590b53f958c" + integrity sha512-9AEqNZZyBx8OdZpxzQlaFEVCSFUM2YXJH46yPOiOpm078k6ZLOCcuAzGum/zK8YBwY+dbahVNbHrbgrAwIRlqw== dependencies: "@types/prop-types" "*" - "@types/scheduler" "*" csstype "^3.0.2" "@types/resolve@1.20.2": @@ -6922,11 +6923,6 @@ resolved "https://registry.yarnpkg.com/@types/retry/-/retry-0.12.0.tgz#2b35eccfcee7d38cd72ad99232fbd58bffb3c84d" integrity sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA== -"@types/scheduler@*": - version "0.16.8" - resolved "https://registry.yarnpkg.com/@types/scheduler/-/scheduler-0.16.8.tgz#ce5ace04cfeabe7ef87c0091e50752e36707deff" - integrity sha512-WZLiwShhwLRmeV6zH+GkbOFT6Z6VklCItrDioxUnv+u4Ll+8vKeFySoFyK/0ctcRpOmwAicELfmys1sDc/Rw+A== - "@types/secp256k1@^4.0.1": version "4.0.6" resolved "https://registry.yarnpkg.com/@types/secp256k1/-/secp256k1-4.0.6.tgz#d60ba2349a51c2cbc5e816dcd831a42029d376bf" @@ -6948,13 +6944,13 @@ "@types/node" "*" "@types/serve-static@*": - version "1.15.5" - resolved "https://registry.yarnpkg.com/@types/serve-static/-/serve-static-1.15.5.tgz#15e67500ec40789a1e8c9defc2d32a896f05b033" - integrity sha512-PDRk21MnK70hja/YF8AHfC7yIsiQHn1rcXx7ijCFBX/k+XQJhQT/gw3xekXKJvx+5SXaMMS8oqQy09Mzvz2TuQ== + version "1.15.7" + resolved "https://registry.yarnpkg.com/@types/serve-static/-/serve-static-1.15.7.tgz#22174bbd74fb97fe303109738e9b5c2f3064f714" + integrity sha512-W8Ym+h8nhuRwaKPaDw34QUkwsGi6Rc4yYqvKFo5rm2FUEhCFbzVWrxXUxuKK8TASjWsysJY0nsmNCGhCOIsrOw== dependencies: "@types/http-errors" "*" - "@types/mime" "*" "@types/node" "*" + "@types/send" "*" "@types/stack-utils@^2.0.0": version "2.0.3" @@ -6962,9 +6958,9 @@ integrity sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw== "@types/superagent@*", "@types/superagent@^8.1.0": - version "8.1.4" - resolved "https://registry.yarnpkg.com/@types/superagent/-/superagent-8.1.4.tgz#f8290d1b7d6081f84f3047851c190c4a3c8cdb21" - integrity sha512-uzSBYwrpal8y2X2Pul5ZSWpzRiDha2FLcquaN95qUPnOjYgm/zQ5LIdqeJpQJTRWNTN+Rhm0aC8H06Ds2rqCYw== + version "8.1.6" + resolved "https://registry.yarnpkg.com/@types/superagent/-/superagent-8.1.6.tgz#e660543b1a4b7c7473caec4799de87ff68216270" + integrity sha512-yzBOv+6meEHSzV2NThYYOA6RtqvPr3Hbob9ZLp3i07SH27CrYVfm8CrF7ydTmidtelsFiKx2I4gZAiAOamGgvQ== dependencies: "@types/cookiejar" "^2.1.5" "@types/methods" "^1.1.4" @@ -7241,10 +7237,10 @@ dependencies: "@ucast/core" "^1.4.1" -"@uiw/codemirror-extensions-basic-setup@4.21.24": - version "4.21.24" - resolved "https://registry.yarnpkg.com/@uiw/codemirror-extensions-basic-setup/-/codemirror-extensions-basic-setup-4.21.24.tgz#b936c3daff0100e1a3d5b0500478747cfc80f7db" - integrity sha512-TJYKlPxNAVJNclW1EGumhC7I02jpdMgBon4jZvb5Aju9+tUzS44IwORxUx8BD8ZtH2UHmYS+04rE3kLk/BtnCQ== +"@uiw/codemirror-extensions-basic-setup@4.21.25": + version "4.21.25" + resolved "https://registry.yarnpkg.com/@uiw/codemirror-extensions-basic-setup/-/codemirror-extensions-basic-setup-4.21.25.tgz#eb0605ac16b18a4d673cf475d81c4d960eaa6ef1" + integrity sha512-eeUKlmEE8aSoSgelS8OR2elcPGntpRo669XinAqPCLa0eKorT2B0d3ts+AE+njAeGk744tiyAEbHb2n+6OQmJw== dependencies: "@codemirror/autocomplete" "^6.0.0" "@codemirror/commands" "^6.0.0" @@ -7255,15 +7251,15 @@ "@codemirror/view" "^6.0.0" "@uiw/react-codemirror@^4.21.20", "@uiw/react-codemirror@^4.21.24": - version "4.21.24" - resolved "https://registry.yarnpkg.com/@uiw/react-codemirror/-/react-codemirror-4.21.24.tgz#38b05e0a24d2307313b2e73390b20d0251837170" - integrity sha512-8zs5OuxbhikHocHBsVBMuW1vqlv4ccZAkt4rFwr7ebLP2Q6RwHsjpsR9GeGyAigAqonKRoeHugqF78UMrkaTgg== + version "4.21.25" + resolved "https://registry.yarnpkg.com/@uiw/react-codemirror/-/react-codemirror-4.21.25.tgz#1efb7737b907fde6f8f7552b5f43b33eec0b7a86" + integrity sha512-mBrCoiffQ+hbTqV1JoixFEcH7BHXkS3PjTyNH7dE8Gzf3GSBRazhtSM5HrAFIiQ5FIRGFs8Gznc4UAdhtevMmw== dependencies: "@babel/runtime" "^7.18.6" "@codemirror/commands" "^6.1.0" "@codemirror/state" "^6.1.1" "@codemirror/theme-one-dark" "^6.0.0" - "@uiw/codemirror-extensions-basic-setup" "4.21.24" + "@uiw/codemirror-extensions-basic-setup" "4.21.25" codemirror "^6.0.0" "@ungap/structured-clone@^1.2.0": @@ -7271,10 +7267,10 @@ resolved "https://registry.yarnpkg.com/@ungap/structured-clone/-/structured-clone-1.2.0.tgz#756641adb587851b5ccb3e095daf27ae581c8406" integrity sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ== -"@vercel/build-utils@7.9.1": - version "7.9.1" - resolved "https://registry.yarnpkg.com/@vercel/build-utils/-/build-utils-7.9.1.tgz#b55180aa0ac5e88a9754152f7b09b74098c590a4" - integrity sha512-yqbP7d8oLAGkh5iy9/Vu1c0+s5jLFK56QHEZlkj1lY3t3OQ+7dsAi0oUP/gv8YxtUYwMDfeYSqZr/4cNhnSBsg== +"@vercel/build-utils@7.11.0": + version "7.11.0" + resolved "https://registry.yarnpkg.com/@vercel/build-utils/-/build-utils-7.11.0.tgz#98d052c215033221ae87572fd97be6dc2884e500" + integrity sha512-UFrx1hNIjNJJkd0NZrYfaOrmcWhQmrVsbKe9o3L9jX9J1iufG685wIZ9tFCKKC0Fa2HWbNDNzNxrE5SCAS2lyA== "@vercel/error-utils@2.0.2": version "2.0.2" @@ -7299,16 +7295,16 @@ node-gyp-build "^4.2.2" resolve-from "^5.0.0" -"@vercel/node@^3.0.24": - version "3.0.24" - resolved "https://registry.yarnpkg.com/@vercel/node/-/node-3.0.24.tgz#84a05c63be1421806cde7e237d5c050bacc6f661" - integrity sha512-2EbC6zsoaj2HH97BZYdkqHNeQ3gpcsETHXySSslkylU1uTAZU5i4c+Ze+RIinVkk7P+DVv4XzDK6xaSHvkXkGA== +"@vercel/node@^3.0.26": + version "3.0.26" + resolved "https://registry.yarnpkg.com/@vercel/node/-/node-3.0.26.tgz#942f2fe31c727523876fe5eb601e870347f1ba98" + integrity sha512-PoyacnoylwpE3+7RFUVHJlbPqtneTCEJVXXx4n8g9ARgUDSRSCwFpJOhiFQon2sS2YtfCzsJa29Z9dAZQedDcQ== dependencies: "@edge-runtime/node-utils" "2.3.0" "@edge-runtime/primitives" "4.1.0" "@edge-runtime/vm" "3.2.0" "@types/node" "14.18.33" - "@vercel/build-utils" "7.9.1" + "@vercel/build-utils" "7.11.0" "@vercel/error-utils" "2.0.2" "@vercel/nft" "0.26.4" "@vercel/static-config" "3.0.0" @@ -7838,7 +7834,7 @@ resolved "https://registry.yarnpkg.com/@web3-storage/multipart-parser/-/multipart-parser-1.0.0.tgz#6b69dc2a32a5b207ba43e556c25cc136a56659c4" integrity sha512-BEO6al7BYqcnfX15W2cnGR+Q566ACXAT9UQykORCWW80lmkpWsnEob6zJS1ZVBKsSJC8+7vJkHwlp+lXG1UCdw== -"@webassemblyjs/ast@1.12.1", "@webassemblyjs/ast@^1.11.5": +"@webassemblyjs/ast@1.12.1", "@webassemblyjs/ast@^1.11.5", "@webassemblyjs/ast@^1.12.1": version "1.12.1" resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.12.1.tgz#bb16a0e8b1914f979f45864c23819cc3e3f0d4bb" integrity sha512-EKfMUOPRRUTy5UII4qJDGPpqfwjOmZ5jeGFwid9mnoqIFK+e0vqoi1qH56JpmZSzEL53jKnNzScdmftJyG5xWg== @@ -7904,7 +7900,7 @@ resolved "https://registry.yarnpkg.com/@webassemblyjs/utf8/-/utf8-1.11.6.tgz#90f8bc34c561595fe156603be7253cdbcd0fab5a" integrity sha512-vtXf2wTQ3+up9Zsg8sa2yWiQpzSsMyXj0qViVP6xKGCUT8p8YJ6HqI7l5eCnWx1T/FYdsv07HQs2wTFbbof/RA== -"@webassemblyjs/wasm-edit@^1.11.5": +"@webassemblyjs/wasm-edit@^1.11.5", "@webassemblyjs/wasm-edit@^1.12.1": version "1.12.1" resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-edit/-/wasm-edit-1.12.1.tgz#9f9f3ff52a14c980939be0ef9d5df9ebc678ae3b" integrity sha512-1DuwbVvADvS5mGnXbE+c9NfA8QRcZ6iKquqjjmR10k6o+zzsRVesil54DKexiowcFCPdr/Q0qaMgB01+SQ1u6g== @@ -7939,7 +7935,7 @@ "@webassemblyjs/wasm-gen" "1.12.1" "@webassemblyjs/wasm-parser" "1.12.1" -"@webassemblyjs/wasm-parser@1.12.1", "@webassemblyjs/wasm-parser@^1.11.5": +"@webassemblyjs/wasm-parser@1.12.1", "@webassemblyjs/wasm-parser@^1.11.5", "@webassemblyjs/wasm-parser@^1.12.1": version "1.12.1" resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-parser/-/wasm-parser-1.12.1.tgz#c47acb90e6f083391e3fa61d113650eea1e95937" integrity sha512-xikIi7c2FHXysxXe3COrVUPSheuBtpcfhbpFj4gmu7KRLYOzANztwUU0IbsqvMqzuNK2+glRGWCEqZo1WCLyAQ== @@ -8121,9 +8117,9 @@ acorn-import-assertions@^1.7.6, acorn-import-assertions@^1.9.0: integrity sha512-cmMwop9x+8KFhxvKrKfPYmN6/pKTYYHBqLa0DfvVZcKMJWNyWLnaqND7dx/qn66R7ewM1UX5XMaDVP5wlVTaVA== acorn-import-attributes@^1.9.2: - version "1.9.2" - resolved "https://registry.yarnpkg.com/acorn-import-attributes/-/acorn-import-attributes-1.9.2.tgz#bc3765bca6ec0c5c69d2293afd582216215279b0" - integrity sha512-O+nfJwNolEA771IYJaiLWK1UAwjNsQmZbTRqqwBYxCgVQTmpFEMvBw6LOIQV0Me339L5UMVYFyRohGnGlQDdIQ== + version "1.9.5" + resolved "https://registry.yarnpkg.com/acorn-import-attributes/-/acorn-import-attributes-1.9.5.tgz#7eb1557b1ba05ef18b5ed0ec67591bfab04688ef" + integrity sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ== acorn-jsx@^5.3.2: version "5.3.2" @@ -8287,11 +8283,9 @@ ansi-escapes@^4.2.1, ansi-escapes@^4.3.0, ansi-escapes@^4.3.2: type-fest "^0.21.3" ansi-escapes@^6.2.0: - version "6.2.0" - resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-6.2.0.tgz#8a13ce75286f417f1963487d86ba9f90dccf9947" - integrity sha512-kzRaCqXnpzWs+3z5ABPQiVke+iq0KXkHo8xiWV4RPTi5Yli0l97BEQuhXV1s7+aSU/fu1kUuxgS4MsQ0fRuygw== - dependencies: - type-fest "^3.0.0" + version "6.2.1" + resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-6.2.1.tgz#76c54ce9b081dad39acec4b5d53377913825fb0f" + integrity sha512-4nJ3yixlEthEJ9Rk4vPcdBRkZvQZlYyu8j4/Mqz5sgIkddmEnH2Yj2ZrnP9S3tQOvSNRUIgVNF/1yPpRAGNRig== ansi-html-community@0.0.8, ansi-html-community@^0.0.8: version "0.0.8" @@ -8445,10 +8439,10 @@ argparse@^2.0.1: resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== -aria-hidden@^1.1.1, aria-hidden@^1.2.3: - version "1.2.3" - resolved "https://registry.yarnpkg.com/aria-hidden/-/aria-hidden-1.2.3.tgz#14aeb7fb692bbb72d69bebfa47279c1fd725e954" - integrity sha512-xcLxITLe2HYa1cnYnwCjkOO1PqUHQpozB8x9AR0OgWN2woOBi5kSDVxKfd0b7sb1hw5qFeJhXm9H1nu3xSfLeQ== +aria-hidden@^1.1.1, aria-hidden@^1.2.3, aria-hidden@^1.2.4: + version "1.2.4" + resolved "https://registry.yarnpkg.com/aria-hidden/-/aria-hidden-1.2.4.tgz#b78e383fdbc04d05762c78b4a25a501e736c4522" + integrity sha512-y+CcFFwelSXpLZk/7fMB2mUbGtX9lKycf1MWJ7CaTIERyitVlyQx6C+sxcROU2BAJ24OiZyK+8wj2i8AlBoS3A== dependencies: tslib "^2.0.0" @@ -8520,14 +8514,15 @@ array-from@^2.1.1: integrity sha512-GQTc6Uupx1FCavi5mPzBvVT7nEOeWMmUA9P95wpfpW1XwMSKs+KaymD5C2Up7KAUKg/mYwbsUYzdZWcoajlNZg== array-includes@^3.1.6, array-includes@^3.1.7: - version "3.1.7" - resolved "https://registry.yarnpkg.com/array-includes/-/array-includes-3.1.7.tgz#8cd2e01b26f7a3086cbc87271593fe921c62abda" - integrity sha512-dlcsNBIiWhPkHdOEEKnehA+RNUWDc4UqFtnIXU4uuYDPtA4LDkr7qip2p0VvFAEXNDr0yWZ9PJyIRiGjRLQzwQ== + version "3.1.8" + resolved "https://registry.yarnpkg.com/array-includes/-/array-includes-3.1.8.tgz#5e370cbe172fdd5dd6530c1d4aadda25281ba97d" + integrity sha512-itaWrbYbqpGXkGhZPGUulwnhVf5Hpy1xiCFsGqyIGglbBxmG5vSjxQen3/WGOjPpNEv1RtBLKxbmVXm8HpJStQ== dependencies: - call-bind "^1.0.2" - define-properties "^1.2.0" - es-abstract "^1.22.1" - get-intrinsic "^1.2.1" + call-bind "^1.0.7" + define-properties "^1.2.1" + es-abstract "^1.23.2" + es-object-atoms "^1.0.0" + get-intrinsic "^1.2.4" is-string "^1.0.7" array-slice@^1.0.0: @@ -8777,9 +8772,9 @@ available-typed-arrays@^1.0.7: possible-typed-array-names "^1.0.0" aws-sdk@^2.1528.0: - version "2.1581.0" - resolved "https://registry.yarnpkg.com/aws-sdk/-/aws-sdk-2.1581.0.tgz#dea12e848aaf619fe96ec54c2901819dde3fc5b1" - integrity sha512-lWvpj36dL0HC8i4l8N0NnV2ljedOrij1Ox0SjHwQTIvePes4lNm+khOLV0T7NDA1C6igsGozBO+8OcNGPDhfAw== + version "2.1592.0" + resolved "https://registry.yarnpkg.com/aws-sdk/-/aws-sdk-2.1592.0.tgz#082703581bed09e2fe07cc71595a252e6170e10b" + integrity sha512-iwmS46jOEHMNodfrpNBJ5eHwjKAY05t/xYV2cp+KyzMX2yGgt2/EtWWnlcoMGBKR31qKTsjMj5ZPouC9/VeDOA== dependencies: buffer "4.9.2" events "1.1.1" @@ -8986,29 +8981,28 @@ balanced-match@^1.0.0: integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== bare-events@^2.0.0, bare-events@^2.2.0: - version "2.2.1" - resolved "https://registry.yarnpkg.com/bare-events/-/bare-events-2.2.1.tgz#7b6d421f26a7a755e20bf580b727c84b807964c1" - integrity sha512-9GYPpsPFvrWBkelIhOhTWtkeZxVxZOdb3VnFTCzlOo3OjvmTvzLoZFUT8kNFACx0vJej6QPney1Cf9BvzCNE/A== + version "2.2.2" + resolved "https://registry.yarnpkg.com/bare-events/-/bare-events-2.2.2.tgz#a98a41841f98b2efe7ecc5c5468814469b018078" + integrity sha512-h7z00dWdG0PYOQEvChhOSWvOfkIKsdZGkWr083FgN/HyoQuebSew/cgirYqh9SCuy/hRvxc5Vy6Fw8xAmYHLkQ== bare-fs@^2.1.1: - version "2.2.2" - resolved "https://registry.yarnpkg.com/bare-fs/-/bare-fs-2.2.2.tgz#286bf54cc6f15f613bee6bb26f0c61c79fb14f06" - integrity sha512-X9IqgvyB0/VA5OZJyb5ZstoN62AzD7YxVGog13kkfYWYqJYcK0kcqLZ6TrmH5qr4/8//ejVcX4x/a0UvaogXmA== + version "2.2.3" + resolved "https://registry.yarnpkg.com/bare-fs/-/bare-fs-2.2.3.tgz#34f8b81b8c79de7ef043383c05e57d4a10392a68" + integrity sha512-amG72llr9pstfXOBOHve1WjiuKKAMnebcmMbPWDZ7BCevAoJLpugjuAPRsDINEyjT0a6tbaVx3DctkXIRbLuJw== dependencies: bare-events "^2.0.0" - bare-os "^2.0.0" bare-path "^2.0.0" streamx "^2.13.0" -bare-os@^2.0.0, bare-os@^2.1.0: +bare-os@^2.1.0: version "2.2.1" resolved "https://registry.yarnpkg.com/bare-os/-/bare-os-2.2.1.tgz#c94a258c7a408ca6766399e44675136c0964913d" integrity sha512-OwPyHgBBMkhC29Hl3O4/YfxW9n7mdTr2+SsO29XBWKKJsbgj3mnorDB80r5TiCQgQstgE5ga1qNYrpes6NvX2w== bare-path@^2.0.0, bare-path@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/bare-path/-/bare-path-2.1.0.tgz#830f17fd39842813ca77d211ebbabe238a88cb4c" - integrity sha512-DIIg7ts8bdRKwJRJrUMy/PICEaQZaPGZ26lsSx9MJSwIhSrcdHn7/C8W+XmnG/rKi6BaRcz+JO00CjZteybDtw== + version "2.1.1" + resolved "https://registry.yarnpkg.com/bare-path/-/bare-path-2.1.1.tgz#111db5bf2db0aed40081aa4ba38b8dfc2bb782eb" + integrity sha512-OHM+iwRDRMDBsSW7kl3dO62JyHdBKO3B25FB9vNQBPcGHMo4+eA8Yj41Lfbk3pS/seDY+siNge0LdRTulAau/A== dependencies: bare-os "^2.1.0" @@ -9316,6 +9310,11 @@ brorand@^1.0.1, brorand@^1.1.0: resolved "https://registry.yarnpkg.com/brorand/-/brorand-1.1.0.tgz#12c25efe40a45e3c323eb8675a0a0ce57b22371f" integrity sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w== +brotli-wasm@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/brotli-wasm/-/brotli-wasm-2.0.1.tgz#2b3f4dc3db0c3e60d2635c055e6bac4ddf4bd3f5" + integrity sha512-+3USgYsC7bzb5yU0/p2HnnynZl0ak0E6uoIm4UW4Aby/8s8HFCq6NCfrrf1E9c3O8OCSzq3oYO1tUVqIi61Nww== + browser-or-node@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/browser-or-node/-/browser-or-node-2.1.1.tgz#738790b3a86a8fc020193fa581273fbe65eaea0f" @@ -9686,9 +9685,9 @@ camelize@^1.0.0: integrity sha512-dU+Tx2fsypxTgtLoE36npi3UqcjSSMNYfkqgmoEhtZrraP5VWq0K7FkWVTYa8eMPtnU/G2txVsfdCJTn9uzpuQ== caniuse-lite@^1.0.30001587: - version "1.0.30001599" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001599.tgz#571cf4f3f1506df9bf41fcbb6d10d5d017817bce" - integrity sha512-LRAQHZ4yT1+f9LemSMeqdMpMxZcc4RMWdj4tiFe3G8tNkWK+E58g+/tzotb5cU6TbcVJLr4fySiAW7XmxQvZQA== + version "1.0.30001605" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001605.tgz#ca12d7330dd8bcb784557eb9aa64f0037870d9d6" + integrity sha512-nXwGlFWo34uliI9z3n6Qc0wZaf7zaZWA1CPZ169La5mV3I/gem7bst0vr5XQH5TJXZIMfDeZyOrZnSlVzKxxHQ== carbites@^1.0.6: version "1.0.6" @@ -10575,9 +10574,9 @@ convert-source-map@^2.0.0: integrity sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg== cookie-es@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/cookie-es/-/cookie-es-1.0.0.tgz#4759684af168dfc54365b2c2dda0a8d7ee1e4865" - integrity sha512-mWYvfOLrfEc996hlKcdABeIiPHUPC6DM2QYZdGGOvhOTbA3tjm2eBwqlJpoFdjC89NI4Qt6h0Pu06Mp+1Pj5OQ== + version "1.1.0" + resolved "https://registry.yarnpkg.com/cookie-es/-/cookie-es-1.1.0.tgz#68f8d9f48aeb5a534f3896f80e792760d3d20def" + integrity sha512-L2rLOcK0wzWSfSDA33YR+PUHDG10a8px7rUHKWbGLP4YfbsMed2KFUw5fczvDPbT98DDe3LEzviswl810apTEw== cookie-parser@^1.4.6: version "1.4.6" @@ -10607,11 +10606,6 @@ cookie@0.4.1: resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.1.tgz#afd713fe26ebd21ba95ceb61f9a8116e50a537d1" integrity sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA== -cookie@0.5.0, cookie@^0.5.0: - version "0.5.0" - resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.5.0.tgz#d1f5d71adec6558c58f389987c366aa47e994f8b" - integrity sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw== - cookie@0.6.0: version "0.6.0" resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.6.0.tgz#2798b04b071b0ecbff0dbb62a505a8efa4e19051" @@ -10622,6 +10616,11 @@ cookie@^0.4.1: resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.2.tgz#0e41f24de5ecf317947c82fc789e06a884824432" integrity sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA== +cookie@^0.5.0: + version "0.5.0" + resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.5.0.tgz#d1f5d71adec6558c58f389987c366aa47e994f8b" + integrity sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw== + cookiejar@^2.1.4: version "2.1.4" resolved "https://registry.yarnpkg.com/cookiejar/-/cookiejar-2.1.4.tgz#ee669c1fea2cf42dc31585469d193fef0d65771b" @@ -10888,15 +10887,15 @@ css-color-keywords@^1.0.0: integrity sha512-FyyrDHZKEjXDpNJYvVsV960FiqQyXc/LlYmsxl2BcdMb2WPx0OGRVgTg55rPSyLSNMqP52R9r8geSp7apN3Ofg== css-loader@^6.8.1: - version "6.10.0" - resolved "https://registry.yarnpkg.com/css-loader/-/css-loader-6.10.0.tgz#7c172b270ec7b833951b52c348861206b184a4b7" - integrity sha512-LTSA/jWbwdMlk+rhmElbDR2vbtQoTBPr7fkJE+mxrHj+7ru0hUmHafDRzWIjIHTwpitWVaqY2/UWGRca3yUgRw== + version "6.11.0" + resolved "https://registry.yarnpkg.com/css-loader/-/css-loader-6.11.0.tgz#33bae3bf6363d0a7c2cf9031c96c744ff54d85ba" + integrity sha512-CTJ+AEQJjq5NzLga5pE39qdiSV56F8ywCIsqNIRF0r7BDgWsN25aazToqAFg7ZrtA/U016xudB3ffgweORxX7g== dependencies: icss-utils "^5.1.0" postcss "^8.4.33" - postcss-modules-extract-imports "^3.0.0" - postcss-modules-local-by-default "^4.0.4" - postcss-modules-scope "^3.1.1" + postcss-modules-extract-imports "^3.1.0" + postcss-modules-local-by-default "^4.0.5" + postcss-modules-scope "^3.2.0" postcss-modules-values "^4.0.0" postcss-value-parser "^4.2.0" semver "^7.5.4" @@ -11412,7 +11411,7 @@ des.js@^1.0.0: inherits "^2.0.1" minimalistic-assert "^1.0.0" -destr@^2.0.1, destr@^2.0.3: +destr@^2.0.3: version "2.0.3" resolved "https://registry.yarnpkg.com/destr/-/destr-2.0.3.tgz#7f9e97cb3d16dbdca7be52aca1644ce402cfe449" integrity sha512-2N3BOUU4gYMpTP24s5rF5iP7BDr7uNTCs4ozw3kf/eKfvWSIu93GEBi5m427YoyJoeOzQ5smuu4nNAPGb8idSQ== @@ -11739,12 +11738,7 @@ dotenv@14.2.0: resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-14.2.0.tgz#7e77fd5dd6cff5942c4496e1acf2d0f37a9e67aa" integrity sha512-05POuPJyPpO6jqzTNweQFfAyMSD4qa4lvsMOWyTRTdpHKy6nnnN+IYWaXF+lHivhBH/ufDKlR4IWCAN3oPnHuw== -dotenv@16.4.1: - version "16.4.1" - resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.4.1.tgz#1d9931f1d3e5d2959350d1250efab299561f7f11" - integrity sha512-CjA3y+Dr3FyFDOAMnxZEGtnW9KBR2M0JvvUtXNW+dYJL5ROWxP9DUHCwgFqpMk0OXCc0ljhaNTr2w/kutYIcHQ== - -dotenv@^16.0.3, dotenv@^16.3.0, dotenv@^16.3.2: +dotenv@16.4.5, dotenv@^16.0.3, dotenv@^16.3.0, dotenv@^16.3.2: version "16.4.5" resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.4.5.tgz#cdd3b3b604cb327e286b4762e13502f717cb099f" integrity sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg== @@ -11816,9 +11810,9 @@ electron-fetch@^1.7.2: encoding "^0.1.13" electron-to-chromium@^1.4.668: - version "1.4.711" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.711.tgz#f9fd04007878cc27ac327d5c6ce300f8b516f635" - integrity sha512-hRg81qzvUEibX2lDxnFlVCHACa+LtrCPIsWAxo161LDYIB3jauf57RGsMZV9mvGwE98yGH06icj3zBEoOkxd/w== + version "1.4.726" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.726.tgz#9ca95f19e9a0d63675e838b24681182203e40a30" + integrity sha512-xtjfBXn53RORwkbyKvDfTajtnTp0OJoPOIBzXvkNbb7+YYvCHJflba3L7Txyx/6Fov3ov2bGPr/n5MTixmPhdQ== elliptic@6.5.4: version "6.5.4" @@ -11905,7 +11899,7 @@ end-of-stream@^1.0.0, end-of-stream@^1.1.0, end-of-stream@^1.4.1: dependencies: once "^1.4.0" -enhanced-resolve@^5.0.0, enhanced-resolve@^5.12.0, enhanced-resolve@^5.14.0, enhanced-resolve@^5.15.0, enhanced-resolve@^5.7.0: +enhanced-resolve@^5.0.0, enhanced-resolve@^5.12.0, enhanced-resolve@^5.14.0, enhanced-resolve@^5.16.0, enhanced-resolve@^5.7.0: version "5.16.0" resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.16.0.tgz#65ec88778083056cb32487faa9aef82ed0864787" integrity sha512-O+QWCviPNSSLAD9Ucn8Awv+poAkqn3T1XY5/N7kR7rQO9yfSGWkYZDwpJ+iKF7B8rxaQKWngSqACpgzeapSyoA== @@ -11967,57 +11961,10 @@ error-stack-parser@^2.0.6: dependencies: stackframe "^1.3.4" -es-abstract@^1.22.1, es-abstract@^1.22.3: - version "1.22.5" - resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.22.5.tgz#1417df4e97cc55f09bf7e58d1e614bc61cb8df46" - integrity sha512-oW69R+4q2wG+Hc3KZePPZxOiisRIqfKBVo/HLx94QcJeWGU/8sZhCvc829rd1kS366vlJbzBfXf9yWwf0+Ko7w== - dependencies: - array-buffer-byte-length "^1.0.1" - arraybuffer.prototype.slice "^1.0.3" - available-typed-arrays "^1.0.7" - call-bind "^1.0.7" - es-define-property "^1.0.0" - es-errors "^1.3.0" - es-set-tostringtag "^2.0.3" - es-to-primitive "^1.2.1" - function.prototype.name "^1.1.6" - get-intrinsic "^1.2.4" - get-symbol-description "^1.0.2" - globalthis "^1.0.3" - gopd "^1.0.1" - has-property-descriptors "^1.0.2" - has-proto "^1.0.3" - has-symbols "^1.0.3" - hasown "^2.0.1" - internal-slot "^1.0.7" - is-array-buffer "^3.0.4" - is-callable "^1.2.7" - is-negative-zero "^2.0.3" - is-regex "^1.1.4" - is-shared-array-buffer "^1.0.3" - is-string "^1.0.7" - is-typed-array "^1.1.13" - is-weakref "^1.0.2" - object-inspect "^1.13.1" - object-keys "^1.1.1" - object.assign "^4.1.5" - regexp.prototype.flags "^1.5.2" - safe-array-concat "^1.1.0" - safe-regex-test "^1.0.3" - string.prototype.trim "^1.2.8" - string.prototype.trimend "^1.0.7" - string.prototype.trimstart "^1.0.7" - typed-array-buffer "^1.0.2" - typed-array-byte-length "^1.0.1" - typed-array-byte-offset "^1.0.2" - typed-array-length "^1.0.5" - unbox-primitive "^1.0.2" - which-typed-array "^1.1.14" - -es-abstract@^1.23.0, es-abstract@^1.23.1, es-abstract@^1.23.2: - version "1.23.2" - resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.23.2.tgz#693312f3940f967b8dd3eebacb590b01712622e0" - integrity sha512-60s3Xv2T2p1ICykc7c+DNDPLDMm9t4QxCOUU0K9JxiLjM3C1zB9YVdN7tjxrFd4+AkZ8CdX1ovUga4P2+1e+/w== +es-abstract@^1.22.1, es-abstract@^1.22.3, es-abstract@^1.23.0, es-abstract@^1.23.1, es-abstract@^1.23.2: + version "1.23.3" + resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.23.3.tgz#8f0c5a35cd215312573c5a27c87dfd6c881a0aa0" + integrity sha512-e+HfNH61Bj1X9/jLc5v1owaLYuHdeHHSQlkhCBiTK8rBvKaULl/beGMxwrMXjpYrv4pz22BlY570vVePA2ho4A== dependencies: array-buffer-byte-length "^1.0.1" arraybuffer.prototype.slice "^1.0.3" @@ -12058,11 +12005,11 @@ es-abstract@^1.23.0, es-abstract@^1.23.1, es-abstract@^1.23.2: safe-regex-test "^1.0.3" string.prototype.trim "^1.2.9" string.prototype.trimend "^1.0.8" - string.prototype.trimstart "^1.0.7" + string.prototype.trimstart "^1.0.8" typed-array-buffer "^1.0.2" typed-array-byte-length "^1.0.1" typed-array-byte-offset "^1.0.2" - typed-array-length "^1.0.5" + typed-array-length "^1.0.6" unbox-primitive "^1.0.2" which-typed-array "^1.1.15" @@ -12113,11 +12060,16 @@ es-iterator-helpers@^1.0.15, es-iterator-helpers@^1.0.17: iterator.prototype "^1.1.2" safe-array-concat "^1.1.2" -es-module-lexer@1.4.1, es-module-lexer@^1.2.1: +es-module-lexer@1.4.1: version "1.4.1" resolved "https://registry.yarnpkg.com/es-module-lexer/-/es-module-lexer-1.4.1.tgz#41ea21b43908fe6a287ffcbe4300f790555331f5" integrity sha512-cXLGjP0c4T3flZJKQSuziYoq7MlT+rnvfZjfp7h+I7K9BNX54kP9nyWvdbwjQ4u1iWbOL4u96fgeZLToQlZC7w== +es-module-lexer@^1.2.1: + version "1.5.0" + resolved "https://registry.yarnpkg.com/es-module-lexer/-/es-module-lexer-1.5.0.tgz#4878fee3789ad99e065f975fdd3c645529ff0236" + integrity sha512-pqrTKmwEIgafsYZAGw9kszYzmagcE/n4dbgwGWLEXg7J4QFJVQRBld8j3Q3GNez79jzxZshq0bcT962QHOghjw== + es-object-atoms@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/es-object-atoms/-/es-object-atoms-1.0.0.tgz#ddb55cd47ac2e240701260bc2a8e31ecb643d941" @@ -13239,44 +13191,7 @@ express-session@^1.17.3: safe-buffer "5.2.1" uid-safe "~2.1.5" -express@4.18.3: - version "4.18.3" - resolved "https://registry.yarnpkg.com/express/-/express-4.18.3.tgz#6870746f3ff904dee1819b82e4b51509afffb0d4" - integrity sha512-6VyCijWQ+9O7WuVMTRBTl+cjNNIzD5cY5mQ1WM8r/LEkI2u8EYpOotESNwzNlyCn3g+dmjKYI6BmNneSr/FSRw== - dependencies: - accepts "~1.3.8" - array-flatten "1.1.1" - body-parser "1.20.2" - content-disposition "0.5.4" - content-type "~1.0.4" - cookie "0.5.0" - cookie-signature "1.0.6" - debug "2.6.9" - depd "2.0.0" - encodeurl "~1.0.2" - escape-html "~1.0.3" - etag "~1.8.1" - finalhandler "1.2.0" - fresh "0.5.2" - http-errors "2.0.0" - merge-descriptors "1.0.1" - methods "~1.1.2" - on-finished "2.4.1" - parseurl "~1.3.3" - path-to-regexp "0.1.7" - proxy-addr "~2.0.7" - qs "6.11.0" - range-parser "~1.2.1" - safe-buffer "5.2.1" - send "0.18.0" - serve-static "1.15.0" - setprototypeof "1.2.0" - statuses "2.0.1" - type-is "~1.6.18" - utils-merge "1.0.1" - vary "~1.1.2" - -express@^4.19.2: +express@4.19.2, express@^4.19.2: version "4.19.2" resolved "https://registry.yarnpkg.com/express/-/express-4.19.2.tgz#e25437827a3aa7f2a827bc8171bbbb664a356465" integrity sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q== @@ -13958,9 +13873,9 @@ get-intrinsic@^1.1.3, get-intrinsic@^1.2.1, get-intrinsic@^1.2.2, get-intrinsic@ hasown "^2.0.0" get-it@^8.0.9: - version "8.4.15" - resolved "https://registry.yarnpkg.com/get-it/-/get-it-8.4.15.tgz#24b0a05a5e371713338b18376deb25c0c93b7a7f" - integrity sha512-UcFJxHQcEgZdP6ZoUUP/95HlLKPaysrL82UO81qEUre/WO8vxhIws9u7divaotsErAF6RsZfGcZc6WCRvadYHw== + version "8.4.17" + resolved "https://registry.yarnpkg.com/get-it/-/get-it-8.4.17.tgz#644057569318d740637bb6526801c4911b406342" + integrity sha512-GloykKQFXggi8dZcJ4KVJahAc6hbuZ/RT/gVfCGpQw3t7jtm4xtq2f37tXajeqSA2icTYgkypluyPH1/bJ6ibA== dependencies: debug "^4.3.4" decompress-response "^7.0.0" @@ -14166,15 +14081,15 @@ glob@9.3.5, glob@^9.2.0: path-scurry "^1.6.1" glob@^10.3.10: - version "10.3.10" - resolved "https://registry.yarnpkg.com/glob/-/glob-10.3.10.tgz#0351ebb809fd187fe421ab96af83d3a70715df4b" - integrity sha512-fa46+tv1Ak0UPK1TOy/pZrIybNNt4HCv7SDzwyfiOZkvZLEbjsZkJBPtDHVshZjbecAoAGSC20MjLDG/qr679g== + version "10.3.12" + resolved "https://registry.yarnpkg.com/glob/-/glob-10.3.12.tgz#3a65c363c2e9998d220338e88a5f6ac97302960b" + integrity sha512-TCNv8vJ+xz4QiqTpfOJA7HvYv+tNIRHKfUWw/q+v2jdgN4ebz+KY9tGx5J4rHP0o84mNP+ApH66HRX8us3Khqg== dependencies: foreground-child "^3.1.0" - jackspeak "^2.3.5" + jackspeak "^2.3.6" minimatch "^9.0.1" - minipass "^5.0.0 || ^6.0.2 || ^7.0.0" - path-scurry "^1.10.1" + minipass "^7.0.4" + path-scurry "^1.10.2" glob@^5.0.15: version "5.0.15" @@ -14344,7 +14259,7 @@ graceful-fs@4.2.10: resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.10.tgz#147d3a006da4ca3ce14728c7aefc287c367d7a6c" integrity sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA== -graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.1.9, graceful-fs@^4.2.0, graceful-fs@^4.2.2, graceful-fs@^4.2.4, graceful-fs@^4.2.9: +graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.1.9, graceful-fs@^4.2.0, graceful-fs@^4.2.11, graceful-fs@^4.2.2, graceful-fs@^4.2.4, graceful-fs@^4.2.9: version "4.2.11" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3" integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== @@ -14547,15 +14462,16 @@ hardhat-deploy@^0.11.43: zksync-web3 "^0.14.3" hardhat-gas-reporter@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/hardhat-gas-reporter/-/hardhat-gas-reporter-2.0.2.tgz#3727ed915256570e09a56cffe0d2183723009e4e" - integrity sha512-i/+g+dX+/+MZ7L4M5NE78TgjDgnE3dINhsNUJmjwbqR3cLSMPrsEyiee+/1c5w32JSD08SKyDf+8W9Q5iC+zLw== + version "2.1.0" + resolved "https://registry.yarnpkg.com/hardhat-gas-reporter/-/hardhat-gas-reporter-2.1.0.tgz#cca3041d435fed76e1974d054ae732c585bc897e" + integrity sha512-d/WU/qHhBFnbweAm2fAAjcaaE0M7BKZ4r+/bqcFlfP6um28BXtlv2FrJ6oyQUGSFD0ttbmB7sH4ZFDzkYw5GzA== dependencies: "@ethersproject/abi" "^5.7.0" "@ethersproject/bytes" "^5.7.0" "@ethersproject/units" "^5.7.0" "@solidity-parser/parser" "^0.18.0" axios "^1.6.7" + brotli-wasm "^2.0.1" chalk "4.1.2" cli-table3 "^0.6.3" ethereum-cryptography "^2.1.3" @@ -14567,9 +14483,9 @@ hardhat-gas-reporter@^2.0.2: viem "2.7.14" hardhat@^2.22.1: - version "2.22.1" - resolved "https://registry.yarnpkg.com/hardhat/-/hardhat-2.22.1.tgz#2640ecc6ff655dea883a25a8b17f1e70e45e6756" - integrity sha512-cTWYIJc5jQ132XUI8oRI/TO9L6oavPoJRCTRU9sIjkVxvkxz0Axz0K83Z3BEdJTqBQ2W84ZRoTekti84kBwCjg== + version "2.22.2" + resolved "https://registry.yarnpkg.com/hardhat/-/hardhat-2.22.2.tgz#0cadd7ec93bf39bab09f81603e75bc5e92acea3d" + integrity sha512-0xZ7MdCZ5sJem4MrvpQWLR3R3zGDoHw5lsR+pBFimqwagimIOn3bWuZv69KA+veXClwI1s/zpqgwPwiFrd4Dxw== dependencies: "@ethersproject/abi" "^5.1.2" "@metamask/eth-sig-util" "^4.0.0" @@ -16247,7 +16163,7 @@ iterator.prototype@^1.1.2: reflect.getprototypeof "^1.0.4" set-function-name "^2.0.1" -jackspeak@^2.3.5: +jackspeak@^2.3.6: version "2.3.6" resolved "https://registry.yarnpkg.com/jackspeak/-/jackspeak-2.3.6.tgz#647ecc472238aee4b06ac0e461acc21a8c505ca8" integrity sha512-N3yCS/NegsOBokc8GAdM8UcmfsKiSS8cipheD/nivzr700H+nsMOxJjQnvwOcRYVuFkdH0wGUvW2WbXGmrZGbQ== @@ -16714,9 +16630,9 @@ jmespath@0.16.0: integrity sha512-9FzQjJ7MATs1tSpnco1K6ayiYE3figslrXA72G2HQ/n76RzvYlofyi5QM+iX4YRs/pu3yzxlVQSST23+dMDknw== joi@^17.12.2: - version "17.12.2" - resolved "https://registry.yarnpkg.com/joi/-/joi-17.12.2.tgz#283a664dabb80c7e52943c557aab82faea09f521" - integrity sha512-RonXAIzCiHLc8ss3Ibuz45u28GOsWE1UpfDXLbN/9NKbL4tCJf8TWYVKsoYuuh+sAUt7fsSNpA+r2+TBA6Wjmw== + version "17.12.3" + resolved "https://registry.yarnpkg.com/joi/-/joi-17.12.3.tgz#944646979cd3b460178547b12ba37aca8482f63d" + integrity sha512-2RRziagf555owrm9IRVtdKynOBeITiDpuZqIpgwqXShPncPKNiRQoiGsl/T8SQdq+8ugRzH2LqY67irr2y/d+g== dependencies: "@hapi/hoek" "^9.3.0" "@hapi/topo" "^5.1.0" @@ -17347,9 +17263,9 @@ libmime@^2.0.3: libqp "1.1.0" libphonenumber-js@^1.10.53: - version "1.10.58" - resolved "https://registry.yarnpkg.com/libphonenumber-js/-/libphonenumber-js-1.10.58.tgz#2015877bd47fd3d32d9fbfcedd75df35be230c9a" - integrity sha512-53A0IpJFL9LdHbpeatwizf8KSwPICrqn9H0g3Y7WQ+Jgeu9cQ4Ew3WrRtrLBu/CX2lXd5+rgT01/tGlkbkzOjw== + version "1.10.59" + resolved "https://registry.yarnpkg.com/libphonenumber-js/-/libphonenumber-js-1.10.59.tgz#ece26801dcf11fe3f8265bbc01981d9d808f9e6c" + integrity sha512-HeTsOrDF/hWhEiKqZVwg9Cqlep5x2T+IYDENvT2VRj3iX8JQ7Y+omENv+AIn0vC8m6GYhivfCed5Cgfw27r5SA== libqp@1.1.0: version "1.1.0" @@ -17800,7 +17716,7 @@ lowercase-keys@^2.0.0: resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-2.0.0.tgz#2603e78b7b4b0006cbca2fbcc8a3202558ac9479" integrity sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA== -lru-cache@^10.2.0, "lru-cache@^9.1.1 || ^10.0.0": +lru-cache@^10.2.0: version "10.2.0" resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-10.2.0.tgz#0bd445ca57363465900f4d1f9bd8db343a4d95c3" integrity sha512-2bIM8x+VAf6JT4bKAljS1qUWgMsqZRPGJS6FSahIMPVvctcNhyVp7AJu7quxOW9jwkryBReKZY5tY5JYv2n/7Q== @@ -17857,9 +17773,9 @@ magic-string@0.30.0: "@jridgewell/sourcemap-codec" "^1.4.13" magic-string@^0.30.0, magic-string@^0.30.3: - version "0.30.8" - resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.30.8.tgz#14e8624246d2bedba70d5462aa99ac9681844613" - integrity sha512-ISQTe55T2ao7XtlAStud6qwYPZjE4GK1S/BeVPus4jrq6JuOnQ00YKQC581RWhR122W7msZV263KzVeLoqidyQ== + version "0.30.9" + resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.30.9.tgz#8927ae21bfdd856310e07a1bc8dd5e73cb6c251d" + integrity sha512-S1+hd+dIrC8EZqKyT9DstTH/0Z+f76kmmvZnkfQVmOpDEF9iVgdYif3Q/pIWHmCoo59bQVGW0kVL3e2nl+9+Sw== dependencies: "@jridgewell/sourcemap-codec" "^1.4.15" @@ -18295,7 +18211,7 @@ minimatch@5.0.1: dependencies: brace-expansion "^2.0.1" -minimatch@9.0.3, minimatch@^9.0.1, minimatch@^9.0.3: +minimatch@9.0.3: version "9.0.3" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-9.0.3.tgz#a6e00c3de44c3a542bfaae70abfc22420a6da825" integrity sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg== @@ -18316,6 +18232,13 @@ minimatch@^8.0.2: dependencies: brace-expansion "^2.0.1" +minimatch@^9.0.1, minimatch@^9.0.3: + version "9.0.4" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-9.0.4.tgz#8e49c731d1749cbec05050ee5145147b32496a51" + integrity sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw== + dependencies: + brace-expansion "^2.0.1" + minimist-options@4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/minimist-options/-/minimist-options-4.1.0.tgz#c0655713c53a8a2ebd77ffa247d342c40f010619" @@ -18367,7 +18290,7 @@ minipass@^5.0.0: resolved "https://registry.yarnpkg.com/minipass/-/minipass-5.0.0.tgz#3e9788ffb90b694a5d0ec94479a45b5d8738133d" integrity sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ== -"minipass@^5.0.0 || ^6.0.2 || ^7.0.0": +"minipass@^5.0.0 || ^6.0.2 || ^7.0.0", minipass@^7.0.4: version "7.0.4" resolved "https://registry.yarnpkg.com/minipass/-/minipass-7.0.4.tgz#dbce03740f50a4786ba994c1fb908844d27b038c" integrity sha512-jYofLM5Dam9279rdkWzqHozUo4ybjdZmCsDHePy5V/PbBcVMiSZR97gmAy45aqi8CK1lG2ECd356FU86avfwUQ== @@ -18428,9 +18351,9 @@ mnemonist@^0.38.0: obliterator "^2.0.0" mocha@^10.0.0, mocha@^10.2.0: - version "10.3.0" - resolved "https://registry.yarnpkg.com/mocha/-/mocha-10.3.0.tgz#0e185c49e6dccf582035c05fa91084a4ff6e3fe9" - integrity sha512-uF2XJs+7xSLsrmIvn37i/wnc91nw7XjOQB8ccyx5aEgdnohr7n+rEiZP23WkCYHjilR6+EboEnbq/ZQDz4LSbg== + version "10.4.0" + resolved "https://registry.yarnpkg.com/mocha/-/mocha-10.4.0.tgz#ed03db96ee9cfc6d20c56f8e2af07b961dbae261" + integrity sha512-eqhGB8JKapEYcC4ytX/xrzKforgEc3j1pGlAXVy3eRwrtAy5/nIfT1SvgGzfN0XZZxeLq0aQWkOUAmqIJiv+bA== dependencies: ansi-colors "4.1.1" browser-stdout "1.3.1" @@ -18734,9 +18657,9 @@ nock@^13.5.1: propagate "^2.0.0" node-abi@^3.3.0: - version "3.56.0" - resolved "https://registry.yarnpkg.com/node-abi/-/node-abi-3.56.0.tgz#ca807d5ff735ac6bbbd684ae3ff2debc1c2a40a7" - integrity sha512-fZjdhDOeRcaS+rcpve7XuwHBmktS1nS1gzgghwKUQQ8nTy2FdSDr6ZT8k6YhvlJeHmmQMYiT/IH9hfco5zeW2Q== + version "3.57.0" + resolved "https://registry.yarnpkg.com/node-abi/-/node-abi-3.57.0.tgz#d772cb899236c0aa46778d0d25256917cf15eb15" + integrity sha512-Dp+A9JWxRaKuHP35H77I4kCKesDy5HUDEmScia2FyncMTOXASMyg251F5PhFoDA5uqBrDDffiLpbqnrZmNXW+g== dependencies: semver "^7.3.5" @@ -18779,10 +18702,10 @@ node-emoji@1.11.0, node-emoji@^1.10.0: dependencies: lodash "^4.17.21" -node-fetch-native@^1.4.0, node-fetch-native@^1.6.1, node-fetch-native@^1.6.2: - version "1.6.2" - resolved "https://registry.yarnpkg.com/node-fetch-native/-/node-fetch-native-1.6.2.tgz#f439000d972eb0c8a741b65dcda412322955e1c6" - integrity sha512-69mtXOFZ6hSkYiXAVB5SqaRvrbITC/NPyqv7yuu/qw0nmgPyYbIMYYNIDhNtwPrzk0ptrimrLz/hhjvm4w5Z+w== +node-fetch-native@^1.6.1, node-fetch-native@^1.6.2, node-fetch-native@^1.6.3: + version "1.6.4" + resolved "https://registry.yarnpkg.com/node-fetch-native/-/node-fetch-native-1.6.4.tgz#679fc8fd8111266d47d7e72c379f1bed9acff06e" + integrity sha512-IhOigYzAKHd244OC0JIMIUrjzctirCmPkaIfhDeGcEETWof5zKYUW7e7MYvChGWh/4CJeXEgsRyGzuF334rOOQ== node-fetch@2.6.9, node-fetch@2.7.0, node-fetch@^2.6.0, node-fetch@^2.6.1, node-fetch@^2.6.12, node-fetch@^2.6.7, node-fetch@^2.6.8, node-fetch@^2.7.0: version "2.7.0" @@ -19098,12 +19021,13 @@ object.groupby@^1.0.1: es-abstract "^1.23.2" object.hasown@^1.1.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/object.hasown/-/object.hasown-1.1.3.tgz#6a5f2897bb4d3668b8e79364f98ccf971bda55ae" - integrity sha512-fFI4VcYpRHvSLXxP7yiZOMAd331cPfd2p7PFDVbgUsYOfCT3tICVqXWngbjr4m49OvsBwUBQ6O2uQoJvy3RexA== + version "1.1.4" + resolved "https://registry.yarnpkg.com/object.hasown/-/object.hasown-1.1.4.tgz#e270ae377e4c120cdcb7656ce66884a6218283dc" + integrity sha512-FZ9LZt9/RHzGySlBARE3VF+gE26TxR38SdmqOqliuTnl9wrKulaQs+4dee1V+Io8VfxqzAfHu6YuRgUy8OHoTg== dependencies: - define-properties "^1.2.0" - es-abstract "^1.22.1" + define-properties "^1.2.1" + es-abstract "^1.23.2" + es-object-atoms "^1.0.0" object.map@^1.0.0: version "1.0.1" @@ -19140,13 +19064,13 @@ oblivious-set@1.0.0: integrity sha512-z+pI07qxo4c2CulUHCDf9lcqDlMSo72N/4rLUpRXf6fu+q8vjt8y0xS+Tlf8NTJDdTXHbdeO1n3MlbctwEoXZw== ofetch@^1.3.3: - version "1.3.3" - resolved "https://registry.yarnpkg.com/ofetch/-/ofetch-1.3.3.tgz#588cb806a28e5c66c2c47dd8994f9059a036d8c0" - integrity sha512-s1ZCMmQWXy4b5K/TW9i/DtiN8Ku+xCiHcjQ6/J/nDdssirrQNOoB165Zu8EqLMA2lln1JUth9a0aW9Ap2ctrUg== + version "1.3.4" + resolved "https://registry.yarnpkg.com/ofetch/-/ofetch-1.3.4.tgz#7ea65ced3c592ec2b9906975ae3fe1d26a56f635" + integrity sha512-KLIET85ik3vhEfS+3fDlc/BAZiAp+43QEC/yCo5zkNoY2YaKvNkOaFr/6wCFgFH1kuYQM5pMNi0Tg8koiIemtw== dependencies: - destr "^2.0.1" - node-fetch-native "^1.4.0" - ufo "^1.3.0" + destr "^2.0.3" + node-fetch-native "^1.6.3" + ufo "^1.5.3" ohash@^1.1.3: version "1.1.3" @@ -19746,12 +19670,12 @@ path-root@^0.1.1: dependencies: path-root-regex "^0.1.0" -path-scurry@^1.10.1, path-scurry@^1.6.1: - version "1.10.1" - resolved "https://registry.yarnpkg.com/path-scurry/-/path-scurry-1.10.1.tgz#9ba6bf5aa8500fe9fd67df4f0d9483b2b0bfc698" - integrity sha512-MkhCqzzBEpPvxxQ71Md0b1Kk51W01lrYvlMzSUaIzNsODdd7mqhiimSZlr+VegAz5Z6Vzt9Xg2ttE//XBhH3EQ== +path-scurry@^1.10.2, path-scurry@^1.6.1: + version "1.10.2" + resolved "https://registry.yarnpkg.com/path-scurry/-/path-scurry-1.10.2.tgz#8f6357eb1239d5fa1da8b9f70e9c080675458ba7" + integrity sha512-7xTavNy5RQXnsjANvVvMkEjvloOinkAjv/Z6Ildz9v2RinZ4SBKTWFOVRbaF8p0vpHnyjV/UwNDdKuUv6M5qcA== dependencies: - lru-cache "^9.1.1 || ^10.0.0" + lru-cache "^10.2.0" minipass "^5.0.0 || ^6.0.2 || ^7.0.0" path-starts-with@^2.0.0: @@ -19833,9 +19757,9 @@ pg-connection-string@2.6.1: integrity sha512-w6ZzNu6oMmIzEAYVw+RLK0+nqHPt8K3ZnknKi+g48Ak2pr3dtljJW3o+D/n2zzCG07Zoe9VOX3aiKpj+BN0pjg== pg-connection-string@^2.6.2: - version "2.6.2" - resolved "https://registry.yarnpkg.com/pg-connection-string/-/pg-connection-string-2.6.2.tgz#713d82053de4e2bd166fab70cd4f26ad36aab475" - integrity sha512-ch6OwaeaPYcova4kKZ15sbJ2hKb/VP48ZD2gE7i1J+L4MspCtBMAx8nMgz7bksc7IojCIIWuEhHibSMFH8m8oA== + version "2.6.4" + resolved "https://registry.yarnpkg.com/pg-connection-string/-/pg-connection-string-2.6.4.tgz#f543862adfa49fa4e14bc8a8892d2a84d754246d" + integrity sha512-v+Z7W/0EO707aNMaAEfiGnGL9sxxumwLl2fJvCQtMn9Fxsg+lPpPkdcyBSv/KFgpGdYkMfn+EI1Or2EHjpgLCA== pg-int8@1.0.1: version "1.0.1" @@ -19843,14 +19767,14 @@ pg-int8@1.0.1: integrity sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw== pg-pool@^3.6.1: - version "3.6.1" - resolved "https://registry.yarnpkg.com/pg-pool/-/pg-pool-3.6.1.tgz#5a902eda79a8d7e3c928b77abf776b3cb7d351f7" - integrity sha512-jizsIzhkIitxCGfPRzJn1ZdcosIt3pz9Sh3V01fm1vZnbnCMgmGl5wvGGdNN2EL9Rmb0EcFoCkixH4Pu+sP9Og== + version "3.6.2" + resolved "https://registry.yarnpkg.com/pg-pool/-/pg-pool-3.6.2.tgz#3a592370b8ae3f02a7c8130d245bc02fa2c5f3f2" + integrity sha512-Htjbg8BlwXqSBQ9V8Vjtc+vzf/6fVUuak/3/XXKA9oxZprwW3IMDQTGHP+KDmVL7rtd+R1QjbnCFPuTHm3G4hg== pg-protocol@^1.6.0: - version "1.6.0" - resolved "https://registry.yarnpkg.com/pg-protocol/-/pg-protocol-1.6.0.tgz#4c91613c0315349363af2084608db843502f8833" - integrity sha512-M+PDm637OY5WM307051+bsDia5Xej6d9IR4GwJse1qA1DIhiKlksvrneZOYQq42OM+spubpcNYEo2FcKQrDk+Q== + version "1.6.1" + resolved "https://registry.yarnpkg.com/pg-protocol/-/pg-protocol-1.6.1.tgz#21333e6d83b01faaebfe7a33a7ad6bfd9ed38cb3" + integrity sha512-jPIlvgoD63hrEuihvIg+tJhoGjUsLPn6poJY9N5CnlPd91c2T18T/9zBtLxZSb1EhYxBRoZJtzScCaWlYLtktg== pg-types@^2.1.0: version "2.2.0" @@ -20019,24 +19943,24 @@ possible-typed-array-names@^1.0.0: resolved "https://registry.yarnpkg.com/possible-typed-array-names/-/possible-typed-array-names-1.0.0.tgz#89bb63c6fada2c3e90adc4a647beeeb39cc7bf8f" integrity sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q== -postcss-modules-extract-imports@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.0.0.tgz#cda1f047c0ae80c97dbe28c3e76a43b88025741d" - integrity sha512-bdHleFnP3kZ4NYDhuGlVK+CMrQ/pqUm8bx/oGL93K6gVwiclvX5x0n76fYMKuIGKzlABOy13zsvqjb0f92TEXw== +postcss-modules-extract-imports@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.1.0.tgz#b4497cb85a9c0c4b5aabeb759bb25e8d89f15002" + integrity sha512-k3kNe0aNFQDAZGbin48pL2VNidTF0w4/eASDsxlyspobzU3wZQLOGj7L9gfRe0Jo9/4uud09DsjFNH7winGv8Q== -postcss-modules-local-by-default@^4.0.4: - version "4.0.4" - resolved "https://registry.yarnpkg.com/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.0.4.tgz#7cbed92abd312b94aaea85b68226d3dec39a14e6" - integrity sha512-L4QzMnOdVwRm1Qb8m4x8jsZzKAaPAgrUF1r/hjDR2Xj7R+8Zsf97jAlSQzWtKx5YNiNGN8QxmPFIc/sh+RQl+Q== +postcss-modules-local-by-default@^4.0.5: + version "4.0.5" + resolved "https://registry.yarnpkg.com/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.0.5.tgz#f1b9bd757a8edf4d8556e8d0f4f894260e3df78f" + integrity sha512-6MieY7sIfTK0hYfafw1OMEG+2bg8Q1ocHCpoWLqOKj3JXlKu4G7btkmM/B7lFubYkYWmRSPLZi5chid63ZaZYw== dependencies: icss-utils "^5.0.0" postcss-selector-parser "^6.0.2" postcss-value-parser "^4.1.0" -postcss-modules-scope@^3.1.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/postcss-modules-scope/-/postcss-modules-scope-3.1.1.tgz#32cfab55e84887c079a19bbb215e721d683ef134" - integrity sha512-uZgqzdTleelWjzJY+Fhti6F3C9iF1JR/dODLs/JDefozYcKTBCdD8BIl6nNPbTbcLnGrk56hzwZC2DaGNvYjzA== +postcss-modules-scope@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/postcss-modules-scope/-/postcss-modules-scope-3.2.0.tgz#a43d28289a169ce2c15c00c4e64c0858e43457d5" + integrity sha512-oq+g1ssrsZOsx9M96c5w8laRmvEu9C3adDSjI8oTcbfkrTE8hx/zfyobUoWIxaKPO8bt6S62kxpw5GqypEw1QQ== dependencies: postcss-selector-parser "^6.0.4" @@ -20060,10 +19984,10 @@ postcss-value-parser@^4.0.2, postcss-value-parser@^4.1.0, postcss-value-parser@^ resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz#723c09920836ba6d3e5af019f92bc0971c02e514" integrity sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ== -postcss@^8.3.11, postcss@^8.4.13, postcss@^8.4.27, postcss@^8.4.33, postcss@^8.4.36: - version "8.4.37" - resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.37.tgz#4505f992cd0c20e03d25f13b31901640b2db731a" - integrity sha512-7iB/v/r7Woof0glKLH8b1SPHrsX7uhdO+Geb41QpF/+mWZHU3uxxSlN+UXGVit1PawOYDToO+AbZzhBzWRDwbQ== +postcss@^8.3.11, postcss@^8.4.13, postcss@^8.4.27, postcss@^8.4.33, postcss@^8.4.38: + version "8.4.38" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.38.tgz#b387d533baf2054288e337066d81c6bee9db9e0e" + integrity sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A== dependencies: nanoid "^3.3.7" picocolors "^1.0.0" @@ -20092,9 +20016,9 @@ postgres-interval@^1.1.0: xtend "^4.0.0" preact@^10.12.0, preact@^10.5.9: - version "10.20.0" - resolved "https://registry.yarnpkg.com/preact/-/preact-10.20.0.tgz#191c10a2ee3b9fca1a7ded6375266266380212f6" - integrity sha512-wU7iZw2BjsaKDal3pDRDy/HpPB6cuFOnVUCcw9aIPKG98+ZrXx3F+szkos8BVME5bquyKDKvRlOJFG8kMkcAbg== + version "10.20.1" + resolved "https://registry.yarnpkg.com/preact/-/preact-10.20.1.tgz#1bc598ab630d8612978f7533da45809a8298542b" + integrity sha512-JIFjgFg9B2qnOoGiYMVBtrcFxHqn+dNXbq76bVmcaHYJFYR4lW67AOcXgAYQQTDYXDOg/kTZrKPNCdRgJ2UJmw== prebuild-install@^7.1.1: version "7.1.2" @@ -20368,9 +20292,9 @@ punycode@^2.1.0, punycode@^2.1.1, punycode@^2.3.0: integrity sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg== pure-rand@^6.0.0: - version "6.0.4" - resolved "https://registry.yarnpkg.com/pure-rand/-/pure-rand-6.0.4.tgz#50b737f6a925468679bff00ad20eade53f37d5c7" - integrity sha512-LA0Y9kxMYv47GIPJy6MI84fqTd2HmYZI83W/kM/SkKfDlajnZYfmXFTxkbY+xSBPkLJxltMa9hIkmdc29eguMA== + version "6.1.0" + resolved "https://registry.yarnpkg.com/pure-rand/-/pure-rand-6.1.0.tgz#d173cf23258231976ccbdb05247c9787957604f2" + integrity sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA== purest@4.0.2: version "4.0.2" @@ -20499,9 +20423,9 @@ rabin-wasm@^0.1.4: readable-stream "^3.6.0" radix3@^1.1.0: - version "1.1.1" - resolved "https://registry.yarnpkg.com/radix3/-/radix3-1.1.1.tgz#60a56876ffec62c88a22396a6a1c4c7efe9eb4b1" - integrity sha512-yUUd5VTiFtcMEx0qFUxGAv5gbMc1un4RvEO1JZdP7ZUl/RHygZK6PknIKntmQRZxnMY3ZXD2ISaw1ij8GYW1yg== + version "1.1.2" + resolved "https://registry.yarnpkg.com/radix3/-/radix3-1.1.2.tgz#fd27d2af3896c6bf4bcdfab6427c69c2afc69ec0" + integrity sha512-b484I/7b8rDEdSDKckSSBA8knMpcdsXudlE/LNL639wFoHKwLbEkQFZHWEYwDC0wa0FKUcCY+GAF73Z7wxNVFA== random-bytes@~1.0.0: version "1.0.0" @@ -20712,7 +20636,7 @@ react-remove-scroll@2.5.5: use-callback-ref "^1.3.0" use-sidecar "^1.1.2" -react-remove-scroll@^2.5.7: +react-remove-scroll@^2.5.7, react-remove-scroll@^2.5.9: version "2.5.9" resolved "https://registry.yarnpkg.com/react-remove-scroll/-/react-remove-scroll-2.5.9.tgz#6a38e7d46043abc2c6b0fb39db650b9f2e38be3e" integrity sha512-bvHCLBrFfM2OgcrpPY2YW84sPdS2o2HKWJUf1xGyGLnSoEnOTOBpahIarjRuYtN0ryahCeP242yf+5TrBX/pZA== @@ -20810,9 +20734,9 @@ react-smooth@^2.0.4: react-transition-group "2.9.0" react-smooth@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/react-smooth/-/react-smooth-4.0.0.tgz#69e560ab69b69a066187d70cb92c1a664f7f046a" - integrity sha512-2NMXOBY1uVUQx1jBeENGA497HK20y6CPGYL1ZnJLeoQ8rrc3UfmOM82sRxtzpcoCkUMy4CS0RGylfuVhuFjBgg== + version "4.0.1" + resolved "https://registry.yarnpkg.com/react-smooth/-/react-smooth-4.0.1.tgz#6200d8699bfe051ae40ba187988323b1449eab1a" + integrity sha512-OE4hm7XqR0jNOq3Qmk9mFLyd6p2+j6bvbPJ7qlB7+oo0eNcL2l7WQzG6MBnT3EXY6xzkLMUBec3AfewJdA0J8w== dependencies: fast-equals "^5.0.1" prop-types "^15.8.1" @@ -20964,9 +20888,9 @@ recharts@2.9.0: victory-vendor "^36.6.8" recharts@^2.7.2: - version "2.12.3" - resolved "https://registry.yarnpkg.com/recharts/-/recharts-2.12.3.tgz#c059bd34eaf1c80d23fc2b953ad3abfa9474694a" - integrity sha512-vE/F7wTlokf5mtCqVDJlVKelCjliLSJ+DJxj79XlMREm7gpV7ljwbrwE3CfeaoDlOaLX+6iwHaVRn9587YkwIg== + version "2.12.4" + resolved "https://registry.yarnpkg.com/recharts/-/recharts-2.12.4.tgz#e560a57cd44ab554c99a0d93bdd58d059b309a2e" + integrity sha512-dM4skmk4fDKEDjL9MNunxv6zcTxePGVEzRnLDXALRpfJ85JoQ0P0APJ/CoJlmnQI0gPjBlOkjzrwrfQrRST3KA== dependencies: clsx "^2.0.0" eventemitter3 "^4.0.1" @@ -21036,9 +20960,9 @@ reflect-metadata@^0.1.13: integrity sha512-ZhYeb6nRaXCfhnndflDK8qI6ZQ/YcWZCISRAWICW9XYqMUwjZM9Z0DveWX/ABN01oxSHwVxKQmxeYZSsm0jh5A== reflect-metadata@^0.2.1: - version "0.2.1" - resolved "https://registry.yarnpkg.com/reflect-metadata/-/reflect-metadata-0.2.1.tgz#8d5513c0f5ef2b4b9c3865287f3c0940c1f67f74" - integrity sha512-i5lLI6iw9AU3Uu4szRNPPEkomnkjRTaVt9hy/bn5g/oSzekBSMeLZblcjP74AW0vBabqERLLIrz+gR8QYR54Tw== + version "0.2.2" + resolved "https://registry.yarnpkg.com/reflect-metadata/-/reflect-metadata-0.2.2.tgz#400c845b6cba87a21f2c65c4aeb158f4fa4d9c5b" + integrity sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q== reflect.getprototypeof@^1.0.4: version "1.0.6" @@ -21468,25 +21392,27 @@ rollup@^3.27.1: fsevents "~2.3.2" rollup@^4.13.0, rollup@^4.9.6: - version "4.13.0" - resolved "https://registry.yarnpkg.com/rollup/-/rollup-4.13.0.tgz#dd2ae144b4cdc2ea25420477f68d4937a721237a" - integrity sha512-3YegKemjoQnYKmsBlOHfMLVPPA5xLkQ8MHLLSw/fBrFaVkEayL51DilPpNNLq1exr98F2B1TzrV0FUlN3gWRPg== + version "4.14.0" + resolved "https://registry.yarnpkg.com/rollup/-/rollup-4.14.0.tgz#c3e2cd479f1b2358b65c1f810fa05b51603d7be8" + integrity sha512-Qe7w62TyawbDzB4yt32R0+AbIo6m1/sqO7UPzFS8Z/ksL5mrfhA0v4CavfdmFav3D+ub4QeAgsGEe84DoWe/nQ== dependencies: "@types/estree" "1.0.5" optionalDependencies: - "@rollup/rollup-android-arm-eabi" "4.13.0" - "@rollup/rollup-android-arm64" "4.13.0" - "@rollup/rollup-darwin-arm64" "4.13.0" - "@rollup/rollup-darwin-x64" "4.13.0" - "@rollup/rollup-linux-arm-gnueabihf" "4.13.0" - "@rollup/rollup-linux-arm64-gnu" "4.13.0" - "@rollup/rollup-linux-arm64-musl" "4.13.0" - "@rollup/rollup-linux-riscv64-gnu" "4.13.0" - "@rollup/rollup-linux-x64-gnu" "4.13.0" - "@rollup/rollup-linux-x64-musl" "4.13.0" - "@rollup/rollup-win32-arm64-msvc" "4.13.0" - "@rollup/rollup-win32-ia32-msvc" "4.13.0" - "@rollup/rollup-win32-x64-msvc" "4.13.0" + "@rollup/rollup-android-arm-eabi" "4.14.0" + "@rollup/rollup-android-arm64" "4.14.0" + "@rollup/rollup-darwin-arm64" "4.14.0" + "@rollup/rollup-darwin-x64" "4.14.0" + "@rollup/rollup-linux-arm-gnueabihf" "4.14.0" + "@rollup/rollup-linux-arm64-gnu" "4.14.0" + "@rollup/rollup-linux-arm64-musl" "4.14.0" + "@rollup/rollup-linux-powerpc64le-gnu" "4.14.0" + "@rollup/rollup-linux-riscv64-gnu" "4.14.0" + "@rollup/rollup-linux-s390x-gnu" "4.14.0" + "@rollup/rollup-linux-x64-gnu" "4.14.0" + "@rollup/rollup-linux-x64-musl" "4.14.0" + "@rollup/rollup-win32-arm64-msvc" "4.14.0" + "@rollup/rollup-win32-ia32-msvc" "4.14.0" + "@rollup/rollup-win32-x64-msvc" "4.14.0" fsevents "~2.3.2" rpc-websockets@^7.5.1: @@ -21540,7 +21466,7 @@ rxjs@^6.4.0, rxjs@^6.6.0, rxjs@^6.6.3: dependencies: tslib "^1.9.0" -safe-array-concat@^1.1.0, safe-array-concat@^1.1.2: +safe-array-concat@^1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/safe-array-concat/-/safe-array-concat-1.1.2.tgz#81d77ee0c4e8b863635227c721278dd524c20edb" integrity sha512-vj6RsCsWBCf19jIeHEfkRMw8DPiBb+DMXklQ/1SGDHOMlHdPUkZXFQ2YdplS23zESTijAcurb1aSgJA3AgMu1Q== @@ -22527,7 +22453,7 @@ string.prototype.matchall@^4.0.10: set-function-name "^2.0.2" side-channel "^1.0.6" -string.prototype.trim@^1.2.8, string.prototype.trim@^1.2.9: +string.prototype.trim@^1.2.9: version "1.2.9" resolved "https://registry.yarnpkg.com/string.prototype.trim/-/string.prototype.trim-1.2.9.tgz#b6fa326d72d2c78b6df02f7759c73f8f6274faa4" integrity sha512-klHuCNxiMZ8MlsOihJhJEBJAiMVqU3Z2nEXWfWnIqjN0gEFS9J9+IxKozWWtQGcgoa1WUZzLjKPTr4ZHNFTFxw== @@ -22537,7 +22463,7 @@ string.prototype.trim@^1.2.8, string.prototype.trim@^1.2.9: es-abstract "^1.23.0" es-object-atoms "^1.0.0" -string.prototype.trimend@^1.0.7, string.prototype.trimend@^1.0.8: +string.prototype.trimend@^1.0.8: version "1.0.8" resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.8.tgz#3651b8513719e8a9f48de7f2f77640b26652b229" integrity sha512-p73uL5VCHCO2BZZ6krwwQE3kCzM7NKmis8S//xEC6fQonchbum4eP6kR4DLEjQFO3Wnj3Fuo8NM0kOSjVdHjZQ== @@ -22546,14 +22472,14 @@ string.prototype.trimend@^1.0.7, string.prototype.trimend@^1.0.8: define-properties "^1.2.1" es-object-atoms "^1.0.0" -string.prototype.trimstart@^1.0.7: - version "1.0.7" - resolved "https://registry.yarnpkg.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.7.tgz#d4cdb44b83a4737ffbac2d406e405d43d0184298" - integrity sha512-NGhtDFu3jCEm7B4Fy0DpLewdJQOZcQ0rGbwQ/+stjnrp2i+rlKeCvos9hOIeCmqwratM47OBxY7uFZzjxHXmrg== +string.prototype.trimstart@^1.0.8: + version "1.0.8" + resolved "https://registry.yarnpkg.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz#7ee834dda8c7c17eff3118472bb35bfedaa34dde" + integrity sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg== dependencies: - call-bind "^1.0.2" - define-properties "^1.2.0" - es-abstract "^1.22.1" + call-bind "^1.0.7" + define-properties "^1.2.1" + es-object-atoms "^1.0.0" string_decoder@^1.0.0, string_decoder@^1.1.1: version "1.3.0" @@ -22654,9 +22580,9 @@ strip-literal@^1.0.1: acorn "^8.10.0" stripe@^14.20.0: - version "14.21.0" - resolved "https://registry.yarnpkg.com/stripe/-/stripe-14.21.0.tgz#6b984b06c16765ca08fb86b9185fe4ea0a8409fd" - integrity sha512-PFmpl35Myn6UDdVLTHcuppdbkPVvlQfkMHOmgGZh5QOdSUxVmvz090Z4obLg8ta1MNs1PNpzr9i7E39iAIv07A== + version "14.23.0" + resolved "https://registry.yarnpkg.com/stripe/-/stripe-14.23.0.tgz#a92ab57c4ce8670c9252f5f6e890fa1018cc5794" + integrity sha512-OPD7LqBmni6uDdqA05GGgMZyyRWxJOehONBNC9tYgY4Uh089EtXd6QLIgRGrqTDlQH3cA2BXo848nxwa/zsQzw== dependencies: "@types/node" ">=8.1.0" qs "^6.11.0" @@ -22856,9 +22782,9 @@ table-layout@^1.0.2: wordwrapjs "^4.0.0" table@^6.8.0: - version "6.8.1" - resolved "https://registry.yarnpkg.com/table/-/table-6.8.1.tgz#ea2b71359fe03b017a5fbc296204471158080bdf" - integrity sha512-Y4X9zqrCftUhMeH2EptSSERdVKt/nEdijTOacGD/97EKjhQ/Qs8RTlEGABSJNNN8lac9kheH+af7yAkEWlgneA== + version "6.8.2" + resolved "https://registry.yarnpkg.com/table/-/table-6.8.2.tgz#c5504ccf201213fa227248bdc8c5569716ac6c58" + integrity sha512-w2sfv80nrAh2VCbqR5AK27wswXhqcck2AhfnNW76beQXskGZ1V12GwS//yYVa3d3fcvAip2OUnbDAjW2k3v9fA== dependencies: ajv "^8.0.1" lodash.truncate "^4.4.2" @@ -22948,9 +22874,9 @@ tar@6.1.13: yallist "^4.0.0" tar@^6.1.0, tar@^6.1.11: - version "6.2.0" - resolved "https://registry.yarnpkg.com/tar/-/tar-6.2.0.tgz#b14ce49a79cb1cd23bc9b016302dea5474493f73" - integrity sha512-/Wo7DcT0u5HUV486xg675HtjNd3BXZ6xDbzsCUZPt5iw8bTQ63bP0Raut3mvro9u+CUyq7YQd8Cx55fsZXxqLQ== + version "6.2.1" + resolved "https://registry.yarnpkg.com/tar/-/tar-6.2.1.tgz#717549c541bc3c2af15751bea94b1dd068d4b03a" + integrity sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A== dependencies: chownr "^2.0.0" fs-minipass "^2.0.0" @@ -23002,9 +22928,9 @@ terser-webpack-plugin@^5.3.10, terser-webpack-plugin@^5.3.7: terser "^5.26.0" terser@^5.10.0, terser@^5.26.0: - version "5.29.2" - resolved "https://registry.yarnpkg.com/terser/-/terser-5.29.2.tgz#c17d573ce1da1b30f21a877bffd5655dd86fdb35" - integrity sha512-ZiGkhUBIM+7LwkNjXYJq8svgkd+QK3UUr0wJqY4MieaezBSAIPgbSPZyIx0idM6XWK5CMzSWa8MJIzmRcB8Caw== + version "5.30.3" + resolved "https://registry.yarnpkg.com/terser/-/terser-5.30.3.tgz#f1bb68ded42408c316b548e3ec2526d7dd03f4d2" + integrity sha512-STdUgOUx8rLbMGO9IOwHLpCqolkDITFFQSMYYwKE1N2lY6MVSaeoi10z/EhWxRc6ybqoVmKSkhKYH/XUpl7vSA== dependencies: "@jridgewell/source-map" "^0.3.3" acorn "^8.8.2" @@ -23585,11 +23511,6 @@ type-fest@^2.13.0, type-fest@^2.18.0, type-fest@^2.19.0: resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-2.19.0.tgz#88068015bb33036a598b952e55e9311a60fd3a9b" integrity sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA== -type-fest@^3.0.0: - version "3.13.1" - resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-3.13.1.tgz#bb744c1f0678bea7543a2d1ec24e83e68e8c8706" - integrity sha512-tLq3bSNx+xSpwvAJnzrK0Ep5CLNWjvFTOp71URMaAEWBfRb9nnJiBoUe0tF8bI4ZFO3omgBR6NvnbzVUT3Ly4g== - type-is@^1.6.14, type-is@^1.6.16, type-is@^1.6.4, type-is@~1.6.18: version "1.6.18" resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131" @@ -23646,10 +23567,10 @@ typed-array-byte-offset@^1.0.2: has-proto "^1.0.3" is-typed-array "^1.1.13" -typed-array-length@^1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/typed-array-length/-/typed-array-length-1.0.5.tgz#57d44da160296d8663fd63180a1802ebf25905d5" - integrity sha512-yMi0PlwuznKHxKmcpoOdeLwxBoVPkqZxd7q2FgMkmD3bNwvF5VW0+UlUQ1k1vmktTu4Yu13Q0RIxEP8+B+wloA== +typed-array-length@^1.0.6: + version "1.0.6" + resolved "https://registry.yarnpkg.com/typed-array-length/-/typed-array-length-1.0.6.tgz#57155207c76e64a3457482dfdc1c9d1d3c4c73a3" + integrity sha512-/OxDN6OtAk5KBpGb28T+HZc2M+ADtvRxXrKKbUwtsLgdoxgX13hyy7ek6bFRl5+aBs2yZzB0c4CnQfAtVypW/g== dependencies: call-bind "^1.0.7" for-each "^0.3.3" @@ -23724,9 +23645,9 @@ typescript@5.2.2: integrity sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w== typescript@^5.0.0, typescript@^5.2.2: - version "5.4.2" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.4.2.tgz#0ae9cebcfae970718474fe0da2c090cad6577372" - integrity sha512-+2/g0Fds1ERlP6JsakQQDXjZdZMM+rqpamFZJEKh4kwTIn3iDkgKtby0CeNd5ATNZ4Ry1ax15TMx0W2V+miizQ== + version "5.4.3" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.4.3.tgz#5c6fedd4c87bee01cd7a528a30145521f8e0feff" + integrity sha512-KrPd3PKaCLr78MalgiwJnA25Nm8HAmdwN3mYUYZgG/wizIo9EainNVQI9/yDavtVFRN2h3k8uf3GLHuhDMgEHg== typical@^4.0.0: version "4.0.0" @@ -23743,10 +23664,10 @@ uc.micro@^1.0.1, uc.micro@^1.0.5: resolved "https://registry.yarnpkg.com/uc.micro/-/uc.micro-1.0.6.tgz#9c411a802a409a91fc6cf74081baba34b24499ac" integrity sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA== -ufo@^1.3.0, ufo@^1.3.2, ufo@^1.4.0: - version "1.5.2" - resolved "https://registry.yarnpkg.com/ufo/-/ufo-1.5.2.tgz#e547561ac56896fc8b9a3f2fb2552169f3629035" - integrity sha512-eiutMaL0J2MKdhcOM1tUy13pIrYnyR87fEd8STJQFrrAwImwvlXkxlZEjaKah8r2viPohld08lt73QfLG1NxMg== +ufo@^1.3.2, ufo@^1.4.0, ufo@^1.5.3: + version "1.5.3" + resolved "https://registry.yarnpkg.com/ufo/-/ufo-1.5.3.tgz#3325bd3c977b6c6cd3160bf4ff52989adc9d3344" + integrity sha512-Y7HYmWaFwPUmkoQCUIAYpKqkOf+SbVj/2fJJZ4RJMCfZp0rTGwRbzQD+HghfnhKOjL9E01okqz+ncJskGYfBNw== uglify-js@^3.1.4: version "3.17.4" @@ -23811,24 +23732,10 @@ undici-types@~5.26.4: resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-5.26.5.tgz#bcd539893d00b56e964fd2657a4866b221a65617" integrity sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA== -undici@5.26.5: - version "5.26.5" - resolved "https://registry.yarnpkg.com/undici/-/undici-5.26.5.tgz#f6dc8c565e3cad8c4475b187f51a13e505092838" - integrity sha512-cSb4bPFd5qgR7qr2jYAi0hlX9n5YKK2ONKkLFkxl+v/9BvC0sOpZjBHDBSXc5lWAf5ty9oZdRXytBIHzgUcerw== - dependencies: - "@fastify/busboy" "^2.0.0" - -undici@^5.14.0: - version "5.28.2" - resolved "https://registry.yarnpkg.com/undici/-/undici-5.28.2.tgz#fea200eac65fc7ecaff80a023d1a0543423b4c91" - integrity sha512-wh1pHJHnUeQV5Xa8/kyQhO7WFa8M34l026L5P/+2TYiakvGy5Rdc8jWZVyG7ieht/0WgJLEd3kcU5gKx+6GC8w== - dependencies: - "@fastify/busboy" "^2.0.0" - -undici@^6.0.0: - version "6.9.0" - resolved "https://registry.yarnpkg.com/undici/-/undici-6.9.0.tgz#281b3c8bf29cafa957e743ab2a710b586b01e66e" - integrity sha512-XPWfXzJedevUziHwun70EKNvGnxv4CnfraFZ4f/JV01+fcvMYzHE26r/j8AY/9c/70nkN4B1zX7E2Oyuqwz4+Q== +undici@5.26.5, undici@^5.14.0, undici@^6.0.0, undici@^6.11.1: + version "6.11.1" + resolved "https://registry.yarnpkg.com/undici/-/undici-6.11.1.tgz#75ab573677885b421ca2e6f5f17ff1185b24c68d" + integrity sha512-KyhzaLJnV1qa3BSHdj4AZ2ndqI0QWPxYzaIOio0WzcEJB9gvuysprJSLtpvc2D9mhR9jPDUk7xlJlZbH2KR5iw== unenv@^1.9.0: version "1.9.0" @@ -24044,9 +23951,9 @@ urlpattern-polyfill@^8.0.0: integrity sha512-Qp95D4TPJl1kC9SKigDcqgyM2VDVO4RiJc2d4qe5GrYm+zbIQCWWKAFaJNQ4BhdFeDGwBmAxqJBwWSJDb9T3BQ== use-callback-ref@^1.3.0: - version "1.3.1" - resolved "https://registry.yarnpkg.com/use-callback-ref/-/use-callback-ref-1.3.1.tgz#9be64c3902cbd72b07fe55e56408ae3a26036fd0" - integrity sha512-Lg4Vx1XZQauB42Hw3kK7JM6yjVjgFmFC5/Ab797s79aARomD2nEErc4mCgM8EZrARLmmbWpi5DGCadmK50DcAQ== + version "1.3.2" + resolved "https://registry.yarnpkg.com/use-callback-ref/-/use-callback-ref-1.3.2.tgz#6134c7f6ff76e2be0b56c809b17a650c942b1693" + integrity sha512-elOQwe6Q8gqZgDA8mrh44qRTQqpIHDcZ3hXTLjBe1i4ph8XpNJnO+aQf3NaG+lriLopI4HMx9VjQLfPQ6vhnoA== dependencies: tslib "^2.0.0" @@ -24297,9 +24204,9 @@ vite@4.4.9: fsevents "~2.3.2" vite@^2.2.0: - version "2.9.17" - resolved "https://registry.yarnpkg.com/vite/-/vite-2.9.17.tgz#6b770525e12fa2a2e3a0fa0d028d304f4f7dc7d4" - integrity sha512-XxcRzra6d7xrKXH66jZUgb+srThoPu+TLJc06GifUyKq9JmjHkc1Numc8ra0h56rju2jfVWw3B3fs5l3OFMvUw== + version "2.9.18" + resolved "https://registry.yarnpkg.com/vite/-/vite-2.9.18.tgz#74e2a83b29da81e602dac4c293312cc575f091c7" + integrity sha512-sAOqI5wNM9QvSEE70W3UGMdT8cyEn0+PmJMTFvTB8wB0YbYUWw3gUbY62AOyrXosGieF2htmeLATvNxpv/zNyQ== dependencies: esbuild "^0.14.27" postcss "^8.4.13" @@ -24309,9 +24216,9 @@ vite@^2.2.0: fsevents "~2.3.2" "vite@^3.0.0 || ^4.0.0": - version "4.5.2" - resolved "https://registry.yarnpkg.com/vite/-/vite-4.5.2.tgz#d6ea8610e099851dad8c7371599969e0f8b97e82" - integrity sha512-tBCZBNSBbHQkaGyhGCDUGqeo2ph8Fstyp6FMSvTtsXeZSPpSMGlviAOav2hxVTqFcx8Hj/twtWKsMJXNY0xI8w== + version "4.5.3" + resolved "https://registry.yarnpkg.com/vite/-/vite-4.5.3.tgz#d88a4529ea58bae97294c7e2e6f0eab39a50fb1a" + integrity sha512-kQL23kMeX92v3ph7IauVkXkikdDRsYMGTVl5KY2E9OY4ONLvkHf04MDTbnfo6NKxZiDLWzVpP5oTa8hQD8U3dg== dependencies: esbuild "^0.18.10" postcss "^8.4.27" @@ -24320,12 +24227,12 @@ vite@^2.2.0: fsevents "~2.3.2" vite@^5.0.12: - version "5.2.0" - resolved "https://registry.yarnpkg.com/vite/-/vite-5.2.0.tgz#a3f899b2fbfb7849f9ffdc3fb2ccd00b1acdef05" - integrity sha512-xMSLJNEjNk/3DJRgWlPADDwaU9AgYRodDH2t6oENhJnIlmU9Hx1Q6VpjyXua/JdMw1WJRbnAgHJ9xgET9gnIAg== + version "5.2.8" + resolved "https://registry.yarnpkg.com/vite/-/vite-5.2.8.tgz#a99e09939f1a502992381395ce93efa40a2844aa" + integrity sha512-OyZR+c1CE8yeHw5V5t59aXsUPPVTHMDjEZz8MgguLL/Q7NblxhZUlTu9xSPqlsUO/y+X7dlU05jdhvyycD55DA== dependencies: esbuild "^0.20.1" - postcss "^8.4.36" + postcss "^8.4.38" rollup "^4.13.0" optionalDependencies: fsevents "~2.3.3" @@ -24413,7 +24320,7 @@ walker@^1.0.8: dependencies: makeerror "1.0.12" -watchpack@^2.4.0: +watchpack@^2.4.0, watchpack@^2.4.1: version "2.4.1" resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-2.4.1.tgz#29308f2cac150fa8e4c92f90e0ec954a9fed7fff" integrity sha512-8wrBCMtVhqcXP2Sup1ctSkga6uc2Bx0IIvKyT7yTFier5AXHooSI+QyQQAtTb7+E0IUCCKyTFmXqdqgum2XWGg== @@ -24502,33 +24409,33 @@ web3-eth-accounts@^4.1.0, web3-eth-accounts@^4.1.1: web3-utils "^4.1.1" web3-validator "^2.0.4" -web3-eth-contract@^4.2.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/web3-eth-contract/-/web3-eth-contract-4.2.0.tgz#73f70b19217cd4854211c05846f0c841763b3b29" - integrity sha512-K7bUypsomTs8/Oa0Lgvq5plsSB5afgKJ79NMuXxvC5jfV+AxNrQyKcr5Vd5yEGNqrdQuIPduGQa8DpuY+rMe1g== +web3-eth-contract@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/web3-eth-contract/-/web3-eth-contract-4.3.0.tgz#5cacbac25f9dbb27bea90ea99fea290e5ebd3f87" + integrity sha512-4fzSklA65zUn6SthU3T3tbVJacfP8/wkJmCuvmPaf2ZTFdnhsF96G5IQtCRf0+wASb4yk0A6IBvXZfk1B4R4HA== dependencies: web3-core "^4.3.2" web3-errors "^1.1.4" - web3-eth "^4.4.0" + web3-eth "^4.5.0" web3-eth-abi "^4.2.0" - web3-types "^1.3.1" - web3-utils "^4.1.1" - web3-validator "^2.0.4" + web3-types "^1.5.0" + web3-utils "^4.2.2" + web3-validator "^2.0.5" -web3-eth-ens@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/web3-eth-ens/-/web3-eth-ens-4.1.0.tgz#8e1f94dcf9a5ee051ab961389e9cc2975e81f5b9" - integrity sha512-B+QsXXJb/gJkHb1ZGfErNLeFI9zUf2TsQcvi2+NsSuzFwvjIO5IyrrGtqBmXMLWC8ZikMOHuc8ZfFuGrELl31Q== +web3-eth-ens@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/web3-eth-ens/-/web3-eth-ens-4.2.0.tgz#8734b034efd48a735f7052fef0205653a78b84cb" + integrity sha512-qYj34te2UctoObt8rlEIY/t2MuTMiMiiHhO2JAHRGqSLCQ7b8DM3RpvkiiSB0N0ZyEn+CetZqJCTYb8DNKBS/g== dependencies: "@adraffy/ens-normalize" "^1.8.8" web3-core "^4.3.2" web3-errors "^1.1.4" web3-eth "^4.5.0" - web3-eth-contract "^4.2.0" + web3-eth-contract "^4.3.0" web3-net "^4.0.7" web3-types "^1.5.0" - web3-utils "^4.2.1" - web3-validator "^2.0.4" + web3-utils "^4.2.2" + web3-validator "^2.0.5" web3-eth-iban@^4.0.7: version "4.0.7" @@ -24552,7 +24459,7 @@ web3-eth-personal@^4.0.8: web3-utils "^4.0.7" web3-validator "^2.0.3" -web3-eth@^4.3.1, web3-eth@^4.4.0, web3-eth@^4.5.0: +web3-eth@^4.3.1, web3-eth@^4.5.0: version "4.5.0" resolved "https://registry.yarnpkg.com/web3-eth/-/web3-eth-4.5.0.tgz#57f5cc020c9b3c4c20d0dacbd87eaa1a9d6c86c0" integrity sha512-crisE46o/SHMVm+XHAXEaR8k76NCImq+hi0QQEJ+VaLZbDobI/Gvog1HwTukDUDRgnYSAFGqD0cTRyAwDurwpA== @@ -24651,40 +24558,40 @@ web3-utils@^1.3.4, web3-utils@^1.3.6, web3-utils@^1.8.1: randombytes "^2.1.0" utf8 "3.0.0" -web3-utils@^4.0.7, web3-utils@^4.1.0, web3-utils@^4.1.1, web3-utils@^4.2.1: - version "4.2.1" - resolved "https://registry.yarnpkg.com/web3-utils/-/web3-utils-4.2.1.tgz#326bc6e9e4d047f7b38ba68bee1399c4f9f621e3" - integrity sha512-Fk29BlEqD9Q9Cnw4pBkKw7czcXiRpsSco/BzEUl4ye0ZTSHANQFfjsfQmNm4t7uY11u6Ah+8F3tNjBeU4CA80A== +web3-utils@^4.0.7, web3-utils@^4.1.0, web3-utils@^4.1.1, web3-utils@^4.2.1, web3-utils@^4.2.2: + version "4.2.2" + resolved "https://registry.yarnpkg.com/web3-utils/-/web3-utils-4.2.2.tgz#8fb7c58cfc02d681f17d7806732ce9fb1170c338" + integrity sha512-z+4owWcnoB4EH8yWIL1FBeyqe+sXwaGxUDtVTNPTMf2oB5C+paCToZUdCV5Bi+M543zZEzlzNTabOD+OWNc7NA== dependencies: ethereum-cryptography "^2.0.0" eventemitter3 "^5.0.1" web3-errors "^1.1.4" web3-types "^1.5.0" - web3-validator "^2.0.4" + web3-validator "^2.0.5" -web3-validator@^2.0.3, web3-validator@^2.0.4: - version "2.0.4" - resolved "https://registry.yarnpkg.com/web3-validator/-/web3-validator-2.0.4.tgz#66f34c94f21a3c94d0dc2a2d30deb8a379825d38" - integrity sha512-qRxVePwdW+SByOmTpDZFWHIUAa7PswvxNszrOua6BoGqAhERo5oJZBN+EbWtK/+O+ApNxt5FR3nCPmiZldiOQA== +web3-validator@^2.0.3, web3-validator@^2.0.4, web3-validator@^2.0.5: + version "2.0.5" + resolved "https://registry.yarnpkg.com/web3-validator/-/web3-validator-2.0.5.tgz#de1984bdb34f292251b86400dba7169700db0849" + integrity sha512-2gLOSW8XqEN5pw5jVUm20EB7A8SbQiekpAtiI0JBmCIV0a2rp97v8FgWY5E3UEqnw5WFfEqvcDVW92EyynDTyQ== dependencies: ethereum-cryptography "^2.0.0" util "^0.12.5" web3-errors "^1.1.4" - web3-types "^1.3.1" + web3-types "^1.5.0" zod "^3.21.4" web3@^4.3.0: - version "4.6.0" - resolved "https://registry.yarnpkg.com/web3/-/web3-4.6.0.tgz#202b31489d843d278547002b9a73327f19952b70" - integrity sha512-hoI6r29B4kjxINI21rBVaE0Bz0hwtW+Sbppn5ZDTWn5PSQpBW4ecYFDVKVE6K3gbmSjY2fknu2cjBTqha7S53A== + version "4.7.0" + resolved "https://registry.yarnpkg.com/web3/-/web3-4.7.0.tgz#d6cb8ff8653b92f26ddd6da0957999e61ae7f107" + integrity sha512-3g+1e7B/IW0Nw9WP1dotrZKWD9o5IBfl27dxEnE1LxBZBax6ZkviiAwf18utIhlNBD07RgI+PPfKDXxfDBlHWA== dependencies: web3-core "^4.3.2" web3-errors "^1.1.4" web3-eth "^4.5.0" web3-eth-abi "^4.2.0" web3-eth-accounts "^4.1.1" - web3-eth-contract "^4.2.0" - web3-eth-ens "^4.1.0" + web3-eth-contract "^4.3.0" + web3-eth-ens "^4.2.0" web3-eth-iban "^4.0.7" web3-eth-personal "^4.0.8" web3-net "^4.0.7" @@ -24692,13 +24599,13 @@ web3@^4.3.0: web3-providers-ws "^4.0.7" web3-rpc-methods "^1.2.0" web3-types "^1.5.0" - web3-utils "^4.2.1" - web3-validator "^2.0.4" + web3-utils "^4.2.2" + web3-validator "^2.0.5" -webcrypto-core@^1.7.8: - version "1.7.8" - resolved "https://registry.yarnpkg.com/webcrypto-core/-/webcrypto-core-1.7.8.tgz#056918036e846c72cfebbb04052e283f57f1114a" - integrity sha512-eBR98r9nQXTqXt/yDRtInszPMjTaSAMJAFDg2AHsgrnczawT1asx9YNBX6k5p+MekbPF4+s/UJJrr88zsTqkSg== +webcrypto-core@^1.7.9: + version "1.7.9" + resolved "https://registry.yarnpkg.com/webcrypto-core/-/webcrypto-core-1.7.9.tgz#a585f0032dbc88d202cff4f266cbef02ba48bd7a" + integrity sha512-FE+a4PPkOmBbgNDIyRmcHhgXn+2ClRl3JzJdDu/P4+B8y81LqKe6RAsI9b3lAOHe1T1BMkSjsRHTYRikImZnVA== dependencies: "@peculiar/asn1-schema" "^2.3.8" "@peculiar/json-schema" "^1.1.12" @@ -24809,25 +24716,25 @@ webpack@5.82.1: webpack-sources "^3.2.3" webpack@^5.88.1: - version "5.90.3" - resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.90.3.tgz#37b8f74d3ded061ba789bb22b31e82eed75bd9ac" - integrity sha512-h6uDYlWCctQRuXBs1oYpVe6sFcWedl0dpcVaTf/YF67J9bKvwJajFulMVSYKHrksMB3I/pIagRzDxwxkebuzKA== + version "5.91.0" + resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.91.0.tgz#ffa92c1c618d18c878f06892bbdc3373c71a01d9" + integrity sha512-rzVwlLeBWHJbmgTC/8TvAcu5vpJNII+MelQpylD4jNERPwpBJOE2lEcko1zJX3QJeLjTTAnQxn/OJ8bjDzVQaw== dependencies: "@types/eslint-scope" "^3.7.3" "@types/estree" "^1.0.5" - "@webassemblyjs/ast" "^1.11.5" - "@webassemblyjs/wasm-edit" "^1.11.5" - "@webassemblyjs/wasm-parser" "^1.11.5" + "@webassemblyjs/ast" "^1.12.1" + "@webassemblyjs/wasm-edit" "^1.12.1" + "@webassemblyjs/wasm-parser" "^1.12.1" acorn "^8.7.1" acorn-import-assertions "^1.9.0" browserslist "^4.21.10" chrome-trace-event "^1.0.2" - enhanced-resolve "^5.15.0" + enhanced-resolve "^5.16.0" es-module-lexer "^1.2.1" eslint-scope "5.1.1" events "^3.2.0" glob-to-regexp "^0.4.1" - graceful-fs "^4.2.9" + graceful-fs "^4.2.11" json-parse-even-better-errors "^2.3.1" loader-runner "^4.2.0" mime-types "^2.1.27" @@ -24835,7 +24742,7 @@ webpack@^5.88.1: schema-utils "^3.2.0" tapable "^2.1.1" terser-webpack-plugin "^5.3.10" - watchpack "^2.4.0" + watchpack "^2.4.1" webpack-sources "^3.2.3" well-known-symbols@^2.0.0: @@ -25292,9 +25199,9 @@ yargs@^17.3.1, yargs@^17.6.2, yargs@^17.7.2: yargs-parser "^21.1.1" ylru@^1.2.0: - version "1.3.2" - resolved "https://registry.yarnpkg.com/ylru/-/ylru-1.3.2.tgz#0de48017473275a4cbdfc83a1eaf67c01af8a785" - integrity sha512-RXRJzMiK6U2ye0BlGGZnmpwJDPgakn6aNQ0A7gHRbD4I0uvK4TW6UqkK1V0pp9jskjJBAXd3dRrbzWkqJ+6cxA== + version "1.4.0" + resolved "https://registry.yarnpkg.com/ylru/-/ylru-1.4.0.tgz#0cf0aa57e9c24f8a2cbde0cc1ca2c9592ac4e0f6" + integrity sha512-2OQsPNEmBCvXuFlIni/a+Rn+R2pHW9INm0BxXJ4hVDA8TirqMj+J/Rp9ItLatT/5pZqWwefVrTQcHpixsxnVlA== yn@3.1.1: version "3.1.1" From 11f8869b1a45ca02431a393392ac1639bbb73bf7 Mon Sep 17 00:00:00 2001 From: portuu3 <61605646+portuu3@users.noreply.github.com> Date: Thu, 4 Apr 2024 16:00:16 +0200 Subject: [PATCH 39/66] change cron expression to 5 min (#1825) --- packages/apps/job-launcher/server/vercel.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/apps/job-launcher/server/vercel.json b/packages/apps/job-launcher/server/vercel.json index e748a56856..6de398ed25 100644 --- a/packages/apps/job-launcher/server/vercel.json +++ b/packages/apps/job-launcher/server/vercel.json @@ -55,23 +55,23 @@ "crons": [ { "path": "/cron/escrow/create", - "schedule": "*/10 * * * *" + "schedule": "*/5 * * * *" }, { "path": "/cron/escrow/setup", - "schedule": "*/10 * * * *" + "schedule": "*/5 * * * *" }, { "path": "/cron/escrow/fund", - "schedule": "*/10 * * * *" + "schedule": "*/5 * * * *" }, { "path": "/cron/escrow/cancel", - "schedule": "*/10 * * * *" + "schedule": "*/5 * * * *" }, { "path": "/cron/webhook/process", - "schedule": "*/10 * * * *" + "schedule": "*/5 * * * *" } ], "headers": [ From f41bb23d1dc0cfa239878e1e41b69747025974b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Francisco=20L=C3=B3pez?= <50665615+flopez7@users.noreply.github.com> Date: Fri, 5 Apr 2024 09:22:21 +0200 Subject: [PATCH 40/66] [Reputation Oracle] Config service (#1810) * Improve config service in reputation oracle * Use disabled as default value for SENDGRID_API_KEY * Create new KYC entry when synaps apiKey disabled * Fix tests --- .../server/src/app.module.ts | 2 + .../src/common/config/auth-config.service.ts | 49 ++++++++++ .../server/src/common/config/config.module.ts | 41 ++++++++ .../common/config/database-config.service.ts | 31 ++++++ .../server/src/common/config/env-schema.ts | 55 +++++++++++ .../server/src/common/config/env.ts | 98 ------------------- .../server/src/common/config/index.ts | 3 +- .../src/common/config/pgp-config.service.ts | 16 +++ .../config/reputation-config.service.ts | 13 +++ .../src/common/config/s3-config.service.ts | 25 +++++ .../server/src/common/config/s3.ts | 13 --- .../common/config/sendgrid-config.service.ts | 26 +++++ .../common/config/server-config.service.ts | 28 ++++++ .../common/config/synaps-config.service.ts | 17 ++++ .../src/common/config/web3-config.service.ts | 16 +++ .../server/src/common/constants/index.ts | 1 + .../server/src/common/guards/cron.auth.ts | 7 +- .../server/src/database/database.module.ts | 48 ++++----- .../apps/reputation-oracle/server/src/main.ts | 11 ++- .../server/src/modules/auth/auth.module.ts | 16 +-- .../src/modules/auth/auth.service.spec.ts | 32 +++--- .../server/src/modules/auth/auth.service.ts | 77 +++++---------- .../src/modules/auth/strategy/jwt.http.ts | 7 +- .../modules/cron-job/cron-job.service.spec.ts | 16 ++- .../src/modules/kyc/kyc.service.spec.ts | 38 +++---- .../server/src/modules/kyc/kyc.service.ts | 38 +++---- .../src/modules/payout/payout.service.spec.ts | 18 +--- .../src/modules/payout/payout.service.ts | 2 - .../reputation/reputation.controller.spec.ts | 23 ++--- .../reputation/reputation.service.spec.ts | 19 +--- .../modules/reputation/reputation.service.ts | 21 +--- .../modules/sendgrid/sendgrid.service.spec.ts | 52 +++------- .../src/modules/sendgrid/sendgrid.service.ts | 35 ++----- .../src/modules/storage/storage.module.ts | 4 +- .../modules/storage/storage.service.spec.ts | 77 +++++---------- .../src/modules/storage/storage.service.ts | 61 +++++++----- .../src/modules/user/user.service.spec.ts | 19 +--- .../server/src/modules/user/user.service.ts | 8 +- .../src/modules/web3/web3.service.spec.ts | 27 ++--- .../server/src/modules/web3/web3.service.ts | 19 +--- .../src/modules/webhook/webhook.repository.ts | 10 +- .../modules/webhook/webhook.service.spec.ts | 29 +++--- .../src/modules/webhook/webhook.service.ts | 14 ++- .../server/test/constants.ts | 2 +- scripts/fortune/.env.rep-oracle | 2 +- 45 files changed, 582 insertions(+), 584 deletions(-) create mode 100644 packages/apps/reputation-oracle/server/src/common/config/auth-config.service.ts create mode 100644 packages/apps/reputation-oracle/server/src/common/config/config.module.ts create mode 100644 packages/apps/reputation-oracle/server/src/common/config/database-config.service.ts create mode 100644 packages/apps/reputation-oracle/server/src/common/config/env-schema.ts delete mode 100644 packages/apps/reputation-oracle/server/src/common/config/env.ts create mode 100644 packages/apps/reputation-oracle/server/src/common/config/pgp-config.service.ts create mode 100644 packages/apps/reputation-oracle/server/src/common/config/reputation-config.service.ts create mode 100644 packages/apps/reputation-oracle/server/src/common/config/s3-config.service.ts delete mode 100644 packages/apps/reputation-oracle/server/src/common/config/s3.ts create mode 100644 packages/apps/reputation-oracle/server/src/common/config/sendgrid-config.service.ts create mode 100644 packages/apps/reputation-oracle/server/src/common/config/server-config.service.ts create mode 100644 packages/apps/reputation-oracle/server/src/common/config/synaps-config.service.ts create mode 100644 packages/apps/reputation-oracle/server/src/common/config/web3-config.service.ts diff --git a/packages/apps/reputation-oracle/server/src/app.module.ts b/packages/apps/reputation-oracle/server/src/app.module.ts index 0a4b60fb3d..4cc6ed71d7 100644 --- a/packages/apps/reputation-oracle/server/src/app.module.ts +++ b/packages/apps/reputation-oracle/server/src/app.module.ts @@ -17,6 +17,7 @@ import { SnakeCaseInterceptor } from './common/interceptors/snake-case'; import { KycModule } from './modules/kyc/kyc.module'; import { CronJobModule } from './modules/cron-job/cron-job.module'; import { PayoutModule } from './modules/payout/payout.module'; +import { EnvConfigModule } from './common/config/config.module'; @Module({ providers: [ @@ -53,6 +54,7 @@ import { PayoutModule } from './modules/payout/payout.module'; }), CronJobModule, PayoutModule, + EnvConfigModule, ], controllers: [AppController], }) diff --git a/packages/apps/reputation-oracle/server/src/common/config/auth-config.service.ts b/packages/apps/reputation-oracle/server/src/common/config/auth-config.service.ts new file mode 100644 index 0000000000..465681fa28 --- /dev/null +++ b/packages/apps/reputation-oracle/server/src/common/config/auth-config.service.ts @@ -0,0 +1,49 @@ +import { Injectable } from '@nestjs/common'; +import { ConfigService } from '@nestjs/config'; + +@Injectable() +export class AuthConfigService { + constructor(private configService: ConfigService) {} + get jwtPrivateKey(): string { + return this.configService.get('JWT_PRIVATE_KEY', ''); + } + get jwtPublicKey(): string { + return this.configService.get('JWT_PUBLIC_KEY', ''); + } + get accessTokenExpiresIn(): number { + return this.configService.get( + 'JWT_ACCESS_TOKEN_EXPIRES_IN', + 300000, + ); + } + get refreshTokenExpiresIn(): number { + return this.configService.get( + 'JWT_REFRESH_TOKEN_EXPIRES_IN', + 3600000, + ); + } + get verifyEmailTokenExpiresIn(): number { + return this.configService.get( + 'VERIFY_EMAIL_TOKEN_EXPIRES_IN', + 1800000, + ); + } + get forgotPasswordExpiresIn(): number { + return this.configService.get( + 'FORGOT_PASSWORD_TOKEN_EXPIRES_IN', + 1800000, + ); + } + get hCaptchaSiteKey(): string { + return this.configService.get('HCAPTCHA_SITE_KEY', ''); + } + get hCaptchaSecret(): string { + return this.configService.get('HCAPTCHA_SECRET', ''); + } + get hCaptchaExchangeURL(): string { + return this.configService.get( + 'HCAPTCHA_EXCHANGE_URL', + 'https://foundation-exchange.hmt.ai', + ); + } +} diff --git a/packages/apps/reputation-oracle/server/src/common/config/config.module.ts b/packages/apps/reputation-oracle/server/src/common/config/config.module.ts new file mode 100644 index 0000000000..2acfa08c1f --- /dev/null +++ b/packages/apps/reputation-oracle/server/src/common/config/config.module.ts @@ -0,0 +1,41 @@ +import { Module, Global } from '@nestjs/common'; +import { ConfigService } from '@nestjs/config'; + +import { AuthConfigService } from './auth-config.service'; +import { ServerConfigService } from './server-config.service'; +import { DatabaseConfigService } from './database-config.service'; +import { PGPConfigService } from './pgp-config.service'; +import { S3ConfigService } from './s3-config.service'; +import { SendgridConfigService } from './sendgrid-config.service'; +import { Web3ConfigService } from './web3-config.service'; +import { ReputationConfigService } from './reputation-config.service'; +import { SynapsConfigService } from './synaps-config.service'; + +@Global() +@Module({ + providers: [ + ConfigService, + ServerConfigService, + AuthConfigService, + DatabaseConfigService, + Web3ConfigService, + S3ConfigService, + ReputationConfigService, + SendgridConfigService, + SynapsConfigService, + PGPConfigService, + ], + exports: [ + ConfigService, + ServerConfigService, + AuthConfigService, + DatabaseConfigService, + Web3ConfigService, + S3ConfigService, + ReputationConfigService, + SendgridConfigService, + SynapsConfigService, + PGPConfigService, + ], +}) +export class EnvConfigModule {} diff --git a/packages/apps/reputation-oracle/server/src/common/config/database-config.service.ts b/packages/apps/reputation-oracle/server/src/common/config/database-config.service.ts new file mode 100644 index 0000000000..7bfba0dbac --- /dev/null +++ b/packages/apps/reputation-oracle/server/src/common/config/database-config.service.ts @@ -0,0 +1,31 @@ +import { Injectable } from '@nestjs/common'; +import { ConfigService } from '@nestjs/config'; + +@Injectable() +export class DatabaseConfigService { + constructor(private configService: ConfigService) {} + get host(): string { + return this.configService.get('POSTGRES_HOST', '127.0.0.1'); + } + get port(): number { + return +this.configService.get('POSTGRES_PORT', 5432); + } + get user(): string { + return this.configService.get('POSTGRES_USER', 'operator'); + } + get password(): string { + return this.configService.get('POSTGRES_PASSWORD', 'qwerty'); + } + get database(): string { + return this.configService.get( + 'POSTGRES_DATABASE', + 'reputation-oracle', + ); + } + get ssl(): boolean { + return this.configService.get('POSTGRES_SSL', 'false') === 'true'; + } + get logging(): string { + return this.configService.get('POSTGRES_LOGGING', ''); + } +} diff --git a/packages/apps/reputation-oracle/server/src/common/config/env-schema.ts b/packages/apps/reputation-oracle/server/src/common/config/env-schema.ts new file mode 100644 index 0000000000..9be96c0d20 --- /dev/null +++ b/packages/apps/reputation-oracle/server/src/common/config/env-schema.ts @@ -0,0 +1,55 @@ +import * as Joi from 'joi'; + +export const envValidator = Joi.object({ + // General + NODE_ENV: Joi.string(), + HOST: Joi.string(), + PORT: Joi.string(), + FE_URL: Joi.string(), + SESSION_SECRET: Joi.string(), + MAX_RETRY_COUNT: Joi.number(), + CRON_SECRET: Joi.string().required(), + // Auth + JWT_PRIVATE_KEY: Joi.string(), + JWT_PUBLIC_KEY: Joi.string(), + JWT_ACCESS_TOKEN_EXPIRES_IN: Joi.number(), + JWT_REFRESH_TOKEN_EXPIRES_IN: Joi.number(), + VERIFY_EMAIL_TOKEN_EXPIRES_IN: Joi.number(), + FORGOT_PASSWORD_TOKEN_EXPIRES_IN: Joi.number(), + HCAPTCHA_SITE_KEY: Joi.string().required(), + HCAPTCHA_SECRET: Joi.string().required(), + HCAPTCHA_EXCHANGE_URL: Joi.string().description('hcaptcha exchange url'), + // Database + POSTGRES_HOST: Joi.string(), + POSTGRES_USER: Joi.string(), + POSTGRES_PASSWORD: Joi.string(), + POSTGRES_DATABASE: Joi.string(), + POSTGRES_PORT: Joi.string(), + POSTGRES_SSL: Joi.string(), + POSTGRES_LOGGING: Joi.string(), + // Web3 + WEB3_ENV: Joi.string(), + WEB3_PRIVATE_KEY: Joi.string().required(), + GAS_PRICE_MULTIPLIER: Joi.number(), + // S3 + S3_ENDPOINT: Joi.string(), + S3_PORT: Joi.string(), + S3_ACCESS_KEY: Joi.string().required(), + S3_SECRET_KEY: Joi.string().required(), + S3_BUCKET: Joi.string(), + S3_USE_SSL: Joi.string(), + // SendGrid + SENDGRID_API_KEY: Joi.string().required(), + SENDGRID_FROM_EMAIL: Joi.string(), + SENDGRID_FROM_NAME: Joi.string(), + // Reputation Level + REPUTATION_LEVEL_LOW: Joi.number(), + REPUTATION_LEVEL_HIGH: Joi.number(), + // Encryption + PGP_PRIVATE_KEY: Joi.string(), + PGP_PASSPHRASE: Joi.string(), + PGP_ENCRYPT: Joi.string(), + // Synaps Kyc + SYNAPS_API_KEY: Joi.string().required(), + SYNAPS_WEBHOOK_SECRET: Joi.string().required(), +}); diff --git a/packages/apps/reputation-oracle/server/src/common/config/env.ts b/packages/apps/reputation-oracle/server/src/common/config/env.ts deleted file mode 100644 index 8330feec58..0000000000 --- a/packages/apps/reputation-oracle/server/src/common/config/env.ts +++ /dev/null @@ -1,98 +0,0 @@ -import * as Joi from 'joi'; - -export const ConfigNames = { - NODE_ENV: 'NODE_ENV', - HOST: 'HOST', - PORT: 'PORT', - FE_URL: 'FE_URL', - SESSION_SECRET: 'SESSION_SECRET', - MAX_RETRY_COUNT: 'MAX_RETRY_COUNT', - JWT_PRIVATE_KEY: 'JWT_PRIVATE_KEY', - JWT_PUBLIC_KEY: 'JWT_PUBLIC_KEY', - JWT_ACCESS_TOKEN_EXPIRES_IN: 'JWT_ACCESS_TOKEN_EXPIRES_IN', - REFRESH_TOKEN_EXPIRES_IN: 'REFRESH_TOKEN_EXPIRES_IN', - VERIFY_EMAIL_TOKEN_EXPIRES_IN: 'VERIFY_EMAIL_TOKEN_EXPIRES_IN', - FORGOT_PASSWORD_TOKEN_EXPIRES_IN: 'FORGOT_PASSWORD_TOKEN_EXPIRES_IN', - POSTGRES_HOST: 'POSTGRES_HOST', - POSTGRES_USER: 'POSTGRES_USER', - POSTGRES_PASSWORD: 'POSTGRES_PASSWORD', - POSTGRES_DATABASE: 'POSTGRES_DATABASE', - POSTGRES_PORT: 'POSTGRES_PORT', - POSTGRES_SYNC: 'POSTGRES_SYNC', - POSTGRES_SSL: 'POSTGRES_SSL', - POSTGRES_LOGGING: 'POSTGRES_LOGGING', - WEB3_ENV: 'WEB3_ENV', - WEB3_PRIVATE_KEY: 'WEB3_PRIVATE_KEY', - GAS_PRICE_MULTIPLIER: 'GAS_PRICE_MULTIPLIER', - S3_ENDPOINT: 'S3_ENDPOINT', - S3_PORT: 'S3_PORT', - S3_ACCESS_KEY: 'S3_ACCESS_KEY', - S3_SECRET_KEY: 'S3_SECRET_KEY', - S3_BUCKET: 'S3_BUCKET', - S3_USE_SSL: 'S3_USE_SSL', - SENDGRID_API_KEY: 'SENDGRID_API_KEY', - SENDGRID_FROM_EMAIL: 'SENDGRID_FROM_EMAIL', - SENDGRID_FROM_NAME: 'SENDGRID_FROM_NAME', - REPUTATION_LEVEL_LOW: 'REPUTATION_LEVEL_LOW', - REPUTATION_LEVEL_HIGH: 'REPUTATION_LEVEL_HIGH', - ENCRYPTION_PRIVATE_KEY: 'ENCRYPTION_PRIVATE_KEY', - ENCRYPTION_PASSPHRASE: 'ENCRYPTION_PASSPHRASE', - PGP_ENCRYPT: 'PGP_ENCRYPT', - SYNAPS_API_KEY: 'SYNAPS_API_KEY', - SYNAPS_WEBHOOK_SECRET: 'SYNAPS_WEBHOOK_SECRET', - APIKEY_ITERATIONS: 'APIKEY_ITERATIONS', - APIKEY_KEY_LENGTH: 'APIKEY_KEY_LENGTH', - CRON_SECRET: 'CRON_SECRET', -}; - -export const envValidator = Joi.object({ - // General - NODE_ENV: Joi.string().default('development'), - HOST: Joi.string().default('localhost'), - PORT: Joi.string().default(5000), - FE_URL: Joi.string().default('http://localhost:3001'), - SESSION_SECRET: Joi.string().default('session_key'), - MAX_RETRY_COUNT: Joi.number().default(5), - // Auth - HASH_SECRET: Joi.string().default('a328af3fc1dad15342cc3d68936008fa'), - JWT_SECRET: Joi.string().default('secret'), - JWT_ACCESS_TOKEN_EXPIRES_IN: Joi.string().default(1000000000), - JWT_REFRESH_TOKEN_EXPIRES_IN: Joi.string().default(1000000000), - // Database - DB_TYPE: Joi.string().default('postgres'), - POSTGRES_HOST: Joi.string().default('127.0.0.1'), - POSTGRES_USER: Joi.string().default('operator'), - POSTGRES_PASSWORD: Joi.string().default('qwerty'), - POSTGRES_DATABASE: Joi.string().default('reputation-oracle'), - POSTGRES_PORT: Joi.string().default('5432'), - POSTGRES_SYNC: Joi.string().default('false'), - POSTGRES_SSL: Joi.string().default('false'), - POSTGRES_LOGGING: Joi.string(), - // Web3 - WEB3_ENV: Joi.string().default('testnet'), - WEB3_PRIVATE_KEY: Joi.string().required(), - GAS_PRICE_MULTIPLIER: Joi.number().default(null), - // S3 - S3_ENDPOINT: Joi.string().default('127.0.0.1'), - S3_PORT: Joi.string().default(9000), - S3_ACCESS_KEY: Joi.string().required(), - S3_SECRET_KEY: Joi.string().required(), - S3_BUCKET: Joi.string().default('reputation'), - S3_USE_SSL: Joi.string().default(false), - // SendGrid - SENDGRID_API_KEY: Joi.string().required(), - SENDGRID_FROM_EMAIL: Joi.string().default('reputation-oracle@hmt.ai'), - SENDGRID_FROM_NAME: Joi.string().default('Human Protocol Reputation Oracle'), - // Reputation Level - REPUTATION_LEVEL_LOW: Joi.number().default(300), - REPUTATION_LEVEL_HIGH: Joi.number().default(700), - // Encryption - ENCRYPTION_PRIVATE_KEY: Joi.string().default(''), - ENCRYPTION_PASSPHRASE: Joi.string().default(''), - PGP_ENCRYPT: Joi.string().default(false), - // Synaps Kyc - SYNAPS_API_KEY: Joi.string().required(), - SYNAPS_WEBHOOK_SECRET: Joi.string().required(), - // Cron Secret - CRON_SECRET: Joi.string().required(), -}); diff --git a/packages/apps/reputation-oracle/server/src/common/config/index.ts b/packages/apps/reputation-oracle/server/src/common/config/index.ts index 20c54299ce..8b28b497be 100644 --- a/packages/apps/reputation-oracle/server/src/common/config/index.ts +++ b/packages/apps/reputation-oracle/server/src/common/config/index.ts @@ -1,3 +1,2 @@ -export * from './env'; +export * from './env-schema'; export * from './networks'; -export * from './s3'; diff --git a/packages/apps/reputation-oracle/server/src/common/config/pgp-config.service.ts b/packages/apps/reputation-oracle/server/src/common/config/pgp-config.service.ts new file mode 100644 index 0000000000..a6ec43a0df --- /dev/null +++ b/packages/apps/reputation-oracle/server/src/common/config/pgp-config.service.ts @@ -0,0 +1,16 @@ +import { Injectable } from '@nestjs/common'; +import { ConfigService } from '@nestjs/config'; + +@Injectable() +export class PGPConfigService { + constructor(private configService: ConfigService) {} + get encrypt(): boolean { + return this.configService.get('PGP_ENCRYPT', 'false') === 'true'; + } + get privateKey(): string { + return this.configService.get('PGP_PRIVATE_KEY', ''); + } + get passphrase(): string { + return this.configService.get('PGP_PASSPHRASE', ''); + } +} diff --git a/packages/apps/reputation-oracle/server/src/common/config/reputation-config.service.ts b/packages/apps/reputation-oracle/server/src/common/config/reputation-config.service.ts new file mode 100644 index 0000000000..5190fc1ea0 --- /dev/null +++ b/packages/apps/reputation-oracle/server/src/common/config/reputation-config.service.ts @@ -0,0 +1,13 @@ +import { Injectable } from '@nestjs/common'; +import { ConfigService } from '@nestjs/config'; + +@Injectable() +export class ReputationConfigService { + constructor(private configService: ConfigService) {} + get lowLevel(): number { + return +this.configService.get('REPUTATION_LEVEL_LOW', 300); + } + get highLevel(): number { + return +this.configService.get('REPUTATION_LEVEL_HIGH', 700); + } +} diff --git a/packages/apps/reputation-oracle/server/src/common/config/s3-config.service.ts b/packages/apps/reputation-oracle/server/src/common/config/s3-config.service.ts new file mode 100644 index 0000000000..14c164614a --- /dev/null +++ b/packages/apps/reputation-oracle/server/src/common/config/s3-config.service.ts @@ -0,0 +1,25 @@ +import { Injectable } from '@nestjs/common'; +import { ConfigService } from '@nestjs/config'; + +@Injectable() +export class S3ConfigService { + constructor(private configService: ConfigService) {} + get endpoint(): string { + return this.configService.get('S3_ENDPOINT', '127.0.0.1'); + } + get port(): number { + return +this.configService.get('S3_PORT', 9000); + } + get accessKey(): string { + return this.configService.get('S3_ACCESS_KEY', ''); + } + get secretKey(): string { + return this.configService.get('S3_SECRET_KEY', ''); + } + get bucket(): string { + return this.configService.get('S3_BUCKET', 'reputation'); + } + get useSSL(): boolean { + return this.configService.get('S3_USE_SSL', 'false') === 'true'; + } +} diff --git a/packages/apps/reputation-oracle/server/src/common/config/s3.ts b/packages/apps/reputation-oracle/server/src/common/config/s3.ts deleted file mode 100644 index 2389bffabb..0000000000 --- a/packages/apps/reputation-oracle/server/src/common/config/s3.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { ConfigType, registerAs } from '@nestjs/config'; - -export const s3Config = registerAs('s3', () => ({ - endPoint: process.env.S3_ENDPOINT || '127.0.0.1', - port: +(process.env.S3_PORT || 9000), - accessKey: process.env.S3_ACCESS_KEY || '', - secretKey: process.env.S3_SECRET_KEY || '', - bucket: process.env.S3_BUCKET || '', - useSSL: process.env.S3_USE_SSL === 'true', -})); - -export const s3ConfigKey = s3Config.KEY; -export type S3ConfigType = ConfigType; diff --git a/packages/apps/reputation-oracle/server/src/common/config/sendgrid-config.service.ts b/packages/apps/reputation-oracle/server/src/common/config/sendgrid-config.service.ts new file mode 100644 index 0000000000..c19005914e --- /dev/null +++ b/packages/apps/reputation-oracle/server/src/common/config/sendgrid-config.service.ts @@ -0,0 +1,26 @@ +import { Injectable } from '@nestjs/common'; +import { ConfigService } from '@nestjs/config'; +import { SENDGRID_API_KEY_DISABLED } from '../constants'; + +@Injectable() +export class SendgridConfigService { + constructor(private configService: ConfigService) {} + get apiKey(): string { + return this.configService.get( + 'SENDGRID_API_KEY', + SENDGRID_API_KEY_DISABLED, + ); + } + get fromEmail(): string { + return this.configService.get( + 'SENDGRID_FROM_EMAIL', + 'reputation-oracle@hmt.ai', + ); + } + get fromName(): string { + return this.configService.get( + 'SENDGRID_FROM_NAME', + 'Human Protocol Reputation Oracle', + ); + } +} diff --git a/packages/apps/reputation-oracle/server/src/common/config/server-config.service.ts b/packages/apps/reputation-oracle/server/src/common/config/server-config.service.ts new file mode 100644 index 0000000000..464b2f39bd --- /dev/null +++ b/packages/apps/reputation-oracle/server/src/common/config/server-config.service.ts @@ -0,0 +1,28 @@ +import { Injectable } from '@nestjs/common'; +import { ConfigService } from '@nestjs/config'; + +@Injectable() +export class ServerConfigService { + constructor(private configService: ConfigService) {} + get nodeEnv(): string { + return this.configService.get('NODE_ENV', 'development'); + } + get host(): string { + return this.configService.get('HOST', 'localhost'); + } + get port(): number { + return +this.configService.get('PORT', 5003); + } + get feURL(): string { + return this.configService.get('FE_URL', 'http://localhost:3001'); + } + get sessionSecret(): string { + return this.configService.get('SESSION_SECRET', 'session_key'); + } + get maxRetryCount(): number { + return +this.configService.get('MAX_RETRY_COUNT', 5); + } + get cronSecret(): string { + return this.configService.get('CRON_SECRET', ''); + } +} diff --git a/packages/apps/reputation-oracle/server/src/common/config/synaps-config.service.ts b/packages/apps/reputation-oracle/server/src/common/config/synaps-config.service.ts new file mode 100644 index 0000000000..8804ee522b --- /dev/null +++ b/packages/apps/reputation-oracle/server/src/common/config/synaps-config.service.ts @@ -0,0 +1,17 @@ +import { Injectable } from '@nestjs/common'; +import { ConfigService } from '@nestjs/config'; +import { SYNAPS_API_KEY_DISABLED } from '../constants'; + +@Injectable() +export class SynapsConfigService { + constructor(private configService: ConfigService) {} + get apiKey(): string { + return this.configService.get( + 'SYNAPS_API_KEY', + SYNAPS_API_KEY_DISABLED, + ); + } + get webhookSecret(): string { + return this.configService.get('SYNAPS_WEBHOOK_SECRET', ''); + } +} diff --git a/packages/apps/reputation-oracle/server/src/common/config/web3-config.service.ts b/packages/apps/reputation-oracle/server/src/common/config/web3-config.service.ts new file mode 100644 index 0000000000..cecfc628fa --- /dev/null +++ b/packages/apps/reputation-oracle/server/src/common/config/web3-config.service.ts @@ -0,0 +1,16 @@ +import { Injectable } from '@nestjs/common'; +import { ConfigService } from '@nestjs/config'; + +@Injectable() +export class Web3ConfigService { + constructor(private configService: ConfigService) {} + get env(): string { + return this.configService.get('WEB3_ENV', 'testnet'); + } + get privateKey(): string { + return this.configService.get('WEB3_PRIVATE_KEY', ''); + } + get gasPriceMultiplier(): number { + return +this.configService.get('GAS_PRICE_MULTIPLIER', 1); + } +} diff --git a/packages/apps/reputation-oracle/server/src/common/constants/index.ts b/packages/apps/reputation-oracle/server/src/common/constants/index.ts index f68da3b9b8..5d633c6872 100644 --- a/packages/apps/reputation-oracle/server/src/common/constants/index.ts +++ b/packages/apps/reputation-oracle/server/src/common/constants/index.ts @@ -8,6 +8,7 @@ export const JWT_PREFIX = 'bearer '; export const SENDGRID_API_KEY_REGEX = /^SG\.[A-Za-z0-9-_]{22}\.[A-Za-z0-9-_]{43}$/; export const SENDGRID_API_KEY_DISABLED = 'sendgrid-disabled'; +export const SYNAPS_API_KEY_DISABLED = 'synaps-disabled'; export const SENDGRID_TEMPLATES = { signup: 'd-ca99cc7410aa4e6dab3e6042d5ecb9a3', diff --git a/packages/apps/reputation-oracle/server/src/common/guards/cron.auth.ts b/packages/apps/reputation-oracle/server/src/common/guards/cron.auth.ts index c0a48cb30a..f59facb459 100644 --- a/packages/apps/reputation-oracle/server/src/common/guards/cron.auth.ts +++ b/packages/apps/reputation-oracle/server/src/common/guards/cron.auth.ts @@ -4,19 +4,18 @@ import { Injectable, UnauthorizedException, } from '@nestjs/common'; -import { ConfigService } from '@nestjs/config'; -import { ConfigNames } from '../config'; +import { ServerConfigService } from '../config/server-config.service'; @Injectable() export class CronAuthGuard implements CanActivate { - constructor(private readonly configService: ConfigService) {} + constructor(private readonly serverConfigService: ServerConfigService) {} public async canActivate(context: ExecutionContext): Promise { const request = context.switchToHttp().getRequest(); if ( request.headers['authorization'] === - `Bearer ${this.configService.get(ConfigNames.CRON_SECRET)}` + `Bearer ${this.serverConfigService.cronSecret}` ) { return true; } diff --git a/packages/apps/reputation-oracle/server/src/database/database.module.ts b/packages/apps/reputation-oracle/server/src/database/database.module.ts index c811cbad33..8a31bde544 100644 --- a/packages/apps/reputation-oracle/server/src/database/database.module.ts +++ b/packages/apps/reputation-oracle/server/src/database/database.module.ts @@ -1,5 +1,5 @@ import { Module } from '@nestjs/common'; -import { ConfigModule, ConfigService } from '@nestjs/config'; +import { ConfigModule } from '@nestjs/config'; import { TypeOrmModule } from '@nestjs/typeorm'; import * as path from 'path'; import { SnakeNamingStrategy } from 'typeorm-naming-strategies'; @@ -13,20 +13,24 @@ import { UserEntity } from '../modules/user/user.entity'; import { KycEntity } from '../modules/kyc/kyc.entity'; import { CronJobEntity } from '../modules/cron-job/cron-job.entity'; import { LoggerOptions } from 'typeorm'; -import { ConfigNames } from '../common/config'; +import { DatabaseConfigService } from '../common/config/database-config.service'; +import { ServerConfigService } from '../common/config/server-config.service'; @Module({ imports: [ TypeOrmModule.forRootAsync({ imports: [TypeOrmLoggerModule, ConfigModule], - inject: [TypeOrmLoggerService, ConfigService], + inject: [ + TypeOrmLoggerService, + DatabaseConfigService, + ServerConfigService, + ], useFactory: ( typeOrmLoggerService: TypeOrmLoggerService, - configService: ConfigService, + databaseConfigService: DatabaseConfigService, + serverConfigService: ServerConfigService, ) => { - const loggerOptions = configService - .get(ConfigNames.POSTGRES_LOGGING) - ?.split(', '); + const loggerOptions = databaseConfigService.logging?.split(', '); typeOrmLoggerService.setOptions( loggerOptions && loggerOptions[0] === 'all' ? 'all' @@ -57,30 +61,14 @@ import { ConfigNames } from '../common/config'; migrations: [path.join(__dirname, '/migrations/**/*{.ts,.js}')], //"migrations": ["dist/migrations/*{.ts,.js}"], logger: typeOrmLoggerService, - host: configService.get( - ConfigNames.POSTGRES_HOST, - 'localhost', - ), - port: configService.get(ConfigNames.POSTGRES_PORT, 5432), - username: configService.get( - ConfigNames.POSTGRES_USER, - 'operator', - ), - password: configService.get( - ConfigNames.POSTGRES_PASSWORD, - 'qwerty', - ), - database: configService.get( - ConfigNames.POSTGRES_DATABASE, - 'reputation-oracle', - ), - keepConnectionAlive: - configService.get(ConfigNames.NODE_ENV) === 'test', + host: databaseConfigService.host, + port: databaseConfigService.port, + username: databaseConfigService.user, + password: databaseConfigService.password, + database: databaseConfigService.database, + keepConnectionAlive: serverConfigService.nodeEnv === 'test', migrationsRun: false, - ssl: - configService - .get(ConfigNames.POSTGRES_SSL) - ?.toLowerCase() === 'true', + ssl: databaseConfigService.ssl, }; }, }), diff --git a/packages/apps/reputation-oracle/server/src/main.ts b/packages/apps/reputation-oracle/server/src/main.ts index 0b0aabdda0..7f5eed0a62 100644 --- a/packages/apps/reputation-oracle/server/src/main.ts +++ b/packages/apps/reputation-oracle/server/src/main.ts @@ -1,6 +1,5 @@ import session from 'express-session'; import { NestFactory } from '@nestjs/core'; -import { ConfigService } from '@nestjs/config'; import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger'; import { json, urlencoded } from 'body-parser'; import { useContainer } from 'class-validator'; @@ -9,6 +8,8 @@ import cookieParser from 'cookie-parser'; import { AppModule } from './app.module'; import { INestApplication } from '@nestjs/common'; +import { ConfigService } from '@nestjs/config'; +import { ServerConfigService } from './common/config/server-config.service'; async function bootstrap() { const app = await NestFactory.create(AppModule, { @@ -16,8 +17,9 @@ async function bootstrap() { }); const configService: ConfigService = app.get(ConfigService); + const serverConfigService = new ServerConfigService(configService); - // const baseUrl = configService.get('FE_URL', 'http://localhost:3001'); + // const baseUrl = serverConfigService.feURL; // app.enableCors({ // origin: @@ -62,9 +64,8 @@ async function bootstrap() { const document = SwaggerModule.createDocument(app, config); SwaggerModule.setup('swagger', app, document); - const host = configService.get('HOST', 'localhost'); - const port = configService.get('PORT', '5000'); - + const host = serverConfigService.host; + const port = serverConfigService.port; app.use(helmet()); await app.listen(port, host, async () => { diff --git a/packages/apps/reputation-oracle/server/src/modules/auth/auth.module.ts b/packages/apps/reputation-oracle/server/src/modules/auth/auth.module.ts index 97fd7b2ffa..4c3454f675 100644 --- a/packages/apps/reputation-oracle/server/src/modules/auth/auth.module.ts +++ b/packages/apps/reputation-oracle/server/src/modules/auth/auth.module.ts @@ -1,6 +1,5 @@ import { Module } from '@nestjs/common'; import { JwtModule } from '@nestjs/jwt'; -import { ConfigService, ConfigModule } from '@nestjs/config'; import { TypeOrmModule } from '@nestjs/typeorm'; import { UserModule } from '../user/user.module'; @@ -9,27 +8,22 @@ import { AuthService } from './auth.service'; import { AuthJwtController } from './auth.controller'; import { TokenEntity } from './token.entity'; import { TokenRepository } from './token.repository'; -import { ConfigNames } from '../../common/config'; import { SendGridModule } from '../sendgrid/sendgrid.module'; import { UserEntity } from '../user/user.entity'; import { UserRepository } from '../user/user.repository'; import { Web3Module } from '../web3/web3.module'; +import { AuthConfigService } from '../../common/config/auth-config.service'; @Module({ imports: [ UserModule, - ConfigModule, JwtModule.registerAsync({ - inject: [ConfigService], - imports: [ConfigModule], - useFactory: (configService: ConfigService) => ({ - privateKey: configService.get(ConfigNames.JWT_PRIVATE_KEY), + inject: [AuthConfigService], + useFactory: (authConfigService: AuthConfigService) => ({ + privateKey: authConfigService.jwtPrivateKey, signOptions: { algorithm: 'ES256', - expiresIn: configService.get( - ConfigNames.JWT_ACCESS_TOKEN_EXPIRES_IN, - 3600, - ), + expiresIn: authConfigService.accessTokenExpiresIn, }, }), }), diff --git a/packages/apps/reputation-oracle/server/src/modules/auth/auth.service.spec.ts b/packages/apps/reputation-oracle/server/src/modules/auth/auth.service.spec.ts index a7aa3ae7d5..8fe017d0af 100644 --- a/packages/apps/reputation-oracle/server/src/modules/auth/auth.service.spec.ts +++ b/packages/apps/reputation-oracle/server/src/modules/auth/auth.service.spec.ts @@ -1,7 +1,6 @@ import { Test } from '@nestjs/testing'; import { AuthService } from './auth.service'; import { TokenRepository } from './token.repository'; -import { ConfigService } from '@nestjs/config'; import { HttpService } from '@nestjs/axios'; import { createMock } from '@golevelup/ts-jest'; import { UserRepository } from '../user/user.repository'; @@ -13,7 +12,6 @@ import { MOCK_ACCESS_TOKEN, MOCK_ADDRESS, MOCK_EMAIL, - MOCK_EXPIRES_IN, MOCK_HASHED_PASSWORD, MOCK_HCAPTCHA_TOKEN, MOCK_PASSWORD, @@ -32,6 +30,10 @@ import { KVStoreClient, Role } from '@human-protocol/sdk'; import { PrepareSignatureDto, SignatureBodyDto } from '../web3/web3.dto'; import { SignatureType } from '../../common/enums/web3'; import { AuthError } from './auth.error'; +import { AuthConfigService } from '../../common/config/auth-config.service'; +import { ServerConfigService } from '../../common/config/server-config.service'; +import { Web3ConfigService } from '../../common/config/web3-config.service'; +import { ConfigService } from '@nestjs/config'; jest.mock('@human-protocol/sdk', () => ({ ...jest.requireActual('@human-protocol/sdk'), @@ -58,17 +60,9 @@ describe('AuthService', () => { let jwtService: JwtService; let sendGridService: SendGridService; let web3Service: Web3Service; + let authConfigService: AuthConfigService; beforeAll(async () => { - const mockConfigService: Partial = { - get: jest.fn((key: string) => { - switch (key) { - case 'JWT_ACCESS_TOKEN_EXPIRES_IN': - return MOCK_EXPIRES_IN; - } - }), - }; - const signerMock = { address: MOCK_ADDRESS, getNetwork: jest.fn().mockResolvedValue({ chainId: 1 }), @@ -78,6 +72,10 @@ describe('AuthService', () => { providers: [ AuthService, UserService, + AuthConfigService, + ServerConfigService, + Web3ConfigService, + ConfigService, { provide: JwtService, useValue: { @@ -86,7 +84,6 @@ describe('AuthService', () => { }, { provide: TokenRepository, useValue: createMock() }, { provide: UserRepository, useValue: createMock() }, - { provide: ConfigService, useValue: mockConfigService }, { provide: HttpService, useValue: createMock() }, { provide: SendGridService, useValue: createMock() }, { @@ -108,6 +105,7 @@ describe('AuthService', () => { jwtService = moduleRef.get(JwtService); sendGridService = moduleRef.get(SendGridService); web3Service = moduleRef.get(Web3Service); + authConfigService = moduleRef.get(AuthConfigService); }); afterEach(() => { @@ -226,12 +224,6 @@ describe('AuthService', () => { email: 'user@example.com', }; - const tokenEntity: Partial = { - uuid: v4(), - type: TokenType.REFRESH, - user: userEntity as UserEntity, - }; - beforeEach(() => { jwtSignMock = jest .spyOn(jwtService, 'signAsync') @@ -264,7 +256,7 @@ describe('AuthService', () => { reputation_network: MOCK_ADDRESS, }, { - expiresIn: MOCK_EXPIRES_IN, + expiresIn: authConfigService.accessTokenExpiresIn, }, ); expect(result).toEqual({ @@ -333,7 +325,7 @@ describe('AuthService', () => { dynamicTemplateData: { service_name: SERVICE_NAME, url: expect.stringContaining( - 'undefined/reset-password?token=', + 'http://localhost:3001/reset-password?token=', ), }, to: email, diff --git a/packages/apps/reputation-oracle/server/src/modules/auth/auth.service.ts b/packages/apps/reputation-oracle/server/src/modules/auth/auth.service.ts index 788ac3a1a0..33fc8d70c4 100644 --- a/packages/apps/reputation-oracle/server/src/modules/auth/auth.service.ts +++ b/packages/apps/reputation-oracle/server/src/modules/auth/auth.service.ts @@ -5,7 +5,6 @@ import { NotFoundException, UnauthorizedException, } from '@nestjs/common'; -import { ConfigService } from '@nestjs/config'; import { JwtService } from '@nestjs/jwt'; import { ErrorAuth, ErrorUser } from '../../common/constants/errors'; @@ -26,7 +25,6 @@ import { } from './auth.dto'; import { TokenEntity, TokenType } from './token.entity'; import { TokenRepository } from './token.repository'; -import { ConfigNames } from '../../common/config'; import { verifySignature } from '../../common/utils/signature'; import { createHash } from 'crypto'; import { SendGridService } from '../sendgrid/sendgrid.service'; @@ -36,59 +34,34 @@ import { ChainId, KVStoreClient, KVStoreKeys, Role } from '@human-protocol/sdk'; import { SignatureType, Web3Env } from '../../common/enums/web3'; import { UserRepository } from '../user/user.repository'; import { AuthError } from './auth.error'; +import { AuthConfigService } from '../../common/config/auth-config.service'; +import { ServerConfigService } from '../../common/config/server-config.service'; +import { Web3ConfigService } from '../../common/config/web3-config.service'; @Injectable() export class AuthService { private readonly logger = new Logger(AuthService.name); - private readonly refreshTokenExpiresIn: number; - private readonly accessTokenExpiresIn: number; - private readonly verifyEmailTokenExpiresIn: number; - private readonly forgotPasswordTokenExpiresIn: number; - private readonly feURL: string; private readonly salt: string; constructor( private readonly jwtService: JwtService, private readonly userService: UserService, private readonly tokenRepository: TokenRepository, - private readonly configService: ConfigService, + private readonly serverConfigService: ServerConfigService, + private readonly authConfigService: AuthConfigService, + private readonly web3ConfigService: Web3ConfigService, private readonly sendgridService: SendGridService, private readonly web3Service: Web3Service, private readonly userRepository: UserRepository, - ) { - this.refreshTokenExpiresIn = this.configService.get( - ConfigNames.REFRESH_TOKEN_EXPIRES_IN, - 3600000, - ); - - this.accessTokenExpiresIn = this.configService.get( - ConfigNames.JWT_ACCESS_TOKEN_EXPIRES_IN, - 300000, - ); - - this.verifyEmailTokenExpiresIn = this.configService.get( - ConfigNames.VERIFY_EMAIL_TOKEN_EXPIRES_IN, - 1800000, - ); - - this.forgotPasswordTokenExpiresIn = this.configService.get( - ConfigNames.FORGOT_PASSWORD_TOKEN_EXPIRES_IN, - 1800000, - ); - - this.feURL = this.configService.get( - ConfigNames.FE_URL, - 'http://localhost:3005', - ); - } + ) {} public async signin(data: SignInDto, ip?: string): Promise { // if ( // !( // await verifyToken( - // this.configService.get(ConfigNames.HCAPTCHA_EXCHANGE_URL)!, - // this.configService.get(ConfigNames.HCAPTCHA_SITE_KEY)!, - // this.configService.get(ConfigNames.HCAPTCHA_SECRET)!, + // this.authConfigService.hCaptchaExchangeURL, + // this.authConfigService.hCaptchaSiteKey, + // this.authConfigService.hCaptchaSecret, // data.hCaptchaToken, // ip, // ) @@ -116,9 +89,9 @@ export class AuthService { // if ( // !( // await verifyToken( - // this.configService.get(ConfigNames.HCAPTCHA_SITE_KEY)!, - // this.configService.get(ConfigNames.HCAPTCHA_EXCHANGE_URL)!, - // this.configService.get(ConfigNames.HCAPTCHA_SECRET)!, + // this.authConfigService.hCaptchaSiteKey, + // this.authConfigService.hCaptchaExchangeURL, + // this.authConfigService.hCaptchaSecret, // data.hCaptchaToken, // ip, // ) @@ -133,7 +106,7 @@ export class AuthService { tokenEntity.user = userEntity; const date = new Date(); tokenEntity.expiresAt = new Date( - date.getTime() + this.verifyEmailTokenExpiresIn, + date.getTime() + this.authConfigService.verifyEmailTokenExpiresIn, ); await this.tokenRepository.createUnique(tokenEntity); @@ -144,7 +117,7 @@ export class AuthService { to: data.email, dynamicTemplateData: { service_name: SERVICE_NAME, - url: `${this.feURL}/verify?token=${tokenEntity.uuid}`, + url: `${this.serverConfigService.feURL}/verify?token=${tokenEntity.uuid}`, }, }, ], @@ -187,7 +160,7 @@ export class AuthService { reputation_network: this.web3Service.getOperatorAddress(), }, { - expiresIn: this.accessTokenExpiresIn, + expiresIn: this.authConfigService.accessTokenExpiresIn, }, ); @@ -200,7 +173,7 @@ export class AuthService { newRefreshTokenEntity.type = TokenType.REFRESH; const date = new Date(); newRefreshTokenEntity.expiresAt = new Date( - date.getTime() + this.refreshTokenExpiresIn, + date.getTime() + this.authConfigService.refreshTokenExpiresIn, ); await this.tokenRepository.createUnique(newRefreshTokenEntity); @@ -233,7 +206,7 @@ export class AuthService { tokenEntity.user = userEntity; const date = new Date(); tokenEntity.expiresAt = new Date( - date.getTime() + this.forgotPasswordTokenExpiresIn, + date.getTime() + this.authConfigService.forgotPasswordExpiresIn, ); await this.tokenRepository.createUnique(tokenEntity); @@ -244,7 +217,7 @@ export class AuthService { to: data.email, dynamicTemplateData: { service_name: SERVICE_NAME, - url: `${this.feURL}/reset-password?token=${tokenEntity.uuid}`, + url: `${this.serverConfigService.feURL}/reset-password?token=${tokenEntity.uuid}`, }, }, ], @@ -259,9 +232,9 @@ export class AuthService { // if ( // !( // await verifyToken( - // this.configService.get(ConfigNames.HCAPTCHA_EXCHANGE_URL)!, - // this.configService.get(ConfigNames.HCAPTCHA_SITE_KEY)!, - // this.configService.get(ConfigNames.HCAPTCHA_SECRET)!, + // this.authConfigService.hCaptchaExchangeURL, + // this.authConfigService.hCaptchaSiteKey, + // this.authConfigService.hCaptchaSecret, // data.hCaptchaToken, // ip, // ) @@ -340,7 +313,7 @@ export class AuthService { tokenEntity.user = userEntity; const date = new Date(); tokenEntity.expiresAt = new Date( - date.getTime() + this.verifyEmailTokenExpiresIn, + date.getTime() + this.authConfigService.verifyEmailTokenExpiresIn, ); await this.tokenRepository.createUnique(tokenEntity); @@ -351,7 +324,7 @@ export class AuthService { to: data.email, dynamicTemplateData: { service_name: SERVICE_NAME, - url: `${this.feURL}/verify?token=${tokenEntity.uuid}`, + url: `${this.serverConfigService.feURL}/verify?token=${tokenEntity.uuid}`, }, }, ], @@ -384,7 +357,7 @@ export class AuthService { } let kvstore: KVStoreClient; - const currentWeb3Env = this.configService.get(ConfigNames.WEB3_ENV); + const currentWeb3Env = this.web3ConfigService.env; if (currentWeb3Env === Web3Env.MAINNET) { kvstore = await KVStoreClient.build( this.web3Service.getSigner(ChainId.POLYGON), diff --git a/packages/apps/reputation-oracle/server/src/modules/auth/strategy/jwt.http.ts b/packages/apps/reputation-oracle/server/src/modules/auth/strategy/jwt.http.ts index 35190d5539..e53658b31c 100644 --- a/packages/apps/reputation-oracle/server/src/modules/auth/strategy/jwt.http.ts +++ b/packages/apps/reputation-oracle/server/src/modules/auth/strategy/jwt.http.ts @@ -1,24 +1,23 @@ import { ExtractJwt, Strategy } from 'passport-jwt'; import { PassportStrategy } from '@nestjs/passport'; import { Injectable, Req, UnauthorizedException } from '@nestjs/common'; -import { ConfigService } from '@nestjs/config'; import { UserEntity } from '../../user/user.entity'; import { RESEND_EMAIL_VERIFICATION_PATH } from '../../../common/constants'; import { UserStatus } from '../../../common/enums/user'; -import { ConfigNames } from '../../../common/config'; import { UserRepository } from '../../user/user.repository'; +import { AuthConfigService } from '../../../common/config/auth-config.service'; @Injectable() export class JwtHttpStrategy extends PassportStrategy(Strategy, 'jwt-http') { constructor( private readonly userRepository: UserRepository, - private readonly configService: ConfigService, + private readonly authConfigService: AuthConfigService, ) { super({ jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), ignoreExpiration: false, - secretOrKey: configService.get(ConfigNames.JWT_PUBLIC_KEY, ''), + secretOrKey: authConfigService.jwtPublicKey, passReqToCallback: true, }); } diff --git a/packages/apps/reputation-oracle/server/src/modules/cron-job/cron-job.service.spec.ts b/packages/apps/reputation-oracle/server/src/modules/cron-job/cron-job.service.spec.ts index d6fc3999df..0bf79a99af 100644 --- a/packages/apps/reputation-oracle/server/src/modules/cron-job/cron-job.service.spec.ts +++ b/packages/apps/reputation-oracle/server/src/modules/cron-job/cron-job.service.spec.ts @@ -24,6 +24,9 @@ import { ReputationService } from '../reputation/reputation.service'; import { StorageService } from '../storage/storage.service'; import { ReputationRepository } from '../reputation/reputation.repository'; import { HttpService } from '@nestjs/axios'; +import { ServerConfigService } from '../../common/config/server-config.service'; +import { Web3ConfigService } from '../../common/config/web3-config.service'; +import { ReputationConfigService } from '../../common/config/reputation-config.service'; jest.mock('@human-protocol/sdk', () => ({ ...jest.requireActual('@human-protocol/sdk'), @@ -64,14 +67,6 @@ describe('CronJobService', () => { }; beforeEach(async () => { - const mockConfigService: Partial = { - get: jest.fn((key: string) => { - switch (key) { - case 'MAX_RETRY_COUNT': - return MOCK_MAX_RETRY_COUNT; - } - }), - }; const module: TestingModule = await Test.createTestingModule({ providers: [ CronJobService, @@ -90,7 +85,10 @@ describe('CronJobService', () => { WebhookService, PayoutService, ReputationService, - { provide: ConfigService, useValue: mockConfigService }, + ConfigService, + ServerConfigService, + Web3ConfigService, + ReputationConfigService, { provide: HttpService, useValue: createMock() }, { provide: WebhookRepository, diff --git a/packages/apps/reputation-oracle/server/src/modules/kyc/kyc.service.spec.ts b/packages/apps/reputation-oracle/server/src/modules/kyc/kyc.service.spec.ts index 22a36b1d87..94e3196a5c 100644 --- a/packages/apps/reputation-oracle/server/src/modules/kyc/kyc.service.spec.ts +++ b/packages/apps/reputation-oracle/server/src/modules/kyc/kyc.service.spec.ts @@ -1,5 +1,4 @@ import { ConfigService } from '@nestjs/config'; -import { ConfigNames } from '../../common/config'; import { Test } from '@nestjs/testing'; import { KycService } from './kyc.service'; import { HttpService } from '@nestjs/axios'; @@ -11,24 +10,15 @@ import { KycRepository } from './kyc.repository'; import { KycEntity } from './kyc.entity'; import { of } from 'rxjs'; import { ErrorKyc } from '../../common/constants/errors'; +import { SynapsConfigService } from '../../common/config/synaps-config.service'; describe('Kyc Service', () => { let kycService: KycService; let httpService: HttpService; let kycRepository: KycRepository; + let synapsConfigService: SynapsConfigService; beforeAll(async () => { - const mockConfigService: Partial = { - get: jest.fn((key: string) => { - switch (key) { - case ConfigNames.SYNAPS_API_KEY: - return 'synaps-api-key'; - case ConfigNames.SYNAPS_WEBHOOK_SECRET: - return 'synaps-webhook-secret'; - } - }), - }; - const mockHttpService: DeepPartial = { axiosRef: { request: jest.fn(), @@ -38,10 +28,8 @@ describe('Kyc Service', () => { const moduleRef = await Test.createTestingModule({ providers: [ KycService, - { - provide: ConfigService, - useValue: mockConfigService, - }, + ConfigService, + SynapsConfigService, { provide: HttpService, useValue: mockHttpService, @@ -53,6 +41,12 @@ describe('Kyc Service', () => { httpService = moduleRef.get(HttpService); kycService = moduleRef.get(KycService); kycRepository = moduleRef.get(KycRepository); + synapsConfigService = + moduleRef.get(SynapsConfigService); + + jest + .spyOn(SynapsConfigService.prototype, 'apiKey', 'get') + .mockReturnValue('test'); }); describe('initSession', () => { @@ -211,7 +205,7 @@ describe('Kyc Service', () => { { alias: 'test@example.com' }, { baseURL: 'https://api.synaps.io/v4', - headers: { 'Api-Key': 'synaps-api-key' }, + headers: { 'Api-Key': synapsConfigService.apiKey }, }, ); expect(kycRepository.create).toHaveBeenCalledWith({ @@ -246,7 +240,10 @@ describe('Kyc Service', () => { }); await expect( - kycService.updateKycStatus('synaps-webhook-secret', mockKycUpdate), + kycService.updateKycStatus( + synapsConfigService.webhookSecret, + mockKycUpdate, + ), ).rejects.toThrow(); }); @@ -264,7 +261,10 @@ describe('Kyc Service', () => { }); }); - await kycService.updateKycStatus('synaps-webhook-secret', mockKycUpdate); + await kycService.updateKycStatus( + synapsConfigService.webhookSecret, + mockKycUpdate, + ); expect(kycRepository.updateOne).toHaveBeenCalledWith( { sessionId: '123' }, diff --git a/packages/apps/reputation-oracle/server/src/modules/kyc/kyc.service.ts b/packages/apps/reputation-oracle/server/src/modules/kyc/kyc.service.ts index 06fd72c2fd..9954abf356 100644 --- a/packages/apps/reputation-oracle/server/src/modules/kyc/kyc.service.ts +++ b/packages/apps/reputation-oracle/server/src/modules/kyc/kyc.service.ts @@ -5,40 +5,29 @@ import { Logger, UnauthorizedException, } from '@nestjs/common'; -import { ConfigService } from '@nestjs/config'; import { UserEntity } from '../user/user.entity'; -import { ConfigNames } from '../../common/config'; import { HttpService } from '@nestjs/axios'; import { KycSessionDto, KycStatusDto } from './kyc.dto'; import { KycRepository } from './kyc.repository'; import { KycStatus } from '../../common/enums/user'; import { firstValueFrom } from 'rxjs'; import { ErrorKyc } from '../../common/constants/errors'; +import { SynapsConfigService } from '../../common/config/synaps-config.service'; +import { SYNAPS_API_KEY_DISABLED } from '../../common/constants'; +import { v4 as uuidv4 } from 'uuid'; @Injectable() export class KycService { private readonly logger = new Logger(KycService.name); private readonly synapsBaseURL: string; - private readonly synapsApiKey: string; - private readonly synapsWebhookSecret: string; constructor( private kycRepository: KycRepository, private readonly httpService: HttpService, - private readonly configService: ConfigService, + private readonly synapsConfigService: SynapsConfigService, ) { this.synapsBaseURL = 'https://api.synaps.io/v4'; - - this.synapsApiKey = this.configService.get( - ConfigNames.SYNAPS_API_KEY, - '', - ); - - this.synapsWebhookSecret = this.configService.get( - ConfigNames.SYNAPS_WEBHOOK_SECRET, - '', - ); } public async initSession(userEntity: UserEntity): Promise { @@ -62,6 +51,19 @@ export class KycService { }; } + if (this.synapsConfigService.apiKey === SYNAPS_API_KEY_DISABLED) { + const sessionId = uuidv4(); + await this.kycRepository.create({ + sessionId: sessionId, + status: KycStatus.NONE, + userId: userEntity.id, + }); + + return { + sessionId: sessionId, + }; + } + const { data } = await firstValueFrom( await this.httpService.post( 'session/init', @@ -71,7 +73,7 @@ export class KycService { { baseURL: this.synapsBaseURL, headers: { - 'Api-Key': this.synapsApiKey, + 'Api-Key': this.synapsConfigService.apiKey, }, }, ), @@ -96,7 +98,7 @@ export class KycService { secret: string, data: KycStatusDto, ): Promise { - if (secret !== this.synapsWebhookSecret) { + if (secret !== this.synapsConfigService.webhookSecret) { throw new UnauthorizedException(ErrorKyc.InvalidWebhookSecret); } @@ -104,7 +106,7 @@ export class KycService { await this.httpService.get(`/individual/session/${data.sessionId}`, { baseURL: this.synapsBaseURL, headers: { - 'Api-Key': this.synapsApiKey, + 'Api-Key': this.synapsConfigService.apiKey, }, }), ); diff --git a/packages/apps/reputation-oracle/server/src/modules/payout/payout.service.spec.ts b/packages/apps/reputation-oracle/server/src/modules/payout/payout.service.spec.ts index a80b4de6d6..5847c2b761 100644 --- a/packages/apps/reputation-oracle/server/src/modules/payout/payout.service.spec.ts +++ b/packages/apps/reputation-oracle/server/src/modules/payout/payout.service.spec.ts @@ -14,8 +14,7 @@ import { MOCK_S3_USE_SSL, } from '../../../test/constants'; import { JobRequestType } from '../../common/enums'; -import { ConfigModule, ConfigService, registerAs } from '@nestjs/config'; -import { ConfigNames } from '../../common/config'; +import { ConfigModule, registerAs } from '@nestjs/config'; import { Web3Service } from '../web3/web3.service'; import { StorageService } from '../storage/storage.service'; import { PayoutService } from './payout.service'; @@ -43,17 +42,6 @@ describe('PayoutService', () => { }; beforeEach(async () => { - const mockConfigService: Partial = { - get: jest.fn((key: string) => { - switch (key) { - case ConfigNames.REPUTATION_LEVEL_LOW: - return 300; - case ConfigNames.REPUTATION_LEVEL_HIGH: - return 700; - } - }), - }; - const moduleRef = await Test.createTestingModule({ imports: [ ConfigModule.forFeature( @@ -78,10 +66,6 @@ describe('PayoutService', () => { }, { provide: StorageService, useValue: createMock() }, PayoutService, - { - provide: ConfigService, - useValue: mockConfigService, - }, ], }).compile(); diff --git a/packages/apps/reputation-oracle/server/src/modules/payout/payout.service.ts b/packages/apps/reputation-oracle/server/src/modules/payout/payout.service.ts index a9f6cb2204..5cefc63960 100644 --- a/packages/apps/reputation-oracle/server/src/modules/payout/payout.service.ts +++ b/packages/apps/reputation-oracle/server/src/modules/payout/payout.service.ts @@ -11,7 +11,6 @@ import { ethers } from 'ethers'; import { Web3Service } from '../web3/web3.service'; import { JobRequestType } from '../../common/enums'; import { StorageService } from '../storage/storage.service'; -import { ConfigService } from '@nestjs/config'; import { CvatManifestDto, FortuneManifestDto } from '../../common/dto/manifest'; import { CvatAnnotationMeta, @@ -28,7 +27,6 @@ export class PayoutService { @Inject(StorageService) private readonly storageService: StorageService, private readonly web3Service: Web3Service, - public readonly configService: ConfigService, ) {} /** diff --git a/packages/apps/reputation-oracle/server/src/modules/reputation/reputation.controller.spec.ts b/packages/apps/reputation-oracle/server/src/modules/reputation/reputation.controller.spec.ts index ed37d8a583..2c07ef7169 100644 --- a/packages/apps/reputation-oracle/server/src/modules/reputation/reputation.controller.spec.ts +++ b/packages/apps/reputation-oracle/server/src/modules/reputation/reputation.controller.spec.ts @@ -5,7 +5,6 @@ import { ReputationController } from './reputation.controller'; import { ReputationService } from './reputation.service'; import { ReputationRepository } from './reputation.repository'; import { ReputationLevel } from '../../common/enums'; -import { ConfigNames } from '../../common/config'; import { ReputationDto } from './reputation.dto'; import { StorageService } from '../storage/storage.service'; import { Web3Service } from '../web3/web3.service'; @@ -18,6 +17,9 @@ import { MOCK_S3_SECRET_KEY, MOCK_S3_USE_SSL, } from '../../../test/constants'; +import { ReputationConfigService } from '../../common/config/reputation-config.service'; +import { S3ConfigService } from '../../common/config/s3-config.service'; +import { PGPConfigService } from '../../common/config/pgp-config.service'; const OPERATOR_ADDRESS = 'TEST_OPERATOR_ADDRESS'; const CHAIN_ID = 1; @@ -32,17 +34,6 @@ describe('ReputationController', () => { }; beforeAll(async () => { - const mockConfigService: Partial = { - get: jest.fn((key: string) => { - switch (key) { - case ConfigNames.REPUTATION_LEVEL_LOW: - return 300; - case ConfigNames.REPUTATION_LEVEL_LOW: - return 700; - } - }), - }; - const moduleRef = await Test.createTestingModule({ imports: [ ConfigModule.forFeature( @@ -73,10 +64,10 @@ describe('ReputationController', () => { findOne: jest.fn(), }, }, - { - provide: ConfigService, - useValue: mockConfigService, - }, + ConfigService, + ReputationConfigService, + S3ConfigService, + PGPConfigService, ], }).compile(); diff --git a/packages/apps/reputation-oracle/server/src/modules/reputation/reputation.service.spec.ts b/packages/apps/reputation-oracle/server/src/modules/reputation/reputation.service.spec.ts index 28b23d95be..e24b58d551 100644 --- a/packages/apps/reputation-oracle/server/src/modules/reputation/reputation.service.spec.ts +++ b/packages/apps/reputation-oracle/server/src/modules/reputation/reputation.service.spec.ts @@ -22,11 +22,11 @@ import { SolutionError, } from '../../common/enums'; import { ConfigModule, ConfigService, registerAs } from '@nestjs/config'; -import { ConfigNames } from '../../common/config'; import { Web3Service } from '../web3/web3.service'; import { StorageService } from '../storage/storage.service'; import { ErrorManifest, ErrorResults } from '../../common/constants/errors'; import { EscrowClient } from '@human-protocol/sdk'; +import { ReputationConfigService } from '../../common/config/reputation-config.service'; jest.mock('@human-protocol/sdk', () => ({ ...jest.requireActual('@human-protocol/sdk'), @@ -52,17 +52,6 @@ describe('ReputationService', () => { }; beforeEach(async () => { - const mockConfigService: Partial = { - get: jest.fn((key: string) => { - switch (key) { - case ConfigNames.REPUTATION_LEVEL_LOW: - return 300; - case ConfigNames.REPUTATION_LEVEL_HIGH: - return 700; - } - }), - }; - const moduleRef = await Test.createTestingModule({ imports: [ ConfigModule.forFeature( @@ -94,10 +83,8 @@ describe('ReputationService', () => { provide: WebhookRepository, useValue: createMock(), }, - { - provide: ConfigService, - useValue: mockConfigService, - }, + ConfigService, + ReputationConfigService, ], }).compile(); diff --git a/packages/apps/reputation-oracle/server/src/modules/reputation/reputation.service.ts b/packages/apps/reputation-oracle/server/src/modules/reputation/reputation.service.ts index fc45b576a6..f830a3a0f5 100644 --- a/packages/apps/reputation-oracle/server/src/modules/reputation/reputation.service.ts +++ b/packages/apps/reputation-oracle/server/src/modules/reputation/reputation.service.ts @@ -1,11 +1,9 @@ import { Inject, Injectable, Logger, NotFoundException } from '@nestjs/common'; -import { ConfigService } from '@nestjs/config'; import { ChainId } from '@human-protocol/sdk'; import { CVAT_VALIDATION_META_FILENAME, INITIAL_REPUTATION, } from '../../common/constants'; -import { ConfigNames } from '../../common/config'; import { JobRequestType, ReputationEntityType, @@ -29,7 +27,8 @@ import { } from '../../common/dto/result'; import { RequestAction } from './reputation.interface'; import { getRequestType } from '../../common/utils'; -import { CvatManifestDto } from 'src/common/dto/manifest'; +import { CvatManifestDto } from '../../common/dto/manifest'; +import { ReputationConfigService } from '../../common/config/reputation-config.service'; @Injectable() export class ReputationService { @@ -39,7 +38,7 @@ export class ReputationService { @Inject(StorageService) private readonly storageService: StorageService, private readonly reputationRepository: ReputationRepository, - private readonly configService: ConfigService, + private readonly reputationConfigService: ReputationConfigService, private readonly web3Service: Web3Service, ) {} @@ -339,21 +338,11 @@ export class ReputationService { * @returns {ReputationLevel} The reputation level. */ public getReputationLevel(reputationPoints: number): ReputationLevel { - const reputationLevelLow = this.configService.get( - ConfigNames.REPUTATION_LEVEL_LOW, - 300, - ); - - const reputationLevelHigh = this.configService.get( - ConfigNames.REPUTATION_LEVEL_HIGH, - 700, - ); - - if (reputationPoints <= reputationLevelLow) { + if (reputationPoints <= this.reputationConfigService.lowLevel) { return ReputationLevel.LOW; } - if (reputationPoints >= reputationLevelHigh) { + if (reputationPoints >= this.reputationConfigService.highLevel) { return ReputationLevel.HIGH; } diff --git a/packages/apps/reputation-oracle/server/src/modules/sendgrid/sendgrid.service.spec.ts b/packages/apps/reputation-oracle/server/src/modules/sendgrid/sendgrid.service.spec.ts index c26f8b7a25..83331589af 100644 --- a/packages/apps/reputation-oracle/server/src/modules/sendgrid/sendgrid.service.spec.ts +++ b/packages/apps/reputation-oracle/server/src/modules/sendgrid/sendgrid.service.spec.ts @@ -3,16 +3,17 @@ import { Test } from '@nestjs/testing'; import { SendGridService } from './sendgrid.service'; import { MailService } from '@sendgrid/mail'; import { ErrorSendGrid } from '../../common/constants/errors'; -import { - MOCK_SENDGRID_API_KEY, - MOCK_SENDGRID_FROM_EMAIL, - MOCK_SENDGRID_FROM_NAME, -} from '../../../test/constants'; +import { MOCK_SENDGRID_API_KEY } from '../../../test/constants'; +import { SendgridConfigService } from '../../common/config/sendgrid-config.service'; describe('SendGridService', () => { let sendGridService: SendGridService; let mailService: MailService; - let mockConfigService: Partial; + let sendgridConfigService: SendgridConfigService; + + jest + .spyOn(SendgridConfigService.prototype, 'apiKey', 'get') + .mockReturnValue(MOCK_SENDGRID_API_KEY); beforeAll(async () => { const mockMailService = { @@ -20,19 +21,6 @@ describe('SendGridService', () => { setApiKey: jest.fn(), }; - mockConfigService = { - get: jest.fn((key: string) => { - switch (key) { - case 'SENDGRID_API_KEY': - return MOCK_SENDGRID_API_KEY; - case 'SENDGRID_FROM_EMAIL': - return MOCK_SENDGRID_FROM_EMAIL; - case 'SENDGRID_FROM_NAME': - return MOCK_SENDGRID_FROM_NAME; - } - }), - }; - const app = await Test.createTestingModule({ providers: [ SendGridService, @@ -40,14 +28,13 @@ describe('SendGridService', () => { provide: MailService, useValue: mockMailService, }, - { - provide: ConfigService, - useValue: mockConfigService, - }, + ConfigService, + SendgridConfigService, ], }).compile(); sendGridService = app.get(SendGridService); mailService = app.get(MailService); + sendgridConfigService = app.get(SendgridConfigService); }); describe('sendEmail', () => { @@ -83,7 +70,7 @@ describe('SendGridService', () => { expect(mock).toHaveBeenCalledWith( expect.objectContaining({ from: expect.objectContaining({ - email: 'info@hmt.ai', + email: 'reputation-oracle@hmt.ai', }), }), ); @@ -107,28 +94,21 @@ describe('SendGridService', () => { describe('constructor', () => { it('should initialize SendGridService with valid API key', () => { - sendGridService = new SendGridService( - mailService, - mockConfigService as any, - ); + sendGridService = new SendGridService(mailService, sendgridConfigService); expect(mailService.setApiKey).toHaveBeenCalledWith(MOCK_SENDGRID_API_KEY); - expect(sendGridService['defaultFromEmail']).toEqual( - MOCK_SENDGRID_FROM_EMAIL, - ); - expect(sendGridService['defaultFromName']).toEqual( - MOCK_SENDGRID_FROM_NAME, - ); }); it('should throw an error with invalid API key', async () => { const invalidApiKey = 'invalid-api-key'; - mockConfigService.get = jest.fn().mockReturnValue(invalidApiKey); + jest + .spyOn(sendgridConfigService, 'apiKey', 'get') + .mockReturnValue(invalidApiKey); expect(() => { sendGridService = new SendGridService( mailService, - mockConfigService as any, + sendgridConfigService, ); }).toThrowError(ErrorSendGrid.InvalidApiKey); }); diff --git a/packages/apps/reputation-oracle/server/src/modules/sendgrid/sendgrid.service.ts b/packages/apps/reputation-oracle/server/src/modules/sendgrid/sendgrid.service.ts index 496c79db96..52c8ec92e2 100644 --- a/packages/apps/reputation-oracle/server/src/modules/sendgrid/sendgrid.service.ts +++ b/packages/apps/reputation-oracle/server/src/modules/sendgrid/sendgrid.service.ts @@ -1,61 +1,42 @@ import { BadRequestException, Injectable, Logger } from '@nestjs/common'; -import { ConfigService } from '@nestjs/config'; import { MailDataRequired, MailService } from '@sendgrid/mail'; -import { ConfigNames } from '../../common/config'; import { SENDGRID_API_KEY_DISABLED, SENDGRID_API_KEY_REGEX, } from '../../common/constants'; import { ErrorSendGrid } from '../../common/constants/errors'; +import { SendgridConfigService } from '../../common/config/sendgrid-config.service'; @Injectable() export class SendGridService { private readonly logger = new Logger(SendGridService.name); - private readonly defaultFromEmail: string; - private readonly defaultFromName: string; - private readonly apiKey: string; - constructor( private readonly mailService: MailService, - private readonly configService: ConfigService, + private readonly sendgridConfigService: SendgridConfigService, ) { - this.apiKey = this.configService.get( - ConfigNames.SENDGRID_API_KEY, - '', - ); - - this.defaultFromEmail = this.configService.get( - ConfigNames.SENDGRID_FROM_EMAIL, - '', - ); - this.defaultFromName = this.configService.get( - ConfigNames.SENDGRID_FROM_NAME, - '', - ); - - if (this.apiKey === SENDGRID_API_KEY_DISABLED) { + if (this.sendgridConfigService.apiKey === SENDGRID_API_KEY_DISABLED) { return; } - if (!SENDGRID_API_KEY_REGEX.test(this.apiKey)) { + if (!SENDGRID_API_KEY_REGEX.test(this.sendgridConfigService.apiKey)) { throw new Error(ErrorSendGrid.InvalidApiKey); } - this.mailService.setApiKey(this.apiKey); + this.mailService.setApiKey(this.sendgridConfigService.apiKey); } async sendEmail({ from = { - email: this.defaultFromEmail, - name: this.defaultFromName, + email: this.sendgridConfigService.fromEmail, + name: this.sendgridConfigService.fromName, }, templateId = '', personalizations, ...emailData }: Partial): Promise { try { - if (this.apiKey === SENDGRID_API_KEY_DISABLED) { + if (this.sendgridConfigService.apiKey === SENDGRID_API_KEY_DISABLED) { this.logger.debug(personalizations); return; } diff --git a/packages/apps/reputation-oracle/server/src/modules/storage/storage.module.ts b/packages/apps/reputation-oracle/server/src/modules/storage/storage.module.ts index 47be30be9c..70cb4e2341 100644 --- a/packages/apps/reputation-oracle/server/src/modules/storage/storage.module.ts +++ b/packages/apps/reputation-oracle/server/src/modules/storage/storage.module.ts @@ -1,11 +1,9 @@ import { Module } from '@nestjs/common'; import { StorageService } from './storage.service'; -import { ConfigModule } from '@nestjs/config'; -import { s3Config } from '../../common/config'; import { Web3Module } from '../web3/web3.module'; @Module({ - imports: [ConfigModule.forFeature(s3Config), Web3Module], + imports: [Web3Module], providers: [StorageService], exports: [StorageService], }) diff --git a/packages/apps/reputation-oracle/server/src/modules/storage/storage.service.spec.ts b/packages/apps/reputation-oracle/server/src/modules/storage/storage.service.spec.ts index e8fcc0e6a0..86ee609ab9 100644 --- a/packages/apps/reputation-oracle/server/src/modules/storage/storage.service.spec.ts +++ b/packages/apps/reputation-oracle/server/src/modules/storage/storage.service.spec.ts @@ -6,22 +6,17 @@ import { KVStoreClient, StorageClient, } from '@human-protocol/sdk'; -import { ConfigModule, ConfigService, registerAs } from '@nestjs/config'; +import { ConfigService } from '@nestjs/config'; import { Test } from '@nestjs/testing'; import { - MOCK_ENCRYPTION_PRIVATE_KEY, MOCK_ENCRYPTION_PUBLIC_KEY, MOCK_FILE_URL, - MOCK_S3_ACCESS_KEY, - MOCK_S3_BUCKET, - MOCK_S3_ENDPOINT, - MOCK_S3_PORT, - MOCK_S3_SECRET_KEY, - MOCK_S3_USE_SSL, } from '../../../test/constants'; import { StorageService } from './storage.service'; import crypto from 'crypto'; import { Web3Service } from '../web3/web3.service'; +import { PGPConfigService } from '../../common/config/pgp-config.service'; +import { S3ConfigService } from '../../common/config/s3-config.service'; jest.mock('@human-protocol/sdk', () => ({ ...jest.requireActual('@human-protocol/sdk'), @@ -63,8 +58,8 @@ jest.mock('axios'); describe('StorageService', () => { let storageService: StorageService; - - let encrypt = 'true'; + let pgpConfigService: PGPConfigService; + let s3ConfigService: S3ConfigService; const signerMock = { address: '0x1234567890123456789012345678901234567892', @@ -72,33 +67,12 @@ describe('StorageService', () => { }; beforeAll(async () => { - const mockConfigService: Partial = { - get: jest.fn((key: string) => { - switch (key) { - case 'ENCRYPTION_PRIVATE_KEY': - return MOCK_ENCRYPTION_PRIVATE_KEY; - case 'PGP_ENCRYPT': - return encrypt; - } - }), - }; - const moduleRef = await Test.createTestingModule({ - imports: [ - ConfigModule.forFeature( - registerAs('s3', () => ({ - accessKey: MOCK_S3_ACCESS_KEY, - secretKey: MOCK_S3_SECRET_KEY, - endPoint: MOCK_S3_ENDPOINT, - port: MOCK_S3_PORT, - useSSL: MOCK_S3_USE_SSL, - bucket: MOCK_S3_BUCKET, - })), - ), - ], providers: [ StorageService, - { provide: ConfigService, useValue: mockConfigService }, + ConfigService, + PGPConfigService, + S3ConfigService, { provide: Web3Service, useValue: { @@ -109,6 +83,8 @@ describe('StorageService', () => { }).compile(); storageService = moduleRef.get(StorageService); + pgpConfigService = moduleRef.get(PGPConfigService); + s3ConfigService = moduleRef.get(S3ConfigService); const jobLauncherAddress = '0x1234567890123456789012345678901234567893'; EscrowClient.build = jest.fn().mockResolvedValue({ @@ -117,6 +93,7 @@ describe('StorageService', () => { (KVStoreClient.build as jest.Mock).mockResolvedValue({ getPublicKey: jest.fn().mockResolvedValue(MOCK_ENCRYPTION_PUBLIC_KEY), }); + jest.spyOn(pgpConfigService, 'encrypt', 'get').mockReturnValue(true); }); describe('uploadJobSolutions', () => { @@ -144,11 +121,11 @@ describe('StorageService', () => { const hash = crypto.createHash('sha1').update('encrypted').digest('hex'); expect(fileData).toEqual({ - url: `http://${MOCK_S3_ENDPOINT}:${MOCK_S3_PORT}/${MOCK_S3_BUCKET}/${hash}.json`, + url: `http://${s3ConfigService.endpoint}:${s3ConfigService.port}/${s3ConfigService.bucket}/${hash}.json`, hash, }); expect(storageService.minioClient.putObject).toHaveBeenCalledWith( - MOCK_S3_BUCKET, + s3ConfigService.bucket, `${hash}.json`, expect.stringContaining('encrypted'), { @@ -160,7 +137,7 @@ describe('StorageService', () => { describe('without encryption', () => { beforeAll(() => { - encrypt = 'false'; + jest.spyOn(pgpConfigService, 'encrypt', 'get').mockReturnValue(false); }); afterEach(() => { @@ -168,7 +145,7 @@ describe('StorageService', () => { }); afterAll(() => { - encrypt = 'true'; + jest.spyOn(pgpConfigService, 'encrypt', 'get').mockReturnValue(true); }); it('should upload the solutions', async () => { @@ -196,11 +173,11 @@ describe('StorageService', () => { const content = JSON.stringify([jobSolution]); const hash = crypto.createHash('sha1').update(content).digest('hex'); expect(fileData).toEqual({ - url: `http://${MOCK_S3_ENDPOINT}:${MOCK_S3_PORT}/${MOCK_S3_BUCKET}/${hash}.json`, + url: `http://${s3ConfigService.endpoint}:${s3ConfigService.port}/${s3ConfigService.bucket}/${hash}.json`, hash, }); expect(storageService.minioClient.putObject).toHaveBeenCalledWith( - MOCK_S3_BUCKET, + s3ConfigService.bucket, `${hash}.json`, expect.stringContaining(content), { @@ -344,12 +321,12 @@ describe('StorageService', () => { expect( uploadedFile.url.includes( - `http://${MOCK_S3_ENDPOINT}:${MOCK_S3_PORT}/${MOCK_S3_BUCKET}/`, + `http://${s3ConfigService.endpoint}:${s3ConfigService.port}/${s3ConfigService.bucket}/`, ), ).toBeTruthy(); expect(uploadedFile.hash).toBeDefined(); expect(storageService.minioClient.putObject).toBeCalledWith( - MOCK_S3_BUCKET, + s3ConfigService.bucket, `s3${crypto .createHash('sha1') .update('encrypted-file-content') @@ -380,12 +357,12 @@ describe('StorageService', () => { expect( uploadedFile.url.includes( - `http://${MOCK_S3_ENDPOINT}:${MOCK_S3_PORT}/${MOCK_S3_BUCKET}/`, + `http://${s3ConfigService.endpoint}:${s3ConfigService.port}/${s3ConfigService.bucket}/`, ), ).toBeTruthy(); expect(uploadedFile.hash).toBeDefined(); expect(storageService.minioClient.putObject).toBeCalledWith( - MOCK_S3_BUCKET, + s3ConfigService.bucket, `s3${crypto .createHash('sha1') .update('encrypted-file-content') @@ -397,7 +374,7 @@ describe('StorageService', () => { describe('without encryption', () => { beforeAll(() => { - encrypt = 'false'; + jest.spyOn(pgpConfigService, 'encrypt', 'get').mockReturnValue(false); }); afterEach(() => { @@ -405,7 +382,7 @@ describe('StorageService', () => { }); afterAll(() => { - encrypt = 'true'; + jest.spyOn(pgpConfigService, 'encrypt', 'get').mockReturnValue(true); }); it('should copy a file from a valid URL to a bucket', async () => { @@ -430,12 +407,12 @@ describe('StorageService', () => { expect( uploadedFile.url.includes( - `http://${MOCK_S3_ENDPOINT}:${MOCK_S3_PORT}/${MOCK_S3_BUCKET}/`, + `http://${s3ConfigService.endpoint}:${s3ConfigService.port}/${s3ConfigService.bucket}/`, ), ).toBeTruthy(); expect(uploadedFile.hash).toBeDefined(); expect(storageService.minioClient.putObject).toBeCalledWith( - MOCK_S3_BUCKET, + s3ConfigService.bucket, `s3${crypto .createHash('sha1') .update('some-file-content') @@ -466,12 +443,12 @@ describe('StorageService', () => { expect( uploadedFile.url.includes( - `http://${MOCK_S3_ENDPOINT}:${MOCK_S3_PORT}/${MOCK_S3_BUCKET}/`, + `http://${s3ConfigService.endpoint}:${s3ConfigService.port}/${s3ConfigService.bucket}/`, ), ).toBeTruthy(); expect(uploadedFile.hash).toBeDefined(); expect(storageService.minioClient.putObject).toBeCalledWith( - MOCK_S3_BUCKET, + s3ConfigService.bucket, `s3${crypto .createHash('sha1') .update('some-file-content') diff --git a/packages/apps/reputation-oracle/server/src/modules/storage/storage.service.ts b/packages/apps/reputation-oracle/server/src/modules/storage/storage.service.ts index f829bf59a8..f7ac486330 100644 --- a/packages/apps/reputation-oracle/server/src/modules/storage/storage.service.ts +++ b/packages/apps/reputation-oracle/server/src/modules/storage/storage.service.ts @@ -6,38 +6,37 @@ import { KVStoreClient, StorageClient, } from '@human-protocol/sdk'; -import { BadRequestException, Inject, Injectable } from '@nestjs/common'; +import { BadRequestException, Injectable } from '@nestjs/common'; import * as Minio from 'minio'; -import { ConfigNames, S3ConfigType, s3ConfigKey } from '../../common/config'; import crypto from 'crypto'; import { UploadedFile } from '../../common/interfaces/s3'; import { Logger } from '@nestjs/common'; -import { ConfigService } from '@nestjs/config'; import { Web3Service } from '../web3/web3.service'; import { FortuneFinalResult } from '../../common/dto/result'; +import { S3ConfigService } from '../../common/config/s3-config.service'; +import { PGPConfigService } from '../../common/config/pgp-config.service'; @Injectable() export class StorageService { public readonly minioClient: Minio.Client; constructor( - @Inject(s3ConfigKey) - private s3Config: S3ConfigType, - public readonly configService: ConfigService, + public readonly s3ConfigService: S3ConfigService, + public readonly pgpConfigService: PGPConfigService, private readonly web3Service: Web3Service, ) { this.minioClient = new Minio.Client({ - endPoint: this.s3Config.endPoint, - port: this.s3Config.port, - accessKey: this.s3Config.accessKey, - secretKey: this.s3Config.secretKey, - useSSL: this.s3Config.useSSL, + endPoint: this.s3ConfigService.endpoint, + port: this.s3ConfigService.port, + accessKey: this.s3ConfigService.accessKey, + secretKey: this.s3ConfigService.secretKey, + useSSL: this.s3ConfigService.useSSL, }); } public getUrl(key: string): string { - return `${this.s3Config.useSSL ? 'https' : 'http'}://${ - this.s3Config.endPoint - }:${this.s3Config.port}/${this.s3Config.bucket}/${key}`; + return `${this.s3ConfigService.useSSL ? 'https' : 'http'}://${ + this.s3ConfigService.endpoint + }:${this.s3ConfigService.port}/${this.s3ConfigService.bucket}/${key}`; } private async encryptFile( @@ -45,7 +44,7 @@ export class StorageService { chainId: ChainId, content: any, ) { - if (this.configService.get(ConfigNames.PGP_ENCRYPT) !== 'true') { + if (!this.pgpConfigService.encrypt) { return content; } @@ -78,8 +77,8 @@ export class StorageService { EncryptionUtils.isEncrypted(fileContent) ) { const encryption = await Encryption.build( - this.configService.get(ConfigNames.ENCRYPTION_PRIVATE_KEY, ''), - this.configService.get(ConfigNames.ENCRYPTION_PASSPHRASE, ''), + this.pgpConfigService.privateKey, + this.pgpConfigService.passphrase, ); return await encryption.decrypt(fileContent); @@ -104,7 +103,7 @@ export class StorageService { chainId: ChainId, solutions: FortuneFinalResult[], ): Promise { - if (!(await this.minioClient.bucketExists(this.s3Config.bucket))) { + if (!(await this.minioClient.bucketExists(this.s3ConfigService.bucket))) { throw new BadRequestException('Bucket not found'); } @@ -117,10 +116,15 @@ export class StorageService { const hash = crypto.createHash('sha1').update(content).digest('hex'); const key = `${hash}.json`; - await this.minioClient.putObject(this.s3Config.bucket, key, content, { - 'Content-Type': 'application/json', - 'Cache-Control': 'no-store', - }); + await this.minioClient.putObject( + this.s3ConfigService.bucket, + key, + content, + { + 'Content-Type': 'application/json', + 'Cache-Control': 'no-store', + }, + ); return { url: this.getUrl(key), hash }; } catch (error) { @@ -156,10 +160,15 @@ export class StorageService { const hash = crypto.createHash('sha1').update(content).digest('hex'); const key = `s3${hash}.zip`; - await this.minioClient.putObject(this.s3Config.bucket, key, content, { - 'Content-Type': 'application/json', - 'Cache-Control': 'no-store', - }); + await this.minioClient.putObject( + this.s3ConfigService.bucket, + key, + content, + { + 'Content-Type': 'application/json', + 'Cache-Control': 'no-store', + }, + ); return { url: this.getUrl(key), diff --git a/packages/apps/reputation-oracle/server/src/modules/user/user.service.spec.ts b/packages/apps/reputation-oracle/server/src/modules/user/user.service.spec.ts index 1fd1c9ada1..14788423b0 100644 --- a/packages/apps/reputation-oracle/server/src/modules/user/user.service.spec.ts +++ b/packages/apps/reputation-oracle/server/src/modules/user/user.service.spec.ts @@ -19,6 +19,7 @@ import { ChainId, KVStoreClient } from '@human-protocol/sdk'; import { ConfigService } from '@nestjs/config'; import { SignatureBodyDto } from '../web3/web3.dto'; import { SignatureType } from '../../common/enums/web3'; +import { Web3ConfigService } from '../../common/config/web3-config.service'; jest.mock('@human-protocol/sdk', () => ({ ...jest.requireActual('@human-protocol/sdk'), @@ -31,23 +32,11 @@ jest.mock('@human-protocol/sdk', () => ({ })); describe('UserService', () => { - let mockConfigService: Partial; let userService: UserService; let userRepository: UserRepository; let web3Service: Web3Service; beforeEach(async () => { - mockConfigService = { - get: jest.fn((key: string, defaultValue?: any) => { - switch (key) { - case 'WEB3_ENV': - return 'testnet'; - default: - return defaultValue; - } - }), - }; - const signerMock = { address: MOCK_ADDRESS, getNetwork: jest.fn().mockResolvedValue({ chainId: 1 }), @@ -65,10 +54,8 @@ describe('UserService', () => { prepareSignatureBody: jest.fn(), }, }, - { - provide: ConfigService, - useValue: mockConfigService, - }, + ConfigService, + Web3ConfigService, ], }).compile(); diff --git a/packages/apps/reputation-oracle/server/src/modules/user/user.service.ts b/packages/apps/reputation-oracle/server/src/modules/user/user.service.ts index bdd34fa73e..7ad1ff8c3e 100644 --- a/packages/apps/reputation-oracle/server/src/modules/user/user.service.ts +++ b/packages/apps/reputation-oracle/server/src/modules/user/user.service.ts @@ -7,7 +7,6 @@ import { UnauthorizedException, } from '@nestjs/common'; import * as bcrypt from 'bcrypt'; -import { Not } from 'typeorm'; import { ErrorAuth, ErrorOperator, @@ -30,10 +29,9 @@ import { UserRepository } from './user.repository'; import { ValidatePasswordDto } from '../auth/auth.dto'; import { Web3Service } from '../web3/web3.service'; import { Wallet } from 'ethers'; -import { ConfigNames } from '../../common/config'; import { SignatureType, Web3Env } from '../../common/enums/web3'; import { ChainId, KVStoreClient } from '@human-protocol/sdk'; -import { ConfigService } from '@nestjs/config'; +import { Web3ConfigService } from '../../common/config/web3-config.service'; @Injectable() export class UserService { @@ -42,7 +40,7 @@ export class UserService { constructor( private userRepository: UserRepository, private readonly web3Service: Web3Service, - private readonly configService: ConfigService, + private readonly web3ConfigService: Web3ConfigService, ) {} public async create(dto: UserCreateDto): Promise { @@ -156,7 +154,7 @@ export class UserService { } let signer: Wallet; - const currentWeb3Env = this.configService.get(ConfigNames.WEB3_ENV); + const currentWeb3Env = this.web3ConfigService.env; if (currentWeb3Env === Web3Env.MAINNET) { signer = this.web3Service.getSigner(ChainId.POLYGON); } else { diff --git a/packages/apps/reputation-oracle/server/src/modules/web3/web3.service.spec.ts b/packages/apps/reputation-oracle/server/src/modules/web3/web3.service.spec.ts index 6ff1706571..4031e1842f 100644 --- a/packages/apps/reputation-oracle/server/src/modules/web3/web3.service.spec.ts +++ b/packages/apps/reputation-oracle/server/src/modules/web3/web3.service.spec.ts @@ -7,33 +7,18 @@ import { ErrorWeb3 } from '../../common/constants/errors'; import { SignatureType } from '../../common/enums/web3'; import { SignatureBodyDto } from './web3.dto'; import { Web3Service } from './web3.service'; +import { Web3ConfigService } from '../../common/config/web3-config.service'; describe('Web3Service', () => { - let mockConfigService: Partial; let web3Service: Web3Service; - beforeAll(async () => { - mockConfigService = { - get: jest.fn((key: string, defaultValue?: any) => { - switch (key) { - case 'WEB3_PRIVATE_KEY': - return MOCK_PRIVATE_KEY; - case 'WEB3_ENV': - return 'testnet'; - default: - return defaultValue; - } - }), - }; + jest + .spyOn(Web3ConfigService.prototype, 'privateKey', 'get') + .mockReturnValue(MOCK_PRIVATE_KEY); + beforeEach(async () => { const moduleRef = await Test.createTestingModule({ - providers: [ - Web3Service, - { - provide: ConfigService, - useValue: mockConfigService, - }, - ], + providers: [Web3Service, ConfigService, Web3ConfigService], }).compile(); web3Service = moduleRef.get(Web3Service); diff --git a/packages/apps/reputation-oracle/server/src/modules/web3/web3.service.ts b/packages/apps/reputation-oracle/server/src/modules/web3/web3.service.ts index 14e6c67126..07c8f44902 100644 --- a/packages/apps/reputation-oracle/server/src/modules/web3/web3.service.ts +++ b/packages/apps/reputation-oracle/server/src/modules/web3/web3.service.ts @@ -1,8 +1,6 @@ import { BadRequestException, Injectable, Logger } from '@nestjs/common'; -import { ConfigService } from '@nestjs/config'; import { Wallet, ethers } from 'ethers'; import { - ConfigNames, LOCALHOST_CHAIN_IDS, MAINNET_CHAIN_IDS, TESTNET_CHAIN_IDS, @@ -12,20 +10,16 @@ import { SignatureType, Web3Env } from '../../common/enums/web3'; import { ErrorWeb3 } from '../../common/constants/errors'; import { ChainId } from '@human-protocol/sdk'; import { SignatureBodyDto } from './web3.dto'; +import { Web3ConfigService } from '../../common/config/web3-config.service'; @Injectable() export class Web3Service { private signers: { [key: number]: Wallet } = {}; public readonly signerAddress: string; - public readonly currentWeb3Env: string; public readonly logger = new Logger(Web3Service.name); - constructor(private readonly configService: ConfigService) { - this.currentWeb3Env = this.configService.get( - ConfigNames.WEB3_ENV, - ) as string; - - const privateKey = this.configService.get(ConfigNames.WEB3_PRIVATE_KEY); + constructor(private readonly web3ConfigService: Web3ConfigService) { + const privateKey = this.web3ConfigService.privateKey; const validChains = this.getValidChains(); const validNetworks = networks.filter((network) => validChains.includes(network.chainId), @@ -50,7 +44,7 @@ export class Web3Service { } public getValidChains(): ChainId[] { - switch (this.currentWeb3Env) { + switch (this.web3ConfigService.env) { case Web3Env.MAINNET: return MAINNET_CHAIN_IDS; case Web3Env.TESTNET: @@ -64,10 +58,7 @@ export class Web3Service { public async calculateGasPrice(chainId: number): Promise { const signer = this.getSigner(chainId); - const multiplier = this.configService.get( - ConfigNames.GAS_PRICE_MULTIPLIER, - 1, - ); + const multiplier = this.web3ConfigService.gasPriceMultiplier; const gasPrice = (await signer.provider?.getFeeData())?.gasPrice; if (gasPrice) { return gasPrice * BigInt(multiplier); diff --git a/packages/apps/reputation-oracle/server/src/modules/webhook/webhook.repository.ts b/packages/apps/reputation-oracle/server/src/modules/webhook/webhook.repository.ts index cd3bced8b0..d9fb284342 100644 --- a/packages/apps/reputation-oracle/server/src/modules/webhook/webhook.repository.ts +++ b/packages/apps/reputation-oracle/server/src/modules/webhook/webhook.repository.ts @@ -1,18 +1,16 @@ import { Injectable, Logger } from '@nestjs/common'; - -import { ConfigService } from '@nestjs/config'; import { BaseRepository } from '../../database/base.repository'; import { DataSource, LessThanOrEqual } from 'typeorm'; -import { ConfigNames } from '../../common/config'; import { WebhookStatus } from '../../common/enums/webhook'; import { WebhookIncomingEntity } from './webhook-incoming.entity'; +import { ServerConfigService } from '../../common/config/server-config.service'; @Injectable() export class WebhookRepository extends BaseRepository { private readonly logger = new Logger(WebhookRepository.name); constructor( private dataSource: DataSource, - public readonly configService: ConfigService, + public readonly serverConfigService: ServerConfigService, ) { super(WebhookIncomingEntity, dataSource); } @@ -21,9 +19,7 @@ export class WebhookRepository extends BaseRepository { return this.find({ where: { status: status, - retriesCount: LessThanOrEqual( - this.configService.get(ConfigNames.MAX_RETRY_COUNT)!, - ), + retriesCount: LessThanOrEqual(this.serverConfigService.maxRetryCount), waitUntil: LessThanOrEqual(new Date()), }, diff --git a/packages/apps/reputation-oracle/server/src/modules/webhook/webhook.service.spec.ts b/packages/apps/reputation-oracle/server/src/modules/webhook/webhook.service.spec.ts index 41455e3837..75fc62f70e 100644 --- a/packages/apps/reputation-oracle/server/src/modules/webhook/webhook.service.spec.ts +++ b/packages/apps/reputation-oracle/server/src/modules/webhook/webhook.service.spec.ts @@ -20,6 +20,8 @@ import { of } from 'rxjs'; import { HEADER_SIGNATURE_KEY } from '../../common/constants'; import { signMessage } from '../../common/utils/signature'; import { HttpStatus } from '@nestjs/common'; +import { Web3ConfigService } from '../../common/config/web3-config.service'; +import { ServerConfigService } from '../../common/config/server-config.service'; jest.mock('@human-protocol/sdk', () => ({ ...jest.requireActual('@human-protocol/sdk'), @@ -34,7 +36,8 @@ jest.mock('@human-protocol/sdk', () => ({ describe('WebhookService', () => { let webhookService: WebhookService, webhookRepository: WebhookRepository, - httpService: HttpService; + httpService: HttpService, + web3ConfigService: Web3ConfigService; const signerMock = { address: MOCK_ADDRESS, @@ -42,21 +45,6 @@ describe('WebhookService', () => { }; beforeEach(async () => { - const mockConfigService: Partial = { - get: jest.fn((key: string) => { - switch (key) { - case 'HOST': - return '127.0.0.1'; - case 'PORT': - return 5000; - case 'WEB3_PRIVATE_KEY': - return MOCK_PRIVATE_KEY; - case 'MAX_RETRY_COUNT': - return MOCK_MAX_RETRY_COUNT; - } - }), - }; - const moduleRef = await Test.createTestingModule({ providers: [ WebhookService, @@ -70,7 +58,9 @@ describe('WebhookService', () => { provide: WebhookRepository, useValue: createMock(), }, - { provide: ConfigService, useValue: mockConfigService }, + ConfigService, + Web3ConfigService, + ServerConfigService, { provide: HttpService, useValue: createMock() }, ], }).compile(); @@ -78,6 +68,11 @@ describe('WebhookService', () => { webhookService = moduleRef.get(WebhookService); webhookRepository = moduleRef.get(WebhookRepository); httpService = moduleRef.get(HttpService); + web3ConfigService = moduleRef.get(Web3ConfigService); + + jest + .spyOn(web3ConfigService, 'privateKey', 'get') + .mockReturnValue(MOCK_PRIVATE_KEY); }); afterEach(() => { diff --git a/packages/apps/reputation-oracle/server/src/modules/webhook/webhook.service.ts b/packages/apps/reputation-oracle/server/src/modules/webhook/webhook.service.ts index d6507e5b88..43cead4dc5 100644 --- a/packages/apps/reputation-oracle/server/src/modules/webhook/webhook.service.ts +++ b/packages/apps/reputation-oracle/server/src/modules/webhook/webhook.service.ts @@ -11,13 +11,13 @@ import { WebhookDto } from './webhook.dto'; import { ErrorWebhook } from '../../common/constants/errors'; import { WebhookRepository } from './webhook.repository'; import { EventType, WebhookStatus } from '../../common/enums'; -import { ConfigService } from '@nestjs/config'; -import { ConfigNames } from '../../common/config'; import { firstValueFrom } from 'rxjs'; import { signMessage } from '../../common/utils/signature'; import { HEADER_SIGNATURE_KEY } from '../../common/constants'; import { HttpService } from '@nestjs/axios'; import { CaseConverter } from '../../common/utils/case-converter'; +import { ServerConfigService } from '../../common/config/server-config.service'; +import { Web3ConfigService } from '../../common/config/web3-config.service'; @Injectable() export class WebhookService { @@ -25,7 +25,8 @@ export class WebhookService { constructor( private readonly httpService: HttpService, private readonly webhookRepository: WebhookRepository, - public readonly configService: ConfigService, + public readonly serverConfigService: ServerConfigService, + public readonly web3ConfigService: Web3ConfigService, ) {} /** @@ -69,10 +70,7 @@ export class WebhookService { public async handleWebhookError( webhookEntity: WebhookIncomingEntity, ): Promise { - if ( - webhookEntity.retriesCount >= - this.configService.get(ConfigNames.MAX_RETRY_COUNT) - ) { + if (webhookEntity.retriesCount >= this.serverConfigService.maxRetryCount) { webhookEntity.status = WebhookStatus.FAILED; } else { webhookEntity.waitUntil = new Date(); @@ -88,7 +86,7 @@ export class WebhookService { const snake_case_body = CaseConverter.transformToSnakeCase(webhookBody); const signedBody = await signMessage( snake_case_body, - this.configService.get(ConfigNames.WEB3_PRIVATE_KEY)!, + this.web3ConfigService.privateKey, ); const { status } = await firstValueFrom( await this.httpService.post(webhookUrl, snake_case_body, { diff --git a/packages/apps/reputation-oracle/server/test/constants.ts b/packages/apps/reputation-oracle/server/test/constants.ts index 3cd1d34d26..46cff32844 100644 --- a/packages/apps/reputation-oracle/server/test/constants.ts +++ b/packages/apps/reputation-oracle/server/test/constants.ts @@ -186,7 +186,7 @@ export const MOCK_ACCESS_TOKEN = 'access_token'; export const MOCK_REFRESH_TOKEN = 'refresh_token'; export const MOCK_ACCESS_TOKEN_HASHED = 'access_token_hashed'; export const MOCK_REFRESH_TOKEN_HASHED = 'refresh_token_hashed'; -export const MOCK_EXPIRES_IN = 1000000000000000; +export const MOCK_EXPIRES_IN = 300000; export const MOCK_SENDGRID_API_KEY = 'SG.xxxxxxxxxxxxxxxxxxxxxx.xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'; export const MOCK_SENDGRID_FROM_EMAIL = 'info@hmt.ai'; diff --git a/scripts/fortune/.env.rep-oracle b/scripts/fortune/.env.rep-oracle index 74e6069fd3..b4fa25ee47 100644 --- a/scripts/fortune/.env.rep-oracle +++ b/scripts/fortune/.env.rep-oracle @@ -61,5 +61,5 @@ REPUTATION_LEVEL_HIGH=700 PGP_ENCRYPT=false # Synaps Kyc -SYNAPS_API_KEY=test +SYNAPS_API_KEY=synaps-disabled SYNAPS_WEBHOOK_SECRET=test From ee99e0eea6637f4b8ace4b7cc1997f3c78a3745f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Francisco=20L=C3=B3pez?= <50665615+flopez7@users.noreply.github.com> Date: Fri, 5 Apr 2024 10:27:32 +0200 Subject: [PATCH 41/66] Improve config service in recording oracle (#1814) --- .../recording-oracle/src/app.module.ts | 10 +-- .../src/common/config/config.module.ts | 25 +++++++ .../src/common/config/env-schema.ts | 23 +++++++ .../src/common/config/index.ts | 4 -- .../src/common/config/pgp-config.service.ts | 16 +++++ .../src/common/config/s3-config.service.ts | 25 +++++++ .../recording-oracle/src/common/config/s3.ts | 13 ---- .../common/config/server-config.service.ts | 19 ++++++ .../src/common/config/server.ts | 12 ---- .../src/common/config/validation.ts | 39 ----------- .../src/common/config/web3-config.service.ts | 10 +++ .../src/common/config/web3.ts | 8 --- .../apps/fortune/recording-oracle/src/main.ts | 11 ++- .../src/modules/job/job.module.ts | 10 +-- .../src/modules/job/job.service.spec.ts | 63 ++++++----------- .../src/modules/job/job.service.ts | 10 ++- .../src/modules/storage/storage.module.ts | 8 +-- .../modules/storage/storage.service.spec.ts | 59 +++++----------- .../src/modules/storage/storage.service.ts | 40 +++++------ .../src/modules/web3/web3.module.ts | 3 - .../src/modules/web3/web3.service.spec.ts | 18 +++-- .../src/modules/web3/web3.service.ts | 11 ++- .../webhook/webhook.controller.spec.ts | 68 +++++-------------- .../src/modules/webhook/webhook.module.ts | 10 +-- .../modules/webhook/webhook.service.spec.ts | 59 +++++----------- 25 files changed, 238 insertions(+), 336 deletions(-) create mode 100644 packages/apps/fortune/recording-oracle/src/common/config/config.module.ts create mode 100644 packages/apps/fortune/recording-oracle/src/common/config/env-schema.ts delete mode 100644 packages/apps/fortune/recording-oracle/src/common/config/index.ts create mode 100644 packages/apps/fortune/recording-oracle/src/common/config/pgp-config.service.ts create mode 100644 packages/apps/fortune/recording-oracle/src/common/config/s3-config.service.ts delete mode 100644 packages/apps/fortune/recording-oracle/src/common/config/s3.ts create mode 100644 packages/apps/fortune/recording-oracle/src/common/config/server-config.service.ts delete mode 100644 packages/apps/fortune/recording-oracle/src/common/config/server.ts delete mode 100644 packages/apps/fortune/recording-oracle/src/common/config/validation.ts create mode 100644 packages/apps/fortune/recording-oracle/src/common/config/web3-config.service.ts delete mode 100644 packages/apps/fortune/recording-oracle/src/common/config/web3.ts diff --git a/packages/apps/fortune/recording-oracle/src/app.module.ts b/packages/apps/fortune/recording-oracle/src/app.module.ts index 646746722b..338dee4ad4 100644 --- a/packages/apps/fortune/recording-oracle/src/app.module.ts +++ b/packages/apps/fortune/recording-oracle/src/app.module.ts @@ -5,14 +5,10 @@ import { HttpValidationPipe } from './common/pipes'; import { JobModule } from './modules/job/job.module'; import { AppController } from './app.controller'; -import { - envValidator, - s3Config, - serverConfig, - web3Config, -} from './common/config'; import { SnakeCaseInterceptor } from './common/interceptors/snake-case'; import { WebhookModule } from './modules/webhook/webhook.module'; +import { envValidator } from './common/config/env-schema'; +import { EnvConfigModule } from './common/config/config.module'; @Module({ providers: [ @@ -31,10 +27,10 @@ import { WebhookModule } from './modules/webhook/webhook.module'; ? `.env.${process.env.NODE_ENV as string}` : '.env', validationSchema: envValidator, - load: [serverConfig, s3Config, web3Config], }), JobModule, WebhookModule, + EnvConfigModule, ], controllers: [AppController], }) diff --git a/packages/apps/fortune/recording-oracle/src/common/config/config.module.ts b/packages/apps/fortune/recording-oracle/src/common/config/config.module.ts new file mode 100644 index 0000000000..fea7c5720d --- /dev/null +++ b/packages/apps/fortune/recording-oracle/src/common/config/config.module.ts @@ -0,0 +1,25 @@ +import { Module, Global } from '@nestjs/common'; +import { ConfigService } from '@nestjs/config'; +import { ServerConfigService } from './server-config.service'; +import { PGPConfigService } from './pgp-config.service'; +import { S3ConfigService } from './s3-config.service'; +import { Web3ConfigService } from './web3-config.service'; + +@Global() +@Module({ + providers: [ + ConfigService, + ServerConfigService, + Web3ConfigService, + S3ConfigService, + PGPConfigService, + ], + exports: [ + ConfigService, + ServerConfigService, + Web3ConfigService, + S3ConfigService, + PGPConfigService, + ], +}) +export class EnvConfigModule {} diff --git a/packages/apps/fortune/recording-oracle/src/common/config/env-schema.ts b/packages/apps/fortune/recording-oracle/src/common/config/env-schema.ts new file mode 100644 index 0000000000..17281a9397 --- /dev/null +++ b/packages/apps/fortune/recording-oracle/src/common/config/env-schema.ts @@ -0,0 +1,23 @@ +import * as Joi from 'joi'; + +export const envValidator = Joi.object({ + // General + NODE_ENV: Joi.string(), + HOST: Joi.string(), + PORT: Joi.string(), + SESSION_SECRET: Joi.string(), + // Web3 + WEB3_ENV: Joi.string(), + WEB3_PRIVATE_KEY: Joi.string().required(), + // S3 + S3_ENDPOINT: Joi.string(), + S3_PORT: Joi.string(), + S3_ACCESS_KEY: Joi.string().required(), + S3_SECRET_KEY: Joi.string().required(), + S3_BUCKET: Joi.string(), + S3_USE_SSL: Joi.string(), + // Encryption + PGP_ENCRYPT: Joi.boolean(), + PGP_PRIVATE_KEY: Joi.string(), + PGP_PASSPHRASE: Joi.string(), +}); diff --git a/packages/apps/fortune/recording-oracle/src/common/config/index.ts b/packages/apps/fortune/recording-oracle/src/common/config/index.ts deleted file mode 100644 index b23c0bb036..0000000000 --- a/packages/apps/fortune/recording-oracle/src/common/config/index.ts +++ /dev/null @@ -1,4 +0,0 @@ -export * from './web3'; -export * from './server'; -export * from './s3'; -export * from './validation'; diff --git a/packages/apps/fortune/recording-oracle/src/common/config/pgp-config.service.ts b/packages/apps/fortune/recording-oracle/src/common/config/pgp-config.service.ts new file mode 100644 index 0000000000..a6ec43a0df --- /dev/null +++ b/packages/apps/fortune/recording-oracle/src/common/config/pgp-config.service.ts @@ -0,0 +1,16 @@ +import { Injectable } from '@nestjs/common'; +import { ConfigService } from '@nestjs/config'; + +@Injectable() +export class PGPConfigService { + constructor(private configService: ConfigService) {} + get encrypt(): boolean { + return this.configService.get('PGP_ENCRYPT', 'false') === 'true'; + } + get privateKey(): string { + return this.configService.get('PGP_PRIVATE_KEY', ''); + } + get passphrase(): string { + return this.configService.get('PGP_PASSPHRASE', ''); + } +} diff --git a/packages/apps/fortune/recording-oracle/src/common/config/s3-config.service.ts b/packages/apps/fortune/recording-oracle/src/common/config/s3-config.service.ts new file mode 100644 index 0000000000..5fe6786749 --- /dev/null +++ b/packages/apps/fortune/recording-oracle/src/common/config/s3-config.service.ts @@ -0,0 +1,25 @@ +import { Injectable } from '@nestjs/common'; +import { ConfigService } from '@nestjs/config'; + +@Injectable() +export class S3ConfigService { + constructor(private configService: ConfigService) {} + get endpoint(): string { + return this.configService.get('S3_ENDPOINT', '127.0.0.1'); + } + get port(): number { + return +this.configService.get('S3_PORT', 9000); + } + get accessKey(): string { + return this.configService.get('S3_ACCESS_KEY', ''); + } + get secretKey(): string { + return this.configService.get('S3_SECRET_KEY', ''); + } + get bucket(): string { + return this.configService.get('S3_BUCKET', 'recording'); + } + get useSSL(): boolean { + return this.configService.get('S3_USE_SSL', 'false') === 'true'; + } +} diff --git a/packages/apps/fortune/recording-oracle/src/common/config/s3.ts b/packages/apps/fortune/recording-oracle/src/common/config/s3.ts deleted file mode 100644 index 2389bffabb..0000000000 --- a/packages/apps/fortune/recording-oracle/src/common/config/s3.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { ConfigType, registerAs } from '@nestjs/config'; - -export const s3Config = registerAs('s3', () => ({ - endPoint: process.env.S3_ENDPOINT || '127.0.0.1', - port: +(process.env.S3_PORT || 9000), - accessKey: process.env.S3_ACCESS_KEY || '', - secretKey: process.env.S3_SECRET_KEY || '', - bucket: process.env.S3_BUCKET || '', - useSSL: process.env.S3_USE_SSL === 'true', -})); - -export const s3ConfigKey = s3Config.KEY; -export type S3ConfigType = ConfigType; diff --git a/packages/apps/fortune/recording-oracle/src/common/config/server-config.service.ts b/packages/apps/fortune/recording-oracle/src/common/config/server-config.service.ts new file mode 100644 index 0000000000..56fff8e409 --- /dev/null +++ b/packages/apps/fortune/recording-oracle/src/common/config/server-config.service.ts @@ -0,0 +1,19 @@ +import { Injectable } from '@nestjs/common'; +import { ConfigService } from '@nestjs/config'; + +@Injectable() +export class ServerConfigService { + constructor(private configService: ConfigService) {} + get nodeEnv(): string { + return this.configService.get('NODE_ENV', 'development'); + } + get host(): string { + return this.configService.get('HOST', 'localhost'); + } + get port(): number { + return +this.configService.get('PORT', 5002); + } + get sessionSecret(): string { + return this.configService.get('SESSION_SECRET', 'session_key'); + } +} diff --git a/packages/apps/fortune/recording-oracle/src/common/config/server.ts b/packages/apps/fortune/recording-oracle/src/common/config/server.ts deleted file mode 100644 index 6cc3a5f1c6..0000000000 --- a/packages/apps/fortune/recording-oracle/src/common/config/server.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { ConfigType, registerAs } from '@nestjs/config'; - -export const serverConfig = registerAs('server', () => ({ - host: process.env.HOST || 'localhost', - port: +(process.env.PORT || 5000), - sessionSecret: process.env.SESSION_SECRET || 'session_key', - pgpEncrypt: process.env.PGP_ENCRYPT || false, - encryptionPrivateKey: process.env.PGP_PRIVATE_KEY || '', - encryptionPassphrase: process.env.PGP_PASSPHRASE || '', -})); -export const serverConfigKey = serverConfig.KEY; -export type ServerConfigType = ConfigType; diff --git a/packages/apps/fortune/recording-oracle/src/common/config/validation.ts b/packages/apps/fortune/recording-oracle/src/common/config/validation.ts deleted file mode 100644 index 1973fc5deb..0000000000 --- a/packages/apps/fortune/recording-oracle/src/common/config/validation.ts +++ /dev/null @@ -1,39 +0,0 @@ -import * as Joi from 'joi'; - -export const ConfigNames = { - NODE_ENV: 'NODE_ENV', - HOST: 'HOST', - PORT: 'PORT', - SESSION_SECRET: 'SESSION_SECRET', - WEB3_PRIVATE_KEY: 'WEB3_PRIVATE_KEY', - S3_ENDPOINT: 'S3_ENDPOINT', - S3_PORT: 'S3_PORT', - S3_ACCESS_KEY: 'S3_ACCESS_KEY', - S3_SECRET_KEY: 'S3_SECRET_KEY', - S3_BUCKET: 'S3_BUCKET', - S3_USE_SSL: 'S3_USE_SSL', - PGP_ENCRYPT: 'PGP_ENCRYPT', - PGP_PRIVATE_KEY: 'PGP_PRIVATE_KEY', - PGP_PASSPHRASE: 'PGP_PASSPHRASE', -}; - -export const envValidator = Joi.object({ - // General - NODE_ENV: Joi.string().default('development'), - HOST: Joi.string().default('localhost'), - PORT: Joi.string().default(5000), - SESSION_SECRET: Joi.string().default('session_key'), - // Web3 - WEB3_PRIVATE_KEY: Joi.string().required(), - // S3 - S3_ENDPOINT: Joi.string().default('127.0.0.1'), - S3_PORT: Joi.string().default(9000), - S3_ACCESS_KEY: Joi.string().required(), - S3_SECRET_KEY: Joi.string().required(), - S3_BUCKET: Joi.string().default('solution'), - S3_USE_SSL: Joi.string().default(false), - // Encryption - PGP_ENCRYPT: Joi.boolean().default(false), - PGP_PRIVATE_KEY: Joi.string().optional(), - PGP_PASSPHRASE: Joi.string().optional(), -}); diff --git a/packages/apps/fortune/recording-oracle/src/common/config/web3-config.service.ts b/packages/apps/fortune/recording-oracle/src/common/config/web3-config.service.ts new file mode 100644 index 0000000000..feb23f7747 --- /dev/null +++ b/packages/apps/fortune/recording-oracle/src/common/config/web3-config.service.ts @@ -0,0 +1,10 @@ +import { Injectable } from '@nestjs/common'; +import { ConfigService } from '@nestjs/config'; + +@Injectable() +export class Web3ConfigService { + constructor(private configService: ConfigService) {} + get privateKey(): string { + return this.configService.get('WEB3_PRIVATE_KEY', ''); + } +} diff --git a/packages/apps/fortune/recording-oracle/src/common/config/web3.ts b/packages/apps/fortune/recording-oracle/src/common/config/web3.ts deleted file mode 100644 index b42d980eff..0000000000 --- a/packages/apps/fortune/recording-oracle/src/common/config/web3.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { ConfigType, registerAs } from '@nestjs/config'; - -export const web3Config = registerAs('web3', () => ({ - web3PrivateKey: process.env.WEB3_PRIVATE_KEY || '', -})); - -export const web3ConfigKey = web3Config.KEY; -export type Web3ConfigType = ConfigType; diff --git a/packages/apps/fortune/recording-oracle/src/main.ts b/packages/apps/fortune/recording-oracle/src/main.ts index f2e00449ff..32da0466fa 100644 --- a/packages/apps/fortune/recording-oracle/src/main.ts +++ b/packages/apps/fortune/recording-oracle/src/main.ts @@ -8,16 +8,21 @@ import helmet from 'helmet'; import { INestApplication } from '@nestjs/common'; import { AppModule } from './app.module'; -import { ServerConfigType, serverConfigKey } from './common/config'; import { GlobalExceptionsFilter } from './common/filter'; +import { ServerConfigService } from './common/config/server-config.service'; +import { ConfigService } from '@nestjs/config'; async function bootstrap() { const app = await NestFactory.create(AppModule, { cors: true, }); - const { sessionSecret, host, port }: ServerConfigType = - app.get(serverConfigKey); + const configService: ConfigService = app.get(ConfigService); + const serverConfigService = new ServerConfigService(configService); + + const host = serverConfigService.host; + const port = serverConfigService.port; + const sessionSecret = serverConfigService.sessionSecret; app.useGlobalFilters(new GlobalExceptionsFilter()); diff --git a/packages/apps/fortune/recording-oracle/src/modules/job/job.module.ts b/packages/apps/fortune/recording-oracle/src/modules/job/job.module.ts index 4ab2a2ba48..f8de93cf57 100644 --- a/packages/apps/fortune/recording-oracle/src/modules/job/job.module.ts +++ b/packages/apps/fortune/recording-oracle/src/modules/job/job.module.ts @@ -1,19 +1,11 @@ import { HttpModule } from '@nestjs/axios'; import { Logger, Module } from '@nestjs/common'; -import { ConfigModule } from '@nestjs/config'; import { JobService } from './job.service'; import { Web3Module } from '../web3/web3.module'; -import { serverConfig, web3Config } from '../../common/config'; import { StorageModule } from '../storage/storage.module'; @Module({ - imports: [ - ConfigModule.forFeature(serverConfig), - ConfigModule.forFeature(web3Config), - HttpModule, - Web3Module, - StorageModule, - ], + imports: [HttpModule, Web3Module, StorageModule], providers: [Logger, JobService], exports: [JobService], }) diff --git a/packages/apps/fortune/recording-oracle/src/modules/job/job.service.spec.ts b/packages/apps/fortune/recording-oracle/src/modules/job/job.service.spec.ts index 73e9f064d7..a8aef9c4c3 100644 --- a/packages/apps/fortune/recording-oracle/src/modules/job/job.service.spec.ts +++ b/packages/apps/fortune/recording-oracle/src/modules/job/job.service.spec.ts @@ -1,8 +1,4 @@ -import { Test } from '@nestjs/testing'; -import { HttpService } from '@nestjs/axios'; -import { JobService } from './job.service'; -import { Web3Service } from '../web3/web3.service'; -import { ErrorJob } from '../../common/constants/errors'; +import { Web3ConfigService } from '@/common/config/web3-config.service'; import { ChainId, EncryptionUtils, @@ -11,32 +7,33 @@ import { KVStoreClient, StorageClient, } from '@human-protocol/sdk'; -import { JobRequestType, SolutionError } from '../../common/enums/job'; +import { HttpService } from '@nestjs/axios'; +import { ConfigService } from '@nestjs/config'; +import { Test } from '@nestjs/testing'; +import { of } from 'rxjs'; import { MOCK_ADDRESS, - MOCK_ENCRYPTION_PASSPHRASE, - MOCK_ENCRYPTION_PRIVATE_KEY, MOCK_EXCHANGE_ORACLE_WEBHOOK_URL, MOCK_FILE_URL, MOCK_REPUTATION_ORACLE_WEBHOOK_URL, MOCK_REQUESTER_DESCRIPTION, MOCK_REQUESTER_TITLE, - MOCK_S3_ACCESS_KEY, - MOCK_S3_BUCKET, MOCK_S3_ENDPOINT, MOCK_S3_PORT, - MOCK_S3_SECRET_KEY, - MOCK_S3_USE_SSL, MOCK_WEB3_PRIVATE_KEY, } from '../../../test/constants'; -import { ConfigModule, registerAs } from '@nestjs/config'; -import { IManifest, ISolution } from '../../common/interfaces/job'; -import { of } from 'rxjs'; -import { StorageService } from '../storage/storage.service'; +import { PGPConfigService } from '../../common/config/pgp-config.service'; +import { S3ConfigService } from '../../common/config/s3-config.service'; import { HEADER_SIGNATURE_KEY } from '../../common/constants'; -import { signMessage } from '../../common/utils/signature'; +import { ErrorJob } from '../../common/constants/errors'; +import { JobRequestType, SolutionError } from '../../common/enums/job'; import { EventType } from '../../common/enums/webhook'; +import { IManifest, ISolution } from '../../common/interfaces/job'; +import { signMessage } from '../../common/utils/signature'; +import { StorageService } from '../storage/storage.service'; +import { Web3Service } from '../web3/web3.service'; import { WebhookDto } from '../webhook/webhook.dto'; +import { JobService } from './job.service'; jest.mock('minio', () => { class Client { @@ -81,6 +78,10 @@ jest.mock('@human-protocol/sdk', () => ({ describe('JobService', () => { let jobService: JobService; + jest + .spyOn(Web3ConfigService.prototype, 'privateKey', 'get') + .mockReturnValue(MOCK_WEB3_PRIVATE_KEY); + const signerMock = { address: MOCK_ADDRESS, getAddress: jest.fn().mockResolvedValue(MOCK_ADDRESS), @@ -93,33 +94,13 @@ describe('JobService', () => { beforeEach(async () => { const moduleRef = await Test.createTestingModule({ - imports: [ - ConfigModule.forFeature( - registerAs('s3', () => ({ - accessKey: MOCK_S3_ACCESS_KEY, - secretKey: MOCK_S3_SECRET_KEY, - endPoint: MOCK_S3_ENDPOINT, - port: MOCK_S3_PORT, - useSSL: MOCK_S3_USE_SSL, - bucket: MOCK_S3_BUCKET, - })), - ), - ConfigModule.forFeature( - registerAs('web3', () => ({ - web3PrivateKey: MOCK_WEB3_PRIVATE_KEY, - })), - ), - ConfigModule.forFeature( - registerAs('server', () => ({ - encryptionPrivateKey: MOCK_ENCRYPTION_PRIVATE_KEY, - encryptionPassphrase: MOCK_ENCRYPTION_PASSPHRASE, - pgpEncrypt: 'false', - })), - ), - ], providers: [ JobService, StorageService, + ConfigService, + Web3ConfigService, + S3ConfigService, + PGPConfigService, { provide: Web3Service, useValue: { diff --git a/packages/apps/fortune/recording-oracle/src/modules/job/job.service.ts b/packages/apps/fortune/recording-oracle/src/modules/job/job.service.ts index 7db1c23825..c280c43e27 100644 --- a/packages/apps/fortune/recording-oracle/src/modules/job/job.service.ts +++ b/packages/apps/fortune/recording-oracle/src/modules/job/job.service.ts @@ -13,8 +13,6 @@ import { } from '@nestjs/common'; import { ethers } from 'ethers'; import * as Minio from 'minio'; - -import { Web3ConfigType, web3ConfigKey } from '../../common/config'; import { ErrorJob } from '../../common/constants/errors'; import { JobRequestType, SolutionError } from '../../common/enums/job'; import { IManifest, ISolution } from '../../common/interfaces/job'; @@ -28,6 +26,7 @@ import { SolutionEventData, WebhookDto, } from '../webhook/webhook.dto'; +import { Web3ConfigService } from '../../common/config/web3-config.service'; @Injectable() export class JobService { @@ -35,8 +34,7 @@ export class JobService { public readonly minioClient: Minio.Client; constructor( - @Inject(web3ConfigKey) - private web3Config: Web3ConfigType, + private web3ConfigService: Web3ConfigService, @Inject(Web3Service) private readonly web3Service: Web3Service, @Inject(StorageService) @@ -201,7 +199,7 @@ export class JobService { escrowAddress: webhook.escrowAddress, eventType: EventType.TASK_COMPLETED, }, - this.web3Config.web3PrivateKey, + this.web3ConfigService.privateKey, ); return 'The requested job is completed.'; @@ -231,7 +229,7 @@ export class JobService { this.logger, exchangeOracleURL, webhookBody, - this.web3Config.web3PrivateKey, + this.web3ConfigService.privateKey, ); } diff --git a/packages/apps/fortune/recording-oracle/src/modules/storage/storage.module.ts b/packages/apps/fortune/recording-oracle/src/modules/storage/storage.module.ts index cf4c63fc05..70cb4e2341 100644 --- a/packages/apps/fortune/recording-oracle/src/modules/storage/storage.module.ts +++ b/packages/apps/fortune/recording-oracle/src/modules/storage/storage.module.ts @@ -1,15 +1,9 @@ import { Module } from '@nestjs/common'; import { StorageService } from './storage.service'; -import { ConfigModule } from '@nestjs/config'; -import { s3Config, serverConfig } from '../../common/config'; import { Web3Module } from '../web3/web3.module'; @Module({ - imports: [ - ConfigModule.forFeature(s3Config), - ConfigModule.forFeature(serverConfig), - Web3Module, - ], + imports: [Web3Module], providers: [StorageService], exports: [StorageService], }) diff --git a/packages/apps/fortune/recording-oracle/src/modules/storage/storage.service.spec.ts b/packages/apps/fortune/recording-oracle/src/modules/storage/storage.service.spec.ts index c9edca9058..9cf3482aae 100644 --- a/packages/apps/fortune/recording-oracle/src/modules/storage/storage.service.spec.ts +++ b/packages/apps/fortune/recording-oracle/src/modules/storage/storage.service.spec.ts @@ -2,27 +2,17 @@ import { ChainId, Encryption, EncryptionUtils, - KVStoreClient, EscrowClient, + KVStoreClient, StorageClient, } from '@human-protocol/sdk'; -import { ConfigModule, registerAs } from '@nestjs/config'; +import { ConfigService } from '@nestjs/config'; import { Test } from '@nestjs/testing'; -import { - MOCK_ADDRESS, - MOCK_ENCRYPTION_PASSPHRASE, - MOCK_ENCRYPTION_PRIVATE_KEY, - MOCK_FILE_URL, - MOCK_S3_ACCESS_KEY, - MOCK_S3_BUCKET, - MOCK_S3_ENDPOINT, - MOCK_S3_PORT, - MOCK_S3_SECRET_KEY, - MOCK_S3_USE_SSL, -} from '../../../test/constants'; -import { StorageService } from './storage.service'; +import { MOCK_ADDRESS, MOCK_FILE_URL } from '../../../test/constants'; +import { PGPConfigService } from '../../common/config/pgp-config.service'; +import { S3ConfigService } from '../../common/config/s3-config.service'; import { Web3Service } from '../web3/web3.service'; -import { ServerConfigType, serverConfigKey } from '@/common/config'; +import { StorageService } from './storage.service'; jest.mock('@human-protocol/sdk', () => ({ ...jest.requireActual('@human-protocol/sdk'), @@ -61,7 +51,8 @@ jest.mock('minio', () => { describe('StorageService', () => { let storageService: StorageService; - let serverConfig: ServerConfigType; + let pgpConfigService: PGPConfigService; + let s3ConfigService: S3ConfigService; const signerMock = { address: '0x1234567890123456789012345678901234567892', @@ -70,24 +61,6 @@ describe('StorageService', () => { beforeAll(async () => { const moduleRef = await Test.createTestingModule({ - imports: [ - ConfigModule.forFeature( - registerAs('s3', () => ({ - accessKey: MOCK_S3_ACCESS_KEY, - secretKey: MOCK_S3_SECRET_KEY, - endPoint: MOCK_S3_ENDPOINT, - port: MOCK_S3_PORT, - useSSL: MOCK_S3_USE_SSL, - bucket: MOCK_S3_BUCKET, - })), - ), - ConfigModule.forFeature( - registerAs('server', () => ({ - encryptionPrivateKey: MOCK_ENCRYPTION_PRIVATE_KEY, - encryptionPassphrase: MOCK_ENCRYPTION_PASSPHRASE, - })), - ), - ], providers: [ StorageService, { @@ -96,11 +69,15 @@ describe('StorageService', () => { getSigner: jest.fn().mockReturnValue(signerMock), }, }, + ConfigService, + PGPConfigService, + S3ConfigService, ], }).compile(); storageService = moduleRef.get(StorageService); - serverConfig = moduleRef.get(serverConfigKey); + pgpConfigService = moduleRef.get(PGPConfigService); + s3ConfigService = moduleRef.get(S3ConfigService); }); describe('uploadJobSolutions', () => { @@ -125,7 +102,7 @@ describe('StorageService', () => { (KVStoreClient.build as jest.Mock).mockResolvedValue({ getPublicKey: jest.fn().mockResolvedValue('publicKey'), }); - serverConfig.pgpEncrypt = true; + jest.spyOn(pgpConfigService, 'encrypt', 'get').mockReturnValue(true); const jobSolution = { workerAddress, @@ -138,11 +115,11 @@ describe('StorageService', () => { ); expect(fileData).toEqual({ - url: `http://${MOCK_S3_ENDPOINT}:${MOCK_S3_PORT}/${MOCK_S3_BUCKET}/${hash}.json`, + url: `http://${s3ConfigService.endpoint}:${s3ConfigService.port}/${s3ConfigService.bucket}/${hash}.json`, hash, }); expect(storageService.minioClient.putObject).toHaveBeenCalledWith( - MOCK_S3_BUCKET, + s3ConfigService.bucket, `${hash}.json`, 'encrypted', { @@ -185,7 +162,7 @@ describe('StorageService', () => { storageService.minioClient.putObject = jest .fn() .mockRejectedValue('Network error'); - serverConfig.pgpEncrypt = false; + jest.spyOn(pgpConfigService, 'encrypt', 'get').mockReturnValue(false); const jobSolution = { workerAddress, solution, @@ -211,7 +188,7 @@ describe('StorageService', () => { (KVStoreClient.build as jest.Mock).mockResolvedValue({ getPublicKey: jest.fn().mockResolvedValue(''), }); - serverConfig.pgpEncrypt = true; + jest.spyOn(pgpConfigService, 'encrypt', 'get').mockReturnValue(true); const jobSolution = { workerAddress, solution, diff --git a/packages/apps/fortune/recording-oracle/src/modules/storage/storage.service.ts b/packages/apps/fortune/recording-oracle/src/modules/storage/storage.service.ts index c37ea24599..99cf35a545 100644 --- a/packages/apps/fortune/recording-oracle/src/modules/storage/storage.service.ts +++ b/packages/apps/fortune/recording-oracle/src/modules/storage/storage.service.ts @@ -8,41 +8,35 @@ import { } from '@human-protocol/sdk'; import { BadRequestException, Inject, Injectable } from '@nestjs/common'; import * as Minio from 'minio'; -import { - S3ConfigType, - ServerConfigType, - s3ConfigKey, - serverConfigKey, -} from '../../common/config'; import { ISolution } from '../../common/interfaces/job'; import crypto from 'crypto'; import { SaveSolutionsDto } from '../job/job.dto'; import { Web3Service } from '../web3/web3.service'; +import { S3ConfigService } from '../../common/config/s3-config.service'; +import { PGPConfigService } from '../../common/config/pgp-config.service'; @Injectable() export class StorageService { public readonly minioClient: Minio.Client; constructor( - @Inject(s3ConfigKey) - private s3Config: S3ConfigType, - @Inject(serverConfigKey) - private serverConfig: ServerConfigType, + private s3ConfigService: S3ConfigService, + private pgpConfigService: PGPConfigService, @Inject(Web3Service) private readonly web3Service: Web3Service, ) { this.minioClient = new Minio.Client({ - endPoint: this.s3Config.endPoint, - port: this.s3Config.port, - accessKey: this.s3Config.accessKey, - secretKey: this.s3Config.secretKey, - useSSL: this.s3Config.useSSL, + endPoint: this.s3ConfigService.endpoint, + port: this.s3ConfigService.port, + accessKey: this.s3ConfigService.accessKey, + secretKey: this.s3ConfigService.secretKey, + useSSL: this.s3ConfigService.useSSL, }); } public getJobUrl(hash: string): string { - return `${this.s3Config.useSSL ? 'https' : 'http'}://${ - this.s3Config.endPoint - }:${this.s3Config.port}/${this.s3Config.bucket}/${hash}.json`; + return `${this.s3ConfigService.useSSL ? 'https' : 'http'}://${ + this.s3ConfigService.endpoint + }:${this.s3ConfigService.port}/${this.s3ConfigService.bucket}/${hash}.json`; } public async download(url: string): Promise { @@ -55,8 +49,8 @@ export class StorageService { ) { try { const encryption = await Encryption.build( - this.serverConfig.encryptionPrivateKey, - this.serverConfig.encryptionPassphrase, + this.pgpConfigService.privateKey, + this.pgpConfigService.passphrase, ); return JSON.parse(await encryption.decrypt(fileContent)); @@ -82,12 +76,12 @@ export class StorageService { chainId: ChainId, solutions: ISolution[], ): Promise { - if (!(await this.minioClient.bucketExists(this.s3Config.bucket))) { + if (!(await this.minioClient.bucketExists(this.s3ConfigService.bucket))) { throw new BadRequestException('Bucket not found'); } let fileToUpload = JSON.stringify(solutions); - if (this.serverConfig.pgpEncrypt.toString() === 'true') { + if (this.pgpConfigService.encrypt) { try { const signer = this.web3Service.getSigner(chainId); const escrowClient = await EscrowClient.build(signer); @@ -125,7 +119,7 @@ export class StorageService { try { const hash = crypto.createHash('sha1').update(fileToUpload).digest('hex'); await this.minioClient.putObject( - this.s3Config.bucket, + this.s3ConfigService.bucket, `${hash}.json`, fileToUpload, { diff --git a/packages/apps/fortune/recording-oracle/src/modules/web3/web3.module.ts b/packages/apps/fortune/recording-oracle/src/modules/web3/web3.module.ts index 1a67598c72..fbc69af597 100644 --- a/packages/apps/fortune/recording-oracle/src/modules/web3/web3.module.ts +++ b/packages/apps/fortune/recording-oracle/src/modules/web3/web3.module.ts @@ -1,10 +1,7 @@ import { Module } from '@nestjs/common'; import { Web3Service } from './web3.service'; -import { ConfigModule } from '@nestjs/config'; -import { web3Config } from '../../common/config'; @Module({ - imports: [ConfigModule.forFeature(web3Config)], providers: [Web3Service], exports: [Web3Service], }) diff --git a/packages/apps/fortune/recording-oracle/src/modules/web3/web3.service.spec.ts b/packages/apps/fortune/recording-oracle/src/modules/web3/web3.service.spec.ts index 72047ba267..7dad6d4e30 100644 --- a/packages/apps/fortune/recording-oracle/src/modules/web3/web3.service.spec.ts +++ b/packages/apps/fortune/recording-oracle/src/modules/web3/web3.service.spec.ts @@ -1,22 +1,20 @@ -import { ConfigModule, registerAs } from '@nestjs/config'; +import { ConfigService } from '@nestjs/config'; import { Test } from '@nestjs/testing'; -import { Web3Service } from './web3.service'; import { MOCK_WEB3_PRIVATE_KEY } from '../../../test/constants'; +import { Web3ConfigService } from '../../common/config/web3-config.service'; import { networkMap } from '../../common/constants/networks'; +import { Web3Service } from './web3.service'; describe('Web3Service', () => { let web3Service: Web3Service; + jest + .spyOn(Web3ConfigService.prototype, 'privateKey', 'get') + .mockReturnValue(MOCK_WEB3_PRIVATE_KEY); + beforeAll(async () => { const moduleRef = await Test.createTestingModule({ - imports: [ - ConfigModule.forFeature( - registerAs('web3', () => ({ - web3PrivateKey: MOCK_WEB3_PRIVATE_KEY, - })), - ), - ], - providers: [Web3Service], + providers: [Web3Service, ConfigService, Web3ConfigService], }).compile(); web3Service = moduleRef.get(Web3Service); diff --git a/packages/apps/fortune/recording-oracle/src/modules/web3/web3.service.ts b/packages/apps/fortune/recording-oracle/src/modules/web3/web3.service.ts index 70b97fe0c6..4142872fa8 100644 --- a/packages/apps/fortune/recording-oracle/src/modules/web3/web3.service.ts +++ b/packages/apps/fortune/recording-oracle/src/modules/web3/web3.service.ts @@ -1,17 +1,14 @@ -import { Inject, Injectable } from '@nestjs/common'; +import { Injectable } from '@nestjs/common'; import { Wallet, ethers } from 'ethers'; -import { Web3ConfigType, web3ConfigKey } from '../../common/config'; import { networkMap } from '../../common/constants/networks'; +import { Web3ConfigService } from '@/common/config/web3-config.service'; @Injectable() export class Web3Service { private signers: { [key: number]: Wallet } = {}; - constructor( - @Inject(web3ConfigKey) - private web3Config: Web3ConfigType, - ) { - const privateKey = this.web3Config.web3PrivateKey; + constructor(private web3ConfigService: Web3ConfigService) { + const privateKey = this.web3ConfigService.privateKey; for (const networkKey of Object.keys(networkMap)) { const network = networkMap[networkKey]; diff --git a/packages/apps/fortune/recording-oracle/src/modules/webhook/webhook.controller.spec.ts b/packages/apps/fortune/recording-oracle/src/modules/webhook/webhook.controller.spec.ts index a7b981edde..9e1d403f95 100644 --- a/packages/apps/fortune/recording-oracle/src/modules/webhook/webhook.controller.spec.ts +++ b/packages/apps/fortune/recording-oracle/src/modules/webhook/webhook.controller.spec.ts @@ -1,28 +1,20 @@ +import { PGPConfigService } from '@/common/config/pgp-config.service'; +import { S3ConfigService } from '@/common/config/s3-config.service'; +import { ServerConfigService } from '@/common/config/server-config.service'; +import { Web3ConfigService } from '@/common/config/web3-config.service'; +import { HttpService } from '@nestjs/axios'; import { ConfigService } from '@nestjs/config'; import { Test } from '@nestjs/testing'; -import { WebhookController } from './webhook.controller'; -import { WebhookService } from './webhook.service'; -import { WebhookDto } from './webhook.dto'; -import { Web3Service } from '../web3/web3.service'; -import { HttpService } from '@nestjs/axios'; import { of } from 'rxjs'; -import { ConfigModule, registerAs } from '@nestjs/config'; -import { - MOCK_FILE_URL, - MOCK_REPUTATION_ORACLE_WEBHOOK_URL, - MOCK_S3_ACCESS_KEY, - MOCK_S3_BUCKET, - MOCK_S3_ENDPOINT, - MOCK_S3_PORT, - MOCK_S3_SECRET_KEY, - MOCK_S3_USE_SSL, - MOCK_SIGNATURE, - MOCK_WEB3_PRIVATE_KEY, -} from '../../../test/constants'; -import { StorageService } from '../storage/storage.service'; -import { verifySignature } from '../../common/utils/signature'; +import { MOCK_FILE_URL, MOCK_SIGNATURE } from '../../../test/constants'; import { EventType } from '../../common/enums/webhook'; +import { verifySignature } from '../../common/utils/signature'; import { JobService } from '../job/job.service'; +import { StorageService } from '../storage/storage.service'; +import { Web3Service } from '../web3/web3.service'; +import { WebhookController } from './webhook.controller'; +import { WebhookDto } from './webhook.dto'; +import { WebhookService } from './webhook.service'; jest.mock('../../common/utils/signature'); @@ -34,47 +26,21 @@ describe('webhookController', () => { const chainId = 1; const escrowAddress = '0x1234567890123456789012345678901234567890'; - const reputationOracleURL = 'https://example.com/reputationoracle'; - const configServiceMock = { - get: jest.fn().mockReturnValue(reputationOracleURL), - }; - const httpServicePostMock = jest .fn() .mockReturnValue(of({ status: 200, data: {} })); beforeAll(async () => { const moduleRef = await Test.createTestingModule({ - imports: [ - ConfigModule.forFeature( - registerAs('s3', () => ({ - accessKey: MOCK_S3_ACCESS_KEY, - secretKey: MOCK_S3_SECRET_KEY, - endPoint: MOCK_S3_ENDPOINT, - port: MOCK_S3_PORT, - useSSL: MOCK_S3_USE_SSL, - bucket: MOCK_S3_BUCKET, - })), - ), - ConfigModule.forFeature( - registerAs('server', () => ({ - reputationOracleWebhookUrl: MOCK_REPUTATION_ORACLE_WEBHOOK_URL, - })), - ), - ConfigModule.forFeature( - registerAs('web3', () => ({ - web3PrivateKey: MOCK_WEB3_PRIVATE_KEY, - })), - ), - ], controllers: [WebhookController], providers: [ WebhookService, JobService, - { - provide: ConfigService, - useValue: configServiceMock, - }, + ConfigService, + Web3ConfigService, + PGPConfigService, + S3ConfigService, + ServerConfigService, { provide: Web3Service, useValue: { diff --git a/packages/apps/fortune/recording-oracle/src/modules/webhook/webhook.module.ts b/packages/apps/fortune/recording-oracle/src/modules/webhook/webhook.module.ts index cb30eaa2e3..12e1beea25 100644 --- a/packages/apps/fortune/recording-oracle/src/modules/webhook/webhook.module.ts +++ b/packages/apps/fortune/recording-oracle/src/modules/webhook/webhook.module.ts @@ -3,20 +3,12 @@ import { Module } from '@nestjs/common'; import { WebhookController } from './webhook.controller'; import { WebhookService } from './webhook.service'; import { JobService } from '../job/job.service'; -import { ConfigModule } from '@nestjs/config'; -import { serverConfig, web3Config } from '../../common/config'; import { HttpModule } from '@nestjs/axios'; import { Web3Module } from '../web3/web3.module'; import { StorageModule } from '../storage/storage.module'; @Module({ - imports: [ - ConfigModule.forFeature(serverConfig), - ConfigModule.forFeature(web3Config), - HttpModule, - Web3Module, - StorageModule, - ], + imports: [HttpModule, Web3Module, StorageModule], controllers: [WebhookController], providers: [WebhookService, JobService], exports: [WebhookService], diff --git a/packages/apps/fortune/recording-oracle/src/modules/webhook/webhook.service.spec.ts b/packages/apps/fortune/recording-oracle/src/modules/webhook/webhook.service.spec.ts index 9c92ddd384..1be196aa32 100644 --- a/packages/apps/fortune/recording-oracle/src/modules/webhook/webhook.service.spec.ts +++ b/packages/apps/fortune/recording-oracle/src/modules/webhook/webhook.service.spec.ts @@ -1,26 +1,18 @@ +import { HttpService } from '@nestjs/axios'; +import { ConfigService } from '@nestjs/config'; import { Test } from '@nestjs/testing'; +import { of } from 'rxjs'; +import { MOCK_FILE_URL } from '../../../test/constants'; +import { PGPConfigService } from '../../common/config/pgp-config.service'; +import { S3ConfigService } from '../../common/config/s3-config.service'; +import { ServerConfigService } from '../../common/config/server-config.service'; +import { Web3ConfigService } from '../../common/config/web3-config.service'; import { EventType } from '../../common/enums/webhook'; -import { WebhookDto } from './webhook.dto'; -import { WebhookService } from './webhook.service'; import { JobService } from '../job/job.service'; -import { ConfigModule, registerAs } from '@nestjs/config'; -import { - MOCK_ENCRYPTION_PASSPHRASE, - MOCK_ENCRYPTION_PRIVATE_KEY, - MOCK_FILE_URL, - MOCK_REPUTATION_ORACLE_WEBHOOK_URL, - MOCK_S3_ACCESS_KEY, - MOCK_S3_BUCKET, - MOCK_S3_ENDPOINT, - MOCK_S3_PORT, - MOCK_S3_SECRET_KEY, - MOCK_S3_USE_SSL, - MOCK_WEB3_PRIVATE_KEY, -} from '../../../test/constants'; -import { Web3Service } from '../web3/web3.service'; -import { of } from 'rxjs'; -import { HttpService } from '@nestjs/axios'; import { StorageService } from '../storage/storage.service'; +import { Web3Service } from '../web3/web3.service'; +import { WebhookDto } from './webhook.dto'; +import { WebhookService } from './webhook.service'; describe('WebhookService', () => { let webhookService: WebhookService, jobService: JobService; @@ -39,34 +31,15 @@ describe('WebhookService', () => { beforeEach(async () => { const moduleRef = await Test.createTestingModule({ - imports: [ - ConfigModule.forFeature( - registerAs('s3', () => ({ - accessKey: MOCK_S3_ACCESS_KEY, - secretKey: MOCK_S3_SECRET_KEY, - endPoint: MOCK_S3_ENDPOINT, - port: MOCK_S3_PORT, - useSSL: MOCK_S3_USE_SSL, - bucket: MOCK_S3_BUCKET, - })), - ), - ConfigModule.forFeature( - registerAs('web3', () => ({ - web3PrivateKey: MOCK_WEB3_PRIVATE_KEY, - })), - ), - ConfigModule.forFeature( - registerAs('server', () => ({ - reputationOracleWebhookUrl: MOCK_REPUTATION_ORACLE_WEBHOOK_URL, - encryptionPrivateKey: MOCK_ENCRYPTION_PRIVATE_KEY, - encryptionPassphrase: MOCK_ENCRYPTION_PASSPHRASE, - })), - ), - ], providers: [ WebhookService, JobService, StorageService, + ConfigService, + Web3ConfigService, + PGPConfigService, + S3ConfigService, + ServerConfigService, { provide: Web3Service, useValue: { From b2754afa1750e219e8fb1c2315208523e58b4136 Mon Sep 17 00:00:00 2001 From: portuu3 <61605646+portuu3@users.noreply.github.com> Date: Fri, 5 Apr 2024 17:04:57 +0200 Subject: [PATCH 42/66] Job Launcher - Refactor bucket urls handling (#1812) * Refactor the way to handle bucket urls * add missing envs --- .../server/src/common/utils/storage.spec.ts | 22 ++--- .../server/src/common/utils/storage.ts | 41 +++++---- .../server/src/modules/job/job.interface.ts | 17 ++-- .../src/modules/job/job.service.spec.ts | 67 ++++----------- .../server/src/modules/job/job.service.ts | 83 ++++++------------- scripts/fortune/.env.rep-oracle | 3 + 6 files changed, 83 insertions(+), 150 deletions(-) diff --git a/packages/apps/job-launcher/server/src/common/utils/storage.spec.ts b/packages/apps/job-launcher/server/src/common/utils/storage.spec.ts index a56762f9d5..d43c9de5f3 100644 --- a/packages/apps/job-launcher/server/src/common/utils/storage.spec.ts +++ b/packages/apps/job-launcher/server/src/common/utils/storage.spec.ts @@ -2,7 +2,7 @@ import { AWSRegions, StorageProviders } from '../enums/storage'; import { JobRequestType } from '../enums/job'; import axios from 'axios'; import { StorageDataDto } from '../../modules/job/job.dto'; -import { listObjectsInBucket } from './storage'; +import { generateBucketUrl, listObjectsInBucket } from './storage'; jest.mock('axios'); @@ -15,7 +15,7 @@ describe('Storage utils', () => { bucketName: 'my-bucket', path: 'my-folder', }; - const jobType: JobRequestType = JobRequestType.IMAGE_POINTS; + const url = generateBucketUrl(storageData, JobRequestType.IMAGE_POINTS); const objects = ['object1', 'object2']; const response = { status: 200, @@ -32,7 +32,7 @@ describe('Storage utils', () => { }; axios.get = jest.fn().mockResolvedValueOnce(response); - const result = await listObjectsInBucket(storageData, jobType); + const result = await listObjectsInBucket(url); expect(result).toEqual(objects); }); @@ -44,7 +44,7 @@ describe('Storage utils', () => { bucketName: 'my-bucket', path: 'my-folder', }; - const jobType: JobRequestType = JobRequestType.IMAGE_POINTS; + const url = generateBucketUrl(storageData, JobRequestType.IMAGE_POINTS); const objects = Array.from({ length: 4 }, (_, i) => `object${i + 1}`); const response1 = { status: 200, @@ -78,7 +78,7 @@ describe('Storage utils', () => { .mockResolvedValueOnce(response1) .mockResolvedValueOnce(response2); - const result = await listObjectsInBucket(storageData, jobType); + const result = await listObjectsInBucket(url); expect(result).toEqual(objects); }); @@ -90,7 +90,7 @@ describe('Storage utils', () => { bucketName: 'my-bucket', path: 'my-folder', }; - const jobType: JobRequestType = JobRequestType.IMAGE_POINTS; + const url = generateBucketUrl(storageData, JobRequestType.IMAGE_POINTS); const response = { status: 200, data: ` @@ -100,7 +100,7 @@ describe('Storage utils', () => { }; axios.get = jest.fn().mockResolvedValueOnce(response); - const result = await listObjectsInBucket(storageData, jobType); + const result = await listObjectsInBucket(url); expect(result).toEqual([]); }); @@ -112,14 +112,14 @@ describe('Storage utils', () => { bucketName: 'non-existent-bucket', path: 'my-folder', }; - const jobType: JobRequestType = JobRequestType.IMAGE_POINTS; + const url = generateBucketUrl(storageData, JobRequestType.IMAGE_POINTS); const response = { status: 404, data: 'Bucket not found', }; axios.get = jest.fn().mockResolvedValueOnce(response); - await expect(listObjectsInBucket(storageData, jobType)).rejects.toEqual( + await expect(listObjectsInBucket(url)).rejects.toEqual( 'Failed to fetch bucket contents', ); }); @@ -131,14 +131,14 @@ describe('Storage utils', () => { bucketName: 'private-bucket', path: 'my-folder', }; - const jobType: JobRequestType = JobRequestType.IMAGE_POINTS; + const url = generateBucketUrl(storageData, JobRequestType.IMAGE_POINTS); const response = { status: 403, data: 'Access denied', }; axios.get = jest.fn().mockResolvedValueOnce(response); - await expect(listObjectsInBucket(storageData, jobType)).rejects.toEqual( + await expect(listObjectsInBucket(url)).rejects.toEqual( 'Failed to fetch bucket contents', ); }); diff --git a/packages/apps/job-launcher/server/src/common/utils/storage.ts b/packages/apps/job-launcher/server/src/common/utils/storage.ts index d304087fa9..e29feda32a 100644 --- a/packages/apps/job-launcher/server/src/common/utils/storage.ts +++ b/packages/apps/job-launcher/server/src/common/utils/storage.ts @@ -1,4 +1,4 @@ -import { BadRequestException } from '@nestjs/common'; +import { BadRequestException, HttpStatus } from '@nestjs/common'; import { StorageDataDto } from '../../modules/job/job.dto'; import { AWSRegions, StorageProviders } from '../enums/storage'; import { ErrorBucket } from '../constants/errors'; @@ -9,8 +9,7 @@ import { parseString } from 'xml2js'; export function generateBucketUrl( storageData: StorageDataDto, jobType: JobRequestType, - addPath = true, -): string { +): URL { if ( (jobType === JobRequestType.IMAGE_BOXES || jobType === JobRequestType.IMAGE_POINTS || @@ -31,17 +30,19 @@ export function generateBucketUrl( if (!isRegion(storageData.region)) { throw new BadRequestException(ErrorBucket.InvalidRegion); } - return `https://${storageData.bucketName}.s3.${ - storageData.region - }.amazonaws.com${ - storageData.path && addPath - ? `/${storageData.path.replace(/\/$/, '')}` - : '' - }`; + return new URL( + `https://${storageData.bucketName}.s3.${ + storageData.region + }.amazonaws.com${ + storageData.path ? `/${storageData.path.replace(/\/$/, '')}` : '' + }`, + ); case StorageProviders.GCS: - return `https://${storageData.bucketName}.storage.googleapis.com${ - storageData.path && addPath ? `/${storageData.path}` : '' - }`; + return new URL( + `https://${storageData.bucketName}.storage.googleapis.com${ + storageData.path ? `/${storageData.path}` : '' + }`, + ); default: throw new BadRequestException(ErrorBucket.InvalidProvider); } @@ -51,28 +52,24 @@ function isRegion(value: string): value is AWSRegions { return Object.values(AWSRegions).includes(value as AWSRegions); } -export async function listObjectsInBucket( - storageData: StorageDataDto, - jobType: JobRequestType, -): Promise { +export async function listObjectsInBucket(url: URL): Promise { return new Promise(async (resolve, reject) => { try { let objects: string[] = []; - const url = generateBucketUrl(storageData, jobType, false); let nextContinuationToken: string | undefined; - + const baseUrl = `${url.protocol}//${url.host}`; do { const response = await axios.get( - `${url}?list-type=2${ + `${baseUrl}?list-type=2${ nextContinuationToken ? `&continuation-token=${encodeURIComponent( nextContinuationToken, )}` : '' - }${storageData.path ? `&prefix=${storageData.path}` : ''}`, + }${url.pathname ? `&prefix=${url.pathname}` : ''}`, ); - if (response.status === 200 && response.data) { + if (response.status === HttpStatus.OK && response.data) { parseString(response.data, (err: any, result: any) => { if (err) { reject(err); diff --git a/packages/apps/job-launcher/server/src/modules/job/job.interface.ts b/packages/apps/job-launcher/server/src/modules/job/job.interface.ts index 1baaac3105..d3e2ad8716 100644 --- a/packages/apps/job-launcher/server/src/modules/job/job.interface.ts +++ b/packages/apps/job-launcher/server/src/modules/job/job.interface.ts @@ -11,11 +11,7 @@ export interface RequestAction { } export interface ManifestAction { - getElementsCount: ( - requestType: JobRequestType, - data: CvatDataDto, - gtUrl?: string, - ) => Promise; + getElementsCount: (urls: GenerateUrls) => Promise; generateUrls: ( data: CvatDataDto, groundTruth: StorageDataDto, @@ -39,16 +35,15 @@ export interface OracleAddresses { export interface CvatCalculateJobBounty { requestType: JobRequestType; fundAmount: number; - data: CvatDataDto; - gtUrl: string; + urls: GenerateUrls; nodesTotal?: number; } export interface GenerateUrls { - dataUrl: string; - gtUrl: string; - pointsUrl?: string; - boxesUrl?: string; + dataUrl: URL; + gtUrl: URL; + pointsUrl?: URL; + boxesUrl?: URL; } export interface CvatImageData { diff --git a/packages/apps/job-launcher/server/src/modules/job/job.service.spec.ts b/packages/apps/job-launcher/server/src/modules/job/job.service.spec.ts index 7a535d092b..10aa757205 100644 --- a/packages/apps/job-launcher/server/src/modules/job/job.service.spec.ts +++ b/packages/apps/job-launcher/server/src/modules/job/job.service.spec.ts @@ -91,11 +91,9 @@ import { JobFortuneDto, JobCvatDto, JobDetailsDto, - StorageDataDto, JobCaptchaDto, CvatManifestDto, JobQuickLaunchDto, - CvatDataDto, } from './job.dto'; import { JobEntity } from './job.entity'; import { JobRepository } from './job.repository'; @@ -124,6 +122,7 @@ import { Web3ConfigService } from '../../common/config/web3-config.service'; import { CvatConfigService } from '../../common/config/cvat-config.service'; import { PGPConfigService } from '../../common/config/pgp-config.service'; import { S3ConfigService } from '../../common/config/s3-config.service'; +import { CvatCalculateJobBounty } from './job.interface'; const rate = 1.5; jest.mock('@human-protocol/sdk', () => ({ @@ -529,38 +528,15 @@ describe('JobService', () => { }); describe('getCvatElementsCount', () => { - let storageDatasetMock: StorageDataDto; - - beforeAll(async () => { - storageDatasetMock = { - provider: StorageProviders.AWS, - region: AWSRegions.AP_EAST_1, - bucketName: 'bucket', - path: 'folder/test', - }; - }); - - it('should throw ConflictException if storageData is not provided', async () => { - await expect( - jobService.getCvatElementsCount( - JobRequestType.IMAGE_BOXES_FROM_POINTS, - null as any, - 'some-gt-url', - ), - ).rejects.toThrow(ConflictException); - }); - it('should calculate the number of CVAT elements correctly', async () => { + const gtUrl = new URL('http://some-gt-url.com'); + const dataUrl = new URL('http://some-data-url.com'); jest .spyOn(storageService, 'download') .mockResolvedValueOnce(MOCK_CVAT_DATA) .mockResolvedValueOnce(MOCK_CVAT_GT); - const result = await jobService.getCvatElementsCount( - JobRequestType.IMAGE_BOXES_FROM_POINTS, - storageDatasetMock, - 'some-gt-url', - ); + const result = await jobService.getCvatElementsCount(gtUrl, dataUrl); expect(result).toBe(2); }); }); @@ -1149,34 +1125,22 @@ describe('JobService', () => { .mockResolvedValueOnce(MOCK_CVAT_DATA) .mockResolvedValueOnce(MOCK_CVAT_GT); - const data = { + const data: CvatCalculateJobBounty = { requestType: JobRequestType.IMAGE_BOXES_FROM_POINTS, fundAmount: 22.918128652290278, - data: storageDatasetMock, - gtUrl: MOCK_FILE_URL, + urls: { + dataUrl: new URL(MOCK_FILE_URL), + gtUrl: new URL(MOCK_FILE_URL), + pointsUrl: new URL(MOCK_FILE_URL), + }, }; - const result = await jobService['calculateJobBounty'](data); // elementsCount = 2 + const result = await jobService.calculateJobBounty(data); // elementsCount = 2 expect(result).toEqual('11.459064326145139'); }); it('should calculate the job bounty correctly for image skeletons from boxed type', async () => { - const storageDatasetMock: any = { - dataset: { - provider: StorageProviders.AWS, - region: AWSRegions.EU_CENTRAL_1, - bucketName: 'bucket', - path: 'folder/test', - }, - boxes: { - provider: StorageProviders.AWS, - region: AWSRegions.EU_CENTRAL_1, - bucketName: 'bucket', - path: 'folder/test', - }, - }; - jest .spyOn(storageService, 'download') .mockResolvedValueOnce(MOCK_CVAT_DATA) @@ -1185,12 +1149,15 @@ describe('JobService', () => { const data = { requestType: JobRequestType.IMAGE_SKELETONS_FROM_BOXES, fundAmount: 22.918128652290278, - data: storageDatasetMock, - gtUrl: MOCK_FILE_URL, + urls: { + dataUrl: new URL(MOCK_FILE_URL), + gtUrl: new URL(MOCK_FILE_URL), + boxesUrl: new URL(MOCK_FILE_URL), + }, nodesTotal: 4, }; - const result = await jobService['calculateJobBounty'](data); // elementsCount = 2 + const result = await jobService.calculateJobBounty(data); // elementsCount = 2 expect(result).toEqual('5.7295321630725695'); }); diff --git a/packages/apps/job-launcher/server/src/modules/job/job.service.ts b/packages/apps/job-launcher/server/src/modules/job/job.service.ts index 28294c4660..a9b16b9519 100644 --- a/packages/apps/job-launcher/server/src/modules/job/job.service.ts +++ b/packages/apps/job-launcher/server/src/modules/job/job.service.ts @@ -161,19 +161,18 @@ export class JobService { const jobBounty = await this.calculateJobBounty({ requestType, fundAmount: tokenFundAmount, - data: dto.data, - gtUrl: urls.gtUrl, + urls: urls, nodesTotal: dto.labels[0]?.nodes?.length, }); return { data: { - data_url: urls.dataUrl, - ...(dto.data.points && { - points_url: urls.pointsUrl, + data_url: urls.dataUrl.href, + ...(urls.pointsUrl && { + points_url: urls.pointsUrl?.href, }), - ...(dto.data.boxes && { - boxes_url: urls.boxesUrl, + ...(urls.boxesUrl && { + boxes_url: urls.boxesUrl?.href, }), }, annotation: { @@ -186,7 +185,7 @@ export class JobService { validation: { min_quality: dto.minQuality, val_size: this.cvatConfigService.valSize, - gt_url: urls.gtUrl, + gt_url: urls.gtUrl.href, }, job_bounty: jobBounty, }; @@ -197,10 +196,7 @@ export class JobService { ): Promise { const jobType = jobDto.annotations.typeOfJob; const dataUrl = generateBucketUrl(jobDto.data, JobRequestType.HCAPTCHA); - const objectsInBucket = await listObjectsInBucket( - jobDto.data, - JobRequestType.HCAPTCHA, - ); + const objectsInBucket = await listObjectsInBucket(dataUrl); const commonManifestProperties = { job_mode: JobCaptchaMode.BATCH, @@ -215,7 +211,7 @@ export class JobService { job_total_tasks: objectsInBucket.length, task_bid_price: jobDto.annotations.taskBidPrice, taskdata_uri: await this.generateAndUploadTaskData( - dataUrl, + dataUrl.href, objectsInBucket, ), public_results: true, @@ -433,14 +429,9 @@ export class JobService { private createJobSpecificActions: Record = { [JobRequestType.HCAPTCHA]: { calculateFundAmount: async (dto: JobCaptchaDto, rate: number) => { - const objectsInBucket = await listObjectsInBucket( - dto.data, - JobRequestType.HCAPTCHA, - ); - return await div( - dto.annotations.taskBidPrice * objectsInBucket.length, - rate, - ); + const dataUrl = generateBucketUrl(dto.data, JobRequestType.HCAPTCHA); + const objectsInBucket = await listObjectsInBucket(dataUrl); + return div(dto.annotations.taskBidPrice * objectsInBucket.length, rate); }, createManifest: (dto: JobCaptchaDto) => this.createHCaptchaManifest(dto), }, @@ -514,17 +505,15 @@ export class JobService { private createManifestActions: Record = { [JobRequestType.HCAPTCHA]: { getElementsCount: async () => 0, - generateUrls: () => ({ dataUrl: '', gtUrl: '' }), + generateUrls: () => ({ dataUrl: new URL(''), gtUrl: new URL('') }), }, [JobRequestType.FORTUNE]: { getElementsCount: async () => 0, - generateUrls: () => ({ dataUrl: '', gtUrl: '' }), + generateUrls: () => ({ dataUrl: new URL(''), gtUrl: new URL('') }), }, [JobRequestType.IMAGE_BOXES]: { - getElementsCount: async ( - requestType: JobRequestType, - data: CvatDataDto, - ) => (await listObjectsInBucket(data.dataset, requestType)).length, + getElementsCount: async (urls: GenerateUrls) => + (await listObjectsInBucket(urls.dataUrl)).length, generateUrls: ( data: CvatDataDto, groundTruth: StorageDataDto, @@ -538,10 +527,8 @@ export class JobService { }, }, [JobRequestType.IMAGE_POINTS]: { - getElementsCount: async ( - requestType: JobRequestType, - data: CvatDataDto, - ) => (await listObjectsInBucket(data.dataset, requestType)).length, + getElementsCount: async (urls: GenerateUrls) => + (await listObjectsInBucket(urls.dataUrl)).length, generateUrls: ( data: CvatDataDto, groundTruth: StorageDataDto, @@ -555,11 +542,8 @@ export class JobService { }, }, [JobRequestType.IMAGE_BOXES_FROM_POINTS]: { - getElementsCount: async ( - requestType: JobRequestType, - data: CvatDataDto, - gtUrl: string, - ) => this.getCvatElementsCount(requestType, data.points!, gtUrl), + getElementsCount: async (urls: GenerateUrls) => + this.getCvatElementsCount(urls.gtUrl, urls.pointsUrl!), generateUrls: ( data: CvatDataDto, groundTruth: StorageDataDto, @@ -578,11 +562,8 @@ export class JobService { }, }, [JobRequestType.IMAGE_SKELETONS_FROM_BOXES]: { - getElementsCount: async ( - requestType: JobRequestType, - data: CvatDataDto, - gtUrl: string, - ) => this.getCvatElementsCount(requestType, data.boxes!, gtUrl), + getElementsCount: async (urls: GenerateUrls) => + this.getCvatElementsCount(urls.gtUrl, urls.boxesUrl!), generateUrls: ( data: CvatDataDto, groundTruth: StorageDataDto, @@ -773,19 +754,9 @@ export class JobService { return jobEntity.id; } - public async getCvatElementsCount( - requestType: JobRequestType, - storageData: StorageDataDto, - gtUrl: string, - ): Promise { - if (!storageData) { - throw new ConflictException(ErrorJob.DataNotExist); - } - - const data = await this.storageService.download( - generateBucketUrl(storageData, requestType), - ); - const gt = await this.storageService.download(gtUrl); + public async getCvatElementsCount(gtUrl: URL, dataUrl: URL): Promise { + const data = await this.storageService.download(dataUrl.href); + const gt = await this.storageService.download(gtUrl.href); let gtEntries = 0; @@ -809,10 +780,10 @@ export class JobService { public async calculateJobBounty( params: CvatCalculateJobBounty, ): Promise { - const { requestType, fundAmount, data, gtUrl, nodesTotal } = params; + const { requestType, fundAmount, urls, nodesTotal } = params; const { getElementsCount } = this.createManifestActions[requestType]; - const elementsCount = await getElementsCount(requestType, data, gtUrl); + const elementsCount = await getElementsCount(urls); let jobSize = Number(this.cvatConfigService.jobSize); diff --git a/scripts/fortune/.env.rep-oracle b/scripts/fortune/.env.rep-oracle index b4fa25ee47..047ad92fcc 100644 --- a/scripts/fortune/.env.rep-oracle +++ b/scripts/fortune/.env.rep-oracle @@ -63,3 +63,6 @@ PGP_ENCRYPT=false # Synaps Kyc SYNAPS_API_KEY=synaps-disabled SYNAPS_WEBHOOK_SECRET=test + +HCAPTCHA_SITE_KEY=test +HCAPTCHA_SECRET=test \ No newline at end of file From dac5463c19b450dd6f0dae23018ce68926db93b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Francisco=20L=C3=B3pez?= <50665615+flopez7@users.noreply.github.com> Date: Fri, 5 Apr 2024 17:06:30 +0200 Subject: [PATCH 43/66] Fix units returned in job launcher server (#1829) Modify getResults by adding the id in path instead of query Fix getJobList call in job launcher client --- .../job-launcher/client/src/services/job.ts | 20 ++++++++++--------- .../server/src/modules/job/job.controller.ts | 6 +++--- .../server/src/modules/job/job.service.ts | 14 ++++++++++--- 3 files changed, 25 insertions(+), 15 deletions(-) diff --git a/packages/apps/job-launcher/client/src/services/job.ts b/packages/apps/job-launcher/client/src/services/job.ts index 3ba1d0f4a7..b31d8e9fbc 100644 --- a/packages/apps/job-launcher/client/src/services/job.ts +++ b/packages/apps/job-launcher/client/src/services/job.ts @@ -64,19 +64,21 @@ export const getJobList = async ({ chainId?: ChainId; status?: JobStatus; }) => { - const { data } = await api.get(`/job/list`, { - params: { - networks: chainId === ChainId.ALL ? SUPPORTED_CHAIN_IDS : chainId, - status, - }, - }); + const networks = chainId === ChainId.ALL ? SUPPORTED_CHAIN_IDS : [chainId]; + let queryString = networks.map((n) => `networks=${n}`).join('&'); + + if (status !== undefined) { + queryString += `&status=${status}`; + } + const { data } = await api.get(`/job/list?${queryString}`); return data; }; export const getJobResult = async (jobId: number) => { - const { data } = await api.get(`/job/result`, { - params: { jobId }, - }); + const { data } = await api.get( + `/job/result/${jobId}`, + {}, + ); return data; }; diff --git a/packages/apps/job-launcher/server/src/modules/job/job.controller.ts b/packages/apps/job-launcher/server/src/modules/job/job.controller.ts index 6a5f1a7b12..ccb323e2cd 100644 --- a/packages/apps/job-launcher/server/src/modules/job/job.controller.ts +++ b/packages/apps/job-launcher/server/src/modules/job/job.controller.ts @@ -236,12 +236,12 @@ export class JobController { status: 404, description: 'Not Found. Could not find the requested content.', }) - @Get('/result') + @Get('/result/:id') public async getResult( @Request() req: RequestWithUser, - @Query('job_id') jobId: number, + @Param() params: JobIdDto, ): Promise { - return this.jobService.getResult(req.user.id, jobId); + return this.jobService.getResult(req.user.id, params.id); } @ApiOperation({ diff --git a/packages/apps/job-launcher/server/src/modules/job/job.service.ts b/packages/apps/job-launcher/server/src/modules/job/job.service.ts index a9b16b9519..bf64de9e44 100644 --- a/packages/apps/job-launcher/server/src/modules/job/job.service.ts +++ b/packages/apps/job-launcher/server/src/modules/job/job.service.ts @@ -1165,11 +1165,17 @@ export class JobService { jobId, userId, ); + if (!jobEntity) { this.logger.log(ErrorJob.NotFound, JobService.name); throw new NotFoundException(ErrorJob.NotFound); } + if (!jobEntity.escrowAddress) { + this.logger.log(ErrorJob.ResultNotFound, JobService.name); + throw new NotFoundException(ErrorJob.ResultNotFound); + } + const signer = this.web3Service.getSigner(jobEntity.chainId); const escrowClient = await EscrowClient.build(signer); @@ -1358,7 +1364,9 @@ export class JobService { chainId, tokenAddress: escrow ? escrow.token : ethers.ZeroAddress, requesterAddress: signer.address, - fundAmount: escrow ? Number(escrow.totalFundedAmount) : 0, + fundAmount: escrow + ? Number(ethers.formatEther(escrow.totalFundedAmount)) + : 0, exchangeOracleAddress: escrow?.exchangeOracle || ethers.ZeroAddress, recordingOracleAddress: escrow?.recordingOracle || ethers.ZeroAddress, reputationOracleAddress: escrow?.reputationOracle || ethers.ZeroAddress, @@ -1417,13 +1425,13 @@ export class JobService { manifestUrl, manifestHash, balance: Number(ethers.formatEther(escrow?.balance || 0)), - paidOut: Number(escrow?.amountPaid || 0), + paidOut: Number(ethers.formatEther(escrow?.amountPaid || 0)), status: jobEntity.status, }, manifest: manifestDetails, staking: { staker: allocation?.staker as string, - allocated: Number(allocation?.tokens), + allocated: Number(ethers.formatEther(allocation?.tokens || 0)), slashed: 0, // TODO: Retrieve slash tokens }, }; From 01d56eb0636f1ecb233f29cbc13fcd1458225d1a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Francisco=20L=C3=B3pez?= <50665615+flopez7@users.noreply.github.com> Date: Mon, 8 Apr 2024 09:14:41 +0200 Subject: [PATCH 44/66] [Exchange Oracle] Improve ConfigService (#1811) * Improve config service in reputation oracle * Update s3-config.service.ts * Add missing cron job and stats tests * Add postgres_url for render deployments --- .../exchange-oracle/server/src/app.module.ts | 2 + .../server/src/common/config/config.module.ts | 29 ++ .../common/config/database-config.service.ts | 34 ++ .../server/src/common/config/env-schema.ts | 32 ++ .../server/src/common/config/env.ts | 63 ---- .../server/src/common/config/index.ts | 3 +- .../src/common/config/pgp-config.service.ts | 16 + .../src/common/config/s3-config.service.ts | 25 ++ .../server/src/common/config/s3.ts | 13 - .../common/config/server-config.service.ts | 22 ++ .../src/common/config/web3-config.service.ts | 13 + .../server/src/common/constant/index.ts | 1 - .../server/src/common/guards/cron.auth.ts | 11 +- .../server/src/database/database.module.ts | 56 +-- .../exchange-oracle/server/src/main.ts | 7 +- .../cron-job/cron-job.controller.spec.ts | 37 ++ ...b.controller.ts => cron-job.controller.ts} | 4 +- .../src/modules/cron-job/cron-job.module.ts | 2 +- .../modules/cron-job/cron-job.service.spec.ts | 350 ++++++++++++++++++ .../src/modules/job/job.service.spec.ts | 43 +-- .../server/src/modules/job/job.service.ts | 9 +- .../modules/stats/stats.controller.spec.ts | 4 +- .../src/modules/stats/stats.service.spec.ts | 66 ++++ .../src/modules/storage/storage.module.ts | 4 +- .../modules/storage/storage.service.spec.ts | 56 +-- .../src/modules/storage/storage.service.ts | 37 +- .../src/modules/web3/web3.service.spec.ts | 37 +- .../server/src/modules/web3/web3.service.ts | 13 +- .../src/modules/webhook/webhook.repository.ts | 13 +- .../modules/webhook/webhook.service.spec.ts | 55 +-- .../src/modules/webhook/webhook.service.ts | 22 +- scripts/fortune/.env.exco-server | 2 +- 32 files changed, 757 insertions(+), 324 deletions(-) create mode 100644 packages/apps/fortune/exchange-oracle/server/src/common/config/config.module.ts create mode 100644 packages/apps/fortune/exchange-oracle/server/src/common/config/database-config.service.ts create mode 100644 packages/apps/fortune/exchange-oracle/server/src/common/config/env-schema.ts delete mode 100644 packages/apps/fortune/exchange-oracle/server/src/common/config/env.ts create mode 100644 packages/apps/fortune/exchange-oracle/server/src/common/config/pgp-config.service.ts create mode 100644 packages/apps/fortune/exchange-oracle/server/src/common/config/s3-config.service.ts delete mode 100644 packages/apps/fortune/exchange-oracle/server/src/common/config/s3.ts create mode 100644 packages/apps/fortune/exchange-oracle/server/src/common/config/server-config.service.ts create mode 100644 packages/apps/fortune/exchange-oracle/server/src/common/config/web3-config.service.ts create mode 100644 packages/apps/fortune/exchange-oracle/server/src/modules/cron-job/cron-job.controller.spec.ts rename packages/apps/fortune/exchange-oracle/server/src/modules/cron-job/{cron.job.controller.ts => cron-job.controller.ts} (88%) create mode 100644 packages/apps/fortune/exchange-oracle/server/src/modules/cron-job/cron-job.service.spec.ts create mode 100644 packages/apps/fortune/exchange-oracle/server/src/modules/stats/stats.service.spec.ts diff --git a/packages/apps/fortune/exchange-oracle/server/src/app.module.ts b/packages/apps/fortune/exchange-oracle/server/src/app.module.ts index e9cd913a06..03c52cbe9c 100644 --- a/packages/apps/fortune/exchange-oracle/server/src/app.module.ts +++ b/packages/apps/fortune/exchange-oracle/server/src/app.module.ts @@ -14,6 +14,7 @@ import { StatsModule } from './modules/stats/stats.module'; import { AssignmentModule } from './modules/assignment/assignment.module'; import { CronJobModule } from './modules/cron-job/cron-job.module'; import { HealthModule } from './modules/health/health.module'; +import { EnvConfigModule } from './common/config/config.module'; @Module({ providers: [ @@ -42,6 +43,7 @@ import { HealthModule } from './modules/health/health.module'; validationSchema: envValidator, }), DatabaseModule, + EnvConfigModule, ], controllers: [AppController], }) diff --git a/packages/apps/fortune/exchange-oracle/server/src/common/config/config.module.ts b/packages/apps/fortune/exchange-oracle/server/src/common/config/config.module.ts new file mode 100644 index 0000000000..96a72b4c2a --- /dev/null +++ b/packages/apps/fortune/exchange-oracle/server/src/common/config/config.module.ts @@ -0,0 +1,29 @@ +import { Module, Global } from '@nestjs/common'; +import { ConfigService } from '@nestjs/config'; + +import { ServerConfigService } from './server-config.service'; +import { DatabaseConfigService } from './database-config.service'; +import { PGPConfigService } from './pgp-config.service'; +import { S3ConfigService } from './s3-config.service'; +import { Web3ConfigService } from './web3-config.service'; + +@Global() +@Module({ + providers: [ + ConfigService, + ServerConfigService, + DatabaseConfigService, + Web3ConfigService, + S3ConfigService, + PGPConfigService, + ], + exports: [ + ConfigService, + ServerConfigService, + DatabaseConfigService, + Web3ConfigService, + S3ConfigService, + PGPConfigService, + ], +}) +export class EnvConfigModule {} diff --git a/packages/apps/fortune/exchange-oracle/server/src/common/config/database-config.service.ts b/packages/apps/fortune/exchange-oracle/server/src/common/config/database-config.service.ts new file mode 100644 index 0000000000..7205f6b29a --- /dev/null +++ b/packages/apps/fortune/exchange-oracle/server/src/common/config/database-config.service.ts @@ -0,0 +1,34 @@ +import { Injectable } from '@nestjs/common'; +import { ConfigService } from '@nestjs/config'; + +@Injectable() +export class DatabaseConfigService { + constructor(private configService: ConfigService) {} + get url(): string | undefined { + return this.configService.get('POSTGRES_URL'); + } + get host(): string | undefined { + return this.configService.get('POSTGRES_HOST'); + } + get port(): number | undefined { + return this.configService.get('POSTGRES_PORT'); + } + get user(): string | undefined { + return this.configService.get('POSTGRES_USER'); + } + get password(): string | undefined { + return this.configService.get('POSTGRES_PASSWORD'); + } + get database(): string | undefined { + return this.configService.get( + 'POSTGRES_DATABASE', + 'exchange-oracle', + ); + } + get ssl(): boolean { + return this.configService.get('POSTGRES_SSL', 'false') === 'true'; + } + get logging(): string { + return this.configService.get('POSTGRES_LOGGING', ''); + } +} diff --git a/packages/apps/fortune/exchange-oracle/server/src/common/config/env-schema.ts b/packages/apps/fortune/exchange-oracle/server/src/common/config/env-schema.ts new file mode 100644 index 0000000000..39ae83f74a --- /dev/null +++ b/packages/apps/fortune/exchange-oracle/server/src/common/config/env-schema.ts @@ -0,0 +1,32 @@ +import * as Joi from 'joi'; + +export const envValidator = Joi.object({ + // General + NODE_ENV: Joi.string(), + HOST: Joi.string(), + PORT: Joi.string(), + MAX_RETRY_COUNT: Joi.number(), + CRON_SECRET: Joi.string().required(), + // Database + POSTGRES_HOST: Joi.string(), + POSTGRES_USER: Joi.string(), + POSTGRES_PASSWORD: Joi.string(), + POSTGRES_DATABASE: Joi.string(), + POSTGRES_PORT: Joi.string(), + POSTGRES_SSL: Joi.string(), + POSTGRES_LOGGING: Joi.string(), + // Web3 + WEB3_ENV: Joi.string(), + WEB3_PRIVATE_KEY: Joi.string().required(), + // S3 + S3_ENDPOINT: Joi.string(), + S3_PORT: Joi.string(), + S3_ACCESS_KEY: Joi.string().required(), + S3_SECRET_KEY: Joi.string().required(), + S3_BUCKET: Joi.string(), + S3_USE_SSL: Joi.string(), + // PGP + PGP_ENCRYPT: Joi.boolean(), + PGP_PRIVATE_KEY: Joi.string(), + PGP_PASSPHRASE: Joi.string(), +}); diff --git a/packages/apps/fortune/exchange-oracle/server/src/common/config/env.ts b/packages/apps/fortune/exchange-oracle/server/src/common/config/env.ts deleted file mode 100644 index 9075f4ee82..0000000000 --- a/packages/apps/fortune/exchange-oracle/server/src/common/config/env.ts +++ /dev/null @@ -1,63 +0,0 @@ -import * as Joi from 'joi'; - -export const ConfigNames = { - NODE_ENV: 'NODE_ENV', - HOST: 'HOST', - PORT: 'PORT', - WEB3_ENV: 'WEB3_ENV', - POSTGRES_URL: 'POSTGRES_URL', - POSTGRES_HOST: 'POSTGRES_HOST', - POSTGRES_USER: 'POSTGRES_USER', - POSTGRES_PASSWORD: 'POSTGRES_PASSWORD', - POSTGRES_DATABASE: 'POSTGRES_DATABASE', - POSTGRES_PORT: 'POSTGRES_PORT', - POSTGRES_SYNC: 'POSTGRES_SYNC', - POSTGRES_SSL: 'POSTGRES_SSL', - POSTGRES_LOGGING: 'POSTGRES_LOGGING', - MAX_RETRY_COUNT: 'MAX_RETRY_COUNT', - WEB3_PRIVATE_KEY: 'WEB3_PRIVATE_KEY', - S3_ENDPOINT: 'S3_ENDPOINT', - S3_PORT: 'S3_PORT', - S3_ACCESS_KEY: 'S3_ACCESS_KEY', - S3_SECRET_KEY: 'S3_SECRET_KEY', - S3_BUCKET: 'S3_BUCKET', - S3_USE_SSL: 'S3_USE_SSL', - PGP_ENCRYPT: 'PGP_ENCRYPT', - PGP_PRIVATE_KEY: 'ENCRYPTION_PRIVATE_KEY', - PGP_PASSPHRASE: 'PGP_PASSPHRASE', - CRON_SECRET: 'CRON_SECRET', -}; - -export const envValidator = Joi.object({ - // General - NODE_ENV: Joi.string().default('development'), - HOST: Joi.string().default('localhost'), - PORT: Joi.string().default(3002), - MAX_RETRY_COUNT: Joi.number().default(5), - WEB3_ENV: Joi.string().default('testnet'), - // Database - DB_TYPE: Joi.string().default('postgres'), - POSTGRES_URL: Joi.string().optional(), - POSTGRES_HOST: Joi.string().optional(), - POSTGRES_USER: Joi.string().optional(), - POSTGRES_PASSWORD: Joi.string().optional(), - POSTGRES_DATABASE: Joi.string().optional(), - POSTGRES_PORT: Joi.string().optional(), - POSTGRES_SYNC: Joi.string().default('false'), - POSTGRES_SSL: Joi.string().default('false'), - POSTGRES_LOGGING: Joi.string(), - // Web3 - WEB3_PRIVATE_KEY: Joi.string().required(), - // S3 - S3_ENDPOINT: Joi.string().default('127.0.0.1'), - S3_PORT: Joi.string().default(9000), - S3_ACCESS_KEY: Joi.string().required(), - S3_SECRET_KEY: Joi.string().required(), - S3_BUCKET: Joi.string().default('solution'), - S3_USE_SSL: Joi.string().default(false), - PGP_ENCRYPT: Joi.boolean().default(false), - PGP_PRIVATE_KEY: Joi.string().optional(), - PGP_PASSPHRASE: Joi.string().optional(), - // Cron Job Secret - CRON_SECRET: Joi.string().optional(), -}); diff --git a/packages/apps/fortune/exchange-oracle/server/src/common/config/index.ts b/packages/apps/fortune/exchange-oracle/server/src/common/config/index.ts index 20c54299ce..8b28b497be 100644 --- a/packages/apps/fortune/exchange-oracle/server/src/common/config/index.ts +++ b/packages/apps/fortune/exchange-oracle/server/src/common/config/index.ts @@ -1,3 +1,2 @@ -export * from './env'; +export * from './env-schema'; export * from './networks'; -export * from './s3'; diff --git a/packages/apps/fortune/exchange-oracle/server/src/common/config/pgp-config.service.ts b/packages/apps/fortune/exchange-oracle/server/src/common/config/pgp-config.service.ts new file mode 100644 index 0000000000..a6ec43a0df --- /dev/null +++ b/packages/apps/fortune/exchange-oracle/server/src/common/config/pgp-config.service.ts @@ -0,0 +1,16 @@ +import { Injectable } from '@nestjs/common'; +import { ConfigService } from '@nestjs/config'; + +@Injectable() +export class PGPConfigService { + constructor(private configService: ConfigService) {} + get encrypt(): boolean { + return this.configService.get('PGP_ENCRYPT', 'false') === 'true'; + } + get privateKey(): string { + return this.configService.get('PGP_PRIVATE_KEY', ''); + } + get passphrase(): string { + return this.configService.get('PGP_PASSPHRASE', ''); + } +} diff --git a/packages/apps/fortune/exchange-oracle/server/src/common/config/s3-config.service.ts b/packages/apps/fortune/exchange-oracle/server/src/common/config/s3-config.service.ts new file mode 100644 index 0000000000..ced3e89cfc --- /dev/null +++ b/packages/apps/fortune/exchange-oracle/server/src/common/config/s3-config.service.ts @@ -0,0 +1,25 @@ +import { Injectable } from '@nestjs/common'; +import { ConfigService } from '@nestjs/config'; + +@Injectable() +export class S3ConfigService { + constructor(private configService: ConfigService) {} + get endpoint(): string { + return this.configService.get('S3_ENDPOINT', '127.0.0.1'); + } + get port(): number { + return +this.configService.get('S3_PORT', 9000); + } + get accessKey(): string { + return this.configService.get('S3_ACCESS_KEY', ''); + } + get secretKey(): string { + return this.configService.get('S3_SECRET_KEY', ''); + } + get bucket(): string { + return this.configService.get('S3_BUCKET', 'exchange'); + } + get useSSL(): boolean { + return this.configService.get('S3_USE_SSL', 'false') === 'true'; + } +} diff --git a/packages/apps/fortune/exchange-oracle/server/src/common/config/s3.ts b/packages/apps/fortune/exchange-oracle/server/src/common/config/s3.ts deleted file mode 100644 index b2d4e3731b..0000000000 --- a/packages/apps/fortune/exchange-oracle/server/src/common/config/s3.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { ConfigType, registerAs } from '@nestjs/config'; - -export const s3Config = registerAs('s3', () => ({ - endPoint: process.env.S3_ENDPOINT!, - port: +process.env.S3_PORT!, - accessKey: process.env.S3_ACCESS_KEY!, - secretKey: process.env.S3_SECRET_KEY!, - bucket: process.env.S3_BUCKET!, - useSSL: process.env.S3_USE_SSL === 'true', -})); - -export const s3ConfigKey = s3Config.KEY; -export type S3ConfigType = ConfigType; diff --git a/packages/apps/fortune/exchange-oracle/server/src/common/config/server-config.service.ts b/packages/apps/fortune/exchange-oracle/server/src/common/config/server-config.service.ts new file mode 100644 index 0000000000..fe72a1be91 --- /dev/null +++ b/packages/apps/fortune/exchange-oracle/server/src/common/config/server-config.service.ts @@ -0,0 +1,22 @@ +import { Injectable } from '@nestjs/common'; +import { ConfigService } from '@nestjs/config'; + +@Injectable() +export class ServerConfigService { + constructor(private configService: ConfigService) {} + get nodeEnv(): string { + return this.configService.get('NODE_ENV', 'development'); + } + get host(): string { + return this.configService.get('HOST', 'localhost'); + } + get port(): number { + return +this.configService.get('PORT', 5001); + } + get maxRetryCount(): number { + return +this.configService.get('MAX_RETRY_COUNT', 5); + } + get cronSecret(): string { + return this.configService.get('CRON_SECRET', ''); + } +} diff --git a/packages/apps/fortune/exchange-oracle/server/src/common/config/web3-config.service.ts b/packages/apps/fortune/exchange-oracle/server/src/common/config/web3-config.service.ts new file mode 100644 index 0000000000..84c4abdafe --- /dev/null +++ b/packages/apps/fortune/exchange-oracle/server/src/common/config/web3-config.service.ts @@ -0,0 +1,13 @@ +import { Injectable } from '@nestjs/common'; +import { ConfigService } from '@nestjs/config'; + +@Injectable() +export class Web3ConfigService { + constructor(private configService: ConfigService) {} + get env(): string { + return this.configService.get('WEB3_ENV', 'testnet'); + } + get privateKey(): string { + return this.configService.get('WEB3_PRIVATE_KEY', ''); + } +} diff --git a/packages/apps/fortune/exchange-oracle/server/src/common/constant/index.ts b/packages/apps/fortune/exchange-oracle/server/src/common/constant/index.ts index 8e2144a097..bef3a34617 100644 --- a/packages/apps/fortune/exchange-oracle/server/src/common/constant/index.ts +++ b/packages/apps/fortune/exchange-oracle/server/src/common/constant/index.ts @@ -3,7 +3,6 @@ import { ChainId } from '@human-protocol/sdk'; export const HEADER_SIGNATURE_KEY = 'human-signature'; export const NS = 'hmt'; export const TOKEN = 'HMT'; -export const DEFAULT_MAX_RETRY_COUNT = 5; export const LOCALHOST_CHAIN_IDS = [ChainId.LOCALHOST]; diff --git a/packages/apps/fortune/exchange-oracle/server/src/common/guards/cron.auth.ts b/packages/apps/fortune/exchange-oracle/server/src/common/guards/cron.auth.ts index b368476bbf..3fae0231ff 100644 --- a/packages/apps/fortune/exchange-oracle/server/src/common/guards/cron.auth.ts +++ b/packages/apps/fortune/exchange-oracle/server/src/common/guards/cron.auth.ts @@ -4,19 +4,18 @@ import { Injectable, UnauthorizedException, } from '@nestjs/common'; -import { ConfigService } from '@nestjs/config'; -import { ConfigNames } from '../config'; + +import { ServerConfigService } from '../config/server-config.service'; @Injectable() export class CronAuthGuard implements CanActivate { - constructor(private readonly configService: ConfigService) {} + constructor(private serverConfigService: ServerConfigService) {} public async canActivate(context: ExecutionContext): Promise { const request = context.switchToHttp().getRequest(); - const cronSecret = this.configService.get(ConfigNames.CRON_SECRET); if ( - cronSecret && - request.headers['authorization'] === `Bearer ${cronSecret}` + request.headers['authorization'] !== + `Bearer ${this.serverConfigService.cronSecret}` ) { throw new UnauthorizedException('Unauthorized'); } diff --git a/packages/apps/fortune/exchange-oracle/server/src/database/database.module.ts b/packages/apps/fortune/exchange-oracle/server/src/database/database.module.ts index f392e7165a..877b042720 100644 --- a/packages/apps/fortune/exchange-oracle/server/src/database/database.module.ts +++ b/packages/apps/fortune/exchange-oracle/server/src/database/database.module.ts @@ -1,5 +1,5 @@ import { Module } from '@nestjs/common'; -import { ConfigModule, ConfigService } from '@nestjs/config'; +import { ConfigModule } from '@nestjs/config'; import { TypeOrmModule } from '@nestjs/typeorm'; import * as path from 'path'; import { SnakeNamingStrategy } from 'typeorm-naming-strategies'; @@ -7,24 +7,28 @@ import { NS } from '../common/constant'; import { TypeOrmLoggerModule, TypeOrmLoggerService } from './typeorm'; import { LoggerOptions } from 'typeorm'; -import { ConfigNames } from '../common/config'; import { JobEntity } from '../modules/job/job.entity'; import { AssignmentEntity } from '../modules/assignment/assignment.entity'; import { WebhookEntity } from '../modules/webhook/webhook.entity'; import { CronJobEntity } from '../modules/cron-job/cron-job.entity'; +import { DatabaseConfigService } from '../common/config/database-config.service'; +import { ServerConfigService } from '../common/config/server-config.service'; @Module({ imports: [ TypeOrmModule.forRootAsync({ imports: [TypeOrmLoggerModule, ConfigModule], - inject: [TypeOrmLoggerService, ConfigService], + inject: [ + TypeOrmLoggerService, + DatabaseConfigService, + ServerConfigService, + ], useFactory: ( typeOrmLoggerService: TypeOrmLoggerService, - configService: ConfigService, + databaseConfigService: DatabaseConfigService, + serverConfigService: ServerConfigService, ) => { - const loggerOptions = configService - .get(ConfigNames.POSTGRES_LOGGING) - ?.split(', '); + const loggerOptions = databaseConfigService.logging.split(', '); typeOrmLoggerService.setOptions( loggerOptions && loggerOptions[0] === 'all' ? 'all' @@ -48,37 +52,15 @@ import { CronJobEntity } from '../modules/cron-job/cron-job.entity'; migrations: [path.join(__dirname, '/migrations/**/*{.ts,.js}')], //"migrations": ["dist/migrations/*{.ts,.js}"], logger: typeOrmLoggerService, - url: configService.get( - ConfigNames.POSTGRES_URL, - undefined, - ), - host: configService.get( - ConfigNames.POSTGRES_HOST, - undefined, - ), - port: configService.get( - ConfigNames.POSTGRES_PORT, - undefined, - ), - username: configService.get( - ConfigNames.POSTGRES_USER, - undefined, - ), - password: configService.get( - ConfigNames.POSTGRES_PASSWORD, - undefined, - ), - database: configService.get( - ConfigNames.POSTGRES_DATABASE, - undefined, - ), - keepConnectionAlive: - configService.get(ConfigNames.NODE_ENV) === 'test', + url: databaseConfigService.url, + host: databaseConfigService.host, + port: databaseConfigService.port, + username: databaseConfigService.user, + password: databaseConfigService.password, + database: databaseConfigService.database, + keepConnectionAlive: serverConfigService.nodeEnv === 'test', migrationsRun: false, - ssl: - configService - .get(ConfigNames.POSTGRES_SSL) - ?.toLowerCase() === 'true', + ssl: databaseConfigService.ssl, }; }, }), diff --git a/packages/apps/fortune/exchange-oracle/server/src/main.ts b/packages/apps/fortune/exchange-oracle/server/src/main.ts index ed46be64d0..2683f7c886 100644 --- a/packages/apps/fortune/exchange-oracle/server/src/main.ts +++ b/packages/apps/fortune/exchange-oracle/server/src/main.ts @@ -5,8 +5,8 @@ import { json, urlencoded } from 'body-parser'; import { useContainer } from 'class-validator'; import { AppModule } from './app.module'; -import { ConfigNames } from './common/config'; import { INestApplication, ValidationPipe } from '@nestjs/common'; +import { ServerConfigService } from './common/config/server-config.service'; async function bootstrap() { const app = await NestFactory.create(AppModule, { @@ -14,9 +14,10 @@ async function bootstrap() { }); const configService: ConfigService = app.get(ConfigService); + const serverConfigService = new ServerConfigService(configService); - const host = configService.get(ConfigNames.HOST)!; - const port = configService.get(ConfigNames.PORT)!; + const host = serverConfigService.host; + const port = serverConfigService.port; // app.enableCors({ // origin: diff --git a/packages/apps/fortune/exchange-oracle/server/src/modules/cron-job/cron-job.controller.spec.ts b/packages/apps/fortune/exchange-oracle/server/src/modules/cron-job/cron-job.controller.spec.ts new file mode 100644 index 0000000000..5c439bc324 --- /dev/null +++ b/packages/apps/fortune/exchange-oracle/server/src/modules/cron-job/cron-job.controller.spec.ts @@ -0,0 +1,37 @@ +import { createMock } from '@golevelup/ts-jest'; +import { ConfigService } from '@nestjs/config'; +import { Test } from '@nestjs/testing'; +import { ServerConfigService } from '../../common/config/server-config.service'; +import { CronJobController } from './cron-job.controller'; +import { CronJobService } from './cron-job.service'; + +jest.mock('../../common/utils/signature'); + +describe('cronJobController', () => { + let cronJobController: CronJobController; + let cronJobService: CronJobService; + + beforeAll(async () => { + const moduleRef = await Test.createTestingModule({ + imports: [], + controllers: [CronJobController], + providers: [ + { provide: CronJobService, useValue: createMock() }, + ConfigService, + ServerConfigService, + ], + }).compile(); + + cronJobController = moduleRef.get(CronJobController); + cronJobService = moduleRef.get(CronJobService); + }); + + describe('processPendingWebhooks', () => { + it('should call cronJobService.processPendingWebhooks', async () => { + jest.spyOn(cronJobService, 'processPendingWebhooks').mockResolvedValue(); + const result = await cronJobController.processPendingWebhooks(); + expect(result).toBe(undefined); + expect(cronJobService.processPendingWebhooks).toHaveBeenCalledWith(); + }); + }); +}); diff --git a/packages/apps/fortune/exchange-oracle/server/src/modules/cron-job/cron.job.controller.ts b/packages/apps/fortune/exchange-oracle/server/src/modules/cron-job/cron-job.controller.ts similarity index 88% rename from packages/apps/fortune/exchange-oracle/server/src/modules/cron-job/cron.job.controller.ts rename to packages/apps/fortune/exchange-oracle/server/src/modules/cron-job/cron-job.controller.ts index 9f9d82d989..6ffa2ce637 100644 --- a/packages/apps/fortune/exchange-oracle/server/src/modules/cron-job/cron.job.controller.ts +++ b/packages/apps/fortune/exchange-oracle/server/src/modules/cron-job/cron-job.controller.ts @@ -7,7 +7,9 @@ import { } from '@nestjs/swagger'; import { CronJobService } from './cron-job.service'; import { CronAuthGuard } from '../../common/guards/cron.auth'; +import { Public } from '../../common/decorators'; +@Public() @UseGuards(CronAuthGuard) @ApiTags('Cron') @Controller('/cron') @@ -28,7 +30,7 @@ export class CronJobController { }) @ApiBearerAuth() @Get('/webhook/process') - public async processPendingWebhooks(): Promise { + public async processPendingWebhooks(): Promise { await this.cronJobService.processPendingWebhooks(); return; } diff --git a/packages/apps/fortune/exchange-oracle/server/src/modules/cron-job/cron-job.module.ts b/packages/apps/fortune/exchange-oracle/server/src/modules/cron-job/cron-job.module.ts index 8fada5ec84..0c9e972539 100644 --- a/packages/apps/fortune/exchange-oracle/server/src/modules/cron-job/cron-job.module.ts +++ b/packages/apps/fortune/exchange-oracle/server/src/modules/cron-job/cron-job.module.ts @@ -7,7 +7,7 @@ import { WebhookRepository } from '../webhook/webhook.repository'; import { CronJobEntity } from './cron-job.entity'; import { CronJobRepository } from './cron-job.repository'; import { CronJobService } from './cron-job.service'; -import { CronJobController } from './cron.job.controller'; +import { CronJobController } from './cron-job.controller'; @Global() @Module({ diff --git a/packages/apps/fortune/exchange-oracle/server/src/modules/cron-job/cron-job.service.spec.ts b/packages/apps/fortune/exchange-oracle/server/src/modules/cron-job/cron-job.service.spec.ts new file mode 100644 index 0000000000..ba84696341 --- /dev/null +++ b/packages/apps/fortune/exchange-oracle/server/src/modules/cron-job/cron-job.service.spec.ts @@ -0,0 +1,350 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { CronJobType } from '../../common/enums/cron-job'; +import { createMock } from '@golevelup/ts-jest'; +import { ChainId, Encryption } from '@human-protocol/sdk'; +import { HttpService } from '@nestjs/axios'; +import { ConfigService } from '@nestjs/config'; +import { MOCK_ADDRESS } from '../../../test/constants'; +import { PGPConfigService } from '../../common/config/pgp-config.service'; +import { ServerConfigService } from '../../common/config/server-config.service'; +import { Web3ConfigService } from '../../common/config/web3-config.service'; +import { WebhookStatus } from '../../common/enums/webhook'; +import { JobRepository } from '../job/job.repository'; +import { JobService } from '../job/job.service'; +import { StorageService } from '../storage/storage.service'; +import { Web3Service } from '../web3/web3.service'; +import { WebhookEntity } from '../webhook/webhook.entity'; +import { WebhookRepository } from '../webhook/webhook.repository'; +import { WebhookService } from '../webhook/webhook.service'; +import { CronJobEntity } from './cron-job.entity'; +import { CronJobRepository } from './cron-job.repository'; +import { CronJobService } from './cron-job.service'; +import { AssignmentRepository } from '../assignment/assignment.repository'; + +jest.mock('@human-protocol/sdk', () => ({ + ...jest.requireActual('@human-protocol/sdk'), + EscrowClient: { + build: jest.fn().mockImplementation(() => ({ + createEscrow: jest.fn().mockResolvedValue(MOCK_ADDRESS), + setup: jest.fn().mockResolvedValue(null), + fund: jest.fn().mockResolvedValue(null), + })), + }, + KVStoreClient: { + build: jest.fn().mockImplementation(() => ({ + get: jest.fn(), + })), + }, +})); + +describe('CronJobService', () => { + let service: CronJobService, + repository: CronJobRepository, + webhookService: WebhookService, + webhookRepository: WebhookRepository, + serverConfigService: ServerConfigService; + + const signerMock = { + address: MOCK_ADDRESS, + getNetwork: jest.fn().mockResolvedValue({ chainId: 1 }), + }; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + providers: [ + CronJobService, + { + provide: CronJobRepository, + useValue: createMock(), + }, + { + provide: Web3Service, + useValue: { + getSigner: jest.fn().mockReturnValue(signerMock), + validateChainId: jest.fn().mockReturnValue(new Error()), + }, + }, + JobService, + WebhookService, + Encryption, + ConfigService, + ServerConfigService, + Web3ConfigService, + PGPConfigService, + { provide: JobRepository, useValue: createMock() }, + { provide: StorageService, useValue: createMock() }, + { + provide: AssignmentRepository, + useValue: createMock(), + }, + { + provide: WebhookRepository, + useValue: createMock(), + }, + { provide: HttpService, useValue: createMock() }, + ], + }).compile(); + + service = module.get(CronJobService); + repository = module.get(CronJobRepository); + webhookService = module.get(WebhookService); + webhookRepository = module.get(WebhookRepository); + serverConfigService = module.get(ServerConfigService); + }); + + describe('startCronJob', () => { + it('should create a cron job if not exists', async () => { + const cronJobType = CronJobType.ProcessPendingWebhook; + + const cronJobEntity = new CronJobEntity(); + cronJobEntity.cronJobType = cronJobType; + cronJobEntity.startedAt = new Date(); + + jest.spyOn(repository, 'findOneByType').mockResolvedValue(null); + + const createUniqueSpy = jest + .spyOn(repository, 'createUnique') + .mockResolvedValue(cronJobEntity); + + const result = await service.startCronJob(cronJobType); + + expect(createUniqueSpy).toHaveBeenCalledWith({ + cronJobType: CronJobType.ProcessPendingWebhook, + }); + expect(result).toEqual(cronJobEntity); + }); + + it('should start a cron job if exists', async () => { + const cronJobType = CronJobType.ProcessPendingWebhook; + const cronJobEntity: Partial = { + cronJobType: cronJobType, + startedAt: new Date(), + completedAt: new Date(), + }; + + const mockDate = new Date(2023, 12, 23); + jest.useFakeTimers(); + jest.setSystemTime(mockDate); + + const findOneByTypeSpy = jest + .spyOn(repository, 'findOneByType') + .mockResolvedValue(cronJobEntity as any); + + const updateOneSpy = jest + .spyOn(repository, 'updateOne') + .mockResolvedValue(cronJobEntity as any); + + const result = await service.startCronJob(cronJobType); + + expect(findOneByTypeSpy).toHaveBeenCalledWith(cronJobType); + expect(updateOneSpy).toHaveBeenCalled(); + cronJobEntity.startedAt = mockDate; + expect(result).toEqual(cronJobEntity); + + jest.useRealTimers(); + }); + }); + + describe('isCronJobRunning', () => { + it('should return false if no cron job is running', async () => { + const cronJobType = CronJobType.ProcessPendingWebhook; + const cronJobEntity = new CronJobEntity(); + cronJobEntity.cronJobType = cronJobType; + + const findOneByTypeSpy = jest + .spyOn(repository, 'findOneByType') + .mockResolvedValue(null); + + const result = await service.isCronJobRunning(cronJobType); + + expect(findOneByTypeSpy).toHaveBeenCalledWith(cronJobType); + expect(result).toEqual(false); + }); + + it('should return false if last cron job is completed', async () => { + const cronJobType = CronJobType.ProcessPendingWebhook; + const cronJobEntity = new CronJobEntity(); + cronJobEntity.cronJobType = cronJobType; + cronJobEntity.completedAt = new Date(); + + const findOneByTypeSpy = jest + .spyOn(repository, 'findOneByType') + .mockResolvedValue(cronJobEntity); + + const result = await service.isCronJobRunning(cronJobType); + + expect(findOneByTypeSpy).toHaveBeenCalledWith(cronJobType); + expect(result).toEqual(false); + }); + + it('should return true if last cron job is not completed', async () => { + const cronJobType = CronJobType.ProcessPendingWebhook; + const cronJobEntity = new CronJobEntity(); + cronJobEntity.cronJobType = cronJobType; + + const findOneByTypeSpy = jest + .spyOn(repository, 'findOneByType') + .mockResolvedValue(cronJobEntity); + + const result = await service.isCronJobRunning(cronJobType); + expect(findOneByTypeSpy).toHaveBeenCalledWith(cronJobType); + expect(result).toEqual(true); + }); + }); + + describe('completeCronJob', () => { + it('should complete a cron job', async () => { + const cronJobType = CronJobType.ProcessPendingWebhook; + const cronJobEntity = new CronJobEntity(); + cronJobEntity.cronJobType = cronJobType; + + const mockDate = new Date(2023, 12, 23); + + jest.useFakeTimers(); + jest.setSystemTime(mockDate); + + const updateOneSpy = jest + .spyOn(repository, 'updateOne') + .mockResolvedValue(cronJobEntity); + + const result = await service.completeCronJob(cronJobEntity); + + expect(updateOneSpy).toHaveBeenCalled(); + expect(cronJobEntity.completedAt).toEqual(mockDate); + expect(result).toEqual(cronJobEntity); + + jest.useRealTimers(); + }); + + it('should throw an error if cron job is already completed', async () => { + const cronJobType = CronJobType.ProcessPendingWebhook; + const cronJobEntity = new CronJobEntity(); + cronJobEntity.cronJobType = cronJobType; + cronJobEntity.completedAt = new Date(); + + const updateOneSpy = jest + .spyOn(repository, 'updateOne') + .mockResolvedValue(cronJobEntity); + + await expect(service.completeCronJob(cronJobEntity)).rejects.toThrow(); + expect(updateOneSpy).not.toHaveBeenCalled(); + }); + }); + + describe('processPendingCronJob', () => { + let sendWebhookMock: any; + let cronJobEntityMock: Partial; + let webhookEntity1: Partial, + webhookEntity2: Partial; + + beforeEach(() => { + cronJobEntityMock = { + cronJobType: CronJobType.ProcessPendingWebhook, + startedAt: new Date(), + }; + + webhookEntity1 = { + id: 1, + chainId: ChainId.LOCALHOST, + escrowAddress: MOCK_ADDRESS, + status: WebhookStatus.PENDING, + waitUntil: new Date(), + retriesCount: 0, + }; + + webhookEntity2 = { + id: 2, + chainId: ChainId.LOCALHOST, + escrowAddress: MOCK_ADDRESS, + status: WebhookStatus.PENDING, + waitUntil: new Date(), + retriesCount: 0, + }; + + jest + .spyOn(webhookRepository, 'findByStatus') + .mockResolvedValue([webhookEntity1 as any, webhookEntity2 as any]); + + sendWebhookMock = jest.spyOn(webhookService as any, 'sendWebhook'); + sendWebhookMock.mockResolvedValue(true); + + jest.spyOn(service, 'isCronJobRunning').mockResolvedValue(false); + + jest.spyOn(repository, 'findOneByType').mockResolvedValue(null); + jest + .spyOn(repository, 'createUnique') + .mockResolvedValue(cronJobEntityMock as any); + }); + + afterEach(() => { + jest.restoreAllMocks(); + }); + + it('should not run if cron job is already running', async () => { + jest.spyOn(service, 'isCronJobRunning').mockResolvedValueOnce(true); + + const startCronJobMock = jest.spyOn(service, 'startCronJob'); + + await service.processPendingWebhooks(); + + expect(startCronJobMock).not.toHaveBeenCalled(); + }); + + it('should create cron job entity to lock the process', async () => { + jest + .spyOn(service, 'startCronJob') + .mockResolvedValueOnce(cronJobEntityMock as any); + + await service.processPendingWebhooks(); + + expect(service.startCronJob).toHaveBeenCalledWith( + CronJobType.ProcessPendingWebhook, + ); + }); + + it('should send webhook for all of the pending webhooks', async () => { + await service.processPendingWebhooks(); + + expect(sendWebhookMock).toHaveBeenCalledTimes(2); + expect(sendWebhookMock).toHaveBeenCalledWith(webhookEntity1); + expect(sendWebhookMock).toHaveBeenCalledWith(webhookEntity2); + + expect(webhookRepository.updateOne).toHaveBeenCalledTimes(2); + expect(webhookEntity1.status).toBe(WebhookStatus.COMPLETED); + expect(webhookEntity2.status).toBe(WebhookStatus.COMPLETED); + }); + + it('should increase retriesCount by 1 if sending webhook fails', async () => { + sendWebhookMock.mockRejectedValueOnce(new Error()); + await service.processPendingWebhooks(); + + expect(webhookRepository.updateOne).toHaveBeenCalled(); + expect(webhookEntity1.status).toBe(WebhookStatus.PENDING); + expect(webhookEntity1.retriesCount).toBe(1); + expect(webhookEntity1.waitUntil).toBeInstanceOf(Date); + }); + + it('should mark webhook as failed if retriesCount exceeds threshold', async () => { + sendWebhookMock.mockRejectedValueOnce(new Error()); + + webhookEntity1.retriesCount = serverConfigService.maxRetryCount; + + await service.processPendingWebhooks(); + + expect(webhookRepository.updateOne).toHaveBeenCalled(); + expect(webhookEntity1.status).toBe(WebhookStatus.FAILED); + }); + + it('should complete the cron job entity to unlock', async () => { + jest + .spyOn(service, 'completeCronJob') + .mockResolvedValueOnce(cronJobEntityMock as any); + + await service.processPendingWebhooks(); + + expect(service.completeCronJob).toHaveBeenCalledWith( + cronJobEntityMock as any, + ); + }); + }); +}); diff --git a/packages/apps/fortune/exchange-oracle/server/src/modules/job/job.service.spec.ts b/packages/apps/fortune/exchange-oracle/server/src/modules/job/job.service.spec.ts index de84fd502c..b19b65d6d7 100644 --- a/packages/apps/fortune/exchange-oracle/server/src/modules/job/job.service.spec.ts +++ b/packages/apps/fortune/exchange-oracle/server/src/modules/job/job.service.spec.ts @@ -6,19 +6,10 @@ import { StorageClient, } from '@human-protocol/sdk'; import { HttpService } from '@nestjs/axios'; -import { ConfigModule, ConfigService, registerAs } from '@nestjs/config'; +import { ConfigService } from '@nestjs/config'; import { Test } from '@nestjs/testing'; import { of } from 'rxjs'; -import { - MOCK_MANIFEST_URL, - MOCK_PRIVATE_KEY, - MOCK_S3_ACCESS_KEY, - MOCK_S3_BUCKET, - MOCK_S3_ENDPOINT, - MOCK_S3_PORT, - MOCK_S3_SECRET_KEY, - MOCK_S3_USE_SSL, -} from '../../../test/constants'; +import { MOCK_MANIFEST_URL } from '../../../test/constants'; import { AssignmentStatus, JobFieldName, @@ -36,6 +27,8 @@ import { ManifestDto } from './job.dto'; import { JobEntity } from './job.entity'; import { JobRepository } from './job.repository'; import { JobService } from './job.service'; +import { PGPConfigService } from '../../common/config/pgp-config.service'; +import { S3ConfigService } from '../../common/config/s3-config.service'; jest.mock('@human-protocol/sdk', () => ({ ...jest.requireActual('@human-protocol/sdk'), @@ -84,40 +77,18 @@ describe('JobService', () => { getNetwork: jest.fn().mockResolvedValue({ chainId: 1 }), }; - const configServiceMock: Partial = { - get: jest.fn((key: string) => { - switch (key) { - case 'WEB3_PRIVATE_KEY': - return MOCK_PRIVATE_KEY; - } - }), - }; - const httpServicePostMock = jest .fn() .mockReturnValue(of({ status: 200, data: {} })); beforeAll(async () => { const moduleRef = await Test.createTestingModule({ - imports: [ - ConfigModule.forFeature( - registerAs('s3', () => ({ - accessKey: MOCK_S3_ACCESS_KEY, - secretKey: MOCK_S3_SECRET_KEY, - endPoint: MOCK_S3_ENDPOINT, - port: MOCK_S3_PORT, - useSSL: MOCK_S3_USE_SSL, - bucket: MOCK_S3_BUCKET, - })), - ), - ], providers: [ JobService, StorageService, - { - provide: ConfigService, - useValue: configServiceMock, - }, + ConfigService, + PGPConfigService, + S3ConfigService, { provide: Web3Service, useValue: { diff --git a/packages/apps/fortune/exchange-oracle/server/src/modules/job/job.service.ts b/packages/apps/fortune/exchange-oracle/server/src/modules/job/job.service.ts index f6e3c230ee..d8643e3928 100644 --- a/packages/apps/fortune/exchange-oracle/server/src/modules/job/job.service.ts +++ b/packages/apps/fortune/exchange-oracle/server/src/modules/job/job.service.ts @@ -12,9 +12,7 @@ import { Logger, NotFoundException, } from '@nestjs/common'; -import { ConfigService } from '@nestjs/config'; import { ethers } from 'ethers'; -import { ConfigNames } from '../../common/config'; import { TOKEN } from '../../common/constant'; import { AssignmentStatus, @@ -34,13 +32,14 @@ import { GetJobsDto, JobDto, ManifestDto } from './job.dto'; import { JobEntity } from './job.entity'; import { JobRepository } from './job.repository'; import { AssignmentRepository } from '../assignment/assignment.repository'; +import { PGPConfigService } from '../../common/config/pgp-config.service'; @Injectable() export class JobService { public readonly logger = new Logger(JobService.name); constructor( - private readonly configService: ConfigService, + private readonly pgpConfigService: PGPConfigService, public readonly jobRepository: JobRepository, public readonly assignmentRepository: AssignmentRepository, @Inject(Web3Service) @@ -253,8 +252,8 @@ export class JobService { ) { try { const encryption = await Encryption.build( - this.configService.get(ConfigNames.PGP_PRIVATE_KEY, ''), - this.configService.get(ConfigNames.PGP_PASSPHRASE), + this.pgpConfigService.privateKey, + this.pgpConfigService.passphrase, ); manifest = JSON.parse(await encryption.decrypt(manifestEncrypted)); diff --git a/packages/apps/fortune/exchange-oracle/server/src/modules/stats/stats.controller.spec.ts b/packages/apps/fortune/exchange-oracle/server/src/modules/stats/stats.controller.spec.ts index 5923a6c8d6..6ff83c866a 100644 --- a/packages/apps/fortune/exchange-oracle/server/src/modules/stats/stats.controller.spec.ts +++ b/packages/apps/fortune/exchange-oracle/server/src/modules/stats/stats.controller.spec.ts @@ -25,7 +25,7 @@ describe('statsController', () => { statsService = moduleRef.get(StatsService); }); - describe('processWebhook', () => { + describe('getOracleStats', () => { it('should call statsService.getOracleStats', async () => { const stats = new OracleStatsDto(); jest.spyOn(statsService, 'getOracleStats').mockResolvedValue(stats); @@ -33,7 +33,9 @@ describe('statsController', () => { expect(result).toBe(stats); expect(statsService.getOracleStats).toHaveBeenCalledWith(); }); + }); + describe('getAssignmentStats', () => { it('should call statsService.getAssignmentStats', async () => { const stats = new AssignmentStatsDto(); jest.spyOn(statsService, 'getAssignmentStats').mockResolvedValue(stats); diff --git a/packages/apps/fortune/exchange-oracle/server/src/modules/stats/stats.service.spec.ts b/packages/apps/fortune/exchange-oracle/server/src/modules/stats/stats.service.spec.ts new file mode 100644 index 0000000000..2adddf90a5 --- /dev/null +++ b/packages/apps/fortune/exchange-oracle/server/src/modules/stats/stats.service.spec.ts @@ -0,0 +1,66 @@ +import { createMock } from '@golevelup/ts-jest'; +import { Test } from '@nestjs/testing'; +import { StatsService } from './stats.service'; +import { AssignmentRepository } from '../assignment/assignment.repository'; + +jest.mock('../../common/utils/signature'); + +describe('statsService', () => { + let statsService: StatsService; + let assignmentRepository: AssignmentRepository; + const userAddress = '0x1234567890123456789012345678901234567890'; + + beforeAll(async () => { + const moduleRef = await Test.createTestingModule({ + imports: [], + controllers: [StatsService], + providers: [ + { + provide: AssignmentRepository, + useValue: createMock(), + }, + ], + }).compile(); + + statsService = moduleRef.get(StatsService); + assignmentRepository = + moduleRef.get(AssignmentRepository); + }); + + describe('getOracleStats', () => { + it('should call assignmentRepository', async () => { + await statsService.getOracleStats(); + expect(assignmentRepository.countTotalWorkers).toHaveBeenCalledWith(); + expect( + assignmentRepository.countCompletedAssignments, + ).toHaveBeenCalledWith(); + expect( + assignmentRepository.countExpiredAssignments, + ).toHaveBeenCalledWith(); + expect( + assignmentRepository.countRejectedAssignments, + ).toHaveBeenCalledWith(); + }); + }); + + describe('getAssignmentStats', () => { + it('should call assignmentR.getAssignmentStats', async () => { + await statsService.getAssignmentStats(userAddress); + expect(assignmentRepository.countTotalAssignments).toHaveBeenCalledWith( + userAddress, + ); + expect(assignmentRepository.countSentAssignments).toHaveBeenCalledWith( + userAddress, + ); + expect( + assignmentRepository.countCompletedAssignments, + ).toHaveBeenCalledWith(userAddress); + expect(assignmentRepository.countExpiredAssignments).toHaveBeenCalledWith( + userAddress, + ); + expect( + assignmentRepository.countRejectedAssignments, + ).toHaveBeenCalledWith(userAddress); + }); + }); +}); diff --git a/packages/apps/fortune/exchange-oracle/server/src/modules/storage/storage.module.ts b/packages/apps/fortune/exchange-oracle/server/src/modules/storage/storage.module.ts index 47be30be9c..70cb4e2341 100644 --- a/packages/apps/fortune/exchange-oracle/server/src/modules/storage/storage.module.ts +++ b/packages/apps/fortune/exchange-oracle/server/src/modules/storage/storage.module.ts @@ -1,11 +1,9 @@ import { Module } from '@nestjs/common'; import { StorageService } from './storage.service'; -import { ConfigModule } from '@nestjs/config'; -import { s3Config } from '../../common/config'; import { Web3Module } from '../web3/web3.module'; @Module({ - imports: [ConfigModule.forFeature(s3Config), Web3Module], + imports: [Web3Module], providers: [StorageService], exports: [StorageService], }) diff --git a/packages/apps/fortune/exchange-oracle/server/src/modules/storage/storage.service.spec.ts b/packages/apps/fortune/exchange-oracle/server/src/modules/storage/storage.service.spec.ts index 332c753ead..93699100b6 100644 --- a/packages/apps/fortune/exchange-oracle/server/src/modules/storage/storage.service.spec.ts +++ b/packages/apps/fortune/exchange-oracle/server/src/modules/storage/storage.service.spec.ts @@ -6,20 +6,13 @@ import { StorageClient, EscrowClient, } from '@human-protocol/sdk'; -import { ConfigModule, registerAs } from '@nestjs/config'; import { Test } from '@nestjs/testing'; -import { - MOCK_ADDRESS, - MOCK_S3_ACCESS_KEY, - MOCK_S3_BUCKET, - MOCK_S3_ENDPOINT, - MOCK_S3_PORT, - MOCK_S3_SECRET_KEY, - MOCK_S3_USE_SSL, -} from '../../../test/constants'; +import { MOCK_ADDRESS } from '../../../test/constants'; import { StorageService } from './storage.service'; import { Web3Service } from '../web3/web3.service'; import { ConfigService } from '@nestjs/config'; +import { S3ConfigService } from '../../common/config/s3-config.service'; +import { PGPConfigService } from '../../common/config/pgp-config.service'; jest.mock('@human-protocol/sdk', () => ({ ...jest.requireActual('@human-protocol/sdk'), @@ -58,46 +51,33 @@ jest.mock('minio', () => { describe('StorageService', () => { let storageService: StorageService; + let pgpConfigService: PGPConfigService; + let s3ConfigService: S3ConfigService; const signerMock = { address: '0x1234567890123456789012345678901234567892', getNetwork: jest.fn().mockResolvedValue({ chainId: 1 }), }; - const configServiceMock: Partial = { - get: jest.fn(), - }; - beforeAll(async () => { const moduleRef = await Test.createTestingModule({ - imports: [ - ConfigModule.forFeature( - registerAs('s3', () => ({ - accessKey: MOCK_S3_ACCESS_KEY, - secretKey: MOCK_S3_SECRET_KEY, - endPoint: MOCK_S3_ENDPOINT, - port: MOCK_S3_PORT, - useSSL: MOCK_S3_USE_SSL, - bucket: MOCK_S3_BUCKET, - })), - ), - ], providers: [ StorageService, + ConfigService, + S3ConfigService, + PGPConfigService, { provide: Web3Service, useValue: { getSigner: jest.fn().mockReturnValue(signerMock), }, }, - { - provide: ConfigService, - useValue: configServiceMock, - }, ], }).compile(); storageService = moduleRef.get(StorageService); + pgpConfigService = moduleRef.get(PGPConfigService); + s3ConfigService = moduleRef.get(S3ConfigService); }); describe('uploadJobSolutions', () => { @@ -120,7 +100,7 @@ describe('StorageService', () => { (KVStoreClient.build as jest.Mock).mockResolvedValue({ getPublicKey: jest.fn().mockResolvedValue('publicKey'), }); - configServiceMock.get = jest.fn().mockReturnValueOnce(true); + jest.spyOn(pgpConfigService, 'encrypt', 'get').mockReturnValue(true); const jobSolution = { workerAddress, @@ -132,10 +112,10 @@ describe('StorageService', () => { [jobSolution], ); expect(fileUrl).toBe( - `http://${MOCK_S3_ENDPOINT}:${MOCK_S3_PORT}/${MOCK_S3_BUCKET}/${escrowAddress}-${chainId}.json`, + `http://${s3ConfigService.endpoint}:${s3ConfigService.port}/${s3ConfigService.bucket}/${escrowAddress}-${chainId}.json`, ); expect(storageService.minioClient.putObject).toHaveBeenCalledWith( - MOCK_S3_BUCKET, + s3ConfigService.bucket, `${escrowAddress}-${chainId}.json`, 'encrypted', { @@ -154,7 +134,7 @@ describe('StorageService', () => { storageService.minioClient.bucketExists = jest .fn() .mockResolvedValue(true); - configServiceMock.get = jest.fn().mockReturnValueOnce(true); + jest.spyOn(pgpConfigService, 'encrypt', 'get').mockReturnValue(true); const jobSolution = { workerAddress, @@ -166,10 +146,10 @@ describe('StorageService', () => { [jobSolution], ); expect(fileUrl).toBe( - `http://${MOCK_S3_ENDPOINT}:${MOCK_S3_PORT}/${MOCK_S3_BUCKET}/${escrowAddress}-${chainId}.json`, + `http://${s3ConfigService.endpoint}:${s3ConfigService.port}/${s3ConfigService.bucket}/${escrowAddress}-${chainId}.json`, ); expect(storageService.minioClient.putObject).toHaveBeenCalledWith( - MOCK_S3_BUCKET, + s3ConfigService.bucket, `${escrowAddress}-${chainId}.json`, 'encrypted', { @@ -209,7 +189,7 @@ describe('StorageService', () => { storageService.minioClient.bucketExists = jest .fn() .mockResolvedValue(true); - configServiceMock.get = jest.fn().mockReturnValueOnce(false); + jest.spyOn(pgpConfigService, 'encrypt', 'get').mockReturnValue(false); storageService.minioClient.putObject = jest .fn() .mockRejectedValue('Network error'); @@ -235,7 +215,7 @@ describe('StorageService', () => { storageService.minioClient.bucketExists = jest .fn() .mockResolvedValue(true); - configServiceMock.get = jest.fn().mockReturnValueOnce(true); + jest.spyOn(pgpConfigService, 'encrypt', 'get').mockReturnValue(true); EncryptionUtils.encrypt = jest.fn().mockResolvedValue('encrypted'); (KVStoreClient.build as jest.Mock).mockResolvedValue({ getPublicKey: jest.fn().mockResolvedValue(''), diff --git a/packages/apps/fortune/exchange-oracle/server/src/modules/storage/storage.service.ts b/packages/apps/fortune/exchange-oracle/server/src/modules/storage/storage.service.ts index 5395afb92b..88c3dbbf05 100644 --- a/packages/apps/fortune/exchange-oracle/server/src/modules/storage/storage.service.ts +++ b/packages/apps/fortune/exchange-oracle/server/src/modules/storage/storage.service.ts @@ -12,36 +12,35 @@ import { Injectable, Logger, } from '@nestjs/common'; -import { ConfigService } from '@nestjs/config'; import * as Minio from 'minio'; -import { ConfigNames, S3ConfigType, s3ConfigKey } from '../../common/config'; import { ISolution } from '../../common/interfaces/job'; import { Web3Service } from '../web3/web3.service'; +import { S3ConfigService } from '../../common/config/s3-config.service'; +import { PGPConfigService } from '../../common/config/pgp-config.service'; @Injectable() export class StorageService { public readonly minioClient: Minio.Client; constructor( - private readonly configService: ConfigService, @Inject(Web3Service) private readonly web3Service: Web3Service, - @Inject(s3ConfigKey) - private s3Config: S3ConfigType, + private s3ConfigService: S3ConfigService, + private readonly pgpConfigService: PGPConfigService, ) { this.minioClient = new Minio.Client({ - endPoint: this.s3Config.endPoint, - port: this.s3Config.port, - accessKey: this.s3Config.accessKey, - secretKey: this.s3Config.secretKey, - useSSL: this.s3Config.useSSL, + endPoint: this.s3ConfigService.endpoint, + port: this.s3ConfigService.port, + accessKey: this.s3ConfigService.accessKey, + secretKey: this.s3ConfigService.secretKey, + useSSL: this.s3ConfigService.useSSL, }); } public getJobUrl(escrowAddress: string, chainId: ChainId): string { - return `${this.s3Config.useSSL ? 'https' : 'http'}://${ - this.s3Config.endPoint - }:${this.s3Config.port}/${ - this.s3Config.bucket + return `${this.s3ConfigService.useSSL ? 'https' : 'http'}://${ + this.s3ConfigService.endpoint + }:${this.s3ConfigService.port}/${ + this.s3ConfigService.bucket }/${escrowAddress}-${chainId}.json`; } @@ -54,8 +53,8 @@ export class StorageService { const fileContent = await StorageClient.downloadFileFromUrl(url); if (EncryptionUtils.isEncrypted(fileContent)) { const encryption = await Encryption.build( - this.configService.get(ConfigNames.PGP_PRIVATE_KEY, ''), - this.configService.get(ConfigNames.PGP_PASSPHRASE), + this.pgpConfigService.privateKey, + this.pgpConfigService.passphrase, ); return JSON.parse(await encryption.decrypt(fileContent)) as ISolution[]; @@ -74,12 +73,12 @@ export class StorageService { chainId: ChainId, solutions: ISolution[], ): Promise { - if (!(await this.minioClient.bucketExists(this.s3Config.bucket))) { + if (!(await this.minioClient.bucketExists(this.s3ConfigService.bucket))) { throw new BadRequestException('Bucket not found'); } let fileToUpload = JSON.stringify(solutions); - if (this.configService.get(ConfigNames.PGP_ENCRYPT) as boolean) { + if (this.pgpConfigService.encrypt) { try { const signer = this.web3Service.getSigner(chainId); const escrowClient = await EscrowClient.build(signer); @@ -113,7 +112,7 @@ export class StorageService { try { await this.minioClient.putObject( - this.s3Config.bucket, + this.s3ConfigService.bucket, `${escrowAddress}-${chainId}.json`, fileToUpload, { diff --git a/packages/apps/fortune/exchange-oracle/server/src/modules/web3/web3.service.spec.ts b/packages/apps/fortune/exchange-oracle/server/src/modules/web3/web3.service.spec.ts index a8e55c24ed..ab64fd1775 100644 --- a/packages/apps/fortune/exchange-oracle/server/src/modules/web3/web3.service.spec.ts +++ b/packages/apps/fortune/exchange-oracle/server/src/modules/web3/web3.service.spec.ts @@ -6,36 +6,23 @@ import { ErrorWeb3 } from '../../common/constant/errors'; import { Web3Env } from '../../common/enums/web3'; import { Web3Service } from './web3.service'; import { MOCK_PRIVATE_KEY } from './../../../test/constants'; +import { Web3ConfigService } from '../../common/config/web3-config.service'; describe('Web3Service', () => { - let mockConfigService: Partial; let web3Service: Web3Service; + let web3ConfigService: Web3ConfigService; - beforeAll(async () => { - mockConfigService = { - get: jest.fn((key: string, defaultValue?: any) => { - switch (key) { - case 'WEB3_PRIVATE_KEY': - return MOCK_PRIVATE_KEY; - case 'WEB3_ENV': - return 'testnet'; - default: - return defaultValue; - } - }), - }; + jest + .spyOn(Web3ConfigService.prototype, 'privateKey', 'get') + .mockReturnValue(MOCK_PRIVATE_KEY); + beforeAll(async () => { const moduleRef = await Test.createTestingModule({ - providers: [ - Web3Service, - { - provide: ConfigService, - useValue: mockConfigService, - }, - ], + providers: [Web3Service, ConfigService, Web3ConfigService], }).compile(); web3Service = moduleRef.get(Web3Service); + web3ConfigService = moduleRef.get(Web3ConfigService); }); describe('getSigner', () => { @@ -57,13 +44,17 @@ describe('Web3Service', () => { describe('getValidChains', () => { it('should get all valid chainIds on MAINNET', () => { - (web3Service as any).currentWeb3Env = Web3Env.MAINNET; + jest + .spyOn(web3ConfigService, 'env', 'get') + .mockReturnValue(Web3Env.MAINNET); const validChainIds = web3Service.getValidChains(); expect(validChainIds).toBe(MAINNET_CHAIN_IDS); }); it('should get all valid chainIds on TESTNET', () => { - (web3Service as any).currentWeb3Env = Web3Env.TESTNET; + jest + .spyOn(web3ConfigService, 'env', 'get') + .mockReturnValue(Web3Env.TESTNET); const validChainIds = web3Service.getValidChains(); expect(validChainIds).toBe(TESTNET_CHAIN_IDS); }); diff --git a/packages/apps/fortune/exchange-oracle/server/src/modules/web3/web3.service.ts b/packages/apps/fortune/exchange-oracle/server/src/modules/web3/web3.service.ts index 4eb05ccf76..5d9652cc2f 100644 --- a/packages/apps/fortune/exchange-oracle/server/src/modules/web3/web3.service.ts +++ b/packages/apps/fortune/exchange-oracle/server/src/modules/web3/web3.service.ts @@ -1,7 +1,6 @@ import { BadRequestException, Injectable, Logger } from '@nestjs/common'; -import { ConfigService } from '@nestjs/config'; import { Wallet, ethers } from 'ethers'; -import { ConfigNames, networks } from '../../common/config'; +import { networks } from '../../common/config'; import { Web3Env } from '../../common/enums/web3'; import { LOCALHOST_CHAIN_IDS, @@ -10,6 +9,7 @@ import { } from '../../common/constant'; import { ErrorWeb3 } from '../../common/constant/errors'; import { ChainId } from '@human-protocol/sdk'; +import { Web3ConfigService } from '../../common/config/web3-config.service'; @Injectable() export class Web3Service { @@ -18,11 +18,8 @@ export class Web3Service { public readonly signerAddress: string; public readonly currentWeb3Env: string; - constructor(private readonly configService: ConfigService) { - this.currentWeb3Env = this.configService.get( - ConfigNames.WEB3_ENV, - ) as string; - const privateKey = this.configService.get(ConfigNames.WEB3_PRIVATE_KEY); + constructor(private readonly web3ConfigService: Web3ConfigService) { + const privateKey = this.web3ConfigService.privateKey; const validChains = this.getValidChains(); const validNetworks = networks.filter((network) => validChains.includes(network.chainId), @@ -48,7 +45,7 @@ export class Web3Service { } public getValidChains(): ChainId[] { - switch (this.currentWeb3Env) { + switch (this.web3ConfigService.env) { case Web3Env.MAINNET: return MAINNET_CHAIN_IDS; case Web3Env.TESTNET: diff --git a/packages/apps/fortune/exchange-oracle/server/src/modules/webhook/webhook.repository.ts b/packages/apps/fortune/exchange-oracle/server/src/modules/webhook/webhook.repository.ts index a83f3268de..c7ad0d3b38 100644 --- a/packages/apps/fortune/exchange-oracle/server/src/modules/webhook/webhook.repository.ts +++ b/packages/apps/fortune/exchange-oracle/server/src/modules/webhook/webhook.repository.ts @@ -1,17 +1,15 @@ import { Injectable } from '@nestjs/common'; -import { ConfigService } from '@nestjs/config'; import { DataSource, LessThanOrEqual } from 'typeorm'; -import { ConfigNames } from '../../common/config'; -import { DEFAULT_MAX_RETRY_COUNT } from '../../common/constant'; import { WebhookStatus } from '../../common/enums/webhook'; import { BaseRepository } from '../../database/base.repository'; import { WebhookEntity } from './webhook.entity'; +import { ServerConfigService } from '../../common/config/server-config.service'; @Injectable() export class WebhookRepository extends BaseRepository { constructor( private dataSource: DataSource, - public readonly configService: ConfigService, + public readonly serverConfigService: ServerConfigService, ) { super(WebhookEntity, dataSource); } @@ -19,12 +17,7 @@ export class WebhookRepository extends BaseRepository { return this.find({ where: { status: status, - retriesCount: LessThanOrEqual( - this.configService.get( - ConfigNames.MAX_RETRY_COUNT, - DEFAULT_MAX_RETRY_COUNT, - ), - ), + retriesCount: LessThanOrEqual(this.serverConfigService.maxRetryCount), waitUntil: LessThanOrEqual(new Date()), }, diff --git a/packages/apps/fortune/exchange-oracle/server/src/modules/webhook/webhook.service.spec.ts b/packages/apps/fortune/exchange-oracle/server/src/modules/webhook/webhook.service.spec.ts index d0d50744fc..4396581ac9 100644 --- a/packages/apps/fortune/exchange-oracle/server/src/modules/webhook/webhook.service.spec.ts +++ b/packages/apps/fortune/exchange-oracle/server/src/modules/webhook/webhook.service.spec.ts @@ -2,7 +2,7 @@ import { createMock } from '@golevelup/ts-jest'; import { ChainId, EscrowClient, OperatorUtils } from '@human-protocol/sdk'; import { HttpService } from '@nestjs/axios'; import { HttpStatus } from '@nestjs/common'; -import { ConfigModule, ConfigService, registerAs } from '@nestjs/config'; +import { ConfigService } from '@nestjs/config'; import { Test } from '@nestjs/testing'; import { of } from 'rxjs'; import { @@ -10,17 +10,8 @@ import { MOCK_ADDRESS, MOCK_PRIVATE_KEY, MOCK_RECORDING_ORACLE_WEBHOOK_URL, - MOCK_S3_ACCESS_KEY, - MOCK_S3_BUCKET, - MOCK_S3_ENDPOINT, - MOCK_S3_PORT, - MOCK_S3_SECRET_KEY, - MOCK_S3_USE_SSL, } from '../../../test/constants'; -import { - DEFAULT_MAX_RETRY_COUNT, - HEADER_SIGNATURE_KEY, -} from '../../common/constant'; +import { HEADER_SIGNATURE_KEY } from '../../common/constant'; import { ErrorWebhook } from '../../common/constant/errors'; import { EventType, WebhookStatus } from '../../common/enums/webhook'; import { AssignmentRepository } from '../assignment/assignment.repository'; @@ -32,6 +23,10 @@ import { WebhookDto } from './webhook.dto'; import { WebhookEntity } from './webhook.entity'; import { WebhookRepository } from './webhook.repository'; import { WebhookService } from './webhook.service'; +import { Web3ConfigService } from '../../common/config/web3-config.service'; +import { ServerConfigService } from '../../common/config/server-config.service'; +import { PGPConfigService } from '../../common/config/pgp-config.service'; +import { S3ConfigService } from '../../common/config/s3-config.service'; jest.mock('@human-protocol/sdk', () => ({ ...jest.requireActual('@human-protocol/sdk'), @@ -50,6 +45,7 @@ describe('WebhookService', () => { let webhookService: WebhookService, webhookRepository: WebhookRepository, jobService: JobService, + web3ConfigService: Web3ConfigService, httpService: HttpService; const chainId = 1; @@ -61,35 +57,12 @@ describe('WebhookService', () => { getNetwork: jest.fn().mockResolvedValue({ chainId: 1 }), }; - const configServiceMock: Partial = { - get: jest.fn((key: string) => { - switch (key) { - case 'WEB3_PRIVATE_KEY': - return MOCK_PRIVATE_KEY; - case 'MAX_RETRY_COUNT': - return DEFAULT_MAX_RETRY_COUNT; - } - }), - }; - const httpServicePostMock = jest .fn() .mockReturnValue(of({ status: 200, data: {} })); beforeEach(async () => { const moduleRef = await Test.createTestingModule({ - imports: [ - ConfigModule.forFeature( - registerAs('s3', () => ({ - accessKey: MOCK_S3_ACCESS_KEY, - secretKey: MOCK_S3_SECRET_KEY, - endPoint: MOCK_S3_ENDPOINT, - port: MOCK_S3_PORT, - useSSL: MOCK_S3_USE_SSL, - bucket: MOCK_S3_BUCKET, - })), - ), - ], providers: [ WebhookService, JobService, @@ -103,10 +76,11 @@ describe('WebhookService', () => { useValue: createMock(), }, StorageService, - { - provide: ConfigService, - useValue: configServiceMock, - }, + ConfigService, + Web3ConfigService, + ServerConfigService, + PGPConfigService, + S3ConfigService, { provide: Web3Service, useValue: { @@ -129,6 +103,11 @@ describe('WebhookService', () => { webhookRepository = moduleRef.get(WebhookRepository); jobService = moduleRef.get(JobService); httpService = moduleRef.get(HttpService); + web3ConfigService = moduleRef.get(Web3ConfigService); + + jest + .spyOn(web3ConfigService, 'privateKey', 'get') + .mockReturnValue(MOCK_PRIVATE_KEY); }); afterEach(() => { diff --git a/packages/apps/fortune/exchange-oracle/server/src/modules/webhook/webhook.service.ts b/packages/apps/fortune/exchange-oracle/server/src/modules/webhook/webhook.service.ts index 0c679c42a3..851ce9559e 100644 --- a/packages/apps/fortune/exchange-oracle/server/src/modules/webhook/webhook.service.ts +++ b/packages/apps/fortune/exchange-oracle/server/src/modules/webhook/webhook.service.ts @@ -12,12 +12,7 @@ import { JobService } from '../job/job.service'; import { WebhookEntity } from './webhook.entity'; import { CaseConverter } from '../../common/utils/case-converter'; import { signMessage } from '../../common/utils/signature'; -import { ConfigService } from '@nestjs/config'; -import { ConfigNames } from '../../common/config'; -import { - DEFAULT_MAX_RETRY_COUNT, - HEADER_SIGNATURE_KEY, -} from '../../common/constant'; +import { HEADER_SIGNATURE_KEY } from '../../common/constant'; import { firstValueFrom } from 'rxjs'; import { HttpService } from '@nestjs/axios'; import { ErrorWebhook } from '../../common/constant/errors'; @@ -25,6 +20,8 @@ import { WebhookRepository } from './webhook.repository'; import { ChainId, EscrowClient, OperatorUtils } from '@human-protocol/sdk'; import { Web3Service } from '../web3/web3.service'; import { StorageService } from '../storage/storage.service'; +import { Web3ConfigService } from '../../common/config/web3-config.service'; +import { ServerConfigService } from '../../common/config/server-config.service'; @Injectable() export class WebhookService { @@ -33,7 +30,8 @@ export class WebhookService { constructor( private readonly webhookRepository: WebhookRepository, private readonly jobService: JobService, - public readonly configService: ConfigService, + public readonly web3ConfigService: Web3ConfigService, + public readonly serverConfigService: ServerConfigService, public readonly httpService: HttpService, public readonly web3Service: Web3Service, public readonly storageService: StorageService, @@ -98,7 +96,7 @@ export class WebhookService { const signedBody = await signMessage( transformedWebhook, - this.configService.get(ConfigNames.WEB3_PRIVATE_KEY)!, + this.web3ConfigService.privateKey, ); config = { @@ -125,13 +123,7 @@ export class WebhookService { * @returns {Promise} - Returns a promise that resolves when the operation is complete. */ public async handleWebhookError(webhookEntity: WebhookEntity): Promise { - if ( - webhookEntity.retriesCount >= - this.configService.get( - ConfigNames.MAX_RETRY_COUNT, - DEFAULT_MAX_RETRY_COUNT, - ) - ) { + if (webhookEntity.retriesCount >= this.serverConfigService.maxRetryCount) { webhookEntity.status = WebhookStatus.FAILED; } else { webhookEntity.waitUntil = new Date(); diff --git a/scripts/fortune/.env.exco-server b/scripts/fortune/.env.exco-server index 35a43bb3e6..2c44b1fc02 100644 --- a/scripts/fortune/.env.exco-server +++ b/scripts/fortune/.env.exco-server @@ -1,13 +1,13 @@ # General NODE_ENV=development PORT=5001 +CRON_SECRET=secret # Database POSTGRES_HOST=0.0.0.0 POSTGRES_USER=default POSTGRES_PASSWORD=qwerty POSTGRES_DATABASE=exchange-oracle -POSTGRES_SYNC=false POSTGRES_PORT=5432 POSTGRES_SSL=false POSTGRES_LOGGING='all' From b173c1ad46fccded7a0ba90aac2c2eff443de650 Mon Sep 17 00:00:00 2001 From: Maxim Zhiltsov Date: Mon, 8 Apr 2024 11:02:28 +0300 Subject: [PATCH 45/66] [CVAT-M2] Improve annotation matching in GT preparation (#1830) * Filter ambiguous boxes and skeletons in GT preparation --- .../src/handlers/job_creation.py | 375 +++++++++++------- 1 file changed, 234 insertions(+), 141 deletions(-) diff --git a/packages/examples/cvat/exchange-oracle/src/handlers/job_creation.py b/packages/examples/cvat/exchange-oracle/src/handlers/job_creation.py index 041818ed71..f1a99a120e 100644 --- a/packages/examples/cvat/exchange-oracle/src/handlers/job_creation.py +++ b/packages/examples/cvat/exchange-oracle/src/handlers/job_creation.py @@ -110,15 +110,15 @@ class _ExcludedAnnotationInfo: @dataclass class _ExcludedAnnotationsInfo: - errors: List[_ExcludedAnnotationInfo] = field(default_factory=list) + messages: List[_ExcludedAnnotationInfo] = field(default_factory=list) excluded_count: int = 0 "The number of excluded annotations. Can be different from len(error_messages)" total_count: int = 0 - def add_error(self, message: str, *, sample_id: str, sample_subset: str): - self.errors.append( + def add_message(self, message: str, *, sample_id: str, sample_subset: str): + self.messages.append( _ExcludedAnnotationInfo( message=message, sample_id=sample_id, sample_subset=sample_subset ) @@ -309,7 +309,7 @@ def _validate_gt_annotations(self): (0 <= bbox.x < bbox.x + bbox.w <= img_w) and (0 <= bbox.y < bbox.y + bbox.h <= img_h) ): - excluded_gt_info.add_error( + excluded_gt_info.add_message( "Sample '{}': GT bbox #{} ({}) - invalid coordinates. " "The image will be skipped".format( gt_sample.id, bbox.id, label_cat[bbox.label].name @@ -321,7 +321,7 @@ def _validate_gt_annotations(self): break if bbox.id in visited_ids: - excluded_gt_info.add_error( + excluded_gt_info.add_message( "Sample '{}': GT bbox #{} ({}) skipped - repeated annotation id {}".format( gt_sample.id, bbox.id, label_cat[bbox.label].name, bbox.id ), @@ -346,18 +346,19 @@ def _validate_gt_annotations(self): if excluded_gt_info.excluded_count: self.logger.warning( "Some GT boxes were excluded due to the errors found: {}".format( - self._format_list([e.message for e in excluded_gt_info.errors], separator="\n") + self._format_list( + [e.message for e in excluded_gt_info.messages], separator="\n" + ) ) ) - if ( - excluded_gt_info.excluded_count - > ceil(excluded_gt_info.total_count * self.max_discarded_threshold) + if excluded_gt_info.excluded_count > ceil( + excluded_gt_info.total_count * self.max_discarded_threshold ): raise TooFewSamples( "Too many GT boxes discarded, canceling job creation. Errors: {}".format( self._format_list( - [error_info.message for error_info in excluded_gt_info.errors] + [error_info.message for error_info in excluded_gt_info.messages] ) ) ) @@ -468,7 +469,7 @@ def _validate_skeleton(skeleton: dm.Skeleton, *, sample_bbox: dm.Bbox): try: _validate_skeleton(skeleton, sample_bbox=sample_bbox) except InvalidCoordinates as error: - excluded_points_info.add_error( + excluded_points_info.add_message( "Sample '{}': point #{} ({}) - {}. " "The image will be skipped".format( sample.id, skeleton.id, label_cat[skeleton.label].name, error @@ -479,7 +480,7 @@ def _validate_skeleton(skeleton: dm.Skeleton, *, sample_bbox: dm.Bbox): valid_skeletons = [] break except DatasetValidationError as error: - excluded_points_info.add_error( + excluded_points_info.add_message( "Sample '{}': point #{} ({}) - {}".format( sample.id, skeleton.id, label_cat[skeleton.label].name, error ), @@ -505,19 +506,18 @@ def _validate_skeleton(skeleton: dm.Skeleton, *, sample_bbox: dm.Bbox): self.logger.warning( "Some points were excluded due to the errors found: {}".format( self._format_list( - [e.message for e in excluded_points_info.errors], separator="\n" + [e.message for e in excluded_points_info.messages], separator="\n" ) ) ) - if ( - excluded_points_info.excluded_count - > ceil(excluded_points_info.total_count * self.max_discarded_threshold) + if excluded_points_info.excluded_count > ceil( + excluded_points_info.total_count * self.max_discarded_threshold ): raise TooFewSamples( "Too many points discarded, canceling job creation. Errors: {}".format( self._format_list( - [error_info.message for error_info in excluded_points_info.errors] + [error_info.message for error_info in excluded_points_info.messages] ) ) ) @@ -537,65 +537,66 @@ def _is_point_in_bbox(px: float, py: float, bbox: dm.Bbox) -> bool: return is_point_in_bbox(px, py, bbox) def _prepare_gt(self): - assert self._data_filenames is not _unset - assert self._points_dataset is not _unset - assert self._gt_dataset is not _unset - assert [label.name for label in self._gt_dataset.categories()[dm.AnnotationType.label]] == [ - label.name for label in self.manifest.annotation.labels - ] - assert [ - label.name - for label in self._points_dataset.categories()[dm.AnnotationType.label] - if not label.parent - ] == [label.name for label in self.manifest.annotation.labels] + def _filter_ambiguous_skeletons( + input_skeletons: List[dm.Skeleton], + gt_boxes: List[dm.Bbox], + ) -> List[dm.Skeleton]: + filtered_skeletons: List[dm.Skeleton] = [] + for input_skeleton in input_skeletons: + matched_boxes: List[dm.Bbox] = [] + for gt_bbox in gt_boxes: + if input_skeleton.label != gt_bbox.label: + continue - gt_dataset = dm.Dataset(categories=self._gt_dataset.categories(), media_type=dm.Image) + input_point = input_skeleton.elements[0] + if not self._is_point_in_bbox(*input_point.points[0:2], bbox=gt_bbox): + continue - gt_label_cat: dm.LabelCategories = self._gt_dataset.categories()[dm.AnnotationType.label] + matched_boxes.append(gt_bbox) - excluded_gt_info = self._excluded_gt_info - gt_count_per_class = {} - bbox_point_mapping = {} # bbox id -> point id - for gt_sample in self._gt_dataset: - points_sample = self._points_dataset.get(gt_sample.id, gt_sample.subset) - assert points_sample + if len(matched_boxes) > 1: + # Handle ambiguous matches + excluded_points_info.add_message( + "Sample '{}': point #{} ({}) skipped - " + "too many matching boxes ({}) found".format( + points_sample.id, + input_skeleton.id, + points_label_cat[input_skeleton.label].name, + self._format_list([f"#{a.id}" for a in matched_boxes]), + ), + sample_id=points_sample.id, + sample_subset=points_sample.subset, + ) + # Don't need to count as excluded, because it's not an error in annotations + continue + elif len(matched_boxes) == 1: + filtered_skeletons.append(input_skeleton) - gt_boxes = [a for a in gt_sample.annotations if isinstance(a, dm.Bbox)] - input_skeletons = [a for a in points_sample.annotations if isinstance(a, dm.Skeleton)] + return filtered_skeletons - # Samples without boxes are allowed, so we just skip them without an error - if not gt_boxes: - continue + def _find_good_gt_boxes( + input_skeletons: List[dm.Skeleton], + gt_boxes: List[dm.Bbox], + ) -> List[dm.Bbox]: + # Exclude from further matching all the skeletons matching more than 1 GT bbox + input_skeletons = _filter_ambiguous_skeletons(input_skeletons, gt_boxes) matched_boxes = [] visited_skeletons = set() for gt_bbox in gt_boxes: gt_bbox_id = gt_bbox.id - if len(visited_skeletons) == len(gt_boxes): - # Handle unmatched boxes - excluded_gt_info.add_error( - "Sample '{}': GT bbox #{} ({}) skipped - " - "no matching points found".format( - gt_sample.id, gt_bbox_id, gt_label_cat[gt_bbox.label].name - ), - sample_id=gt_sample.id, - sample_subset=gt_sample.subset, - ) - excluded_gt_info.excluded_count += 1 - continue - matched_skeletons: List[dm.Skeleton] = [] for input_skeleton in input_skeletons: skeleton_id = input_skeleton.id if skeleton_id in visited_skeletons: continue - input_point = input_skeleton.elements[0] - if not self._is_point_in_bbox(*input_point.points[0:2], bbox=gt_bbox): + if input_skeleton.label != gt_bbox.label: continue - if input_skeleton.label != gt_bbox.label: + input_point = input_skeleton.elements[0] + if not self._is_point_in_bbox(*input_point.points[0:2], bbox=gt_bbox): continue matched_skeletons.append(input_skeleton) @@ -603,13 +604,13 @@ def _prepare_gt(self): if len(matched_skeletons) > 1: # Handle ambiguous matches - excluded_gt_info.add_error( + excluded_gt_info.add_message( "Sample '{}': GT bbox #{} ({}) skipped - " "too many matching points ({}) found".format( gt_sample.id, gt_bbox_id, gt_label_cat[gt_bbox.label].name, - len(matched_skeletons), + self._format_list([f"#{a.id}" for a in matched_skeletons]), ), sample_id=gt_sample.id, sample_subset=gt_sample.subset, @@ -618,7 +619,7 @@ def _prepare_gt(self): continue elif len(matched_skeletons) == 0: # Handle unmatched boxes - excluded_gt_info.add_error( + excluded_gt_info.add_message( "Sample '{}': GT bbox #{} ({}) skipped - " "no matching points found".format( gt_sample.id, @@ -636,21 +637,70 @@ def _prepare_gt(self): matched_boxes.append(gt_bbox) bbox_point_mapping[gt_bbox_id] = matched_skeletons[0].id + return matched_boxes + + assert self._data_filenames is not _unset + assert self._points_dataset is not _unset + assert self._gt_dataset is not _unset + assert [label.name for label in self._gt_dataset.categories()[dm.AnnotationType.label]] == [ + label.name for label in self.manifest.annotation.labels + ] + assert [ + label.name + for label in self._points_dataset.categories()[dm.AnnotationType.label] + if not label.parent + ] == [label.name for label in self.manifest.annotation.labels] + + points_label_cat: dm.LabelCategories = self._points_dataset.categories()[ + dm.AnnotationType.label + ] + gt_label_cat: dm.LabelCategories = self._gt_dataset.categories()[dm.AnnotationType.label] + + updated_gt_dataset = dm.Dataset( + categories=self._gt_dataset.categories(), media_type=dm.Image + ) + + excluded_points_info = _ExcludedAnnotationsInfo() # local for the function + excluded_gt_info = self._excluded_gt_info + gt_count_per_class = {} + bbox_point_mapping = {} # bbox id -> point id + for gt_sample in self._gt_dataset: + points_sample = self._points_dataset.get(gt_sample.id, gt_sample.subset) + assert points_sample + + gt_boxes = [a for a in gt_sample.annotations if isinstance(a, dm.Bbox)] + input_skeletons = [a for a in points_sample.annotations if isinstance(a, dm.Skeleton)] + + # Samples without boxes are allowed, so we just skip them without an error + if not gt_boxes: + continue + + matched_boxes = _find_good_gt_boxes(input_skeletons, gt_boxes) if not matched_boxes: continue - gt_dataset.put(gt_sample.wrap(annotations=matched_boxes)) + updated_gt_dataset.put(gt_sample.wrap(annotations=matched_boxes)) - if excluded_gt_info.excluded_count: + if excluded_points_info.messages: self.logger.warning( - "Some GT annotations were excluded due to the errors found: {}".format( - self._format_list([e.message for e in excluded_gt_info.errors], separator="\n") + "Some points were excluded from GT due to the problems found: {}".format( + self._format_list( + [e.message for e in excluded_points_info.messages], separator="\n" + ) + ) + ) + + if excluded_gt_info.messages: + self.logger.warning( + "Some GT annotations were excluded due to the problems found: {}".format( + self._format_list( + [e.message for e in excluded_gt_info.messages], separator="\n" + ) ) ) - if ( - excluded_gt_info.excluded_count - > ceil(excluded_gt_info.total_count * self.max_discarded_threshold) + if excluded_gt_info.excluded_count > ceil( + excluded_gt_info.total_count * self.max_discarded_threshold ): raise DatasetValidationError( "Too many GT boxes discarded ({} out of {}). " @@ -672,7 +722,7 @@ def _prepare_gt(self): ) ) - self._gt_dataset = gt_dataset + self._gt_dataset = updated_gt_dataset self._bbox_point_mapping = bbox_point_mapping def _estimate_roi_sizes(self): @@ -1278,7 +1328,7 @@ def _validate_skeleton(skeleton: dm.Skeleton, *, sample_bbox: dm.Bbox): try: _validate_skeleton(skeleton, sample_bbox=sample_bbox) except InvalidCoordinates as error: - excluded_gt_info.add_error( + excluded_gt_info.add_message( "Sample '{}': GT skeleton #{} ({}) - {}. " "The image will be skipped".format( gt_sample.id, skeleton.id, label_cat[skeleton.label].name, error @@ -1289,7 +1339,7 @@ def _validate_skeleton(skeleton: dm.Skeleton, *, sample_bbox: dm.Bbox): valid_skeletons = [] break except DatasetValidationError as error: - excluded_gt_info.add_error( + excluded_gt_info.add_message( "Sample '{}': GT skeleton #{} ({}) skipped - {}".format( gt_sample.id, skeleton.id, label_cat[skeleton.label].name, error ), @@ -1321,18 +1371,19 @@ def _validate_skeleton(skeleton: dm.Skeleton, *, sample_bbox: dm.Bbox): if excluded_gt_info.excluded_count: self.logger.warning( "Some GT skeletons were excluded due to the errors found: {}".format( - self._format_list([e.message for e in excluded_gt_info.errors], separator="\n") + self._format_list( + [e.message for e in excluded_gt_info.messages], separator="\n" + ) ) ) - if ( - excluded_gt_info.excluded_count - > ceil(excluded_gt_info.total_count * self.max_discarded_threshold) + if excluded_gt_info.excluded_count > ceil( + excluded_gt_info.total_count * self.max_discarded_threshold ): raise TooFewSamples( "Too many GT skeletons discarded, canceling job creation. Errors: {}".format( self._format_list( - [error_info.message for error_info in excluded_gt_info.errors] + [error_info.message for error_info in excluded_gt_info.messages] ) ) ) @@ -1402,7 +1453,7 @@ def _validate_boxes_annotations(self): (0 <= bbox.x < bbox.x + bbox.w <= image_w) and (0 <= bbox.y < bbox.y + bbox.h <= image_h) ): - excluded_boxes_info.add_error( + excluded_boxes_info.add_message( "Sample '{}': bbox #{} ({}) skipped - invalid coordinates".format( sample.id, bbox.id, label_cat[bbox.label].name ), @@ -1411,7 +1462,7 @@ def _validate_boxes_annotations(self): ) if bbox.id in visited_ids: - excluded_boxes_info.add_error( + excluded_boxes_info.add_message( "Sample '{}': bbox #{} ({}) skipped - repeated annotation id {}".format( sample.id, bbox.id, label_cat[bbox.label].name, bbox.id ), @@ -1428,19 +1479,18 @@ def _validate_boxes_annotations(self): if len(valid_boxes) != len(sample.annotations): self._boxes_dataset.put(sample.wrap(annotations=valid_boxes)) - if ( - excluded_boxes_info.excluded_count - > ceil(excluded_boxes_info.total_count * self.max_discarded_threshold) + if excluded_boxes_info.excluded_count > ceil( + excluded_boxes_info.total_count * self.max_discarded_threshold ): raise TooFewSamples( "Too many boxes discarded, canceling job creation. Errors: {}".format( self._format_list( - [error_info.message for error_info in excluded_boxes_info.errors] + [error_info.message for error_info in excluded_boxes_info.messages] ) ) ) - excluded_samples = set((e.sample_id, e.sample_subset) for e in excluded_boxes_info.errors) + excluded_samples = set((e.sample_id, e.sample_subset) for e in excluded_boxes_info.messages) for excluded_sample in excluded_samples: self._boxes_dataset.remove(*excluded_sample) @@ -1448,7 +1498,7 @@ def _validate_boxes_annotations(self): self.logger.warning( "Some boxes were excluded due to the errors found: {}".format( self._format_list( - [e.message for e in excluded_boxes_info.errors], separator="\n" + [e.message for e in excluded_boxes_info.messages], separator="\n" ) ) ) @@ -1479,68 +1529,61 @@ def _match_boxes(self, a: BboxCoords, b: BboxCoords): return bbox_iou(a, b) > 0 def _prepare_gt(self): - assert self._data_filenames is not _unset - assert self._boxes_dataset is not _unset - assert self._gt_dataset is not _unset - assert [ - label.name - for label in self._gt_dataset.categories()[dm.AnnotationType.label] - if not label.parent - ] == [label.name for label in self.manifest.annotation.labels] - assert [ - label.name - for label in self._boxes_dataset.categories()[dm.AnnotationType.label] - if not label.parent - ] == [label.name for label in self.manifest.annotation.labels] + def _filter_ambiguous_boxes( + input_boxes: List[dm.Bbox], + gt_skeletons: List[dm.Skeleton], + ) -> List[dm.Bbox]: + filtered_boxes: List[dm.Bbox] = [] + for input_bbox in input_boxes: + matched_skeletons: List[dm.Skeleton] = [] + for gt_skeleton in gt_skeletons: + if input_bbox.label != gt_skeleton.label: + continue - updated_gt_dataset = dm.Dataset( - categories=self._gt_dataset.categories(), media_type=dm.Image - ) + if not self._match_boxes(input_bbox.get_bbox(), gt_skeleton.get_bbox()): + continue - gt_label_cat: dm.LabelCategories = self._gt_dataset.categories()[dm.AnnotationType.label] + matched_skeletons.append(gt_skeleton) - excluded_gt_info = self._excluded_gt_info - gt_count_per_class = {} - skeleton_bbox_mapping = {} # skeleton id -> bbox id - for gt_sample in self._gt_dataset: - boxes_sample = self._boxes_dataset.get(gt_sample.id, gt_sample.subset) - # Samples could be discarded, so we just skip them without an error - if not boxes_sample: - continue + if len(matched_skeletons) > 1: + # Handle ambiguous matches + excluded_boxes_info.add_message( + "Sample '{}': bbox #{} ({}) skipped - " + "too many matching skeletons ({}) found".format( + boxes_sample.id, + input_bbox.id, + boxes_label_cat[input_bbox.label].name, + self._format_list([f"#{a.id}" for a in matched_skeletons]), + ), + sample_id=boxes_sample.id, + sample_subset=boxes_sample.subset, + ) + # Don't need to count as excluded, because it's not an error in annotations + continue + elif len(matched_skeletons) == 1: + filtered_boxes.append(input_bbox) - gt_skeletons = [a for a in gt_sample.annotations if isinstance(a, dm.Skeleton)] - input_boxes = [a for a in boxes_sample.annotations if isinstance(a, dm.Bbox)] + return filtered_boxes - # Samples without boxes are allowed, so we just skip them without an error - if not gt_skeletons: - continue + def _find_good_gt_skeletons( + input_boxes: List[dm.Bbox], gt_skeletons: List[dm.Skeleton] + ) -> List[dm.Bbox]: + # Exclude from further matching all the boxes matching more than 1 GT bbox + input_boxes = _filter_ambiguous_boxes(input_boxes, gt_skeletons) # Find unambiguous gt skeleton - input bbox pairs matched_skeletons = [] - visited_skeletons = set() + visited_boxes = set() for gt_skeleton in gt_skeletons: gt_skeleton_id = gt_skeleton.id - if len(visited_skeletons) == len(gt_skeletons): - # Handle unmatched boxes - excluded_gt_info.add_error( - "Sample '{}': GT skeleton #{} ({}) skipped - " - "no matching boxes found".format( - gt_sample.id, gt_skeleton_id, gt_label_cat[gt_skeleton.label].name - ), - sample_id=gt_sample.id, - sample_subset=gt_sample.subset, - ) - excluded_gt_info.excluded_count += 1 - continue - if all( v != dm.Points.Visibility.visible for p in gt_skeleton.elements for v in p.visibility ): # Handle fully hidden skeletons - excluded_gt_info.add_error( + excluded_gt_info.add_message( "Sample '{}': GT skeleton #{} ({}) skipped - " "no visible points".format( gt_sample.id, gt_skeleton_id, gt_label_cat[gt_skeleton.label].name @@ -1553,23 +1596,22 @@ def _prepare_gt(self): matched_boxes: List[dm.Bbox] = [] for input_bbox in input_boxes: - skeleton_id = gt_skeleton.id - if skeleton_id in visited_skeletons: + box_id = input_bbox.id + if box_id in visited_boxes: continue - gt_skeleton_bbox = gt_skeleton.get_bbox() - if not self._match_boxes(input_bbox.get_bbox(), gt_skeleton_bbox): + if input_bbox.label != gt_skeleton.label: continue - if input_bbox.label != gt_skeleton.label: + if not self._match_boxes(input_bbox.get_bbox(), gt_skeleton.get_bbox()): continue matched_boxes.append(input_bbox) - visited_skeletons.add(skeleton_id) + visited_boxes.add(box_id) if len(matched_boxes) > 1: # Handle ambiguous matches - excluded_gt_info.add_error( + excluded_gt_info.add_message( "Sample '{}': GT skeleton #{} ({}) skipped - " "too many matching boxes ({}) found".format( gt_sample.id, @@ -1584,7 +1626,7 @@ def _prepare_gt(self): continue elif len(matched_boxes) == 0: # Handle unmatched boxes - excluded_gt_info.add_error( + excluded_gt_info.add_message( "Sample '{}': GT skeleton #{} ({}) skipped - " "no matching boxes found".format( gt_sample.id, @@ -1607,21 +1649,72 @@ def _prepare_gt(self): matched_skeletons.append(gt_skeleton) skeleton_bbox_mapping[gt_skeleton_id] = matched_boxes[0].id + assert self._data_filenames is not _unset + assert self._boxes_dataset is not _unset + assert self._gt_dataset is not _unset + assert [ + label.name + for label in self._gt_dataset.categories()[dm.AnnotationType.label] + if not label.parent + ] == [label.name for label in self.manifest.annotation.labels] + assert [ + label.name + for label in self._boxes_dataset.categories()[dm.AnnotationType.label] + if not label.parent + ] == [label.name for label in self.manifest.annotation.labels] + + boxes_label_cat: dm.LabelCategories = self._boxes_dataset.categories()[ + dm.AnnotationType.label + ] + gt_label_cat: dm.LabelCategories = self._gt_dataset.categories()[dm.AnnotationType.label] + + updated_gt_dataset = dm.Dataset( + categories=self._gt_dataset.categories(), media_type=dm.Image + ) + + excluded_boxes_info = _ExcludedAnnotationsInfo() # local for the function + excluded_gt_info = self._excluded_gt_info + gt_count_per_class = {} + skeleton_bbox_mapping = {} # skeleton id -> bbox id + for gt_sample in self._gt_dataset: + boxes_sample = self._boxes_dataset.get(gt_sample.id, gt_sample.subset) + # Samples could be discarded, so we just skip them without an error + if not boxes_sample: + continue + + gt_skeletons = [a for a in gt_sample.annotations if isinstance(a, dm.Skeleton)] + input_boxes = [a for a in boxes_sample.annotations if isinstance(a, dm.Bbox)] + + # Samples without boxes are allowed, so we just skip them without an error + if not gt_skeletons: + continue + + matched_skeletons = _find_good_gt_skeletons(input_boxes, gt_skeletons) if not matched_skeletons: continue updated_gt_dataset.put(gt_sample.wrap(annotations=matched_skeletons)) - if excluded_gt_info.excluded_count: + if excluded_boxes_info.messages: + self.logger.warning( + "Some boxes were excluded from GT due to the problems found: {}".format( + self._format_list( + [e.message for e in excluded_boxes_info.messages], separator="\n" + ) + ) + ) + + if excluded_gt_info.messages: self.logger.warning( "Some GT annotations were excluded due to the errors found: {}".format( - self._format_list([e.message for e in excluded_gt_info.errors], separator="\n") + self._format_list( + [e.message for e in excluded_gt_info.messages], separator="\n" + ) ) ) - if ( - excluded_gt_info.excluded_count - > ceil(self.max_discarded_threshold * excluded_gt_info.total_count) + if excluded_gt_info.excluded_count > ceil( + self.max_discarded_threshold * excluded_gt_info.total_count ): raise DatasetValidationError( "Too many GT skeletons discarded ({} out of {}). " From 472cde0669491818365355c48dc05540e74cfac2 Mon Sep 17 00:00:00 2001 From: Maxim Zhiltsov Date: Mon, 8 Apr 2024 11:54:35 +0300 Subject: [PATCH 46/66] [CVAT-M2] Increase discarded / unused GT threshold (#1831) * Increase the threshold for unused or discarded GT in new tasks --- .../cvat/exchange-oracle/src/handlers/job_creation.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/examples/cvat/exchange-oracle/src/handlers/job_creation.py b/packages/examples/cvat/exchange-oracle/src/handlers/job_creation.py index f1a99a120e..23b887e3a2 100644 --- a/packages/examples/cvat/exchange-oracle/src/handlers/job_creation.py +++ b/packages/examples/cvat/exchange-oracle/src/handlers/job_creation.py @@ -191,12 +191,14 @@ def __init__(self, manifest: TaskManifest, escrow_address: str, chain_id: int): prediction quality, making them unreliable. """ - self.max_discarded_threshold = 0.05 + self.max_discarded_threshold = 0.5 """ The maximum allowed percent of discarded GT boxes, points, or samples for successful job launch """ + # TODO: probably, need to also add an absolute number of minimum GT RoIs + def __enter__(self): return self @@ -1184,12 +1186,14 @@ def __init__(self, manifest: TaskManifest, escrow_address: str, chain_id: int): self.min_label_gt_samples = 2 # TODO: find good threshold - self.max_discarded_threshold = 0.05 + self.max_discarded_threshold = 0.5 """ The maximum allowed percent of discarded GT annotations or samples for successful job launch """ + # TODO: probably, need to also add an absolute number of minimum GT RoIs per class + def __enter__(self): return self From 2468a2305eae7b271f3b927d094b82aa25f97e72 Mon Sep 17 00:00:00 2001 From: portuu3 <61605646+portuu3@users.noreply.github.com> Date: Mon, 8 Apr 2024 12:44:52 +0200 Subject: [PATCH 47/66] Reputation Oracle - Vercel serverless (#1832) * reputation oracle serverless * fix get reputation serverless function --- .../apps/reputation-oracle/server/api/app.ts | 31 +++++++ .../reputation-oracle/server/src/app-init.ts | 42 ++++++++++ .../apps/reputation-oracle/server/src/main.ts | 56 +------------ .../apps/reputation-oracle/server/vercel.json | 80 ++++++++++++++++--- 4 files changed, 143 insertions(+), 66 deletions(-) create mode 100644 packages/apps/reputation-oracle/server/api/app.ts create mode 100644 packages/apps/reputation-oracle/server/src/app-init.ts diff --git a/packages/apps/reputation-oracle/server/api/app.ts b/packages/apps/reputation-oracle/server/api/app.ts new file mode 100644 index 0000000000..7a0debf042 --- /dev/null +++ b/packages/apps/reputation-oracle/server/api/app.ts @@ -0,0 +1,31 @@ +import { NestFactory } from '@nestjs/core'; +import { ExpressAdapter } from '@nestjs/platform-express'; +import express from 'express'; +import type { VercelRequest, VercelResponse } from '@vercel/node'; +import { AppModule } from '../src/app.module'; +import init from '../src/app-init'; + +const expressApp = express(); +const adapter = new ExpressAdapter(expressApp); +let nestAppInitialized = false; + +async function bootstrapNestApp() { + if (!nestAppInitialized) { + const app = await NestFactory.create(AppModule, adapter); + await init(app); // Initialize additional settings + await app.init(); + nestAppInitialized = true; + } +} + +export default async function handler(req: VercelRequest, res: VercelResponse) { + if (req.method === 'OPTIONS') { + res.statusCode = 200; + res.end(); + return; + } + + await bootstrapNestApp(); // Ensure NestJS app is bootstrapped + + return expressApp(req, res); +} diff --git a/packages/apps/reputation-oracle/server/src/app-init.ts b/packages/apps/reputation-oracle/server/src/app-init.ts new file mode 100644 index 0000000000..db17b11307 --- /dev/null +++ b/packages/apps/reputation-oracle/server/src/app-init.ts @@ -0,0 +1,42 @@ +import session from 'express-session'; +import { ConfigService } from '@nestjs/config'; +import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger'; +import { json, urlencoded } from 'body-parser'; +import { useContainer } from 'class-validator'; +import helmet from 'helmet'; +import cookieParser from 'cookie-parser'; + +import { AppModule } from './app.module'; + +export default async function init(app: any) { + useContainer(app.select(AppModule), { fallbackOnErrors: true }); + const configService: ConfigService = app.get(ConfigService); + + app.use(cookieParser()); + + const sessionSecret = configService.get('SESSION_SECRET', ''); + + app.use( + session({ + secret: sessionSecret, + resave: false, + saveUninitialized: false, + cookie: { + secure: true, + }, + }), + ); + app.use(json({ limit: '5mb' })); + app.use(urlencoded({ limit: '5mb', extended: true })); + + const config = new DocumentBuilder() + .addBearerAuth() + .setTitle('Reputation Oracle API') + .setDescription('Swagger Reputation Oracle API') + .setVersion('1.0') + .build(); + const document = SwaggerModule.createDocument(app, config); + SwaggerModule.setup('swagger', app, document); + + app.use(helmet()); +} diff --git a/packages/apps/reputation-oracle/server/src/main.ts b/packages/apps/reputation-oracle/server/src/main.ts index 7f5eed0a62..0a503e36bf 100644 --- a/packages/apps/reputation-oracle/server/src/main.ts +++ b/packages/apps/reputation-oracle/server/src/main.ts @@ -1,72 +1,22 @@ -import session from 'express-session'; import { NestFactory } from '@nestjs/core'; -import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger'; -import { json, urlencoded } from 'body-parser'; -import { useContainer } from 'class-validator'; -import helmet from 'helmet'; -import cookieParser from 'cookie-parser'; - import { AppModule } from './app.module'; import { INestApplication } from '@nestjs/common'; import { ConfigService } from '@nestjs/config'; import { ServerConfigService } from './common/config/server-config.service'; +import init from './app-init'; async function bootstrap() { const app = await NestFactory.create(AppModule, { cors: true, }); + await init(app); + const configService: ConfigService = app.get(ConfigService); const serverConfigService = new ServerConfigService(configService); - // const baseUrl = serverConfigService.feURL; - - // app.enableCors({ - // origin: - // process.env.NODE_ENV === 'development' || - // process.env.NODE_ENV === 'staging' - // ? [ - // `http://localhost:3001`, - // `http://127.0.0.1:3001`, - // `http://0.0.0.0:3001`, - // baseUrl, - // ] - // : [baseUrl], - // credentials: true, - // exposedHeaders: ['Content-Disposition'], - // }); - - useContainer(app.select(AppModule), { fallbackOnErrors: true }); - - app.use(cookieParser()); - - const sessionSecret = configService.get('SESSION_SECRET', ''); - - app.use( - session({ - secret: sessionSecret, - resave: false, - saveUninitialized: false, - cookie: { - secure: true, - }, - }), - ); - app.use(json({ limit: '5mb' })); - app.use(urlencoded({ limit: '5mb', extended: true })); - - const config = new DocumentBuilder() - .addBearerAuth() - .setTitle('Reputation Oracle API') - .setDescription('Swagger Reputation Oracle API') - .setVersion('1.0') - .build(); - const document = SwaggerModule.createDocument(app, config); - SwaggerModule.setup('swagger', app, document); - const host = serverConfigService.host; const port = serverConfigService.port; - app.use(helmet()); await app.listen(port, host, async () => { console.info(`API server is running on http://${host}:${port}`); diff --git a/packages/apps/reputation-oracle/server/vercel.json b/packages/apps/reputation-oracle/server/vercel.json index 1d24153cb1..d52856a5dc 100644 --- a/packages/apps/reputation-oracle/server/vercel.json +++ b/packages/apps/reputation-oracle/server/vercel.json @@ -1,20 +1,55 @@ { "version": 2, - "builds": [ - { - "src": "src/main.ts", - "use": "@vercel/node" + "buildCommand": "yarn workspace @human-protocol/sdk build && yarn build", + "outputDirectory": "dist", + "functions": { + "api/app.ts":{ + "maxDuration": 300 } + }, + "redirects": [ + { "source": "/", "destination": "/swagger" } ], - "routes": [ - { - "src": "/(.*)", - "dest": "src/main.ts", - "headers": { - "Access-Control-Allow-Origin": "*", - "Access-Control-Allow-Methods": "*", - "Access-Control-Allow-Headers": "X-Requested-With,Content-Type,Accept" - } + "rewrites": [ + { + "source": "/swagger", + "destination": "/api/app.ts" + }, + { + "source": "/swagger/(.*)", + "destination": "/api/app.ts" + }, + { + "source": "/health", + "destination": "/api/app.ts" + }, + { + "source": "/reputation(.*)", + "destination": "/api/app.ts" + }, + { + "source": "/web3/prepare-signature", + "destination": "/api/app.ts" + }, + { + "source": "/webhook", + "destination": "/api/app.ts" + }, + { + "source": "/auth/(.*)", + "destination": "/api/app.ts" + }, + { + "source": "/user/(.*)", + "destination": "/api/app.ts" + }, + { + "source": "/kyc/(.*)", + "destination": "/api/app.ts" + }, + { + "source": "/cron/(.*)", + "destination": "/api/app.ts" } ], "crons": [ @@ -27,5 +62,24 @@ "schedule": "*/5 * * * *" } ], + "headers": [ + { + "source": "/(.*)", + "headers": [ + { + "key": "Access-Control-Allow-Origin", + "value": "*" + }, + { + "key": "Access-Control-Allow-Methods", + "value": "*" + }, + { + "key": "Access-Control-Allow-Headers", + "value": "*" + } + ] + } + ], "ignoreCommand": "git diff HEAD^ HEAD --quiet ." } From b0cdea970875827b50b51268a37c860096018532 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Francisco=20L=C3=B3pez?= <50665615+flopez7@users.noreply.github.com> Date: Mon, 8 Apr 2024 12:58:04 +0200 Subject: [PATCH 48/66] [Oracles] Fix JWT time (#1834) * Update jwt times from milliseconds to seconds Remove parseEthers in jl front since it is already done in server * Remove bignumber.ts utils file --- .../client/src/pages/Job/JobDetail/index.tsx | 13 ++++--------- .../job-launcher/client/src/utils/bignumber.ts | 5 ----- .../src/common/config/auth-config.service.ts | 11 ++++------- .../src/common/config/auth-config.service.ts | 14 ++++---------- scripts/fortune/.env.jl-server | 2 +- scripts/fortune/.env.rep-oracle | 4 ++-- 6 files changed, 15 insertions(+), 34 deletions(-) delete mode 100644 packages/apps/job-launcher/client/src/utils/bignumber.ts diff --git a/packages/apps/job-launcher/client/src/pages/Job/JobDetail/index.tsx b/packages/apps/job-launcher/client/src/pages/Job/JobDetail/index.tsx index 0f8eae2e57..e32111631e 100644 --- a/packages/apps/job-launcher/client/src/pages/Job/JobDetail/index.tsx +++ b/packages/apps/job-launcher/client/src/pages/Job/JobDetail/index.tsx @@ -11,7 +11,6 @@ import { useJobDetails } from '../../../hooks/useJobDetails'; import { useSnackbar } from '../../../providers/SnackProvider'; import * as jobService from '../../../services/job'; import { JobStatus } from '../../../types'; -import { formatAmount } from '../../../utils/bignumber'; const CardContainer = styled(Card)(({ theme }) => ({ borderRadius: '16px', @@ -103,7 +102,7 @@ export default function JobDetail() { /> @@ -124,13 +123,11 @@ export default function JobDetail() { @@ -167,9 +164,7 @@ export default function JobDetail() { /> { - return ethers.utils.formatUnits(BigNumber.from(amount), 18); -}; diff --git a/packages/apps/job-launcher/server/src/common/config/auth-config.service.ts b/packages/apps/job-launcher/server/src/common/config/auth-config.service.ts index 1ac4d7d81f..c24966fabe 100644 --- a/packages/apps/job-launcher/server/src/common/config/auth-config.service.ts +++ b/packages/apps/job-launcher/server/src/common/config/auth-config.service.ts @@ -11,24 +11,21 @@ export class AuthConfigService { return this.configService.get('JWT_PUBLIC_KEY', ''); } get accessTokenExpiresIn(): number { - return +this.configService.get( - 'JWT_ACCESS_TOKEN_EXPIRES_IN', - 300000, - ); + return +this.configService.get('JWT_ACCESS_TOKEN_EXPIRES_IN', 600); } get refreshTokenExpiresIn(): number { - return +this.configService.get('REFRESH_TOKEN_EXPIRES_IN', 3600000); + return +this.configService.get('REFRESH_TOKEN_EXPIRES_IN', 3600); } get verifyEmailTokenExpiresIn(): number { return +this.configService.get( 'VERIFY_EMAIL_TOKEN_EXPIRES_IN', - 1800000, + 86400, ); } get forgotPasswordExpiresIn(): number { return +this.configService.get( 'FORGOT_PASSWORD_TOKEN_EXPIRES_IN', - 1800000, + 86400, ); } get apiKeyIterations(): number { diff --git a/packages/apps/reputation-oracle/server/src/common/config/auth-config.service.ts b/packages/apps/reputation-oracle/server/src/common/config/auth-config.service.ts index 465681fa28..186132a3c4 100644 --- a/packages/apps/reputation-oracle/server/src/common/config/auth-config.service.ts +++ b/packages/apps/reputation-oracle/server/src/common/config/auth-config.service.ts @@ -11,27 +11,21 @@ export class AuthConfigService { return this.configService.get('JWT_PUBLIC_KEY', ''); } get accessTokenExpiresIn(): number { - return this.configService.get( - 'JWT_ACCESS_TOKEN_EXPIRES_IN', - 300000, - ); + return this.configService.get('JWT_ACCESS_TOKEN_EXPIRES_IN', 600); } get refreshTokenExpiresIn(): number { - return this.configService.get( - 'JWT_REFRESH_TOKEN_EXPIRES_IN', - 3600000, - ); + return this.configService.get('JWT_REFRESH_TOKEN_EXPIRES_IN', 3600); } get verifyEmailTokenExpiresIn(): number { return this.configService.get( 'VERIFY_EMAIL_TOKEN_EXPIRES_IN', - 1800000, + 86400, ); } get forgotPasswordExpiresIn(): number { return this.configService.get( 'FORGOT_PASSWORD_TOKEN_EXPIRES_IN', - 1800000, + 86400, ); } get hCaptchaSiteKey(): string { diff --git a/scripts/fortune/.env.jl-server b/scripts/fortune/.env.jl-server index b15e2ec4e0..d808ebf43b 100644 --- a/scripts/fortune/.env.jl-server +++ b/scripts/fortune/.env.jl-server @@ -47,7 +47,7 @@ MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEUznVCoagfRCuMA3TfG51xWShNrMJ t86lkzfAep9bfBxbaCBbUhJ1s9+9eeLMG/nUMAaGxWeOwJ92L/KvzN6RFw== -----END PUBLIC KEY-----" JWT_ACCESS_TOKEN_EXPIRES_IN=600 -JWT_REFRESH_TOKEN_EXPIRES_IN=1d +JWT_REFRESH_TOKEN_EXPIRES_IN=3600 # APIKEY # APIKEY_ITERATIONS= diff --git a/scripts/fortune/.env.rep-oracle b/scripts/fortune/.env.rep-oracle index 047ad92fcc..28866f5c7e 100644 --- a/scripts/fortune/.env.rep-oracle +++ b/scripts/fortune/.env.rep-oracle @@ -30,8 +30,8 @@ JWT_PUBLIC_KEY="-----BEGIN PUBLIC KEY----- MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEmxypAnb2RpdFhhOfY4JpfKG1SlH7 pDsp3Y6apiRBPO7DXgmBrwY3cljrl3cdbeEgkp5SSYBGSdoGDsce8NnQdA== -----END PUBLIC KEY-----" -JWT_ACCESS_TOKEN_EXPIRES_IN=1000000000 -JWT_REFRESH_TOKEN_EXPIRES_IN=1000000000 +JWT_ACCESS_TOKEN_EXPIRES_IN=600 +JWT_REFRESH_TOKEN_EXPIRES_IN=3600 # S3 S3_ENDPOINT=localhost From 04544df14ffac6f4abdf364ab5638da7c065a5e4 Mon Sep 17 00:00:00 2001 From: eugenvoronov <104138627+eugenvoronov@users.noreply.github.com> Date: Mon, 8 Apr 2024 17:26:56 +0300 Subject: [PATCH 49/66] [Job Launcher] e2e auth workflow (#1778) * Implemented auth workflow * add database and envs to e2e tests * fix env load * [CVAT-M2] Add more logging to recording oracle (#1813) * Add more logs to recording oracle * fix default echo value * [CVAT-M2] Fix validation when no GT stats to be updated (#1815) * Fix recording oracle failure when no gt stats to be updated * Add trace loglevel for db queries logging * Reputation Oracle - add hcaptcha token to signin dto (#1816) add hcaptcha token to sign in * Removed spaces in jwt keys * Revert "Removed spaces in jwt keys" This reverts commit 35e91b8428edb9ef52a8b7776ecfc84a72d67ebc. * Revert "Reputation Oracle - add hcaptcha token to signin dto (#1816)" This reverts commit d5711571fa312d9d9510f68dacd33ab060a7e57d. * Revert "[CVAT-M2] Fix validation when no GT stats to be updated (#1815)" This reverts commit e7632c7dcc5282425c1413a4456d62be8046c107. * Removed spaces in jwt keys * Revert "fix env load" This reverts commit 354b96aed13b0fc3b2608d079247efb639904248. * Revert "[CVAT-M2] Add more logging to recording oracle (#1813)" This reverts commit 90f0eca35488e8b0de0ba724fd4158cda27f3ef5. * Fix env load * add set NODE_ENV --------- Co-authored-by: portuu3 Co-authored-by: Maxim Zhiltsov Co-authored-by: portuu3 <61605646+portuu3@users.noreply.github.com> --- .../job-launcher/server/docker-compose.yml | 12 +- .../apps/job-launcher/server/package.json | 2 +- .../job-launcher/server/src/app.module.ts | 15 ++- .../server/src/common/constants/index.ts | 2 + .../src/modules/sendgrid/sendgrid.service.ts | 11 +- .../server/test/{ => e2e}/app.e2e-spec.ts | 12 +- .../server/test/e2e/auth-workflow.e2e-spec.ts | 123 ++++++++++++++++++ .../job-launcher/server/test/e2e/env-setup.ts | 76 +++++++++++ 8 files changed, 227 insertions(+), 26 deletions(-) rename packages/apps/job-launcher/server/test/{ => e2e}/app.e2e-spec.ts (64%) create mode 100644 packages/apps/job-launcher/server/test/e2e/auth-workflow.e2e-spec.ts create mode 100644 packages/apps/job-launcher/server/test/e2e/env-setup.ts diff --git a/packages/apps/job-launcher/server/docker-compose.yml b/packages/apps/job-launcher/server/docker-compose.yml index 62e950064f..fbe683400c 100644 --- a/packages/apps/job-launcher/server/docker-compose.yml +++ b/packages/apps/job-launcher/server/docker-compose.yml @@ -1,6 +1,7 @@ version: '3.8' services: postgres: + container_name: postgres image: postgres:latest restart: always logging: @@ -11,11 +12,10 @@ services: - 5432:5432 # volumes: # - ./db:/var/lib/postgresql/data - env_file: - - path: .env - required: true # default environment: POSTGRES_DB: ${POSTGRES_DATABASE} + POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} + POSTGRES_USER: ${POSTGRES_USER} minio: container_name: minio image: minio/minio:RELEASE.2022-05-26T05-48-41Z @@ -30,9 +30,6 @@ services: interval: 5s timeout: 5s retries: 3 - env_file: - - path: .env - required: true # default minio-mc: container_name: minio-mc image: minio/mc @@ -45,9 +42,6 @@ services: /usr/bin/mc mb myminio/manifests; /usr/bin/mc anonymous set public myminio/manifests; " - env_file: - - path: .env - required: true # default # job-launcher: # container_name: job-launcher # restart: unless-stopped diff --git a/packages/apps/job-launcher/server/package.json b/packages/apps/job-launcher/server/package.json index a444cf2218..35f7968dc3 100644 --- a/packages/apps/job-launcher/server/package.json +++ b/packages/apps/job-launcher/server/package.json @@ -25,7 +25,7 @@ "test:watch": "jest --watch", "test:cov": "jest --coverage", "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand", - "test:e2e": "jest --config ./test/jest-e2e.json" + "test:e2e": "export POSTGRES_DATABASE=job-launcher POSTGRES_PASSWORD=qwerty POSTGRES_USER=default POSTGRES_HOST=0.0.0.0 NODE_ENV=test-e2e && docker compose up -d postgres && yarn migration:run && jest --config ./test/jest-e2e.json; testStatus=$?; docker rm -f postgres; exit $testStatus" }, "dependencies": { "@human-protocol/sdk": "*", diff --git a/packages/apps/job-launcher/server/src/app.module.ts b/packages/apps/job-launcher/server/src/app.module.ts index ef8085b1cc..74c09ba5fc 100644 --- a/packages/apps/job-launcher/server/src/app.module.ts +++ b/packages/apps/job-launcher/server/src/app.module.ts @@ -1,7 +1,6 @@ import { Module } from '@nestjs/common'; import { APP_FILTER, APP_GUARD, APP_INTERCEPTOR, APP_PIPE } from '@nestjs/core'; import { ConfigModule } from '@nestjs/config'; -import { ScheduleModule } from '@nestjs/schedule'; import { AppController } from './app.controller'; import { DatabaseModule } from './database/database.module'; import { JwtAuthGuard } from './common/guards'; @@ -21,6 +20,7 @@ import { SnakeCaseInterceptor } from './common/interceptors/snake-case'; import { DatabaseExceptionFilter } from './common/exceptions/database.filter'; import { WebhookModule } from './modules/webhook/webhook.module'; import { EnvConfigModule } from './common/config/config.module'; +import { E2E_TEST_ENV } from './common/constants'; @Module({ providers: [ @@ -43,10 +43,15 @@ import { EnvConfigModule } from './common/config/config.module'; ], imports: [ ConfigModule.forRoot({ - envFilePath: process.env.NODE_ENV - ? `.env.${process.env.NODE_ENV as string}` - : '.env', - validationSchema: envValidator, + ignoreEnvFile: process.env.NODE_ENV === E2E_TEST_ENV, + ...(process.env.NODE_ENV !== E2E_TEST_ENV && { + envFilePath: process.env.NODE_ENV + ? `.env.${process.env.NODE_ENV as string}` + : '.env', + }), + ...(process.env.NODE_ENV !== E2E_TEST_ENV && { + validationSchema: envValidator, + }), }), DatabaseModule, HealthModule, diff --git a/packages/apps/job-launcher/server/src/common/constants/index.ts b/packages/apps/job-launcher/server/src/common/constants/index.ts index cdf79b35a2..60fe171374 100644 --- a/packages/apps/job-launcher/server/src/common/constants/index.ts +++ b/packages/apps/job-launcher/server/src/common/constants/index.ts @@ -65,3 +65,5 @@ export const HCAPTCHA_ORACLE_STAKE = 0.05; export const HCAPTCHA_NOT_PRESENTED_LABEL = 'Not presented'; export const RESEND_EMAIL_VERIFICATION_PATH = '/auth/resend-email-verification'; + +export const E2E_TEST_ENV = 'test-e2e'; diff --git a/packages/apps/job-launcher/server/src/modules/sendgrid/sendgrid.service.ts b/packages/apps/job-launcher/server/src/modules/sendgrid/sendgrid.service.ts index 161a3c71a9..4520cf53ba 100644 --- a/packages/apps/job-launcher/server/src/modules/sendgrid/sendgrid.service.ts +++ b/packages/apps/job-launcher/server/src/modules/sendgrid/sendgrid.service.ts @@ -13,23 +13,20 @@ export class SendGridService { private readonly defaultFromEmail: string; private readonly defaultFromName: string; - private readonly apiKey: string; constructor( private readonly mailService: MailService, private readonly sendgridConfigService: SendgridConfigService, ) { - const apiKey = this.sendgridConfigService.apiKey; - - if (this.apiKey === SENDGRID_API_KEY_DISABLED) { + if (this.sendgridConfigService.apiKey === SENDGRID_API_KEY_DISABLED) { return; } - if (!SENDGRID_API_KEY_REGEX.test(apiKey)) { + if (!SENDGRID_API_KEY_REGEX.test(this.sendgridConfigService.apiKey)) { throw new Error(ErrorSendGrid.InvalidApiKey); } - this.mailService.setApiKey(apiKey); + this.mailService.setApiKey(this.sendgridConfigService.apiKey); this.defaultFromEmail = this.sendgridConfigService.fromEmail; this.defaultFromName = this.sendgridConfigService.fromName; @@ -45,7 +42,7 @@ export class SendGridService { ...emailData }: Partial): Promise { try { - if (this.apiKey === SENDGRID_API_KEY_DISABLED) { + if (this.sendgridConfigService.apiKey === SENDGRID_API_KEY_DISABLED) { this.logger.debug(personalizations); return; } diff --git a/packages/apps/job-launcher/server/test/app.e2e-spec.ts b/packages/apps/job-launcher/server/test/e2e/app.e2e-spec.ts similarity index 64% rename from packages/apps/job-launcher/server/test/app.e2e-spec.ts rename to packages/apps/job-launcher/server/test/e2e/app.e2e-spec.ts index 50cda62332..8dc338a2ab 100644 --- a/packages/apps/job-launcher/server/test/app.e2e-spec.ts +++ b/packages/apps/job-launcher/server/test/e2e/app.e2e-spec.ts @@ -1,10 +1,14 @@ import { Test, TestingModule } from '@nestjs/testing'; import { INestApplication } from '@nestjs/common'; -import * as request from 'supertest'; -import { AppModule } from './../src/app.module'; +import request from 'supertest'; +import { AppModule } from '../../src/app.module'; +import setupE2eEnvironment from './env-setup'; describe('AppController (e2e)', () => { let app: INestApplication; + beforeAll(async () => { + setupE2eEnvironment(); + }); beforeEach(async () => { const moduleFixture: TestingModule = await Test.createTestingModule({ @@ -18,7 +22,7 @@ describe('AppController (e2e)', () => { it('/ (GET)', () => { return request(app.getHttpServer()) .get('/') - .expect(200) - .expect('Hello World!'); + .expect(301) + .expect('Moved Permanently. Redirecting to /swagger'); }); }); diff --git a/packages/apps/job-launcher/server/test/e2e/auth-workflow.e2e-spec.ts b/packages/apps/job-launcher/server/test/e2e/auth-workflow.e2e-spec.ts new file mode 100644 index 0000000000..63158539d1 --- /dev/null +++ b/packages/apps/job-launcher/server/test/e2e/auth-workflow.e2e-spec.ts @@ -0,0 +1,123 @@ +import request from 'supertest'; +import * as crypto from 'crypto'; +import { INestApplication } from '@nestjs/common'; +import { Test, TestingModule } from '@nestjs/testing'; +import { AppModule } from '../../src/app.module'; +import { UserRepository } from '../../src/modules/user/user.repository'; +import { TokenRepository } from '../../src/modules/auth/token.repository'; +import { TokenType } from '../../src/modules/auth/token.entity'; +import { UserStatus } from '../../src/common/enums/user'; +import setupE2eEnvironment from './env-setup'; + +describe('AuthService E2E', () => { + let app: INestApplication; + let userRepository: UserRepository; + let tokenRepository: TokenRepository; + + beforeAll(async () => { + setupE2eEnvironment(); + const moduleFixture: TestingModule = await Test.createTestingModule({ + imports: [AppModule], + }).compile(); + + app = moduleFixture.createNestApplication(); + await app.init(); + + userRepository = moduleFixture.get(UserRepository); + tokenRepository = moduleFixture.get(TokenRepository); + }); + + afterAll(async () => { + await app.close(); + }); + + it('should test authentication workflow', async () => { + const email = `${crypto.randomBytes(16).toString('hex')}@hmt.ai`; + const password = 'Password1!'; + const hCaptchaToken = 'string'; + + // User Registration + const signUpResponse = await request(app.getHttpServer()) + .post('/auth/signup') + .send({ + email, + password, + h_captcha_token: hCaptchaToken, + }); + expect(signUpResponse.status).toBe(201); + + // Verify user registration + const userEntity = await userRepository.findByEmail(email); + expect(userEntity).toBeDefined(); + + // Email Verification + expect(userEntity!.status).toBe(UserStatus.PENDING); + + // Invalid verification + const invalidVerifyTokenResponse = await request(app.getHttpServer()) + .post(`/auth/email-verification`) + .send({ token: 123 }); + expect(invalidVerifyTokenResponse.status).toBe(400); + + const tokenEntity = await tokenRepository.findOneByUserIdAndType( + userEntity!.id, + TokenType.EMAIL, + ); + + // Valid verification + const verifyTokenResponse = await request(app.getHttpServer()) + .post(`/auth/email-verification`) + .send({ token: tokenEntity!.uuid }); + + expect(verifyTokenResponse.status).toBe(200); + + const updatedUserEntity = await userRepository.findByEmail(email); + expect(updatedUserEntity!.status).toBe(UserStatus.ACTIVE); + + // User Authentication + const signInResponse = await request(app.getHttpServer()) + .post('/auth/signin') + .send({ + email, + password, + h_captcha_token: hCaptchaToken, + }); + expect(signInResponse.status).toBe(200); + + const refreshedUserEntity = await userRepository.findByEmail(email); + const refreshTokenEntity = await tokenRepository.findOneByUserIdAndType( + refreshedUserEntity!.id, + TokenType.REFRESH, + ); + expect(refreshTokenEntity).toBeDefined(); + + // Invalid Authentication + const invalidSignInResponse = await request(app.getHttpServer()) + .post('/auth/signin') + .send({ + email, + password: 'Incorrect', + h_captcha_token: hCaptchaToken, + }); + expect(invalidSignInResponse.status).toBe(403); + + // Refresh Token + const refreshTokenResponse = await request(app.getHttpServer()) + .post('/auth/refresh') + .send({ refresh_token: refreshTokenEntity!.uuid }); // Add appropriate data if needed + + expect(refreshTokenResponse.status).toBe(200); + expect(refreshTokenResponse.body).toHaveProperty('access_token'); + expect(refreshTokenResponse.body).toHaveProperty('refresh_token'); + + const accessToken = refreshTokenResponse.body.access_token; + const refreshToken = refreshTokenResponse.body.refresh_token; + + // Resend Email Verification + const resendEmailVerificationResponse = await request(app.getHttpServer()) + .post('/auth/resend-email-verification') + .set('Authorization', `Bearer ${accessToken}`) + .send({ email }); + expect(resendEmailVerificationResponse.status).toBe(204); + }); +}); diff --git a/packages/apps/job-launcher/server/test/e2e/env-setup.ts b/packages/apps/job-launcher/server/test/e2e/env-setup.ts new file mode 100644 index 0000000000..06fe8d1ff3 --- /dev/null +++ b/packages/apps/job-launcher/server/test/e2e/env-setup.ts @@ -0,0 +1,76 @@ +export default function setupE2eEnvironment() { + process.env.NODE_ENV = 'test-e2e'; + process.env.S3_ACCESS_KEY = 'value'; + + process.env.POSTGRES_DATABASE = 'job-launcher'; + process.env.POSTGRES_PASSWORD = 'qwerty'; + process.env.POSTGRES_USER = 'default'; + process.env.POSTGRES_HOST = '0.0.0.0'; + + process.env.SESSION_SECRET = 'test'; + process.env.POSTGRES_HOST = '0.0.0.0'; + process.env.POSTGRES_USER = 'default'; + process.env.POSTGRES_PASSWORD = 'qwerty'; + process.env.POSTGRES_DATABASE = 'job-launcher'; + process.env.POSTGRES_PORT = '5432'; + process.env.POSTGRES_SSL = 'false'; + + process.env.WEB3_ENV = 'localhost'; + process.env.WEB3_PRIVATE_KEY = + '0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d'; + process.env.GAS_PRICE_MULTIPLIER = '1'; + process.env.PGP_ENCRYPT = 'false'; + process.env.FORTUNE_EXCHANGE_ORACLE_ADDRESS = + '0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC'; + process.env.FORTUNE_RECORDING_ORACLE_ADDRESS = + '0x90F79bf6EB2c4f870365E785982E1f101E93b906'; + process.env.CVAT_EXCHANGE_ORACLE_ADDRESS = + '0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC'; + process.env.CVAT_RECORDING_ORACLE_ADDRESS = + '0x90F79bf6EB2c4f870365E785982E1f101E93b906'; + process.env.REPUTATION_ORACLE_ADDRESS = + '0x90F79bf6EB2c4f870365E785982E1f101E93b906'; + process.env.HCAPTCHA_RECORDING_ORACLE_URI = 'a'; + process.env.HCAPTCHA_REPUTATION_ORACLE_URI = 'a'; + process.env.HCAPTCHA_ORACLE_ADDRESS = 'a'; + process.env.HCAPTCHA_SITE_KEY = 'a'; + + process.env.HASH_SECRET = 'a328af3fc1dad15342cc3d68936008fa'; + process.env.JWT_PRIVATE_KEY = `-----BEGIN EC PARAMETERS----- +BggqhkjOPQMBBw== +-----END EC PARAMETERS----- +-----BEGIN EC PRIVATE KEY----- +MHcCAQEEID2jVcOtjupW4yqNTz70nvmt1GSvqET5G7lpC0Gp31LFoAoGCCqGSM49 +AwEHoUQDQgAEUznVCoagfRCuMA3TfG51xWShNrMJt86lkzfAep9bfBxbaCBbUhJ1 +s9+9eeLMG/nUMAaGxWeOwJ92L/KvzN6RFw== +-----END EC PRIVATE KEY-----`; + + process.env.JWT_PUBLIC_KEY = `-----BEGIN PUBLIC KEY----- +MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEUznVCoagfRCuMA3TfG51xWShNrMJ +t86lkzfAep9bfBxbaCBbUhJ1s9+9eeLMG/nUMAaGxWeOwJ92L/KvzN6RFw== +-----END PUBLIC KEY-----`; + process.env.JWT_ACCESS_TOKEN_EXPIRES_IN = '600'; + process.env.JWT_REFRESH_TOKEN_EXPIRES_IN = '1d'; + + process.env.S3_ENDPOINT = 'localhost'; + process.env.S3_PORT = '9000'; + process.env.S3_ACCESS_KEY = 'access-key'; + process.env.S3_SECRET_KEY = 'secret-key'; + process.env.S3_BUCKET = 'bucket'; + process.env.S3_USE_SSL = 'false'; + + process.env.MINIO_ROOT_USER = 'access-key'; + process.env.MINIO_ROOT_PASSWORD = 'secrets-key'; + + process.env.STRIPE_SECRET_KEY = + 'sk_test_GvCQRGFcSwDzNE9wBSaH6MbBzk6yUntQp5GSGjDU7VzMpkJpVq32kYx52EBP9t4Gin5UQ9nYwprq7ZRNPTQG4gvdUuTwYEGy7rp'; + process.env.STRIPE_API_VERSION = '2022-11-15'; + process.env.STRIPE_APP_NAME = 'Staging Launcher Server'; + process.env.STRIPE_APP_VERSION = '1.0.0'; + process.env.STRIPE_APP_INFO_URL = + 'https://github.com/humanprotocol/human-protocol/tree/main/packages/apps/job-launcher/server'; + + process.env.SENDGRID_API_KEY = 'sendgrid-disabled'; + + process.env.CRON_SECRET = 'test'; +} From 483bf4c5618352af4403b68d25da1a94fc28f342 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 9 Apr 2024 11:08:20 +0200 Subject: [PATCH 50/66] Bump stripe from 14.23.0 to 14.24.0 (#1837) Bumps [stripe](https://github.com/stripe/stripe-node) from 14.23.0 to 14.24.0. - [Release notes](https://github.com/stripe/stripe-node/releases) - [Changelog](https://github.com/stripe/stripe-node/blob/master/CHANGELOG.md) - [Commits](https://github.com/stripe/stripe-node/compare/v14.23.0...v14.24.0) --- updated-dependencies: - dependency-name: stripe dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- packages/apps/job-launcher/server/package.json | 2 +- yarn.lock | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/packages/apps/job-launcher/server/package.json b/packages/apps/job-launcher/server/package.json index 35f7968dc3..9a18544581 100644 --- a/packages/apps/job-launcher/server/package.json +++ b/packages/apps/job-launcher/server/package.json @@ -58,7 +58,7 @@ "pg": "8.11.3", "reflect-metadata": "^0.1.13", "rxjs": "^7.2.0", - "stripe": "^14.20.0", + "stripe": "^14.24.0", "typeorm": "^0.3.17", "typeorm-naming-strategies": "^4.1.0", "zxcvbn": "^4.4.2" diff --git a/yarn.lock b/yarn.lock index 3a19466d95..fa8fb34755 100644 --- a/yarn.lock +++ b/yarn.lock @@ -22579,10 +22579,10 @@ strip-literal@^1.0.1: dependencies: acorn "^8.10.0" -stripe@^14.20.0: - version "14.23.0" - resolved "https://registry.yarnpkg.com/stripe/-/stripe-14.23.0.tgz#a92ab57c4ce8670c9252f5f6e890fa1018cc5794" - integrity sha512-OPD7LqBmni6uDdqA05GGgMZyyRWxJOehONBNC9tYgY4Uh089EtXd6QLIgRGrqTDlQH3cA2BXo848nxwa/zsQzw== +stripe@^14.24.0: + version "14.24.0" + resolved "https://registry.yarnpkg.com/stripe/-/stripe-14.24.0.tgz#cb52bcdf233186d060e285e39ce937d3bf4ab88c" + integrity sha512-r0JWz2quThXsFbp1pevkAAoDk4sw3kFMQEc2qvxMFUOhw/SFGqtAGz4vQgP/fMWzO28ljBNEiz68KqRx0JS3dw== dependencies: "@types/node" ">=8.1.0" qs "^6.11.0" @@ -23733,9 +23733,9 @@ undici-types@~5.26.4: integrity sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA== undici@5.26.5, undici@^5.14.0, undici@^6.0.0, undici@^6.11.1: - version "6.11.1" - resolved "https://registry.yarnpkg.com/undici/-/undici-6.11.1.tgz#75ab573677885b421ca2e6f5f17ff1185b24c68d" - integrity sha512-KyhzaLJnV1qa3BSHdj4AZ2ndqI0QWPxYzaIOio0WzcEJB9gvuysprJSLtpvc2D9mhR9jPDUk7xlJlZbH2KR5iw== + version "6.12.0" + resolved "https://registry.yarnpkg.com/undici/-/undici-6.12.0.tgz#396d4dbd2ea2351094c00be44655d489afe4fb8a" + integrity sha512-d87yk8lqSFUYtR5fTFe2frpkMIrUEz+lgoJmhcL+J3StVl+8fj8ytE4lLnJOTPCE12YbumNGzf4LYsQyusdV5g== unenv@^1.9.0: version "1.9.0" From b25d34bca8c0e58f63a4693cbd58c44d63ef5784 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 9 Apr 2024 12:55:16 +0200 Subject: [PATCH 51/66] Bump tenderly from 0.7.0 to 0.9.1 (#1838) Bumps [tenderly](https://github.com/Tenderly/hardhat-tenderly) from 0.7.0 to 0.9.1. - [Release notes](https://github.com/Tenderly/hardhat-tenderly/releases) - [Changelog](https://github.com/Tenderly/hardhat-tenderly/blob/master/CHANGELOG.md) - [Commits](https://github.com/Tenderly/hardhat-tenderly/compare/tenderly@0.7.0...tenderly@0.9.1) --- updated-dependencies: - dependency-name: tenderly dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- packages/core/package.json | 2 +- yarn.lock | 13 ------------- 2 files changed, 1 insertion(+), 14 deletions(-) diff --git a/packages/core/package.json b/packages/core/package.json index 7e24aa196f..b5ab3a77ff 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -73,7 +73,7 @@ "openpgp": "5.10.2", "prettier-plugin-solidity": "^1.3.1", "solidity-coverage": "^0.8.2", - "tenderly": "^0.7.0", + "tenderly": "^0.9.1", "typechain": "^8.3.2", "xdeployer": "3.0.0" }, diff --git a/yarn.lock b/yarn.lock index fa8fb34755..e6b81da49d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -22890,19 +22890,6 @@ tarn@^3.0.2: resolved "https://registry.yarnpkg.com/tarn/-/tarn-3.0.2.tgz#73b6140fbb881b71559c4f8bfde3d9a4b3d27693" integrity sha512-51LAVKUSZSVfI05vjPESNc5vwqqZpbXCsU+/+wxlOrUjk2SnFTt97v9ZgQrD4YmxYW1Px6w2KjaDitCfkvgxMQ== -tenderly@^0.7.0: - version "0.7.0" - resolved "https://registry.yarnpkg.com/tenderly/-/tenderly-0.7.0.tgz#70fb9aa78fe9b738df38fdf76c72f083a6bd18d1" - integrity sha512-yA5KEYRRgT5lva1o8mhAU+zwpVc5f6+HJ+lVxPhdmK3LeTrm+ZEW7FYKM8WwM2HHgVugwKja+7wuS7gRhnZ/ug== - dependencies: - axios "^0.27.2" - cli-table3 "^0.6.2" - commander "^9.4.0" - js-yaml "^4.1.0" - open "^8.4.0" - prompts "^2.4.2" - tslog "^4.4.0" - tenderly@^0.9.1: version "0.9.1" resolved "https://registry.yarnpkg.com/tenderly/-/tenderly-0.9.1.tgz#de988bc5b65a106c2e1f84faf17863ba9bb81173" From af7c34d09f583a1fb7e1b6ce92abefe4d89f3be3 Mon Sep 17 00:00:00 2001 From: eugenvoronov <104138627+eugenvoronov@users.noreply.github.com> Date: Wed, 10 Apr 2024 18:49:49 +0300 Subject: [PATCH 52/66] [Job Launcher] Implemented e2e quick launch workflow (#1843) * Implemented e2e quick launch workflow * Added extra checks * Updated tests --- .../e2e/quick-launch-workflow.e2e-spec.ts | 165 ++++++++++++++++++ 1 file changed, 165 insertions(+) create mode 100644 packages/apps/job-launcher/server/test/e2e/quick-launch-workflow.e2e-spec.ts diff --git a/packages/apps/job-launcher/server/test/e2e/quick-launch-workflow.e2e-spec.ts b/packages/apps/job-launcher/server/test/e2e/quick-launch-workflow.e2e-spec.ts new file mode 100644 index 0000000000..ea9e928611 --- /dev/null +++ b/packages/apps/job-launcher/server/test/e2e/quick-launch-workflow.e2e-spec.ts @@ -0,0 +1,165 @@ +import request from 'supertest'; +import * as crypto from 'crypto'; +import { HttpStatus, INestApplication } from '@nestjs/common'; +import { Test, TestingModule } from '@nestjs/testing'; +import { AppModule } from '../../src/app.module'; +import { UserRepository } from '../../src/modules/user/user.repository'; +import { UserStatus } from '../../src/common/enums/user'; +import { UserService } from '../../src/modules/user/user.service'; +import { UserEntity } from '../../src/modules/user/user.entity'; +import setupE2eEnvironment from './env-setup'; +import { MOCK_FILE_HASH, MOCK_FILE_URL } from '../../test/constants'; +import { JobRequestType, JobStatus } from '../../src/common/enums/job'; +import { ChainId } from '@human-protocol/sdk'; +import { ErrorJob } from '../../src/common/constants/errors'; +import { + Currency, + PaymentSource, + PaymentStatus, + PaymentType, + TokenId, +} from '../../src/common/enums/payment'; +import { PaymentEntity } from '../../src/modules/payment/payment.entity'; +import { PaymentRepository } from '../../src/modules/payment/payment.repository'; +import { JobRepository } from '../../src/modules/job/job.repository'; + +describe('Quick launch E2E workflow', () => { + let app: INestApplication; + let userRepository: UserRepository; + let paymentRepository: PaymentRepository; + let jobRepository: JobRepository; + let userService: UserService; + + let userEntity: UserEntity; + let accessToken: string; + + const email = `${crypto.randomBytes(16).toString('hex')}@hmt.ai`; + + beforeAll(async () => { + setupE2eEnvironment(); + const moduleFixture: TestingModule = await Test.createTestingModule({ + imports: [AppModule], + }).compile(); + + app = moduleFixture.createNestApplication(); + await app.init(); + + userRepository = moduleFixture.get(UserRepository); + paymentRepository = moduleFixture.get(PaymentRepository); + jobRepository = moduleFixture.get(JobRepository); + userService = moduleFixture.get(UserService); + + userEntity = await userService.create({ + email, + password: 'Password1!', + hCaptchaToken: 'string', + }); + + userEntity.status = UserStatus.ACTIVE; + await userEntity.save(); + + const signInResponse = await request(app.getHttpServer()) + .post('/auth/signin') + .send({ + email, + password: 'Password1!', + h_captcha_token: 'string', + }); + + accessToken = signInResponse.body.access_token; + + const newPaymentEntity = new PaymentEntity(); + Object.assign(newPaymentEntity, { + userId: userEntity.id, + source: PaymentSource.FIAT, + type: PaymentType.DEPOSIT, + amount: 100, + currency: Currency.USD, + rate: 1, + transaction: 'payment_intent_id', + status: PaymentStatus.SUCCEEDED, + }); + await paymentRepository.createUnique(newPaymentEntity); + }); + + afterAll(async () => { + await app.close(); + }); + + it('should create a job via quick launch successfully', async () => { + const quickLaunchDto = { + chain_id: ChainId.LOCALHOST, + request_type: JobRequestType.HCAPTCHA, + manifest_url: MOCK_FILE_URL, + manifest_hash: MOCK_FILE_HASH, + fund_amount: 10, // HMT + }; + + const response = await request(app.getHttpServer()) + .post('/job/quick-launch') + .set('Authorization', `Bearer ${accessToken}`) + .send(quickLaunchDto) + .expect(201); + + expect(response.text).toBeDefined(); + expect(typeof response.text).toBe('string'); + + const jobEntity = await jobRepository.findOneByIdAndUserId( + Number(response.text), + userEntity.id, + ); + + expect(jobEntity).toBeDefined(); + expect(jobEntity!.status).toBe(JobStatus.PAID); + + const paymentEntities = await paymentRepository.findByUserAndStatus( + userEntity.id, + PaymentStatus.SUCCEEDED, + ); + + expect(paymentEntities[0]).toBeDefined(); + expect(paymentEntities[0].type).toBe(PaymentType.WITHDRAWAL); + expect(paymentEntities[0].currency).toBe(TokenId.HMT); + }); + + it('should handle not enough funds error', async () => { + const quickLaunchData = { + chain_id: ChainId.LOCALHOST, + request_type: JobRequestType.HCAPTCHA, + manifest_url: MOCK_FILE_URL, + manifest_hash: MOCK_FILE_HASH, + fund_amount: 100000000, // HMT + }; + + const invalidQuickLaunchResponse = await request(app.getHttpServer()) + .post('/job/quick-launch') + .set('Authorization', `Bearer ${accessToken}`) + .send(quickLaunchData) + .expect(400); + + expect(invalidQuickLaunchResponse.status).toBe(HttpStatus.BAD_REQUEST); + expect(invalidQuickLaunchResponse.body.message).toBe( + ErrorJob.NotEnoughFunds, + ); + }); + + it('should handle manifest hash does not exist error', async () => { + const quickLaunchData = { + chain_id: ChainId.LOCALHOST, + request_type: JobRequestType.HCAPTCHA, + manifest_url: 'http://example.com', + fund_amount: 10, // HMT + }; + + const invalidQuickLaunchResponse = await request(app.getHttpServer()) + .post('/job/quick-launch') + .set('Authorization', `Bearer ${accessToken}`) + .send(quickLaunchData) + .expect(409); + + expect(invalidQuickLaunchResponse.status).toBe(HttpStatus.CONFLICT); + expect(invalidQuickLaunchResponse.body.message).toBe( + ErrorJob.ManifestHashNotExist, + ); + }); +}); From db2bc2188ab9bbcaee100fbe2a337a6c783cf5e6 Mon Sep 17 00:00:00 2001 From: eugenvoronov <104138627+eugenvoronov@users.noreply.github.com> Date: Wed, 10 Apr 2024 18:50:05 +0300 Subject: [PATCH 53/66] [Job Launcher] Added restore password e2e workflow (#1828) * Added restore password e2e workflow * Added e2e file --- .../server/src/modules/auth/auth.dto.ts | 4 +- .../test/e2e/restore-password.e2e-spec.ts | 175 ++++++++++++++++++ 2 files changed, 177 insertions(+), 2 deletions(-) create mode 100644 packages/apps/job-launcher/server/test/e2e/restore-password.e2e-spec.ts diff --git a/packages/apps/job-launcher/server/src/modules/auth/auth.dto.ts b/packages/apps/job-launcher/server/src/modules/auth/auth.dto.ts index ad745d83f4..6ed97234f6 100644 --- a/packages/apps/job-launcher/server/src/modules/auth/auth.dto.ts +++ b/packages/apps/job-launcher/server/src/modules/auth/auth.dto.ts @@ -46,7 +46,7 @@ export class ResendEmailVerificationDto { export class RestorePasswordDto extends ValidatePasswordDto { @ApiProperty() - @IsString() + @IsUUID() public token: string; @ApiProperty({ name: 'h_captcha_token' }) @@ -56,7 +56,7 @@ export class RestorePasswordDto extends ValidatePasswordDto { export class VerifyEmailDto { @ApiProperty() - @IsString() + @IsUUID() public token: string; } diff --git a/packages/apps/job-launcher/server/test/e2e/restore-password.e2e-spec.ts b/packages/apps/job-launcher/server/test/e2e/restore-password.e2e-spec.ts new file mode 100644 index 0000000000..3080ca2685 --- /dev/null +++ b/packages/apps/job-launcher/server/test/e2e/restore-password.e2e-spec.ts @@ -0,0 +1,175 @@ +import request from 'supertest'; +import * as crypto from 'crypto'; +import { HttpStatus, INestApplication } from '@nestjs/common'; +import { Test, TestingModule } from '@nestjs/testing'; +import { AppModule } from '../../src/app.module'; +import { UserRepository } from '../../src/modules/user/user.repository'; +import { TokenRepository } from '../../src/modules/auth/token.repository'; +import { TokenEntity, TokenType } from '../../src/modules/auth/token.entity'; +import { UserStatus } from '../../src/common/enums/user'; +import { UserService } from '../../src/modules/user/user.service'; +import { UserEntity } from '../../src/modules/user/user.entity'; +import { ErrorAuth } from '../../src/common/constants/errors'; +import setupE2eEnvironment from './env-setup'; + +describe('Restore password E2E workflow', () => { + let app: INestApplication; + let userRepository: UserRepository; + let tokenRepository: TokenRepository; + let userService: UserService; + + let userEntity: UserEntity; + let tokenEntity: TokenEntity; + + const email = `${crypto.randomBytes(16).toString('hex')}@hmt.ai`; + + beforeAll(async () => { + setupE2eEnvironment(); + const moduleFixture: TestingModule = await Test.createTestingModule({ + imports: [AppModule], + }).compile(); + + app = moduleFixture.createNestApplication(); + await app.init(); + + userRepository = moduleFixture.get(UserRepository); + tokenRepository = moduleFixture.get(TokenRepository); + userService = moduleFixture.get(UserService); + + userEntity = await userService.create({ + email, + password: 'Password1!', + hCaptchaToken: 'string', + }); + + userEntity.status = UserStatus.ACTIVE; + await userEntity.save(); + + tokenEntity = new TokenEntity(); + tokenEntity.type = TokenType.EMAIL; + tokenEntity.user = userEntity; + const date = new Date(); + tokenEntity.expiresAt = new Date(date.getTime() + 100000); + + await tokenRepository.createUnique(tokenEntity); + }); + + afterAll(async () => { + await app.close(); + }); + + it('should restore password successfully', async () => { + // Call forgot password endpoint + await request(app.getHttpServer()) + .post('/auth/forgot-password') + .send({ email }) + .expect(HttpStatus.NO_CONTENT); + + // Ensure old token was deleted and a new token was created + const oldTokenExists = await tokenRepository.findOneByUuidAndType( + tokenEntity.uuid, + TokenType.PASSWORD, + ); + expect(oldTokenExists).toBeNull(); + + const newToken = await tokenRepository.findOneByUserIdAndType( + userEntity.id, + TokenType.PASSWORD, + ); + expect(newToken).toBeDefined(); + + // Call restore password endpoint + const restorePasswordData = { + password: 'NewPassword1!', + token: newToken!.uuid, + hCaptchaToken: 'string', + }; + await request(app.getHttpServer()) + .post('/auth/restore-password') + .send(restorePasswordData) + .expect(HttpStatus.NO_CONTENT); + + // Ensure password was updated + const updatedUser = await userRepository.findById(userEntity!.id); + expect(updatedUser!.password).not.toEqual(userEntity.password); + + userEntity = updatedUser!; + + // Ensure new token was deleted + const deletedToken = await tokenRepository.findOneByUuidAndType( + newToken!.uuid, + TokenType.PASSWORD, + ); + expect(deletedToken).toBeNull(); + }); + + it('should handle invalid token error', async () => { + // Call forgot password endpoint + await request(app.getHttpServer()) + .post('/auth/forgot-password') + .send({ email }) + .expect(HttpStatus.NO_CONTENT); + + // Delete the new token + await tokenRepository.delete(tokenEntity.id); + + // Call restore password endpoint with invalid token + const invalidToken = '00000000-0000-0000-0000-000000000000'; + const restorePasswordData = { + password: 'NewPassword2!', + token: invalidToken, + hCaptchaToken: 'string', + }; + + const invalidRestorePasswordResponse = await request(app.getHttpServer()) + .post('/auth/restore-password') + .send(restorePasswordData) + .expect(HttpStatus.FORBIDDEN); + + expect(invalidRestorePasswordResponse.status).toBe(HttpStatus.FORBIDDEN); + expect(invalidRestorePasswordResponse.body.message).toBe( + ErrorAuth.InvalidToken, + ); + + // Ensure password was not updated + const updatedUser = await userRepository.findById(userEntity.id); + expect(updatedUser!.password).toEqual(userEntity.password); + }); + + it('should handle token expired error', async () => { + // Call forgot password endpoint + await request(app.getHttpServer()) + .post('/auth/forgot-password') + .send({ email }) + .expect(HttpStatus.NO_CONTENT); + + // Expire the new token + const date = new Date(); + const expiredToken = await tokenRepository.findOneByUserIdAndType( + userEntity.id, + TokenType.PASSWORD, + ); + expiredToken!.expiresAt = new Date(date.getTime() - 100000); // Set token expiration in the past + await expiredToken!.save(); + + // Call restore password endpoint with expired token + const expiredTokenValue = expiredToken!.uuid; + const restorePasswordData = { + password: 'NewPassword2!', + token: expiredTokenValue, + hCaptchaToken: 'string', + }; + const invalidRestorePasswordResponse = await request(app.getHttpServer()) + .post('/auth/restore-password') + .send(restorePasswordData); + + expect(invalidRestorePasswordResponse.status).toBe(HttpStatus.FORBIDDEN); + expect(invalidRestorePasswordResponse.body.message).toBe( + ErrorAuth.TokenExpired, + ); + + // Ensure password was not updated + const updatedUser = await userRepository.findById(userEntity.id); + expect(updatedUser!.password).toEqual(userEntity.password); + }); +}); From 1a730e95535e48a278352f149a144059ef078b02 Mon Sep 17 00:00:00 2001 From: Maxim Zhiltsov Date: Wed, 10 Apr 2024 19:22:11 +0300 Subject: [PATCH 54/66] [CVAT-M2] Review GT use (#1850) * Exclude input GT files from the data for assignments * Use the original GT during merging final results for new task types * Fix gt preparation for skeletons_from_boxes * Fix debug message --- .../src/handlers/job_creation.py | 106 ++++++++++++------ .../handlers/process_intermediate_results.py | 11 +- 2 files changed, 82 insertions(+), 35 deletions(-) diff --git a/packages/examples/cvat/exchange-oracle/src/handlers/job_creation.py b/packages/examples/cvat/exchange-oracle/src/handlers/job_creation.py index 23b887e3a2..25696765c4 100644 --- a/packages/examples/cvat/exchange-oracle/src/handlers/job_creation.py +++ b/packages/examples/cvat/exchange-oracle/src/handlers/job_creation.py @@ -138,6 +138,7 @@ def __init__(self, manifest: TaskManifest, escrow_address: str, chain_id: int): self._input_points_data: _MaybeUnset[bytes] = _unset self._data_filenames: _MaybeUnset[Sequence[str]] = _unset + self._input_gt_dataset: _MaybeUnset[dm.Dataset] = _unset self._gt_dataset: _MaybeUnset[dm.Dataset] = _unset self._points_dataset: _MaybeUnset[dm.Dataset] = _unset @@ -242,7 +243,7 @@ def _parse_dataset(self, annotation_file_data: bytes, dataset_format: str) -> dm def _parse_gt(self): assert self._input_gt_data is not _unset - self._gt_dataset = self._parse_dataset( + self._input_gt_dataset = self._parse_dataset( self._input_gt_data, dataset_format=DM_GT_DATASET_FORMAT_MAPPING[self.manifest.annotation.type], ) @@ -257,7 +258,7 @@ def _parse_points(self): def _validate_gt_labels(self): gt_labels = set( label.name - for label in self._gt_dataset.categories()[dm.AnnotationType.label] + for label in self._input_gt_dataset.categories()[dm.AnnotationType.label] if not label.parent ) manifest_labels = set(label.name for label in self.manifest.annotation.labels) @@ -268,13 +269,13 @@ def _validate_gt_labels(self): ) ) - self._gt_dataset.transform( + self._input_gt_dataset.transform( ProjectLabels, dst_labels=[label.name for label in self.manifest.annotation.labels] ) - self._gt_dataset.init_cache() + self._input_gt_dataset.init_cache() def _validate_gt_filenames(self): - gt_filenames = set(s.id + s.media.ext for s in self._gt_dataset) + gt_filenames = set(s.id + s.media.ext for s in self._input_gt_dataset) known_data_filenames = set(self._data_filenames) matched_gt_filenames = gt_filenames.intersection(known_data_filenames) @@ -295,12 +296,12 @@ def _validate_gt_filenames(self): ) def _validate_gt_annotations(self): - label_cat: dm.LabelCategories = self._gt_dataset.categories()[dm.AnnotationType.label] + label_cat: dm.LabelCategories = self._input_gt_dataset.categories()[dm.AnnotationType.label] excluded_gt_info = _ExcludedAnnotationsInfo() excluded_samples = set() visited_ids = set() - for gt_sample in self._gt_dataset: + for gt_sample in self._input_gt_dataset: # Could fail on this as well img_h, img_w = gt_sample.media_as(dm.Image).size @@ -340,10 +341,10 @@ def _validate_gt_annotations(self): if not valid_boxes: excluded_samples.add((gt_sample.id, gt_sample.subset)) else: - self._gt_dataset.put(gt_sample.wrap(annotations=valid_boxes)) + self._input_gt_dataset.put(gt_sample.wrap(annotations=valid_boxes)) for excluded_sample in excluded_samples: - self._gt_dataset.remove(*excluded_sample) + self._input_gt_dataset.remove(*excluded_sample) if excluded_gt_info.excluded_count: self.logger.warning( @@ -369,7 +370,7 @@ def _validate_gt_annotations(self): def _validate_gt(self): assert self._data_filenames is not _unset - assert self._gt_dataset is not _unset + assert self._input_gt_dataset is not _unset self._validate_gt_filenames() self._validate_gt_labels() @@ -643,10 +644,10 @@ def _find_good_gt_boxes( assert self._data_filenames is not _unset assert self._points_dataset is not _unset - assert self._gt_dataset is not _unset - assert [label.name for label in self._gt_dataset.categories()[dm.AnnotationType.label]] == [ - label.name for label in self.manifest.annotation.labels - ] + assert self._input_gt_dataset is not _unset + assert [ + label.name for label in self._input_gt_dataset.categories()[dm.AnnotationType.label] + ] == [label.name for label in self.manifest.annotation.labels] assert [ label.name for label in self._points_dataset.categories()[dm.AnnotationType.label] @@ -656,17 +657,19 @@ def _find_good_gt_boxes( points_label_cat: dm.LabelCategories = self._points_dataset.categories()[ dm.AnnotationType.label ] - gt_label_cat: dm.LabelCategories = self._gt_dataset.categories()[dm.AnnotationType.label] + gt_label_cat: dm.LabelCategories = self._input_gt_dataset.categories()[ + dm.AnnotationType.label + ] updated_gt_dataset = dm.Dataset( - categories=self._gt_dataset.categories(), media_type=dm.Image + categories=self._input_gt_dataset.categories(), media_type=dm.Image ) excluded_points_info = _ExcludedAnnotationsInfo() # local for the function excluded_gt_info = self._excluded_gt_info gt_count_per_class = {} bbox_point_mapping = {} # bbox id -> point id - for gt_sample in self._gt_dataset: + for gt_sample in self._input_gt_dataset: points_sample = self._points_dataset.get(gt_sample.id, gt_sample.subset) assert points_sample @@ -856,12 +859,24 @@ def _prepare_job_layout(self): assert self._rois is not _unset assert self._bbox_point_mapping is not _unset + assert self._input_gt_dataset is not _unset + + # This list can be different from what is selected for validation + input_gt_filenames = set(sample.media.path for sample in self._input_gt_dataset) + original_image_id_to_filename = { + sample.attributes["id"]: sample.media.path for sample in self._points_dataset + } + point_id_to_original_image_id = {roi.point_id: roi.original_image_key for roi in self._rois} gt_point_ids = set(self._bbox_point_mapping.values()) gt_filenames = [self._roi_filenames[point_id] for point_id in gt_point_ids] data_filenames = [ - fn for point_id, fn in self._roi_filenames.items() if not point_id in gt_point_ids + fn + for point_id, fn in self._roi_filenames.items() + if not point_id in gt_point_ids + if not original_image_id_to_filename[point_id_to_original_image_id[point_id]] + in input_gt_filenames ] random.shuffle(data_filenames) @@ -874,6 +889,12 @@ def _prepare_job_layout(self): self._job_layout = job_layout + self.logger.info( + "Task creation for escrow '%s': will create %s assignments", + self.escrow_address, + len(job_layout), + ) + def _prepare_label_configuration(self): self._label_configuration = make_label_configuration(self.manifest) @@ -1145,6 +1166,7 @@ def __init__(self, manifest: TaskManifest, escrow_address: str, chain_id: int): self._input_boxes_data: _MaybeUnset[bytes] = _unset self._data_filenames: _MaybeUnset[Sequence[str]] = _unset + self._input_gt_dataset: _MaybeUnset[dm.Dataset] = _unset self._gt_dataset: _MaybeUnset[dm.Dataset] = _unset self._boxes_dataset: _MaybeUnset[dm.Dataset] = _unset @@ -1237,7 +1259,7 @@ def _parse_dataset(self, annotation_file_data: bytes, dataset_format: str) -> dm def _parse_gt(self): assert self._input_gt_data is not _unset - self._gt_dataset = self._parse_dataset( + self._input_gt_dataset = self._parse_dataset( self._input_gt_data, dataset_format=DM_GT_DATASET_FORMAT_MAPPING[self.manifest.annotation.type], ) @@ -1252,7 +1274,7 @@ def _parse_boxes(self): def _validate_gt_labels(self): gt_labels = set( (label.name, label.parent) - for label in self._gt_dataset.categories()[dm.AnnotationType.label] + for label in self._input_gt_dataset.categories()[dm.AnnotationType.label] ) manifest_labels = set() @@ -1274,13 +1296,13 @@ def _validate_gt_labels(self): ) # Reorder labels to match the manifest - self._gt_dataset.transform( + self._input_gt_dataset.transform( ProjectLabels, dst_labels=[label.name for label in self.manifest.annotation.labels] ) - self._gt_dataset.init_cache() + self._input_gt_dataset.init_cache() def _validate_gt_filenames(self): - gt_filenames = set(s.id + s.media.ext for s in self._gt_dataset) + gt_filenames = set(s.id + s.media.ext for s in self._input_gt_dataset) known_data_filenames = set(self._data_filenames) matched_gt_filenames = gt_filenames.intersection(known_data_filenames) @@ -1316,12 +1338,12 @@ def _validate_skeleton(skeleton: dm.Skeleton, *, sample_bbox: dm.Bbox): if not is_point_in_bbox(px, py, sample_bbox): raise InvalidCoordinates("skeleton point is outside the image") - label_cat: dm.LabelCategories = self._gt_dataset.categories()[dm.AnnotationType.label] + label_cat: dm.LabelCategories = self._input_gt_dataset.categories()[dm.AnnotationType.label] excluded_gt_info = _ExcludedAnnotationsInfo() excluded_samples = set() visited_ids = set() - for gt_sample in self._gt_dataset: + for gt_sample in self._input_gt_dataset: # Could fail on this as well img_h, img_w = gt_sample.media_as(dm.Image).size sample_bbox = dm.Bbox(0, 0, w=img_w, h=img_h) @@ -1363,14 +1385,14 @@ def _validate_skeleton(skeleton: dm.Skeleton, *, sample_bbox: dm.Bbox): else: # Skeleton boxes can be in the list as well with the same ids / groups skeleton_ids = set(a.id for a in valid_skeletons) - {0} - self._gt_dataset.put( + self._input_gt_dataset.put( gt_sample.wrap( annotations=[a for a in gt_sample.annotations if a.id in skeleton_ids] ) ) for excluded_sample in excluded_samples: - self._gt_dataset.remove(*excluded_sample) + self._input_gt_dataset.remove(*excluded_sample) if excluded_gt_info.excluded_count: self.logger.warning( @@ -1396,7 +1418,7 @@ def _validate_skeleton(skeleton: dm.Skeleton, *, sample_bbox: dm.Bbox): def _validate_gt(self): assert self._data_filenames is not _unset - assert self._gt_dataset is not _unset + assert self._input_gt_dataset is not _unset self._validate_gt_filenames() self._validate_gt_labels() @@ -1653,12 +1675,14 @@ def _find_good_gt_skeletons( matched_skeletons.append(gt_skeleton) skeleton_bbox_mapping[gt_skeleton_id] = matched_boxes[0].id + return matched_skeletons + assert self._data_filenames is not _unset assert self._boxes_dataset is not _unset - assert self._gt_dataset is not _unset + assert self._input_gt_dataset is not _unset assert [ label.name - for label in self._gt_dataset.categories()[dm.AnnotationType.label] + for label in self._input_gt_dataset.categories()[dm.AnnotationType.label] if not label.parent ] == [label.name for label in self.manifest.annotation.labels] assert [ @@ -1670,17 +1694,19 @@ def _find_good_gt_skeletons( boxes_label_cat: dm.LabelCategories = self._boxes_dataset.categories()[ dm.AnnotationType.label ] - gt_label_cat: dm.LabelCategories = self._gt_dataset.categories()[dm.AnnotationType.label] + gt_label_cat: dm.LabelCategories = self._input_gt_dataset.categories()[ + dm.AnnotationType.label + ] updated_gt_dataset = dm.Dataset( - categories=self._gt_dataset.categories(), media_type=dm.Image + categories=self._input_gt_dataset.categories(), media_type=dm.Image ) excluded_boxes_info = _ExcludedAnnotationsInfo() # local for the function excluded_gt_info = self._excluded_gt_info gt_count_per_class = {} skeleton_bbox_mapping = {} # skeleton id -> bbox id - for gt_sample in self._gt_dataset: + for gt_sample in self._input_gt_dataset: boxes_sample = self._boxes_dataset.get(gt_sample.id, gt_sample.subset) # Samples could be discarded, so we just skip them without an error if not boxes_sample: @@ -1799,6 +1825,13 @@ def _mangle_filenames(self): def _prepare_job_params(self): assert self._roi_infos is not _unset assert self._skeleton_bbox_mapping is not _unset + assert self._input_gt_dataset is not _unset + + # This list can be different from what is selected for validation + input_gt_filenames = set(sample.media.path for sample in self._input_gt_dataset) + image_id_to_filename = { + sample.attributes["id"]: sample.media.path for sample in self._boxes_dataset + } # Make job layouts wrt. manifest params # 1 job per task, 1 task for each point label @@ -1824,6 +1857,7 @@ def _prepare_job_params(self): for roi_info in self._roi_infos if roi_info.bbox_label == label_id if roi_info.bbox_id not in label_gt_roi_ids + if image_id_to_filename[roi_info.original_image_key] not in input_gt_filenames ] random.shuffle(label_data_roi_ids) @@ -1844,6 +1878,12 @@ def _prepare_job_params(self): self._job_params = job_params + self.logger.info( + "Task creation for escrow '%s': will create %s assignments", + self.escrow_address, + sum(len(self.manifest.annotation.labels[jp.label_id].nodes) for jp in job_params), + ) + def _prepare_job_labels(self): self.point_labels = {} diff --git a/packages/examples/cvat/recording-oracle/src/handlers/process_intermediate_results.py b/packages/examples/cvat/recording-oracle/src/handlers/process_intermediate_results.py index a139d2ddad..d6210810cb 100644 --- a/packages/examples/cvat/recording-oracle/src/handlers/process_intermediate_results.py +++ b/packages/examples/cvat/recording-oracle/src/handlers/process_intermediate_results.py @@ -510,6 +510,10 @@ def _make_gt_dataset_for_job(self, job_id: int, job_dataset: dm.Dataset) -> dm.D return job_gt_dataset + def _prepare_merged_dataset(self): + super()._parse_gt() # We need to download the original GT dataset + return super()._prepare_merged_dataset() + class _SkeletonsFromBoxesValidator(_TaskValidatorWithPerJobGt): def __init__(self, *args, **kwargs): @@ -845,6 +849,10 @@ def _update_gt_stats( return updated_gt_stats + def _prepare_merged_dataset(self): + super()._parse_gt() # We need to download the original GT dataset + return super()._prepare_merged_dataset() + def _compute_gt_stats_update( initial_gt_stats: _FailedGtAttempts, validation_gt_stats: _UpdatedFailedGtStats @@ -894,8 +902,7 @@ def process_intermediate_results( if logger.isEnabledFor(logging.DEBUG): logger.debug("process_intermediate_results for escrow %s", escrow_address) - logger.debug("Task id %s", task_id) - logger.debug("Task %s %s", task, getattr(task, "__dict__", None)) + logger.debug("Task id %s, %s", getattr(task, 'id', None), getattr(task, "__dict__", None)) initial_gt_stats = { gt_image_stat.gt_key: gt_image_stat.failed_attempts From 643909536b96db902011efd8769d934b2026d7b4 Mon Sep 17 00:00:00 2001 From: Maxim Zhiltsov Date: Thu, 11 Apr 2024 11:20:05 +0300 Subject: [PATCH 55/66] [CVAT-M2] Add row locks during CVAT task creation (#1848) * Add row locks in task creation --- .../src/handlers/job_creation.py | 89 +++++++++++-------- 1 file changed, 52 insertions(+), 37 deletions(-) diff --git a/packages/examples/cvat/exchange-oracle/src/handlers/job_creation.py b/packages/examples/cvat/exchange-oracle/src/handlers/job_creation.py index 25696765c4..6c519dcb3a 100644 --- a/packages/examples/cvat/exchange-oracle/src/handlers/job_creation.py +++ b/packages/examples/cvat/exchange-oracle/src/handlers/job_creation.py @@ -1063,25 +1063,25 @@ def _create_on_cvat(self): oracle_bucket = self.oracle_data_bucket # Register cloud storage on CVAT to pass user dataset - cloud_storage = cvat_api.create_cloudstorage( + cvat_cloud_storage = cvat_api.create_cloudstorage( **_make_cvat_cloud_storage_params(oracle_bucket) ) # Create a project - project = cvat_api.create_project( + cvat_project = cvat_api.create_project( self.escrow_address, labels=self._label_configuration, user_guide=self.manifest.annotation.user_guide, ) # Setup webhooks for a project (update:task, update:job) - webhook = cvat_api.create_cvat_webhook(project.id) + cvat_webhook = cvat_api.create_cvat_webhook(cvat_project.id) with SessionLocal.begin() as session: - db_service.create_project( + project_id = db_service.create_project( session, - project.id, - cloud_storage.id, + cvat_project.id, + cvat_cloud_storage.id, self.manifest.annotation.type, self.escrow_address, self.chain_id, @@ -1090,11 +1090,13 @@ def _create_on_cvat(self): bucket_host=input_data_bucket.host_url, provider=input_data_bucket.provider, ), - cvat_webhook_id=webhook.id, + cvat_webhook_id=cvat_webhook.id, ) + + db_service.get_project_by_id(session, project_id, for_update=True) # lock the row db_service.add_project_images( session, - project.id, + cvat_project.id, [ compose_data_bucket_filename(self.escrow_address, self.chain_id, fn) for fn in self._roi_filenames.values() @@ -1102,16 +1104,18 @@ def _create_on_cvat(self): ) for job_filenames in self._job_layout: - task = cvat_api.create_task(project.id, self.escrow_address) + cvat_task = cvat_api.create_task(cvat_project.id, self.escrow_address) with SessionLocal.begin() as session: - db_service.create_task(session, task.id, project.id, TaskStatuses[task.status]) + task_id = db_service.create_task( + session, cvat_task.id, cvat_project.id, TaskStatuses[cvat_task.status] + ) # Actual task creation in CVAT takes some time, so it's done in an async process. # The task will be created in DB once 'update:task' or 'update:job' webhook is received. cvat_api.put_task_data( - task.id, - cloud_storage.id, + cvat_task.id, + cvat_cloud_storage.id, filenames=[ compose_data_bucket_filename(self.escrow_address, self.chain_id, fn) for fn in job_filenames @@ -1120,7 +1124,8 @@ def _create_on_cvat(self): ) with SessionLocal.begin() as session: - db_service.create_data_upload(session, cvat_task_id=task.id) + db_service.get_task_by_id(session, task_id, for_update=True) # lock the row + db_service.create_data_upload(session, cvat_task.id) @classmethod def _make_cloud_storage_client(cls, bucket_info: BucketAccessInfo) -> StorageClient: @@ -2060,7 +2065,7 @@ def _create_on_cvat(self): oracle_bucket = self.oracle_data_bucket # Register cloud storage on CVAT to pass user dataset - cloud_storage = cvat_api.create_cloudstorage( + cvat_cloud_storage = cvat_api.create_cloudstorage( **_make_cvat_cloud_storage_params(oracle_bucket) ) @@ -2080,7 +2085,7 @@ def _create_on_cvat(self): for point_label_spec in label_specs_by_skeleton[skeleton_label_id]: # Create a project for each point label. # CVAT doesn't support tasks with different labels in a project. - project = cvat_api.create_project( + cvat_project = cvat_api.create_project( name="{} ({} {})".format( self.escrow_address, self.manifest.annotation.labels[skeleton_label_id].name, @@ -2092,13 +2097,13 @@ def _create_on_cvat(self): ) # Setup webhooks for a project (update:task, update:job) - webhook = cvat_api.create_cvat_webhook(project.id) + cvat_webhook = cvat_api.create_cvat_webhook(cvat_project.id) with SessionLocal.begin() as session: - db_service.create_project( + project_id = db_service.create_project( session, - project.id, - cloud_storage.id, + cvat_project.id, + cvat_cloud_storage.id, self.manifest.annotation.type, self.escrow_address, self.chain_id, @@ -2107,33 +2112,38 @@ def _create_on_cvat(self): bucket_host=input_data_bucket.host_url, provider=input_data_bucket.provider, ), - cvat_webhook_id=webhook.id, + cvat_webhook_id=cvat_webhook.id, ) + + db_service.get_project_by_id( + session, project_id, for_update=True + ) # lock the row db_service.add_project_images( session, - project.id, + cvat_project.id, list(set(chain.from_iterable(skeleton_label_filenames))), ) for point_label_filenames in skeleton_label_filenames: - task = cvat_api.create_task(project.id, name=project.name) + cvat_task = cvat_api.create_task(cvat_project.id, name=cvat_project.name) with SessionLocal.begin() as session: - db_service.create_task( - session, task.id, project.id, TaskStatuses[task.status] + task_id = db_service.create_task( + session, cvat_task.id, cvat_project.id, TaskStatuses[cvat_task.status] ) # Actual task creation in CVAT takes some time, so it's done in an async process. # The task will be created in DB once 'update:task' or 'update:job' webhook is received. cvat_api.put_task_data( - task.id, - cloud_storage.id, + cvat_task.id, + cvat_cloud_storage.id, filenames=point_label_filenames, sort_images=False, ) with SessionLocal.begin() as session: - db_service.create_data_upload(session, cvat_task_id=task.id) + db_service.get_task_by_id(session, task_id, for_update=True) # lock the row + db_service.create_data_upload(session, cvat_task.id) @classmethod def _make_cloud_storage_client(cls, bucket_info: BucketAccessInfo) -> StorageClient: @@ -2293,19 +2303,19 @@ def create_task(escrow_address: str, chain_id: int) -> None: cloud_storage = cvat_api.create_cloudstorage(**_make_cvat_cloud_storage_params(data_bucket)) # Create a project - project = cvat_api.create_project( + cvat_project = cvat_api.create_project( escrow_address, labels=label_configuration, user_guide=manifest.annotation.user_guide, ) # Setup webhooks for a project (update:task, update:job) - webhook = cvat_api.create_cvat_webhook(project.id) + cvat_webhook = cvat_api.create_cvat_webhook(cvat_project.id) with SessionLocal.begin() as session: - db_service.create_project( + project_id = db_service.create_project( session, - project.id, + cvat_project.id, cloud_storage.id, manifest.annotation.type, escrow_address, @@ -2315,27 +2325,32 @@ def create_task(escrow_address: str, chain_id: int) -> None: bucket_host=data_bucket.host_url, provider=data_bucket.provider, ), - cvat_webhook_id=webhook.id, + cvat_webhook_id=cvat_webhook.id, ) - db_service.add_project_images(session, project.id, data_filenames) + + db_service.get_project_by_id(session, project_id, for_update=True) # lock the row + db_service.add_project_images(session, cvat_project.id, data_filenames) for job_filenames in job_configuration: - task = cvat_api.create_task(project.id, escrow_address) + cvat_task = cvat_api.create_task(cvat_project.id, escrow_address) with SessionLocal.begin() as session: - db_service.create_task(session, task.id, project.id, TaskStatuses[task.status]) + task_id = db_service.create_task( + session, cvat_task.id, cvat_project.id, TaskStatuses[cvat_task.status] + ) # Actual task creation in CVAT takes some time, so it's done in an async process. # The task will be created in DB once 'update:task' or 'update:job' webhook is received. cvat_api.put_task_data( - task.id, + cvat_task.id, cloud_storage.id, filenames=[os.path.join(data_bucket.path, fn) for fn in job_filenames], sort_images=False, ) with SessionLocal.begin() as session: - db_service.create_data_upload(session, cvat_task_id=task.id) + db_service.get_task_by_id(session, task_id, for_update=True) # lock the row + db_service.create_data_upload(session, cvat_task.id) elif manifest.annotation.type in [TaskTypes.image_boxes_from_points]: with BoxesFromPointsTaskBuilder(manifest, escrow_address, chain_id) as task_builder: From 6e633f5e1dfcaf5c3c885f50c9e820e50b07f635 Mon Sep 17 00:00:00 2001 From: Dzeranov Date: Thu, 11 Apr 2024 13:36:10 +0300 Subject: [PATCH 56/66] `oracleAddress` field removed from `WebhookIncomingEntity` (#1817) Temporarily disable sending webhooks to Recording Oracle --- .../1712220998800-removeOracleAddress.ts | 17 +++++++++++++++++ .../src/modules/cron-job/cron-job.service.ts | 13 +++++++------ .../modules/webhook/webhook-incoming.entity.ts | 3 --- 3 files changed, 24 insertions(+), 9 deletions(-) create mode 100644 packages/apps/reputation-oracle/server/src/database/migrations/1712220998800-removeOracleAddress.ts diff --git a/packages/apps/reputation-oracle/server/src/database/migrations/1712220998800-removeOracleAddress.ts b/packages/apps/reputation-oracle/server/src/database/migrations/1712220998800-removeOracleAddress.ts new file mode 100644 index 0000000000..ce0a0ef700 --- /dev/null +++ b/packages/apps/reputation-oracle/server/src/database/migrations/1712220998800-removeOracleAddress.ts @@ -0,0 +1,17 @@ +import { MigrationInterface, QueryRunner } from 'typeorm'; + +export class RemoveOracleAddress1712220998800 implements MigrationInterface { + name = 'RemoveOracleAddress1712220998800'; + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `ALTER TABLE "hmt"."webhook_incoming" DROP COLUMN "oracle_address"`, + ); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `ALTER TABLE "hmt"."webhook_incoming" ADD "oracle_address" character varying`, + ); + } +} diff --git a/packages/apps/reputation-oracle/server/src/modules/cron-job/cron-job.service.ts b/packages/apps/reputation-oracle/server/src/modules/cron-job/cron-job.service.ts index 6421f62196..70a5efa899 100644 --- a/packages/apps/reputation-oracle/server/src/modules/cron-job/cron-job.service.ts +++ b/packages/apps/reputation-oracle/server/src/modules/cron-job/cron-job.service.ts @@ -178,12 +178,13 @@ export class CronJobService { await escrowClient.getJobLauncherAddress(escrowAddress), ) ).webhookUrl, - ( - await OperatorUtils.getLeader( - chainId, - await escrowClient.getRecordingOracleAddress(escrowAddress), - ) - ).webhookUrl, + // Temporarily disable sending webhook to Recoring Oracle + // ( + // await OperatorUtils.getLeader( + // chainId, + // await escrowClient.getRecordingOracleAddress(escrowAddress), + // ) + // ).webhookUrl, ]; const webhookBody: WebhookDto = { chainId, diff --git a/packages/apps/reputation-oracle/server/src/modules/webhook/webhook-incoming.entity.ts b/packages/apps/reputation-oracle/server/src/modules/webhook/webhook-incoming.entity.ts index 7d5be0bcbe..386311b37f 100644 --- a/packages/apps/reputation-oracle/server/src/modules/webhook/webhook-incoming.entity.ts +++ b/packages/apps/reputation-oracle/server/src/modules/webhook/webhook-incoming.entity.ts @@ -11,9 +11,6 @@ export class WebhookIncomingEntity extends BaseEntity { @Column({ type: 'int' }) public chainId: ChainId; - @Column({ type: 'varchar', nullable: true }) - public oracleAddress: string; - @Column({ type: 'varchar' }) public escrowAddress: string; From 0c413b4ec9aba82599b4d70a4a574dceeabef943 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Francisco=20L=C3=B3pez?= <50665615+flopez7@users.noreply.github.com> Date: Fri, 12 Apr 2024 07:40:55 +0200 Subject: [PATCH 57/66] [Job Launcher] Display partially paid escrows (#1856) * Display partially paid escrows on Job Launcher * Fix subgraph tests --- .../components/Jobs/StatusToggleButtons.tsx | 1 + .../client/src/layouts/AuthLayout.tsx | 3 + .../client/src/pages/Job/JobList/index.tsx | 1 + .../job-launcher/client/src/types/index.ts | 1 + .../server/src/common/enums/job.ts | 2 + .../server/src/common/utils/status.ts | 9 +- .../1712826368303-addPartialStatus.ts | 60 ++++++++++++ .../src/modules/job/job.service.spec.ts | 95 ++++++++++++++++++- .../server/src/modules/job/job.service.ts | 33 +++---- .../typescript/subgraph/src/mapping/Escrow.ts | 2 +- .../subgraph/tests/escrow/escrow.test.ts | 7 +- 11 files changed, 178 insertions(+), 36 deletions(-) create mode 100644 packages/apps/job-launcher/server/src/database/migrations/1712826368303-addPartialStatus.ts diff --git a/packages/apps/job-launcher/client/src/components/Jobs/StatusToggleButtons.tsx b/packages/apps/job-launcher/client/src/components/Jobs/StatusToggleButtons.tsx index 8470fbc1c6..ecd1015eac 100644 --- a/packages/apps/job-launcher/client/src/components/Jobs/StatusToggleButtons.tsx +++ b/packages/apps/job-launcher/client/src/components/Jobs/StatusToggleButtons.tsx @@ -8,6 +8,7 @@ import { JobStatus } from '../../types'; const RANGE_BUTTONS = [ { label: 'Launched', value: JobStatus.LAUNCHED }, { label: 'Pending', value: JobStatus.PENDING }, + { label: 'Partial', value: JobStatus.PARTIAL }, { label: 'Completed', value: JobStatus.COMPLETED }, { label: 'Canceled', value: JobStatus.CANCELED }, { label: 'Failed', value: JobStatus.FAILED }, diff --git a/packages/apps/job-launcher/client/src/layouts/AuthLayout.tsx b/packages/apps/job-launcher/client/src/layouts/AuthLayout.tsx index d2b13902cc..db03ad1081 100644 --- a/packages/apps/job-launcher/client/src/layouts/AuthLayout.tsx +++ b/packages/apps/job-launcher/client/src/layouts/AuthLayout.tsx @@ -60,6 +60,9 @@ export default function AuthLayout() { Pending + + Partial + Completed diff --git a/packages/apps/job-launcher/client/src/pages/Job/JobList/index.tsx b/packages/apps/job-launcher/client/src/pages/Job/JobList/index.tsx index 1871b395eb..bc0769213d 100644 --- a/packages/apps/job-launcher/client/src/pages/Job/JobList/index.tsx +++ b/packages/apps/job-launcher/client/src/pages/Job/JobList/index.tsx @@ -8,6 +8,7 @@ import { JobStatus } from '../../../types'; const JOB_NAV_ITEMS = [ { status: JobStatus.LAUNCHED, label: 'launched' }, + { status: JobStatus.PARTIAL, label: 'partial' }, { status: JobStatus.COMPLETED, label: 'completed' }, { status: JobStatus.PENDING, label: 'pending' }, { status: JobStatus.CANCELED, label: 'canceled' }, diff --git a/packages/apps/job-launcher/client/src/types/index.ts b/packages/apps/job-launcher/client/src/types/index.ts index da10a02a63..fb5c8145c9 100644 --- a/packages/apps/job-launcher/client/src/types/index.ts +++ b/packages/apps/job-launcher/client/src/types/index.ts @@ -235,6 +235,7 @@ export type JobRequest = { export enum JobStatus { LAUNCHED = 'LAUNCHED', PENDING = 'PENDING', + PARTIAL = 'PARTIAL', CANCELED = 'CANCELED', FAILED = 'FAILED', COMPLETED = 'COMPLETED', diff --git a/packages/apps/job-launcher/server/src/common/enums/job.ts b/packages/apps/job-launcher/server/src/common/enums/job.ts index 1ced28c009..b852d9a666 100644 --- a/packages/apps/job-launcher/server/src/common/enums/job.ts +++ b/packages/apps/job-launcher/server/src/common/enums/job.ts @@ -4,6 +4,7 @@ export enum JobStatus { CREATED = 'CREATED', SET_UP = 'SET_UP', LAUNCHED = 'LAUNCHED', + PARTIAL = 'PARTIAL', COMPLETED = 'COMPLETED', FAILED = 'FAILED', TO_CANCEL = 'TO_CANCEL', @@ -13,6 +14,7 @@ export enum JobStatus { export enum JobStatusFilter { PENDING = 'PENDING', LAUNCHED = 'LAUNCHED', + PARTIAL = 'PARTIAL', COMPLETED = 'COMPLETED', FAILED = 'FAILED', CANCELED = 'CANCELED', diff --git a/packages/apps/job-launcher/server/src/common/utils/status.ts b/packages/apps/job-launcher/server/src/common/utils/status.ts index 509984e4e9..8cddb55caf 100644 --- a/packages/apps/job-launcher/server/src/common/utils/status.ts +++ b/packages/apps/job-launcher/server/src/common/utils/status.ts @@ -5,16 +5,13 @@ export function filterToEscrowStatus( filterStatus: JobStatusFilter, ): EscrowStatus[] { switch (filterStatus) { + case JobStatusFilter.PARTIAL: + return [EscrowStatus.Partial]; case JobStatusFilter.COMPLETED: return [EscrowStatus.Complete]; case JobStatusFilter.CANCELED: return [EscrowStatus.Cancelled]; default: - return [ - EscrowStatus.Launched, - EscrowStatus.Pending, - EscrowStatus.Partial, - EscrowStatus.Paid, - ]; + return [EscrowStatus.Launched, EscrowStatus.Pending, EscrowStatus.Paid]; } } diff --git a/packages/apps/job-launcher/server/src/database/migrations/1712826368303-addPartialStatus.ts b/packages/apps/job-launcher/server/src/database/migrations/1712826368303-addPartialStatus.ts new file mode 100644 index 0000000000..e115d82c70 --- /dev/null +++ b/packages/apps/job-launcher/server/src/database/migrations/1712826368303-addPartialStatus.ts @@ -0,0 +1,60 @@ +import { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddPartialStatus1712826368303 implements MigrationInterface { + name = 'AddPartialStatus1712826368303'; + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + ALTER TYPE "hmt"."jobs_status_enum" + RENAME TO "jobs_status_enum_old" + `); + await queryRunner.query(` + CREATE TYPE "hmt"."jobs_status_enum" AS ENUM( + 'PENDING', + 'PAID', + 'CREATED', + 'SET_UP', + 'LAUNCHED', + 'PARTIAL', + 'COMPLETED', + 'FAILED', + 'TO_CANCEL', + 'CANCELED' + ) + `); + await queryRunner.query(` + ALTER TABLE "hmt"."jobs" + ALTER COLUMN "status" TYPE "hmt"."jobs_status_enum" USING "status"::"text"::"hmt"."jobs_status_enum" + `); + await queryRunner.query(` + DROP TYPE "hmt"."jobs_status_enum_old" + `); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + CREATE TYPE "hmt"."jobs_status_enum_old" AS ENUM( + 'PENDING', + 'PAID', + 'CREATED', + 'SET_UP', + 'LAUNCHED', + 'COMPLETED', + 'FAILED', + 'TO_CANCEL', + 'CANCELED' + ) + `); + await queryRunner.query(` + ALTER TABLE "hmt"."jobs" + ALTER COLUMN "status" TYPE "hmt"."jobs_status_enum_old" USING "status"::"text"::"hmt"."jobs_status_enum_old" + `); + await queryRunner.query(` + DROP TYPE "hmt"."jobs_status_enum" + `); + await queryRunner.query(` + ALTER TYPE "hmt"."jobs_status_enum_old" + RENAME TO "jobs_status_enum" + `); + } +} diff --git a/packages/apps/job-launcher/server/src/modules/job/job.service.spec.ts b/packages/apps/job-launcher/server/src/modules/job/job.service.spec.ts index 10aa757205..046fab17a7 100644 --- a/packages/apps/job-launcher/server/src/modules/job/job.service.spec.ts +++ b/packages/apps/job-launcher/server/src/modules/job/job.service.spec.ts @@ -2579,7 +2579,6 @@ describe('JobService', () => { MOCK_ADDRESS, MOCK_ADDRESS, MOCK_ADDRESS, - MOCK_ADDRESS, ]); }); it('should call the database with CANCELLED status', async () => { @@ -2598,6 +2597,100 @@ describe('JobService', () => { limit, ); }); + + it('should call subgraph and database with COMPLETED status', async () => { + const jobEntityMock = [ + { + status: JobStatus.LAUNCHED, + fundAmount: 100, + userId: 1, + id: 1, + escrowAddress: MOCK_ADDRESS, + chainId: ChainId.LOCALHOST, + }, + ]; + const getEscrowsData = [ + { + address: MOCK_ADDRESS, + status: EscrowStatus[EscrowStatus.Complete], + }, + ]; + jobRepository.findByEscrowAddresses = jest + .fn() + .mockResolvedValue(jobEntityMock as any); + jobRepository.updateOne = jest + .fn() + .mockResolvedValue({ status: JobStatus.COMPLETED }); + EscrowUtils.getEscrows = jest.fn().mockResolvedValue(getEscrowsData); + + const results = await jobService.getJobsByStatus( + [ChainId.LOCALHOST], + userId, + JobStatusFilter.COMPLETED, + skip, + limit, + ); + + expect(results).toMatchObject([ + { + status: JobStatus.COMPLETED, + fundAmount: 100, + jobId: 1, + escrowAddress: MOCK_ADDRESS, + network: NETWORKS[ChainId.LOCALHOST]?.title, + }, + ]); + expect(jobRepository.findByEscrowAddresses).toHaveBeenCalledWith(userId, [ + MOCK_ADDRESS, + ]); + }); + + it('should call subgraph and database with PARTIAL status', async () => { + const jobEntityMock = [ + { + status: JobStatus.LAUNCHED, + fundAmount: 100, + userId: 1, + id: 1, + escrowAddress: MOCK_ADDRESS, + chainId: ChainId.LOCALHOST, + }, + ]; + const getEscrowsData = [ + { + address: MOCK_ADDRESS, + status: EscrowStatus[EscrowStatus.Partial], + }, + ]; + jobRepository.findByEscrowAddresses = jest + .fn() + .mockResolvedValue(jobEntityMock as any); + jobRepository.updateOne = jest + .fn() + .mockResolvedValue({ status: JobStatus.PARTIAL }); + EscrowUtils.getEscrows = jest.fn().mockResolvedValue(getEscrowsData); + + const results = await jobService.getJobsByStatus( + [ChainId.LOCALHOST], + userId, + JobStatusFilter.PARTIAL, + skip, + limit, + ); + + expect(results).toMatchObject([ + { + status: JobStatus.PARTIAL, + fundAmount: 100, + jobId: 1, + escrowAddress: MOCK_ADDRESS, + network: NETWORKS[ChainId.LOCALHOST]?.title, + }, + ]); + expect(jobRepository.findByEscrowAddresses).toHaveBeenCalledWith(userId, [ + MOCK_ADDRESS, + ]); + }); }); describe('escrowFailedWebhook', () => { diff --git a/packages/apps/job-launcher/server/src/modules/job/job.service.ts b/packages/apps/job-launcher/server/src/modules/job/job.service.ts index bf64de9e44..5b33fbf4bd 100644 --- a/packages/apps/job-launcher/server/src/modules/job/job.service.ts +++ b/packages/apps/job-launcher/server/src/modules/job/job.service.ts @@ -949,25 +949,6 @@ export class JobService { await this.jobRepository.updateOne(jobEntity); } - private getOracleAddresses(manifest: any) { - let exchangeOracle; - let recordingOracle; - const oracleType = this.getOracleType(manifest); - if (oracleType === OracleType.FORTUNE) { - exchangeOracle = this.web3ConfigService.fortuneExchangeOracleAddress; - recordingOracle = this.web3ConfigService.fortuneRecordingOracleAddress; - } else if (oracleType === OracleType.HCAPTCHA) { - exchangeOracle = this.web3ConfigService.hCaptchaOracleAddress; - recordingOracle = this.web3ConfigService.hCaptchaOracleAddress; - } else { - exchangeOracle = this.web3ConfigService.cvatExchangeOracleAddress; - recordingOracle = this.web3ConfigService.cvatRecordingOracleAddress; - } - const reputationOracle = this.web3ConfigService.reputationOracleAddress; - - return { exchangeOracle, recordingOracle, reputationOracle }; - } - public async uploadManifest( requestType: JobRequestType, chainId: ChainId, @@ -1068,6 +1049,7 @@ export class JobService { ); break; case JobStatusFilter.LAUNCHED: + case JobStatusFilter.PARTIAL: case JobStatusFilter.COMPLETED: escrows = await this.findEscrowsByStatus( networks, @@ -1149,7 +1131,7 @@ export class JobService { escrow.address.toLowerCase() === job.escrowAddress.toLowerCase(), ); if (escrow) { - const newJob = await this.updateCompletedStatus(job, escrow); + const newJob = await this.updateJobStatus(job, escrow); return newJob.status; } } @@ -1334,7 +1316,7 @@ export class JobService { escrow = await EscrowUtils.getEscrow(chainId, escrowAddress); allocation = await stakingClient.getAllocation(escrowAddress); - jobEntity = await this.updateCompletedStatus(jobEntity, escrow); + jobEntity = await this.updateJobStatus(jobEntity, escrow); } let manifestData = await this.storageService.download(manifestUrl); @@ -1497,7 +1479,7 @@ export class JobService { return BigInt(feeValue ? feeValue : 1); } - private async updateCompletedStatus( + private async updateJobStatus( job: JobEntity, escrow: EscrowData, ): Promise { @@ -1509,6 +1491,13 @@ export class JobService { job.status = JobStatus.COMPLETED; updatedJob = await this.jobRepository.updateOne(job); } + if ( + escrow.status === EscrowStatus[EscrowStatus.Partial] && + job.status !== JobStatus.PARTIAL + ) { + job.status = JobStatus.PARTIAL; + updatedJob = await this.jobRepository.updateOne(job); + } return updatedJob; } diff --git a/packages/sdk/typescript/subgraph/src/mapping/Escrow.ts b/packages/sdk/typescript/subgraph/src/mapping/Escrow.ts index 9c95875454..9d28112450 100644 --- a/packages/sdk/typescript/subgraph/src/mapping/Escrow.ts +++ b/packages/sdk/typescript/subgraph/src/mapping/Escrow.ts @@ -233,7 +233,7 @@ export function handleBulkTransfer(event: BulkTransfer): void { // Update escrow entity const escrowEntity = Escrow.load(dataSource.address().toHex()); if (escrowEntity) { - escrowEntity.status = event.params._isPartial ? 'Partially Paid' : 'Paid'; + escrowEntity.status = event.params._isPartial ? 'Partial' : 'Paid'; // Read data on-chain const escrowContract = EscrowContract.bind(event.address); diff --git a/packages/sdk/typescript/subgraph/tests/escrow/escrow.test.ts b/packages/sdk/typescript/subgraph/tests/escrow/escrow.test.ts index 0d29316ec5..8f131ffffb 100644 --- a/packages/sdk/typescript/subgraph/tests/escrow/escrow.test.ts +++ b/packages/sdk/typescript/subgraph/tests/escrow/escrow.test.ts @@ -611,12 +611,7 @@ describe('Escrow', () => { ); // Escrow - assert.fieldEquals( - 'Escrow', - escrowAddress.toHex(), - 'status', - 'Partially Paid' - ); + assert.fieldEquals('Escrow', escrowAddress.toHex(), 'status', 'Partial'); // Bulk 2 const bulk2 = createBulkTransferEvent( From a750c447c70b1a698fa852aadc83be8d778b22c1 Mon Sep 17 00:00:00 2001 From: Mehdi <75360886+mrhouzlane@users.noreply.github.com> Date: Mon, 15 Apr 2024 10:32:50 +0200 Subject: [PATCH 58/66] [SDK] Enhance Operator Data (#1849) * modify getReputationNetworkOperators * update test * test fixed * job_types not optional * update * modify python sdk * change schema * modify subgraph schema * update subgraph * update subgraph * update python sdk * remove job type as param * undo changes * make fixes * changes * fix sdks * fix test * undo subgraph * change subgraph url in tests --------- Co-authored-by: portuu3 --- .../python/human_protocol_sdk.constants.md | 2 + docs/sdk/python/human_protocol_sdk.md | 1 + ...an_protocol_sdk.operator.operator_utils.md | 11 +- .../classes/base.BaseEthersClient.md | 6 +- .../classes/encryption.Encryption.md | 12 +- .../classes/encryption.EncryptionUtils.md | 10 +- .../typescript/classes/escrow.EscrowClient.md | 56 ++++----- .../typescript/classes/escrow.EscrowUtils.md | 4 +- .../classes/kvstore.KVStoreClient.md | 22 ++-- .../classes/operator.OperatorUtils.md | 8 +- .../classes/staking.StakingClient.md | 36 +++--- .../classes/statistics.StatisticsClient.md | 12 +- .../classes/storage.StorageClient.md | 16 +-- .../apps/job-launcher/server/test/setup.ts | 17 ++- .../sdk/python/human-protocol-sdk/example.py | 114 ++---------------- .../human_protocol_sdk/agreement/measures.py | 12 +- .../human_protocol_sdk/constants.py | 3 +- .../human_protocol_sdk/gql/escrow.py | 4 +- .../human_protocol_sdk/gql/hmtoken.py | 4 +- .../human_protocol_sdk/gql/operator.py | 4 +- .../human_protocol_sdk/gql/reward.py | 4 +- .../human_protocol_sdk/gql/statistics.py | 8 +- .../operator/operator_utils.py | 16 ++- .../operator/test_operator_utils.py | 13 +- .../statistics/test_statistics_client.py | 14 +-- .../human-protocol-sdk/src/constants.ts | 2 +- .../src/graphql/queries/operator.ts | 5 +- .../human-protocol-sdk/src/interfaces.ts | 2 + .../human-protocol-sdk/src/kvstore.ts | 2 +- .../human-protocol-sdk/test/escrow.test.ts | 1 - .../human-protocol-sdk/test/kvstore.test.ts | 2 - .../human-protocol-sdk/test/operator.test.ts | 2 + .../sdk/typescript/subgraph/schema.graphql | 1 + .../subgraph/src/mapping/KVStore.ts | 4 + 34 files changed, 196 insertions(+), 234 deletions(-) diff --git a/docs/sdk/python/human_protocol_sdk.constants.md b/docs/sdk/python/human_protocol_sdk.constants.md index 4d3e23b977..ffca349691 100644 --- a/docs/sdk/python/human_protocol_sdk.constants.md +++ b/docs/sdk/python/human_protocol_sdk.constants.md @@ -44,6 +44,8 @@ Enum for KVStore keys #### fee *= 'fee'* +#### job_types *= 'job_types'* + #### public_key *= 'public_key'* #### role *= 'role'* diff --git a/docs/sdk/python/human_protocol_sdk.md b/docs/sdk/python/human_protocol_sdk.md index 15b821b663..9e84eed9c2 100644 --- a/docs/sdk/python/human_protocol_sdk.md +++ b/docs/sdk/python/human_protocol_sdk.md @@ -110,6 +110,7 @@ * [`ChainId.SKALE`](human_protocol_sdk.constants.md#human_protocol_sdk.constants.ChainId.SKALE) * [`KVStoreKeys`](human_protocol_sdk.constants.md#human_protocol_sdk.constants.KVStoreKeys) * [`KVStoreKeys.fee`](human_protocol_sdk.constants.md#human_protocol_sdk.constants.KVStoreKeys.fee) + * [`KVStoreKeys.job_types`](human_protocol_sdk.constants.md#human_protocol_sdk.constants.KVStoreKeys.job_types) * [`KVStoreKeys.public_key`](human_protocol_sdk.constants.md#human_protocol_sdk.constants.KVStoreKeys.public_key) * [`KVStoreKeys.role`](human_protocol_sdk.constants.md#human_protocol_sdk.constants.KVStoreKeys.role) * [`KVStoreKeys.url`](human_protocol_sdk.constants.md#human_protocol_sdk.constants.KVStoreKeys.url) diff --git a/docs/sdk/python/human_protocol_sdk.operator.operator_utils.md b/docs/sdk/python/human_protocol_sdk.operator.operator_utils.md index d774829b84..e478de1c87 100644 --- a/docs/sdk/python/human_protocol_sdk.operator.operator_utils.md +++ b/docs/sdk/python/human_protocol_sdk.operator.operator_utils.md @@ -17,11 +17,11 @@ print( ## Module -### *class* human_protocol_sdk.operator.operator_utils.LeaderData(chain_id, id, address, amount_staked, amount_allocated, amount_locked, locked_until_timestamp, amount_withdrawn, amount_slashed, reputation, reward, amount_jobs_launched, role=None, fee=None, public_key=None, webhook_url=None, url=None) +### *class* human_protocol_sdk.operator.operator_utils.LeaderData(chain_id, id, address, amount_staked, amount_allocated, amount_locked, locked_until_timestamp, amount_withdrawn, amount_slashed, reputation, reward, amount_jobs_launched, role=None, fee=None, public_key=None, webhook_url=None, url=None, job_types=None) Bases: `object` -#### \_\_init_\_(chain_id, id, address, amount_staked, amount_allocated, amount_locked, locked_until_timestamp, amount_withdrawn, amount_slashed, reputation, reward, amount_jobs_launched, role=None, fee=None, public_key=None, webhook_url=None, url=None) +#### \_\_init_\_(chain_id, id, address, amount_staked, amount_allocated, amount_locked, locked_until_timestamp, amount_withdrawn, amount_slashed, reputation, reward, amount_jobs_launched, role=None, fee=None, public_key=None, webhook_url=None, url=None, job_types=None) Initializes an LeaderData instance. @@ -43,6 +43,7 @@ Initializes an LeaderData instance. * **public_key** (`Optional`[`str`]) – Public key * **webhook_url** (`Optional`[`str`]) – Webhook url * **url** (`Optional`[`str`]) – Url + * **job_types** (`Optional`[`str`]) – Job types ### *class* human_protocol_sdk.operator.operator_utils.LeaderFilter(networks, role=None) @@ -58,11 +59,11 @@ Initializes a LeaderFilter instance. * **networks** (`List`[[`ChainId`](human_protocol_sdk.constants.md#human_protocol_sdk.constants.ChainId)]) – Networks to request data * **role** (`Optional`[`str`]) – Leader role -### *class* human_protocol_sdk.operator.operator_utils.Operator(address, role) +### *class* human_protocol_sdk.operator.operator_utils.Operator(address, role, url='', job_types='') Bases: `object` -#### \_\_init_\_(address, role) +#### \_\_init_\_(address, role, url='', job_types='') Initializes an Operator instance. @@ -128,6 +129,8 @@ Get the reputation network operators of the specified address. * **chain_id** ([`ChainId`](human_protocol_sdk.constants.md#human_protocol_sdk.constants.ChainId)) – Network in which the reputation network exists * **address** (`str`) – Address of the reputation oracle * **role** (`Optional`[`str`]) – (Optional) Role of the operator +* **Parem job_types:** + (Optional) Job types of the operator * **Return type:** `List`[[`Operator`](#human_protocol_sdk.operator.operator_utils.Operator)] * **Returns:** diff --git a/docs/sdk/typescript/classes/base.BaseEthersClient.md b/docs/sdk/typescript/classes/base.BaseEthersClient.md index e75e8362d4..f40127c63b 100644 --- a/docs/sdk/typescript/classes/base.BaseEthersClient.md +++ b/docs/sdk/typescript/classes/base.BaseEthersClient.md @@ -50,7 +50,7 @@ This class is used as a base class for other clients making on-chain calls. #### Defined in -[base.ts:20](https://github.com/humanprotocol/human-protocol/blob/48066071/packages/sdk/typescript/human-protocol-sdk/src/base.ts#L20) +[base.ts:20](https://github.com/humanprotocol/human-protocol/blob/e4b60ab1/packages/sdk/typescript/human-protocol-sdk/src/base.ts#L20) ## Properties @@ -60,7 +60,7 @@ This class is used as a base class for other clients making on-chain calls. #### Defined in -[base.ts:12](https://github.com/humanprotocol/human-protocol/blob/48066071/packages/sdk/typescript/human-protocol-sdk/src/base.ts#L12) +[base.ts:12](https://github.com/humanprotocol/human-protocol/blob/e4b60ab1/packages/sdk/typescript/human-protocol-sdk/src/base.ts#L12) ___ @@ -70,4 +70,4 @@ ___ #### Defined in -[base.ts:11](https://github.com/humanprotocol/human-protocol/blob/48066071/packages/sdk/typescript/human-protocol-sdk/src/base.ts#L11) +[base.ts:11](https://github.com/humanprotocol/human-protocol/blob/e4b60ab1/packages/sdk/typescript/human-protocol-sdk/src/base.ts#L11) diff --git a/docs/sdk/typescript/classes/encryption.Encryption.md b/docs/sdk/typescript/classes/encryption.Encryption.md index be5ada8b27..c662c7c5d4 100644 --- a/docs/sdk/typescript/classes/encryption.Encryption.md +++ b/docs/sdk/typescript/classes/encryption.Encryption.md @@ -81,7 +81,7 @@ Constructor for the Encryption class. #### Defined in -[encryption.ts:53](https://github.com/humanprotocol/human-protocol/blob/48066071/packages/sdk/typescript/human-protocol-sdk/src/encryption.ts#L53) +[encryption.ts:53](https://github.com/humanprotocol/human-protocol/blob/e4b60ab1/packages/sdk/typescript/human-protocol-sdk/src/encryption.ts#L53) ## Properties @@ -91,7 +91,7 @@ Constructor for the Encryption class. #### Defined in -[encryption.ts:46](https://github.com/humanprotocol/human-protocol/blob/48066071/packages/sdk/typescript/human-protocol-sdk/src/encryption.ts#L46) +[encryption.ts:46](https://github.com/humanprotocol/human-protocol/blob/e4b60ab1/packages/sdk/typescript/human-protocol-sdk/src/encryption.ts#L46) ## Methods @@ -140,7 +140,7 @@ const resultMessage = await encription.decrypt('message'); #### Defined in -[encryption.ts:180](https://github.com/humanprotocol/human-protocol/blob/48066071/packages/sdk/typescript/human-protocol-sdk/src/encryption.ts#L180) +[encryption.ts:180](https://github.com/humanprotocol/human-protocol/blob/e4b60ab1/packages/sdk/typescript/human-protocol-sdk/src/encryption.ts#L180) ___ @@ -176,7 +176,7 @@ const resultMessage = await encription.sign('message'); #### Defined in -[encryption.ts:217](https://github.com/humanprotocol/human-protocol/blob/48066071/packages/sdk/typescript/human-protocol-sdk/src/encryption.ts#L217) +[encryption.ts:217](https://github.com/humanprotocol/human-protocol/blob/e4b60ab1/packages/sdk/typescript/human-protocol-sdk/src/encryption.ts#L217) ___ @@ -238,7 +238,7 @@ const resultMessage = await encription.signAndEncrypt('message', publicKeys); #### Defined in -[encryption.ts:129](https://github.com/humanprotocol/human-protocol/blob/48066071/packages/sdk/typescript/human-protocol-sdk/src/encryption.ts#L129) +[encryption.ts:129](https://github.com/humanprotocol/human-protocol/blob/e4b60ab1/packages/sdk/typescript/human-protocol-sdk/src/encryption.ts#L129) ___ @@ -263,4 +263,4 @@ Builds an Encryption instance by decrypting the private key from an encrypted pr #### Defined in -[encryption.ts:64](https://github.com/humanprotocol/human-protocol/blob/48066071/packages/sdk/typescript/human-protocol-sdk/src/encryption.ts#L64) +[encryption.ts:64](https://github.com/humanprotocol/human-protocol/blob/e4b60ab1/packages/sdk/typescript/human-protocol-sdk/src/encryption.ts#L64) diff --git a/docs/sdk/typescript/classes/encryption.EncryptionUtils.md b/docs/sdk/typescript/classes/encryption.EncryptionUtils.md index 20cf3b2cac..70bea3054a 100644 --- a/docs/sdk/typescript/classes/encryption.EncryptionUtils.md +++ b/docs/sdk/typescript/classes/encryption.EncryptionUtils.md @@ -108,7 +108,7 @@ const result = await EncriptionUtils.encrypt('message', publicKeys); #### Defined in -[encryption.ts:422](https://github.com/humanprotocol/human-protocol/blob/48066071/packages/sdk/typescript/human-protocol-sdk/src/encryption.ts#L422) +[encryption.ts:422](https://github.com/humanprotocol/human-protocol/blob/e4b60ab1/packages/sdk/typescript/human-protocol-sdk/src/encryption.ts#L422) ___ @@ -157,7 +157,7 @@ const result = await EncriptionUtils.generateKeyPair(name, email, passphrase); #### Defined in -[encryption.ts:360](https://github.com/humanprotocol/human-protocol/blob/48066071/packages/sdk/typescript/human-protocol-sdk/src/encryption.ts#L360) +[encryption.ts:360](https://github.com/humanprotocol/human-protocol/blob/e4b60ab1/packages/sdk/typescript/human-protocol-sdk/src/encryption.ts#L360) ___ @@ -189,7 +189,7 @@ const signedData = await EncriptionUtils.getSignedData('message'); #### Defined in -[encryption.ts:317](https://github.com/humanprotocol/human-protocol/blob/48066071/packages/sdk/typescript/human-protocol-sdk/src/encryption.ts#L317) +[encryption.ts:317](https://github.com/humanprotocol/human-protocol/blob/e4b60ab1/packages/sdk/typescript/human-protocol-sdk/src/encryption.ts#L317) ___ @@ -237,7 +237,7 @@ if (isEncrypted) { #### Defined in -[encryption.ts:471](https://github.com/humanprotocol/human-protocol/blob/48066071/packages/sdk/typescript/human-protocol-sdk/src/encryption.ts#L471) +[encryption.ts:471](https://github.com/humanprotocol/human-protocol/blob/e4b60ab1/packages/sdk/typescript/human-protocol-sdk/src/encryption.ts#L471) ___ @@ -282,4 +282,4 @@ const result = await EncriptionUtils.verify('message', publicKey); #### Defined in -[encryption.ts:284](https://github.com/humanprotocol/human-protocol/blob/48066071/packages/sdk/typescript/human-protocol-sdk/src/encryption.ts#L284) +[encryption.ts:284](https://github.com/humanprotocol/human-protocol/blob/e4b60ab1/packages/sdk/typescript/human-protocol-sdk/src/encryption.ts#L284) diff --git a/docs/sdk/typescript/classes/escrow.EscrowClient.md b/docs/sdk/typescript/classes/escrow.EscrowClient.md index 4885a7c969..a22887728b 100644 --- a/docs/sdk/typescript/classes/escrow.EscrowClient.md +++ b/docs/sdk/typescript/classes/escrow.EscrowClient.md @@ -142,7 +142,7 @@ const escrowClient = await EscrowClient.build(provider); #### Defined in -[escrow.ts:127](https://github.com/humanprotocol/human-protocol/blob/48066071/packages/sdk/typescript/human-protocol-sdk/src/escrow.ts#L127) +[escrow.ts:127](https://github.com/humanprotocol/human-protocol/blob/e4b60ab1/packages/sdk/typescript/human-protocol-sdk/src/escrow.ts#L127) ## Properties @@ -152,7 +152,7 @@ const escrowClient = await EscrowClient.build(provider); #### Defined in -[escrow.ts:119](https://github.com/humanprotocol/human-protocol/blob/48066071/packages/sdk/typescript/human-protocol-sdk/src/escrow.ts#L119) +[escrow.ts:119](https://github.com/humanprotocol/human-protocol/blob/e4b60ab1/packages/sdk/typescript/human-protocol-sdk/src/escrow.ts#L119) ___ @@ -166,7 +166,7 @@ ___ #### Defined in -[base.ts:12](https://github.com/humanprotocol/human-protocol/blob/48066071/packages/sdk/typescript/human-protocol-sdk/src/base.ts#L12) +[base.ts:12](https://github.com/humanprotocol/human-protocol/blob/e4b60ab1/packages/sdk/typescript/human-protocol-sdk/src/base.ts#L12) ___ @@ -180,7 +180,7 @@ ___ #### Defined in -[base.ts:11](https://github.com/humanprotocol/human-protocol/blob/48066071/packages/sdk/typescript/human-protocol-sdk/src/base.ts#L11) +[base.ts:11](https://github.com/humanprotocol/human-protocol/blob/e4b60ab1/packages/sdk/typescript/human-protocol-sdk/src/base.ts#L11) ## Methods @@ -223,7 +223,7 @@ await escrowClient.abort('0x62dD51230A30401C455c8398d06F85e4EaB6309f'); #### Defined in -[escrow.ts:835](https://github.com/humanprotocol/human-protocol/blob/48066071/packages/sdk/typescript/human-protocol-sdk/src/escrow.ts#L835) +[escrow.ts:835](https://github.com/humanprotocol/human-protocol/blob/e4b60ab1/packages/sdk/typescript/human-protocol-sdk/src/escrow.ts#L835) ___ @@ -268,7 +268,7 @@ await escrowClient.addTrustedHandlers('0x62dD51230A30401C455c8398d06F85e4EaB6309 #### Defined in -[escrow.ts:883](https://github.com/humanprotocol/human-protocol/blob/48066071/packages/sdk/typescript/human-protocol-sdk/src/escrow.ts#L883) +[escrow.ts:883](https://github.com/humanprotocol/human-protocol/blob/e4b60ab1/packages/sdk/typescript/human-protocol-sdk/src/escrow.ts#L883) ___ @@ -320,7 +320,7 @@ await escrowClient.bulkPayOut('0x62dD51230A30401C455c8398d06F85e4EaB6309f', reci #### Defined in -[escrow.ts:648](https://github.com/humanprotocol/human-protocol/blob/48066071/packages/sdk/typescript/human-protocol-sdk/src/escrow.ts#L648) +[escrow.ts:648](https://github.com/humanprotocol/human-protocol/blob/e4b60ab1/packages/sdk/typescript/human-protocol-sdk/src/escrow.ts#L648) ___ @@ -363,7 +363,7 @@ await escrowClient.cancel('0x62dD51230A30401C455c8398d06F85e4EaB6309f'); #### Defined in -[escrow.ts:751](https://github.com/humanprotocol/human-protocol/blob/48066071/packages/sdk/typescript/human-protocol-sdk/src/escrow.ts#L751) +[escrow.ts:751](https://github.com/humanprotocol/human-protocol/blob/e4b60ab1/packages/sdk/typescript/human-protocol-sdk/src/escrow.ts#L751) ___ @@ -406,7 +406,7 @@ await escrowClient.complete('0x62dD51230A30401C455c8398d06F85e4EaB6309f'); #### Defined in -[escrow.ts:590](https://github.com/humanprotocol/human-protocol/blob/48066071/packages/sdk/typescript/human-protocol-sdk/src/escrow.ts#L590) +[escrow.ts:590](https://github.com/humanprotocol/human-protocol/blob/e4b60ab1/packages/sdk/typescript/human-protocol-sdk/src/escrow.ts#L590) ___ @@ -464,7 +464,7 @@ const escrowAddress = await escrowClient.createAndSetupEscrow(tokenAddress, trus #### Defined in -[escrow.ts:413](https://github.com/humanprotocol/human-protocol/blob/48066071/packages/sdk/typescript/human-protocol-sdk/src/escrow.ts#L413) +[escrow.ts:413](https://github.com/humanprotocol/human-protocol/blob/e4b60ab1/packages/sdk/typescript/human-protocol-sdk/src/escrow.ts#L413) ___ @@ -512,7 +512,7 @@ const escrowAddress = await escrowClient.createEscrow(tokenAddress, trustedHandl #### Defined in -[escrow.ts:207](https://github.com/humanprotocol/human-protocol/blob/48066071/packages/sdk/typescript/human-protocol-sdk/src/escrow.ts#L207) +[escrow.ts:207](https://github.com/humanprotocol/human-protocol/blob/e4b60ab1/packages/sdk/typescript/human-protocol-sdk/src/escrow.ts#L207) ___ @@ -555,7 +555,7 @@ await escrowClient.fund('0x62dD51230A30401C455c8398d06F85e4EaB6309f', amount); #### Defined in -[escrow.ts:461](https://github.com/humanprotocol/human-protocol/blob/48066071/packages/sdk/typescript/human-protocol-sdk/src/escrow.ts#L461) +[escrow.ts:461](https://github.com/humanprotocol/human-protocol/blob/e4b60ab1/packages/sdk/typescript/human-protocol-sdk/src/escrow.ts#L461) ___ @@ -593,7 +593,7 @@ const balance = await escrowClient.getBalance('0x62dD51230A30401C455c8398d06F85e #### Defined in -[escrow.ts:938](https://github.com/humanprotocol/human-protocol/blob/48066071/packages/sdk/typescript/human-protocol-sdk/src/escrow.ts#L938) +[escrow.ts:938](https://github.com/humanprotocol/human-protocol/blob/e4b60ab1/packages/sdk/typescript/human-protocol-sdk/src/escrow.ts#L938) ___ @@ -615,7 +615,7 @@ Connects to the escrow contract #### Defined in -[escrow.ts:167](https://github.com/humanprotocol/human-protocol/blob/48066071/packages/sdk/typescript/human-protocol-sdk/src/escrow.ts#L167) +[escrow.ts:167](https://github.com/humanprotocol/human-protocol/blob/e4b60ab1/packages/sdk/typescript/human-protocol-sdk/src/escrow.ts#L167) ___ @@ -653,7 +653,7 @@ const oracleAddress = await escrowClient.getExchangeOracleAddress('0x62dD51230A3 #### Defined in -[escrow.ts:1318](https://github.com/humanprotocol/human-protocol/blob/48066071/packages/sdk/typescript/human-protocol-sdk/src/escrow.ts#L1318) +[escrow.ts:1318](https://github.com/humanprotocol/human-protocol/blob/e4b60ab1/packages/sdk/typescript/human-protocol-sdk/src/escrow.ts#L1318) ___ @@ -691,7 +691,7 @@ const factoryAddress = await escrowClient.getFactoryAddress('0x62dD51230A30401C4 #### Defined in -[escrow.ts:1356](https://github.com/humanprotocol/human-protocol/blob/48066071/packages/sdk/typescript/human-protocol-sdk/src/escrow.ts#L1356) +[escrow.ts:1356](https://github.com/humanprotocol/human-protocol/blob/e4b60ab1/packages/sdk/typescript/human-protocol-sdk/src/escrow.ts#L1356) ___ @@ -729,7 +729,7 @@ const intemediateResultsUrl = await escrowClient.getIntermediateResultsUrl('0x62 #### Defined in -[escrow.ts:1090](https://github.com/humanprotocol/human-protocol/blob/48066071/packages/sdk/typescript/human-protocol-sdk/src/escrow.ts#L1090) +[escrow.ts:1090](https://github.com/humanprotocol/human-protocol/blob/e4b60ab1/packages/sdk/typescript/human-protocol-sdk/src/escrow.ts#L1090) ___ @@ -767,7 +767,7 @@ const jobLauncherAddress = await escrowClient.getJobLauncherAddress('0x62dD51230 #### Defined in -[escrow.ts:1242](https://github.com/humanprotocol/human-protocol/blob/48066071/packages/sdk/typescript/human-protocol-sdk/src/escrow.ts#L1242) +[escrow.ts:1242](https://github.com/humanprotocol/human-protocol/blob/e4b60ab1/packages/sdk/typescript/human-protocol-sdk/src/escrow.ts#L1242) ___ @@ -805,7 +805,7 @@ const manifestHash = await escrowClient.getManifestHash('0x62dD51230A30401C455c8 #### Defined in -[escrow.ts:976](https://github.com/humanprotocol/human-protocol/blob/48066071/packages/sdk/typescript/human-protocol-sdk/src/escrow.ts#L976) +[escrow.ts:976](https://github.com/humanprotocol/human-protocol/blob/e4b60ab1/packages/sdk/typescript/human-protocol-sdk/src/escrow.ts#L976) ___ @@ -843,7 +843,7 @@ const manifestUrl = await escrowClient.getManifestUrl('0x62dD51230A30401C455c839 #### Defined in -[escrow.ts:1014](https://github.com/humanprotocol/human-protocol/blob/48066071/packages/sdk/typescript/human-protocol-sdk/src/escrow.ts#L1014) +[escrow.ts:1014](https://github.com/humanprotocol/human-protocol/blob/e4b60ab1/packages/sdk/typescript/human-protocol-sdk/src/escrow.ts#L1014) ___ @@ -881,7 +881,7 @@ const oracleAddress = await escrowClient.getRecordingOracleAddress('0x62dD51230A #### Defined in -[escrow.ts:1204](https://github.com/humanprotocol/human-protocol/blob/48066071/packages/sdk/typescript/human-protocol-sdk/src/escrow.ts#L1204) +[escrow.ts:1204](https://github.com/humanprotocol/human-protocol/blob/e4b60ab1/packages/sdk/typescript/human-protocol-sdk/src/escrow.ts#L1204) ___ @@ -919,7 +919,7 @@ const oracleAddress = await escrowClient.getReputationOracleAddress('0x62dD51230 #### Defined in -[escrow.ts:1280](https://github.com/humanprotocol/human-protocol/blob/48066071/packages/sdk/typescript/human-protocol-sdk/src/escrow.ts#L1280) +[escrow.ts:1280](https://github.com/humanprotocol/human-protocol/blob/e4b60ab1/packages/sdk/typescript/human-protocol-sdk/src/escrow.ts#L1280) ___ @@ -957,7 +957,7 @@ const resultsUrl = await escrowClient.getResultsUrl('0x62dD51230A30401C455c8398d #### Defined in -[escrow.ts:1052](https://github.com/humanprotocol/human-protocol/blob/48066071/packages/sdk/typescript/human-protocol-sdk/src/escrow.ts#L1052) +[escrow.ts:1052](https://github.com/humanprotocol/human-protocol/blob/e4b60ab1/packages/sdk/typescript/human-protocol-sdk/src/escrow.ts#L1052) ___ @@ -995,7 +995,7 @@ const status = await escrowClient.getStatus('0x62dD51230A30401C455c8398d06F85e4E #### Defined in -[escrow.ts:1166](https://github.com/humanprotocol/human-protocol/blob/48066071/packages/sdk/typescript/human-protocol-sdk/src/escrow.ts#L1166) +[escrow.ts:1166](https://github.com/humanprotocol/human-protocol/blob/e4b60ab1/packages/sdk/typescript/human-protocol-sdk/src/escrow.ts#L1166) ___ @@ -1033,7 +1033,7 @@ const tokenAddress = await escrowClient.getTokenAddress('0x62dD51230A30401C455c8 #### Defined in -[escrow.ts:1128](https://github.com/humanprotocol/human-protocol/blob/48066071/packages/sdk/typescript/human-protocol-sdk/src/escrow.ts#L1128) +[escrow.ts:1128](https://github.com/humanprotocol/human-protocol/blob/e4b60ab1/packages/sdk/typescript/human-protocol-sdk/src/escrow.ts#L1128) ___ @@ -1088,7 +1088,7 @@ await escrowClient.setup(escrowAddress, escrowConfig); #### Defined in -[escrow.ts:288](https://github.com/humanprotocol/human-protocol/blob/48066071/packages/sdk/typescript/human-protocol-sdk/src/escrow.ts#L288) +[escrow.ts:288](https://github.com/humanprotocol/human-protocol/blob/e4b60ab1/packages/sdk/typescript/human-protocol-sdk/src/escrow.ts#L288) ___ @@ -1133,7 +1133,7 @@ await storeResults.storeResults('0x62dD51230A30401C455c8398d06F85e4EaB6309f', 'h #### Defined in -[escrow.ts:526](https://github.com/humanprotocol/human-protocol/blob/48066071/packages/sdk/typescript/human-protocol-sdk/src/escrow.ts#L526) +[escrow.ts:526](https://github.com/humanprotocol/human-protocol/blob/e4b60ab1/packages/sdk/typescript/human-protocol-sdk/src/escrow.ts#L526) ___ @@ -1165,4 +1165,4 @@ Thrown if the network's chainId is not supported #### Defined in -[escrow.ts:145](https://github.com/humanprotocol/human-protocol/blob/48066071/packages/sdk/typescript/human-protocol-sdk/src/escrow.ts#L145) +[escrow.ts:145](https://github.com/humanprotocol/human-protocol/blob/e4b60ab1/packages/sdk/typescript/human-protocol-sdk/src/escrow.ts#L145) diff --git a/docs/sdk/typescript/classes/escrow.EscrowUtils.md b/docs/sdk/typescript/classes/escrow.EscrowUtils.md index b05c24a6cb..f5c8ee4945 100644 --- a/docs/sdk/typescript/classes/escrow.EscrowUtils.md +++ b/docs/sdk/typescript/classes/escrow.EscrowUtils.md @@ -138,7 +138,7 @@ const escrowData = new EscrowUtils.getEscrow(ChainId.POLYGON_MUMBAI, "0x12345678 #### Defined in -[escrow.ts:1632](https://github.com/humanprotocol/human-protocol/blob/48066071/packages/sdk/typescript/human-protocol-sdk/src/escrow.ts#L1632) +[escrow.ts:1632](https://github.com/humanprotocol/human-protocol/blob/e4b60ab1/packages/sdk/typescript/human-protocol-sdk/src/escrow.ts#L1632) ___ @@ -251,4 +251,4 @@ const escrowDatas = await EscrowUtils.getEscrows(filters); #### Defined in -[escrow.ts:1504](https://github.com/humanprotocol/human-protocol/blob/48066071/packages/sdk/typescript/human-protocol-sdk/src/escrow.ts#L1504) +[escrow.ts:1504](https://github.com/humanprotocol/human-protocol/blob/e4b60ab1/packages/sdk/typescript/human-protocol-sdk/src/escrow.ts#L1504) diff --git a/docs/sdk/typescript/classes/kvstore.KVStoreClient.md b/docs/sdk/typescript/classes/kvstore.KVStoreClient.md index 302f33649e..28179499d7 100644 --- a/docs/sdk/typescript/classes/kvstore.KVStoreClient.md +++ b/docs/sdk/typescript/classes/kvstore.KVStoreClient.md @@ -125,7 +125,7 @@ const kvstoreClient = await KVStoreClient.build(signer); #### Defined in -[kvstore.ts:100](https://github.com/humanprotocol/human-protocol/blob/48066071/packages/sdk/typescript/human-protocol-sdk/src/kvstore.ts#L100) +[kvstore.ts:100](https://github.com/humanprotocol/human-protocol/blob/e4b60ab1/packages/sdk/typescript/human-protocol-sdk/src/kvstore.ts#L100) ## Properties @@ -135,7 +135,7 @@ const kvstoreClient = await KVStoreClient.build(signer); #### Defined in -[kvstore.ts:92](https://github.com/humanprotocol/human-protocol/blob/48066071/packages/sdk/typescript/human-protocol-sdk/src/kvstore.ts#L92) +[kvstore.ts:92](https://github.com/humanprotocol/human-protocol/blob/e4b60ab1/packages/sdk/typescript/human-protocol-sdk/src/kvstore.ts#L92) ___ @@ -149,7 +149,7 @@ ___ #### Defined in -[base.ts:12](https://github.com/humanprotocol/human-protocol/blob/48066071/packages/sdk/typescript/human-protocol-sdk/src/base.ts#L12) +[base.ts:12](https://github.com/humanprotocol/human-protocol/blob/e4b60ab1/packages/sdk/typescript/human-protocol-sdk/src/base.ts#L12) ___ @@ -163,7 +163,7 @@ ___ #### Defined in -[base.ts:11](https://github.com/humanprotocol/human-protocol/blob/48066071/packages/sdk/typescript/human-protocol-sdk/src/base.ts#L11) +[base.ts:11](https://github.com/humanprotocol/human-protocol/blob/e4b60ab1/packages/sdk/typescript/human-protocol-sdk/src/base.ts#L11) ## Methods @@ -204,7 +204,7 @@ const value = await kvstoreClient.get('0xf39Fd6e51aad88F6F4ce6aB8827279cffFb9226 #### Defined in -[kvstore.ts:301](https://github.com/humanprotocol/human-protocol/blob/48066071/packages/sdk/typescript/human-protocol-sdk/src/kvstore.ts#L301) +[kvstore.ts:301](https://github.com/humanprotocol/human-protocol/blob/e4b60ab1/packages/sdk/typescript/human-protocol-sdk/src/kvstore.ts#L301) ___ @@ -247,7 +247,7 @@ const linkedinUrl = await kvstoreClient.getFileUrlAndVerifyHash( #### Defined in -[kvstore.ts:340](https://github.com/humanprotocol/human-protocol/blob/48066071/packages/sdk/typescript/human-protocol-sdk/src/kvstore.ts#L340) +[kvstore.ts:340](https://github.com/humanprotocol/human-protocol/blob/e4b60ab1/packages/sdk/typescript/human-protocol-sdk/src/kvstore.ts#L340) ___ @@ -285,7 +285,7 @@ const publicKey = await kvstoreClient.getPublicKey('0xf39Fd6e51aad88F6F4ce6aB882 #### Defined in -[kvstore.ts:398](https://github.com/humanprotocol/human-protocol/blob/48066071/packages/sdk/typescript/human-protocol-sdk/src/kvstore.ts#L398) +[kvstore.ts:398](https://github.com/humanprotocol/human-protocol/blob/e4b60ab1/packages/sdk/typescript/human-protocol-sdk/src/kvstore.ts#L398) ___ @@ -329,7 +329,7 @@ await kvstoreClient.set('Role', 'RecordingOracle'); #### Defined in -[kvstore.ts:163](https://github.com/humanprotocol/human-protocol/blob/48066071/packages/sdk/typescript/human-protocol-sdk/src/kvstore.ts#L163) +[kvstore.ts:163](https://github.com/humanprotocol/human-protocol/blob/e4b60ab1/packages/sdk/typescript/human-protocol-sdk/src/kvstore.ts#L163) ___ @@ -375,7 +375,7 @@ await kvstoreClient.set(keys, values); #### Defined in -[kvstore.ts:206](https://github.com/humanprotocol/human-protocol/blob/48066071/packages/sdk/typescript/human-protocol-sdk/src/kvstore.ts#L206) +[kvstore.ts:206](https://github.com/humanprotocol/human-protocol/blob/e4b60ab1/packages/sdk/typescript/human-protocol-sdk/src/kvstore.ts#L206) ___ @@ -418,7 +418,7 @@ await kvstoreClient.setFileUrlAndHash('linkedin.com/example', 'linkedin_url); #### Defined in -[kvstore.ts:249](https://github.com/humanprotocol/human-protocol/blob/48066071/packages/sdk/typescript/human-protocol-sdk/src/kvstore.ts#L249) +[kvstore.ts:249](https://github.com/humanprotocol/human-protocol/blob/e4b60ab1/packages/sdk/typescript/human-protocol-sdk/src/kvstore.ts#L249) ___ @@ -450,4 +450,4 @@ Creates an instance of KVStoreClient from a runner. #### Defined in -[kvstore.ts:118](https://github.com/humanprotocol/human-protocol/blob/48066071/packages/sdk/typescript/human-protocol-sdk/src/kvstore.ts#L118) +[kvstore.ts:118](https://github.com/humanprotocol/human-protocol/blob/e4b60ab1/packages/sdk/typescript/human-protocol-sdk/src/kvstore.ts#L118) diff --git a/docs/sdk/typescript/classes/operator.OperatorUtils.md b/docs/sdk/typescript/classes/operator.OperatorUtils.md index 608aec83df..4c0f52dabf 100644 --- a/docs/sdk/typescript/classes/operator.OperatorUtils.md +++ b/docs/sdk/typescript/classes/operator.OperatorUtils.md @@ -58,7 +58,7 @@ const leader = await OperatorUtils.getLeader(ChainId.POLYGON_MUMBAI, '0x62dD5123 #### Defined in -[operator.ts:43](https://github.com/humanprotocol/human-protocol/blob/48066071/packages/sdk/typescript/human-protocol-sdk/src/operator.ts#L43) +[operator.ts:43](https://github.com/humanprotocol/human-protocol/blob/e4b60ab1/packages/sdk/typescript/human-protocol-sdk/src/operator.ts#L43) ___ @@ -90,7 +90,7 @@ const leaders = await OperatorUtils.getLeaders(); #### Defined in -[operator.ts:84](https://github.com/humanprotocol/human-protocol/blob/48066071/packages/sdk/typescript/human-protocol-sdk/src/operator.ts#L84) +[operator.ts:84](https://github.com/humanprotocol/human-protocol/blob/e4b60ab1/packages/sdk/typescript/human-protocol-sdk/src/operator.ts#L84) ___ @@ -124,7 +124,7 @@ const operators = await OperatorUtils.getReputationNetworkOperators(ChainId.POLY #### Defined in -[operator.ts:123](https://github.com/humanprotocol/human-protocol/blob/48066071/packages/sdk/typescript/human-protocol-sdk/src/operator.ts#L123) +[operator.ts:123](https://github.com/humanprotocol/human-protocol/blob/e4b60ab1/packages/sdk/typescript/human-protocol-sdk/src/operator.ts#L123) ___ @@ -157,4 +157,4 @@ const rewards = await OperatorUtils.getRewards(ChainId.POLYGON_MUMBAI, '0x62dD51 #### Defined in -[operator.ts:162](https://github.com/humanprotocol/human-protocol/blob/48066071/packages/sdk/typescript/human-protocol-sdk/src/operator.ts#L162) +[operator.ts:162](https://github.com/humanprotocol/human-protocol/blob/e4b60ab1/packages/sdk/typescript/human-protocol-sdk/src/operator.ts#L162) diff --git a/docs/sdk/typescript/classes/staking.StakingClient.md b/docs/sdk/typescript/classes/staking.StakingClient.md index 4af90b9d85..4e34e16a52 100644 --- a/docs/sdk/typescript/classes/staking.StakingClient.md +++ b/docs/sdk/typescript/classes/staking.StakingClient.md @@ -132,7 +132,7 @@ const stakingClient = await StakingClient.build(provider); #### Defined in -[staking.ts:111](https://github.com/humanprotocol/human-protocol/blob/48066071/packages/sdk/typescript/human-protocol-sdk/src/staking.ts#L111) +[staking.ts:111](https://github.com/humanprotocol/human-protocol/blob/e4b60ab1/packages/sdk/typescript/human-protocol-sdk/src/staking.ts#L111) ## Properties @@ -142,7 +142,7 @@ const stakingClient = await StakingClient.build(provider); #### Defined in -[staking.ts:102](https://github.com/humanprotocol/human-protocol/blob/48066071/packages/sdk/typescript/human-protocol-sdk/src/staking.ts#L102) +[staking.ts:102](https://github.com/humanprotocol/human-protocol/blob/e4b60ab1/packages/sdk/typescript/human-protocol-sdk/src/staking.ts#L102) ___ @@ -156,7 +156,7 @@ ___ #### Defined in -[base.ts:12](https://github.com/humanprotocol/human-protocol/blob/48066071/packages/sdk/typescript/human-protocol-sdk/src/base.ts#L12) +[base.ts:12](https://github.com/humanprotocol/human-protocol/blob/e4b60ab1/packages/sdk/typescript/human-protocol-sdk/src/base.ts#L12) ___ @@ -166,7 +166,7 @@ ___ #### Defined in -[staking.ts:103](https://github.com/humanprotocol/human-protocol/blob/48066071/packages/sdk/typescript/human-protocol-sdk/src/staking.ts#L103) +[staking.ts:103](https://github.com/humanprotocol/human-protocol/blob/e4b60ab1/packages/sdk/typescript/human-protocol-sdk/src/staking.ts#L103) ___ @@ -180,7 +180,7 @@ ___ #### Defined in -[base.ts:11](https://github.com/humanprotocol/human-protocol/blob/48066071/packages/sdk/typescript/human-protocol-sdk/src/base.ts#L11) +[base.ts:11](https://github.com/humanprotocol/human-protocol/blob/e4b60ab1/packages/sdk/typescript/human-protocol-sdk/src/base.ts#L11) ___ @@ -190,7 +190,7 @@ ___ #### Defined in -[staking.ts:101](https://github.com/humanprotocol/human-protocol/blob/48066071/packages/sdk/typescript/human-protocol-sdk/src/staking.ts#L101) +[staking.ts:101](https://github.com/humanprotocol/human-protocol/blob/e4b60ab1/packages/sdk/typescript/human-protocol-sdk/src/staking.ts#L101) ___ @@ -200,7 +200,7 @@ ___ #### Defined in -[staking.ts:100](https://github.com/humanprotocol/human-protocol/blob/48066071/packages/sdk/typescript/human-protocol-sdk/src/staking.ts#L100) +[staking.ts:100](https://github.com/humanprotocol/human-protocol/blob/e4b60ab1/packages/sdk/typescript/human-protocol-sdk/src/staking.ts#L100) ## Methods @@ -245,7 +245,7 @@ await stakingClient.allocate('0x62dD51230A30401C455c8398d06F85e4EaB6309f', amoun #### Defined in -[staking.ts:458](https://github.com/humanprotocol/human-protocol/blob/48066071/packages/sdk/typescript/human-protocol-sdk/src/staking.ts#L458) +[staking.ts:458](https://github.com/humanprotocol/human-protocol/blob/e4b60ab1/packages/sdk/typescript/human-protocol-sdk/src/staking.ts#L458) ___ @@ -287,7 +287,7 @@ await stakingClient.approveStake(amount); #### Defined in -[staking.ts:203](https://github.com/humanprotocol/human-protocol/blob/48066071/packages/sdk/typescript/human-protocol-sdk/src/staking.ts#L203) +[staking.ts:203](https://github.com/humanprotocol/human-protocol/blob/e4b60ab1/packages/sdk/typescript/human-protocol-sdk/src/staking.ts#L203) ___ @@ -309,7 +309,7 @@ Check if escrow exists #### Defined in -[staking.ts:167](https://github.com/humanprotocol/human-protocol/blob/48066071/packages/sdk/typescript/human-protocol-sdk/src/staking.ts#L167) +[staking.ts:167](https://github.com/humanprotocol/human-protocol/blob/e4b60ab1/packages/sdk/typescript/human-protocol-sdk/src/staking.ts#L167) ___ @@ -353,7 +353,7 @@ await stakingClient.closeAllocation('0x62dD51230A30401C455c8398d06F85e4EaB6309f' #### Defined in -[staking.ts:511](https://github.com/humanprotocol/human-protocol/blob/48066071/packages/sdk/typescript/human-protocol-sdk/src/staking.ts#L511) +[staking.ts:511](https://github.com/humanprotocol/human-protocol/blob/e4b60ab1/packages/sdk/typescript/human-protocol-sdk/src/staking.ts#L511) ___ @@ -396,7 +396,7 @@ await stakingClient.distributeReward('0x62dD51230A30401C455c8398d06F85e4EaB6309f #### Defined in -[staking.ts:554](https://github.com/humanprotocol/human-protocol/blob/48066071/packages/sdk/typescript/human-protocol-sdk/src/staking.ts#L554) +[staking.ts:554](https://github.com/humanprotocol/human-protocol/blob/e4b60ab1/packages/sdk/typescript/human-protocol-sdk/src/staking.ts#L554) ___ @@ -434,7 +434,7 @@ const allocationInfo = await stakingClient.getAllocation('0x62dD51230A30401C455c #### Defined in -[staking.ts:591](https://github.com/humanprotocol/human-protocol/blob/48066071/packages/sdk/typescript/human-protocol-sdk/src/staking.ts#L591) +[staking.ts:591](https://github.com/humanprotocol/human-protocol/blob/e4b60ab1/packages/sdk/typescript/human-protocol-sdk/src/staking.ts#L591) ___ @@ -479,7 +479,7 @@ await stakingClient.slash('0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266', '0xf39Fd #### Defined in -[staking.ts:387](https://github.com/humanprotocol/human-protocol/blob/48066071/packages/sdk/typescript/human-protocol-sdk/src/staking.ts#L387) +[staking.ts:387](https://github.com/humanprotocol/human-protocol/blob/e4b60ab1/packages/sdk/typescript/human-protocol-sdk/src/staking.ts#L387) ___ @@ -524,7 +524,7 @@ await stakingClient.approveStake(amount); #### Defined in -[staking.ts:258](https://github.com/humanprotocol/human-protocol/blob/48066071/packages/sdk/typescript/human-protocol-sdk/src/staking.ts#L258) +[staking.ts:258](https://github.com/humanprotocol/human-protocol/blob/e4b60ab1/packages/sdk/typescript/human-protocol-sdk/src/staking.ts#L258) ___ @@ -568,7 +568,7 @@ await stakingClient.unstake(amount); #### Defined in -[staking.ts:303](https://github.com/humanprotocol/human-protocol/blob/48066071/packages/sdk/typescript/human-protocol-sdk/src/staking.ts#L303) +[staking.ts:303](https://github.com/humanprotocol/human-protocol/blob/e4b60ab1/packages/sdk/typescript/human-protocol-sdk/src/staking.ts#L303) ___ @@ -610,7 +610,7 @@ await stakingClient.withdraw(); #### Defined in -[staking.ts:349](https://github.com/humanprotocol/human-protocol/blob/48066071/packages/sdk/typescript/human-protocol-sdk/src/staking.ts#L349) +[staking.ts:349](https://github.com/humanprotocol/human-protocol/blob/e4b60ab1/packages/sdk/typescript/human-protocol-sdk/src/staking.ts#L349) ___ @@ -642,4 +642,4 @@ Creates an instance of StakingClient from a Runner. #### Defined in -[staking.ts:145](https://github.com/humanprotocol/human-protocol/blob/48066071/packages/sdk/typescript/human-protocol-sdk/src/staking.ts#L145) +[staking.ts:145](https://github.com/humanprotocol/human-protocol/blob/e4b60ab1/packages/sdk/typescript/human-protocol-sdk/src/staking.ts#L145) diff --git a/docs/sdk/typescript/classes/statistics.StatisticsClient.md b/docs/sdk/typescript/classes/statistics.StatisticsClient.md index a61a7d8025..1e0f9f9671 100644 --- a/docs/sdk/typescript/classes/statistics.StatisticsClient.md +++ b/docs/sdk/typescript/classes/statistics.StatisticsClient.md @@ -77,7 +77,7 @@ const statisticsClient = new StatisticsClient(NETWORKS[ChainId.POLYGON_MUMBAI]); #### Defined in -[statistics.ts:68](https://github.com/humanprotocol/human-protocol/blob/48066071/packages/sdk/typescript/human-protocol-sdk/src/statistics.ts#L68) +[statistics.ts:68](https://github.com/humanprotocol/human-protocol/blob/e4b60ab1/packages/sdk/typescript/human-protocol-sdk/src/statistics.ts#L68) ## Properties @@ -87,7 +87,7 @@ const statisticsClient = new StatisticsClient(NETWORKS[ChainId.POLYGON_MUMBAI]); #### Defined in -[statistics.ts:61](https://github.com/humanprotocol/human-protocol/blob/48066071/packages/sdk/typescript/human-protocol-sdk/src/statistics.ts#L61) +[statistics.ts:61](https://github.com/humanprotocol/human-protocol/blob/e4b60ab1/packages/sdk/typescript/human-protocol-sdk/src/statistics.ts#L61) ## Methods @@ -151,7 +151,7 @@ const escrowStatisticsApril = await statisticsClient.getEscrowStatistics({ #### Defined in -[statistics.ts:121](https://github.com/humanprotocol/human-protocol/blob/48066071/packages/sdk/typescript/human-protocol-sdk/src/statistics.ts#L121) +[statistics.ts:121](https://github.com/humanprotocol/human-protocol/blob/e4b60ab1/packages/sdk/typescript/human-protocol-sdk/src/statistics.ts#L121) ___ @@ -247,7 +247,7 @@ console.log('HMT statistics from 5/8 - 6/8:', { #### Defined in -[statistics.ts:394](https://github.com/humanprotocol/human-protocol/blob/48066071/packages/sdk/typescript/human-protocol-sdk/src/statistics.ts#L394) +[statistics.ts:394](https://github.com/humanprotocol/human-protocol/blob/e4b60ab1/packages/sdk/typescript/human-protocol-sdk/src/statistics.ts#L394) ___ @@ -329,7 +329,7 @@ console.log( #### Defined in -[statistics.ts:285](https://github.com/humanprotocol/human-protocol/blob/48066071/packages/sdk/typescript/human-protocol-sdk/src/statistics.ts#L285) +[statistics.ts:285](https://github.com/humanprotocol/human-protocol/blob/e4b60ab1/packages/sdk/typescript/human-protocol-sdk/src/statistics.ts#L285) ___ @@ -388,4 +388,4 @@ const workerStatisticsApril = await statisticsClient.getWorkerStatistics({ #### Defined in -[statistics.ts:196](https://github.com/humanprotocol/human-protocol/blob/48066071/packages/sdk/typescript/human-protocol-sdk/src/statistics.ts#L196) +[statistics.ts:196](https://github.com/humanprotocol/human-protocol/blob/e4b60ab1/packages/sdk/typescript/human-protocol-sdk/src/statistics.ts#L196) diff --git a/docs/sdk/typescript/classes/storage.StorageClient.md b/docs/sdk/typescript/classes/storage.StorageClient.md index 576834e3e2..5fd2fb0083 100644 --- a/docs/sdk/typescript/classes/storage.StorageClient.md +++ b/docs/sdk/typescript/classes/storage.StorageClient.md @@ -91,7 +91,7 @@ const storageClient = new StorageClient(params, credentials); #### Defined in -[storage.ts:73](https://github.com/humanprotocol/human-protocol/blob/48066071/packages/sdk/typescript/human-protocol-sdk/src/storage.ts#L73) +[storage.ts:73](https://github.com/humanprotocol/human-protocol/blob/e4b60ab1/packages/sdk/typescript/human-protocol-sdk/src/storage.ts#L73) ## Properties @@ -101,7 +101,7 @@ const storageClient = new StorageClient(params, credentials); #### Defined in -[storage.ts:64](https://github.com/humanprotocol/human-protocol/blob/48066071/packages/sdk/typescript/human-protocol-sdk/src/storage.ts#L64) +[storage.ts:64](https://github.com/humanprotocol/human-protocol/blob/e4b60ab1/packages/sdk/typescript/human-protocol-sdk/src/storage.ts#L64) ___ @@ -111,7 +111,7 @@ ___ #### Defined in -[storage.ts:65](https://github.com/humanprotocol/human-protocol/blob/48066071/packages/sdk/typescript/human-protocol-sdk/src/storage.ts#L65) +[storage.ts:65](https://github.com/humanprotocol/human-protocol/blob/e4b60ab1/packages/sdk/typescript/human-protocol-sdk/src/storage.ts#L65) ## Methods @@ -155,7 +155,7 @@ const exists = await storageClient.bucketExists('bucket-name'); #### Defined in -[storage.ts:266](https://github.com/humanprotocol/human-protocol/blob/48066071/packages/sdk/typescript/human-protocol-sdk/src/storage.ts#L266) +[storage.ts:266](https://github.com/humanprotocol/human-protocol/blob/e4b60ab1/packages/sdk/typescript/human-protocol-sdk/src/storage.ts#L266) ___ @@ -198,7 +198,7 @@ const files = await storageClient.downloadFiles(keys, 'bucket-name'); #### Defined in -[storage.ts:113](https://github.com/humanprotocol/human-protocol/blob/48066071/packages/sdk/typescript/human-protocol-sdk/src/storage.ts#L113) +[storage.ts:113](https://github.com/humanprotocol/human-protocol/blob/e4b60ab1/packages/sdk/typescript/human-protocol-sdk/src/storage.ts#L113) ___ @@ -242,7 +242,7 @@ const fileNames = await storageClient.listObjects('bucket-name'); #### Defined in -[storage.ts:297](https://github.com/humanprotocol/human-protocol/blob/48066071/packages/sdk/typescript/human-protocol-sdk/src/storage.ts#L297) +[storage.ts:297](https://github.com/humanprotocol/human-protocol/blob/e4b60ab1/packages/sdk/typescript/human-protocol-sdk/src/storage.ts#L297) ___ @@ -290,7 +290,7 @@ const uploadedFiles = await storageClient.uploadFiles(files, 'bucket-name'); #### Defined in -[storage.ts:201](https://github.com/humanprotocol/human-protocol/blob/48066071/packages/sdk/typescript/human-protocol-sdk/src/storage.ts#L201) +[storage.ts:201](https://github.com/humanprotocol/human-protocol/blob/e4b60ab1/packages/sdk/typescript/human-protocol-sdk/src/storage.ts#L201) ___ @@ -322,4 +322,4 @@ const file = await storageClient.downloadFileFromUrl('http://localhost/file.json #### Defined in -[storage.ts:148](https://github.com/humanprotocol/human-protocol/blob/48066071/packages/sdk/typescript/human-protocol-sdk/src/storage.ts#L148) +[storage.ts:148](https://github.com/humanprotocol/human-protocol/blob/e4b60ab1/packages/sdk/typescript/human-protocol-sdk/src/storage.ts#L148) diff --git a/packages/apps/job-launcher/server/test/setup.ts b/packages/apps/job-launcher/server/test/setup.ts index 56cba97184..9dd6ba3e77 100644 --- a/packages/apps/job-launcher/server/test/setup.ts +++ b/packages/apps/job-launcher/server/test/setup.ts @@ -31,9 +31,22 @@ export async function setup(): Promise { nonce: 1, }); const kvStoreClient = await KVStoreClient.build(wallet); + await kvStoreClient.setBulk( - [KVStoreKeys.role, KVStoreKeys.fee, KVStoreKeys.webhookUrl], - [Role.JobLauncher, '1', 'http://localhost:5000/webhook'], + [ + KVStoreKeys.role, + KVStoreKeys.fee, + KVStoreKeys.webhookUrl, + KVStoreKeys.url, + KVStoreKeys.jobTypes, + ], + [ + Role.JobLauncher, + '1', + 'http://localhost:5000/webhook', + 'http://localhost:5000', + 'type1, type2', + ], { nonce: 2 }, ); } diff --git a/packages/sdk/python/human-protocol-sdk/example.py b/packages/sdk/python/human-protocol-sdk/example.py index 3736653a5d..e338d03827 100644 --- a/packages/sdk/python/human-protocol-sdk/example.py +++ b/packages/sdk/python/human-protocol-sdk/example.py @@ -2,111 +2,19 @@ import json from human_protocol_sdk.constants import ChainId -from human_protocol_sdk.escrow import EscrowUtils, Status +from human_protocol_sdk.escrow import EscrowUtils from human_protocol_sdk.filter import EscrowFilter -from human_protocol_sdk.staking import StakingUtils, LeaderFilter from human_protocol_sdk.statistics import StatisticsClient, StatisticsParam from human_protocol_sdk.storage import StorageClient from human_protocol_sdk.agreement import agreement +from human_protocol_sdk.operator import OperatorUtils -def get_escrow_statistics(statistics_client: StatisticsClient): - print(statistics_client.get_escrow_statistics()) - print( - statistics_client.get_escrow_statistics( - StatisticsParam( - date_from=datetime.datetime(2023, 5, 8), - date_to=datetime.datetime(2023, 6, 8), - ) - ) +def get_rep(): + aa = OperatorUtils.get_reputation_network_operators( + ChainId.LOCALHOST, "0x15d34aaf54267db7d7c367839aaf71a00a2c6a65" ) - - -def get_worker_statistics(statistics_client: StatisticsClient): - print(statistics_client.get_worker_statistics()) - print( - statistics_client.get_worker_statistics( - StatisticsParam( - date_from=datetime.datetime(2023, 5, 8), - date_to=datetime.datetime(2023, 6, 8), - ) - ) - ) - - -def get_payment_statistics(statistics_client: StatisticsClient): - print(statistics_client.get_payment_statistics()) - print( - statistics_client.get_payment_statistics( - StatisticsParam( - date_from=datetime.datetime(2023, 5, 8), - date_to=datetime.datetime(2023, 6, 8), - ) - ) - ) - - -def get_hmt_statistics(statistics_client: StatisticsClient): - print(statistics_client.get_hmt_statistics()) - print( - statistics_client.get_hmt_statistics( - StatisticsParam( - date_from=datetime.datetime(2023, 5, 8), - date_to=datetime.datetime(2023, 6, 8), - ) - ) - ) - - -def get_escrows(): - print( - EscrowUtils.get_escrows( - EscrowFilter( - networks=[ChainId.POLYGON_MUMBAI], - status=Status.Pending, - date_from=datetime.datetime(2023, 5, 8), - date_to=datetime.datetime(2023, 6, 8), - ) - ) - ) - - print( - vars( - EscrowUtils.get_escrow( - ChainId.POLYGON_MUMBAI, "0xf9ec66feeafb850d85b88142a7305f55e0532959" - ) - ) - ) - - -def get_leaders(): - leaders = StakingUtils.get_leaders() - print(leaders) - print(vars(StakingUtils.get_leader(ChainId.POLYGON_MUMBAI, leaders[0].address))) - print( - StakingUtils.get_leaders( - LeaderFilter(networks=[ChainId.POLYGON_MUMBAI], role="Job Launcher") - ) - ) - - -def agreement_example(): - # process annotation data and get quality estimates - url = "https://raw.githubusercontent.com/humanprotocol/human-protocol/efa8d3789ac35915b42435011cd0a8d36507564c/packages/sdk/python/human-protocol-sdk/example_annotations.json" - annotations = json.loads(StorageClient.download_file_from_url(url)) - print(annotations) - - report = agreement( - data=annotations["data"], - data_format=annotations["data_format"], - labels=annotations["labels"], - nan_values=annotations["nan_values"], - measure="fleiss_kappa", - bootstrap_method="bca", - bootstrap_kwargs={"seed": 42}, - ) - print(report["results"]) - print(report["config"]) + print(aa[0].job_types) if __name__ == "__main__": @@ -114,12 +22,4 @@ def agreement_example(): # Run single example while testing, and remove comments before commit - get_escrow_statistics(statistics_client) - get_worker_statistics(statistics_client) - get_payment_statistics(statistics_client) - get_hmt_statistics(statistics_client) - - agreement_example() - - get_escrows() - get_leaders() + get_rep() diff --git a/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/agreement/measures.py b/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/agreement/measures.py index a4b938ca89..b0ccf3a7a0 100644 --- a/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/agreement/measures.py +++ b/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/agreement/measures.py @@ -185,11 +185,13 @@ def _percentage_from_label_counts(label_counts): max_item_agreements = (n_raters * (n_raters - 1)).sum() if max_item_agreements == 0: - warn(""" + warn( + """ All annotations were made by a single annotator, check your data to ensure this is not an error. Returning 1.0 - """) + """ + ) return 1.0 return item_agreements / max_item_agreements @@ -197,11 +199,13 @@ def _percentage_from_label_counts(label_counts): def _kappa(agreement_observed, agreement_expected): if agreement_expected == 1.0: - warn(""" + warn( + """ Annotations contained only a single value, check your data to ensure this is not an error. Returning 1.0. - """) + """ + ) return 1.0 return (agreement_observed - agreement_expected) / (1 - agreement_expected) diff --git a/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/constants.py b/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/constants.py index 702b0a3a5e..a22a06e36d 100644 --- a/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/constants.py +++ b/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/constants.py @@ -216,7 +216,7 @@ class ChainId(Enum): ChainId.LOCALHOST: { "title": "Localhost", "scan_url": "", - "subgraph_url": "subgraph_url", + "subgraph_url": "http://localhost:8000/subgraphs/name/humanprotocol/localhost", "hmt_address": "0x5FbDB2315678afecb367f032d93F642f64180aa3", "factory_address": "0xDc64a140Aa3E981100a9becA4E685f962f0cF6C9", "staking_address": "0x9fE46736679d2D9a65F0992F2272dE9f3c7fa6e0", @@ -262,3 +262,4 @@ class KVStoreKeys(Enum): public_key = "public_key" webhook_url = "webhook_url" url = "url" + job_types = "job_types" diff --git a/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/gql/escrow.py b/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/gql/escrow.py index 168c0dbca9..30e12f382b 100644 --- a/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/gql/escrow.py +++ b/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/gql/escrow.py @@ -87,4 +87,6 @@ def get_escrow_query(): }} }} {escrow_fragment} -""".format(escrow_fragment=escrow_fragment) +""".format( + escrow_fragment=escrow_fragment + ) diff --git a/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/gql/hmtoken.py b/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/gql/hmtoken.py index 04f2096ad7..a9bd32c28e 100644 --- a/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/gql/hmtoken.py +++ b/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/gql/hmtoken.py @@ -12,4 +12,6 @@ }} }} {holder_fragment} -""".format(holder_fragment=holder_fragment) +""".format( + holder_fragment=holder_fragment +) diff --git a/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/gql/operator.py b/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/gql/operator.py index 5b36801222..743cbca1a7 100644 --- a/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/gql/operator.py +++ b/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/gql/operator.py @@ -69,7 +69,9 @@ def get_reputation_network_query(role: Optional[str]): }} ) {{ address, - role + role, + url, + jobTypes }} }} }} diff --git a/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/gql/reward.py b/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/gql/reward.py index 09c06278ae..3f43bbff9e 100644 --- a/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/gql/reward.py +++ b/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/gql/reward.py @@ -14,4 +14,6 @@ }} }} {reward_added_event_fragment} -""".format(reward_added_event_fragment=reward_added_event_fragment) +""".format( + reward_added_event_fragment=reward_added_event_fragment +) diff --git a/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/gql/statistics.py b/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/gql/statistics.py index 4e787baa9a..74da8d6815 100644 --- a/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/gql/statistics.py +++ b/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/gql/statistics.py @@ -56,7 +56,9 @@ }} }} {hmtoken_statistics_fragment} -""".format(hmtoken_statistics_fragment=hmtoken_statistics_fragment) +""".format( + hmtoken_statistics_fragment=hmtoken_statistics_fragment +) get_escrow_statistics_query = """ query GetEscrowStatistics {{ @@ -65,7 +67,9 @@ }} }} {escrow_statistics_fragment} -""".format(escrow_statistics_fragment=escrow_statistics_fragment) +""".format( + escrow_statistics_fragment=escrow_statistics_fragment +) def get_event_day_data_query(param: StatisticsParam): diff --git a/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/operator/operator_utils.py b/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/operator/operator_utils.py index fea376522e..dda6348b42 100644 --- a/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/operator/operator_utils.py +++ b/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/operator/operator_utils.py @@ -88,6 +88,7 @@ def __init__( public_key: Optional[str] = None, webhook_url: Optional[str] = None, url: Optional[str] = None, + job_types: Optional[str] = None, ): """ Initializes an LeaderData instance. @@ -109,6 +110,7 @@ def __init__( :param public_key: Public key :param webhook_url: Webhook url :param url: Url + :param job_types: Job types """ self.chain_id = chain_id @@ -128,6 +130,7 @@ def __init__( self.public_key = public_key self.webhook_url = webhook_url self.url = url + self.job_types = job_types class RewardData: @@ -148,11 +151,7 @@ def __init__( class Operator: - def __init__( - self, - address: str, - role: str, - ): + def __init__(self, address: str, role: str, url: str = "", job_types: str = ""): """ Initializes an Operator instance. @@ -162,6 +161,8 @@ def __init__( self.address = address self.role = role + self.url = url + self.job_types = job_types class OperatorUtils: @@ -230,6 +231,7 @@ def get_leaders( public_key=leader.get("publicKey", None), webhook_url=leader.get("webhookUrl", None), url=leader.get("url", None), + job_types=leader.get("jobTypes", None), ) for leader in leaders_raw ] @@ -299,6 +301,7 @@ def get_leader( public_key=leader.get("publicKey", None), webhook_url=leader.get("webhookUrl", None), url=leader.get("url", None), + job_types=leader.get("jobTypes", None), ) @staticmethod @@ -312,6 +315,7 @@ def get_reputation_network_operators( :param chain_id: Network in which the reputation network exists :param address: Address of the reputation oracle :param role: (Optional) Role of the operator + :parem job_types: (Optional) Job types of the operator :return: Returns an array of operator details @@ -352,6 +356,8 @@ def get_reputation_network_operators( Operator( address=operator.get("address", ""), role=operator.get("role", ""), + url=operator.get("url", ""), + job_types=operator.get("jobTypes", []), ) for operator in operators ] diff --git a/packages/sdk/python/human-protocol-sdk/test/human_protocol_sdk/operator/test_operator_utils.py b/packages/sdk/python/human-protocol-sdk/test/human_protocol_sdk/operator/test_operator_utils.py index 1e2adbd2bf..5ce1abd9c7 100644 --- a/packages/sdk/python/human-protocol-sdk/test/human_protocol_sdk/operator/test_operator_utils.py +++ b/packages/sdk/python/human-protocol-sdk/test/human_protocol_sdk/operator/test_operator_utils.py @@ -181,6 +181,8 @@ def test_get_reputation_network_operators(self): reputation_address = "0x1234567890123456789012345678901234567891" operator_address = "0x1234567890123456789012345678901234567891" role = "Job Launcher" + url = "https://example.com" + job_types = ["type1", "type2"] mock_function = MagicMock() @@ -193,7 +195,14 @@ def test_get_reputation_network_operators(self): "reputationNetwork": { "id": reputation_address, "address": reputation_address, - "operators": [{"address": operator_address, "role": role}], + "operators": [ + { + "address": operator_address, + "role": role, + "url": url, + "jobTypes": job_types, + } + ], } } } @@ -212,6 +221,8 @@ def test_get_reputation_network_operators(self): self.assertNotEqual(operators, []) self.assertEqual(operators[0].address, operator_address) self.assertEqual(operators[0].role, role) + self.assertEqual(operators[0].url, url) + self.assertEqual(operators[0].job_types, job_types) def test_get_reputation_network_operators_empty_data(self): reputation_address = "0x1234567890123456789012345678901234567891" diff --git a/packages/sdk/python/human-protocol-sdk/test/human_protocol_sdk/statistics/test_statistics_client.py b/packages/sdk/python/human-protocol-sdk/test/human_protocol_sdk/statistics/test_statistics_client.py index a78fedf48b..889657115c 100644 --- a/packages/sdk/python/human-protocol-sdk/test/human_protocol_sdk/statistics/test_statistics_client.py +++ b/packages/sdk/python/human-protocol-sdk/test/human_protocol_sdk/statistics/test_statistics_client.py @@ -62,12 +62,12 @@ def test_get_escrow_statistics(self): escrow_statistics = self.statistics.get_escrow_statistics(param) mock_function.assert_any_call( - "subgraph_url", + "http://localhost:8000/subgraphs/name/humanprotocol/localhost", query=get_escrow_statistics_query, ) mock_function.assert_any_call( - "subgraph_url", + "http://localhost:8000/subgraphs/name/humanprotocol/localhost", query=get_event_day_data_query(param), params={ "from": 1683811973, @@ -115,7 +115,7 @@ def test_get_worker_statistics(self): payment_statistics = self.statistics.get_worker_statistics(param) mock_function.assert_any_call( - "subgraph_url", + "http://localhost:8000/subgraphs/name/humanprotocol/localhost", query=get_event_day_data_query(param), params={ "from": 1683811973, @@ -157,7 +157,7 @@ def test_get_payment_statistics(self): payment_statistics = self.statistics.get_payment_statistics(param) mock_function.assert_any_call( - "subgraph_url", + "http://localhost:8000/subgraphs/name/humanprotocol/localhost", query=get_event_day_data_query(param), params={ "from": 1683811973, @@ -224,17 +224,17 @@ def test_get_hmt_statistics(self): hmt_statistics = self.statistics.get_hmt_statistics(param) mock_function.assert_any_call( - "subgraph_url", + "http://localhost:8000/subgraphs/name/humanprotocol/localhost", query=get_hmtoken_statistics_query, ) mock_function.assert_any_call( - "subgraph_url", + "http://localhost:8000/subgraphs/name/humanprotocol/localhost", query=get_holders_query, ) mock_function.assert_any_call( - "subgraph_url", + "http://localhost:8000/subgraphs/name/humanprotocol/localhost", query=get_event_day_data_query(param), params={ "from": 1683811973, diff --git a/packages/sdk/typescript/human-protocol-sdk/src/constants.ts b/packages/sdk/typescript/human-protocol-sdk/src/constants.ts index 1e021db45a..1cfbe0cc4d 100644 --- a/packages/sdk/typescript/human-protocol-sdk/src/constants.ts +++ b/packages/sdk/typescript/human-protocol-sdk/src/constants.ts @@ -272,10 +272,10 @@ export const KVStoreKeys = { publicKey: 'public_key', webhookUrl: 'webhook_url', url: 'url', + jobTypes: 'job_types', }; export const Role = { - Validator: 'Validator', JobLauncher: 'Job Launcher', ExchangeOracle: 'Exchange Oracle', ReputationOracle: 'Reputation Oracle', diff --git a/packages/sdk/typescript/human-protocol-sdk/src/graphql/queries/operator.ts b/packages/sdk/typescript/human-protocol-sdk/src/graphql/queries/operator.ts index ca787c2b19..0ec86ee96e 100644 --- a/packages/sdk/typescript/human-protocol-sdk/src/graphql/queries/operator.ts +++ b/packages/sdk/typescript/human-protocol-sdk/src/graphql/queries/operator.ts @@ -19,6 +19,7 @@ const LEADER_FRAGMENT = gql` publicKey webhookUrl url + jobTypes } `; @@ -64,7 +65,9 @@ export const GET_REPUTATION_NETWORK_QUERY = (role?: string) => { ${WHERE_CLAUSE} ) { address, - role + role, + url, + jobTypes } } } diff --git a/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts b/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts index 6ac4e3d530..3d4cfd6814 100644 --- a/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts +++ b/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts @@ -48,6 +48,8 @@ export interface IReputationNetwork { export interface IOperator { address: string; role?: string; + url?: string; + jobTypes?: string[]; } export interface IEscrowsFilter { diff --git a/packages/sdk/typescript/human-protocol-sdk/src/kvstore.ts b/packages/sdk/typescript/human-protocol-sdk/src/kvstore.ts index c4e71513df..f32d8713ae 100644 --- a/packages/sdk/typescript/human-protocol-sdk/src/kvstore.ts +++ b/packages/sdk/typescript/human-protocol-sdk/src/kvstore.ts @@ -18,7 +18,6 @@ import { } from './error'; import { NetworkData } from './types'; import { isValidUrl } from './utils'; - /** * ## Introduction * @@ -88,6 +87,7 @@ import { isValidUrl } from './utils'; * const kvstoreClient = await KVStoreClient.build(signer); * ``` */ + export class KVStoreClient extends BaseEthersClient { private contract: KVStore; diff --git a/packages/sdk/typescript/human-protocol-sdk/test/escrow.test.ts b/packages/sdk/typescript/human-protocol-sdk/test/escrow.test.ts index 3a1ef8e940..f4cf1adc0f 100644 --- a/packages/sdk/typescript/human-protocol-sdk/test/escrow.test.ts +++ b/packages/sdk/typescript/human-protocol-sdk/test/escrow.test.ts @@ -48,7 +48,6 @@ vi.mock('graphql-request', () => { default: vi.fn(), }; }); -vi.mock('../src/init'); describe('EscrowClient', () => { let escrowClient: any, diff --git a/packages/sdk/typescript/human-protocol-sdk/test/kvstore.test.ts b/packages/sdk/typescript/human-protocol-sdk/test/kvstore.test.ts index 8a8b1724f1..1ffd7f51b2 100644 --- a/packages/sdk/typescript/human-protocol-sdk/test/kvstore.test.ts +++ b/packages/sdk/typescript/human-protocol-sdk/test/kvstore.test.ts @@ -17,8 +17,6 @@ import { KVStoreClient } from '../src/kvstore'; import { NetworkData } from '../src/types'; import { DEFAULT_GAS_PAYER_PRIVKEY } from './utils/constants'; -vi.mock('../src/init'); - global.fetch = vi.fn().mockResolvedValue({ text: () => Promise.resolve('example'), }); diff --git a/packages/sdk/typescript/human-protocol-sdk/test/operator.test.ts b/packages/sdk/typescript/human-protocol-sdk/test/operator.test.ts index c20e7e59ac..43eda3280c 100644 --- a/packages/sdk/typescript/human-protocol-sdk/test/operator.test.ts +++ b/packages/sdk/typescript/human-protocol-sdk/test/operator.test.ts @@ -136,6 +136,8 @@ describe('OperatorUtils', () => { const mockOperator: IOperator = { address: '0x0000000000000000000000000000000000000001', role: Role.JobLauncher, + url: 'www.google.com', + jobTypes: ['type1,type2'], }; const mockReputationNetwork: IReputationNetwork = { id: stakerAddress, diff --git a/packages/sdk/typescript/subgraph/schema.graphql b/packages/sdk/typescript/subgraph/schema.graphql index ee244c93f1..8630ef86bb 100644 --- a/packages/sdk/typescript/subgraph/schema.graphql +++ b/packages/sdk/typescript/subgraph/schema.graphql @@ -32,6 +32,7 @@ type Leader @entity { fee: BigInt publicKey: String webhookUrl: String + jobTypes: [String!] url: String urls: [LeaderURL!]! @derivedFrom(field: "leader") reputationNetwork: ReputationNetwork diff --git a/packages/sdk/typescript/subgraph/src/mapping/KVStore.ts b/packages/sdk/typescript/subgraph/src/mapping/KVStore.ts index a1b99cc5ff..7b41de6dd7 100644 --- a/packages/sdk/typescript/subgraph/src/mapping/KVStore.ts +++ b/packages/sdk/typescript/subgraph/src/mapping/KVStore.ts @@ -63,6 +63,10 @@ export function handleDataSaved(event: DataSaved): void { leader.webhookUrl = event.params.value; } else if (key == 'url') { leader.url = event.params.value; + } else if (key == 'jobtypes' || key == 'job_types') { + leader.jobTypes = event.params.value + .split(',') + .map((type) => type.trim()); } else if ( isValidEthAddress(event.params.key) && leader.role == 'Reputation Oracle' From 6465876a9976076e09a3350b70485099bc8141c4 Mon Sep 17 00:00:00 2001 From: Maxim Zhiltsov Date: Mon, 15 Apr 2024 17:27:14 +0300 Subject: [PATCH 59/66] Improve and fix validation (#1867) --- .../src/handlers/job_creation.py | 33 +++++++++---------- 1 file changed, 16 insertions(+), 17 deletions(-) diff --git a/packages/examples/cvat/exchange-oracle/src/handlers/job_creation.py b/packages/examples/cvat/exchange-oracle/src/handlers/job_creation.py index 6c519dcb3a..e1eb450516 100644 --- a/packages/examples/cvat/exchange-oracle/src/handlers/job_creation.py +++ b/packages/examples/cvat/exchange-oracle/src/handlers/job_creation.py @@ -309,19 +309,17 @@ def _validate_gt_annotations(self): valid_boxes = [] for bbox in sample_boxes: if not ( - (0 <= bbox.x < bbox.x + bbox.w <= img_w) - and (0 <= bbox.y < bbox.y + bbox.h <= img_h) + (0 <= int(bbox.x) < int(bbox.x + bbox.w) <= img_w) + and (0 <= int(bbox.y) < int(bbox.y + bbox.h) <= img_h) ): excluded_gt_info.add_message( - "Sample '{}': GT bbox #{} ({}) - invalid coordinates. " - "The image will be skipped".format( + "Sample '{}': GT bbox #{} ({}) - invalid coordinates".format( gt_sample.id, bbox.id, label_cat[bbox.label].name ), sample_id=gt_sample.id, sample_subset=gt_sample.subset, ) - valid_boxes = [] - break + continue if bbox.id in visited_ids: excluded_gt_info.add_message( @@ -331,6 +329,7 @@ def _validate_gt_annotations(self): sample_id=gt_sample.id, sample_subset=gt_sample.subset, ) + continue valid_boxes.append(bbox) @@ -473,15 +472,13 @@ def _validate_skeleton(skeleton: dm.Skeleton, *, sample_bbox: dm.Bbox): _validate_skeleton(skeleton, sample_bbox=sample_bbox) except InvalidCoordinates as error: excluded_points_info.add_message( - "Sample '{}': point #{} ({}) - {}. " - "The image will be skipped".format( + "Sample '{}': point #{} ({}) skipped - {}".format( sample.id, skeleton.id, label_cat[skeleton.label].name, error ), sample_id=sample.id, sample_subset=sample.subset, ) - valid_skeletons = [] - break + continue except DatasetValidationError as error: excluded_points_info.add_message( "Sample '{}': point #{} ({}) - {}".format( @@ -490,6 +487,7 @@ def _validate_skeleton(skeleton: dm.Skeleton, *, sample_bbox: dm.Bbox): sample_id=sample.id, sample_subset=sample.subset, ) + continue valid_skeletons.append(skeleton) @@ -1340,7 +1338,7 @@ def _validate_skeleton(skeleton: dm.Skeleton, *, sample_bbox: dm.Bbox): continue px, py = element.points[:2] - if not is_point_in_bbox(px, py, sample_bbox): + if not is_point_in_bbox(int(px), int(py), sample_bbox): raise InvalidCoordinates("skeleton point is outside the image") label_cat: dm.LabelCategories = self._input_gt_dataset.categories()[dm.AnnotationType.label] @@ -1360,15 +1358,13 @@ def _validate_skeleton(skeleton: dm.Skeleton, *, sample_bbox: dm.Bbox): _validate_skeleton(skeleton, sample_bbox=sample_bbox) except InvalidCoordinates as error: excluded_gt_info.add_message( - "Sample '{}': GT skeleton #{} ({}) - {}. " - "The image will be skipped".format( + "Sample '{}': GT skeleton #{} ({}) skipped - {}".format( gt_sample.id, skeleton.id, label_cat[skeleton.label].name, error ), sample_id=gt_sample.id, sample_subset=gt_sample.subset, ) - valid_skeletons = [] - break + continue except DatasetValidationError as error: excluded_gt_info.add_message( "Sample '{}': GT skeleton #{} ({}) skipped - {}".format( @@ -1377,6 +1373,7 @@ def _validate_skeleton(skeleton: dm.Skeleton, *, sample_bbox: dm.Bbox): sample_id=gt_sample.id, sample_subset=gt_sample.subset, ) + continue valid_skeletons.append(skeleton) visited_ids.add(skeleton.id) @@ -1481,8 +1478,8 @@ def _validate_boxes_annotations(self): valid_boxes = [] for bbox in sample_boxes: if not ( - (0 <= bbox.x < bbox.x + bbox.w <= image_w) - and (0 <= bbox.y < bbox.y + bbox.h <= image_h) + (0 <= int(bbox.x) < int(bbox.x + bbox.w) <= image_w) + and (0 <= int(bbox.y) < int(bbox.y + bbox.h) <= image_h) ): excluded_boxes_info.add_message( "Sample '{}': bbox #{} ({}) skipped - invalid coordinates".format( @@ -1491,6 +1488,7 @@ def _validate_boxes_annotations(self): sample_id=sample.id, sample_subset=sample.subset, ) + continue if bbox.id in visited_ids: excluded_boxes_info.add_message( @@ -1500,6 +1498,7 @@ def _validate_boxes_annotations(self): sample_id=sample.id, sample_subset=sample.subset, ) + continue valid_boxes.append(bbox) visited_ids.add(bbox.id) From 342c0c449af22347f1923bbdfb723e682bc01b63 Mon Sep 17 00:00:00 2001 From: portuu3 <61605646+portuu3@users.noreply.github.com> Date: Mon, 15 Apr 2024 22:11:38 +0200 Subject: [PATCH 60/66] Polygon Amoy network deployment (#1863) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * deploy contracts on Amoy * polygon amoy * Fix SDK failing tests * fix jl client for amoy network --------- Co-authored-by: Francisco López --- .github/workflows/cd-deploy-contracts.yaml | 10 +- .../python/human_protocol_sdk.constants.md | 2 + .../human_protocol_sdk.escrow.escrow_utils.md | 6 +- docs/sdk/python/human_protocol_sdk.md | 1 + ...an_protocol_sdk.operator.operator_utils.md | 10 +- ...otocol_sdk.statistics.statistics_client.md | 14 +- .../classes/base.BaseEthersClient.md | 12 + .../classes/encryption.Encryption.md | 24 + .../classes/encryption.EncryptionUtils.md | 20 + .../typescript/classes/escrow.EscrowClient.md | 112 ++++ .../typescript/classes/escrow.EscrowUtils.md | 16 +- .../classes/kvstore.KVStoreClient.md | 44 ++ .../classes/operator.OperatorUtils.md | 22 +- .../classes/staking.StakingClient.md | 72 +++ .../classes/statistics.StatisticsClient.md | 34 +- .../classes/storage.StorageClient.md | 32 + .../apps/dashboard/admin/config/cron-tasks.ts | 2 + packages/apps/dashboard/admin/package.json | 2 +- .../ui/src/components/Faucet/FaucetView.tsx | 2 +- .../Faucet/__tests__/RequestData.test.tsx | 4 +- .../ui/src/components/Icons/chains.tsx | 1 + .../apps/dashboard/ui/src/constants/index.ts | 6 + .../server/src/common/config/networks.ts | 6 +- .../server/src/common/constant/index.ts | 2 +- .../src/modules/web3/web3.service.spec.ts | 2 +- .../src/common/constants/networks.ts | 6 +- .../client/src/components/Icons/chains.tsx | 1 + .../client/src/constants/chains.ts | 6 +- .../apps/job-launcher/client/src/main.tsx | 24 + .../src/providers/CreateJobPageUIProvider.tsx | 2 +- .../server/src/common/config/networks.ts | 9 +- .../server/src/common/constants/index.ts | 2 +- .../modules/payment/payment.service.spec.ts | 24 +- .../src/modules/web3/web3.service.spec.ts | 2 +- .../server/src/common/config/networks.ts | 8 +- .../server/src/modules/auth/auth.service.ts | 2 +- .../src/modules/user/user.service.spec.ts | 10 +- .../server/src/modules/user/user.service.ts | 2 +- .../src/modules/web3/web3.service.spec.ts | 2 +- packages/core/.gitignore | 2 + .../core/.openzeppelin/unknown-80002.json | 583 ++++++++++++++++++ packages/core/hardhat.config.ts | 17 + packages/core/scripts/deploy-proxies.ts | 6 +- .../sdk/python/human-protocol-sdk/example.py | 101 ++- .../human_protocol_sdk/constants.py | 15 + .../human_protocol_sdk/escrow/escrow_utils.py | 8 +- .../operator/operator_utils.py | 12 +- .../statistics/statistics_client.py | 12 +- .../escrow/test_escrow_client.py | 10 +- .../escrow/test_escrow_utils.py | 22 +- .../human-protocol-sdk/example/escrow.ts | 4 +- .../human-protocol-sdk/example/operator.ts | 6 +- .../human-protocol-sdk/src/constants.ts | 14 + .../human-protocol-sdk/src/enums.ts | 1 + .../human-protocol-sdk/src/escrow.ts | 8 +- .../human-protocol-sdk/src/operator.ts | 8 +- .../human-protocol-sdk/src/statistics.ts | 10 +- .../human-protocol-sdk/test/escrow.test.ts | 18 +- .../sdk/typescript/subgraph/config/amoy.json | 35 ++ yarn.lock | 21 +- 60 files changed, 1314 insertions(+), 157 deletions(-) create mode 100644 packages/core/.openzeppelin/unknown-80002.json create mode 100644 packages/sdk/typescript/subgraph/config/amoy.json diff --git a/.github/workflows/cd-deploy-contracts.yaml b/.github/workflows/cd-deploy-contracts.yaml index 5cb19a1cba..b6abf5eb95 100644 --- a/.github/workflows/cd-deploy-contracts.yaml +++ b/.github/workflows/cd-deploy-contracts.yaml @@ -24,7 +24,7 @@ jobs: env: ETH_GOERLI_TESTNET_URL: ${{ secrets.ETH_GOERLI_TESTNET_URL }} ETH_POLYGON_URL: ${{ secrets.ETH_POLYGON_URL }} - ETH_POLYGON_MUMBAI_URL: ${{ secrets.ETH_POLYGON_MUMBAI_URL }} + ETH_POLYGON_AMOY_URL: ${{ secrets.ETH_POLYGON_AMOY_URL }} ETH_BSC_URL: ${{ secrets.ETH_BSC_URL }} ETH_BSC_TESTNET_URL: ${{ secrets.ETH_BSC_TESTNET_URL }} ETH_MOONBEAM_URL: ${{ secrets.ETH_MOONBEAM_URL }} @@ -62,10 +62,10 @@ jobs: echo "reward_pool=0x1371057BAec59944B924A7963F2EeCF43ff94CE4" >> $GITHUB_OUTPUT echo "private_key=MAINNET_PRIVATE_KEY" >> $GITHUB_OUTPUT ;; - "polygonMumbai") - echo "escrow_factory=0xA8D927C4DA17A6b71675d2D49dFda4E9eBE58f2d" >> $GITHUB_OUTPUT - echo "staking=0x7Fd3dF914E7b6Bd96B4c744Df32183b51368Bfac" >> $GITHUB_OUTPUT - echo "reward_pool=0xf0145eD99AC3c4f877aDa7dA4D1E059ec9116BAE" >> $GITHUB_OUTPUT + "polygonAmoy") + echo "escrow_factory=0xAFf5a986A530ff839d49325A5dF69F96627E8D29" >> $GITHUB_OUTPUT + echo "staking=0xCc0AF0635aa19fE799B6aFDBe28fcFAeA7f00a60" >> $GITHUB_OUTPUT + echo "reward_pool=0xd866bCEFf6D0F77E1c3EAE28230AE6C79b03fDa7" >> $GITHUB_OUTPUT echo "private_key=TESTNET_PRIVATE_KEY" >> $GITHUB_OUTPUT ;; "bsc") diff --git a/docs/sdk/python/human_protocol_sdk.constants.md b/docs/sdk/python/human_protocol_sdk.constants.md index ffca349691..cc483e5542 100644 --- a/docs/sdk/python/human_protocol_sdk.constants.md +++ b/docs/sdk/python/human_protocol_sdk.constants.md @@ -30,6 +30,8 @@ Enum for chain IDs. #### POLYGON *= 137* +#### POLYGON_AMOY *= 80002* + #### POLYGON_MUMBAI *= 80001* #### RINKEBY *= 4* diff --git a/docs/sdk/python/human_protocol_sdk.escrow.escrow_utils.md b/docs/sdk/python/human_protocol_sdk.escrow.escrow_utils.md index 67c4ef2cdd..2601bb65f3 100644 --- a/docs/sdk/python/human_protocol_sdk.escrow.escrow_utils.md +++ b/docs/sdk/python/human_protocol_sdk.escrow.escrow_utils.md @@ -11,7 +11,7 @@ from human_protocol_sdk.escrow import EscrowUtils, EscorwFilter, Status print( EscrowUtils.get_escrows( EscrowFilter( - networks=[ChainId.POLYGON_MUMBAI], + networks=[ChainId.POLYGON_AMOY], status=Status.Pending, date_from=datetime.datetime(2023, 5, 8), date_to=datetime.datetime(2023, 6, 8), @@ -78,7 +78,7 @@ Returns the escrow for a given address. print( EscrowUtils.get_escrow( - ChainId.POLYGON_MUMBAI, + ChainId.POLYGON_AMOY, "0x1234567890123456789012345678901234567890" ) ) @@ -102,7 +102,7 @@ Get an array of escrow addresses based on the specified filter parameters. print( EscrowUtils.get_escrows( EscrowFilter( - networks=[ChainId.POLYGON_MUMBAI], + networks=[ChainId.POLYGON_AMOY], status=Status.Pending, date_from=datetime.datetime(2023, 5, 8), date_to=datetime.datetime(2023, 6, 8), diff --git a/docs/sdk/python/human_protocol_sdk.md b/docs/sdk/python/human_protocol_sdk.md index 9e84eed9c2..fff5984da1 100644 --- a/docs/sdk/python/human_protocol_sdk.md +++ b/docs/sdk/python/human_protocol_sdk.md @@ -105,6 +105,7 @@ * [`ChainId.MOONBASE_ALPHA`](human_protocol_sdk.constants.md#human_protocol_sdk.constants.ChainId.MOONBASE_ALPHA) * [`ChainId.MOONBEAM`](human_protocol_sdk.constants.md#human_protocol_sdk.constants.ChainId.MOONBEAM) * [`ChainId.POLYGON`](human_protocol_sdk.constants.md#human_protocol_sdk.constants.ChainId.POLYGON) + * [`ChainId.POLYGON_AMOY`](human_protocol_sdk.constants.md#human_protocol_sdk.constants.ChainId.POLYGON_AMOY) * [`ChainId.POLYGON_MUMBAI`](human_protocol_sdk.constants.md#human_protocol_sdk.constants.ChainId.POLYGON_MUMBAI) * [`ChainId.RINKEBY`](human_protocol_sdk.constants.md#human_protocol_sdk.constants.ChainId.RINKEBY) * [`ChainId.SKALE`](human_protocol_sdk.constants.md#human_protocol_sdk.constants.ChainId.SKALE) diff --git a/docs/sdk/python/human_protocol_sdk.operator.operator_utils.md b/docs/sdk/python/human_protocol_sdk.operator.operator_utils.md index e478de1c87..2cf753203e 100644 --- a/docs/sdk/python/human_protocol_sdk.operator.operator_utils.md +++ b/docs/sdk/python/human_protocol_sdk.operator.operator_utils.md @@ -10,7 +10,7 @@ from human_protocol_sdk.operator import OperatorUtils, LeaderFilter print( OperatorUtils.get_leaders( - LeaderFilter(networks=[ChainId.POLYGON_MUMBAI], role="Job Launcher") + LeaderFilter(networks=[ChainId.POLYGON_AMOY], role="Job Launcher") ) ) ``` @@ -94,7 +94,7 @@ Get the leader details. from human_protocol_sdk.operator import OperatorUtils leader = OperatorUtils.get_leader( - ChainId.POLYGON_MUMBAI, + ChainId.POLYGON_AMOY, '0x62dD51230A30401C455c8398d06F85e4EaB6309f' ) ``` @@ -116,7 +116,7 @@ Get leaders data of the protocol print( OperatorUtils.get_leaders( - LeaderFilter(networks=[ChainId.POLYGON_MUMBAI]) + LeaderFilter(networks=[ChainId.POLYGON_AMOY]) ) ) ``` @@ -141,7 +141,7 @@ Get the reputation network operators of the specified address. from human_protocol_sdk.operator import OperatorUtils leader = OperatorUtils.get_reputation_network_operators( - ChainId.POLYGON_MUMBAI, + ChainId.POLYGON_AMOY, '0x62dD51230A30401C455c8398d06F85e4EaB6309f' ) ``` @@ -163,7 +163,7 @@ Get rewards of the given slasher from human_protocol_sdk.operator import OperatorUtils rewards_info = OperatorUtils.get_rewards_info( - ChainId.POLYGON_MUMBAI, + ChainId.POLYGON_AMOY, '0x62dD51230A30401C455c8398d06F85e4EaB6309f' ) ``` diff --git a/docs/sdk/python/human_protocol_sdk.statistics.statistics_client.md b/docs/sdk/python/human_protocol_sdk.statistics.statistics_client.md index 37f9aceca9..0c2bd26c86 100644 --- a/docs/sdk/python/human_protocol_sdk.statistics.statistics_client.md +++ b/docs/sdk/python/human_protocol_sdk.statistics.statistics_client.md @@ -8,7 +8,7 @@ This client enables to obtain statistical information from the subgraph. from human_protocol_sdk.constants import ChainId from human_protocol_sdk.statistics import StatisticsClient -statistics_client = StatisticsClient(ChainId.POLYGON_MUMBAI) +statistics_client = StatisticsClient(ChainId.POLYGON_AMOY) ``` ## Module @@ -134,13 +134,13 @@ Initializes a PaymentStatistics instance. * **Parameters:** **daily_payments_data** (`List`[[`DailyPaymentData`](#human_protocol_sdk.statistics.statistics_client.DailyPaymentData)]) – Daily payments data -### *class* human_protocol_sdk.statistics.statistics_client.StatisticsClient(chain_id=ChainId.POLYGON_MUMBAI) +### *class* human_protocol_sdk.statistics.statistics_client.StatisticsClient(chain_id=ChainId.POLYGON_AMOY) Bases: `object` A client used to get statistical data. -#### \_\_init_\_(chain_id=ChainId.POLYGON_MUMBAI) +#### \_\_init_\_(chain_id=ChainId.POLYGON_AMOY) Initializes a Statistics instance @@ -162,7 +162,7 @@ Get escrow statistics data for the given date range. from human_protocol_sdk.contants import ChainId from human_protocol_sdk.statistics import StatisticsClient, StatisticsParam - statistics_client = StatisticsClient(ChainId.POLYGON_MUMBAI) + statistics_client = StatisticsClient(ChainId.POLYGON_AMOY) print(statistics_client.get_escrow_statistics()) print( @@ -190,7 +190,7 @@ Get HMT statistics data for the given date range. from human_protocol_sdk.contants import ChainId from human_protocol_sdk.statistics import StatisticsClient, StatisticsParam - statistics_client = StatisticsClient(ChainId.POLYGON_MUMBAI) + statistics_client = StatisticsClient(ChainId.POLYGON_AMOY) print(statistics_client.get_hmt_statistics()) print( @@ -218,7 +218,7 @@ Get payment statistics data for the given date range. from human_protocol_sdk.contants import ChainId from human_protocol_sdk.statistics import StatisticsClient, StatisticsParam - statistics_client = StatisticsClient(ChainId.POLYGON_MUMBAI) + statistics_client = StatisticsClient(ChainId.POLYGON_AMOY) print(statistics_client.get_payment_statistics()) print( @@ -246,7 +246,7 @@ Get worker statistics data for the given date range. from human_protocol_sdk.contants import ChainId from human_protocol_sdk.statistics import StatisticsClient, StatisticsParam - statistics_client = StatisticsClient(ChainId.POLYGON_MUMBAI) + statistics_client = StatisticsClient(ChainId.POLYGON_AMOY) print(statistics_client.get_worker_statistics()) print( diff --git a/docs/sdk/typescript/classes/base.BaseEthersClient.md b/docs/sdk/typescript/classes/base.BaseEthersClient.md index f40127c63b..3c10160774 100644 --- a/docs/sdk/typescript/classes/base.BaseEthersClient.md +++ b/docs/sdk/typescript/classes/base.BaseEthersClient.md @@ -50,7 +50,11 @@ This class is used as a base class for other clients making on-chain calls. #### Defined in +<<<<<<< HEAD +[base.ts:20](https://github.com/humanprotocol/human-protocol/blob/4a01940c/packages/sdk/typescript/human-protocol-sdk/src/base.ts#L20) +======= [base.ts:20](https://github.com/humanprotocol/human-protocol/blob/e4b60ab1/packages/sdk/typescript/human-protocol-sdk/src/base.ts#L20) +>>>>>>> develop ## Properties @@ -60,7 +64,11 @@ This class is used as a base class for other clients making on-chain calls. #### Defined in +<<<<<<< HEAD +[base.ts:12](https://github.com/humanprotocol/human-protocol/blob/4a01940c/packages/sdk/typescript/human-protocol-sdk/src/base.ts#L12) +======= [base.ts:12](https://github.com/humanprotocol/human-protocol/blob/e4b60ab1/packages/sdk/typescript/human-protocol-sdk/src/base.ts#L12) +>>>>>>> develop ___ @@ -70,4 +78,8 @@ ___ #### Defined in +<<<<<<< HEAD +[base.ts:11](https://github.com/humanprotocol/human-protocol/blob/4a01940c/packages/sdk/typescript/human-protocol-sdk/src/base.ts#L11) +======= [base.ts:11](https://github.com/humanprotocol/human-protocol/blob/e4b60ab1/packages/sdk/typescript/human-protocol-sdk/src/base.ts#L11) +>>>>>>> develop diff --git a/docs/sdk/typescript/classes/encryption.Encryption.md b/docs/sdk/typescript/classes/encryption.Encryption.md index c662c7c5d4..2c4f088735 100644 --- a/docs/sdk/typescript/classes/encryption.Encryption.md +++ b/docs/sdk/typescript/classes/encryption.Encryption.md @@ -81,7 +81,11 @@ Constructor for the Encryption class. #### Defined in +<<<<<<< HEAD +[encryption.ts:53](https://github.com/humanprotocol/human-protocol/blob/4a01940c/packages/sdk/typescript/human-protocol-sdk/src/encryption.ts#L53) +======= [encryption.ts:53](https://github.com/humanprotocol/human-protocol/blob/e4b60ab1/packages/sdk/typescript/human-protocol-sdk/src/encryption.ts#L53) +>>>>>>> develop ## Properties @@ -91,7 +95,11 @@ Constructor for the Encryption class. #### Defined in +<<<<<<< HEAD +[encryption.ts:46](https://github.com/humanprotocol/human-protocol/blob/4a01940c/packages/sdk/typescript/human-protocol-sdk/src/encryption.ts#L46) +======= [encryption.ts:46](https://github.com/humanprotocol/human-protocol/blob/e4b60ab1/packages/sdk/typescript/human-protocol-sdk/src/encryption.ts#L46) +>>>>>>> develop ## Methods @@ -140,7 +148,11 @@ const resultMessage = await encription.decrypt('message'); #### Defined in +<<<<<<< HEAD +[encryption.ts:180](https://github.com/humanprotocol/human-protocol/blob/4a01940c/packages/sdk/typescript/human-protocol-sdk/src/encryption.ts#L180) +======= [encryption.ts:180](https://github.com/humanprotocol/human-protocol/blob/e4b60ab1/packages/sdk/typescript/human-protocol-sdk/src/encryption.ts#L180) +>>>>>>> develop ___ @@ -176,7 +188,11 @@ const resultMessage = await encription.sign('message'); #### Defined in +<<<<<<< HEAD +[encryption.ts:217](https://github.com/humanprotocol/human-protocol/blob/4a01940c/packages/sdk/typescript/human-protocol-sdk/src/encryption.ts#L217) +======= [encryption.ts:217](https://github.com/humanprotocol/human-protocol/blob/e4b60ab1/packages/sdk/typescript/human-protocol-sdk/src/encryption.ts#L217) +>>>>>>> develop ___ @@ -238,7 +254,11 @@ const resultMessage = await encription.signAndEncrypt('message', publicKeys); #### Defined in +<<<<<<< HEAD +[encryption.ts:129](https://github.com/humanprotocol/human-protocol/blob/4a01940c/packages/sdk/typescript/human-protocol-sdk/src/encryption.ts#L129) +======= [encryption.ts:129](https://github.com/humanprotocol/human-protocol/blob/e4b60ab1/packages/sdk/typescript/human-protocol-sdk/src/encryption.ts#L129) +>>>>>>> develop ___ @@ -263,4 +283,8 @@ Builds an Encryption instance by decrypting the private key from an encrypted pr #### Defined in +<<<<<<< HEAD +[encryption.ts:64](https://github.com/humanprotocol/human-protocol/blob/4a01940c/packages/sdk/typescript/human-protocol-sdk/src/encryption.ts#L64) +======= [encryption.ts:64](https://github.com/humanprotocol/human-protocol/blob/e4b60ab1/packages/sdk/typescript/human-protocol-sdk/src/encryption.ts#L64) +>>>>>>> develop diff --git a/docs/sdk/typescript/classes/encryption.EncryptionUtils.md b/docs/sdk/typescript/classes/encryption.EncryptionUtils.md index 70bea3054a..17dbecde01 100644 --- a/docs/sdk/typescript/classes/encryption.EncryptionUtils.md +++ b/docs/sdk/typescript/classes/encryption.EncryptionUtils.md @@ -108,7 +108,11 @@ const result = await EncriptionUtils.encrypt('message', publicKeys); #### Defined in +<<<<<<< HEAD +[encryption.ts:422](https://github.com/humanprotocol/human-protocol/blob/4a01940c/packages/sdk/typescript/human-protocol-sdk/src/encryption.ts#L422) +======= [encryption.ts:422](https://github.com/humanprotocol/human-protocol/blob/e4b60ab1/packages/sdk/typescript/human-protocol-sdk/src/encryption.ts#L422) +>>>>>>> develop ___ @@ -157,7 +161,11 @@ const result = await EncriptionUtils.generateKeyPair(name, email, passphrase); #### Defined in +<<<<<<< HEAD +[encryption.ts:360](https://github.com/humanprotocol/human-protocol/blob/4a01940c/packages/sdk/typescript/human-protocol-sdk/src/encryption.ts#L360) +======= [encryption.ts:360](https://github.com/humanprotocol/human-protocol/blob/e4b60ab1/packages/sdk/typescript/human-protocol-sdk/src/encryption.ts#L360) +>>>>>>> develop ___ @@ -189,7 +197,11 @@ const signedData = await EncriptionUtils.getSignedData('message'); #### Defined in +<<<<<<< HEAD +[encryption.ts:317](https://github.com/humanprotocol/human-protocol/blob/4a01940c/packages/sdk/typescript/human-protocol-sdk/src/encryption.ts#L317) +======= [encryption.ts:317](https://github.com/humanprotocol/human-protocol/blob/e4b60ab1/packages/sdk/typescript/human-protocol-sdk/src/encryption.ts#L317) +>>>>>>> develop ___ @@ -237,7 +249,11 @@ if (isEncrypted) { #### Defined in +<<<<<<< HEAD +[encryption.ts:471](https://github.com/humanprotocol/human-protocol/blob/4a01940c/packages/sdk/typescript/human-protocol-sdk/src/encryption.ts#L471) +======= [encryption.ts:471](https://github.com/humanprotocol/human-protocol/blob/e4b60ab1/packages/sdk/typescript/human-protocol-sdk/src/encryption.ts#L471) +>>>>>>> develop ___ @@ -282,4 +298,8 @@ const result = await EncriptionUtils.verify('message', publicKey); #### Defined in +<<<<<<< HEAD +[encryption.ts:284](https://github.com/humanprotocol/human-protocol/blob/4a01940c/packages/sdk/typescript/human-protocol-sdk/src/encryption.ts#L284) +======= [encryption.ts:284](https://github.com/humanprotocol/human-protocol/blob/e4b60ab1/packages/sdk/typescript/human-protocol-sdk/src/encryption.ts#L284) +>>>>>>> develop diff --git a/docs/sdk/typescript/classes/escrow.EscrowClient.md b/docs/sdk/typescript/classes/escrow.EscrowClient.md index a22887728b..841db8138a 100644 --- a/docs/sdk/typescript/classes/escrow.EscrowClient.md +++ b/docs/sdk/typescript/classes/escrow.EscrowClient.md @@ -142,7 +142,11 @@ const escrowClient = await EscrowClient.build(provider); #### Defined in +<<<<<<< HEAD +[escrow.ts:127](https://github.com/humanprotocol/human-protocol/blob/4a01940c/packages/sdk/typescript/human-protocol-sdk/src/escrow.ts#L127) +======= [escrow.ts:127](https://github.com/humanprotocol/human-protocol/blob/e4b60ab1/packages/sdk/typescript/human-protocol-sdk/src/escrow.ts#L127) +>>>>>>> develop ## Properties @@ -152,7 +156,11 @@ const escrowClient = await EscrowClient.build(provider); #### Defined in +<<<<<<< HEAD +[escrow.ts:119](https://github.com/humanprotocol/human-protocol/blob/4a01940c/packages/sdk/typescript/human-protocol-sdk/src/escrow.ts#L119) +======= [escrow.ts:119](https://github.com/humanprotocol/human-protocol/blob/e4b60ab1/packages/sdk/typescript/human-protocol-sdk/src/escrow.ts#L119) +>>>>>>> develop ___ @@ -166,7 +174,11 @@ ___ #### Defined in +<<<<<<< HEAD +[base.ts:12](https://github.com/humanprotocol/human-protocol/blob/4a01940c/packages/sdk/typescript/human-protocol-sdk/src/base.ts#L12) +======= [base.ts:12](https://github.com/humanprotocol/human-protocol/blob/e4b60ab1/packages/sdk/typescript/human-protocol-sdk/src/base.ts#L12) +>>>>>>> develop ___ @@ -180,7 +192,11 @@ ___ #### Defined in +<<<<<<< HEAD +[base.ts:11](https://github.com/humanprotocol/human-protocol/blob/4a01940c/packages/sdk/typescript/human-protocol-sdk/src/base.ts#L11) +======= [base.ts:11](https://github.com/humanprotocol/human-protocol/blob/e4b60ab1/packages/sdk/typescript/human-protocol-sdk/src/base.ts#L11) +>>>>>>> develop ## Methods @@ -223,7 +239,11 @@ await escrowClient.abort('0x62dD51230A30401C455c8398d06F85e4EaB6309f'); #### Defined in +<<<<<<< HEAD +[escrow.ts:835](https://github.com/humanprotocol/human-protocol/blob/4a01940c/packages/sdk/typescript/human-protocol-sdk/src/escrow.ts#L835) +======= [escrow.ts:835](https://github.com/humanprotocol/human-protocol/blob/e4b60ab1/packages/sdk/typescript/human-protocol-sdk/src/escrow.ts#L835) +>>>>>>> develop ___ @@ -268,7 +288,11 @@ await escrowClient.addTrustedHandlers('0x62dD51230A30401C455c8398d06F85e4EaB6309 #### Defined in +<<<<<<< HEAD +[escrow.ts:883](https://github.com/humanprotocol/human-protocol/blob/4a01940c/packages/sdk/typescript/human-protocol-sdk/src/escrow.ts#L883) +======= [escrow.ts:883](https://github.com/humanprotocol/human-protocol/blob/e4b60ab1/packages/sdk/typescript/human-protocol-sdk/src/escrow.ts#L883) +>>>>>>> develop ___ @@ -320,7 +344,11 @@ await escrowClient.bulkPayOut('0x62dD51230A30401C455c8398d06F85e4EaB6309f', reci #### Defined in +<<<<<<< HEAD +[escrow.ts:648](https://github.com/humanprotocol/human-protocol/blob/4a01940c/packages/sdk/typescript/human-protocol-sdk/src/escrow.ts#L648) +======= [escrow.ts:648](https://github.com/humanprotocol/human-protocol/blob/e4b60ab1/packages/sdk/typescript/human-protocol-sdk/src/escrow.ts#L648) +>>>>>>> develop ___ @@ -363,7 +391,11 @@ await escrowClient.cancel('0x62dD51230A30401C455c8398d06F85e4EaB6309f'); #### Defined in +<<<<<<< HEAD +[escrow.ts:751](https://github.com/humanprotocol/human-protocol/blob/4a01940c/packages/sdk/typescript/human-protocol-sdk/src/escrow.ts#L751) +======= [escrow.ts:751](https://github.com/humanprotocol/human-protocol/blob/e4b60ab1/packages/sdk/typescript/human-protocol-sdk/src/escrow.ts#L751) +>>>>>>> develop ___ @@ -406,7 +438,11 @@ await escrowClient.complete('0x62dD51230A30401C455c8398d06F85e4EaB6309f'); #### Defined in +<<<<<<< HEAD +[escrow.ts:590](https://github.com/humanprotocol/human-protocol/blob/4a01940c/packages/sdk/typescript/human-protocol-sdk/src/escrow.ts#L590) +======= [escrow.ts:590](https://github.com/humanprotocol/human-protocol/blob/e4b60ab1/packages/sdk/typescript/human-protocol-sdk/src/escrow.ts#L590) +>>>>>>> develop ___ @@ -464,7 +500,11 @@ const escrowAddress = await escrowClient.createAndSetupEscrow(tokenAddress, trus #### Defined in +<<<<<<< HEAD +[escrow.ts:413](https://github.com/humanprotocol/human-protocol/blob/4a01940c/packages/sdk/typescript/human-protocol-sdk/src/escrow.ts#L413) +======= [escrow.ts:413](https://github.com/humanprotocol/human-protocol/blob/e4b60ab1/packages/sdk/typescript/human-protocol-sdk/src/escrow.ts#L413) +>>>>>>> develop ___ @@ -512,7 +552,11 @@ const escrowAddress = await escrowClient.createEscrow(tokenAddress, trustedHandl #### Defined in +<<<<<<< HEAD +[escrow.ts:207](https://github.com/humanprotocol/human-protocol/blob/4a01940c/packages/sdk/typescript/human-protocol-sdk/src/escrow.ts#L207) +======= [escrow.ts:207](https://github.com/humanprotocol/human-protocol/blob/e4b60ab1/packages/sdk/typescript/human-protocol-sdk/src/escrow.ts#L207) +>>>>>>> develop ___ @@ -555,7 +599,11 @@ await escrowClient.fund('0x62dD51230A30401C455c8398d06F85e4EaB6309f', amount); #### Defined in +<<<<<<< HEAD +[escrow.ts:461](https://github.com/humanprotocol/human-protocol/blob/4a01940c/packages/sdk/typescript/human-protocol-sdk/src/escrow.ts#L461) +======= [escrow.ts:461](https://github.com/humanprotocol/human-protocol/blob/e4b60ab1/packages/sdk/typescript/human-protocol-sdk/src/escrow.ts#L461) +>>>>>>> develop ___ @@ -593,7 +641,11 @@ const balance = await escrowClient.getBalance('0x62dD51230A30401C455c8398d06F85e #### Defined in +<<<<<<< HEAD +[escrow.ts:938](https://github.com/humanprotocol/human-protocol/blob/4a01940c/packages/sdk/typescript/human-protocol-sdk/src/escrow.ts#L938) +======= [escrow.ts:938](https://github.com/humanprotocol/human-protocol/blob/e4b60ab1/packages/sdk/typescript/human-protocol-sdk/src/escrow.ts#L938) +>>>>>>> develop ___ @@ -615,7 +667,11 @@ Connects to the escrow contract #### Defined in +<<<<<<< HEAD +[escrow.ts:167](https://github.com/humanprotocol/human-protocol/blob/4a01940c/packages/sdk/typescript/human-protocol-sdk/src/escrow.ts#L167) +======= [escrow.ts:167](https://github.com/humanprotocol/human-protocol/blob/e4b60ab1/packages/sdk/typescript/human-protocol-sdk/src/escrow.ts#L167) +>>>>>>> develop ___ @@ -653,7 +709,11 @@ const oracleAddress = await escrowClient.getExchangeOracleAddress('0x62dD51230A3 #### Defined in +<<<<<<< HEAD +[escrow.ts:1318](https://github.com/humanprotocol/human-protocol/blob/4a01940c/packages/sdk/typescript/human-protocol-sdk/src/escrow.ts#L1318) +======= [escrow.ts:1318](https://github.com/humanprotocol/human-protocol/blob/e4b60ab1/packages/sdk/typescript/human-protocol-sdk/src/escrow.ts#L1318) +>>>>>>> develop ___ @@ -691,7 +751,11 @@ const factoryAddress = await escrowClient.getFactoryAddress('0x62dD51230A30401C4 #### Defined in +<<<<<<< HEAD +[escrow.ts:1356](https://github.com/humanprotocol/human-protocol/blob/4a01940c/packages/sdk/typescript/human-protocol-sdk/src/escrow.ts#L1356) +======= [escrow.ts:1356](https://github.com/humanprotocol/human-protocol/blob/e4b60ab1/packages/sdk/typescript/human-protocol-sdk/src/escrow.ts#L1356) +>>>>>>> develop ___ @@ -729,7 +793,11 @@ const intemediateResultsUrl = await escrowClient.getIntermediateResultsUrl('0x62 #### Defined in +<<<<<<< HEAD +[escrow.ts:1090](https://github.com/humanprotocol/human-protocol/blob/4a01940c/packages/sdk/typescript/human-protocol-sdk/src/escrow.ts#L1090) +======= [escrow.ts:1090](https://github.com/humanprotocol/human-protocol/blob/e4b60ab1/packages/sdk/typescript/human-protocol-sdk/src/escrow.ts#L1090) +>>>>>>> develop ___ @@ -767,7 +835,11 @@ const jobLauncherAddress = await escrowClient.getJobLauncherAddress('0x62dD51230 #### Defined in +<<<<<<< HEAD +[escrow.ts:1242](https://github.com/humanprotocol/human-protocol/blob/4a01940c/packages/sdk/typescript/human-protocol-sdk/src/escrow.ts#L1242) +======= [escrow.ts:1242](https://github.com/humanprotocol/human-protocol/blob/e4b60ab1/packages/sdk/typescript/human-protocol-sdk/src/escrow.ts#L1242) +>>>>>>> develop ___ @@ -805,7 +877,11 @@ const manifestHash = await escrowClient.getManifestHash('0x62dD51230A30401C455c8 #### Defined in +<<<<<<< HEAD +[escrow.ts:976](https://github.com/humanprotocol/human-protocol/blob/4a01940c/packages/sdk/typescript/human-protocol-sdk/src/escrow.ts#L976) +======= [escrow.ts:976](https://github.com/humanprotocol/human-protocol/blob/e4b60ab1/packages/sdk/typescript/human-protocol-sdk/src/escrow.ts#L976) +>>>>>>> develop ___ @@ -843,7 +919,11 @@ const manifestUrl = await escrowClient.getManifestUrl('0x62dD51230A30401C455c839 #### Defined in +<<<<<<< HEAD +[escrow.ts:1014](https://github.com/humanprotocol/human-protocol/blob/4a01940c/packages/sdk/typescript/human-protocol-sdk/src/escrow.ts#L1014) +======= [escrow.ts:1014](https://github.com/humanprotocol/human-protocol/blob/e4b60ab1/packages/sdk/typescript/human-protocol-sdk/src/escrow.ts#L1014) +>>>>>>> develop ___ @@ -881,7 +961,11 @@ const oracleAddress = await escrowClient.getRecordingOracleAddress('0x62dD51230A #### Defined in +<<<<<<< HEAD +[escrow.ts:1204](https://github.com/humanprotocol/human-protocol/blob/4a01940c/packages/sdk/typescript/human-protocol-sdk/src/escrow.ts#L1204) +======= [escrow.ts:1204](https://github.com/humanprotocol/human-protocol/blob/e4b60ab1/packages/sdk/typescript/human-protocol-sdk/src/escrow.ts#L1204) +>>>>>>> develop ___ @@ -919,7 +1003,11 @@ const oracleAddress = await escrowClient.getReputationOracleAddress('0x62dD51230 #### Defined in +<<<<<<< HEAD +[escrow.ts:1280](https://github.com/humanprotocol/human-protocol/blob/4a01940c/packages/sdk/typescript/human-protocol-sdk/src/escrow.ts#L1280) +======= [escrow.ts:1280](https://github.com/humanprotocol/human-protocol/blob/e4b60ab1/packages/sdk/typescript/human-protocol-sdk/src/escrow.ts#L1280) +>>>>>>> develop ___ @@ -957,7 +1045,11 @@ const resultsUrl = await escrowClient.getResultsUrl('0x62dD51230A30401C455c8398d #### Defined in +<<<<<<< HEAD +[escrow.ts:1052](https://github.com/humanprotocol/human-protocol/blob/4a01940c/packages/sdk/typescript/human-protocol-sdk/src/escrow.ts#L1052) +======= [escrow.ts:1052](https://github.com/humanprotocol/human-protocol/blob/e4b60ab1/packages/sdk/typescript/human-protocol-sdk/src/escrow.ts#L1052) +>>>>>>> develop ___ @@ -995,7 +1087,11 @@ const status = await escrowClient.getStatus('0x62dD51230A30401C455c8398d06F85e4E #### Defined in +<<<<<<< HEAD +[escrow.ts:1166](https://github.com/humanprotocol/human-protocol/blob/4a01940c/packages/sdk/typescript/human-protocol-sdk/src/escrow.ts#L1166) +======= [escrow.ts:1166](https://github.com/humanprotocol/human-protocol/blob/e4b60ab1/packages/sdk/typescript/human-protocol-sdk/src/escrow.ts#L1166) +>>>>>>> develop ___ @@ -1033,7 +1129,11 @@ const tokenAddress = await escrowClient.getTokenAddress('0x62dD51230A30401C455c8 #### Defined in +<<<<<<< HEAD +[escrow.ts:1128](https://github.com/humanprotocol/human-protocol/blob/4a01940c/packages/sdk/typescript/human-protocol-sdk/src/escrow.ts#L1128) +======= [escrow.ts:1128](https://github.com/humanprotocol/human-protocol/blob/e4b60ab1/packages/sdk/typescript/human-protocol-sdk/src/escrow.ts#L1128) +>>>>>>> develop ___ @@ -1088,7 +1188,11 @@ await escrowClient.setup(escrowAddress, escrowConfig); #### Defined in +<<<<<<< HEAD +[escrow.ts:288](https://github.com/humanprotocol/human-protocol/blob/4a01940c/packages/sdk/typescript/human-protocol-sdk/src/escrow.ts#L288) +======= [escrow.ts:288](https://github.com/humanprotocol/human-protocol/blob/e4b60ab1/packages/sdk/typescript/human-protocol-sdk/src/escrow.ts#L288) +>>>>>>> develop ___ @@ -1133,7 +1237,11 @@ await storeResults.storeResults('0x62dD51230A30401C455c8398d06F85e4EaB6309f', 'h #### Defined in +<<<<<<< HEAD +[escrow.ts:526](https://github.com/humanprotocol/human-protocol/blob/4a01940c/packages/sdk/typescript/human-protocol-sdk/src/escrow.ts#L526) +======= [escrow.ts:526](https://github.com/humanprotocol/human-protocol/blob/e4b60ab1/packages/sdk/typescript/human-protocol-sdk/src/escrow.ts#L526) +>>>>>>> develop ___ @@ -1165,4 +1273,8 @@ Thrown if the network's chainId is not supported #### Defined in +<<<<<<< HEAD +[escrow.ts:145](https://github.com/humanprotocol/human-protocol/blob/4a01940c/packages/sdk/typescript/human-protocol-sdk/src/escrow.ts#L145) +======= [escrow.ts:145](https://github.com/humanprotocol/human-protocol/blob/e4b60ab1/packages/sdk/typescript/human-protocol-sdk/src/escrow.ts#L145) +>>>>>>> develop diff --git a/docs/sdk/typescript/classes/escrow.EscrowUtils.md b/docs/sdk/typescript/classes/escrow.EscrowUtils.md index f5c8ee4945..7a9fac1d3b 100644 --- a/docs/sdk/typescript/classes/escrow.EscrowUtils.md +++ b/docs/sdk/typescript/classes/escrow.EscrowUtils.md @@ -30,7 +30,7 @@ yarn install @human-protocol/sdk import { ChainId, EscrowUtils } from '@human-protocol/sdk'; const escrowAddresses = new EscrowUtils.getEscrows({ - networks: [ChainId.POLYGON_MUMBAI] + networks: [ChainId.POLYGON_AMOY] }); ``` @@ -77,6 +77,7 @@ enum ChainId { BSC_TESTNET = 97, POLYGON = 137, POLYGON_MUMBAI = 80001, + POLYGON_AMOY = 80002, MOONBEAM = 1284, MOONBASE_ALPHA = 1287, AVALANCHE = 43114, @@ -133,12 +134,16 @@ Escrow data ```ts import { ChainId, EscrowUtils } from '@human-protocol/sdk'; -const escrowData = new EscrowUtils.getEscrow(ChainId.POLYGON_MUMBAI, "0x1234567890123456789012345678901234567890"); +const escrowData = new EscrowUtils.getEscrow(ChainId.POLYGON_AMOY, "0x1234567890123456789012345678901234567890"); ``` #### Defined in +<<<<<<< HEAD +[escrow.ts:1634](https://github.com/humanprotocol/human-protocol/blob/4a01940c/packages/sdk/typescript/human-protocol-sdk/src/escrow.ts#L1634) +======= [escrow.ts:1632](https://github.com/humanprotocol/human-protocol/blob/e4b60ab1/packages/sdk/typescript/human-protocol-sdk/src/escrow.ts#L1632) +>>>>>>> develop ___ @@ -174,6 +179,7 @@ enum ChainId { BSC_TESTNET = 97, POLYGON = 137, POLYGON_MUMBAI = 80001, + POLYGON_AMOY=80002, MOONBEAM = 1284, MOONBASE_ALPHA = 1287, AVALANCHE = 43114, @@ -244,11 +250,15 @@ const filters: IEscrowsFilter = { status: EscrowStatus.Pending, from: new Date(2023, 4, 8), to: new Date(2023, 5, 8), - networks: [ChainId.POLYGON_MUMBAI] + networks: [ChainId.POLYGON_AMOY] }; const escrowDatas = await EscrowUtils.getEscrows(filters); ``` #### Defined in +<<<<<<< HEAD +[escrow.ts:1505](https://github.com/humanprotocol/human-protocol/blob/4a01940c/packages/sdk/typescript/human-protocol-sdk/src/escrow.ts#L1505) +======= [escrow.ts:1504](https://github.com/humanprotocol/human-protocol/blob/e4b60ab1/packages/sdk/typescript/human-protocol-sdk/src/escrow.ts#L1504) +>>>>>>> develop diff --git a/docs/sdk/typescript/classes/kvstore.KVStoreClient.md b/docs/sdk/typescript/classes/kvstore.KVStoreClient.md index 28179499d7..6b68e394e5 100644 --- a/docs/sdk/typescript/classes/kvstore.KVStoreClient.md +++ b/docs/sdk/typescript/classes/kvstore.KVStoreClient.md @@ -125,7 +125,11 @@ const kvstoreClient = await KVStoreClient.build(signer); #### Defined in +<<<<<<< HEAD +[kvstore.ts:100](https://github.com/humanprotocol/human-protocol/blob/4a01940c/packages/sdk/typescript/human-protocol-sdk/src/kvstore.ts#L100) +======= [kvstore.ts:100](https://github.com/humanprotocol/human-protocol/blob/e4b60ab1/packages/sdk/typescript/human-protocol-sdk/src/kvstore.ts#L100) +>>>>>>> develop ## Properties @@ -135,7 +139,11 @@ const kvstoreClient = await KVStoreClient.build(signer); #### Defined in +<<<<<<< HEAD +[kvstore.ts:92](https://github.com/humanprotocol/human-protocol/blob/4a01940c/packages/sdk/typescript/human-protocol-sdk/src/kvstore.ts#L92) +======= [kvstore.ts:92](https://github.com/humanprotocol/human-protocol/blob/e4b60ab1/packages/sdk/typescript/human-protocol-sdk/src/kvstore.ts#L92) +>>>>>>> develop ___ @@ -149,7 +157,11 @@ ___ #### Defined in +<<<<<<< HEAD +[base.ts:12](https://github.com/humanprotocol/human-protocol/blob/4a01940c/packages/sdk/typescript/human-protocol-sdk/src/base.ts#L12) +======= [base.ts:12](https://github.com/humanprotocol/human-protocol/blob/e4b60ab1/packages/sdk/typescript/human-protocol-sdk/src/base.ts#L12) +>>>>>>> develop ___ @@ -163,7 +175,11 @@ ___ #### Defined in +<<<<<<< HEAD +[base.ts:11](https://github.com/humanprotocol/human-protocol/blob/4a01940c/packages/sdk/typescript/human-protocol-sdk/src/base.ts#L11) +======= [base.ts:11](https://github.com/humanprotocol/human-protocol/blob/e4b60ab1/packages/sdk/typescript/human-protocol-sdk/src/base.ts#L11) +>>>>>>> develop ## Methods @@ -204,7 +220,11 @@ const value = await kvstoreClient.get('0xf39Fd6e51aad88F6F4ce6aB8827279cffFb9226 #### Defined in +<<<<<<< HEAD +[kvstore.ts:301](https://github.com/humanprotocol/human-protocol/blob/4a01940c/packages/sdk/typescript/human-protocol-sdk/src/kvstore.ts#L301) +======= [kvstore.ts:301](https://github.com/humanprotocol/human-protocol/blob/e4b60ab1/packages/sdk/typescript/human-protocol-sdk/src/kvstore.ts#L301) +>>>>>>> develop ___ @@ -247,7 +267,11 @@ const linkedinUrl = await kvstoreClient.getFileUrlAndVerifyHash( #### Defined in +<<<<<<< HEAD +[kvstore.ts:340](https://github.com/humanprotocol/human-protocol/blob/4a01940c/packages/sdk/typescript/human-protocol-sdk/src/kvstore.ts#L340) +======= [kvstore.ts:340](https://github.com/humanprotocol/human-protocol/blob/e4b60ab1/packages/sdk/typescript/human-protocol-sdk/src/kvstore.ts#L340) +>>>>>>> develop ___ @@ -285,7 +309,11 @@ const publicKey = await kvstoreClient.getPublicKey('0xf39Fd6e51aad88F6F4ce6aB882 #### Defined in +<<<<<<< HEAD +[kvstore.ts:398](https://github.com/humanprotocol/human-protocol/blob/4a01940c/packages/sdk/typescript/human-protocol-sdk/src/kvstore.ts#L398) +======= [kvstore.ts:398](https://github.com/humanprotocol/human-protocol/blob/e4b60ab1/packages/sdk/typescript/human-protocol-sdk/src/kvstore.ts#L398) +>>>>>>> develop ___ @@ -329,7 +357,11 @@ await kvstoreClient.set('Role', 'RecordingOracle'); #### Defined in +<<<<<<< HEAD +[kvstore.ts:163](https://github.com/humanprotocol/human-protocol/blob/4a01940c/packages/sdk/typescript/human-protocol-sdk/src/kvstore.ts#L163) +======= [kvstore.ts:163](https://github.com/humanprotocol/human-protocol/blob/e4b60ab1/packages/sdk/typescript/human-protocol-sdk/src/kvstore.ts#L163) +>>>>>>> develop ___ @@ -375,7 +407,11 @@ await kvstoreClient.set(keys, values); #### Defined in +<<<<<<< HEAD +[kvstore.ts:206](https://github.com/humanprotocol/human-protocol/blob/4a01940c/packages/sdk/typescript/human-protocol-sdk/src/kvstore.ts#L206) +======= [kvstore.ts:206](https://github.com/humanprotocol/human-protocol/blob/e4b60ab1/packages/sdk/typescript/human-protocol-sdk/src/kvstore.ts#L206) +>>>>>>> develop ___ @@ -418,7 +454,11 @@ await kvstoreClient.setFileUrlAndHash('linkedin.com/example', 'linkedin_url); #### Defined in +<<<<<<< HEAD +[kvstore.ts:249](https://github.com/humanprotocol/human-protocol/blob/4a01940c/packages/sdk/typescript/human-protocol-sdk/src/kvstore.ts#L249) +======= [kvstore.ts:249](https://github.com/humanprotocol/human-protocol/blob/e4b60ab1/packages/sdk/typescript/human-protocol-sdk/src/kvstore.ts#L249) +>>>>>>> develop ___ @@ -450,4 +490,8 @@ Creates an instance of KVStoreClient from a runner. #### Defined in +<<<<<<< HEAD +[kvstore.ts:118](https://github.com/humanprotocol/human-protocol/blob/4a01940c/packages/sdk/typescript/human-protocol-sdk/src/kvstore.ts#L118) +======= [kvstore.ts:118](https://github.com/humanprotocol/human-protocol/blob/e4b60ab1/packages/sdk/typescript/human-protocol-sdk/src/kvstore.ts#L118) +>>>>>>> develop diff --git a/docs/sdk/typescript/classes/operator.OperatorUtils.md b/docs/sdk/typescript/classes/operator.OperatorUtils.md index 4c0f52dabf..76e2ba9626 100644 --- a/docs/sdk/typescript/classes/operator.OperatorUtils.md +++ b/docs/sdk/typescript/classes/operator.OperatorUtils.md @@ -53,12 +53,16 @@ Returns the leader details. ```ts import { OperatorUtils, ChainId } from '@human-protocol/sdk'; -const leader = await OperatorUtils.getLeader(ChainId.POLYGON_MUMBAI, '0x62dD51230A30401C455c8398d06F85e4EaB6309f'); +const leader = await OperatorUtils.getLeader(ChainId.POLYGON_AMOY, '0x62dD51230A30401C455c8398d06F85e4EaB6309f'); ``` #### Defined in +<<<<<<< HEAD +[operator.ts:43](https://github.com/humanprotocol/human-protocol/blob/4a01940c/packages/sdk/typescript/human-protocol-sdk/src/operator.ts#L43) +======= [operator.ts:43](https://github.com/humanprotocol/human-protocol/blob/e4b60ab1/packages/sdk/typescript/human-protocol-sdk/src/operator.ts#L43) +>>>>>>> develop ___ @@ -90,7 +94,11 @@ const leaders = await OperatorUtils.getLeaders(); #### Defined in +<<<<<<< HEAD +[operator.ts:84](https://github.com/humanprotocol/human-protocol/blob/4a01940c/packages/sdk/typescript/human-protocol-sdk/src/operator.ts#L84) +======= [operator.ts:84](https://github.com/humanprotocol/human-protocol/blob/e4b60ab1/packages/sdk/typescript/human-protocol-sdk/src/operator.ts#L84) +>>>>>>> develop ___ @@ -119,12 +127,16 @@ Retrieves the reputation network operators of the specified address. ```typescript import { OperatorUtils, ChainId } from '@human-protocol/sdk'; -const operators = await OperatorUtils.getReputationNetworkOperators(ChainId.POLYGON_MUMBAI, '0x62dD51230A30401C455c8398d06F85e4EaB6309f'); +const operators = await OperatorUtils.getReputationNetworkOperators(ChainId.POLYGON_AMOY, '0x62dD51230A30401C455c8398d06F85e4EaB6309f'); ``` #### Defined in +<<<<<<< HEAD +[operator.ts:123](https://github.com/humanprotocol/human-protocol/blob/4a01940c/packages/sdk/typescript/human-protocol-sdk/src/operator.ts#L123) +======= [operator.ts:123](https://github.com/humanprotocol/human-protocol/blob/e4b60ab1/packages/sdk/typescript/human-protocol-sdk/src/operator.ts#L123) +>>>>>>> develop ___ @@ -152,9 +164,13 @@ Returns an array of Reward objects that contain the rewards earned by the user t ```ts import { OperatorUtils, ChainId } from '@human-protocol/sdk'; -const rewards = await OperatorUtils.getRewards(ChainId.POLYGON_MUMBAI, '0x62dD51230A30401C455c8398d06F85e4EaB6309f'); +const rewards = await OperatorUtils.getRewards(ChainId.POLYGON_AMOY, '0x62dD51230A30401C455c8398d06F85e4EaB6309f'); ``` #### Defined in +<<<<<<< HEAD +[operator.ts:162](https://github.com/humanprotocol/human-protocol/blob/4a01940c/packages/sdk/typescript/human-protocol-sdk/src/operator.ts#L162) +======= [operator.ts:162](https://github.com/humanprotocol/human-protocol/blob/e4b60ab1/packages/sdk/typescript/human-protocol-sdk/src/operator.ts#L162) +>>>>>>> develop diff --git a/docs/sdk/typescript/classes/staking.StakingClient.md b/docs/sdk/typescript/classes/staking.StakingClient.md index 4e34e16a52..538c0c79bd 100644 --- a/docs/sdk/typescript/classes/staking.StakingClient.md +++ b/docs/sdk/typescript/classes/staking.StakingClient.md @@ -132,7 +132,11 @@ const stakingClient = await StakingClient.build(provider); #### Defined in +<<<<<<< HEAD +[staking.ts:111](https://github.com/humanprotocol/human-protocol/blob/4a01940c/packages/sdk/typescript/human-protocol-sdk/src/staking.ts#L111) +======= [staking.ts:111](https://github.com/humanprotocol/human-protocol/blob/e4b60ab1/packages/sdk/typescript/human-protocol-sdk/src/staking.ts#L111) +>>>>>>> develop ## Properties @@ -142,7 +146,11 @@ const stakingClient = await StakingClient.build(provider); #### Defined in +<<<<<<< HEAD +[staking.ts:102](https://github.com/humanprotocol/human-protocol/blob/4a01940c/packages/sdk/typescript/human-protocol-sdk/src/staking.ts#L102) +======= [staking.ts:102](https://github.com/humanprotocol/human-protocol/blob/e4b60ab1/packages/sdk/typescript/human-protocol-sdk/src/staking.ts#L102) +>>>>>>> develop ___ @@ -156,7 +164,11 @@ ___ #### Defined in +<<<<<<< HEAD +[base.ts:12](https://github.com/humanprotocol/human-protocol/blob/4a01940c/packages/sdk/typescript/human-protocol-sdk/src/base.ts#L12) +======= [base.ts:12](https://github.com/humanprotocol/human-protocol/blob/e4b60ab1/packages/sdk/typescript/human-protocol-sdk/src/base.ts#L12) +>>>>>>> develop ___ @@ -166,7 +178,11 @@ ___ #### Defined in +<<<<<<< HEAD +[staking.ts:103](https://github.com/humanprotocol/human-protocol/blob/4a01940c/packages/sdk/typescript/human-protocol-sdk/src/staking.ts#L103) +======= [staking.ts:103](https://github.com/humanprotocol/human-protocol/blob/e4b60ab1/packages/sdk/typescript/human-protocol-sdk/src/staking.ts#L103) +>>>>>>> develop ___ @@ -180,7 +196,11 @@ ___ #### Defined in +<<<<<<< HEAD +[base.ts:11](https://github.com/humanprotocol/human-protocol/blob/4a01940c/packages/sdk/typescript/human-protocol-sdk/src/base.ts#L11) +======= [base.ts:11](https://github.com/humanprotocol/human-protocol/blob/e4b60ab1/packages/sdk/typescript/human-protocol-sdk/src/base.ts#L11) +>>>>>>> develop ___ @@ -190,7 +210,11 @@ ___ #### Defined in +<<<<<<< HEAD +[staking.ts:101](https://github.com/humanprotocol/human-protocol/blob/4a01940c/packages/sdk/typescript/human-protocol-sdk/src/staking.ts#L101) +======= [staking.ts:101](https://github.com/humanprotocol/human-protocol/blob/e4b60ab1/packages/sdk/typescript/human-protocol-sdk/src/staking.ts#L101) +>>>>>>> develop ___ @@ -200,7 +224,11 @@ ___ #### Defined in +<<<<<<< HEAD +[staking.ts:100](https://github.com/humanprotocol/human-protocol/blob/4a01940c/packages/sdk/typescript/human-protocol-sdk/src/staking.ts#L100) +======= [staking.ts:100](https://github.com/humanprotocol/human-protocol/blob/e4b60ab1/packages/sdk/typescript/human-protocol-sdk/src/staking.ts#L100) +>>>>>>> develop ## Methods @@ -245,7 +273,11 @@ await stakingClient.allocate('0x62dD51230A30401C455c8398d06F85e4EaB6309f', amoun #### Defined in +<<<<<<< HEAD +[staking.ts:458](https://github.com/humanprotocol/human-protocol/blob/4a01940c/packages/sdk/typescript/human-protocol-sdk/src/staking.ts#L458) +======= [staking.ts:458](https://github.com/humanprotocol/human-protocol/blob/e4b60ab1/packages/sdk/typescript/human-protocol-sdk/src/staking.ts#L458) +>>>>>>> develop ___ @@ -287,7 +319,11 @@ await stakingClient.approveStake(amount); #### Defined in +<<<<<<< HEAD +[staking.ts:203](https://github.com/humanprotocol/human-protocol/blob/4a01940c/packages/sdk/typescript/human-protocol-sdk/src/staking.ts#L203) +======= [staking.ts:203](https://github.com/humanprotocol/human-protocol/blob/e4b60ab1/packages/sdk/typescript/human-protocol-sdk/src/staking.ts#L203) +>>>>>>> develop ___ @@ -309,7 +345,11 @@ Check if escrow exists #### Defined in +<<<<<<< HEAD +[staking.ts:167](https://github.com/humanprotocol/human-protocol/blob/4a01940c/packages/sdk/typescript/human-protocol-sdk/src/staking.ts#L167) +======= [staking.ts:167](https://github.com/humanprotocol/human-protocol/blob/e4b60ab1/packages/sdk/typescript/human-protocol-sdk/src/staking.ts#L167) +>>>>>>> develop ___ @@ -353,7 +393,11 @@ await stakingClient.closeAllocation('0x62dD51230A30401C455c8398d06F85e4EaB6309f' #### Defined in +<<<<<<< HEAD +[staking.ts:511](https://github.com/humanprotocol/human-protocol/blob/4a01940c/packages/sdk/typescript/human-protocol-sdk/src/staking.ts#L511) +======= [staking.ts:511](https://github.com/humanprotocol/human-protocol/blob/e4b60ab1/packages/sdk/typescript/human-protocol-sdk/src/staking.ts#L511) +>>>>>>> develop ___ @@ -396,7 +440,11 @@ await stakingClient.distributeReward('0x62dD51230A30401C455c8398d06F85e4EaB6309f #### Defined in +<<<<<<< HEAD +[staking.ts:554](https://github.com/humanprotocol/human-protocol/blob/4a01940c/packages/sdk/typescript/human-protocol-sdk/src/staking.ts#L554) +======= [staking.ts:554](https://github.com/humanprotocol/human-protocol/blob/e4b60ab1/packages/sdk/typescript/human-protocol-sdk/src/staking.ts#L554) +>>>>>>> develop ___ @@ -434,7 +482,11 @@ const allocationInfo = await stakingClient.getAllocation('0x62dD51230A30401C455c #### Defined in +<<<<<<< HEAD +[staking.ts:591](https://github.com/humanprotocol/human-protocol/blob/4a01940c/packages/sdk/typescript/human-protocol-sdk/src/staking.ts#L591) +======= [staking.ts:591](https://github.com/humanprotocol/human-protocol/blob/e4b60ab1/packages/sdk/typescript/human-protocol-sdk/src/staking.ts#L591) +>>>>>>> develop ___ @@ -479,7 +531,11 @@ await stakingClient.slash('0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266', '0xf39Fd #### Defined in +<<<<<<< HEAD +[staking.ts:387](https://github.com/humanprotocol/human-protocol/blob/4a01940c/packages/sdk/typescript/human-protocol-sdk/src/staking.ts#L387) +======= [staking.ts:387](https://github.com/humanprotocol/human-protocol/blob/e4b60ab1/packages/sdk/typescript/human-protocol-sdk/src/staking.ts#L387) +>>>>>>> develop ___ @@ -524,7 +580,11 @@ await stakingClient.approveStake(amount); #### Defined in +<<<<<<< HEAD +[staking.ts:258](https://github.com/humanprotocol/human-protocol/blob/4a01940c/packages/sdk/typescript/human-protocol-sdk/src/staking.ts#L258) +======= [staking.ts:258](https://github.com/humanprotocol/human-protocol/blob/e4b60ab1/packages/sdk/typescript/human-protocol-sdk/src/staking.ts#L258) +>>>>>>> develop ___ @@ -568,7 +628,11 @@ await stakingClient.unstake(amount); #### Defined in +<<<<<<< HEAD +[staking.ts:303](https://github.com/humanprotocol/human-protocol/blob/4a01940c/packages/sdk/typescript/human-protocol-sdk/src/staking.ts#L303) +======= [staking.ts:303](https://github.com/humanprotocol/human-protocol/blob/e4b60ab1/packages/sdk/typescript/human-protocol-sdk/src/staking.ts#L303) +>>>>>>> develop ___ @@ -610,7 +674,11 @@ await stakingClient.withdraw(); #### Defined in +<<<<<<< HEAD +[staking.ts:349](https://github.com/humanprotocol/human-protocol/blob/4a01940c/packages/sdk/typescript/human-protocol-sdk/src/staking.ts#L349) +======= [staking.ts:349](https://github.com/humanprotocol/human-protocol/blob/e4b60ab1/packages/sdk/typescript/human-protocol-sdk/src/staking.ts#L349) +>>>>>>> develop ___ @@ -642,4 +710,8 @@ Creates an instance of StakingClient from a Runner. #### Defined in +<<<<<<< HEAD +[staking.ts:145](https://github.com/humanprotocol/human-protocol/blob/4a01940c/packages/sdk/typescript/human-protocol-sdk/src/staking.ts#L145) +======= [staking.ts:145](https://github.com/humanprotocol/human-protocol/blob/e4b60ab1/packages/sdk/typescript/human-protocol-sdk/src/staking.ts#L145) +>>>>>>> develop diff --git a/docs/sdk/typescript/classes/statistics.StatisticsClient.md b/docs/sdk/typescript/classes/statistics.StatisticsClient.md index 1e0f9f9671..65bcd425f2 100644 --- a/docs/sdk/typescript/classes/statistics.StatisticsClient.md +++ b/docs/sdk/typescript/classes/statistics.StatisticsClient.md @@ -37,7 +37,7 @@ yarn install @human-protocol/sdk ```ts import { StatisticsClient, ChainId, NETWORKS } from '@human-protocol/sdk'; -const statisticsClient = new StatisticsClient(NETWORKS[ChainId.POLYGON_MUMBAI]); +const statisticsClient = new StatisticsClient(NETWORKS[ChainId.POLYGON_AMOY]); ``` ## Table of contents @@ -77,7 +77,11 @@ const statisticsClient = new StatisticsClient(NETWORKS[ChainId.POLYGON_MUMBAI]); #### Defined in +<<<<<<< HEAD +[statistics.ts:68](https://github.com/humanprotocol/human-protocol/blob/4a01940c/packages/sdk/typescript/human-protocol-sdk/src/statistics.ts#L68) +======= [statistics.ts:68](https://github.com/humanprotocol/human-protocol/blob/e4b60ab1/packages/sdk/typescript/human-protocol-sdk/src/statistics.ts#L68) +>>>>>>> develop ## Properties @@ -87,7 +91,11 @@ const statisticsClient = new StatisticsClient(NETWORKS[ChainId.POLYGON_MUMBAI]); #### Defined in +<<<<<<< HEAD +[statistics.ts:61](https://github.com/humanprotocol/human-protocol/blob/4a01940c/packages/sdk/typescript/human-protocol-sdk/src/statistics.ts#L61) +======= [statistics.ts:61](https://github.com/humanprotocol/human-protocol/blob/e4b60ab1/packages/sdk/typescript/human-protocol-sdk/src/statistics.ts#L61) +>>>>>>> develop ## Methods @@ -140,7 +148,7 @@ Escrow statistics data. ```ts import { StatisticsClient, ChainId, NETWORKS } from '@human-protocol/sdk'; -const statisticsClient = new StatisticsClient(NETWORKS[ChainId.POLYGON_MUMBAI]); +const statisticsClient = new StatisticsClient(NETWORKS[ChainId.POLYGON_AMOY]); const escrowStatistics = await statisticsClient.getEscrowStatistics(); const escrowStatisticsApril = await statisticsClient.getEscrowStatistics({ @@ -151,7 +159,11 @@ const escrowStatisticsApril = await statisticsClient.getEscrowStatistics({ #### Defined in +<<<<<<< HEAD +[statistics.ts:121](https://github.com/humanprotocol/human-protocol/blob/4a01940c/packages/sdk/typescript/human-protocol-sdk/src/statistics.ts#L121) +======= [statistics.ts:121](https://github.com/humanprotocol/human-protocol/blob/e4b60ab1/packages/sdk/typescript/human-protocol-sdk/src/statistics.ts#L121) +>>>>>>> develop ___ @@ -209,7 +221,7 @@ HMToken statistics data. ```ts import { StatisticsClient, ChainId, NETWORKS } from '@human-protocol/sdk'; -const statisticsClient = new StatisticsClient(NETWORKS[ChainId.POLYGON_MUMBAI]); +const statisticsClient = new StatisticsClient(NETWORKS[ChainId.POLYGON_AMOY]); const hmtStatistics = await statisticsClient.getHMTStatistics(); @@ -247,7 +259,11 @@ console.log('HMT statistics from 5/8 - 6/8:', { #### Defined in +<<<<<<< HEAD +[statistics.ts:394](https://github.com/humanprotocol/human-protocol/blob/4a01940c/packages/sdk/typescript/human-protocol-sdk/src/statistics.ts#L394) +======= [statistics.ts:394](https://github.com/humanprotocol/human-protocol/blob/e4b60ab1/packages/sdk/typescript/human-protocol-sdk/src/statistics.ts#L394) +>>>>>>> develop ___ @@ -297,7 +313,7 @@ Payment statistics data. ```ts import { StatisticsClient, ChainId, NETWORKS } from '@human-protocol/sdk'; -const statisticsClient = new StatisticsClient(NETWORKS[ChainId.POLYGON_MUMBAI]); +const statisticsClient = new StatisticsClient(NETWORKS[ChainId.POLYGON_AMOY]); console.log( 'Payment statistics:', @@ -329,7 +345,11 @@ console.log( #### Defined in +<<<<<<< HEAD +[statistics.ts:285](https://github.com/humanprotocol/human-protocol/blob/4a01940c/packages/sdk/typescript/human-protocol-sdk/src/statistics.ts#L285) +======= [statistics.ts:285](https://github.com/humanprotocol/human-protocol/blob/e4b60ab1/packages/sdk/typescript/human-protocol-sdk/src/statistics.ts#L285) +>>>>>>> develop ___ @@ -377,7 +397,7 @@ Worker statistics data. ```ts import { StatisticsClient, ChainId, NETWORKS } from '@human-protocol/sdk'; -const statisticsClient = new StatisticsClient(NETWORKS[ChainId.POLYGON_MUMBAI]); +const statisticsClient = new StatisticsClient(NETWORKS[ChainId.POLYGON_AMOY]); const workerStatistics = await statisticsClient.getWorkerStatistics(); const workerStatisticsApril = await statisticsClient.getWorkerStatistics({ @@ -388,4 +408,8 @@ const workerStatisticsApril = await statisticsClient.getWorkerStatistics({ #### Defined in +<<<<<<< HEAD +[statistics.ts:196](https://github.com/humanprotocol/human-protocol/blob/4a01940c/packages/sdk/typescript/human-protocol-sdk/src/statistics.ts#L196) +======= [statistics.ts:196](https://github.com/humanprotocol/human-protocol/blob/e4b60ab1/packages/sdk/typescript/human-protocol-sdk/src/statistics.ts#L196) +>>>>>>> develop diff --git a/docs/sdk/typescript/classes/storage.StorageClient.md b/docs/sdk/typescript/classes/storage.StorageClient.md index 5fd2fb0083..6d98df672f 100644 --- a/docs/sdk/typescript/classes/storage.StorageClient.md +++ b/docs/sdk/typescript/classes/storage.StorageClient.md @@ -91,7 +91,11 @@ const storageClient = new StorageClient(params, credentials); #### Defined in +<<<<<<< HEAD +[storage.ts:73](https://github.com/humanprotocol/human-protocol/blob/4a01940c/packages/sdk/typescript/human-protocol-sdk/src/storage.ts#L73) +======= [storage.ts:73](https://github.com/humanprotocol/human-protocol/blob/e4b60ab1/packages/sdk/typescript/human-protocol-sdk/src/storage.ts#L73) +>>>>>>> develop ## Properties @@ -101,7 +105,11 @@ const storageClient = new StorageClient(params, credentials); #### Defined in +<<<<<<< HEAD +[storage.ts:64](https://github.com/humanprotocol/human-protocol/blob/4a01940c/packages/sdk/typescript/human-protocol-sdk/src/storage.ts#L64) +======= [storage.ts:64](https://github.com/humanprotocol/human-protocol/blob/e4b60ab1/packages/sdk/typescript/human-protocol-sdk/src/storage.ts#L64) +>>>>>>> develop ___ @@ -111,7 +119,11 @@ ___ #### Defined in +<<<<<<< HEAD +[storage.ts:65](https://github.com/humanprotocol/human-protocol/blob/4a01940c/packages/sdk/typescript/human-protocol-sdk/src/storage.ts#L65) +======= [storage.ts:65](https://github.com/humanprotocol/human-protocol/blob/e4b60ab1/packages/sdk/typescript/human-protocol-sdk/src/storage.ts#L65) +>>>>>>> develop ## Methods @@ -155,7 +167,11 @@ const exists = await storageClient.bucketExists('bucket-name'); #### Defined in +<<<<<<< HEAD +[storage.ts:266](https://github.com/humanprotocol/human-protocol/blob/4a01940c/packages/sdk/typescript/human-protocol-sdk/src/storage.ts#L266) +======= [storage.ts:266](https://github.com/humanprotocol/human-protocol/blob/e4b60ab1/packages/sdk/typescript/human-protocol-sdk/src/storage.ts#L266) +>>>>>>> develop ___ @@ -198,7 +214,11 @@ const files = await storageClient.downloadFiles(keys, 'bucket-name'); #### Defined in +<<<<<<< HEAD +[storage.ts:113](https://github.com/humanprotocol/human-protocol/blob/4a01940c/packages/sdk/typescript/human-protocol-sdk/src/storage.ts#L113) +======= [storage.ts:113](https://github.com/humanprotocol/human-protocol/blob/e4b60ab1/packages/sdk/typescript/human-protocol-sdk/src/storage.ts#L113) +>>>>>>> develop ___ @@ -242,7 +262,11 @@ const fileNames = await storageClient.listObjects('bucket-name'); #### Defined in +<<<<<<< HEAD +[storage.ts:297](https://github.com/humanprotocol/human-protocol/blob/4a01940c/packages/sdk/typescript/human-protocol-sdk/src/storage.ts#L297) +======= [storage.ts:297](https://github.com/humanprotocol/human-protocol/blob/e4b60ab1/packages/sdk/typescript/human-protocol-sdk/src/storage.ts#L297) +>>>>>>> develop ___ @@ -290,7 +314,11 @@ const uploadedFiles = await storageClient.uploadFiles(files, 'bucket-name'); #### Defined in +<<<<<<< HEAD +[storage.ts:201](https://github.com/humanprotocol/human-protocol/blob/4a01940c/packages/sdk/typescript/human-protocol-sdk/src/storage.ts#L201) +======= [storage.ts:201](https://github.com/humanprotocol/human-protocol/blob/e4b60ab1/packages/sdk/typescript/human-protocol-sdk/src/storage.ts#L201) +>>>>>>> develop ___ @@ -322,4 +350,8 @@ const file = await storageClient.downloadFileFromUrl('http://localhost/file.json #### Defined in +<<<<<<< HEAD +[storage.ts:148](https://github.com/humanprotocol/human-protocol/blob/4a01940c/packages/sdk/typescript/human-protocol-sdk/src/storage.ts#L148) +======= [storage.ts:148](https://github.com/humanprotocol/human-protocol/blob/e4b60ab1/packages/sdk/typescript/human-protocol-sdk/src/storage.ts#L148) +>>>>>>> develop diff --git a/packages/apps/dashboard/admin/config/cron-tasks.ts b/packages/apps/dashboard/admin/config/cron-tasks.ts index cb8d7ae33e..f84e3dd29f 100644 --- a/packages/apps/dashboard/admin/config/cron-tasks.ts +++ b/packages/apps/dashboard/admin/config/cron-tasks.ts @@ -8,6 +8,7 @@ import { goerli, mainnet, polygon, + polygonAmoy, polygonMumbai, moonbeam, moonbaseAlpha, @@ -22,6 +23,7 @@ const SUPPORTED_CHAINS = { [ChainId.BSC_MAINNET]: bsc, [ChainId.BSC_TESTNET]: bscTestnet, [ChainId.POLYGON]: polygon, + [ChainId.POLYGON_AMOY]: polygonAmoy, [ChainId.POLYGON_MUMBAI]: polygonMumbai, [ChainId.MOONBEAM]: moonbeam, [ChainId.MOONBASE_ALPHA]: moonbaseAlpha, diff --git a/packages/apps/dashboard/admin/package.json b/packages/apps/dashboard/admin/package.json index 19500e0c83..a25ca9fd16 100644 --- a/packages/apps/dashboard/admin/package.json +++ b/packages/apps/dashboard/admin/package.json @@ -24,7 +24,7 @@ "react-dom": "^18.0.0", "react-router-dom": "5.3.4", "styled-components": "5.3.3", - "viem": "^1.16.6" + "viem": "^2.9.17" }, "author": { "name": "Tony Wen" diff --git a/packages/apps/dashboard/ui/src/components/Faucet/FaucetView.tsx b/packages/apps/dashboard/ui/src/components/Faucet/FaucetView.tsx index 76376fef9d..616f0e7a3d 100644 --- a/packages/apps/dashboard/ui/src/components/Faucet/FaucetView.tsx +++ b/packages/apps/dashboard/ui/src/components/Faucet/FaucetView.tsx @@ -19,7 +19,7 @@ export const FaucetView: FC = () => { const [step, setStep] = useState(0); const [txHash, setTxHash] = useState(''); const [network, setNetwork] = useState( - NETWORKS[ChainId.POLYGON_MUMBAI]! + NETWORKS[ChainId.POLYGON_AMOY]! ); return ( diff --git a/packages/apps/dashboard/ui/src/components/Faucet/__tests__/RequestData.test.tsx b/packages/apps/dashboard/ui/src/components/Faucet/__tests__/RequestData.test.tsx index c4e3f432df..77429edbc4 100644 --- a/packages/apps/dashboard/ui/src/components/Faucet/__tests__/RequestData.test.tsx +++ b/packages/apps/dashboard/ui/src/components/Faucet/__tests__/RequestData.test.tsx @@ -11,7 +11,7 @@ describe('when rendered AfterConnect component', () => { render( , { wrapper: MemoryRouter } ); @@ -25,7 +25,7 @@ it('AfterConnect component renders correctly, corresponds to the snapshot', () = ).toJSON(); diff --git a/packages/apps/dashboard/ui/src/components/Icons/chains.tsx b/packages/apps/dashboard/ui/src/components/Icons/chains.tsx index a7133a1070..87302d727e 100644 --- a/packages/apps/dashboard/ui/src/components/Icons/chains.tsx +++ b/packages/apps/dashboard/ui/src/components/Icons/chains.tsx @@ -18,6 +18,7 @@ export const CHAIN_ICONS: { [chainId in ChainId]?: ReactElement } = { [ChainId.GOERLI]: , [ChainId.POLYGON]: , [ChainId.POLYGON_MUMBAI]: , + [ChainId.POLYGON_AMOY]: , [ChainId.BSC_MAINNET]: , [ChainId.BSC_TESTNET]: , [ChainId.MOONBEAM]: , diff --git a/packages/apps/dashboard/ui/src/constants/index.ts b/packages/apps/dashboard/ui/src/constants/index.ts index 5e199df044..4a65244ef6 100644 --- a/packages/apps/dashboard/ui/src/constants/index.ts +++ b/packages/apps/dashboard/ui/src/constants/index.ts @@ -7,6 +7,7 @@ export const V2_SUPPORTED_CHAIN_IDS = [ ChainId.BSC_TESTNET, ChainId.POLYGON, ChainId.POLYGON_MUMBAI, + ChainId.POLYGON_AMOY, ChainId.MOONBEAM, ChainId.MOONBASE_ALPHA, ChainId.CELO, @@ -24,6 +25,7 @@ export const SUPPORTED_CHAIN_IDS = [ ChainId.BSC_TESTNET, ChainId.POLYGON, ChainId.POLYGON_MUMBAI, + ChainId.POLYGON_AMOY, ChainId.SKALE, ChainId.MOONBEAM, ChainId.MOONBASE_ALPHA, @@ -46,6 +48,7 @@ export const TESTNET_CHAIN_IDS = [ ChainId.GOERLI, ChainId.BSC_TESTNET, ChainId.POLYGON_MUMBAI, + ChainId.POLYGON_AMOY, ChainId.MOONBASE_ALPHA, ChainId.AVALANCHE_TESTNET, ChainId.CELO_ALFAJORES, @@ -55,6 +58,7 @@ export const FAUCET_CHAIN_IDS = [ ChainId.GOERLI, ChainId.BSC_TESTNET, ChainId.POLYGON_MUMBAI, + ChainId.POLYGON_AMOY, ChainId.MOONBASE_ALPHA, ChainId.AVALANCHE_TESTNET, ChainId.SKALE, @@ -72,6 +76,8 @@ export const RPC_URLS: { [ChainId.BSC_TESTNET]: 'https://bsc-testnet.publicnode.com', [ChainId.POLYGON]: 'https://polygon-rpc.com/', [ChainId.POLYGON_MUMBAI]: 'https://rpc-mumbai.maticvigil.com', + [ChainId.POLYGON_AMOY]: + 'https://polygon-amoy.g.alchemy.com/v2/Jomagi_shxwCUrKtZfgZepvngWRuO8-e', [ChainId.MOONBEAM]: 'https://rpc.api.moonbeam.network', [ChainId.MOONBASE_ALPHA]: 'https://rpc.api.moonbase.moonbeam.network', [ChainId.AVALANCHE_TESTNET]: 'https://api.avax-test.network/ext/C/rpc', diff --git a/packages/apps/fortune/exchange-oracle/server/src/common/config/networks.ts b/packages/apps/fortune/exchange-oracle/server/src/common/config/networks.ts index d19864a850..631cd25eab 100644 --- a/packages/apps/fortune/exchange-oracle/server/src/common/config/networks.ts +++ b/packages/apps/fortune/exchange-oracle/server/src/common/config/networks.ts @@ -19,10 +19,10 @@ export const networkMap: NetworkMapDto = { chainId: ChainId.BSC_MAINNET, rpcUrl: 'https://bsc-dataseed1.binance.org/', }, - mumbai: { - chainId: ChainId.POLYGON_MUMBAI, + amoy: { + chainId: ChainId.POLYGON_AMOY, rpcUrl: - 'https://polygon-mumbai.g.alchemy.com/v2/vKNSJzJf6SW2sdW-05bgFwoyFxUrMzii', + 'https://polygon-amoy.g.alchemy.com/v2/Jomagi_shxwCUrKtZfgZepvngWRuO8-e', }, goerli: { chainId: ChainId.GOERLI, diff --git a/packages/apps/fortune/exchange-oracle/server/src/common/constant/index.ts b/packages/apps/fortune/exchange-oracle/server/src/common/constant/index.ts index bef3a34617..edc886f9f6 100644 --- a/packages/apps/fortune/exchange-oracle/server/src/common/constant/index.ts +++ b/packages/apps/fortune/exchange-oracle/server/src/common/constant/index.ts @@ -7,7 +7,7 @@ export const TOKEN = 'HMT'; export const LOCALHOST_CHAIN_IDS = [ChainId.LOCALHOST]; export const TESTNET_CHAIN_IDS = [ - ChainId.POLYGON_MUMBAI, + ChainId.POLYGON_AMOY, ChainId.BSC_TESTNET, ChainId.GOERLI, ]; diff --git a/packages/apps/fortune/exchange-oracle/server/src/modules/web3/web3.service.spec.ts b/packages/apps/fortune/exchange-oracle/server/src/modules/web3/web3.service.spec.ts index ab64fd1775..6982faf3c7 100644 --- a/packages/apps/fortune/exchange-oracle/server/src/modules/web3/web3.service.spec.ts +++ b/packages/apps/fortune/exchange-oracle/server/src/modules/web3/web3.service.spec.ts @@ -27,7 +27,7 @@ describe('Web3Service', () => { describe('getSigner', () => { it('should return a signer for a valid chainId on TESTNET', () => { - const validChainId = ChainId.POLYGON_MUMBAI; + const validChainId = ChainId.POLYGON_AMOY; const signer = web3Service.getSigner(validChainId); expect(signer).toBeDefined(); diff --git a/packages/apps/fortune/recording-oracle/src/common/constants/networks.ts b/packages/apps/fortune/recording-oracle/src/common/constants/networks.ts index d8e35c5bb4..95f749f964 100644 --- a/packages/apps/fortune/recording-oracle/src/common/constants/networks.ts +++ b/packages/apps/fortune/recording-oracle/src/common/constants/networks.ts @@ -11,10 +11,10 @@ export const networkMap: NetworkMapDto = { chainId: ChainId.BSC_MAINNET, rpcUrl: 'https://bsc-dataseed1.binance.org/', }, - mumbai: { - chainId: ChainId.POLYGON_MUMBAI, + amoy: { + chainId: ChainId.POLYGON_AMOY, rpcUrl: - 'https://polygon-mumbai.g.alchemy.com/v2/vKNSJzJf6SW2sdW-05bgFwoyFxUrMzii', + 'https://polygon-amoy.g.alchemy.com/v2/Jomagi_shxwCUrKtZfgZepvngWRuO8-e', }, goerli: { chainId: ChainId.GOERLI, diff --git a/packages/apps/job-launcher/client/src/components/Icons/chains.tsx b/packages/apps/job-launcher/client/src/components/Icons/chains.tsx index 782028b006..53a7f2cac2 100644 --- a/packages/apps/job-launcher/client/src/components/Icons/chains.tsx +++ b/packages/apps/job-launcher/client/src/components/Icons/chains.tsx @@ -18,6 +18,7 @@ export const CHAIN_ICONS: { [chainId in ChainId]?: ReactElement } = { [ChainId.GOERLI]: , [ChainId.POLYGON]: , [ChainId.POLYGON_MUMBAI]: , + [ChainId.POLYGON_AMOY]: , [ChainId.BSC_MAINNET]: , [ChainId.BSC_TESTNET]: , [ChainId.MOONBEAM]: , diff --git a/packages/apps/job-launcher/client/src/constants/chains.ts b/packages/apps/job-launcher/client/src/constants/chains.ts index 297938dd02..d460229c2f 100644 --- a/packages/apps/job-launcher/client/src/constants/chains.ts +++ b/packages/apps/job-launcher/client/src/constants/chains.ts @@ -12,7 +12,7 @@ switch (import.meta.env.VITE_APP_ENVIRONMENT.toLowerCase()) { case 'testnet': SUPPORTED_CHAIN_IDS = [ ChainId.BSC_TESTNET, - ChainId.POLYGON_MUMBAI, + ChainId.POLYGON_AMOY, ChainId.GOERLI, ]; break; @@ -23,7 +23,7 @@ switch (import.meta.env.VITE_APP_ENVIRONMENT.toLowerCase()) { } export const CHAIN_ID_BY_NAME: Record = { - 'Polygon Mumbai': ChainId.POLYGON_MUMBAI, + 'Polygon Amoy': ChainId.POLYGON_AMOY, 'Binance Smart Chain': ChainId.BSC_MAINNET, 'Ethereum Goerli': ChainId.GOERLI, Localhost: ChainId.LOCALHOST, @@ -40,6 +40,8 @@ export const RPC_URLS: { [ChainId.BSC_TESTNET]: 'https://bsc-testnet.publicnode.com', [ChainId.POLYGON]: 'https://polygon-rpc.com/', [ChainId.POLYGON_MUMBAI]: 'https://rpc-mumbai.maticvigil.com', + [ChainId.POLYGON_AMOY]: + 'https://polygon-amoy.g.alchemy.com/v2/Jomagi_shxwCUrKtZfgZepvngWRuO8-e', [ChainId.MOONBEAM]: 'https://rpc.api.moonbeam.network', [ChainId.MOONBASE_ALPHA]: 'https://rpc.api.moonbase.moonbeam.network', [ChainId.AVALANCHE_TESTNET]: 'https://api.avax-test.network/ext/C/rpc', diff --git a/packages/apps/job-launcher/client/src/main.tsx b/packages/apps/job-launcher/client/src/main.tsx index 17ac793edf..1477ee85f5 100644 --- a/packages/apps/job-launcher/client/src/main.tsx +++ b/packages/apps/job-launcher/client/src/main.tsx @@ -55,6 +55,29 @@ const fortune: Chain = { }, }; +const polygonAmoy: Chain = { + id: 80002, + name: 'Polygon Amoy', + network: 'Amoy', + nativeCurrency: { + decimals: 18, + name: 'Matic', + symbol: 'MATIC', + }, + rpcUrls: { + default: { + http: [ + 'https://polygon-amoy.g.alchemy.com/v2/Jomagi_shxwCUrKtZfgZepvngWRuO8-e', + ], + }, + public: { + http: [ + 'https://polygon-amoy.g.alchemy.com/v2/Jomagi_shxwCUrKtZfgZepvngWRuO8-e', + ], + }, + }, +}; + // Configure chains & providers with the Alchemy provider. // Two popular providers are Alchemy (alchemy.com) and Infura (infura.io) const { chains, provider, webSocketProvider } = configureChains( @@ -64,6 +87,7 @@ const { chains, provider, webSocketProvider } = configureChains( polygon, skaleHumanProtocol, polygonMumbai, + polygonAmoy, bsc, bscTestnet, fortune, diff --git a/packages/apps/job-launcher/client/src/providers/CreateJobPageUIProvider.tsx b/packages/apps/job-launcher/client/src/providers/CreateJobPageUIProvider.tsx index cff9eb887c..4a67dd6832 100644 --- a/packages/apps/job-launcher/client/src/providers/CreateJobPageUIProvider.tsx +++ b/packages/apps/job-launcher/client/src/providers/CreateJobPageUIProvider.tsx @@ -49,7 +49,7 @@ export const CreateJobPageUIProvider = ({ ? chain?.id : !IS_MAINNET ? !chain?.id - ? ChainId.POLYGON_MUMBAI + ? ChainId.POLYGON_AMOY : undefined : undefined, }); diff --git a/packages/apps/job-launcher/server/src/common/config/networks.ts b/packages/apps/job-launcher/server/src/common/config/networks.ts index b0d9cf1250..c7c8fa575c 100644 --- a/packages/apps/job-launcher/server/src/common/config/networks.ts +++ b/packages/apps/job-launcher/server/src/common/config/networks.ts @@ -31,13 +31,12 @@ export const networkMap: NetworkMapDto = { usdt: '0x55d398326f99059fF775485246999027B3197955', }, }, - mumbai: { - chainId: ChainId.POLYGON_MUMBAI, + amoy: { + chainId: ChainId.POLYGON_AMOY, rpcUrl: - 'https://polygon-mumbai.g.alchemy.com/v2/vKNSJzJf6SW2sdW-05bgFwoyFxUrMzii', + 'https://polygon-amoy.g.alchemy.com/v2/Jomagi_shxwCUrKtZfgZepvngWRuO8-e', tokens: { - hmt: NETWORKS[ChainId.POLYGON_MUMBAI]?.hmtAddress, - usdt: '0x5b20e68f501590C130d77C87C2A2f2B43Fc09701', + hmt: NETWORKS[ChainId.POLYGON_AMOY]?.hmtAddress, }, }, goerli: { diff --git a/packages/apps/job-launcher/server/src/common/constants/index.ts b/packages/apps/job-launcher/server/src/common/constants/index.ts index 60fe171374..313de9bed6 100644 --- a/packages/apps/job-launcher/server/src/common/constants/index.ts +++ b/packages/apps/job-launcher/server/src/common/constants/index.ts @@ -14,7 +14,7 @@ export const LOCALHOST_CHAIN_IDS = [ChainId.LOCALHOST]; export const TESTNET_CHAIN_IDS = [ ChainId.BSC_TESTNET, - ChainId.POLYGON_MUMBAI, + ChainId.POLYGON_AMOY, ChainId.GOERLI, ]; export const MAINNET_CHAIN_IDS = [ diff --git a/packages/apps/job-launcher/server/src/modules/payment/payment.service.spec.ts b/packages/apps/job-launcher/server/src/modules/payment/payment.service.spec.ts index 715260a67e..cc5d02071e 100644 --- a/packages/apps/job-launcher/server/src/modules/payment/payment.service.spec.ts +++ b/packages/apps/job-launcher/server/src/modules/payment/payment.service.spec.ts @@ -375,7 +375,7 @@ describe('PaymentService', () => { it('should create a crypto payment successfully', async () => { const userId = 1; const dto = { - chainId: ChainId.POLYGON_MUMBAI, + chainId: ChainId.POLYGON_AMOY, transactionHash: MOCK_TRANSACTION_HASH, }; @@ -390,7 +390,7 @@ describe('PaymentService', () => { blockHash: '123', transactionIndex: 123, removed: false, - address: NETWORKS[ChainId.POLYGON_MUMBAI]?.hmtAddress as string, + address: NETWORKS[ChainId.POLYGON_AMOY]?.hmtAddress as string, topics: ['0x123', '0x0000000000000000000000000123', MOCK_ADDRESS], transactionHash: MOCK_TRANSACTION_HASH, logIndex: 123, @@ -426,7 +426,7 @@ describe('PaymentService', () => { amount: 10, rate: 1.5, transaction: MOCK_TRANSACTION_HASH, - chainId: ChainId.POLYGON_MUMBAI, + chainId: ChainId.POLYGON_AMOY, status: PaymentStatus.SUCCEEDED, }); expect(result).toBe(true); @@ -435,7 +435,7 @@ describe('PaymentService', () => { it('should throw a conflict exception if the token address is unsupported', async () => { const userId = 1; const dto = { - chainId: ChainId.POLYGON_MUMBAI, + chainId: ChainId.POLYGON_AMOY, transactionHash: MOCK_TRANSACTION_HASH, }; @@ -474,7 +474,7 @@ describe('PaymentService', () => { it('should throw a conflict exception if an unsupported token is used', async () => { const userId = 1; const dto = { - chainId: ChainId.POLYGON_MUMBAI, + chainId: ChainId.POLYGON_AMOY, transactionHash: MOCK_TRANSACTION_HASH, }; @@ -489,7 +489,7 @@ describe('PaymentService', () => { blockHash: '123', transactionIndex: 123, removed: false, - address: NETWORKS[ChainId.POLYGON_MUMBAI]?.hmtAddress as string, + address: NETWORKS[ChainId.POLYGON_AMOY]?.hmtAddress as string, topics: ['0x123', '0x0000000000000000000000000123', MOCK_ADDRESS], transactionHash: MOCK_TRANSACTION_HASH, logIndex: 123, @@ -513,7 +513,7 @@ describe('PaymentService', () => { it('should throw a signature error if the signature is wrong', async () => { const userId = 1; const dto = { - chainId: ChainId.POLYGON_MUMBAI, + chainId: ChainId.POLYGON_AMOY, transactionHash: MOCK_TRANSACTION_HASH, }; (verifySignature as jest.Mock).mockImplementation(() => { @@ -538,7 +538,7 @@ describe('PaymentService', () => { it('should throw a not found exception if the transaction is not found by hash', async () => { const userId = 1; const dto = { - chainId: ChainId.POLYGON_MUMBAI, + chainId: ChainId.POLYGON_AMOY, transactionHash: MOCK_TRANSACTION_HASH, }; @@ -574,7 +574,7 @@ describe('PaymentService', () => { it('should throw a not found exception if the transaction has insufficient confirmations', async () => { const userId = 1; const dto = { - chainId: ChainId.POLYGON_MUMBAI, + chainId: ChainId.POLYGON_AMOY, transactionHash: MOCK_TRANSACTION_HASH, }; @@ -587,7 +587,7 @@ describe('PaymentService', () => { blockHash: '123', transactionIndex: 123, removed: false, - address: NETWORKS[ChainId.POLYGON_MUMBAI]?.hmtAddress as string, + address: NETWORKS[ChainId.POLYGON_AMOY]?.hmtAddress as string, topics: ['0x123', '0x0000000000000000000000000123', MOCK_ADDRESS], transactionHash: MOCK_TRANSACTION_HASH, logIndex: 123, @@ -613,7 +613,7 @@ describe('PaymentService', () => { it('should throw a bad request exception if the payment with the same transaction hash already exists', async () => { const userId = 1; const dto = { - chainId: ChainId.POLYGON_MUMBAI, + chainId: ChainId.POLYGON_AMOY, transactionHash: MOCK_TRANSACTION_HASH, }; @@ -628,7 +628,7 @@ describe('PaymentService', () => { blockHash: '123', transactionIndex: 123, removed: false, - address: NETWORKS[ChainId.POLYGON_MUMBAI]?.hmtAddress as string, + address: NETWORKS[ChainId.POLYGON_AMOY]?.hmtAddress as string, topics: ['0x123', '0x0000000000000000000000000123', MOCK_ADDRESS], transactionHash: MOCK_TRANSACTION_HASH, logIndex: 123, diff --git a/packages/apps/job-launcher/server/src/modules/web3/web3.service.spec.ts b/packages/apps/job-launcher/server/src/modules/web3/web3.service.spec.ts index eb0da32eb2..e3e72bb60c 100644 --- a/packages/apps/job-launcher/server/src/modules/web3/web3.service.spec.ts +++ b/packages/apps/job-launcher/server/src/modules/web3/web3.service.spec.ts @@ -42,7 +42,7 @@ describe('Web3Service', () => { describe('getSigner', () => { it('should return a signer for a valid chainId on TESTNET', () => { - const validChainId = ChainId.POLYGON_MUMBAI; + const validChainId = ChainId.POLYGON_AMOY; const signer = web3Service.getSigner(validChainId); expect(signer).toBeDefined(); diff --git a/packages/apps/reputation-oracle/server/src/common/config/networks.ts b/packages/apps/reputation-oracle/server/src/common/config/networks.ts index 8d8b8124e1..dd2f48f0b3 100644 --- a/packages/apps/reputation-oracle/server/src/common/config/networks.ts +++ b/packages/apps/reputation-oracle/server/src/common/config/networks.ts @@ -19,10 +19,10 @@ export const networkMap: NetworkMapDto = { chainId: ChainId.BSC_MAINNET, rpcUrl: 'https://bsc-dataseed1.binance.org/', }, - mumbai: { - chainId: ChainId.POLYGON_MUMBAI, + amoy: { + chainId: ChainId.POLYGON_AMOY, rpcUrl: - 'https://polygon-mumbai.g.alchemy.com/v2/vKNSJzJf6SW2sdW-05bgFwoyFxUrMzii', + 'https://polygon-amoy.g.alchemy.com/v2/Jomagi_shxwCUrKtZfgZepvngWRuO8-e', }, goerli: { chainId: ChainId.GOERLI, @@ -46,7 +46,7 @@ export const networks = Object.values(networkMap).map((network) => network); export const TESTNET_CHAIN_IDS = [ ChainId.BSC_TESTNET, - ChainId.POLYGON_MUMBAI, + ChainId.POLYGON_AMOY, ChainId.GOERLI, ]; diff --git a/packages/apps/reputation-oracle/server/src/modules/auth/auth.service.ts b/packages/apps/reputation-oracle/server/src/modules/auth/auth.service.ts index 33fc8d70c4..dce42fea6c 100644 --- a/packages/apps/reputation-oracle/server/src/modules/auth/auth.service.ts +++ b/packages/apps/reputation-oracle/server/src/modules/auth/auth.service.ts @@ -364,7 +364,7 @@ export class AuthService { ); } else { kvstore = await KVStoreClient.build( - this.web3Service.getSigner(ChainId.POLYGON_MUMBAI), + this.web3Service.getSigner(ChainId.POLYGON_AMOY), ); } diff --git a/packages/apps/reputation-oracle/server/src/modules/user/user.service.spec.ts b/packages/apps/reputation-oracle/server/src/modules/user/user.service.spec.ts index 14788423b0..97e5340d78 100644 --- a/packages/apps/reputation-oracle/server/src/modules/user/user.service.spec.ts +++ b/packages/apps/reputation-oracle/server/src/modules/user/user.service.spec.ts @@ -157,7 +157,7 @@ describe('UserService', () => { const result = await userService.registerAddress( userEntity as UserEntity, - { chainId: ChainId.POLYGON_MUMBAI, address }, + { chainId: ChainId.POLYGON_AMOY, address }, ); expect(userEntity.save).toHaveBeenCalledWith(); @@ -175,7 +175,7 @@ describe('UserService', () => { await expect( userService.registerAddress(userEntity as UserEntity, { - chainId: ChainId.POLYGON_MUMBAI, + chainId: ChainId.POLYGON_AMOY, address, }), ).rejects.toThrow(BadRequestException); @@ -195,7 +195,7 @@ describe('UserService', () => { await expect( userService.registerAddress(userEntity as UserEntity, { - chainId: ChainId.POLYGON_MUMBAI, + chainId: ChainId.POLYGON_AMOY, address, }), ).rejects.toThrow(BadRequestException); @@ -245,9 +245,7 @@ describe('UserService', () => { SignatureType.DISABLE_OPERATOR, MOCK_ADDRESS, ); - expect(web3Service.getSigner).toHaveBeenCalledWith( - ChainId.POLYGON_MUMBAI, - ); + expect(web3Service.getSigner).toHaveBeenCalledWith(ChainId.POLYGON_AMOY); expect(kvstoreClientMock.get).toHaveBeenCalledWith( MOCK_ADDRESS, diff --git a/packages/apps/reputation-oracle/server/src/modules/user/user.service.ts b/packages/apps/reputation-oracle/server/src/modules/user/user.service.ts index 7ad1ff8c3e..4d3b748db6 100644 --- a/packages/apps/reputation-oracle/server/src/modules/user/user.service.ts +++ b/packages/apps/reputation-oracle/server/src/modules/user/user.service.ts @@ -158,7 +158,7 @@ export class UserService { if (currentWeb3Env === Web3Env.MAINNET) { signer = this.web3Service.getSigner(ChainId.POLYGON); } else { - signer = this.web3Service.getSigner(ChainId.POLYGON_MUMBAI); + signer = this.web3Service.getSigner(ChainId.POLYGON_AMOY); } const kvstore = await KVStoreClient.build(signer); diff --git a/packages/apps/reputation-oracle/server/src/modules/web3/web3.service.spec.ts b/packages/apps/reputation-oracle/server/src/modules/web3/web3.service.spec.ts index 4031e1842f..c128b4ff3a 100644 --- a/packages/apps/reputation-oracle/server/src/modules/web3/web3.service.spec.ts +++ b/packages/apps/reputation-oracle/server/src/modules/web3/web3.service.spec.ts @@ -26,7 +26,7 @@ describe('Web3Service', () => { describe('getSigner', () => { it('should return a signer for a valid chainId on TESTNET', () => { - const validChainId = ChainId.POLYGON_MUMBAI; + const validChainId = ChainId.POLYGON_AMOY; const signer = web3Service.getSigner(validChainId); expect(signer).toBeDefined(); diff --git a/packages/core/.gitignore b/packages/core/.gitignore index 178014e7c2..b35344f287 100644 --- a/packages/core/.gitignore +++ b/packages/core/.gitignore @@ -29,5 +29,7 @@ deployments_tenderly !.openzeppelin/unknown-1284.json # moonbaseAlpha !.openzeppelin/unknown-1287.json +# amoy +!.openzeppelin/unknown-80002.json # skale !.openzeppelin/unknown-1273227453.json diff --git a/packages/core/.openzeppelin/unknown-80002.json b/packages/core/.openzeppelin/unknown-80002.json new file mode 100644 index 0000000000..d7474eabbe --- /dev/null +++ b/packages/core/.openzeppelin/unknown-80002.json @@ -0,0 +1,583 @@ +{ + "manifestVersion": "3.2", + "proxies": [ + { + "address": "0xCc0AF0635aa19fE799B6aFDBe28fcFAeA7f00a60", + "txHash": "0x39a358407f33eb7af7d67bb78ba10036479006c690ad43e231fe8b16b004cd87", + "kind": "uups" + }, + { + "address": "0xAFf5a986A530ff839d49325A5dF69F96627E8D29", + "txHash": "0xe42cf19c15cb4d6755bd11f2625368644848b519f2374868f9408d116d8febf3", + "kind": "uups" + }, + { + "address": "0xd866bCEFf6D0F77E1c3EAE28230AE6C79b03fDa7", + "txHash": "0xa49b638330e7b55d7e99feaf1b245e98cc17d768ef4ece92ece93d96b0211a4d", + "kind": "uups" + } + ], + "impls": { + "d2ec2471c85bd1da9dea7e26082bfd535accbe8621b587cd0433f4363983f3ac": { + "address": "0xD6D347ba6987519B4e42EcED43dF98eFf5465a23", + "txHash": "0x74524c7bf9df713acd4ecd0838242d9f30bab35f48345ef7aa7990651c8b8a05", + "layout": { + "solcVersion": "0.8.23", + "storage": [ + { + "label": "_initialized", + "offset": 0, + "slot": "0", + "type": "t_uint8", + "contract": "Initializable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:63", + "retypedFrom": "bool" + }, + { + "label": "_initializing", + "offset": 1, + "slot": "0", + "type": "t_bool", + "contract": "Initializable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:68" + }, + { + "label": "__gap", + "offset": 0, + "slot": "1", + "type": "t_array(t_uint256)50_storage", + "contract": "ContextUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol:40" + }, + { + "label": "_owner", + "offset": 0, + "slot": "51", + "type": "t_address", + "contract": "OwnableUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol:22" + }, + { + "label": "__gap", + "offset": 0, + "slot": "52", + "type": "t_array(t_uint256)49_storage", + "contract": "OwnableUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol:94" + }, + { + "label": "__gap", + "offset": 0, + "slot": "101", + "type": "t_array(t_uint256)50_storage", + "contract": "ERC1967UpgradeUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/proxy/ERC1967/ERC1967UpgradeUpgradeable.sol:169" + }, + { + "label": "__gap", + "offset": 0, + "slot": "151", + "type": "t_array(t_uint256)50_storage", + "contract": "UUPSUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol:111" + }, + { + "label": "token", + "offset": 0, + "slot": "201", + "type": "t_address", + "contract": "Staking", + "src": "contracts/Staking.sol:26" + }, + { + "label": "rewardPool", + "offset": 0, + "slot": "202", + "type": "t_address", + "contract": "Staking", + "src": "contracts/Staking.sol:29" + }, + { + "label": "minimumStake", + "offset": 0, + "slot": "203", + "type": "t_uint256", + "contract": "Staking", + "src": "contracts/Staking.sol:32" + }, + { + "label": "lockPeriod", + "offset": 0, + "slot": "204", + "type": "t_uint32", + "contract": "Staking", + "src": "contracts/Staking.sol:35" + }, + { + "label": "stakes", + "offset": 0, + "slot": "205", + "type": "t_mapping(t_address,t_struct(Staker)22746_storage)", + "contract": "Staking", + "src": "contracts/Staking.sol:38" + }, + { + "label": "stakers", + "offset": 0, + "slot": "206", + "type": "t_array(t_address)dyn_storage", + "contract": "Staking", + "src": "contracts/Staking.sol:41" + }, + { + "label": "allocations", + "offset": 0, + "slot": "207", + "type": "t_mapping(t_address,t_struct(Allocation)22606_storage)", + "contract": "Staking", + "src": "contracts/Staking.sol:44" + }, + { + "label": "__gap", + "offset": 0, + "slot": "208", + "type": "t_array(t_uint256)43_storage", + "contract": "Staking", + "src": "contracts/Staking.sol:592" + } + ], + "types": { + "t_address": { + "label": "address", + "numberOfBytes": "20" + }, + "t_array(t_address)dyn_storage": { + "label": "address[]", + "numberOfBytes": "32" + }, + "t_array(t_uint256)43_storage": { + "label": "uint256[43]", + "numberOfBytes": "1376" + }, + "t_array(t_uint256)49_storage": { + "label": "uint256[49]", + "numberOfBytes": "1568" + }, + "t_array(t_uint256)50_storage": { + "label": "uint256[50]", + "numberOfBytes": "1600" + }, + "t_bool": { + "label": "bool", + "numberOfBytes": "1" + }, + "t_mapping(t_address,t_struct(Allocation)22606_storage)": { + "label": "mapping(address => struct IStaking.Allocation)", + "numberOfBytes": "32" + }, + "t_mapping(t_address,t_struct(Staker)22746_storage)": { + "label": "mapping(address => struct Stakes.Staker)", + "numberOfBytes": "32" + }, + "t_struct(Allocation)22606_storage": { + "label": "struct IStaking.Allocation", + "members": [ + { + "label": "escrowAddress", + "type": "t_address", + "offset": 0, + "slot": "0" + }, + { + "label": "staker", + "type": "t_address", + "offset": 0, + "slot": "1" + }, + { + "label": "tokens", + "type": "t_uint256", + "offset": 0, + "slot": "2" + }, + { + "label": "createdAt", + "type": "t_uint256", + "offset": 0, + "slot": "3" + }, + { + "label": "closedAt", + "type": "t_uint256", + "offset": 0, + "slot": "4" + } + ], + "numberOfBytes": "160" + }, + "t_struct(Staker)22746_storage": { + "label": "struct Stakes.Staker", + "members": [ + { + "label": "tokensStaked", + "type": "t_uint256", + "offset": 0, + "slot": "0" + }, + { + "label": "tokensAllocated", + "type": "t_uint256", + "offset": 0, + "slot": "1" + }, + { + "label": "tokensLocked", + "type": "t_uint256", + "offset": 0, + "slot": "2" + }, + { + "label": "tokensLockedUntil", + "type": "t_uint256", + "offset": 0, + "slot": "3" + } + ], + "numberOfBytes": "128" + }, + "t_uint256": { + "label": "uint256", + "numberOfBytes": "32" + }, + "t_uint32": { + "label": "uint32", + "numberOfBytes": "4" + }, + "t_uint8": { + "label": "uint8", + "numberOfBytes": "1" + } + }, + "namespaces": {} + } + }, + "f4e498a6a024c4825c43a8350bce6d3db0ff5ce8a8a79bdb6ea4117e4d315584": { + "address": "0x6D72734F28E807465AeBC2e1302336129242c2e6", + "txHash": "0x4feedc06b7b797e2db0b5fc70b91efcc55fe332b14f8ddeaddfa62da7ee27df2", + "layout": { + "solcVersion": "0.8.23", + "storage": [ + { + "label": "_initialized", + "offset": 0, + "slot": "0", + "type": "t_uint8", + "contract": "Initializable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:63", + "retypedFrom": "bool" + }, + { + "label": "_initializing", + "offset": 1, + "slot": "0", + "type": "t_bool", + "contract": "Initializable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:68" + }, + { + "label": "__gap", + "offset": 0, + "slot": "1", + "type": "t_array(t_uint256)50_storage", + "contract": "ContextUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol:40" + }, + { + "label": "_owner", + "offset": 0, + "slot": "51", + "type": "t_address", + "contract": "OwnableUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol:22" + }, + { + "label": "__gap", + "offset": 0, + "slot": "52", + "type": "t_array(t_uint256)49_storage", + "contract": "OwnableUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol:94" + }, + { + "label": "__gap", + "offset": 0, + "slot": "101", + "type": "t_array(t_uint256)50_storage", + "contract": "ERC1967UpgradeUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/proxy/ERC1967/ERC1967UpgradeUpgradeable.sol:169" + }, + { + "label": "__gap", + "offset": 0, + "slot": "151", + "type": "t_array(t_uint256)50_storage", + "contract": "UUPSUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol:111" + }, + { + "label": "counter", + "offset": 0, + "slot": "201", + "type": "t_uint256", + "contract": "EscrowFactory", + "src": "contracts/EscrowFactory.sol:16" + }, + { + "label": "escrowCounters", + "offset": 0, + "slot": "202", + "type": "t_mapping(t_address,t_uint256)", + "contract": "EscrowFactory", + "src": "contracts/EscrowFactory.sol:17" + }, + { + "label": "lastEscrow", + "offset": 0, + "slot": "203", + "type": "t_address", + "contract": "EscrowFactory", + "src": "contracts/EscrowFactory.sol:18" + }, + { + "label": "staking", + "offset": 0, + "slot": "204", + "type": "t_address", + "contract": "EscrowFactory", + "src": "contracts/EscrowFactory.sol:19" + }, + { + "label": "__gap", + "offset": 0, + "slot": "205", + "type": "t_array(t_uint256)46_storage", + "contract": "EscrowFactory", + "src": "contracts/EscrowFactory.sol:80" + } + ], + "types": { + "t_address": { + "label": "address", + "numberOfBytes": "20" + }, + "t_array(t_uint256)46_storage": { + "label": "uint256[46]", + "numberOfBytes": "1472" + }, + "t_array(t_uint256)49_storage": { + "label": "uint256[49]", + "numberOfBytes": "1568" + }, + "t_array(t_uint256)50_storage": { + "label": "uint256[50]", + "numberOfBytes": "1600" + }, + "t_bool": { + "label": "bool", + "numberOfBytes": "1" + }, + "t_mapping(t_address,t_uint256)": { + "label": "mapping(address => uint256)", + "numberOfBytes": "32" + }, + "t_uint256": { + "label": "uint256", + "numberOfBytes": "32" + }, + "t_uint8": { + "label": "uint8", + "numberOfBytes": "1" + } + }, + "namespaces": {} + } + }, + "5c1e0b4b2495e6a02c16b3d332f8259eb8d8335016f8d88b1dc761febc6c941a": { + "address": "0x66929B629f5F53AF8d7aE97Aa498CE78212D570D", + "txHash": "0x088b1577216a7e177be01bf8dbbce80f897212c28da063c69e94ca230c390c0e", + "layout": { + "solcVersion": "0.8.23", + "storage": [ + { + "label": "_initialized", + "offset": 0, + "slot": "0", + "type": "t_uint8", + "contract": "Initializable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:63", + "retypedFrom": "bool" + }, + { + "label": "_initializing", + "offset": 1, + "slot": "0", + "type": "t_bool", + "contract": "Initializable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:68" + }, + { + "label": "__gap", + "offset": 0, + "slot": "1", + "type": "t_array(t_uint256)50_storage", + "contract": "ContextUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol:40" + }, + { + "label": "_owner", + "offset": 0, + "slot": "51", + "type": "t_address", + "contract": "OwnableUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol:22" + }, + { + "label": "__gap", + "offset": 0, + "slot": "52", + "type": "t_array(t_uint256)49_storage", + "contract": "OwnableUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol:94" + }, + { + "label": "__gap", + "offset": 0, + "slot": "101", + "type": "t_array(t_uint256)50_storage", + "contract": "ERC1967UpgradeUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/proxy/ERC1967/ERC1967UpgradeUpgradeable.sol:169" + }, + { + "label": "__gap", + "offset": 0, + "slot": "151", + "type": "t_array(t_uint256)50_storage", + "contract": "UUPSUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol:111" + }, + { + "label": "token", + "offset": 0, + "slot": "201", + "type": "t_address", + "contract": "RewardPool", + "src": "contracts/RewardPool.sol:21" + }, + { + "label": "staking", + "offset": 0, + "slot": "202", + "type": "t_address", + "contract": "RewardPool", + "src": "contracts/RewardPool.sol:24" + }, + { + "label": "fees", + "offset": 0, + "slot": "203", + "type": "t_uint256", + "contract": "RewardPool", + "src": "contracts/RewardPool.sol:27" + }, + { + "label": "rewards", + "offset": 0, + "slot": "204", + "type": "t_mapping(t_address,t_array(t_struct(Reward)22549_storage)dyn_storage)", + "contract": "RewardPool", + "src": "contracts/RewardPool.sol:30" + }, + { + "label": "totalFee", + "offset": 0, + "slot": "205", + "type": "t_uint256", + "contract": "RewardPool", + "src": "contracts/RewardPool.sol:32" + }, + { + "label": "__gap", + "offset": 0, + "slot": "206", + "type": "t_array(t_uint256)45_storage", + "contract": "RewardPool", + "src": "contracts/RewardPool.sol:146" + } + ], + "types": { + "t_address": { + "label": "address", + "numberOfBytes": "20" + }, + "t_array(t_struct(Reward)22549_storage)dyn_storage": { + "label": "struct IRewardPool.Reward[]", + "numberOfBytes": "32" + }, + "t_array(t_uint256)45_storage": { + "label": "uint256[45]", + "numberOfBytes": "1440" + }, + "t_array(t_uint256)49_storage": { + "label": "uint256[49]", + "numberOfBytes": "1568" + }, + "t_array(t_uint256)50_storage": { + "label": "uint256[50]", + "numberOfBytes": "1600" + }, + "t_bool": { + "label": "bool", + "numberOfBytes": "1" + }, + "t_mapping(t_address,t_array(t_struct(Reward)22549_storage)dyn_storage)": { + "label": "mapping(address => struct IRewardPool.Reward[])", + "numberOfBytes": "32" + }, + "t_struct(Reward)22549_storage": { + "label": "struct IRewardPool.Reward", + "members": [ + { + "label": "escrowAddress", + "type": "t_address", + "offset": 0, + "slot": "0" + }, + { + "label": "slasher", + "type": "t_address", + "offset": 0, + "slot": "1" + }, + { + "label": "tokens", + "type": "t_uint256", + "offset": 0, + "slot": "2" + } + ], + "numberOfBytes": "96" + }, + "t_uint256": { + "label": "uint256", + "numberOfBytes": "32" + }, + "t_uint8": { + "label": "uint8", + "numberOfBytes": "1" + } + }, + "namespaces": {} + } + } + } +} diff --git a/packages/core/hardhat.config.ts b/packages/core/hardhat.config.ts index aac1c7aff6..89eb04d197 100644 --- a/packages/core/hardhat.config.ts +++ b/packages/core/hardhat.config.ts @@ -100,6 +100,13 @@ const config: HardhatUserConfig = { process.env.PRIVATE_KEY !== undefined ? [process.env.PRIVATE_KEY] : [], timeout: 2000000, }, + polygonAmoy: { + chainId: 80002, + url: process.env.ETH_POLYGON_AMOY_URL || '', + accounts: + process.env.PRIVATE_KEY !== undefined ? [process.env.PRIVATE_KEY] : [], + timeout: 2000000, + }, bsc: { chainId: 56, url: process.env.ETH_BSC_URL || '', @@ -203,6 +210,7 @@ const config: HardhatUserConfig = { goerli: process.env.ETHERSCAN_API_KEY || '', polygon: process.env.POLYGONSCAN_API_KEY || '', polygonMumbai: process.env.POLYGONSCAN_API_KEY || '', + polygonAmoy: process.env.OKLINK_API_KEY || '', bsc: process.env.BSC_API_KEY || '', bscTestnet: process.env.BSC_API_KEY || '', moonbeam: process.env.MOONSCAN_API_KEY || '', @@ -222,6 +230,15 @@ const config: HardhatUserConfig = { browserURL: process.env.SKALE_BROWSER_URL || '', }, }, + { + network: 'polygonAmoy', + chainId: 80002, + urls: { + apiURL: + 'https://www.oklink.com/api/v5/explorer/contract/verify-source-code-plugin/AMOY_TESTNET', + browserURL: 'https://www.oklink.com', + }, + }, ], }, mocha: { diff --git a/packages/core/scripts/deploy-proxies.ts b/packages/core/scripts/deploy-proxies.ts index 9c9c6606ef..11f195e95e 100644 --- a/packages/core/scripts/deploy-proxies.ts +++ b/packages/core/scripts/deploy-proxies.ts @@ -14,7 +14,7 @@ async function main() { [hmtAddress, 1, 1], { initializer: 'initialize', kind: 'uups' } ); - await stakingContract.deployed(); + await stakingContract.waitForDeployment(); console.log('Staking Proxy Address: ', await stakingContract.getAddress()); console.log( 'Staking Implementation Address: ', @@ -31,7 +31,7 @@ async function main() { [await stakingContract.getAddress()], { initializer: 'initialize', kind: 'uups' } ); - await escrowFactoryContract.deployed(); + await escrowFactoryContract.waitForDeployment(); console.log( 'Escrow Factory Proxy Address: ', await escrowFactoryContract.getAddress() @@ -54,7 +54,7 @@ async function main() { [hmtAddress, await stakingContract.getAddress(), 1], { initializer: 'initialize', kind: 'uups' } ); - await rewardPoolContract.deployed(); + await rewardPoolContract.waitForDeployment(); console.log( 'Reward Pool Proxy Address: ', await rewardPoolContract.getAddress() diff --git a/packages/sdk/python/human-protocol-sdk/example.py b/packages/sdk/python/human-protocol-sdk/example.py index e338d03827..a504829d32 100644 --- a/packages/sdk/python/human-protocol-sdk/example.py +++ b/packages/sdk/python/human-protocol-sdk/example.py @@ -7,14 +7,105 @@ from human_protocol_sdk.statistics import StatisticsClient, StatisticsParam from human_protocol_sdk.storage import StorageClient from human_protocol_sdk.agreement import agreement -from human_protocol_sdk.operator import OperatorUtils -def get_rep(): - aa = OperatorUtils.get_reputation_network_operators( - ChainId.LOCALHOST, "0x15d34aaf54267db7d7c367839aaf71a00a2c6a65" +def get_escrow_statistics(statistics_client: StatisticsClient): + print(statistics_client.get_escrow_statistics()) + print( + statistics_client.get_escrow_statistics( + StatisticsParam( + date_from=datetime.datetime(2023, 5, 8), + date_to=datetime.datetime(2023, 6, 8), + ) + ) ) - print(aa[0].job_types) + + +def get_worker_statistics(statistics_client: StatisticsClient): + print(statistics_client.get_worker_statistics()) + print( + statistics_client.get_worker_statistics( + StatisticsParam( + date_from=datetime.datetime(2023, 5, 8), + date_to=datetime.datetime(2023, 6, 8), + ) + ) + ) + + +def get_payment_statistics(statistics_client: StatisticsClient): + print(statistics_client.get_payment_statistics()) + print( + statistics_client.get_payment_statistics( + StatisticsParam( + date_from=datetime.datetime(2023, 5, 8), + date_to=datetime.datetime(2023, 6, 8), + ) + ) + ) + + +def get_hmt_statistics(statistics_client: StatisticsClient): + print(statistics_client.get_hmt_statistics()) + print( + statistics_client.get_hmt_statistics( + StatisticsParam( + date_from=datetime.datetime(2023, 5, 8), + date_to=datetime.datetime(2023, 6, 8), + ) + ) + ) + + +def get_escrows(): + print( + EscrowUtils.get_escrows( + EscrowFilter( + networks=[ChainId.POLYGON_MUMBAI], + status=Status.Pending, + date_from=datetime.datetime(2023, 5, 8), + date_to=datetime.datetime(2023, 6, 8), + ) + ) + ) + + print( + vars( + EscrowUtils.get_escrow( + ChainId.POLYGON_MUMBAI, "0xf9ec66feeafb850d85b88142a7305f55e0532959" + ) + ) + ) + + +def get_leaders(): + leaders = StakingUtils.get_leaders() + print(leaders) + print(vars(StakingUtils.get_leader(ChainId.POLYGON_MUMBAI, leaders[0].address))) + print( + StakingUtils.get_leaders( + LeaderFilter(networks=[ChainId.POLYGON_MUMBAI], role="Job Launcher") + ) + ) + + +def agreement_example(): + # process annotation data and get quality estimates + url = "https://raw.githubusercontent.com/humanprotocol/human-protocol/efa8d3789ac35915b42435011cd0a8d36507564c/packages/sdk/python/human-protocol-sdk/example_annotations.json" + annotations = json.loads(StorageClient.download_file_from_url(url)) + print(annotations) + + report = agreement( + data=annotations["data"], + data_format=annotations["data_format"], + labels=annotations["labels"], + nan_values=annotations["nan_values"], + measure="fleiss_kappa", + bootstrap_method="bca", + bootstrap_kwargs={"seed": 42}, + ) + print(report["results"]) + print(report["config"]) if __name__ == "__main__": diff --git a/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/constants.py b/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/constants.py index a22a06e36d..7e088692b8 100644 --- a/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/constants.py +++ b/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/constants.py @@ -12,6 +12,7 @@ class ChainId(Enum): BSC_TESTNET = 97 POLYGON = 137 POLYGON_MUMBAI = 80001 + POLYGON_AMOY = 80002 MOONBEAM = 1284 MOONBASE_ALPHA = 1287 AVALANCHE_TESTNET = 43113 @@ -113,6 +114,20 @@ class ChainId(Enum): ), "old_factory_address": "0x558cd800f9F0B02f3B149667bDe003284c867E94", }, + ChainId.POLYGON_AMOY: { + "title": "Polygon Amoy", + "scan_url": "https://www.oklink.com/amoy", + "subgraph_url": ( + "https://api.thegraph.com/subgraphs/name/humanprotocol/mumbai-v2" + ), + "hmt_address": "0x792abbcC99c01dbDec49c9fa9A828a186Da45C33", + "factory_address": "0xAFf5a986A530ff839d49325A5dF69F96627E8D29", + "staking_address": "0xCc0AF0635aa19fE799B6aFDBe28fcFAeA7f00a60", + "reward_pool_address": "0xd866bCEFf6D0F77E1c3EAE28230AE6C79b03fDa7", + "kvstore_address": "0x724AeFC243EdacCA27EAB86D3ec5a76Af4436Fc7", + "old_subgraph_url": "", + "old_factory_address": "", + }, ChainId.MOONBEAM: { "title": "Moonbeam", "scan_url": "https://moonbeam.moonscan.io", diff --git a/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/escrow/escrow_utils.py b/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/escrow/escrow_utils.py index 1157508a12..8c113862df 100644 --- a/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/escrow/escrow_utils.py +++ b/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/escrow/escrow_utils.py @@ -12,7 +12,7 @@ print( EscrowUtils.get_escrows( EscrowFilter( - networks=[ChainId.POLYGON_MUMBAI], + networks=[ChainId.POLYGON_AMOY], status=Status.Pending, date_from=datetime.datetime(2023, 5, 8), date_to=datetime.datetime(2023, 6, 8), @@ -128,7 +128,7 @@ class EscrowUtils: @staticmethod def get_escrows( - filter: EscrowFilter = EscrowFilter(networks=[ChainId.POLYGON_MUMBAI]), + filter: EscrowFilter = EscrowFilter(networks=[ChainId.POLYGON_AMOY]), ) -> List[EscrowData]: """Get an array of escrow addresses based on the specified filter parameters. @@ -145,7 +145,7 @@ def get_escrows( print( EscrowUtils.get_escrows( EscrowFilter( - networks=[ChainId.POLYGON_MUMBAI], + networks=[ChainId.POLYGON_AMOY], status=Status.Pending, date_from=datetime.datetime(2023, 5, 8), date_to=datetime.datetime(2023, 6, 8), @@ -258,7 +258,7 @@ def get_escrow( print( EscrowUtils.get_escrow( - ChainId.POLYGON_MUMBAI, + ChainId.POLYGON_AMOY, "0x1234567890123456789012345678901234567890" ) ) diff --git a/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/operator/operator_utils.py b/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/operator/operator_utils.py index dda6348b42..696736e72c 100644 --- a/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/operator/operator_utils.py +++ b/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/operator/operator_utils.py @@ -11,7 +11,7 @@ print( OperatorUtils.get_leaders( - LeaderFilter(networks=[ChainId.POLYGON_MUMBAI], role="Job Launcher") + LeaderFilter(networks=[ChainId.POLYGON_AMOY], role="Job Launcher") ) ) @@ -172,7 +172,7 @@ class OperatorUtils: @staticmethod def get_leaders( - filter: LeaderFilter = LeaderFilter(networks=[ChainId.POLYGON_MUMBAI]), + filter: LeaderFilter = LeaderFilter(networks=[ChainId.POLYGON_AMOY]), ) -> List[LeaderData]: """Get leaders data of the protocol @@ -188,7 +188,7 @@ def get_leaders( print( OperatorUtils.get_leaders( - LeaderFilter(networks=[ChainId.POLYGON_MUMBAI]) + LeaderFilter(networks=[ChainId.POLYGON_AMOY]) ) ) """ @@ -258,7 +258,7 @@ def get_leader( from human_protocol_sdk.operator import OperatorUtils leader = OperatorUtils.get_leader( - ChainId.POLYGON_MUMBAI, + ChainId.POLYGON_AMOY, '0x62dD51230A30401C455c8398d06F85e4EaB6309f' ) """ @@ -326,7 +326,7 @@ def get_reputation_network_operators( from human_protocol_sdk.operator import OperatorUtils leader = OperatorUtils.get_reputation_network_operators( - ChainId.POLYGON_MUMBAI, + ChainId.POLYGON_AMOY, '0x62dD51230A30401C455c8398d06F85e4EaB6309f' ) """ @@ -378,7 +378,7 @@ def get_rewards_info(chain_id: ChainId, slasher: str) -> List[RewardData]: from human_protocol_sdk.operator import OperatorUtils rewards_info = OperatorUtils.get_rewards_info( - ChainId.POLYGON_MUMBAI, + ChainId.POLYGON_AMOY, '0x62dD51230A30401C455c8398d06F85e4EaB6309f' ) """ diff --git a/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/statistics/statistics_client.py b/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/statistics/statistics_client.py index 0321154d95..bcf539a26e 100644 --- a/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/statistics/statistics_client.py +++ b/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/statistics/statistics_client.py @@ -9,7 +9,7 @@ from human_protocol_sdk.constants import ChainId from human_protocol_sdk.statistics import StatisticsClient - statistics_client = StatisticsClient(ChainId.POLYGON_MUMBAI) + statistics_client = StatisticsClient(ChainId.POLYGON_AMOY) Module ------ @@ -278,7 +278,7 @@ class StatisticsClient: A client used to get statistical data. """ - def __init__(self, chain_id: ChainId = ChainId.POLYGON_MUMBAI): + def __init__(self, chain_id: ChainId = ChainId.POLYGON_AMOY): """Initializes a Statistics instance :param chain_id: Chain ID to get statistical data from @@ -308,7 +308,7 @@ def get_escrow_statistics( from human_protocol_sdk.contants import ChainId from human_protocol_sdk.statistics import StatisticsClient, StatisticsParam - statistics_client = StatisticsClient(ChainId.POLYGON_MUMBAI) + statistics_client = StatisticsClient(ChainId.POLYGON_AMOY) print(statistics_client.get_escrow_statistics()) print( @@ -382,7 +382,7 @@ def get_worker_statistics( from human_protocol_sdk.contants import ChainId from human_protocol_sdk.statistics import StatisticsClient, StatisticsParam - statistics_client = StatisticsClient(ChainId.POLYGON_MUMBAI) + statistics_client = StatisticsClient(ChainId.POLYGON_AMOY) print(statistics_client.get_worker_statistics()) print( @@ -435,7 +435,7 @@ def get_payment_statistics( from human_protocol_sdk.contants import ChainId from human_protocol_sdk.statistics import StatisticsClient, StatisticsParam - statistics_client = StatisticsClient(ChainId.POLYGON_MUMBAI) + statistics_client = StatisticsClient(ChainId.POLYGON_AMOY) print(statistics_client.get_payment_statistics()) print( @@ -496,7 +496,7 @@ def get_hmt_statistics( from human_protocol_sdk.contants import ChainId from human_protocol_sdk.statistics import StatisticsClient, StatisticsParam - statistics_client = StatisticsClient(ChainId.POLYGON_MUMBAI) + statistics_client = StatisticsClient(ChainId.POLYGON_AMOY) print(statistics_client.get_hmt_statistics()) print( diff --git a/packages/sdk/python/human-protocol-sdk/test/human_protocol_sdk/escrow/test_escrow_client.py b/packages/sdk/python/human-protocol-sdk/test/human_protocol_sdk/escrow/test_escrow_client.py index 14d3be5587..a7b5e12b4c 100644 --- a/packages/sdk/python/human-protocol-sdk/test/human_protocol_sdk/escrow/test_escrow_client.py +++ b/packages/sdk/python/human-protocol-sdk/test/human_protocol_sdk/escrow/test_escrow_client.py @@ -2204,7 +2204,7 @@ def test_escrow_filter_valid_params(self): date_from = datetime.fromtimestamp(1683811973) date_to = datetime.fromtimestamp(1683812007) escrow_filter = EscrowFilter( - networks=[ChainId.POLYGON_MUMBAI], + networks=[ChainId.POLYGON_AMOY], launcher=launcher, reputation_oracle=reputation_oracle, recording_oracle=recording_oracle, @@ -2232,13 +2232,13 @@ def test_escrow_filter_invalid_chain_id(self): def test_escrow_filter_invalid_address_launcher(self): with self.assertRaises(FilterError) as cm: - EscrowFilter(networks=[ChainId.POLYGON_MUMBAI], launcher="invalid_address") + EscrowFilter(networks=[ChainId.POLYGON_AMOY], launcher="invalid_address") self.assertEqual("Invalid address: invalid_address", str(cm.exception)) def test_escrow_filter_invalid_address_reputation_oracle(self): with self.assertRaises(FilterError) as cm: EscrowFilter( - networks=[ChainId.POLYGON_MUMBAI], + networks=[ChainId.POLYGON_AMOY], reputation_oracle="invalid_address", ) self.assertEqual("Invalid address: invalid_address", str(cm.exception)) @@ -2246,7 +2246,7 @@ def test_escrow_filter_invalid_address_reputation_oracle(self): def test_escrow_filter_invalid_address_recording_oracle(self): with self.assertRaises(FilterError) as cm: EscrowFilter( - networks=[ChainId.POLYGON_MUMBAI], + networks=[ChainId.POLYGON_AMOY], recording_oracle="invalid_address", ) self.assertEqual("Invalid address: invalid_address", str(cm.exception)) @@ -2254,7 +2254,7 @@ def test_escrow_filter_invalid_address_recording_oracle(self): def test_escrow_filter_invalid_dates(self): with self.assertRaises(FilterError) as cm: EscrowFilter( - networks=[ChainId.POLYGON_MUMBAI], + networks=[ChainId.POLYGON_AMOY], date_from=datetime.fromtimestamp(1683812007), date_to=datetime.fromtimestamp(1683811973), ) diff --git a/packages/sdk/python/human-protocol-sdk/test/human_protocol_sdk/escrow/test_escrow_utils.py b/packages/sdk/python/human-protocol-sdk/test/human_protocol_sdk/escrow/test_escrow_utils.py index 63ce15df5b..bb0a2588ff 100644 --- a/packages/sdk/python/human-protocol-sdk/test/human_protocol_sdk/escrow/test_escrow_utils.py +++ b/packages/sdk/python/human-protocol-sdk/test/human_protocol_sdk/escrow/test_escrow_utils.py @@ -65,7 +65,7 @@ def test_get_escrows(self): } def side_effect(subgraph_url, query, params): - if subgraph_url == NETWORKS[ChainId.POLYGON_MUMBAI]["subgraph_url"]: + if subgraph_url == NETWORKS[ChainId.POLYGON_AMOY]["subgraph_url"]: return {"data": {"escrows": [mock_escrow_1]}} else: return {"data": {"escrows": [mock_escrow_2]}} @@ -73,7 +73,7 @@ def side_effect(subgraph_url, query, params): mock_function.side_effect = side_effect filter = EscrowFilter( - networks=[ChainId.POLYGON_MUMBAI], + networks=[ChainId.POLYGON_AMOY], launcher="0x1234567890123456789012345678901234567891", job_requester_id="1", status=Status.Pending, @@ -83,7 +83,7 @@ def side_effect(subgraph_url, query, params): filtered = EscrowUtils.get_escrows(filter) mock_function.assert_called_once_with( - NETWORKS[ChainId.POLYGON_MUMBAI]["subgraph_url"], + NETWORKS[ChainId.POLYGON_AMOY]["subgraph_url"], query=get_escrows_query(filter), params={ "launcher": "0x1234567890123456789012345678901234567891", @@ -99,12 +99,12 @@ def side_effect(subgraph_url, query, params): self.assertEqual(len(filtered), 1) self.assertEqual(filtered[0].address, mock_escrow_1["address"]) - filter = EscrowFilter(networks=[ChainId.POLYGON, ChainId.POLYGON_MUMBAI]) + filter = EscrowFilter(networks=[ChainId.POLYGON, ChainId.POLYGON_AMOY]) filtered = EscrowUtils.get_escrows(filter) mock_function.assert_called_with( - NETWORKS[ChainId.POLYGON_MUMBAI]["subgraph_url"], + NETWORKS[ChainId.POLYGON_AMOY]["subgraph_url"], query=get_escrows_query(filter), params={ "launcher": None, @@ -119,7 +119,7 @@ def side_effect(subgraph_url, query, params): ) self.assertEqual(len(filtered), 2) self.assertEqual(filtered[0].chain_id, ChainId.POLYGON) - self.assertEqual(filtered[1].chain_id, ChainId.POLYGON_MUMBAI) + self.assertEqual(filtered[1].chain_id, ChainId.POLYGON_AMOY) def test_get_escrow(self): with patch( @@ -155,18 +155,18 @@ def test_get_escrow(self): } escrow = EscrowUtils.get_escrow( - ChainId.POLYGON_MUMBAI, + ChainId.POLYGON_AMOY, "0x1234567890123456789012345678901234567890", ) mock_function.assert_called_once_with( - NETWORKS[ChainId.POLYGON_MUMBAI]["subgraph_url"], + NETWORKS[ChainId.POLYGON_AMOY]["subgraph_url"], query=get_escrow_query(), params={ "escrowAddress": "0x1234567890123456789012345678901234567890", }, ) - self.assertEqual(escrow.chain_id, ChainId.POLYGON_MUMBAI) + self.assertEqual(escrow.chain_id, ChainId.POLYGON_AMOY) self.assertEqual(escrow.address, mock_escrow["address"]) self.assertEqual(escrow.amount_paid, int(mock_escrow["amountPaid"])) @@ -181,11 +181,11 @@ def test_get_escrow_empty_data(self): } escrow = EscrowUtils.get_escrow( - ChainId.POLYGON_MUMBAI, + ChainId.POLYGON_AMOY, "0x1234567890123456789012345678901234567890", ) mock_function.assert_called_once_with( - NETWORKS[ChainId.POLYGON_MUMBAI]["subgraph_url"], + NETWORKS[ChainId.POLYGON_AMOY]["subgraph_url"], query=get_escrow_query(), params={ "escrowAddress": "0x1234567890123456789012345678901234567890", diff --git a/packages/sdk/typescript/human-protocol-sdk/example/escrow.ts b/packages/sdk/typescript/human-protocol-sdk/example/escrow.ts index fe53ee25d4..c31675afda 100644 --- a/packages/sdk/typescript/human-protocol-sdk/example/escrow.ts +++ b/packages/sdk/typescript/human-protocol-sdk/example/escrow.ts @@ -5,7 +5,7 @@ import { EscrowUtils } from '../src/escrow'; import { EscrowStatus } from '../src/types'; export const getEscrows = async () => { - if (!NETWORKS[ChainId.POLYGON_MUMBAI]) { + if (!NETWORKS[ChainId.POLYGON_AMOY]) { return; } @@ -13,7 +13,7 @@ export const getEscrows = async () => { status: EscrowStatus.Pending, from: new Date(2023, 4, 8), to: new Date(2023, 5, 8), - networks: [ChainId.POLYGON_MUMBAI], + networks: [ChainId.POLYGON_AMOY], }); console.log('Pending escrows:', escrows); diff --git a/packages/sdk/typescript/human-protocol-sdk/example/operator.ts b/packages/sdk/typescript/human-protocol-sdk/example/operator.ts index f187f13078..53a5fd5dea 100644 --- a/packages/sdk/typescript/human-protocol-sdk/example/operator.ts +++ b/packages/sdk/typescript/human-protocol-sdk/example/operator.ts @@ -4,7 +4,7 @@ import { ChainId } from '../src/enums'; import { OperatorUtils } from '../src/operator'; export const getLeaders = async () => { - if (!NETWORKS[ChainId.POLYGON_MUMBAI]) { + if (!NETWORKS[ChainId.POLYGON_AMOY]) { return; } @@ -13,14 +13,14 @@ export const getLeaders = async () => { console.log('Leaders:', leaders); const leader = await OperatorUtils.getLeader( - ChainId.POLYGON_MUMBAI, + ChainId.POLYGON_AMOY, leaders[0].address ); console.log('First leader: ', leader); const reputationOracles = await OperatorUtils.getLeaders({ - networks: [ChainId.POLYGON_MUMBAI], + networks: [ChainId.POLYGON_AMOY], role: 'Reputation Oracle', }); diff --git a/packages/sdk/typescript/human-protocol-sdk/src/constants.ts b/packages/sdk/typescript/human-protocol-sdk/src/constants.ts index 1cfbe0cc4d..55547a5016 100644 --- a/packages/sdk/typescript/human-protocol-sdk/src/constants.ts +++ b/packages/sdk/typescript/human-protocol-sdk/src/constants.ts @@ -151,6 +151,20 @@ export const NETWORKS: { 'https://api.thegraph.com/subgraphs/name/humanprotocol/mumbai', oldFactoryAddress: '0x558cd800f9F0B02f3B149667bDe003284c867E94', }, + [ChainId.POLYGON_AMOY]: { + chainId: ChainId.POLYGON_AMOY, + title: 'Polygon Amoy', + scanUrl: 'https://www.oklink.com/amoy', + factoryAddress: '0xAFf5a986A530ff839d49325A5dF69F96627E8D29', + hmtAddress: '0x792abbcC99c01dbDec49c9fa9A828a186Da45C33', + stakingAddress: '0xCc0AF0635aa19fE799B6aFDBe28fcFAeA7f00a60', + rewardPoolAddress: '0xd866bCEFf6D0F77E1c3EAE28230AE6C79b03fDa7', + kvstoreAddress: '0x724AeFC243EdacCA27EAB86D3ec5a76Af4436Fc7', + subgraphUrl: + 'https://subgraph.satsuma-prod.com/8d51f9873a51/team--2543/humanprotocol-amoy/api', + oldSubgraphUrl: '', + oldFactoryAddress: '', + }, [ChainId.MOONBEAM]: { chainId: ChainId.MOONBEAM, title: 'Moonbeam', diff --git a/packages/sdk/typescript/human-protocol-sdk/src/enums.ts b/packages/sdk/typescript/human-protocol-sdk/src/enums.ts index d0a398c41c..c7387fee11 100644 --- a/packages/sdk/typescript/human-protocol-sdk/src/enums.ts +++ b/packages/sdk/typescript/human-protocol-sdk/src/enums.ts @@ -7,6 +7,7 @@ export enum ChainId { BSC_TESTNET = 97, POLYGON = 137, POLYGON_MUMBAI = 80001, + POLYGON_AMOY = 80002, MOONBEAM = 1284, MOONBASE_ALPHA = 1287, AVALANCHE_TESTNET = 43113, diff --git a/packages/sdk/typescript/human-protocol-sdk/src/escrow.ts b/packages/sdk/typescript/human-protocol-sdk/src/escrow.ts index f9b65a93cc..21fb27d39a 100644 --- a/packages/sdk/typescript/human-protocol-sdk/src/escrow.ts +++ b/packages/sdk/typescript/human-protocol-sdk/src/escrow.ts @@ -1399,7 +1399,7 @@ export class EscrowClient extends BaseEthersClient { * import { ChainId, EscrowUtils } from '@human-protocol/sdk'; * * const escrowAddresses = new EscrowUtils.getEscrows({ - * networks: [ChainId.POLYGON_MUMBAI] + * networks: [ChainId.POLYGON_AMOY] * }); * ``` */ @@ -1434,6 +1434,7 @@ export class EscrowUtils { * BSC_TESTNET = 97, * POLYGON = 137, * POLYGON_MUMBAI = 80001, + * POLYGON_AMOY=80002, * MOONBEAM = 1284, * MOONBASE_ALPHA = 1287, * AVALANCHE = 43114, @@ -1496,7 +1497,7 @@ export class EscrowUtils { * status: EscrowStatus.Pending, * from: new Date(2023, 4, 8), * to: new Date(2023, 5, 8), - * networks: [ChainId.POLYGON_MUMBAI] + * networks: [ChainId.POLYGON_AMOY] * }; * const escrowDatas = await EscrowUtils.getEscrows(filters); * ``` @@ -1578,6 +1579,7 @@ export class EscrowUtils { * BSC_TESTNET = 97, * POLYGON = 137, * POLYGON_MUMBAI = 80001, + * POLYGON_AMOY = 80002, * MOONBEAM = 1284, * MOONBASE_ALPHA = 1287, * AVALANCHE = 43114, @@ -1626,7 +1628,7 @@ export class EscrowUtils { * ```ts * import { ChainId, EscrowUtils } from '@human-protocol/sdk'; * - * const escrowData = new EscrowUtils.getEscrow(ChainId.POLYGON_MUMBAI, "0x1234567890123456789012345678901234567890"); + * const escrowData = new EscrowUtils.getEscrow(ChainId.POLYGON_AMOY, "0x1234567890123456789012345678901234567890"); * ``` */ public static async getEscrow( diff --git a/packages/sdk/typescript/human-protocol-sdk/src/operator.ts b/packages/sdk/typescript/human-protocol-sdk/src/operator.ts index 7038f51883..3e4646ea94 100644 --- a/packages/sdk/typescript/human-protocol-sdk/src/operator.ts +++ b/packages/sdk/typescript/human-protocol-sdk/src/operator.ts @@ -37,7 +37,7 @@ export class OperatorUtils { * ```ts * import { OperatorUtils, ChainId } from '@human-protocol/sdk'; * - * const leader = await OperatorUtils.getLeader(ChainId.POLYGON_MUMBAI, '0x62dD51230A30401C455c8398d06F85e4EaB6309f'); + * const leader = await OperatorUtils.getLeader(ChainId.POLYGON_AMOY, '0x62dD51230A30401C455c8398d06F85e4EaB6309f'); * ``` */ public static async getLeader( @@ -82,7 +82,7 @@ export class OperatorUtils { * ``` */ public static async getLeaders( - filter: ILeadersFilter = { networks: [ChainId.POLYGON_MUMBAI] } + filter: ILeadersFilter = { networks: [ChainId.POLYGON_AMOY] } ): Promise { try { let leaders_data: ILeader[] = []; @@ -117,7 +117,7 @@ export class OperatorUtils { * ```typescript * import { OperatorUtils, ChainId } from '@human-protocol/sdk'; * - * const operators = await OperatorUtils.getReputationNetworkOperators(ChainId.POLYGON_MUMBAI, '0x62dD51230A30401C455c8398d06F85e4EaB6309f'); + * const operators = await OperatorUtils.getReputationNetworkOperators(ChainId.POLYGON_AMOY, '0x62dD51230A30401C455c8398d06F85e4EaB6309f'); * ``` */ public static async getReputationNetworkOperators( @@ -156,7 +156,7 @@ export class OperatorUtils { * ```ts * import { OperatorUtils, ChainId } from '@human-protocol/sdk'; * - * const rewards = await OperatorUtils.getRewards(ChainId.POLYGON_MUMBAI, '0x62dD51230A30401C455c8398d06F85e4EaB6309f'); + * const rewards = await OperatorUtils.getRewards(ChainId.POLYGON_AMOY, '0x62dD51230A30401C455c8398d06F85e4EaB6309f'); * ``` */ public static async getRewards( diff --git a/packages/sdk/typescript/human-protocol-sdk/src/statistics.ts b/packages/sdk/typescript/human-protocol-sdk/src/statistics.ts index 8e8d316449..75bbf7eaf0 100644 --- a/packages/sdk/typescript/human-protocol-sdk/src/statistics.ts +++ b/packages/sdk/typescript/human-protocol-sdk/src/statistics.ts @@ -54,7 +54,7 @@ import { throwError } from './utils'; * ```ts * import { StatisticsClient, ChainId, NETWORKS } from '@human-protocol/sdk'; * - * const statisticsClient = new StatisticsClient(NETWORKS[ChainId.POLYGON_MUMBAI]); + * const statisticsClient = new StatisticsClient(NETWORKS[ChainId.POLYGON_AMOY]); * ``` */ export class StatisticsClient { @@ -109,7 +109,7 @@ export class StatisticsClient { * ```ts * import { StatisticsClient, ChainId, NETWORKS } from '@human-protocol/sdk'; * - * const statisticsClient = new StatisticsClient(NETWORKS[ChainId.POLYGON_MUMBAI]); + * const statisticsClient = new StatisticsClient(NETWORKS[ChainId.POLYGON_AMOY]); * * const escrowStatistics = await statisticsClient.getEscrowStatistics(); * const escrowStatisticsApril = await statisticsClient.getEscrowStatistics({ @@ -184,7 +184,7 @@ export class StatisticsClient { * ```ts * import { StatisticsClient, ChainId, NETWORKS } from '@human-protocol/sdk'; * - * const statisticsClient = new StatisticsClient(NETWORKS[ChainId.POLYGON_MUMBAI]); + * const statisticsClient = new StatisticsClient(NETWORKS[ChainId.POLYGON_AMOY]); * * const workerStatistics = await statisticsClient.getWorkerStatistics(); * const workerStatisticsApril = await statisticsClient.getWorkerStatistics({ @@ -252,7 +252,7 @@ export class StatisticsClient { * ```ts * import { StatisticsClient, ChainId, NETWORKS } from '@human-protocol/sdk'; * - * const statisticsClient = new StatisticsClient(NETWORKS[ChainId.POLYGON_MUMBAI]); + * const statisticsClient = new StatisticsClient(NETWORKS[ChainId.POLYGON_AMOY]); * * console.log( * 'Payment statistics:', @@ -355,7 +355,7 @@ export class StatisticsClient { * ```ts * import { StatisticsClient, ChainId, NETWORKS } from '@human-protocol/sdk'; * - * const statisticsClient = new StatisticsClient(NETWORKS[ChainId.POLYGON_MUMBAI]); + * const statisticsClient = new StatisticsClient(NETWORKS[ChainId.POLYGON_AMOY]); * * const hmtStatistics = await statisticsClient.getHMTStatistics(); * diff --git a/packages/sdk/typescript/human-protocol-sdk/test/escrow.test.ts b/packages/sdk/typescript/human-protocol-sdk/test/escrow.test.ts index f4cf1adc0f..90b664218b 100644 --- a/packages/sdk/typescript/human-protocol-sdk/test/escrow.test.ts +++ b/packages/sdk/typescript/human-protocol-sdk/test/escrow.test.ts @@ -2162,7 +2162,7 @@ describe('EscrowUtils', () => { const launcher = FAKE_ADDRESS; await expect( - EscrowUtils.getEscrows({ networks: [ChainId.POLYGON_MUMBAI], launcher }) + EscrowUtils.getEscrows({ networks: [ChainId.POLYGON_AMOY], launcher }) ).rejects.toThrow(ErrorInvalidAddress); }); @@ -2171,7 +2171,7 @@ describe('EscrowUtils', () => { await expect( EscrowUtils.getEscrows({ - networks: [ChainId.POLYGON_MUMBAI], + networks: [ChainId.POLYGON_AMOY], recordingOracle, }) ).rejects.toThrow(ErrorInvalidAddress); @@ -2182,7 +2182,7 @@ describe('EscrowUtils', () => { await expect( EscrowUtils.getEscrows({ - networks: [ChainId.POLYGON_MUMBAI], + networks: [ChainId.POLYGON_AMOY], reputationOracle, }) ).rejects.toThrow(ErrorInvalidAddress); @@ -2222,13 +2222,13 @@ describe('EscrowUtils', () => { .mockResolvedValue({ escrows }); const filter = { - networks: [ChainId.POLYGON_MUMBAI], + networks: [ChainId.POLYGON_AMOY], }; const result = await EscrowUtils.getEscrows(filter); expect(result).toEqual(escrows); expect(gqlFetchSpy).toHaveBeenCalledWith( - 'https://api.thegraph.com/subgraphs/name/humanprotocol/mumbai-v2', + 'https://subgraph.satsuma-prod.com/8d51f9873a51/team--2543/humanprotocol-amoy/api', GET_ESCROWS_QUERY(filter), filter ); @@ -2255,7 +2255,7 @@ describe('EscrowUtils', () => { .mockResolvedValue({ escrows }); const result = await EscrowUtils.getEscrows({ - networks: [ChainId.POLYGON_MUMBAI], + networks: [ChainId.POLYGON_AMOY], launcher: ethers.ZeroAddress, }); @@ -2301,12 +2301,12 @@ describe('EscrowUtils', () => { }); const result = await EscrowUtils.getEscrows({ - networks: [ChainId.POLYGON, ChainId.POLYGON_MUMBAI], + networks: [ChainId.POLYGON, ChainId.POLYGON_AMOY], }); expect(result[0]).toEqual(polygonEscrow); expect(result[1]).toEqual(mumbaiEscrow); expect(result[0].chainId).toEqual(ChainId.POLYGON); - expect(result[1].chainId).toEqual(ChainId.POLYGON_MUMBAI); + expect(result[1].chainId).toEqual(ChainId.POLYGON_AMOY); expect(gqlFetchSpy).toHaveBeenCalled(); }); @@ -2331,7 +2331,7 @@ describe('EscrowUtils', () => { .mockResolvedValue({ escrows }); const result = await EscrowUtils.getEscrows({ - networks: [ChainId.POLYGON_MUMBAI], + networks: [ChainId.POLYGON_AMOY], jobRequesterId: '1', }); diff --git a/packages/sdk/typescript/subgraph/config/amoy.json b/packages/sdk/typescript/subgraph/config/amoy.json new file mode 100644 index 0000000000..4418472949 --- /dev/null +++ b/packages/sdk/typescript/subgraph/config/amoy.json @@ -0,0 +1,35 @@ +{ + "network": "polygon-amoy", + "description": "HUMAN subgraph on Amoy Testnet", + "EscrowFactory": { + "address": "0xAFf5a986A530ff839d49325A5dF69F96627E8D29", + "startBlock": 5773000, + "abi": "../../../../node_modules/@human-protocol/core/abis/EscrowFactory.json" + }, + "HMToken": { + "address": "0x792abbcC99c01dbDec49c9fa9A828a186Da45C33", + "startBlock": 5769546, + "abi": "../../../../node_modules/@human-protocol/core/abis/HMToken.json" + }, + "Escrow": { + "abi": "../../../../node_modules/@human-protocol/core/abis/Escrow.json" + }, + "KVStore": { + "address": "0x724AeFC243EdacCA27EAB86D3ec5a76Af4436Fc7", + "startBlock": 5773002, + "abi": "../../../../node_modules/@human-protocol/core/abis/KVStore.json" + }, + "Staking": { + "address": "0xCc0AF0635aa19fE799B6aFDBe28fcFAeA7f00a60", + "startBlock": 5772993, + "abi": "../../../../node_modules/@human-protocol/core/abis/Staking.json" + }, + "RewardPool": { + "address": "0xd866bCEFf6D0F77E1c3EAE28230AE6C79b03fDa7", + "startBlock": 5773007, + "abi": "../../../../node_modules/@human-protocol/core/abis/RewardPool.json" + }, + "LegacyEscrow": { + "abi": "../../../../node_modules/@human-protocol/core/abis/legacy/Escrow.json" + } +} diff --git a/yarn.lock b/yarn.lock index e6b81da49d..79db197178 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8073,11 +8073,6 @@ abitype@0.7.1: resolved "https://registry.yarnpkg.com/abitype/-/abitype-0.7.1.tgz#16db20abe67de80f6183cf75f3de1ff86453b745" integrity sha512-VBkRHTDZf9Myaek/dO3yMmOzB/y2s3Zo6nVU7yaw1G+TvCHAjwaJzNGN9yo4K5D8bU/VZXKP1EJpRhFr862PlQ== -abitype@0.9.8: - version "0.9.8" - resolved "https://registry.yarnpkg.com/abitype/-/abitype-0.9.8.tgz#1f120b6b717459deafd213dfbf3a3dd1bf10ae8c" - integrity sha512-puLifILdm+8sjyss4S+fsUN09obiT1g2YW6CtcQF+QDzxR0euzgEB29MZujC6zMk2a6SVmtttq1fc6+YFA7WYQ== - abitype@1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/abitype/-/abitype-1.0.0.tgz#237176dace81d90d018bebf3a45cb42f2a2d9e97" @@ -23720,9 +23715,9 @@ undici-types@~5.26.4: integrity sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA== undici@5.26.5, undici@^5.14.0, undici@^6.0.0, undici@^6.11.1: - version "6.12.0" - resolved "https://registry.yarnpkg.com/undici/-/undici-6.12.0.tgz#396d4dbd2ea2351094c00be44655d489afe4fb8a" - integrity sha512-d87yk8lqSFUYtR5fTFe2frpkMIrUEz+lgoJmhcL+J3StVl+8fj8ytE4lLnJOTPCE12YbumNGzf4LYsQyusdV5g== + version "6.13.0" + resolved "https://registry.yarnpkg.com/undici/-/undici-6.13.0.tgz#7edbf4b7f3aac5f8a681d515151bf55cb3589d72" + integrity sha512-Q2rtqmZWrbP8nePMq7mOJIN98M0fYvSgV89vwl/BQRT4mDOeY2GXZngfGpcBBhtky3woM7G24wZV3Q304Bv6cw== unenv@^1.9.0: version "1.9.0" @@ -24121,17 +24116,17 @@ viem@2.7.14: isows "1.0.3" ws "8.13.0" -viem@^1.16.6: - version "1.21.4" - resolved "https://registry.yarnpkg.com/viem/-/viem-1.21.4.tgz#883760e9222540a5a7e0339809202b45fe6a842d" - integrity sha512-BNVYdSaUjeS2zKQgPs+49e5JKocfo60Ib2yiXOWBT6LuVxY1I/6fFX3waEtpXvL1Xn4qu+BVitVtMh9lyThyhQ== +viem@^2.9.17: + version "2.9.17" + resolved "https://registry.yarnpkg.com/viem/-/viem-2.9.17.tgz#4a74b5302fe5b3d6ac8db4769418a0466867befa" + integrity sha512-xMQ4JhgR1fPXQYagEeSsq9lmKXXooHP2gcnowb0eJRq3NTheyzpVBtMuH8DZnnWT4aeFepZktqSXlFul+Ou5Xg== dependencies: "@adraffy/ens-normalize" "1.10.0" "@noble/curves" "1.2.0" "@noble/hashes" "1.3.2" "@scure/bip32" "1.3.2" "@scure/bip39" "1.2.1" - abitype "0.9.8" + abitype "1.0.0" isows "1.0.3" ws "8.13.0" From 587fbd04eaa928400da19ac0126d3577015d871b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 16 Apr 2024 09:05:40 +0200 Subject: [PATCH 61/66] Bump @sendgrid/mail from 7.7.0 to 8.1.3 (#1866) Bumps [@sendgrid/mail](https://github.com/sendgrid/sendgrid-nodejs) from 7.7.0 to 8.1.3. - [Release notes](https://github.com/sendgrid/sendgrid-nodejs/releases) - [Changelog](https://github.com/sendgrid/sendgrid-nodejs/blob/main/CHANGELOG.md) - [Upgrade guide](https://github.com/sendgrid/sendgrid-nodejs/blob/main/UPGRADE.md) - [Commits](https://github.com/sendgrid/sendgrid-nodejs/compare/7.7.0...8.1.3) --- updated-dependencies: - dependency-name: "@sendgrid/mail" dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .../apps/job-launcher/server/package.json | 2 +- yarn.lock | 43 ++++++++----------- 2 files changed, 19 insertions(+), 26 deletions(-) diff --git a/packages/apps/job-launcher/server/package.json b/packages/apps/job-launcher/server/package.json index 9a18544581..612cae0646 100644 --- a/packages/apps/job-launcher/server/package.json +++ b/packages/apps/job-launcher/server/package.json @@ -41,7 +41,7 @@ "@nestjs/swagger": "^7.1.13", "@nestjs/terminus": "^10.2.1", "@nestjs/typeorm": "^10.0.1", - "@sendgrid/mail": "^7.7.0", + "@sendgrid/mail": "^8.1.3", "@types/cookie-parser": "^1.4.3", "@types/express-session": "^1.17.10", "@types/passport-jwt": "^3.0.10", diff --git a/yarn.lock b/yarn.lock index 79db197178..092ba16b34 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4971,28 +4971,28 @@ "@noble/hashes" "~1.3.2" "@scure/base" "~1.1.4" -"@sendgrid/client@^7.7.0": - version "7.7.0" - resolved "https://registry.yarnpkg.com/@sendgrid/client/-/client-7.7.0.tgz#f8f67abd604205a0d0b1af091b61517ef465fdbf" - integrity sha512-SxH+y8jeAQSnDavrTD0uGDXYIIkFylCo+eDofVmZLQ0f862nnqbC3Vd1ej6b7Le7lboyzQF6F7Fodv02rYspuA== +"@sendgrid/client@^8.1.3": + version "8.1.3" + resolved "https://registry.yarnpkg.com/@sendgrid/client/-/client-8.1.3.tgz#51fd4a318627c4b615ff98e35609e98486a3bd6f" + integrity sha512-mRwTticRZIdUTsnyzvlK6dMu3jni9ci9J+dW/6fMMFpGRAJdCJlivFVYQvqk8kRS3RnFzS7sf6BSmhLl1ldDhA== dependencies: - "@sendgrid/helpers" "^7.7.0" - axios "^0.26.0" + "@sendgrid/helpers" "^8.0.0" + axios "^1.6.8" -"@sendgrid/helpers@^7.7.0": - version "7.7.0" - resolved "https://registry.yarnpkg.com/@sendgrid/helpers/-/helpers-7.7.0.tgz#93fb4b6e2f0dc65080440d6a784cc93e8e148757" - integrity sha512-3AsAxfN3GDBcXoZ/y1mzAAbKzTtUZ5+ZrHOmWQ279AuaFXUNCh9bPnRpN504bgveTqoW+11IzPg3I0WVgDINpw== +"@sendgrid/helpers@^8.0.0": + version "8.0.0" + resolved "https://registry.yarnpkg.com/@sendgrid/helpers/-/helpers-8.0.0.tgz#f74bf9743bacafe4c8573be46166130c604c0fc1" + integrity sha512-Ze7WuW2Xzy5GT5WRx+yEv89fsg/pgy3T1E3FS0QEx0/VvRmigMZ5qyVGhJz4SxomegDkzXv/i0aFPpHKN8qdAA== dependencies: deepmerge "^4.2.2" -"@sendgrid/mail@^7.7.0": - version "7.7.0" - resolved "https://registry.yarnpkg.com/@sendgrid/mail/-/mail-7.7.0.tgz#aba09f5ce2e9d8ceee92284c3ea8b4a90b0e38fe" - integrity sha512-5+nApPE9wINBvHSUxwOxkkQqM/IAAaBYoP9hw7WwgDNQPxraruVqHizeTitVtKGiqWCKm2mnjh4XGN3fvFLqaw== +"@sendgrid/mail@^8.1.3": + version "8.1.3" + resolved "https://registry.yarnpkg.com/@sendgrid/mail/-/mail-8.1.3.tgz#d371cbddcd2e8ca9469a68d1ed0c6b3a5c365e5e" + integrity sha512-Wg5iKSUOER83/cfY6rbPa+o3ChnYzWwv1OcsR8gCV8SKi+sUPIMroildimlnb72DBkQxcbylxng1W7f0RIX7MQ== dependencies: - "@sendgrid/client" "^7.7.0" - "@sendgrid/helpers" "^7.7.0" + "@sendgrid/client" "^8.1.3" + "@sendgrid/helpers" "^8.0.0" "@sentry/core@5.30.0": version "5.30.0" @@ -8803,13 +8803,6 @@ axios@^0.21.1, axios@^0.21.4: dependencies: follow-redirects "^1.14.0" -axios@^0.26.0: - version "0.26.1" - resolved "https://registry.yarnpkg.com/axios/-/axios-0.26.1.tgz#1ede41c51fcf51bbbd6fd43669caaa4f0495aaa9" - integrity sha512-fPwcX4EvnSHuInCMItEhAGnaSEXRBjtzh9fOtsE6E1G6p7vl7edEeZe11QHf18+6+9gR5PbKV/sGKNaD8YaMeA== - dependencies: - follow-redirects "^1.14.8" - axios@^0.27.2: version "0.27.2" resolved "https://registry.yarnpkg.com/axios/-/axios-0.27.2.tgz#207658cc8621606e586c85db4b41a750e756d972" @@ -8818,7 +8811,7 @@ axios@^0.27.2: follow-redirects "^1.14.9" form-data "^4.0.0" -axios@^1.1.3, axios@^1.2.3, axios@^1.3.4, axios@^1.4.0, axios@^1.5.1, axios@^1.6.7: +axios@^1.1.3, axios@^1.2.3, axios@^1.3.4, axios@^1.4.0, axios@^1.5.1, axios@^1.6.7, axios@^1.6.8: version "1.6.8" resolved "https://registry.yarnpkg.com/axios/-/axios-1.6.8.tgz#66d294951f5d988a00e87a0ffb955316a619ea66" integrity sha512-v/ZHtJDU39mDpyBoFVkETcd/uNdxrWRrg3bKpOKzXFA6Bvqopts6ALSMU3y6ijYxbw2B+wPrIv46egTzJXCLGQ== @@ -13559,7 +13552,7 @@ fn.name@1.x.x: resolved "https://registry.yarnpkg.com/fn.name/-/fn.name-1.1.0.tgz#26cad8017967aea8731bc42961d04a3d5988accc" integrity sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw== -follow-redirects@^1.12.1, follow-redirects@^1.14.0, follow-redirects@^1.14.8, follow-redirects@^1.14.9, follow-redirects@^1.15.0, follow-redirects@^1.15.6: +follow-redirects@^1.12.1, follow-redirects@^1.14.0, follow-redirects@^1.14.9, follow-redirects@^1.15.0, follow-redirects@^1.15.6: version "1.15.6" resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.6.tgz#7f815c0cda4249c74ff09e95ef97c23b5fd0399b" integrity sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA== From 9632e7f4b96dd78af1d616ff67c1c7fc8048479e Mon Sep 17 00:00:00 2001 From: portuu3 <61605646+portuu3@users.noreply.github.com> Date: Tue, 16 Apr 2024 09:44:44 +0200 Subject: [PATCH 62/66] set version of eth-typing before breaking changes (#1869) --- .../sdk/python/human-protocol-sdk/.gitignore | 1 - .../sdk/python/human-protocol-sdk/Pipfile | 1 + .../python/human-protocol-sdk/Pipfile.lock | 2310 +++++++++++++++++ 3 files changed, 2311 insertions(+), 1 deletion(-) create mode 100644 packages/sdk/python/human-protocol-sdk/Pipfile.lock diff --git a/packages/sdk/python/human-protocol-sdk/.gitignore b/packages/sdk/python/human-protocol-sdk/.gitignore index 5bc6187c59..bf05ed1cf4 100644 --- a/packages/sdk/python/human-protocol-sdk/.gitignore +++ b/packages/sdk/python/human-protocol-sdk/.gitignore @@ -3,7 +3,6 @@ __pycache__ .pytest_cache # pipenv -Pipfile.lock pip-log.txt pip-wheel-metadata/ diff --git a/packages/sdk/python/human-protocol-sdk/Pipfile b/packages/sdk/python/human-protocol-sdk/Pipfile index 115e1ff111..c10bd6c056 100644 --- a/packages/sdk/python/human-protocol-sdk/Pipfile +++ b/packages/sdk/python/human-protocol-sdk/Pipfile @@ -22,6 +22,7 @@ validators = "==0.20.0" web3 = "==6.8.*" aiohttp = "<4.0.0" # broken freeze in one of dependencies pgpy = "*" +eth-typing = "==4.1.0" [requires] python_version = "3.10" diff --git a/packages/sdk/python/human-protocol-sdk/Pipfile.lock b/packages/sdk/python/human-protocol-sdk/Pipfile.lock new file mode 100644 index 0000000000..08557dfc5e --- /dev/null +++ b/packages/sdk/python/human-protocol-sdk/Pipfile.lock @@ -0,0 +1,2310 @@ +{ + "_meta": { + "hash": { + "sha256": "4b32dd6a1ab61ee13cf859830c0dfd1f5e4b0231a349498014fd39066c660bb4" + }, + "pipfile-spec": 6, + "requires": { + "python_version": "3.10" + }, + "sources": [ + { + "name": "pypi", + "url": "https://pypi.org/simple", + "verify_ssl": true + } + ] + }, + "default": { + "aiohttp": { + "hashes": [ + "sha256:0593822dcdb9483d41f12041ff7c90d4d1033ec0e880bcfaf102919b715f47f1", + "sha256:10afd99b8251022ddf81eaed1d90f5a988e349ee7d779eb429fb07b670751e8c", + "sha256:17e7c051f53a0d2ebf33013a9cbf020bb4e098c4bc5bce6f7b0c962108d97eab", + "sha256:221204dbda5ef350e8db6287937621cf75e85778b296c9c52260b522231940ed", + "sha256:2506d9f7a9b91033201be9ffe7d89c6a54150b0578803cce5cb84a943d075bc3", + "sha256:268ba22d917655d1259af2d5659072b7dc11b4e1dc2cb9662fdd867d75afc6a4", + "sha256:2bd9d334412961125e9f68d5b73c1d0ab9ea3f74a58a475e6b119f5293eee7ba", + "sha256:305edae1dea368ce09bcb858cf5a63a064f3bff4767dec6fa60a0cc0e805a1d3", + "sha256:32dc814ddbb254f6170bca198fe307920f6c1308a5492f049f7f63554b88ef36", + "sha256:35d78076736f4a668d57ade00c65d30a8ce28719d8a42471b2a06ccd1a2e3063", + "sha256:3b2feaf1b7031ede1bc0880cec4b0776fd347259a723d625357bb4b82f62687b", + "sha256:418bb0038dfafeac923823c2e63226179976c76f981a2aaad0ad5d51f2229bca", + "sha256:419f009fa4cfde4d16a7fc070d64f36d70a8d35a90d71aa27670bba2be4fd039", + "sha256:47f6eb74e1ecb5e19a78f4a4228aa24df7fbab3b62d4a625d3f41194a08bd54f", + "sha256:4d79aad0ad4b980663316f26d9a492e8fab2af77c69c0f33780a56843ad2f89e", + "sha256:4f7e69a7fd4b5ce419238388e55abd220336bd32212c673ceabc57ccf3d05b55", + "sha256:52b8b4e06fc15519019e128abedaeb56412b106ab88b3c452188ca47a25c4093", + "sha256:56181093c10dbc6ceb8a29dfeea1e815e1dfdc020169203d87fd8d37616f73f9", + "sha256:63f41a909d182d2b78fe3abef557fcc14da50c7852f70ae3be60e83ff64edba5", + "sha256:689eb4356649ec9535b3686200b231876fb4cab4aca54e3bece71d37f50c1d13", + "sha256:69046cd9a2a17245c4ce3c1f1a4ff8c70c7701ef222fce3d1d8435f09042bba1", + "sha256:69b97aa5792428f321f72aeb2f118e56893371f27e0b7d05750bcad06fc42ca1", + "sha256:69ff36d3f8f5652994e08bd22f093e11cfd0444cea310f92e01b45a4e46b624e", + "sha256:6f121900131d116e4a93b55ab0d12ad72573f967b100e49086e496a9b24523ea", + "sha256:6ff71ede6d9a5a58cfb7b6fffc83ab5d4a63138276c771ac91ceaaddf5459644", + "sha256:71a8f241456b6c2668374d5d28398f8e8cdae4cce568aaea54e0f39359cd928d", + "sha256:74e4e48c8752d14ecfb36d2ebb3d76d614320570e14de0a3aa7a726ff150a03c", + "sha256:7673a76772bda15d0d10d1aa881b7911d0580c980dbd16e59d7ba1422b2d83cd", + "sha256:76d32588ef7e4a3f3adff1956a0ba96faabbdee58f2407c122dd45aa6e34f372", + "sha256:7b39476ee69cfe64061fd77a73bf692c40021f8547cda617a3466530ef63f947", + "sha256:7be99f4abb008cb38e144f85f515598f4c2c8932bf11b65add0ff59c9c876d99", + "sha256:7bfdb41dc6e85d8535b00d73947548a748e9534e8e4fddd2638109ff3fb081df", + "sha256:7d2334e387b2adcc944680bebcf412743f2caf4eeebd550f67249c1c3696be04", + "sha256:7d29dd5319d20aa3b7749719ac9685fbd926f71ac8c77b2477272725f882072d", + "sha256:7d4845f8501ab28ebfdbeab980a50a273b415cf69e96e4e674d43d86a464df9d", + "sha256:824dff4f9f4d0f59d0fa3577932ee9a20e09edec8a2f813e1d6b9f89ced8293f", + "sha256:84e90494db7df3be5e056f91412f9fa9e611fbe8ce4aaef70647297f5943b276", + "sha256:8a78dfb198a328bfb38e4308ca8167028920fb747ddcf086ce706fbdd23b2926", + "sha256:8b73a06bafc8dcc508420db43b4dd5850e41e69de99009d0351c4f3007960019", + "sha256:916b0417aeddf2c8c61291238ce25286f391a6acb6f28005dd9ce282bd6311b6", + "sha256:935c369bf8acc2dc26f6eeb5222768aa7c62917c3554f7215f2ead7386b33748", + "sha256:939393e8c3f0a5bcd33ef7ace67680c318dc2ae406f15e381c0054dd658397de", + "sha256:9860d455847cd98eb67897f5957b7cd69fbcb436dd3f06099230f16a66e66f79", + "sha256:9b6787b6d0b3518b2ee4cbeadd24a507756ee703adbac1ab6dc7c4434b8c572a", + "sha256:9c0b09d76e5a4caac3d27752027fbd43dc987b95f3748fad2b924a03fe8632ad", + "sha256:a1885d2470955f70dfdd33a02e1749613c5a9c5ab855f6db38e0b9389453dce7", + "sha256:a3666cf4182efdb44d73602379a66f5fdfd5da0db5e4520f0ac0dcca644a3497", + "sha256:aba80e77c227f4234aa34a5ff2b6ff30c5d6a827a91d22ff6b999de9175d71bd", + "sha256:b33f34c9c7decdb2ab99c74be6443942b730b56d9c5ee48fb7df2c86492f293c", + "sha256:b65b0f8747b013570eea2f75726046fa54fa8e0c5db60f3b98dd5d161052004a", + "sha256:b71e614c1ae35c3d62a293b19eface83d5e4d194e3eb2fabb10059d33e6e8cbf", + "sha256:c111b3c69060d2bafc446917534150fd049e7aedd6cbf21ba526a5a97b4402a5", + "sha256:c3770365675f6be220032f6609a8fbad994d6dcf3ef7dbcf295c7ee70884c9af", + "sha256:c4870cb049f10d7680c239b55428916d84158798eb8f353e74fa2c98980dcc0b", + "sha256:c5ff8ff44825736a4065d8544b43b43ee4c6dd1530f3a08e6c0578a813b0aa35", + "sha256:c78700130ce2dcebb1a8103202ae795be2fa8c9351d0dd22338fe3dac74847d9", + "sha256:c7a5b676d3c65e88b3aca41816bf72831898fcd73f0cbb2680e9d88e819d1e4d", + "sha256:c8b04a3dbd54de6ccb7604242fe3ad67f2f3ca558f2d33fe19d4b08d90701a89", + "sha256:d12a244627eba4e9dc52cbf924edef905ddd6cafc6513849b4876076a6f38b0e", + "sha256:d1df528a85fb404899d4207a8d9934cfd6be626e30e5d3a5544a83dbae6d8a7e", + "sha256:d58a54d6ff08d2547656356eea8572b224e6f9bbc0cf55fa9966bcaac4ddfb10", + "sha256:d6577140cd7db19e430661e4b2653680194ea8c22c994bc65b7a19d8ec834403", + "sha256:d6a67e26daa686a6fbdb600a9af8619c80a332556245fa8e86c747d226ab1a1e", + "sha256:dcad56c8d8348e7e468899d2fb3b309b9bc59d94e6db08710555f7436156097f", + "sha256:e0198ea897680e480845ec0ffc5a14e8b694e25b3f104f63676d55bf76a82f1a", + "sha256:e27d3b5ed2c2013bce66ad67ee57cbf614288bda8cdf426c8d8fe548316f1b5f", + "sha256:e40d2cd22914d67c84824045861a5bb0fb46586b15dfe4f046c7495bf08306b2", + "sha256:e4370dda04dc8951012f30e1ce7956a0a226ac0714a7b6c389fb2f43f22a250e", + "sha256:e571fdd9efd65e86c6af2f332e0e95dad259bfe6beb5d15b3c3eca3a6eb5d87b", + "sha256:e78da6b55275987cbc89141a1d8e75f5070e577c482dd48bd9123a76a96f0bbb", + "sha256:eae569fb1e7559d4f3919965617bb39f9e753967fae55ce13454bec2d1c54f09", + "sha256:eb30c4510a691bb87081192a394fb661860e75ca3896c01c6d186febe7c88530", + "sha256:efbdd51872cf170093998c87ccdf3cb5993add3559341a8e5708bcb311934c94", + "sha256:f3460a92638dce7e47062cf088d6e7663adb135e936cb117be88d5e6c48c9d53", + "sha256:f595db1bceabd71c82e92df212dd9525a8a2c6947d39e3c994c4f27d2fe15b11", + "sha256:fb68dc73bc8ac322d2e392a59a9e396c4f35cb6fdbdd749e139d1d6c985f2527" + ], + "index": "pypi", + "markers": "python_version >= '3.8'", + "version": "==3.9.4" + }, + "aiosignal": { + "hashes": [ + "sha256:54cd96e15e1649b75d6c87526a6ff0b6c1b0dd3459f43d9ca11d48c339b68cfc", + "sha256:f8376fb07dd1e86a584e4fcdec80b36b7f81aac666ebc724e2c090300dd83b17" + ], + "markers": "python_version >= '3.7'", + "version": "==1.3.1" + }, + "annotated-types": { + "hashes": [ + "sha256:0641064de18ba7a25dee8f96403ebc39113d0cb953a01429249d5c7564666a43", + "sha256:563339e807e53ffd9c267e99fc6d9ea23eb8443c08f112651963e24e22f84a5d" + ], + "markers": "python_version >= '3.8'", + "version": "==0.6.0" + }, + "argon2-cffi": { + "hashes": [ + "sha256:879c3e79a2729ce768ebb7d36d4609e3a78a4ca2ec3a9f12286ca057e3d0db08", + "sha256:c670642b78ba29641818ab2e68bd4e6a78ba53b7eff7b4c3815ae16abf91c7ea" + ], + "markers": "python_version >= '3.7'", + "version": "==23.1.0" + }, + "argon2-cffi-bindings": { + "hashes": [ + "sha256:20ef543a89dee4db46a1a6e206cd015360e5a75822f76df533845c3cbaf72670", + "sha256:2c3e3cc67fdb7d82c4718f19b4e7a87123caf8a93fde7e23cf66ac0337d3cb3f", + "sha256:3b9ef65804859d335dc6b31582cad2c5166f0c3e7975f324d9ffaa34ee7e6583", + "sha256:3e385d1c39c520c08b53d63300c3ecc28622f076f4c2b0e6d7e796e9f6502194", + "sha256:58ed19212051f49a523abb1dbe954337dc82d947fb6e5a0da60f7c8471a8476c", + "sha256:5e00316dabdaea0b2dd82d141cc66889ced0cdcbfa599e8b471cf22c620c329a", + "sha256:603ca0aba86b1349b147cab91ae970c63118a0f30444d4bc80355937c950c082", + "sha256:6a22ad9800121b71099d0fb0a65323810a15f2e292f2ba450810a7316e128ee5", + "sha256:8cd69c07dd875537a824deec19f978e0f2078fdda07fd5c42ac29668dda5f40f", + "sha256:93f9bf70084f97245ba10ee36575f0c3f1e7d7724d67d8e5b08e61787c320ed7", + "sha256:9524464572e12979364b7d600abf96181d3541da11e23ddf565a32e70bd4dc0d", + "sha256:b2ef1c30440dbbcba7a5dc3e319408b59676e2e039e2ae11a8775ecf482b192f", + "sha256:b746dba803a79238e925d9046a63aa26bf86ab2a2fe74ce6b009a1c3f5c8f2ae", + "sha256:bb89ceffa6c791807d1305ceb77dbfacc5aa499891d2c55661c6459651fc39e3", + "sha256:bd46088725ef7f58b5a1ef7ca06647ebaf0eb4baff7d1d0d177c6cc8744abd86", + "sha256:ccb949252cb2ab3a08c02024acb77cfb179492d5701c7cbdbfd776124d4d2367", + "sha256:d4966ef5848d820776f5f562a7d45fdd70c2f330c961d0d745b784034bd9f48d", + "sha256:e415e3f62c8d124ee16018e491a009937f8cf7ebf5eb430ffc5de21b900dad93", + "sha256:ed2937d286e2ad0cc79a7087d3c272832865f779430e0cc2b4f3718d3159b0cb", + "sha256:f1152ac548bd5b8bcecfb0b0371f082037e47128653df2e8ba6e914d384f3c3e", + "sha256:f9f8b450ed0547e3d473fdc8612083fd08dd2120d6ac8f73828df9b7d45bb351" + ], + "markers": "python_version >= '3.6'", + "version": "==21.2.0" + }, + "async-timeout": { + "hashes": [ + "sha256:4640d96be84d82d02ed59ea2b7105a0f7b33abe8703703cd0ab0bf87c427522f", + "sha256:7405140ff1230c310e51dc27b3145b9092d659ce68ff733fb0cefe3ee42be028" + ], + "markers": "python_version < '3.11'", + "version": "==4.0.3" + }, + "attrs": { + "hashes": [ + "sha256:935dc3b529c262f6cf76e50877d35a4bd3c1de194fd41f47a2b7ae8f19971f30", + "sha256:99b87a485a5820b23b879f04c2305b44b951b502fd64be915879d77a7e8fc6f1" + ], + "markers": "python_version >= '3.7'", + "version": "==23.2.0" + }, + "bitarray": { + "hashes": [ + "sha256:03adaacb79e2fb8f483ab3a67665eec53bb3fd0cd5dbd7358741aef124688db3", + "sha256:052c5073bdcaa9dd10628d99d37a2f33ec09364b86dd1f6281e2d9f8d3db3060", + "sha256:0a99b23ac845a9ea3157782c97465e6ae026fe0c7c4c1ed1d88f759fd6ea52d9", + "sha256:0b3543c8a1cb286ad105f11c25d8d0f712f41c5c55f90be39f0e5a1376c7d0b0", + "sha256:128cc3488176145b9b137fdcf54c1c201809bbb8dd30b260ee40afe915843b43", + "sha256:1bb33673e7f7190a65f0a940c1ef63266abdb391f4a3e544a47542d40a81f536", + "sha256:1e0b63a565e8a311cc8348ff1262d5784df0f79d64031d546411afd5dd7ef67d", + "sha256:1e497c535f2a9b68c69d36631bf2dba243e05eb343b00b9c7bbdc8c601c6802d", + "sha256:1ff9e38356cc803e06134cf8ae9758e836ccd1b793135ef3db53c7c5d71e93bc", + "sha256:21f21e7f56206be346bdbda2a6bdb2165a5e6a11821f88fd4911c5a6bbbdc7e2", + "sha256:2c6be1b651fad8f3adb7a5aa12c65b612cd9b89530969af941844ae680f7d981", + "sha256:2f32948c86e0d230a296686db28191b67ed229756f84728847daa0c7ab7406e3", + "sha256:321841cdad1dd0f58fe62e80e9c9c7531f8ebf8be93f047401e930dc47425b1e", + "sha256:345c76b349ff145549652436235c5532e5bfe9db690db6f0a6ad301c62b9ef21", + "sha256:393cb27fd859af5fd9c16eb26b1c59b17b390ff66b3ae5d0dd258270191baf13", + "sha256:3c4344e96642e2211fb3a50558feff682c31563a4c64529a931769d40832ca79", + "sha256:3fa909cfd675004aed8b4cc9df352415933656e0155a6209d878b7cb615c787e", + "sha256:405b83bed28efaae6d86b6ab287c75712ead0adbfab2a1075a1b7ab47dad4d62", + "sha256:43847799461d8ba71deb4d97b47250c2c2fb66d82cd3cb8b4caf52bb97c03034", + "sha256:461a3dafb9d5fda0bb3385dc507d78b1984b49da3fe4c6d56c869a54373b7008", + "sha256:48a30d718d1a6dfc22a49547450107abe8f4afdf2abdcbe76eb9ed88edc49498", + "sha256:4a22266fb416a3b6c258bf7f83c9fe531ba0b755a56986a81ad69dc0f3bcc070", + "sha256:4b558ce85579b51a2e38703877d1e93b7728a7af664dd45a34e833534f0b755d", + "sha256:4d0e32530f941c41eddfc77600ec89b65184cb909c549336463a738fab3ed285", + "sha256:4da73ebd537d75fa7bccfc2228fcaedea0803f21dd9d0bf0d3b67fef3c4af294", + "sha256:4e2936f090bf3f4d1771f44f9077ebccdbc0415d2b598d51a969afcb519df505", + "sha256:508069a04f658210fdeee85a7a0ca84db4bcc110cbb1d21f692caa13210f24a7", + "sha256:5361413fd2ecfdf44dc8f065177dc6aba97fa80a91b815586cb388763acf7f8d", + "sha256:54e16e32e60973bb83c315de9975bc1bcfc9bd50bb13001c31da159bc49b0ca1", + "sha256:5b7b09489b71f9f1f64c0fa0977e250ec24500767dab7383ba9912495849cadf", + "sha256:5cb378eaa65cd43098f11ff5d27e48ee3b956d2c00d2d6b5bfc2a09fe183be47", + "sha256:5d6fb422772e75385b76ad1c52f45a68bd4efafd8be8d0061c11877be74c4d43", + "sha256:5f4dd3af86dd8a617eb6464622fb64ca86e61ce99b59b5c35d8cd33f9c30603d", + "sha256:603e7d640e54ad764d2b4da6b61e126259af84f253a20f512dd10689566e5478", + "sha256:6067f2f07a7121749858c7daa93c8774325c91590b3e81a299621e347740c2ae", + "sha256:60df43e868a615c7e15117a1e1c2e5e11f48f6457280eba6ddf8fbefbec7da99", + "sha256:64115ccabbdbe279c24c367b629c6b1d3da9ed36c7420129e27c338a3971bfee", + "sha256:6465de861aff7a2559f226b37982007417eab8c3557543879987f58b453519bd", + "sha256:648d2f2685590b0103c67a937c2fb9e09bcc8dfb166f0c7c77bd341902a6f5b3", + "sha256:64b433e26993127732ac7b66a7821b2537c3044355798de7c5fcb0af34b8296f", + "sha256:677e67f50e2559efc677a4366707070933ad5418b8347a603a49a070890b19bc", + "sha256:6ab0f1dbfe5070db98771a56aa14797595acd45a1af9eadfb193851a270e7996", + "sha256:6d70b1579da7fb71be5a841a1f965d19aca0ef27f629cfc07d06b09aafd0a333", + "sha256:6ec84668dd7b937874a2b2c293cd14ba84f37be0d196dead852e0ada9815d807", + "sha256:6f71d92f533770fb027388b35b6e11988ab89242b883f48a6fe7202d238c61f8", + "sha256:76b76a07d4ee611405045c6950a1e24c4362b6b44808d4ad6eea75e0dbc59af4", + "sha256:79a9b8b05f2876c7195a2b698c47528e86a73c61ea203394ff8e7a4434bda5c8", + "sha256:7c1f4bf6ea8eb9d7f30808c2e9894237a96650adfecbf5f3643862dc5982f89e", + "sha256:7dfefdcb0dc6a3ba9936063cec65a74595571b375beabe18742b3d91d087eefd", + "sha256:7e913098de169c7fc890638ce5e171387363eb812579e637c44261460ac00aa2", + "sha256:7eb8be687c50da0b397d5e0ab7ca200b5ebb639e79a9f5e285851d1944c94be9", + "sha256:7eea9318293bc0ea6447e9ebfba600a62f3428bea7e9c6d42170ae4f481dbab3", + "sha256:852e202875dd6dfd6139ce7ec4e98dac2b17d8d25934dc99900831e81c3adaef", + "sha256:856bbe1616425f71c0df5ef2e8755e878d9504d5a531acba58ab4273c52c117a", + "sha256:87580c7f7d14f7ec401eda7adac1e2a25e95153e9c339872c8ae61b3208819a1", + "sha256:87abb7f80c0a042f3fe8e5264da1a2756267450bb602110d5327b8eaff7682e7", + "sha256:90e3a281ffe3897991091b7c46fca38c2675bfd4399ffe79dfeded6c52715436", + "sha256:917905de565d9576eb20f53c797c15ba88b9f4f19728acabec8d01eee1d3756a", + "sha256:9521f49ae121a17c0a41e5112249e6fa7f6a571245b1118de81fb86e7c1bc1ce", + "sha256:962892646599529917ef26266091e4cb3077c88b93c3833a909d68dcc971c4e3", + "sha256:9ae5b0657380d2581e13e46864d147a52c1e2bbac9f59b59c576e42fa7d10cf0", + "sha256:9bbcfc7c279e8d74b076e514e669b683f77b4a2a328585b3f16d4c5259c91222", + "sha256:a035da89c959d98afc813e3c62f052690d67cfd55a36592f25d734b70de7d4b0", + "sha256:a09c4f81635408e3387348f415521d4b94198c562c23330f560596a6aaa26eaf", + "sha256:a23397da092ef0a8cfe729571da64c2fc30ac18243caa82ac7c4f965087506ff", + "sha256:a484061616fb4b158b80789bd3cb511f399d2116525a8b29b6334c68abc2310f", + "sha256:a5cc9381fd54f3c23ae1039f977bfd6d041a5c3c1518104f616643c3a5a73b15", + "sha256:a620d8ce4ea2f1c73c6b6b1399e14cb68c6915e2be3fad5808c2998ed55b4acf", + "sha256:a6cc6545d6d76542aee3d18c1c9485fb7b9812b8df4ebe52c4535ec42081b48f", + "sha256:a8873089be2aa15494c0f81af1209f6e1237d762c5065bc4766c1b84321e1b50", + "sha256:a8f286a51a32323715d77755ed959f94bef13972e9a2fe71b609e40e6d27957e", + "sha256:aeb60962ec4813c539a59fbd4f383509c7222b62c3fb1faa76b54943a613e33a", + "sha256:b069ca9bf728e0c5c5b60e00a89df9af34cc170c695c3bfa3b372d8f40288efb", + "sha256:b0ef2d0a6f1502d38d911d25609b44c6cc27bee0a4363dd295df78b075041b60", + "sha256:b306c4cf66912511422060f7f5e1149c8bdb404f8e00e600561b0749fdd45659", + "sha256:b35bfcb08b7693ab4bf9059111a6e9f14e07d57ac93cd967c420db58ab9b71e1", + "sha256:b44105792fbdcfbda3e26ee88786790fda409da4c71f6c2b73888108cf8f062f", + "sha256:b76ffec27c7450b8a334f967366a9ebadaea66ee43f5b530c12861b1a991f503", + "sha256:ba0734aa300757c924f3faf8148e1b8c247176a0ac8e16aefdf9c1eb19e868f7", + "sha256:bb198c6ed1edbcdaf3d1fa3c9c9d1cdb7e179a5134ef5ee660b53cdec43b34e7", + "sha256:bb6b86cfdfc503e92cb71c68766a24565359136961642504a7cc9faf936d9c88", + "sha256:be94e5a685e60f9d24532af8fe5c268002e9016fa80272a94727f435de3d1003", + "sha256:bed637b674db5e6c8a97a4a321e3e4d73e72d50b5c6b29950008a93069cc64cd", + "sha256:c5b399ae6ab975257ec359f03b48fc00b1c1cd109471e41903548469b8feae5c", + "sha256:c71d1cabdeee0cdda4669168618f0e46b7dace207b29da7b63aaa1adc2b54081", + "sha256:c7d16beeaaab15b075990cd26963d6b5b22e8c5becd131781514a00b8bdd04bd", + "sha256:c8919fdbd3bb596b104388b56ae4b266eb28da1f2f7dff2e1f9334a21840fe96", + "sha256:c9b87baa7bfff9a5878fcc1bffe49ecde6e647a72a64b39a69cd8a2992a43a34", + "sha256:cd56b8ae87ebc71bcacbd73615098e8a8de952ecbb5785b6b4e2b07da8a06e1f", + "sha256:cd926e8ae4d1ed1ac4a8f37212a62886292f692bc1739fde98013bf210c2d175", + "sha256:cf0620da2b81946d28c0b16f3e3704d38e9837d85ee4f0652816e2609aaa4fed", + "sha256:d14c790b91f6cbcd9b718f88ed737c78939980c69ac8c7f03dd7e60040c12951", + "sha256:d4bba8042ea6ab331ade91bc435d81ad72fddb098e49108610b0ce7780c14e68", + "sha256:d527172919cdea1e13994a66d9708a80c3d33dedcf2f0548e4925e600fef3a3a", + "sha256:d656ad38c942e38a470ddbce26b5020e08e1a7ea86b8fd413bb9024b5189993a", + "sha256:d6fe315355cdfe3ed22ef355b8bdc81a805ca4d0949d921576560e5b227a1112", + "sha256:d91406f413ccbf4af6ab5ae7bc78f772a95609f9ddd14123db36ef8c37116d95", + "sha256:dac2399ee2889fbdd3472bfc2ede74c34cceb1ccf29a339964281a16eb1d3188", + "sha256:dbaf2bb71d6027152d603f1d5f31e0dfd5e50173d06f877bec484e5396d4594b", + "sha256:e064caa55a6ed493aca1eda06f8b3f689778bc780a75e6ad7724642ba5dc62f7", + "sha256:e40b3cb9fa1edb4e0175d7c06345c49c7925fe93e39ef55ecb0bc40c906b0c09", + "sha256:e49066d251dbbe4e6e3a5c3937d85b589e40e2669ad0eef41a00f82ec17d844b", + "sha256:e6ec283d4741befb86e8c3ea2e9ac1d17416c956d392107e45263e736954b1f7", + "sha256:e788608ed7767b7b3bbde6d49058bccdf94df0de9ca75d13aa99020cc7e68095", + "sha256:e8a9475d415ef1eaae7942df6f780fa4dcd48fce32825eda591a17abba869299", + "sha256:e8da5355d7d75a52df5b84750989e34e39919ec7e59fafc4c104cc1607ab2d31", + "sha256:ea1923d2e7880f9e1959e035da661767b5a2e16a45dfd57d6aa831e8b65ee1bf", + "sha256:ea816dc8f8e65841a8bbdd30e921edffeeb6f76efe6a1eb0da147b60d539d1cf", + "sha256:eb7a9d8a2e400a1026de341ad48e21670a6261a75b06df162c5c39b0d0e7c8f4", + "sha256:eceb551dfeaf19c609003a69a0cf8264b0efd7abc3791a11dfabf4788daf0d19", + "sha256:ed0f7982f10581bb16553719e5e8f933e003f5b22f7d25a68bdb30fac630a6ff", + "sha256:f00079f8e69d75c2a417de7961a77612bb77ef46c09bc74607d86de4740771ef", + "sha256:f0b84fc50b6dbeced4fa390688c07c10a73222810fb0e08392bd1a1b8259de36", + "sha256:f135e804986b12bf14f2cd1eb86674c47dea86c4c5f0fa13c88978876b97ebe6", + "sha256:f2de9a31c34e543ae089fd2a5ced01292f725190e379921384f695e2d7184bd3", + "sha256:f2f8692f95c9e377eb19ca519d30d1f884b02feb7e115f798de47570a359e43f", + "sha256:f4dcadb7b8034aa3491ee8f5a69b3d9ba9d7d1e55c3cc1fc45be313e708277f8", + "sha256:f4f44381b0a4bdf64416082f4f0e7140377ae962c0ced6f983c6d7bbfc034040", + "sha256:f708e91fdbe443f3bec2df394ed42328fb9b0446dff5cb4199023ac6499e09fd", + "sha256:f9346e98fc2abcef90b942973087e2462af6d3e3710e82938078d3493f7fef52", + "sha256:fc6d3e80dd8239850f2604833ff3168b28909c8a9357abfed95632cccd17e3e7", + "sha256:fe71fd4b76380c2772f96f1e53a524da7063645d647a4fcd3b651bdd80ca0f2e" + ], + "version": "==2.9.2" + }, + "certifi": { + "hashes": [ + "sha256:0569859f95fc761b18b45ef421b1290a0f65f147e92a1e5eb3e635f9a5e4e66f", + "sha256:dc383c07b76109f368f6106eee2b593b04a011ea4d55f652c6ca24a754d1cdd1" + ], + "markers": "python_version >= '3.6'", + "version": "==2024.2.2" + }, + "cffi": { + "hashes": [ + "sha256:0c9ef6ff37e974b73c25eecc13952c55bceed9112be2d9d938ded8e856138bcc", + "sha256:131fd094d1065b19540c3d72594260f118b231090295d8c34e19a7bbcf2e860a", + "sha256:1b8ebc27c014c59692bb2664c7d13ce7a6e9a629be20e54e7271fa696ff2b417", + "sha256:2c56b361916f390cd758a57f2e16233eb4f64bcbeee88a4881ea90fca14dc6ab", + "sha256:2d92b25dbf6cae33f65005baf472d2c245c050b1ce709cc4588cdcdd5495b520", + "sha256:31d13b0f99e0836b7ff893d37af07366ebc90b678b6664c955b54561fc36ef36", + "sha256:32c68ef735dbe5857c810328cb2481e24722a59a2003018885514d4c09af9743", + "sha256:3686dffb02459559c74dd3d81748269ffb0eb027c39a6fc99502de37d501faa8", + "sha256:582215a0e9adbe0e379761260553ba11c58943e4bbe9c36430c4ca6ac74b15ed", + "sha256:5b50bf3f55561dac5438f8e70bfcdfd74543fd60df5fa5f62d94e5867deca684", + "sha256:5bf44d66cdf9e893637896c7faa22298baebcd18d1ddb6d2626a6e39793a1d56", + "sha256:6602bc8dc6f3a9e02b6c22c4fc1e47aa50f8f8e6d3f78a5e16ac33ef5fefa324", + "sha256:673739cb539f8cdaa07d92d02efa93c9ccf87e345b9a0b556e3ecc666718468d", + "sha256:68678abf380b42ce21a5f2abde8efee05c114c2fdb2e9eef2efdb0257fba1235", + "sha256:68e7c44931cc171c54ccb702482e9fc723192e88d25a0e133edd7aff8fcd1f6e", + "sha256:6b3d6606d369fc1da4fd8c357d026317fbb9c9b75d36dc16e90e84c26854b088", + "sha256:748dcd1e3d3d7cd5443ef03ce8685043294ad6bd7c02a38d1bd367cfd968e000", + "sha256:7651c50c8c5ef7bdb41108b7b8c5a83013bfaa8a935590c5d74627c047a583c7", + "sha256:7b78010e7b97fef4bee1e896df8a4bbb6712b7f05b7ef630f9d1da00f6444d2e", + "sha256:7e61e3e4fa664a8588aa25c883eab612a188c725755afff6289454d6362b9673", + "sha256:80876338e19c951fdfed6198e70bc88f1c9758b94578d5a7c4c91a87af3cf31c", + "sha256:8895613bcc094d4a1b2dbe179d88d7fb4a15cee43c052e8885783fac397d91fe", + "sha256:88e2b3c14bdb32e440be531ade29d3c50a1a59cd4e51b1dd8b0865c54ea5d2e2", + "sha256:8f8e709127c6c77446a8c0a8c8bf3c8ee706a06cd44b1e827c3e6a2ee6b8c098", + "sha256:9cb4a35b3642fc5c005a6755a5d17c6c8b6bcb6981baf81cea8bfbc8903e8ba8", + "sha256:9f90389693731ff1f659e55c7d1640e2ec43ff725cc61b04b2f9c6d8d017df6a", + "sha256:a09582f178759ee8128d9270cd1344154fd473bb77d94ce0aeb2a93ebf0feaf0", + "sha256:a6a14b17d7e17fa0d207ac08642c8820f84f25ce17a442fd15e27ea18d67c59b", + "sha256:a72e8961a86d19bdb45851d8f1f08b041ea37d2bd8d4fd19903bc3083d80c896", + "sha256:abd808f9c129ba2beda4cfc53bde801e5bcf9d6e0f22f095e45327c038bfe68e", + "sha256:ac0f5edd2360eea2f1daa9e26a41db02dd4b0451b48f7c318e217ee092a213e9", + "sha256:b29ebffcf550f9da55bec9e02ad430c992a87e5f512cd63388abb76f1036d8d2", + "sha256:b2ca4e77f9f47c55c194982e10f058db063937845bb2b7a86c84a6cfe0aefa8b", + "sha256:b7be2d771cdba2942e13215c4e340bfd76398e9227ad10402a8767ab1865d2e6", + "sha256:b84834d0cf97e7d27dd5b7f3aca7b6e9263c56308ab9dc8aae9784abb774d404", + "sha256:b86851a328eedc692acf81fb05444bdf1891747c25af7529e39ddafaf68a4f3f", + "sha256:bcb3ef43e58665bbda2fb198698fcae6776483e0c4a631aa5647806c25e02cc0", + "sha256:c0f31130ebc2d37cdd8e44605fb5fa7ad59049298b3f745c74fa74c62fbfcfc4", + "sha256:c6a164aa47843fb1b01e941d385aab7215563bb8816d80ff3a363a9f8448a8dc", + "sha256:d8a9d3ebe49f084ad71f9269834ceccbf398253c9fac910c4fd7053ff1386936", + "sha256:db8e577c19c0fda0beb7e0d4e09e0ba74b1e4c092e0e40bfa12fe05b6f6d75ba", + "sha256:dc9b18bf40cc75f66f40a7379f6a9513244fe33c0e8aa72e2d56b0196a7ef872", + "sha256:e09f3ff613345df5e8c3667da1d918f9149bd623cd9070c983c013792a9a62eb", + "sha256:e4108df7fe9b707191e55f33efbcb2d81928e10cea45527879a4749cbe472614", + "sha256:e6024675e67af929088fda399b2094574609396b1decb609c55fa58b028a32a1", + "sha256:e70f54f1796669ef691ca07d046cd81a29cb4deb1e5f942003f401c0c4a2695d", + "sha256:e715596e683d2ce000574bae5d07bd522c781a822866c20495e52520564f0969", + "sha256:e760191dd42581e023a68b758769e2da259b5d52e3103c6060ddc02c9edb8d7b", + "sha256:ed86a35631f7bfbb28e108dd96773b9d5a6ce4811cf6ea468bb6a359b256b1e4", + "sha256:ee07e47c12890ef248766a6e55bd38ebfb2bb8edd4142d56db91b21ea68b7627", + "sha256:fa3a0128b152627161ce47201262d3140edb5a5c3da88d73a1b790a959126956", + "sha256:fcc8eb6d5902bb1cf6dc4f187ee3ea80a1eba0a89aba40a5cb20a5087d961357" + ], + "markers": "platform_python_implementation != 'PyPy'", + "version": "==1.16.0" + }, + "charset-normalizer": { + "hashes": [ + "sha256:06435b539f889b1f6f4ac1758871aae42dc3a8c0e24ac9e60c2384973ad73027", + "sha256:06a81e93cd441c56a9b65d8e1d043daeb97a3d0856d177d5c90ba85acb3db087", + "sha256:0a55554a2fa0d408816b3b5cedf0045f4b8e1a6065aec45849de2d6f3f8e9786", + "sha256:0b2b64d2bb6d3fb9112bafa732def486049e63de9618b5843bcdd081d8144cd8", + "sha256:10955842570876604d404661fbccbc9c7e684caf432c09c715ec38fbae45ae09", + "sha256:122c7fa62b130ed55f8f285bfd56d5f4b4a5b503609d181f9ad85e55c89f4185", + "sha256:1ceae2f17a9c33cb48e3263960dc5fc8005351ee19db217e9b1bb15d28c02574", + "sha256:1d3193f4a680c64b4b6a9115943538edb896edc190f0b222e73761716519268e", + "sha256:1f79682fbe303db92bc2b1136016a38a42e835d932bab5b3b1bfcfbf0640e519", + "sha256:2127566c664442652f024c837091890cb1942c30937add288223dc895793f898", + "sha256:22afcb9f253dac0696b5a4be4a1c0f8762f8239e21b99680099abd9b2b1b2269", + "sha256:25baf083bf6f6b341f4121c2f3c548875ee6f5339300e08be3f2b2ba1721cdd3", + "sha256:2e81c7b9c8979ce92ed306c249d46894776a909505d8f5a4ba55b14206e3222f", + "sha256:3287761bc4ee9e33561a7e058c72ac0938c4f57fe49a09eae428fd88aafe7bb6", + "sha256:34d1c8da1e78d2e001f363791c98a272bb734000fcef47a491c1e3b0505657a8", + "sha256:37e55c8e51c236f95b033f6fb391d7d7970ba5fe7ff453dad675e88cf303377a", + "sha256:3d47fa203a7bd9c5b6cee4736ee84ca03b8ef23193c0d1ca99b5089f72645c73", + "sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc", + "sha256:42cb296636fcc8b0644486d15c12376cb9fa75443e00fb25de0b8602e64c1714", + "sha256:45485e01ff4d3630ec0d9617310448a8702f70e9c01906b0d0118bdf9d124cf2", + "sha256:4a78b2b446bd7c934f5dcedc588903fb2f5eec172f3d29e52a9096a43722adfc", + "sha256:4ab2fe47fae9e0f9dee8c04187ce5d09f48eabe611be8259444906793ab7cbce", + "sha256:4d0d1650369165a14e14e1e47b372cfcb31d6ab44e6e33cb2d4e57265290044d", + "sha256:549a3a73da901d5bc3ce8d24e0600d1fa85524c10287f6004fbab87672bf3e1e", + "sha256:55086ee1064215781fff39a1af09518bc9255b50d6333f2e4c74ca09fac6a8f6", + "sha256:572c3763a264ba47b3cf708a44ce965d98555f618ca42c926a9c1616d8f34269", + "sha256:573f6eac48f4769d667c4442081b1794f52919e7edada77495aaed9236d13a96", + "sha256:5b4c145409bef602a690e7cfad0a15a55c13320ff7a3ad7ca59c13bb8ba4d45d", + "sha256:6463effa3186ea09411d50efc7d85360b38d5f09b870c48e4600f63af490e56a", + "sha256:65f6f63034100ead094b8744b3b97965785388f308a64cf8d7c34f2f2e5be0c4", + "sha256:663946639d296df6a2bb2aa51b60a2454ca1cb29835324c640dafb5ff2131a77", + "sha256:6897af51655e3691ff853668779c7bad41579facacf5fd7253b0133308cf000d", + "sha256:68d1f8a9e9e37c1223b656399be5d6b448dea850bed7d0f87a8311f1ff3dabb0", + "sha256:6ac7ffc7ad6d040517be39eb591cac5ff87416c2537df6ba3cba3bae290c0fed", + "sha256:6b3251890fff30ee142c44144871185dbe13b11bab478a88887a639655be1068", + "sha256:6c4caeef8fa63d06bd437cd4bdcf3ffefe6738fb1b25951440d80dc7df8c03ac", + "sha256:6ef1d82a3af9d3eecdba2321dc1b3c238245d890843e040e41e470ffa64c3e25", + "sha256:753f10e867343b4511128c6ed8c82f7bec3bd026875576dfd88483c5c73b2fd8", + "sha256:7cd13a2e3ddeed6913a65e66e94b51d80a041145a026c27e6bb76c31a853c6ab", + "sha256:7ed9e526742851e8d5cc9e6cf41427dfc6068d4f5a3bb03659444b4cabf6bc26", + "sha256:7f04c839ed0b6b98b1a7501a002144b76c18fb1c1850c8b98d458ac269e26ed2", + "sha256:802fe99cca7457642125a8a88a084cef28ff0cf9407060f7b93dca5aa25480db", + "sha256:80402cd6ee291dcb72644d6eac93785fe2c8b9cb30893c1af5b8fdd753b9d40f", + "sha256:8465322196c8b4d7ab6d1e049e4c5cb460d0394da4a27d23cc242fbf0034b6b5", + "sha256:86216b5cee4b06df986d214f664305142d9c76df9b6512be2738aa72a2048f99", + "sha256:87d1351268731db79e0f8e745d92493ee2841c974128ef629dc518b937d9194c", + "sha256:8bdb58ff7ba23002a4c5808d608e4e6c687175724f54a5dade5fa8c67b604e4d", + "sha256:8c622a5fe39a48f78944a87d4fb8a53ee07344641b0562c540d840748571b811", + "sha256:8d756e44e94489e49571086ef83b2bb8ce311e730092d2c34ca8f7d925cb20aa", + "sha256:8f4a014bc36d3c57402e2977dada34f9c12300af536839dc38c0beab8878f38a", + "sha256:9063e24fdb1e498ab71cb7419e24622516c4a04476b17a2dab57e8baa30d6e03", + "sha256:90d558489962fd4918143277a773316e56c72da56ec7aa3dc3dbbe20fdfed15b", + "sha256:923c0c831b7cfcb071580d3f46c4baf50f174be571576556269530f4bbd79d04", + "sha256:95f2a5796329323b8f0512e09dbb7a1860c46a39da62ecb2324f116fa8fdc85c", + "sha256:96b02a3dc4381e5494fad39be677abcb5e6634bf7b4fa83a6dd3112607547001", + "sha256:9f96df6923e21816da7e0ad3fd47dd8f94b2a5ce594e00677c0013018b813458", + "sha256:a10af20b82360ab00827f916a6058451b723b4e65030c5a18577c8b2de5b3389", + "sha256:a50aebfa173e157099939b17f18600f72f84eed3049e743b68ad15bd69b6bf99", + "sha256:a981a536974bbc7a512cf44ed14938cf01030a99e9b3a06dd59578882f06f985", + "sha256:a9a8e9031d613fd2009c182b69c7b2c1ef8239a0efb1df3f7c8da66d5dd3d537", + "sha256:ae5f4161f18c61806f411a13b0310bea87f987c7d2ecdbdaad0e94eb2e404238", + "sha256:aed38f6e4fb3f5d6bf81bfa990a07806be9d83cf7bacef998ab1a9bd660a581f", + "sha256:b01b88d45a6fcb69667cd6d2f7a9aeb4bf53760d7fc536bf679ec94fe9f3ff3d", + "sha256:b261ccdec7821281dade748d088bb6e9b69e6d15b30652b74cbbac25e280b796", + "sha256:b2b0a0c0517616b6869869f8c581d4eb2dd83a4d79e0ebcb7d373ef9956aeb0a", + "sha256:b4a23f61ce87adf89be746c8a8974fe1c823c891d8f86eb218bb957c924bb143", + "sha256:bd8f7df7d12c2db9fab40bdd87a7c09b1530128315d047a086fa3ae3435cb3a8", + "sha256:beb58fe5cdb101e3a055192ac291b7a21e3b7ef4f67fa1d74e331a7f2124341c", + "sha256:c002b4ffc0be611f0d9da932eb0f704fe2602a9a949d1f738e4c34c75b0863d5", + "sha256:c083af607d2515612056a31f0a8d9e0fcb5876b7bfc0abad3ecd275bc4ebc2d5", + "sha256:c180f51afb394e165eafe4ac2936a14bee3eb10debc9d9e4db8958fe36afe711", + "sha256:c235ebd9baae02f1b77bcea61bce332cb4331dc3617d254df3323aa01ab47bd4", + "sha256:cd70574b12bb8a4d2aaa0094515df2463cb429d8536cfb6c7ce983246983e5a6", + "sha256:d0eccceffcb53201b5bfebb52600a5fb483a20b61da9dbc885f8b103cbe7598c", + "sha256:d965bba47ddeec8cd560687584e88cf699fd28f192ceb452d1d7ee807c5597b7", + "sha256:db364eca23f876da6f9e16c9da0df51aa4f104a972735574842618b8c6d999d4", + "sha256:ddbb2551d7e0102e7252db79ba445cdab71b26640817ab1e3e3648dad515003b", + "sha256:deb6be0ac38ece9ba87dea880e438f25ca3eddfac8b002a2ec3d9183a454e8ae", + "sha256:e06ed3eb3218bc64786f7db41917d4e686cc4856944f53d5bdf83a6884432e12", + "sha256:e27ad930a842b4c5eb8ac0016b0a54f5aebbe679340c26101df33424142c143c", + "sha256:e537484df0d8f426ce2afb2d0f8e1c3d0b114b83f8850e5f2fbea0e797bd82ae", + "sha256:eb00ed941194665c332bf8e078baf037d6c35d7c4f3102ea2d4f16ca94a26dc8", + "sha256:eb6904c354526e758fda7167b33005998fb68c46fbc10e013ca97f21ca5c8887", + "sha256:eb8821e09e916165e160797a6c17edda0679379a4be5c716c260e836e122f54b", + "sha256:efcb3f6676480691518c177e3b465bcddf57cea040302f9f4e6e191af91174d4", + "sha256:f27273b60488abe721a075bcca6d7f3964f9f6f067c8c4c605743023d7d3944f", + "sha256:f30c3cb33b24454a82faecaf01b19c18562b1e89558fb6c56de4d9118a032fd5", + "sha256:fb69256e180cb6c8a894fee62b3afebae785babc1ee98b81cdf68bbca1987f33", + "sha256:fd1abc0d89e30cc4e02e4064dc67fcc51bd941eb395c502aac3ec19fab46b519", + "sha256:ff8fa367d09b717b2a17a052544193ad76cd49979c805768879cb63d9ca50561" + ], + "markers": "python_full_version >= '3.7.0'", + "version": "==3.3.2" + }, + "ckzg": { + "hashes": [ + "sha256:02a8d97acb5f84cf2c4db0c962ce3aefa2819b10c5b6b9dccf55e83f2a999676", + "sha256:0404db8ded404b36617d60d678d5671652798952571ae4993d4d379ef6563f4f", + "sha256:0c72b07d5cac293d7e49a5510d56163f18cdbf9c7a6c6446422964d5667097c2", + "sha256:1272db9cf5cdd6f564b3de48dae4646d9e04aa10432c0f278ca7c752cf6a333c", + "sha256:1a35e0f027749a131a5086dcb3f094ec424280cdf7708c24e0c45421a0e9bebf", + "sha256:1ae70915d41702d33775d9b81c106b2bff5aa7981f82b06e0c5892daa921ff55", + "sha256:1d1bd47cfa82f92f14ec77fffee6480b03144f414861fc6664190e89d3aa542d", + "sha256:2896c108425b64f6b741cc389beee2b8467a41f8d4f901f4a4ecc037311dc681", + "sha256:2eceae0ef7189d47bd89fd9efd9d8f54c5b06bc92c435ec00c62815363cd9d79", + "sha256:36735543ce3aec4730e7128690265ef90781d28e9b56c039c72b6b2ce9b06839", + "sha256:37192e9fcbced22e64cd00785ea082bd22254ce7d9cfdfd5364683bea8e1d043", + "sha256:3aee88b228a9ca81d677d57d8d3f6ee483165d8b3955ea408bda674d0f9b4ee5", + "sha256:3aefd29f6d339358904ed88e5a642e5bf338fd85151a982a040d4352ae95e53f", + "sha256:3d7f609e943880303ea3f60b0426c9b53a596c74bb09ceed00c917618b519373", + "sha256:409f1f18dbc92df5ddbf1ff0d154dc2280a495ec929a4fa27abc69eeacf31ff0", + "sha256:43935d730a9ee13ca264455356bdd01055c55c241508f5682d67265379b29dcf", + "sha256:481dfd101acc8a473146b35e61c11cee2ef41210b77775f306c4f1f7f8bdbf28", + "sha256:4a5d3367cee7ebb48131acc78ca3fb0565e3af3fd8fa8eb4ca25bb88577692c4", + "sha256:4f552fa3b654bc376fcb73e975d521eacff324dba111fa2f0c80c84ad586a0b1", + "sha256:52bfcad99cc0f5611c3a7e452de4d7fa66ce020327c1c1de425b84b20794687b", + "sha256:52cbe279f5d3ec9dd7745de8e796383ba201302606edaa9838b5dd5a34218241", + "sha256:54808ba5b3692ff31713de6d57c30c21060f11916d2e233f5554fcc85790fcda", + "sha256:55e8c6d8df9dc1bdd3862114e239c292f9bdd92d67055ca4e0e7503826e6524f", + "sha256:56256067d31eb6eed1a42c9f3038936aeb7decee322aa13a3224b51cfa3e8026", + "sha256:5deaae9151215d1fad3934fa423a87ee752345f665448a30f58bf5c3177c4623", + "sha256:63bb5e6bc4822c732270c70ef12522b0215775ff61cae04fb54983973aef32e3", + "sha256:67144d1b545cdd6cb5af38ed2c03b234a24f72b6021ea095b70f0cfe11181bd6", + "sha256:6aff64ce8eae856bb5684c76f8e07d4ac31ff07ad46a24bf62c9ea2104975bc9", + "sha256:6f6fd5bc8c2362483c61adbd00188f7448c968807f00ee067666355c63cf45e0", + "sha256:6f7e8861174fe26e6bb0f13655aa1f07fd7c3300852748b0f6e47b998153b56b", + "sha256:71860eda6019cc57b197037427ad4078466de232a768fa7c77c7094585689a8d", + "sha256:7951c53321136aabdab64dc389c92ffeda5859d59304b97092e893a6b09e9722", + "sha256:7a49bd5dcf288a40df063f7ebd88476fa96a5d22dcbafc843193964993f36e26", + "sha256:7a864097cb88be5b7aeff6103bf03d7dfb1c6dda6c8ef82378838ce32e158a15", + "sha256:7b1eed4e35a3fb35f867770eee12018098bd261fa66b768f75b343e0198ff258", + "sha256:851b7eaca0034b51b6867623b0fae2260466126d8fc669646890464812afd932", + "sha256:87729a2e861093d9ee4667dcf047a0073644da7f9de5b9c269821e3c9c3f7164", + "sha256:88fafab3493a12d5212374889783352bb4b59dddc9e61c86d063358eff6da7bb", + "sha256:8a00c295c5657162c24b162ca9a030fbfbc6930e0782378ce3e3d64b14cf470e", + "sha256:8a02d21ceda0c3bec82342f050de5b22eb4a928be00913fa8992ab0f717095f8", + "sha256:8a09cce801a20929d49337bd0f1df6d079d5a2ebaa58f58ab8649c706485c759", + "sha256:8b5d08189ffda2f869711c4149dc41012f73656bc20606f69b174d15488f6ed1", + "sha256:8d272107d63500ba9c62adef39f01835390ee467c2583fd96c78f05773d87b0d", + "sha256:8f2bbbcd24f5ac7f29a0f3f3f51d8934764f5d579e63601a415ace4dad0c2785", + "sha256:8facda4eafc451bb5f6019a2b779f1b6da7f91322aef0eab1f1d9f542220de1c", + "sha256:91868e2aa17497ea864bb9408269176d961ba56d89543af292556549b18a03b7", + "sha256:91dafec4f72e30176fb9861d0e2ed46cd506f6837ed70066f2136378f5cd84df", + "sha256:9205a6ea38c5e030f6f719b8f8ea6207423378e0339d45db81c946a0818d0f31", + "sha256:96d88c6ea2fd49ecfa16767d05a2d056f1bd1a42b0cf10ab99fb4f88fefab5d7", + "sha256:9936e5adf2030fc2747aaadc0cbfee6b5a06507e2b74e70998ac4e37cd7203a6", + "sha256:9ae6d24e83af8c097b62fdc2183378b9f2d8253fa14ccfc07d075a579f98d876", + "sha256:9d8c45cd427f34682add5715360b358ffc2cbd9533372470eae12cbb74960042", + "sha256:a4644e6e0d66d4a36dc37c2ef64807d1db39bf76b10a933b2f7fbb0b4ee9d991", + "sha256:a5911419a785c732f0f5edcda89ecc489e7880191b8c0147f629025cb910f913", + "sha256:ab02c7ad64fb8616a430b05ad2f8fa4f3fc0a22e3dd4ea7a5d5fa4362534bb21", + "sha256:ad39d0549237d136e32263a71182833e26fab8fe8ab62db4d6161b9a7f74623e", + "sha256:b040396df453b51cd5f1461bec9b942173b95ca181c7f65caa10c0204cb6144a", + "sha256:b1ed54765a3067f20786a0c6ee24a8440cfedfe39c5865744c99f605e6ec4249", + "sha256:bd392f3ae05a851f9aa1fc114b565cb7e6744cec39790af56af2adf9dd400f3d", + "sha256:be79e3c4a735f5bf4c71cc07a89500448555f2d4f4f765da5867194c7e46ec5c", + "sha256:bf751e989a428be569e27010c98192451af4c729d5c27a6e0132647fe93b6e84", + "sha256:c166f254ce3434dd0d56ef64788fc9637d60721f4e7e126b15a847abb9a44962", + "sha256:c37af3d01a4b0c3f0a4f51cd0b85df44e30d3686f90c2a7cc84530e4e9d7a00e", + "sha256:c5101500009a8851843b5aab44bc320b281cfe46ffbbab35f29fa763dc2ac4a2", + "sha256:c921b9172aa155ede173abe9d3495c04a55b1afde317339443451e889b531891", + "sha256:cffc3a23ccc967fd7993a9839aa0c133579bfcfd9f124c1ad8916a21c40ed594", + "sha256:d02164a0d84e55965c14132f6d43cc367be3d12eb318f79ba2f262dac47665c2", + "sha256:d2ca50b9d0e947d3b5530dacf25cc00391d041e861751c4872eba4a4567a2efe", + "sha256:d392ef8281f306a0377f4e5fe816e03e4dce2754a4b2ab209b16d9628b7a0bac", + "sha256:d3b343a4a26d5994bdb39216f5b03bf2345bb6e37ae90fcf7181df37c244217a", + "sha256:d5a464900627b66848f4187dd415bea5edf78f3918927bd27461749e75730459", + "sha256:d5a838b4de4cc0b01a84531a115cf19aa508049c20256e493a2cca98cf806e3e", + "sha256:dc6c211e16ef7750b2579346eaa05e4f1e7f1726effa55c2cb42354603800b01", + "sha256:df63d78d9a3d1ffcf32ccb262512c780de42798543affc1209f6fd0cddac49b4", + "sha256:dfadf8aab3f5a9a94796ba2b688f3679d1d681afe92dfa223da7d4f751fe487d", + "sha256:e189c00a0030d1a593b020264d7f9b30fa0b980d108923f353c565c206a99147", + "sha256:e1d4abc0d58cb04678915ef7c4236834e58774ef692194b9bca15f837a0aaff8", + "sha256:e9321226e65868e66edbe18301b8f76f3298d316e6d3a1261371c7fdbc913816", + "sha256:ef6b4d15188803602afc56e113fc588617219a6316789766fc95e0fa010a93ab", + "sha256:f1495b5bb9016160a71d5f2727b935cb532d5578b7d29b280f0531b50c5ef1ee", + "sha256:f40731759b608d74b240fe776853b7b081100d8fc06ac35e22fd0db760b7bcaa", + "sha256:f8c422673236ea67608c434956181b050039b2f57b1006503eeec574b1af8467", + "sha256:fd3f0db4cf514054c386d1a38f9a144725b5109379dd9d2c1b4b0736119f848e", + "sha256:fef5f276e24f4bdd19e28ddcc5212e9b6c8514d3c7426bd443c9221f348c176f" + ], + "version": "==1.0.0" + }, + "cryptography": { + "hashes": [ + "sha256:0270572b8bd2c833c3981724b8ee9747b3ec96f699a9665470018594301439ee", + "sha256:111a0d8553afcf8eb02a4fea6ca4f59d48ddb34497aa8706a6cf536f1a5ec576", + "sha256:16a48c23a62a2f4a285699dba2e4ff2d1cff3115b9df052cdd976a18856d8e3d", + "sha256:1b95b98b0d2af784078fa69f637135e3c317091b615cd0905f8b8a087e86fa30", + "sha256:1f71c10d1e88467126f0efd484bd44bca5e14c664ec2ede64c32f20875c0d413", + "sha256:2424ff4c4ac7f6b8177b53c17ed5d8fa74ae5955656867f5a8affaca36a27abb", + "sha256:2bce03af1ce5a5567ab89bd90d11e7bbdff56b8af3acbbec1faded8f44cb06da", + "sha256:329906dcc7b20ff3cad13c069a78124ed8247adcac44b10bea1130e36caae0b4", + "sha256:37dd623507659e08be98eec89323469e8c7b4c1407c85112634ae3dbdb926fdd", + "sha256:3eaafe47ec0d0ffcc9349e1708be2aaea4c6dd4978d76bf6eb0cb2c13636c6fc", + "sha256:5e6275c09d2badf57aea3afa80d975444f4be8d3bc58f7f80d2a484c6f9485c8", + "sha256:6fe07eec95dfd477eb9530aef5bead34fec819b3aaf6c5bd6d20565da607bfe1", + "sha256:7367d7b2eca6513681127ebad53b2582911d1736dc2ffc19f2c3ae49997496bc", + "sha256:7cde5f38e614f55e28d831754e8a3bacf9ace5d1566235e39d91b35502d6936e", + "sha256:9481ffe3cf013b71b2428b905c4f7a9a4f76ec03065b05ff499bb5682a8d9ad8", + "sha256:98d8dc6d012b82287f2c3d26ce1d2dd130ec200c8679b6213b3c73c08b2b7940", + "sha256:a011a644f6d7d03736214d38832e030d8268bcff4a41f728e6030325fea3e400", + "sha256:a2913c5375154b6ef2e91c10b5720ea6e21007412f6437504ffea2109b5a33d7", + "sha256:a30596bae9403a342c978fb47d9b0ee277699fa53bbafad14706af51fe543d16", + "sha256:b03c2ae5d2f0fc05f9a2c0c997e1bc18c8229f392234e8a0194f202169ccd278", + "sha256:b6cd2203306b63e41acdf39aa93b86fb566049aeb6dc489b70e34bcd07adca74", + "sha256:b7ffe927ee6531c78f81aa17e684e2ff617daeba7f189f911065b2ea2d526dec", + "sha256:b8cac287fafc4ad485b8a9b67d0ee80c66bf3574f655d3b97ef2e1082360faf1", + "sha256:ba334e6e4b1d92442b75ddacc615c5476d4ad55cc29b15d590cc6b86efa487e2", + "sha256:ba3e4a42397c25b7ff88cdec6e2a16c2be18720f317506ee25210f6d31925f9c", + "sha256:c41fb5e6a5fe9ebcd58ca3abfeb51dffb5d83d6775405305bfa8715b76521922", + "sha256:cd2030f6650c089aeb304cf093f3244d34745ce0cfcc39f20c6fbfe030102e2a", + "sha256:cd65d75953847815962c84a4654a84850b2bb4aed3f26fadcc1c13892e1e29f6", + "sha256:e4985a790f921508f36f81831817cbc03b102d643b5fcb81cd33df3fa291a1a1", + "sha256:e807b3188f9eb0eaa7bbb579b462c5ace579f1cedb28107ce8b48a9f7ad3679e", + "sha256:f12764b8fffc7a123f641d7d049d382b73f96a34117e0b637b80643169cec8ac", + "sha256:f8837fe1d6ac4a8052a9a8ddab256bc006242696f03368a4009be7ee3075cdb7" + ], + "index": "pypi", + "markers": "python_version >= '3.7'", + "version": "==42.0.5" + }, + "cytoolz": { + "hashes": [ + "sha256:01cfb8518828c1189200c02a5010ea404407fb18fd5589e29c126e84bbeadd36", + "sha256:04afa90d9d9d18394c40d9bed48c51433d08b57c042e0e50c8c0f9799735dcbd", + "sha256:08a438701c6141dd34eaf92e9e9a1f66e23a22f7840ef8a371eba274477de85d", + "sha256:0a79d72b08048a0980a59457c239555f111ac0c8bdc140c91a025f124104dbb4", + "sha256:0ba1cbc4d9cd7571c917f88f4a069568e5121646eb5d82b2393b2cf84712cf2a", + "sha256:0cf1e1e96dd86829a0539baf514a9c8473a58fbb415f92401a68e8e52a34ecd5", + "sha256:0d8edfbc694af6c9bda4db56643fb8ed3d14e47bec358c2f1417de9a12d6d1fb", + "sha256:0e9199c9e3fbf380a92b8042c677eb9e7ed4bccb126de5e9c0d26f5888d96788", + "sha256:0fbad1fb9bb47e827d00e01992a099b0ba79facf5e5aa453be066033232ac4b5", + "sha256:131ff4820e5d64a25d7ad3c3556f2d8aa65c66b3f021b03f8a8e98e4180dd808", + "sha256:1651a9bd591a8326329ce1d6336f3129161a36d7061a4d5ea9e5377e033364cf", + "sha256:18cd61e078bd6bffe088e40f1ed02001387c29174750abce79499d26fa57f5eb", + "sha256:1c18e351956f70db9e2d04ff02f28e9a41839250d3f936a4c8a1eabd1c3094d2", + "sha256:1dd70141b32b717696a72b8876e86bc9c6f8eff995c1808e299db3541213ff82", + "sha256:1f501ae1353071fa5d6677437bbeb1aeb5622067dce0977cedc2c5ec5843b202", + "sha256:20d36430d8ac809186736fda735ee7d595b6242bdb35f69b598ef809ebfa5605", + "sha256:27513a5d5b6624372d63313574381d3217a66e7a2626b056c695179623a5cb1a", + "sha256:2905fdccacc64b4beba37f95cab9d792289c80f4d70830b70de2fc66c007ec01", + "sha256:2c6dd75dae3d84fa8988861ab8b1189d2488cb8a9b8653828f9cd6126b5e7abd", + "sha256:33c63186f3bf9d7ef1347bc0537bb9a0b4111a0d7d6e619623cabc18fef0dc3b", + "sha256:37441bf4a2a4e2e0fe9c3b0ea5e72db352f5cca03903977ffc42f6f6c5467be9", + "sha256:3ac4f2fb38bbc67ff1875b7d2f0f162a247f43bd28eb7c9d15e6175a982e558d", + "sha256:4503dc59f4ced53a54643272c61dc305d1dbbfbd7d6bdf296948de9f34c3a282", + "sha256:456395d7aec01db32bf9e6db191d667347c78d8d48e77234521fa1078f60dabb", + "sha256:46f505d4c6eb79585c8ad0b9dc140ef30a138c880e4e3b40230d642690e36366", + "sha256:47feb089506fc66e1593cd9ade3945693a9d089a445fbe9a11385cab200b9f22", + "sha256:4fba0616fcd487e34b8beec1ad9911d192c62e758baa12fcb44448b9b6feae22", + "sha256:534fa66db8564d9b13872d81d54b6b09ae592c585eb826aac235bd6f1830f8ad", + "sha256:55f9bd1ae6c2a27eda5abe2a0b65a83029d2385c5a1da7b8ef47af5905d7e905", + "sha256:56f899758146a52e2f8cfb3fb6f4ca19c1e5814178c3d584de35f9e4d7166d91", + "sha256:581f1ce479769fe7eeb9ae6d87eadb230df8c7c5fff32138162cdd99d7fb8fc3", + "sha256:582c22f97a380211fb36a7b65b1beeb84ea11d82015fa84b054be78580390082", + "sha256:59276021619b432a5c21c01cda8320b9cc7dbc40351ffc478b440bfccd5bbdd3", + "sha256:59b19223e7f7bd7a73ec3aa6fdfb73b579ff09c2bc0b7d26857eec2d01a58c76", + "sha256:6986632d8a969ea1e720990c818dace1a24c11015fd7c59b9fea0b65ef71f726", + "sha256:6c2875bcd1397d0627a09a4f9172fa513185ad302c63758efc15b8eb33cc2a98", + "sha256:6d3bfe45173cc8e6c76206be3a916d8bfd2214fb2965563e288088012f1dabfc", + "sha256:6f6e8207d732651e0204779e1ba5a4925c93081834570411f959b80681f8d333", + "sha256:71b6eb97f6695f7ba8ce69c49b707a351c5f46fd97f5aeb5f6f2fb0d6e72b887", + "sha256:727b01a2004ddb513496507a695e19b5c0cfebcdfcc68349d3efd92a1c297bf4", + "sha256:765b8381d4003ceb1a07896a854eee2c31ebc950a4ae17d1e7a17c2a8feb2a68", + "sha256:780c06110f383344d537f48d9010d79fa4f75070d214fc47f389357dd4f010b6", + "sha256:7ad1331cb68afeec58469c31d944a2100cee14eac221553f0d5218ace1a0b25d", + "sha256:7d267ffc9a36c0a9a58c7e0adc9fa82620f22e4a72533e15dd1361f57fc9accf", + "sha256:800f0526adf9e53d3c6acda748f4def1f048adaa780752f154da5cf22aa488a2", + "sha256:8119bf5961091cfe644784d0bae214e273b3b3a479f93ee3baab97bbd995ccfe", + "sha256:8587c3c3dbe78af90c5025288766ac10dc2240c1e76eb0a93a4e244c265ccefd", + "sha256:86923d823bd19ce35805953b018d436f6b862edd6a7c8b747a13d52b39ed5716", + "sha256:8893223b87c2782bd59f9c4bd5c7bf733edd8728b523c93efb91d7468b486528", + "sha256:8e21932d6d260996f7109f2a40b2586070cb0a0cf1d65781e156326d5ebcc329", + "sha256:921e6d2440ac758c4945c587b1d1d9b781b72737ac0c0ca5d5e02ca1db8bded2", + "sha256:92b6f43f086e5a965d33d62a145ae121b4ccb6e0789ac0acc895ce084fec8c65", + "sha256:92c53d508fb8a4463acc85b322fa24734efdc66933a5c8661bdc862103a3373d", + "sha256:95e878868a172a41fbf6c505a4b967309e6870e22adc7b1c3b19653d062711fa", + "sha256:96a5a0292575c3697121f97cc605baf2fd125120c7dcdf39edd1a135798482ca", + "sha256:96c715404a3825e37fe3966fe84c5f8a1f036e7640b2a02dbed96cac0c933451", + "sha256:99462abd8323c52204a2a0ce62454ce8fa0f4e94b9af397945c12830de73f27e", + "sha256:9bac0adffc1b6b6a4c5f1fd1dd2161afb720bcc771a91016dc6bdba59af0a5d3", + "sha256:9e04d22049233394e0b08193aca9737200b4a2afa28659d957327aa780ddddf2", + "sha256:9e45803d9e75ef90a2f859ef8f7f77614730f4a8ce1b9244375734567299d239", + "sha256:9eef0d23035fa4dcfa21e570961e86c375153a7ee605cdd11a8b088c24f707f6", + "sha256:a1445c91009eb775d479e88954c51d0b4cf9a1e8ce3c503c2672d17252882647", + "sha256:a3e61acfd029bfb81c2c596249b508dfd2b4f72e31b7b53b62e5fb0507dd7293", + "sha256:a447247ed312dd64e3a8d9483841ecc5338ee26d6e6fbd29cd373ed030db0240", + "sha256:a7fde09384d23048a7b4ac889063761e44b89a0b64015393e2d1d21d5c1f534a", + "sha256:a83f4532707963ae1a5108e51fdfe1278cc8724e3301fee48b9e73e1316de64f", + "sha256:b4a52dd2a36b0a91f7aa50ca6c8509057acc481a24255f6cb07b15d339a34e0f", + "sha256:b76f2f50a789c44d6fd7f773ec43d2a8686781cd52236da03f7f7d7998989bee", + "sha256:ba3f843aa89f35467b38c398ae5b980a824fdbdb94065adc6ec7c47a0a22f4c7", + "sha256:ba9002d2f043943744a9dc8e50a47362bcb6e6f360dc0a1abcb19642584d87bb", + "sha256:bbe58e26c84b163beba0fbeacf6b065feabc8f75c6d3fe305550d33f24a2d346", + "sha256:be6feb903d2a08a4ba2e70e950e862fd3be9be9a588b7c38cee4728150a52918", + "sha256:bfa3f8e01bc423a933f2e1c510cbb0632c6787865b5242857cc955cae220d1bf", + "sha256:c51b66ada9bfdb88cf711bf350fcc46f82b83a4683cf2413e633c31a64df6201", + "sha256:c64f8e60c1dd69e4d5e615481f2d57937746f4a6be2d0f86e9e7e3b9e2243b5e", + "sha256:c6b6f11b0d7ed91be53166aeef2a23a799e636625675bb30818f47f41ad31821", + "sha256:c835eab01466cb67d0ce6290601ebef2d82d8d0d0a285ed0d6e46989e4a7a71a", + "sha256:ca6a9a9300d5bda417d9090107c6d2b007683efc59d63cc09aca0e7930a08a85", + "sha256:caf07a97b5220e6334dd32c8b6d8b2bd255ca694eca5dfe914bb5b880ee66cdb", + "sha256:cd88028bb897fba99ddd84f253ca6bef73ecb7bdf3f3cf25bc493f8f97d3c7c5", + "sha256:cee3de65584e915053412cd178729ff510ad5f8f585c21c5890e91028283518f", + "sha256:d028044524ee2e815f36210a793c414551b689d4f4eda28f8bbb0883ad78bf5f", + "sha256:d0976a3fcb81d065473173e9005848218ce03ddb2ec7d40dd6a8d2dba7f1c3ae", + "sha256:d294e5e81ff094fe920fd545052ff30838ea49f9e91227a55ecd9f3ca19774a0", + "sha256:d2d271393c378282727f1231d40391ae93b93ddc0997448acc21dd0cb6a1e56d", + "sha256:d9a38332cfad2a91e89405b7c18b3f00e2edc951c225accbc217597d3e4e9fde", + "sha256:da125221b1fa25c690fcd030a54344cecec80074df018d906fc6a99f46c1e3a6", + "sha256:dc1ca9c610425f9854323669a671fc163300b873731584e258975adf50931164", + "sha256:dd728f4e6051af6af234651df49319da1d813f47894d4c3c8ab7455e01703a37", + "sha256:de74ef266e2679c3bf8b5fc20cee4fc0271ba13ae0d9097b1491c7a9bcadb389", + "sha256:e44f4c25e1e7cf6149b499c74945a14649c8866d36371a2c2d2164e4649e7755", + "sha256:e4d2961644153c5ae186db964aa9f6109da81b12df0f1d3494b4e5cf2c332ee2", + "sha256:e70d9c615e5c9dc10d279d1e32e846085fe1fd6f08d623ddd059a92861f4e3dd", + "sha256:ec9be3e4b6f86ea8b294d34c990c99d2ba6c526ef1e8f46f1d52c263d4f32cd7", + "sha256:ed0cfb9326747759e2ad81cb6e45f20086a273b67ac3a4c00b19efcbab007c60", + "sha256:ee98968d6a66ee83a8ceabf31182189ab5d8598998c8ce69b6d5843daeb2db60", + "sha256:f04037302049cb30033f7fa4e1d0e44afe35ed6bfcf9b380fc11f2a27d3ed697", + "sha256:f1ebe23028eac51251f22ba01dba6587d30aa9c320372ca0c14eeab67118ec3f", + "sha256:f37b60e66378e7a116931d7220f5352186abfcc950d64856038aa2c01944929c", + "sha256:f702e295dddef5f8af4a456db93f114539b8dc2a7a9bc4de7c7e41d169aa6ec3", + "sha256:fdddb9d988405f24035234f1e8d1653ab2e48cc2404226d21b49a129aefd1d25", + "sha256:fe1e1779a39dbe83f13886d2b4b02f8c4b10755e3c8d9a89b630395f49f4f406", + "sha256:fe8c6267caa7ec67bcc37e360f0d8a26bc3bdce510b15b97f2f2e0143bdd3673", + "sha256:fea649f979def23150680de1bd1d09682da3b54932800a0f90f29fc2a6c98ba8" + ], + "markers": "implementation_name == 'cpython'", + "version": "==0.12.3" + }, + "decorator": { + "hashes": [ + "sha256:637996211036b6385ef91435e4fae22989472f9d571faba8927ba8253acbc330", + "sha256:b8c3f85900b9dc423225913c5aace94729fe1fa9763b38939a95226f02d37186" + ], + "markers": "python_version >= '3.5'", + "version": "==5.1.1" + }, + "eth-abi": { + "hashes": [ + "sha256:33ddd756206e90f7ddff1330cc8cac4aa411a824fe779314a0a52abea2c8fc14", + "sha256:84cac2626a7db8b7d9ebe62b0fdca676ab1014cc7f777189e3c0cd721a4c16d8" + ], + "markers": "python_version >= '3.8' and python_version < '4'", + "version": "==5.1.0" + }, + "eth-account": { + "hashes": [ + "sha256:abb995aff8db969fecf2e1233d307bf69389623bb6fd1cdbfbd96725f7449ebd", + "sha256:dcaeca6f30b50d0b98557396eaa217113c5cdea61687e424c527c91d4048cd6b" + ], + "markers": "python_version >= '3.8' and python_version < '4'", + "version": "==0.12.2" + }, + "eth-hash": { + "extras": [ + "pycryptodome" + ], + "hashes": [ + "sha256:b8d5a230a2b251f4a291e3164a23a14057c4a6de4b0aa4a16fa4dc9161b57e2f", + "sha256:bacdc705bfd85dadd055ecd35fd1b4f846b671add101427e089a4ca2e8db310a" + ], + "markers": "python_version >= '3.8' and python_version < '4'", + "version": "==0.7.0" + }, + "eth-keyfile": { + "hashes": [ + "sha256:02e3c2e564c7403b92db3fef8ecae3d21123b15787daecd5b643a57369c530f9", + "sha256:9e09f5bc97c8309876c06bdea7a94f0051c25ba3109b5df37afb815418322efe" + ], + "markers": "python_version >= '3.8' and python_version < '4'", + "version": "==0.8.0" + }, + "eth-keys": { + "hashes": [ + "sha256:a0abccb83f3d84322591a2c047a1e3aa52ea86b185fa3e82ce311d120ca2791e", + "sha256:b2bed3ff3bcede68cc0cd4458c7147baaeaac1211a1efdb6ca019f9d3d989f2b" + ], + "markers": "python_version >= '3.8' and python_version < '4'", + "version": "==0.5.0" + }, + "eth-rlp": { + "hashes": [ + "sha256:6f476eb7e37d81feaba5d98aed887e467be92648778c44b19fe594aea209cde1", + "sha256:d5b408a8cd20ed496e8e66d0559560d29bc21cee482f893936a1f05d0dddc4a0" + ], + "markers": "python_version >= '3.8' and python_version < '4'", + "version": "==2.1.0" + }, + "eth-typing": { + "hashes": [ + "sha256:1f1b16bf37bfe0be730731fd24c7398e931a2b45a8feebf82df2e77a611a23be", + "sha256:ed52b0c6b049240fd810bc87c8857c7ea39370f060f70b9ca3876285269f2938" + ], + "index": "pypi", + "markers": "python_version >= '3.8' and python_version < '4'", + "version": "==4.1.0" + }, + "eth-utils": { + "hashes": [ + "sha256:c170168198ddecac1ea911f74937e9364de81dbd03f42450fe40725c4d6e6220", + "sha256:f2e0f617edc81e53fad0faca7f7b169e56bef59ecc530d919a7482640236a228" + ], + "markers": "python_version >= '3.8' and python_version < '4'", + "version": "==4.1.0" + }, + "frozenlist": { + "hashes": [ + "sha256:04ced3e6a46b4cfffe20f9ae482818e34eba9b5fb0ce4056e4cc9b6e212d09b7", + "sha256:0633c8d5337cb5c77acbccc6357ac49a1770b8c487e5b3505c57b949b4b82e98", + "sha256:068b63f23b17df8569b7fdca5517edef76171cf3897eb68beb01341131fbd2ad", + "sha256:0c250a29735d4f15321007fb02865f0e6b6a41a6b88f1f523ca1596ab5f50bd5", + "sha256:1979bc0aeb89b33b588c51c54ab0161791149f2461ea7c7c946d95d5f93b56ae", + "sha256:1a4471094e146b6790f61b98616ab8e44f72661879cc63fa1049d13ef711e71e", + "sha256:1b280e6507ea8a4fa0c0a7150b4e526a8d113989e28eaaef946cc77ffd7efc0a", + "sha256:1d0ce09d36d53bbbe566fe296965b23b961764c0bcf3ce2fa45f463745c04701", + "sha256:20b51fa3f588ff2fe658663db52a41a4f7aa6c04f6201449c6c7c476bd255c0d", + "sha256:23b2d7679b73fe0e5a4560b672a39f98dfc6f60df63823b0a9970525325b95f6", + "sha256:23b701e65c7b36e4bf15546a89279bd4d8675faabc287d06bbcfac7d3c33e1e6", + "sha256:2471c201b70d58a0f0c1f91261542a03d9a5e088ed3dc6c160d614c01649c106", + "sha256:27657df69e8801be6c3638054e202a135c7f299267f1a55ed3a598934f6c0d75", + "sha256:29acab3f66f0f24674b7dc4736477bcd4bc3ad4b896f5f45379a67bce8b96868", + "sha256:32453c1de775c889eb4e22f1197fe3bdfe457d16476ea407472b9442e6295f7a", + "sha256:3a670dc61eb0d0eb7080890c13de3066790f9049b47b0de04007090807c776b0", + "sha256:3e0153a805a98f5ada7e09826255ba99fb4f7524bb81bf6b47fb702666484ae1", + "sha256:410478a0c562d1a5bcc2f7ea448359fcb050ed48b3c6f6f4f18c313a9bdb1826", + "sha256:442acde1e068288a4ba7acfe05f5f343e19fac87bfc96d89eb886b0363e977ec", + "sha256:48f6a4533887e189dae092f1cf981f2e3885175f7a0f33c91fb5b7b682b6bab6", + "sha256:4f57dab5fe3407b6c0c1cc907ac98e8a189f9e418f3b6e54d65a718aaafe3950", + "sha256:4f9c515e7914626b2a2e1e311794b4c35720a0be87af52b79ff8e1429fc25f19", + "sha256:55fdc093b5a3cb41d420884cdaf37a1e74c3c37a31f46e66286d9145d2063bd0", + "sha256:5667ed53d68d91920defdf4035d1cdaa3c3121dc0b113255124bcfada1cfa1b8", + "sha256:590344787a90ae57d62511dd7c736ed56b428f04cd8c161fcc5e7232c130c69a", + "sha256:5a7d70357e7cee13f470c7883a063aae5fe209a493c57d86eb7f5a6f910fae09", + "sha256:5c3894db91f5a489fc8fa6a9991820f368f0b3cbdb9cd8849547ccfab3392d86", + "sha256:5c849d495bf5154cd8da18a9eb15db127d4dba2968d88831aff6f0331ea9bd4c", + "sha256:64536573d0a2cb6e625cf309984e2d873979709f2cf22839bf2d61790b448ad5", + "sha256:693945278a31f2086d9bf3df0fe8254bbeaef1fe71e1351c3bd730aa7d31c41b", + "sha256:6db4667b187a6742b33afbbaf05a7bc551ffcf1ced0000a571aedbb4aa42fc7b", + "sha256:6eb73fa5426ea69ee0e012fb59cdc76a15b1283d6e32e4f8dc4482ec67d1194d", + "sha256:722e1124aec435320ae01ee3ac7bec11a5d47f25d0ed6328f2273d287bc3abb0", + "sha256:7268252af60904bf52c26173cbadc3a071cece75f873705419c8681f24d3edea", + "sha256:74fb4bee6880b529a0c6560885fce4dc95936920f9f20f53d99a213f7bf66776", + "sha256:780d3a35680ced9ce682fbcf4cb9c2bad3136eeff760ab33707b71db84664e3a", + "sha256:82e8211d69a4f4bc360ea22cd6555f8e61a1bd211d1d5d39d3d228b48c83a897", + "sha256:89aa2c2eeb20957be2d950b85974b30a01a762f3308cd02bb15e1ad632e22dc7", + "sha256:8aefbba5f69d42246543407ed2461db31006b0f76c4e32dfd6f42215a2c41d09", + "sha256:96ec70beabbd3b10e8bfe52616a13561e58fe84c0101dd031dc78f250d5128b9", + "sha256:9750cc7fe1ae3b1611bb8cfc3f9ec11d532244235d75901fb6b8e42ce9229dfe", + "sha256:9acbb16f06fe7f52f441bb6f413ebae6c37baa6ef9edd49cdd567216da8600cd", + "sha256:9d3e0c25a2350080e9319724dede4f31f43a6c9779be48021a7f4ebde8b2d742", + "sha256:a06339f38e9ed3a64e4c4e43aec7f59084033647f908e4259d279a52d3757d09", + "sha256:a0cb6f11204443f27a1628b0e460f37fb30f624be6051d490fa7d7e26d4af3d0", + "sha256:a7496bfe1da7fb1a4e1cc23bb67c58fab69311cc7d32b5a99c2007b4b2a0e932", + "sha256:a828c57f00f729620a442881cc60e57cfcec6842ba38e1b19fd3e47ac0ff8dc1", + "sha256:a9b2de4cf0cdd5bd2dee4c4f63a653c61d2408055ab77b151c1957f221cabf2a", + "sha256:b46c8ae3a8f1f41a0d2ef350c0b6e65822d80772fe46b653ab6b6274f61d4a49", + "sha256:b7e3ed87d4138356775346e6845cccbe66cd9e207f3cd11d2f0b9fd13681359d", + "sha256:b7f2f9f912dca3934c1baec2e4585a674ef16fe00218d833856408c48d5beee7", + "sha256:ba60bb19387e13597fb059f32cd4d59445d7b18b69a745b8f8e5db0346f33480", + "sha256:beee944ae828747fd7cb216a70f120767fc9f4f00bacae8543c14a6831673f89", + "sha256:bfa4a17e17ce9abf47a74ae02f32d014c5e9404b6d9ac7f729e01562bbee601e", + "sha256:c037a86e8513059a2613aaba4d817bb90b9d9b6b69aace3ce9c877e8c8ed402b", + "sha256:c302220494f5c1ebeb0912ea782bcd5e2f8308037b3c7553fad0e48ebad6ad82", + "sha256:c6321c9efe29975232da3bd0af0ad216800a47e93d763ce64f291917a381b8eb", + "sha256:c757a9dd70d72b076d6f68efdbb9bc943665ae954dad2801b874c8c69e185068", + "sha256:c99169d4ff810155ca50b4da3b075cbde79752443117d89429595c2e8e37fed8", + "sha256:c9c92be9fd329ac801cc420e08452b70e7aeab94ea4233a4804f0915c14eba9b", + "sha256:cc7b01b3754ea68a62bd77ce6020afaffb44a590c2289089289363472d13aedb", + "sha256:db9e724bebd621d9beca794f2a4ff1d26eed5965b004a97f1f1685a173b869c2", + "sha256:dca69045298ce5c11fd539682cff879cc1e664c245d1c64da929813e54241d11", + "sha256:dd9b1baec094d91bf36ec729445f7769d0d0cf6b64d04d86e45baf89e2b9059b", + "sha256:e02a0e11cf6597299b9f3bbd3f93d79217cb90cfd1411aec33848b13f5c656cc", + "sha256:e6a20a581f9ce92d389a8c7d7c3dd47c81fd5d6e655c8dddf341e14aa48659d0", + "sha256:e7004be74cbb7d9f34553a5ce5fb08be14fb33bc86f332fb71cbe5216362a497", + "sha256:e774d53b1a477a67838a904131c4b0eef6b3d8a651f8b138b04f748fccfefe17", + "sha256:edb678da49d9f72c9f6c609fbe41a5dfb9a9282f9e6a2253d5a91e0fc382d7c0", + "sha256:f146e0911cb2f1da549fc58fc7bcd2b836a44b79ef871980d605ec392ff6b0d2", + "sha256:f56e2333dda1fe0f909e7cc59f021eba0d2307bc6f012a1ccf2beca6ba362439", + "sha256:f9a3ea26252bd92f570600098783d1371354d89d5f6b7dfd87359d669f2109b5", + "sha256:f9aa1878d1083b276b0196f2dfbe00c9b7e752475ed3b682025ff20c1c1f51ac", + "sha256:fb3c2db03683b5767dedb5769b8a40ebb47d6f7f45b1b3e3b4b51ec8ad9d9825", + "sha256:fbeb989b5cc29e8daf7f976b421c220f1b8c731cbf22b9130d8815418ea45887", + "sha256:fde5bd59ab5357e3853313127f4d3565fc7dad314a74d7b5d43c22c6a5ed2ced", + "sha256:fe1a06da377e3a1062ae5fe0926e12b84eceb8a50b350ddca72dc85015873f74" + ], + "markers": "python_version >= '3.8'", + "version": "==1.4.1" + }, + "hexbytes": { + "hashes": [ + "sha256:965f1cc712e7b263c41fdf3fb36cf671ba6f59b895937cf33941a5c996ec3a5c", + "sha256:bb243ab58b8d8390e3a753fbc9e3616f0f958df43d874e19ae0e4b746722a7e9" + ], + "markers": "python_version >= '3.8' and python_version < '4'", + "version": "==1.2.0" + }, + "idna": { + "hashes": [ + "sha256:028ff3aadf0609c1fd278d8ea3089299412a7a8b9bd005dd08b9f8285bcb5cfc", + "sha256:82fee1fc78add43492d3a1898bfa6d8a904cc97d8427f683ed8e798d07761aa0" + ], + "markers": "python_version >= '3.5'", + "version": "==3.7" + }, + "jsonschema": { + "hashes": [ + "sha256:7996507afae316306f9e2290407761157c6f78002dcf7419acb99822143d1c6f", + "sha256:85727c00279f5fa6bedbe6238d2aa6403bedd8b4864ab11207d07df3cc1b2ee5" + ], + "markers": "python_version >= '3.8'", + "version": "==4.21.1" + }, + "jsonschema-specifications": { + "hashes": [ + "sha256:48a76787b3e70f5ed53f1160d2b81f586e4ca6d1548c5de7085d1682674764cc", + "sha256:87e4fdf3a94858b8a2ba2778d9ba57d8a9cafca7c7489c46ba0d30a8bc6a9c3c" + ], + "markers": "python_version >= '3.8'", + "version": "==2023.12.1" + }, + "lru-dict": { + "hashes": [ + "sha256:0213ab4e3d9a8d386c18e485ad7b14b615cb6f05df6ef44fb2a0746c6ea9278b", + "sha256:04cda617f4e4c27009005d0a8185ef02829b14b776d2791f5c994cc9d668bc24", + "sha256:0ad6361e4dd63b47b2fc8eab344198f37387e1da3dcfacfee19bafac3ec9f1eb", + "sha256:0e1845024c31e6ff246c9eb5e6f6f1a8bb564c06f8a7d6d031220044c081090b", + "sha256:0e88dba16695f17f41701269fa046197a3fd7b34a8dba744c8749303ddaa18df", + "sha256:0fce5f95489ca1fc158cc9fe0f4866db9cec82c2be0470926a9080570392beaf", + "sha256:1470f5828c7410e16c24b5150eb649647986e78924816e6fb0264049dea14a2b", + "sha256:170b66d29945391460351588a7bd8210a95407ae82efe0b855e945398a1d24ea", + "sha256:1958cb70b9542773d6241974646e5410e41ef32e5c9e437d44040d59bd80daf2", + "sha256:1ecb7ae557239c64077e9b26a142eb88e63cddb104111a5122de7bebbbd00098", + "sha256:20c595764695d20bdc3ab9b582e0cc99814da183544afb83783a36d6741a0dac", + "sha256:2682bfca24656fb7a643621520d57b7fe684ed5fa7be008704c1235d38e16a32", + "sha256:2789296819525a1f3204072dfcf3df6db8bcf69a8fc740ffd3de43a684ea7002", + "sha256:28aa1ea42a7e48174bf513dc2416fea7511a547961e678dc6f5670ca987c18cb", + "sha256:2a47740652b25900ac5ce52667b2eade28d8b5fdca0ccd3323459df710e8210a", + "sha256:350e2233cfee9f326a0d7a08e309372d87186565e43a691b120006285a0ac549", + "sha256:3b4f121afe10f5a82b8e317626eb1e1c325b3f104af56c9756064cd833b1950b", + "sha256:3c497fb60279f1e1d7dfbe150b1b069eaa43f7e172dab03f206282f4994676c5", + "sha256:3ca5474b1649555d014be1104e5558a92497509021a5ba5ea6e9b492303eb66b", + "sha256:3cb1de0ce4137b060abaafed8474cc0ebd12cedd88aaa7f7b3ebb1ddfba86ae0", + "sha256:4073333894db9840f066226d50e6f914a2240711c87d60885d8c940b69a6673f", + "sha256:40a8daddc29c7edb09dfe44292cf111f1e93a8344349778721d430d336b50505", + "sha256:4eafb188a84483b3231259bf19030859f070321b00326dcb8e8c6cbf7db4b12f", + "sha256:5247d1f011f92666010942434020ddc5a60951fefd5d12a594f0e5d9f43e3b3b", + "sha256:54fd1966d6bd1fcde781596cb86068214edeebff1db13a2cea11079e3fd07b6b", + "sha256:5ad659cbc349d0c9ba8e536b5f40f96a70c360f43323c29f4257f340d891531c", + "sha256:6123aefe97762ad74215d05320a7f389f196f0594c8813534284d4eafeca1a96", + "sha256:64545fca797fe2c68c5168efb5f976c6e1459e058cab02445207a079180a3557", + "sha256:6a03170e4152836987a88dcebde61aaeb73ab7099a00bb86509d45b3fe424230", + "sha256:6af36166d22dba851e06a13e35bbf33845d3dd88872e6aebbc8e3e7db70f4682", + "sha256:6bba2863060caeaedd8386b0c8ee9a7ce4d57a7cb80ceeddf440b4eff2d013ba", + "sha256:6cb0be5e79c3f34d69b90d8559f0221e374b974b809a22377122c4b1a610ff67", + "sha256:6ffaf595e625b388babc8e7d79b40f26c7485f61f16efe76764e32dce9ea17fc", + "sha256:73593791047e36b37fdc0b67b76aeed439fcea80959c7d46201240f9ec3b2563", + "sha256:774ca88501a9effe8797c3db5a6685cf20978c9cb0fe836b6813cfe1ca60d8c9", + "sha256:784ca9d3b0730b3ec199c0a58f66264c63dd5d438119c739c349a6a9be8e5f6e", + "sha256:7969cb034b3ccc707aff877c73c225c32d7e2a7981baa8f92f5dd4d468fe8c33", + "sha256:7ffbce5c2e80f57937679553c8f27e61ec327c962bf7ea0b15f1d74277fd5363", + "sha256:82eb230d48eaebd6977a92ddaa6d788f14cf4f4bcf5bbffa4ddfd60d051aa9d4", + "sha256:8551ccab1349d4bebedab333dfc8693c74ff728f4b565fe15a6bf7d296bd7ea9", + "sha256:8d9509d817a47597988615c1a322580c10100acad10c98dfcf3abb41e0e5877f", + "sha256:8ee38d420c77eed548df47b7d74b5169a98e71c9e975596e31ab808e76d11f09", + "sha256:9537e1cee6fa582cb68f2fb9ce82d51faf2ccc0a638b275d033fdcb1478eb80b", + "sha256:96fc87ddf569181827458ec5ad8fa446c4690cffacda66667de780f9fcefd44d", + "sha256:9710737584650a4251b9a566cbb1a86f83437adb209c9ba43a4e756d12faf0d7", + "sha256:9bd13af06dab7c6ee92284fd02ed9a5613a07d5c1b41948dc8886e7207f86dfd", + "sha256:9f725f2a0bdf1c18735372d5807af4ea3b77888208590394d4660e3d07971f21", + "sha256:a193a14c66cfc0c259d05dddc5e566a4b09e8f1765e941503d065008feebea9d", + "sha256:a1efc59bfba6aac33684d87b9e02813b0e2445b2f1c444dae2a0b396ad0ed60c", + "sha256:a3c9f746a9917e784fffcedeac4c8c47a3dbd90cbe13b69e9140182ad97ce4b7", + "sha256:a690c23fc353681ed8042d9fe8f48f0fb79a57b9a45daea2f0be1eef8a1a4aa4", + "sha256:a9fb71ba262c6058a0017ce83d343370d0a0dbe2ae62c2eef38241ec13219330", + "sha256:abd0c284b26b5c4ee806ca4f33ab5e16b4bf4d5ec9e093e75a6f6287acdde78e", + "sha256:acd04b7e7b0c0c192d738df9c317093335e7282c64c9d1bb6b7ebb54674b4e24", + "sha256:b2bf2e24cf5f19c3ff69bf639306e83dced273e6fa775b04e190d7f5cd16f794", + "sha256:b50fbd69cd3287196796ab4d50e4cc741eb5b5a01f89d8e930df08da3010c385", + "sha256:b84c321ae34f2f40aae80e18b6fa08b31c90095792ab64bb99d2e385143effaa", + "sha256:ba490b8972531d153ac0d4e421f60d793d71a2f4adbe2f7740b3c55dce0a12f1", + "sha256:bc1cd3ed2cee78a47f11f3b70be053903bda197a873fd146e25c60c8e5a32cd6", + "sha256:c0131351b8a7226c69f1eba5814cbc9d1d8daaf0fdec1ae3f30508e3de5262d4", + "sha256:c265f16c936a8ff3bb4b8a4bda0be94c15ec28b63e99fdb1439c1ffe4cd437db", + "sha256:c279068f68af3b46a5d649855e1fb87f5705fe1f744a529d82b2885c0e1fc69d", + "sha256:c637ab54b8cd9802fe19b260261e38820d748adf7606e34045d3c799b6dde813", + "sha256:c95f8751e2abd6f778da0399c8e0239321d560dbc58cb063827123137d213242", + "sha256:ca3703ff03b03a1848c563bc2663d0ad813c1cd42c4d9cf75b623716d4415d9a", + "sha256:ca9ab676609cce85dd65d91c275e47da676d13d77faa72de286fbea30fbaa596", + "sha256:cd869cadba9a63e1e7fe2dced4a5747d735135b86016b0a63e8c9e324ab629ac", + "sha256:cf9da32ef2582434842ab6ba6e67290debfae72771255a8e8ab16f3e006de0aa", + "sha256:cfaf75ac574447afcf8ad998789071af11d2bcf6f947643231f692948839bd98", + "sha256:d9b30a8f50c3fa72a494eca6be5810a1b5c89e4f0fda89374f0d1c5ad8d37d51", + "sha256:dcec98e2c7da7631f0811730303abc4bdfe70d013f7a11e174a2ccd5612a7c59", + "sha256:df2e119c6ae412d2fd641a55f8a1e2e51f45a3de3449c18b1b86c319ab79e0c4", + "sha256:e13b2f58f647178470adaa14603bb64cc02eeed32601772ccea30e198252883c", + "sha256:e5c20f236f27551e3f0adbf1a987673fb1e9c38d6d284502cd38f5a3845ef681", + "sha256:e90059f7701bef3c4da073d6e0434a9c7dc551d5adce30e6b99ef86b186f4b4a", + "sha256:ebb03a9bd50c2ed86d4f72a54e0aae156d35a14075485b2127c4b01a3f4a63fa", + "sha256:eed24272b4121b7c22f234daed99899817d81d671b3ed030c876ac88bc9dc890", + "sha256:efd3f4e0385d18f20f7ea6b08af2574c1bfaa5cb590102ef1bee781bdfba84bc", + "sha256:f27c078b5d75989952acbf9b77e14c3dadc468a4aafe85174d548afbc5efc38b", + "sha256:f5b88a7c39e307739a3701194993455968fcffe437d1facab93546b1b8a334c1", + "sha256:f8f7824db5a64581180ab9d09842e6dd9fcdc46aac9cb592a0807cd37ea55680" + ], + "markers": "python_version >= '3.8'", + "version": "==1.3.0" + }, + "minio": { + "hashes": [ + "sha256:59d8906e2da248a9caac34d4958a859cc3a44abbe6447910c82b5abfa9d6a2e1", + "sha256:ed9176c96d4271cb1022b9ecb8a538b1e55b32ae06add6de16425cab99ef2304" + ], + "index": "pypi", + "version": "==7.2.5" + }, + "multidict": { + "hashes": [ + "sha256:01265f5e40f5a17f8241d52656ed27192be03bfa8764d88e8220141d1e4b3556", + "sha256:0275e35209c27a3f7951e1ce7aaf93ce0d163b28948444bec61dd7badc6d3f8c", + "sha256:04bde7a7b3de05732a4eb39c94574db1ec99abb56162d6c520ad26f83267de29", + "sha256:04da1bb8c8dbadf2a18a452639771951c662c5ad03aefe4884775454be322c9b", + "sha256:09a892e4a9fb47331da06948690ae38eaa2426de97b4ccbfafbdcbe5c8f37ff8", + "sha256:0d63c74e3d7ab26de115c49bffc92cc77ed23395303d496eae515d4204a625e7", + "sha256:107c0cdefe028703fb5dafe640a409cb146d44a6ae201e55b35a4af8e95457dd", + "sha256:141b43360bfd3bdd75f15ed811850763555a251e38b2405967f8e25fb43f7d40", + "sha256:14c2976aa9038c2629efa2c148022ed5eb4cb939e15ec7aace7ca932f48f9ba6", + "sha256:19fe01cea168585ba0f678cad6f58133db2aa14eccaf22f88e4a6dccadfad8b3", + "sha256:1d147090048129ce3c453f0292e7697d333db95e52616b3793922945804a433c", + "sha256:1d9ea7a7e779d7a3561aade7d596649fbecfa5c08a7674b11b423783217933f9", + "sha256:215ed703caf15f578dca76ee6f6b21b7603791ae090fbf1ef9d865571039ade5", + "sha256:21fd81c4ebdb4f214161be351eb5bcf385426bf023041da2fd9e60681f3cebae", + "sha256:220dd781e3f7af2c2c1053da9fa96d9cf3072ca58f057f4c5adaaa1cab8fc442", + "sha256:228b644ae063c10e7f324ab1ab6b548bdf6f8b47f3ec234fef1093bc2735e5f9", + "sha256:29bfeb0dff5cb5fdab2023a7a9947b3b4af63e9c47cae2a10ad58394b517fddc", + "sha256:2f4848aa3baa109e6ab81fe2006c77ed4d3cd1e0ac2c1fbddb7b1277c168788c", + "sha256:2faa5ae9376faba05f630d7e5e6be05be22913782b927b19d12b8145968a85ea", + "sha256:2ffc42c922dbfddb4a4c3b438eb056828719f07608af27d163191cb3e3aa6cc5", + "sha256:37b15024f864916b4951adb95d3a80c9431299080341ab9544ed148091b53f50", + "sha256:3cc2ad10255f903656017363cd59436f2111443a76f996584d1077e43ee51182", + "sha256:3d25f19500588cbc47dc19081d78131c32637c25804df8414463ec908631e453", + "sha256:403c0911cd5d5791605808b942c88a8155c2592e05332d2bf78f18697a5fa15e", + "sha256:411bf8515f3be9813d06004cac41ccf7d1cd46dfe233705933dd163b60e37600", + "sha256:425bf820055005bfc8aa9a0b99ccb52cc2f4070153e34b701acc98d201693733", + "sha256:435a0984199d81ca178b9ae2c26ec3d49692d20ee29bc4c11a2a8d4514c67eda", + "sha256:4a6a4f196f08c58c59e0b8ef8ec441d12aee4125a7d4f4fef000ccb22f8d7241", + "sha256:4cc0ef8b962ac7a5e62b9e826bd0cd5040e7d401bc45a6835910ed699037a461", + "sha256:51d035609b86722963404f711db441cf7134f1889107fb171a970c9701f92e1e", + "sha256:53689bb4e102200a4fafa9de9c7c3c212ab40a7ab2c8e474491914d2305f187e", + "sha256:55205d03e8a598cfc688c71ca8ea5f66447164efff8869517f175ea632c7cb7b", + "sha256:5c0631926c4f58e9a5ccce555ad7747d9a9f8b10619621f22f9635f069f6233e", + "sha256:5cb241881eefd96b46f89b1a056187ea8e9ba14ab88ba632e68d7a2ecb7aadf7", + "sha256:60d698e8179a42ec85172d12f50b1668254628425a6bd611aba022257cac1386", + "sha256:612d1156111ae11d14afaf3a0669ebf6c170dbb735e510a7438ffe2369a847fd", + "sha256:6214c5a5571802c33f80e6c84713b2c79e024995b9c5897f794b43e714daeec9", + "sha256:6939c95381e003f54cd4c5516740faba40cf5ad3eeff460c3ad1d3e0ea2549bf", + "sha256:69db76c09796b313331bb7048229e3bee7928eb62bab5e071e9f7fcc4879caee", + "sha256:6bf7a982604375a8d49b6cc1b781c1747f243d91b81035a9b43a2126c04766f5", + "sha256:766c8f7511df26d9f11cd3a8be623e59cca73d44643abab3f8c8c07620524e4a", + "sha256:76c0de87358b192de7ea9649beb392f107dcad9ad27276324c24c91774ca5271", + "sha256:76f067f5121dcecf0d63a67f29080b26c43c71a98b10c701b0677e4a065fbd54", + "sha256:7901c05ead4b3fb75113fb1dd33eb1253c6d3ee37ce93305acd9d38e0b5f21a4", + "sha256:79660376075cfd4b2c80f295528aa6beb2058fd289f4c9252f986751a4cd0496", + "sha256:79a6d2ba910adb2cbafc95dad936f8b9386e77c84c35bc0add315b856d7c3abb", + "sha256:7afcdd1fc07befad18ec4523a782cde4e93e0a2bf71239894b8d61ee578c1319", + "sha256:7be7047bd08accdb7487737631d25735c9a04327911de89ff1b26b81745bd4e3", + "sha256:7c6390cf87ff6234643428991b7359b5f59cc15155695deb4eda5c777d2b880f", + "sha256:7df704ca8cf4a073334e0427ae2345323613e4df18cc224f647f251e5e75a527", + "sha256:85f67aed7bb647f93e7520633d8f51d3cbc6ab96957c71272b286b2f30dc70ed", + "sha256:896ebdcf62683551312c30e20614305f53125750803b614e9e6ce74a96232604", + "sha256:92d16a3e275e38293623ebf639c471d3e03bb20b8ebb845237e0d3664914caef", + "sha256:99f60d34c048c5c2fabc766108c103612344c46e35d4ed9ae0673d33c8fb26e8", + "sha256:9fe7b0653ba3d9d65cbe7698cca585bf0f8c83dbbcc710db9c90f478e175f2d5", + "sha256:a3145cb08d8625b2d3fee1b2d596a8766352979c9bffe5d7833e0503d0f0b5e5", + "sha256:aeaf541ddbad8311a87dd695ed9642401131ea39ad7bc8cf3ef3967fd093b626", + "sha256:b55358304d7a73d7bdf5de62494aaf70bd33015831ffd98bc498b433dfe5b10c", + "sha256:b82cc8ace10ab5bd93235dfaab2021c70637005e1ac787031f4d1da63d493c1d", + "sha256:c0868d64af83169e4d4152ec612637a543f7a336e4a307b119e98042e852ad9c", + "sha256:c1c1496e73051918fcd4f58ff2e0f2f3066d1c76a0c6aeffd9b45d53243702cc", + "sha256:c9bf56195c6bbd293340ea82eafd0071cb3d450c703d2c93afb89f93b8386ccc", + "sha256:cbebcd5bcaf1eaf302617c114aa67569dd3f090dd0ce8ba9e35e9985b41ac35b", + "sha256:cd6c8fca38178e12c00418de737aef1261576bd1b6e8c6134d3e729a4e858b38", + "sha256:ceb3b7e6a0135e092de86110c5a74e46bda4bd4fbfeeb3a3bcec79c0f861e450", + "sha256:cf590b134eb70629e350691ecca88eac3e3b8b3c86992042fb82e3cb1830d5e1", + "sha256:d3eb1ceec286eba8220c26f3b0096cf189aea7057b6e7b7a2e60ed36b373b77f", + "sha256:d65f25da8e248202bd47445cec78e0025c0fe7582b23ec69c3b27a640dd7a8e3", + "sha256:d6f6d4f185481c9669b9447bf9d9cf3b95a0e9df9d169bbc17e363b7d5487755", + "sha256:d84a5c3a5f7ce6db1f999fb9438f686bc2e09d38143f2d93d8406ed2dd6b9226", + "sha256:d946b0a9eb8aaa590df1fe082cee553ceab173e6cb5b03239716338629c50c7a", + "sha256:dce1c6912ab9ff5f179eaf6efe7365c1f425ed690b03341911bf4939ef2f3046", + "sha256:de170c7b4fe6859beb8926e84f7d7d6c693dfe8e27372ce3b76f01c46e489fcf", + "sha256:e02021f87a5b6932fa6ce916ca004c4d441509d33bbdbeca70d05dff5e9d2479", + "sha256:e030047e85cbcedbfc073f71836d62dd5dadfbe7531cae27789ff66bc551bd5e", + "sha256:e0e79d91e71b9867c73323a3444724d496c037e578a0e1755ae159ba14f4f3d1", + "sha256:e4428b29611e989719874670fd152b6625500ad6c686d464e99f5aaeeaca175a", + "sha256:e4972624066095e52b569e02b5ca97dbd7a7ddd4294bf4e7247d52635630dd83", + "sha256:e7be68734bd8c9a513f2b0cfd508802d6609da068f40dc57d4e3494cefc92929", + "sha256:e8e94e6912639a02ce173341ff62cc1201232ab86b8a8fcc05572741a5dc7d93", + "sha256:ea1456df2a27c73ce51120fa2f519f1bea2f4a03a917f4a43c8707cf4cbbae1a", + "sha256:ebd8d160f91a764652d3e51ce0d2956b38efe37c9231cd82cfc0bed2e40b581c", + "sha256:eca2e9d0cc5a889850e9bbd68e98314ada174ff6ccd1129500103df7a94a7a44", + "sha256:edd08e6f2f1a390bf137080507e44ccc086353c8e98c657e666c017718561b89", + "sha256:f285e862d2f153a70586579c15c44656f888806ed0e5b56b64489afe4a2dbfba", + "sha256:f2a1dee728b52b33eebff5072817176c172050d44d67befd681609b4746e1c2e", + "sha256:f7e301075edaf50500f0b341543c41194d8df3ae5caf4702f2095f3ca73dd8da", + "sha256:fb616be3538599e797a2017cccca78e354c767165e8858ab5116813146041a24", + "sha256:fce28b3c8a81b6b36dfac9feb1de115bab619b3c13905b419ec71d03a3fc1423", + "sha256:fe5d7785250541f7f5019ab9cba2c71169dc7d74d0f45253f8313f436458a4ef" + ], + "markers": "python_version >= '3.7'", + "version": "==6.0.5" + }, + "parsimonious": { + "hashes": [ + "sha256:8281600da180ec8ae35427a4ab4f7b82bfec1e3d1e52f80cb60ea82b9512501c", + "sha256:982ab435fabe86519b57f6b35610aa4e4e977e9f02a14353edf4bbc75369fc0f" + ], + "version": "==0.10.0" + }, + "pgpy": { + "hashes": [ + "sha256:279c2e353f4c3a319f00bd9bd582456e420f8a3ac6de2b4e9731444746828383" + ], + "index": "pypi", + "markers": "python_version >= '3.6'", + "version": "==0.6.0" + }, + "protobuf": { + "hashes": [ + "sha256:38aa5f535721d5bb99861166c445c4105c4e285c765fbb2ac10f116e32dcd46d", + "sha256:3c388ea6ddfe735f8cf69e3f7dc7611e73107b60bdfcf5d0f024c3ccd3794e23", + "sha256:7ee014c2c87582e101d6b54260af03b6596728505c79f17c8586e7523aaa8f8c", + "sha256:8ca2a1d97c290ec7b16e4e5dff2e5ae150cc1582f55b5ab300d45cb0dfa90e51", + "sha256:9b557c317ebe6836835ec4ef74ec3e994ad0894ea424314ad3552bc6e8835b4e", + "sha256:b9ba3ca83c2e31219ffbeb9d76b63aad35a3eb1544170c55336993d7a18ae72c", + "sha256:d693d2504ca96750d92d9de8a103102dd648fda04540495535f0fec7577ed8fc", + "sha256:da612f2720c0183417194eeaa2523215c4fcc1a1949772dc65f05047e08d5932", + "sha256:e6039957449cb918f331d32ffafa8eb9255769c96aa0560d9a5bf0b4e00a2a33", + "sha256:f7417703f841167e5a27d48be13389d52ad705ec09eade63dfc3180a959215d7", + "sha256:fbfe61e7ee8c1860855696e3ac6cfd1b01af5498facc6834fcc345c9684fb2ca" + ], + "markers": "python_version >= '3.8'", + "version": "==5.26.1" + }, + "pyasn1": { + "hashes": [ + "sha256:3a35ab2c4b5ef98e17dfdec8ab074046fbda76e281c5a706ccd82328cfc8f64c", + "sha256:cca4bb0f2df5504f02f6f8a775b6e416ff9b0b3b16f7ee80b5a3153d9b804473" + ], + "markers": "python_version >= '3.8'", + "version": "==0.6.0" + }, + "pycparser": { + "hashes": [ + "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6", + "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc" + ], + "markers": "python_version >= '3.8'", + "version": "==2.22" + }, + "pycryptodome": { + "hashes": [ + "sha256:06d6de87c19f967f03b4cf9b34e538ef46e99a337e9a61a77dbe44b2cbcf0690", + "sha256:09609209ed7de61c2b560cc5c8c4fbf892f8b15b1faf7e4cbffac97db1fffda7", + "sha256:210ba1b647837bfc42dd5a813cdecb5b86193ae11a3f5d972b9a0ae2c7e9e4b4", + "sha256:2a1250b7ea809f752b68e3e6f3fd946b5939a52eaeea18c73bdab53e9ba3c2dd", + "sha256:2ab6ab0cb755154ad14e507d1df72de9897e99fd2d4922851a276ccc14f4f1a5", + "sha256:3427d9e5310af6680678f4cce149f54e0bb4af60101c7f2c16fdf878b39ccccc", + "sha256:3cd3ef3aee1079ae44afaeee13393cf68b1058f70576b11439483e34f93cf818", + "sha256:405002eafad114a2f9a930f5db65feef7b53c4784495dd8758069b89baf68eab", + "sha256:417a276aaa9cb3be91f9014e9d18d10e840a7a9b9a9be64a42f553c5b50b4d1d", + "sha256:4401564ebf37dfde45d096974c7a159b52eeabd9969135f0426907db367a652a", + "sha256:49a4c4dc60b78ec41d2afa392491d788c2e06edf48580fbfb0dd0f828af49d25", + "sha256:5601c934c498cd267640b57569e73793cb9a83506f7c73a8ec57a516f5b0b091", + "sha256:6e0e4a987d38cfc2e71b4a1b591bae4891eeabe5fa0f56154f576e26287bfdea", + "sha256:76658f0d942051d12a9bd08ca1b6b34fd762a8ee4240984f7c06ddfb55eaf15a", + "sha256:76cb39afede7055127e35a444c1c041d2e8d2f1f9c121ecef573757ba4cd2c3c", + "sha256:8d6b98d0d83d21fb757a182d52940d028564efe8147baa9ce0f38d057104ae72", + "sha256:9b3ae153c89a480a0ec402e23db8d8d84a3833b65fa4b15b81b83be9d637aab9", + "sha256:a60fedd2b37b4cb11ccb5d0399efe26db9e0dd149016c1cc6c8161974ceac2d6", + "sha256:ac1c7c0624a862f2e53438a15c9259d1655325fc2ec4392e66dc46cdae24d044", + "sha256:acae12b9ede49f38eb0ef76fdec2df2e94aad85ae46ec85be3648a57f0a7db04", + "sha256:acc2614e2e5346a4a4eab6e199203034924313626f9620b7b4b38e9ad74b7e0c", + "sha256:acf6e43fa75aca2d33e93409f2dafe386fe051818ee79ee8a3e21de9caa2ac9e", + "sha256:baee115a9ba6c5d2709a1e88ffe62b73ecc044852a925dcb67713a288c4ec70f", + "sha256:c18b381553638414b38705f07d1ef0a7cf301bc78a5f9bc17a957eb19446834b", + "sha256:d29daa681517f4bc318cd8a23af87e1f2a7bad2fe361e8aa29c77d652a065de4", + "sha256:d5954acfe9e00bc83ed9f5cb082ed22c592fbbef86dc48b907238be64ead5c33", + "sha256:ec0bb1188c1d13426039af8ffcb4dbe3aad1d7680c35a62d8eaf2a529b5d3d4f", + "sha256:ec1f93feb3bb93380ab0ebf8b859e8e5678c0f010d2d78367cf6bc30bfeb148e", + "sha256:f0e6d631bae3f231d3634f91ae4da7a960f7ff87f2865b2d2b831af1dfb04e9a", + "sha256:f35d6cee81fa145333137009d9c8ba90951d7d77b67c79cbe5f03c7eb74d8fe2", + "sha256:f47888542a0633baff535a04726948e876bf1ed880fddb7c10a736fa99146ab3", + "sha256:fb3b87461fa35afa19c971b0a2b7456a7b1db7b4eba9a8424666104925b78128" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", + "version": "==3.20.0" + }, + "pydantic": { + "hashes": [ + "sha256:9dee74a271705f14f9a1567671d144a851c675b072736f0a7b2608fd9e495352", + "sha256:b5ecdd42262ca2462e2624793551e80911a1e989f462910bb81aef974b4bb383" + ], + "markers": "python_version >= '3.8'", + "version": "==2.7.0" + }, + "pydantic-core": { + "hashes": [ + "sha256:030e4f9516f9947f38179249778709a460a3adb516bf39b5eb9066fcfe43d0e6", + "sha256:09f03dfc0ef8c22622eaa8608caa4a1e189cfb83ce847045eca34f690895eccb", + "sha256:12a05db5013ec0ca4a32cc6433f53faa2a014ec364031408540ba858c2172bb0", + "sha256:14fe73881cf8e4cbdaded8ca0aa671635b597e42447fec7060d0868b52d074e6", + "sha256:1a0c3e718f4e064efde68092d9d974e39572c14e56726ecfaeebbe6544521f47", + "sha256:1be91ad664fc9245404a789d60cba1e91c26b1454ba136d2a1bf0c2ac0c0505a", + "sha256:201713f2f462e5c015b343e86e68bd8a530a4f76609b33d8f0ec65d2b921712a", + "sha256:2027493cc44c23b598cfaf200936110433d9caa84e2c6cf487a83999638a96ac", + "sha256:250ae39445cb5475e483a36b1061af1bc233de3e9ad0f4f76a71b66231b07f88", + "sha256:2533ad2883f001efa72f3d0e733fb846710c3af6dcdd544fe5bf14fa5fe2d7db", + "sha256:25595ac311f20e5324d1941909b0d12933f1fd2171075fcff763e90f43e92a0d", + "sha256:2684a94fdfd1b146ff10689c6e4e815f6a01141781c493b97342cdc5b06f4d5d", + "sha256:27f1009dc292f3b7ca77feb3571c537276b9aad5dd4efb471ac88a8bd09024e9", + "sha256:2adaeea59849ec0939af5c5d476935f2bab4b7f0335b0110f0f069a41024278e", + "sha256:2ae80f72bb7a3e397ab37b53a2b49c62cc5496412e71bc4f1277620a7ce3f52b", + "sha256:2d5728e93d28a3c63ee513d9ffbac9c5989de8c76e049dbcb5bfe4b923a9739d", + "sha256:2e91711e36e229978d92642bfc3546333a9127ecebb3f2761372e096395fc649", + "sha256:2fe0c1ce5b129455e43f941f7a46f61f3d3861e571f2905d55cdbb8b5c6f5e2c", + "sha256:38a5024de321d672a132b1834a66eeb7931959c59964b777e8f32dbe9523f6b1", + "sha256:3e352f0191d99fe617371096845070dee295444979efb8f27ad941227de6ad09", + "sha256:48dd883db92e92519201f2b01cafa881e5f7125666141a49ffba8b9facc072b0", + "sha256:54764c083bbe0264f0f746cefcded6cb08fbbaaf1ad1d78fb8a4c30cff999a90", + "sha256:54c7375c62190a7845091f521add19b0f026bcf6ae674bdb89f296972272e86d", + "sha256:561cf62c8a3498406495cfc49eee086ed2bb186d08bcc65812b75fda42c38294", + "sha256:56823a92075780582d1ffd4489a2e61d56fd3ebb4b40b713d63f96dd92d28144", + "sha256:582cf2cead97c9e382a7f4d3b744cf0ef1a6e815e44d3aa81af3ad98762f5a9b", + "sha256:58aca931bef83217fca7a390e0486ae327c4af9c3e941adb75f8772f8eeb03a1", + "sha256:5f7973c381283783cd1043a8c8f61ea5ce7a3a58b0369f0ee0ee975eaf2f2a1b", + "sha256:6395a4435fa26519fd96fdccb77e9d00ddae9dd6c742309bd0b5610609ad7fb2", + "sha256:63d7523cd95d2fde0d28dc42968ac731b5bb1e516cc56b93a50ab293f4daeaad", + "sha256:641a018af4fe48be57a2b3d7a1f0f5dbca07c1d00951d3d7463f0ac9dac66622", + "sha256:667880321e916a8920ef49f5d50e7983792cf59f3b6079f3c9dac2b88a311d17", + "sha256:684d840d2c9ec5de9cb397fcb3f36d5ebb6fa0d94734f9886032dd796c1ead06", + "sha256:68717c38a68e37af87c4da20e08f3e27d7e4212e99e96c3d875fbf3f4812abfc", + "sha256:6b7bbb97d82659ac8b37450c60ff2e9f97e4eb0f8a8a3645a5568b9334b08b50", + "sha256:72722ce529a76a4637a60be18bd789d8fb871e84472490ed7ddff62d5fed620d", + "sha256:73c1bc8a86a5c9e8721a088df234265317692d0b5cd9e86e975ce3bc3db62a59", + "sha256:76909849d1a6bffa5a07742294f3fa1d357dc917cb1fe7b470afbc3a7579d539", + "sha256:76b86e24039c35280ceee6dce7e62945eb93a5175d43689ba98360ab31eebc4a", + "sha256:7a5d83efc109ceddb99abd2c1316298ced2adb4570410defe766851a804fcd5b", + "sha256:80e0e57cc704a52fb1b48f16d5b2c8818da087dbee6f98d9bf19546930dc64b5", + "sha256:85233abb44bc18d16e72dc05bf13848a36f363f83757541f1a97db2f8d58cfd9", + "sha256:907a4d7720abfcb1c81619863efd47c8a85d26a257a2dbebdb87c3b847df0278", + "sha256:9376d83d686ec62e8b19c0ac3bf8d28d8a5981d0df290196fb6ef24d8a26f0d6", + "sha256:94b9769ba435b598b547c762184bcfc4783d0d4c7771b04a3b45775c3589ca44", + "sha256:9a29726f91c6cb390b3c2338f0df5cd3e216ad7a938762d11c994bb37552edb0", + "sha256:9b6431559676a1079eac0f52d6d0721fb8e3c5ba43c37bc537c8c83724031feb", + "sha256:9ece8a49696669d483d206b4474c367852c44815fca23ac4e48b72b339807f80", + "sha256:a139fe9f298dc097349fb4f28c8b81cc7a202dbfba66af0e14be5cfca4ef7ce5", + "sha256:a32204489259786a923e02990249c65b0f17235073149d0033efcebe80095570", + "sha256:a3982b0a32d0a88b3907e4b0dc36809fda477f0757c59a505d4e9b455f384b8b", + "sha256:aad17e462f42ddbef5984d70c40bfc4146c322a2da79715932cd8976317054de", + "sha256:b560b72ed4816aee52783c66854d96157fd8175631f01ef58e894cc57c84f0f6", + "sha256:b6b0e4912030c6f28bcb72b9ebe4989d6dc2eebcd2a9cdc35fefc38052dd4fe8", + "sha256:baf1c7b78cddb5af00971ad5294a4583188bda1495b13760d9f03c9483bb6203", + "sha256:c0295d52b012cbe0d3059b1dba99159c3be55e632aae1999ab74ae2bd86a33d7", + "sha256:c562b49c96906b4029b5685075fe1ebd3b5cc2601dfa0b9e16c2c09d6cbce048", + "sha256:c69567ddbac186e8c0aadc1f324a60a564cfe25e43ef2ce81bcc4b8c3abffbae", + "sha256:ca71d501629d1fa50ea7fa3b08ba884fe10cefc559f5c6c8dfe9036c16e8ae89", + "sha256:ca976884ce34070799e4dfc6fbd68cb1d181db1eefe4a3a94798ddfb34b8867f", + "sha256:d0491006a6ad20507aec2be72e7831a42efc93193d2402018007ff827dc62926", + "sha256:d074b07a10c391fc5bbdcb37b2f16f20fcd9e51e10d01652ab298c0d07908ee2", + "sha256:d2ce426ee691319d4767748c8e0895cfc56593d725594e415f274059bcf3cb76", + "sha256:d4284c621f06a72ce2cb55f74ea3150113d926a6eb78ab38340c08f770eb9b4d", + "sha256:d5e6b7155b8197b329dc787356cfd2684c9d6a6b1a197f6bbf45f5555a98d411", + "sha256:d816f44a51ba5175394bc6c7879ca0bd2be560b2c9e9f3411ef3a4cbe644c2e9", + "sha256:dd3f79e17b56741b5177bcc36307750d50ea0698df6aa82f69c7db32d968c1c2", + "sha256:dd63cec4e26e790b70544ae5cc48d11b515b09e05fdd5eff12e3195f54b8a586", + "sha256:de9d3e8717560eb05e28739d1b35e4eac2e458553a52a301e51352a7ffc86a35", + "sha256:df4249b579e75094f7e9bb4bd28231acf55e308bf686b952f43100a5a0be394c", + "sha256:e178e5b66a06ec5bf51668ec0d4ac8cfb2bdcb553b2c207d58148340efd00143", + "sha256:e60defc3c15defb70bb38dd605ff7e0fae5f6c9c7cbfe0ad7868582cb7e844a6", + "sha256:ee2794111c188548a4547eccc73a6a8527fe2af6cf25e1a4ebda2fd01cdd2e60", + "sha256:ee7ccc7fb7e921d767f853b47814c3048c7de536663e82fbc37f5eb0d532224b", + "sha256:ee9cf33e7fe14243f5ca6977658eb7d1042caaa66847daacbd2117adb258b226", + "sha256:f0f17814c505f07806e22b28856c59ac80cee7dd0fbb152aed273e116378f519", + "sha256:f3202a429fe825b699c57892d4371c74cc3456d8d71b7f35d6028c96dfecad31", + "sha256:f7054fdc556f5421f01e39cbb767d5ec5c1139ea98c3e5b350e02e62201740c7", + "sha256:fd1a9edb9dd9d79fbeac1ea1f9a8dd527a6113b18d2e9bcc0d541d308dae639b" + ], + "markers": "python_version >= '3.8'", + "version": "==2.18.1" + }, + "pyunormalize": { + "hashes": [ + "sha256:cf4a87451a0f1cb76911aa97f432f4579e1f564a2f0c84ce488c73a73901b6c1" + ], + "markers": "python_version >= '3.6'", + "version": "==15.1.0" + }, + "referencing": { + "hashes": [ + "sha256:5773bd84ef41799a5a8ca72dc34590c041eb01bf9aa02632b4a973fb0181a844", + "sha256:d53ae300ceddd3169f1ffa9caf2cb7b769e92657e4fafb23d34b93679116dfd4" + ], + "markers": "python_version >= '3.8'", + "version": "==0.34.0" + }, + "regex": { + "hashes": [ + "sha256:0694219a1d54336fd0445ea382d49d36882415c0134ee1e8332afd1529f0baa5", + "sha256:086dd15e9435b393ae06f96ab69ab2d333f5d65cbe65ca5a3ef0ec9564dfe770", + "sha256:094ba386bb5c01e54e14434d4caabf6583334090865b23ef58e0424a6286d3dc", + "sha256:09da66917262d9481c719599116c7dc0c321ffcec4b1f510c4f8a066f8768105", + "sha256:0ecf44ddf9171cd7566ef1768047f6e66975788258b1c6c6ca78098b95cf9a3d", + "sha256:0fda75704357805eb953a3ee15a2b240694a9a514548cd49b3c5124b4e2ad01b", + "sha256:11a963f8e25ab5c61348d090bf1b07f1953929c13bd2309a0662e9ff680763c9", + "sha256:150c39f5b964e4d7dba46a7962a088fbc91f06e606f023ce57bb347a3b2d4630", + "sha256:1b9d811f72210fa9306aeb88385b8f8bcef0dfbf3873410413c00aa94c56c2b6", + "sha256:1e0eabac536b4cc7f57a5f3d095bfa557860ab912f25965e08fe1545e2ed8b4c", + "sha256:22a86d9fff2009302c440b9d799ef2fe322416d2d58fc124b926aa89365ec482", + "sha256:22f3470f7524b6da61e2020672df2f3063676aff444db1daa283c2ea4ed259d6", + "sha256:263ef5cc10979837f243950637fffb06e8daed7f1ac1e39d5910fd29929e489a", + "sha256:283fc8eed679758de38fe493b7d7d84a198b558942b03f017b1f94dda8efae80", + "sha256:29171aa128da69afdf4bde412d5bedc335f2ca8fcfe4489038577d05f16181e5", + "sha256:298dc6354d414bc921581be85695d18912bea163a8b23cac9a2562bbcd5088b1", + "sha256:2aae8101919e8aa05ecfe6322b278f41ce2994c4a430303c4cd163fef746e04f", + "sha256:2f4e475a80ecbd15896a976aa0b386c5525d0ed34d5c600b6d3ebac0a67c7ddf", + "sha256:34e4af5b27232f68042aa40a91c3b9bb4da0eeb31b7632e0091afc4310afe6cb", + "sha256:37f8e93a81fc5e5bd8db7e10e62dc64261bcd88f8d7e6640aaebe9bc180d9ce2", + "sha256:3a17d3ede18f9cedcbe23d2daa8a2cd6f59fe2bf082c567e43083bba3fb00347", + "sha256:3b1de218d5375cd6ac4b5493e0b9f3df2be331e86520f23382f216c137913d20", + "sha256:43f7cd5754d02a56ae4ebb91b33461dc67be8e3e0153f593c509e21d219c5060", + "sha256:4558410b7a5607a645e9804a3e9dd509af12fb72b9825b13791a37cd417d73a5", + "sha256:4719bb05094d7d8563a450cf8738d2e1061420f79cfcc1fa7f0a44744c4d8f73", + "sha256:4bfc2b16e3ba8850e0e262467275dd4d62f0d045e0e9eda2bc65078c0110a11f", + "sha256:518440c991f514331f4850a63560321f833979d145d7d81186dbe2f19e27ae3d", + "sha256:51f4b32f793812714fd5307222a7f77e739b9bc566dc94a18126aba3b92b98a3", + "sha256:531ac6cf22b53e0696f8e1d56ce2396311254eb806111ddd3922c9d937151dae", + "sha256:5cd05d0f57846d8ba4b71d9c00f6f37d6b97d5e5ef8b3c3840426a475c8f70f4", + "sha256:5dd58946bce44b53b06d94aa95560d0b243eb2fe64227cba50017a8d8b3cd3e2", + "sha256:60080bb3d8617d96f0fb7e19796384cc2467447ef1c491694850ebd3670bc457", + "sha256:636ba0a77de609d6510235b7f0e77ec494d2657108f777e8765efc060094c98c", + "sha256:67d3ccfc590e5e7197750fcb3a2915b416a53e2de847a728cfa60141054123d4", + "sha256:68191f80a9bad283432385961d9efe09d783bcd36ed35a60fb1ff3f1ec2efe87", + "sha256:7502534e55c7c36c0978c91ba6f61703faf7ce733715ca48f499d3dbbd7657e0", + "sha256:7aa47c2e9ea33a4a2a05f40fcd3ea36d73853a2aae7b4feab6fc85f8bf2c9704", + "sha256:7d2af3f6b8419661a0c421584cfe8aaec1c0e435ce7e47ee2a97e344b98f794f", + "sha256:7e316026cc1095f2a3e8cc012822c99f413b702eaa2ca5408a513609488cb62f", + "sha256:88ad44e220e22b63b0f8f81f007e8abbb92874d8ced66f32571ef8beb0643b2b", + "sha256:88d1f7bef20c721359d8675f7d9f8e414ec5003d8f642fdfd8087777ff7f94b5", + "sha256:89723d2112697feaa320c9d351e5f5e7b841e83f8b143dba8e2d2b5f04e10923", + "sha256:8a0ccf52bb37d1a700375a6b395bff5dd15c50acb745f7db30415bae3c2b0715", + "sha256:8c2c19dae8a3eb0ea45a8448356ed561be843b13cbc34b840922ddf565498c1c", + "sha256:905466ad1702ed4acfd67a902af50b8db1feeb9781436372261808df7a2a7bca", + "sha256:9852b76ab558e45b20bf1893b59af64a28bd3820b0c2efc80e0a70a4a3ea51c1", + "sha256:98a2636994f943b871786c9e82bfe7883ecdaba2ef5df54e1450fa9869d1f756", + "sha256:9aa1a67bbf0f957bbe096375887b2505f5d8ae16bf04488e8b0f334c36e31360", + "sha256:9eda5f7a50141291beda3edd00abc2d4a5b16c29c92daf8d5bd76934150f3edc", + "sha256:a6d1047952c0b8104a1d371f88f4ab62e6275567d4458c1e26e9627ad489b445", + "sha256:a9b6d73353f777630626f403b0652055ebfe8ff142a44ec2cf18ae470395766e", + "sha256:a9cc99d6946d750eb75827cb53c4371b8b0fe89c733a94b1573c9dd16ea6c9e4", + "sha256:ad83e7545b4ab69216cef4cc47e344d19622e28aabec61574b20257c65466d6a", + "sha256:b014333bd0217ad3d54c143de9d4b9a3ca1c5a29a6d0d554952ea071cff0f1f8", + "sha256:b43523d7bc2abd757119dbfb38af91b5735eea45537ec6ec3a5ec3f9562a1c53", + "sha256:b521dcecebc5b978b447f0f69b5b7f3840eac454862270406a39837ffae4e697", + "sha256:b77e27b79448e34c2c51c09836033056a0547aa360c45eeeb67803da7b0eedaf", + "sha256:b7a635871143661feccce3979e1727c4e094f2bdfd3ec4b90dfd4f16f571a87a", + "sha256:b7fca9205b59c1a3d5031f7e64ed627a1074730a51c2a80e97653e3e9fa0d415", + "sha256:ba1b30765a55acf15dce3f364e4928b80858fa8f979ad41f862358939bdd1f2f", + "sha256:ba99d8077424501b9616b43a2d208095746fb1284fc5ba490139651f971d39d9", + "sha256:c25a8ad70e716f96e13a637802813f65d8a6760ef48672aa3502f4c24ea8b400", + "sha256:c3c4a78615b7762740531c27cf46e2f388d8d727d0c0c739e72048beb26c8a9d", + "sha256:c40281f7d70baf6e0db0c2f7472b31609f5bc2748fe7275ea65a0b4601d9b392", + "sha256:c7ad32824b7f02bb3c9f80306d405a1d9b7bb89362d68b3c5a9be53836caebdb", + "sha256:cb3fe77aec8f1995611f966d0c656fdce398317f850d0e6e7aebdfe61f40e1cd", + "sha256:cc038b2d8b1470364b1888a98fd22d616fba2b6309c5b5f181ad4483e0017861", + "sha256:cc37b9aeebab425f11f27e5e9e6cf580be7206c6582a64467a14dda211abc232", + "sha256:cc6bb9aa69aacf0f6032c307da718f61a40cf970849e471254e0e91c56ffca95", + "sha256:d126361607b33c4eb7b36debc173bf25d7805847346dd4d99b5499e1fef52bc7", + "sha256:d15b274f9e15b1a0b7a45d2ac86d1f634d983ca40d6b886721626c47a400bf39", + "sha256:d166eafc19f4718df38887b2bbe1467a4f74a9830e8605089ea7a30dd4da8887", + "sha256:d498eea3f581fbe1b34b59c697512a8baef88212f92e4c7830fcc1499f5b45a5", + "sha256:d6f7e255e5fa94642a0724e35406e6cb7001c09d476ab5fce002f652b36d0c39", + "sha256:d78bd484930c1da2b9679290a41cdb25cc127d783768a0369d6b449e72f88beb", + "sha256:d865984b3f71f6d0af64d0d88f5733521698f6c16f445bb09ce746c92c97c586", + "sha256:d902a43085a308cef32c0d3aea962524b725403fd9373dea18110904003bac97", + "sha256:d94a1db462d5690ebf6ae86d11c5e420042b9898af5dcf278bd97d6bda065423", + "sha256:da695d75ac97cb1cd725adac136d25ca687da4536154cdc2815f576e4da11c69", + "sha256:db2a0b1857f18b11e3b0e54ddfefc96af46b0896fb678c85f63fb8c37518b3e7", + "sha256:df26481f0c7a3f8739fecb3e81bc9da3fcfae34d6c094563b9d4670b047312e1", + "sha256:e14b73607d6231f3cc4622809c196b540a6a44e903bcfad940779c80dffa7be7", + "sha256:e2610e9406d3b0073636a3a2e80db05a02f0c3169b5632022b4e81c0364bcda5", + "sha256:e692296c4cc2873967771345a876bcfc1c547e8dd695c6b89342488b0ea55cd8", + "sha256:e693e233ac92ba83a87024e1d32b5f9ab15ca55ddd916d878146f4e3406b5c91", + "sha256:e81469f7d01efed9b53740aedd26085f20d49da65f9c1f41e822a33992cb1590", + "sha256:e8c7e08bb566de4faaf11984af13f6bcf6a08f327b13631d41d62592681d24fe", + "sha256:ed19b3a05ae0c97dd8f75a5d8f21f7723a8c33bbc555da6bbe1f96c470139d3c", + "sha256:efb2d82f33b2212898f1659fb1c2e9ac30493ac41e4d53123da374c3b5541e64", + "sha256:f44dd4d68697559d007462b0a3a1d9acd61d97072b71f6d1968daef26bc744bd", + "sha256:f72cbae7f6b01591f90814250e636065850c5926751af02bb48da94dfced7baa", + "sha256:f7bc09bc9c29ebead055bcba136a67378f03d66bf359e87d0f7c759d6d4ffa31", + "sha256:ff100b203092af77d1a5a7abe085b3506b7eaaf9abf65b73b7d6905b6cb76988" + ], + "markers": "python_version >= '3.7'", + "version": "==2023.12.25" + }, + "requests": { + "hashes": [ + "sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f", + "sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1" + ], + "markers": "python_version >= '3.7'", + "version": "==2.31.0" + }, + "rlp": { + "hashes": [ + "sha256:1747fd933e054e6d25abfe591be92e19a4193a56c93981c05bd0f84dfe279f14", + "sha256:61a5541f86e4684ab145cb849a5929d2ced8222930a570b3941cf4af16b72a78" + ], + "markers": "python_version >= '3.8' and python_version < '4'", + "version": "==4.0.0" + }, + "rpds-py": { + "hashes": [ + "sha256:01e36a39af54a30f28b73096dd39b6802eddd04c90dbe161c1b8dbe22353189f", + "sha256:044a3e61a7c2dafacae99d1e722cc2d4c05280790ec5a05031b3876809d89a5c", + "sha256:08231ac30a842bd04daabc4d71fddd7e6d26189406d5a69535638e4dcb88fe76", + "sha256:08f9ad53c3f31dfb4baa00da22f1e862900f45908383c062c27628754af2e88e", + "sha256:0ab39c1ba9023914297dd88ec3b3b3c3f33671baeb6acf82ad7ce883f6e8e157", + "sha256:0af039631b6de0397ab2ba16eaf2872e9f8fca391b44d3d8cac317860a700a3f", + "sha256:0b8612cd233543a3781bc659c731b9d607de65890085098986dfd573fc2befe5", + "sha256:11a8c85ef4a07a7638180bf04fe189d12757c696eb41f310d2426895356dcf05", + "sha256:1374f4129f9bcca53a1bba0bb86bf78325a0374577cf7e9e4cd046b1e6f20e24", + "sha256:1d4acf42190d449d5e89654d5c1ed3a4f17925eec71f05e2a41414689cda02d1", + "sha256:1d9a5be316c15ffb2b3c405c4ff14448c36b4435be062a7f578ccd8b01f0c4d8", + "sha256:1df3659d26f539ac74fb3b0c481cdf9d725386e3552c6fa2974f4d33d78e544b", + "sha256:22806714311a69fd0af9b35b7be97c18a0fc2826e6827dbb3a8c94eac6cf7eeb", + "sha256:2644e47de560eb7bd55c20fc59f6daa04682655c58d08185a9b95c1970fa1e07", + "sha256:2e6d75ab12b0bbab7215e5d40f1e5b738aa539598db27ef83b2ec46747df90e1", + "sha256:30f43887bbae0d49113cbaab729a112251a940e9b274536613097ab8b4899cf6", + "sha256:34b18ba135c687f4dac449aa5157d36e2cbb7c03cbea4ddbd88604e076aa836e", + "sha256:36b3ee798c58ace201289024b52788161e1ea133e4ac93fba7d49da5fec0ef9e", + "sha256:39514da80f971362f9267c600b6d459bfbbc549cffc2cef8e47474fddc9b45b1", + "sha256:39f5441553f1c2aed4de4377178ad8ff8f9d733723d6c66d983d75341de265ab", + "sha256:3a96e0c6a41dcdba3a0a581bbf6c44bb863f27c541547fb4b9711fd8cf0ffad4", + "sha256:3f26b5bd1079acdb0c7a5645e350fe54d16b17bfc5e71f371c449383d3342e17", + "sha256:41ef53e7c58aa4ef281da975f62c258950f54b76ec8e45941e93a3d1d8580594", + "sha256:42821446ee7a76f5d9f71f9e33a4fb2ffd724bb3e7f93386150b61a43115788d", + "sha256:43fbac5f22e25bee1d482c97474f930a353542855f05c1161fd804c9dc74a09d", + "sha256:4457a94da0d5c53dc4b3e4de1158bdab077db23c53232f37a3cb7afdb053a4e3", + "sha256:465a3eb5659338cf2a9243e50ad9b2296fa15061736d6e26240e713522b6235c", + "sha256:482103aed1dfe2f3b71a58eff35ba105289b8d862551ea576bd15479aba01f66", + "sha256:4832d7d380477521a8c1644bbab6588dfedea5e30a7d967b5fb75977c45fd77f", + "sha256:4901165d170a5fde6f589acb90a6b33629ad1ec976d4529e769c6f3d885e3e80", + "sha256:5307def11a35f5ae4581a0b658b0af8178c65c530e94893345bebf41cc139d33", + "sha256:5417558f6887e9b6b65b4527232553c139b57ec42c64570569b155262ac0754f", + "sha256:56a737287efecafc16f6d067c2ea0117abadcd078d58721f967952db329a3e5c", + "sha256:586f8204935b9ec884500498ccc91aa869fc652c40c093bd9e1471fbcc25c022", + "sha256:5b4e7d8d6c9b2e8ee2d55c90b59c707ca59bc30058269b3db7b1f8df5763557e", + "sha256:5ddcba87675b6d509139d1b521e0c8250e967e63b5909a7e8f8944d0f90ff36f", + "sha256:618a3d6cae6ef8ec88bb76dd80b83cfe415ad4f1d942ca2a903bf6b6ff97a2da", + "sha256:635dc434ff724b178cb192c70016cc0ad25a275228f749ee0daf0eddbc8183b1", + "sha256:661d25cbffaf8cc42e971dd570d87cb29a665f49f4abe1f9e76be9a5182c4688", + "sha256:66e6a3af5a75363d2c9a48b07cb27c4ea542938b1a2e93b15a503cdfa8490795", + "sha256:67071a6171e92b6da534b8ae326505f7c18022c6f19072a81dcf40db2638767c", + "sha256:685537e07897f173abcf67258bee3c05c374fa6fff89d4c7e42fb391b0605e98", + "sha256:69e64831e22a6b377772e7fb337533c365085b31619005802a79242fee620bc1", + "sha256:6b0817e34942b2ca527b0e9298373e7cc75f429e8da2055607f4931fded23e20", + "sha256:6c81e5f372cd0dc5dc4809553d34f832f60a46034a5f187756d9b90586c2c307", + "sha256:6d7faa6f14017c0b1e69f5e2c357b998731ea75a442ab3841c0dbbbfe902d2c4", + "sha256:6ef0befbb5d79cf32d0266f5cff01545602344eda89480e1dd88aca964260b18", + "sha256:6ef687afab047554a2d366e112dd187b62d261d49eb79b77e386f94644363294", + "sha256:7223a2a5fe0d217e60a60cdae28d6949140dde9c3bcc714063c5b463065e3d66", + "sha256:77f195baa60a54ef9d2de16fbbfd3ff8b04edc0c0140a761b56c267ac11aa467", + "sha256:793968759cd0d96cac1e367afd70c235867831983f876a53389ad869b043c948", + "sha256:7bd339195d84439cbe5771546fe8a4e8a7a045417d8f9de9a368c434e42a721e", + "sha256:7cd863afe7336c62ec78d7d1349a2f34c007a3cc6c2369d667c65aeec412a5b1", + "sha256:7f2facbd386dd60cbbf1a794181e6aa0bd429bd78bfdf775436020172e2a23f0", + "sha256:84ffab12db93b5f6bad84c712c92060a2d321b35c3c9960b43d08d0f639d60d7", + "sha256:8c8370641f1a7f0e0669ddccca22f1da893cef7628396431eb445d46d893e5cd", + "sha256:8db715ebe3bb7d86d77ac1826f7d67ec11a70dbd2376b7cc214199360517b641", + "sha256:8e8916ae4c720529e18afa0b879473049e95949bf97042e938530e072fde061d", + "sha256:8f03bccbd8586e9dd37219bce4d4e0d3ab492e6b3b533e973fa08a112cb2ffc9", + "sha256:8f2fc11e8fe034ee3c34d316d0ad8808f45bc3b9ce5857ff29d513f3ff2923a1", + "sha256:923d39efa3cfb7279a0327e337a7958bff00cc447fd07a25cddb0a1cc9a6d2da", + "sha256:93df1de2f7f7239dc9cc5a4a12408ee1598725036bd2dedadc14d94525192fc3", + "sha256:998e33ad22dc7ec7e030b3df701c43630b5bc0d8fbc2267653577e3fec279afa", + "sha256:99f70b740dc04d09e6b2699b675874367885217a2e9f782bdf5395632ac663b7", + "sha256:9a00312dea9310d4cb7dbd7787e722d2e86a95c2db92fbd7d0155f97127bcb40", + "sha256:9d54553c1136b50fd12cc17e5b11ad07374c316df307e4cfd6441bea5fb68496", + "sha256:9dbbeb27f4e70bfd9eec1be5477517365afe05a9b2c441a0b21929ee61048124", + "sha256:a1ce3ba137ed54f83e56fb983a5859a27d43a40188ba798993812fed73c70836", + "sha256:a34d557a42aa28bd5c48a023c570219ba2593bcbbb8dc1b98d8cf5d529ab1434", + "sha256:a5f446dd5055667aabaee78487f2b5ab72e244f9bc0b2ffebfeec79051679984", + "sha256:ad36cfb355e24f1bd37cac88c112cd7730873f20fb0bdaf8ba59eedf8216079f", + "sha256:aec493917dd45e3c69d00a8874e7cbed844efd935595ef78a0f25f14312e33c6", + "sha256:b316144e85316da2723f9d8dc75bada12fa58489a527091fa1d5a612643d1a0e", + "sha256:b34ae4636dfc4e76a438ab826a0d1eed2589ca7d9a1b2d5bb546978ac6485461", + "sha256:b34b7aa8b261c1dbf7720b5d6f01f38243e9b9daf7e6b8bc1fd4657000062f2c", + "sha256:bc362ee4e314870a70f4ae88772d72d877246537d9f8cb8f7eacf10884862432", + "sha256:bed88b9a458e354014d662d47e7a5baafd7ff81c780fd91584a10d6ec842cb73", + "sha256:c0013fe6b46aa496a6749c77e00a3eb07952832ad6166bd481c74bda0dcb6d58", + "sha256:c0b5dcf9193625afd8ecc92312d6ed78781c46ecbf39af9ad4681fc9f464af88", + "sha256:c4325ff0442a12113a6379af66978c3fe562f846763287ef66bdc1d57925d337", + "sha256:c463ed05f9dfb9baebef68048aed8dcdc94411e4bf3d33a39ba97e271624f8f7", + "sha256:c8362467a0fdeccd47935f22c256bec5e6abe543bf0d66e3d3d57a8fb5731863", + "sha256:cd5bf1af8efe569654bbef5a3e0a56eca45f87cfcffab31dd8dde70da5982475", + "sha256:cf1ea2e34868f6fbf070e1af291c8180480310173de0b0c43fc38a02929fc0e3", + "sha256:d62dec4976954a23d7f91f2f4530852b0c7608116c257833922a896101336c51", + "sha256:d68c93e381010662ab873fea609bf6c0f428b6d0bb00f2c6939782e0818d37bf", + "sha256:d7c36232a90d4755b720fbd76739d8891732b18cf240a9c645d75f00639a9024", + "sha256:dd18772815d5f008fa03d2b9a681ae38d5ae9f0e599f7dda233c439fcaa00d40", + "sha256:ddc2f4dfd396c7bfa18e6ce371cba60e4cf9d2e5cdb71376aa2da264605b60b9", + "sha256:e003b002ec72c8d5a3e3da2989c7d6065b47d9eaa70cd8808b5384fbb970f4ec", + "sha256:e32a92116d4f2a80b629778280103d2a510a5b3f6314ceccd6e38006b5e92dcb", + "sha256:e4461d0f003a0aa9be2bdd1b798a041f177189c1a0f7619fe8c95ad08d9a45d7", + "sha256:e541ec6f2ec456934fd279a3120f856cd0aedd209fc3852eca563f81738f6861", + "sha256:e546e768d08ad55b20b11dbb78a745151acbd938f8f00d0cfbabe8b0199b9880", + "sha256:ea7d4a99f3b38c37eac212dbd6ec42b7a5ec51e2c74b5d3223e43c811609e65f", + "sha256:ed4eb745efbff0a8e9587d22a84be94a5eb7d2d99c02dacf7bd0911713ed14dd", + "sha256:f8a2f084546cc59ea99fda8e070be2fd140c3092dc11524a71aa8f0f3d5a55ca", + "sha256:fcb25daa9219b4cf3a0ab24b0eb9a5cc8949ed4dc72acb8fa16b7e1681aa3c58", + "sha256:fdea4952db2793c4ad0bdccd27c1d8fdd1423a92f04598bc39425bcc2b8ee46e" + ], + "markers": "python_version >= '3.8'", + "version": "==0.18.0" + }, + "toolz": { + "hashes": [ + "sha256:d22731364c07d72eea0a0ad45bafb2c2937ab6fd38a3507bf55eae8744aa7d85", + "sha256:ecca342664893f177a13dac0e6b41cbd8ac25a358e5f215316d43e2100224f4d" + ], + "markers": "python_version >= '3.7'", + "version": "==0.12.1" + }, + "typing-extensions": { + "hashes": [ + "sha256:83f085bd5ca59c80295fc2a82ab5dac679cbe02b9f33f7d83af68e241bea51b0", + "sha256:c1f94d72897edaf4ce775bb7558d5b79d8126906a14ea5ed1635921406c0387a" + ], + "markers": "python_version >= '3.8'", + "version": "==4.11.0" + }, + "urllib3": { + "hashes": [ + "sha256:450b20ec296a467077128bff42b73080516e71b56ff59a60a02bef2232c4fa9d", + "sha256:d0570876c61ab9e520d776c38acbbb5b05a776d3f9ff98a5c8fd5162a444cf19" + ], + "markers": "python_version >= '3.8'", + "version": "==2.2.1" + }, + "validators": { + "hashes": [ + "sha256:24148ce4e64100a2d5e267233e23e7afeb55316b47d30faae7eb6e7292bc226a" + ], + "index": "pypi", + "markers": "python_version >= '3.4'", + "version": "==0.20.0" + }, + "web3": { + "hashes": [ + "sha256:5db6ed63bca526a5542d9a84778c8b549199009b9f71079ff53a7b82dadf0d0b", + "sha256:cc383afaf4cfde9fd6c8934ec23c47035520129ee93e4e068520e565d31dee9e" + ], + "index": "pypi", + "markers": "python_full_version >= '3.7.2'", + "version": "==6.8.0" + }, + "websockets": { + "hashes": [ + "sha256:00700340c6c7ab788f176d118775202aadea7602c5cc6be6ae127761c16d6b0b", + "sha256:0bee75f400895aef54157b36ed6d3b308fcab62e5260703add87f44cee9c82a6", + "sha256:0e6e2711d5a8e6e482cacb927a49a3d432345dfe7dea8ace7b5790df5932e4df", + "sha256:12743ab88ab2af1d17dd4acb4645677cb7063ef4db93abffbf164218a5d54c6b", + "sha256:1a9d160fd080c6285e202327aba140fc9a0d910b09e423afff4ae5cbbf1c7205", + "sha256:1bf386089178ea69d720f8db6199a0504a406209a0fc23e603b27b300fdd6892", + "sha256:1df2fbd2c8a98d38a66f5238484405b8d1d16f929bb7a33ed73e4801222a6f53", + "sha256:1e4b3f8ea6a9cfa8be8484c9221ec0257508e3a1ec43c36acdefb2a9c3b00aa2", + "sha256:1f38a7b376117ef7aff996e737583172bdf535932c9ca021746573bce40165ed", + "sha256:23509452b3bc38e3a057382c2e941d5ac2e01e251acce7adc74011d7d8de434c", + "sha256:248d8e2446e13c1d4326e0a6a4e9629cb13a11195051a73acf414812700badbd", + "sha256:25eb766c8ad27da0f79420b2af4b85d29914ba0edf69f547cc4f06ca6f1d403b", + "sha256:27a5e9964ef509016759f2ef3f2c1e13f403725a5e6a1775555994966a66e931", + "sha256:2c71bd45a777433dd9113847af751aae36e448bc6b8c361a566cb043eda6ec30", + "sha256:2cb388a5bfb56df4d9a406783b7f9dbefb888c09b71629351cc6b036e9259370", + "sha256:2d225bb6886591b1746b17c0573e29804619c8f755b5598d875bb4235ea639be", + "sha256:2e5fc14ec6ea568200ea4ef46545073da81900a2b67b3e666f04adf53ad452ec", + "sha256:363f57ca8bc8576195d0540c648aa58ac18cf85b76ad5202b9f976918f4219cf", + "sha256:3c6cc1360c10c17463aadd29dd3af332d4a1adaa8796f6b0e9f9df1fdb0bad62", + "sha256:3d829f975fc2e527a3ef2f9c8f25e553eb7bc779c6665e8e1d52aa22800bb38b", + "sha256:3e3aa8c468af01d70332a382350ee95f6986db479ce7af14d5e81ec52aa2b402", + "sha256:3f61726cae9f65b872502ff3c1496abc93ffbe31b278455c418492016e2afc8f", + "sha256:423fc1ed29f7512fceb727e2d2aecb952c46aa34895e9ed96071821309951123", + "sha256:46e71dbbd12850224243f5d2aeec90f0aaa0f2dde5aeeb8fc8df21e04d99eff9", + "sha256:4d87be612cbef86f994178d5186add3d94e9f31cc3cb499a0482b866ec477603", + "sha256:5693ef74233122f8ebab026817b1b37fe25c411ecfca084b29bc7d6efc548f45", + "sha256:5aa9348186d79a5f232115ed3fa9020eab66d6c3437d72f9d2c8ac0c6858c558", + "sha256:5d873c7de42dea355d73f170be0f23788cf3fa9f7bed718fd2830eefedce01b4", + "sha256:5f6ffe2c6598f7f7207eef9a1228b6f5c818f9f4d53ee920aacd35cec8110438", + "sha256:604428d1b87edbf02b233e2c207d7d528460fa978f9e391bd8aaf9c8311de137", + "sha256:6350b14a40c95ddd53e775dbdbbbc59b124a5c8ecd6fbb09c2e52029f7a9f480", + "sha256:6e2df67b8014767d0f785baa98393725739287684b9f8d8a1001eb2839031447", + "sha256:6e96f5ed1b83a8ddb07909b45bd94833b0710f738115751cdaa9da1fb0cb66e8", + "sha256:6e9e7db18b4539a29cc5ad8c8b252738a30e2b13f033c2d6e9d0549b45841c04", + "sha256:70ec754cc2a769bcd218ed8d7209055667b30860ffecb8633a834dde27d6307c", + "sha256:7b645f491f3c48d3f8a00d1fce07445fab7347fec54a3e65f0725d730d5b99cb", + "sha256:7fa3d25e81bfe6a89718e9791128398a50dec6d57faf23770787ff441d851967", + "sha256:81df9cbcbb6c260de1e007e58c011bfebe2dafc8435107b0537f393dd38c8b1b", + "sha256:8572132c7be52632201a35f5e08348137f658e5ffd21f51f94572ca6c05ea81d", + "sha256:87b4aafed34653e465eb77b7c93ef058516cb5acf3eb21e42f33928616172def", + "sha256:8e332c210b14b57904869ca9f9bf4ca32f5427a03eeb625da9b616c85a3a506c", + "sha256:9893d1aa45a7f8b3bc4510f6ccf8db8c3b62120917af15e3de247f0780294b92", + "sha256:9edf3fc590cc2ec20dc9d7a45108b5bbaf21c0d89f9fd3fd1685e223771dc0b2", + "sha256:9fdf06fd06c32205a07e47328ab49c40fc1407cdec801d698a7c41167ea45113", + "sha256:a02413bc474feda2849c59ed2dfb2cddb4cd3d2f03a2fedec51d6e959d9b608b", + "sha256:a1d9697f3337a89691e3bd8dc56dea45a6f6d975f92e7d5f773bc715c15dde28", + "sha256:a571f035a47212288e3b3519944f6bf4ac7bc7553243e41eac50dd48552b6df7", + "sha256:ab3d732ad50a4fbd04a4490ef08acd0517b6ae6b77eb967251f4c263011a990d", + "sha256:ae0a5da8f35a5be197f328d4727dbcfafa53d1824fac3d96cdd3a642fe09394f", + "sha256:b067cb952ce8bf40115f6c19f478dc71c5e719b7fbaa511359795dfd9d1a6468", + "sha256:b2ee7288b85959797970114deae81ab41b731f19ebcd3bd499ae9ca0e3f1d2c8", + "sha256:b81f90dcc6c85a9b7f29873beb56c94c85d6f0dac2ea8b60d995bd18bf3e2aae", + "sha256:ba0cab91b3956dfa9f512147860783a1829a8d905ee218a9837c18f683239611", + "sha256:baa386875b70cbd81798fa9f71be689c1bf484f65fd6fb08d051a0ee4e79924d", + "sha256:bbe6013f9f791944ed31ca08b077e26249309639313fff132bfbf3ba105673b9", + "sha256:bea88d71630c5900690fcb03161ab18f8f244805c59e2e0dc4ffadae0a7ee0ca", + "sha256:befe90632d66caaf72e8b2ed4d7f02b348913813c8b0a32fae1cc5fe3730902f", + "sha256:c3181df4583c4d3994d31fb235dc681d2aaad744fbdbf94c4802485ececdecf2", + "sha256:c4e37d36f0d19f0a4413d3e18c0d03d0c268ada2061868c1e6f5ab1a6d575077", + "sha256:c588f6abc13f78a67044c6b1273a99e1cf31038ad51815b3b016ce699f0d75c2", + "sha256:cbe83a6bbdf207ff0541de01e11904827540aa069293696dd528a6640bd6a5f6", + "sha256:d554236b2a2006e0ce16315c16eaa0d628dab009c33b63ea03f41c6107958374", + "sha256:dbcf72a37f0b3316e993e13ecf32f10c0e1259c28ffd0a85cee26e8549595fbc", + "sha256:dc284bbc8d7c78a6c69e0c7325ab46ee5e40bb4d50e494d8131a07ef47500e9e", + "sha256:dff6cdf35e31d1315790149fee351f9e52978130cef6c87c4b6c9b3baf78bc53", + "sha256:e469d01137942849cff40517c97a30a93ae79917752b34029f0ec72df6b46399", + "sha256:eb809e816916a3b210bed3c82fb88eaf16e8afcf9c115ebb2bacede1797d2547", + "sha256:ed2fcf7a07334c77fc8a230755c2209223a7cc44fc27597729b8ef5425aa61a3", + "sha256:f44069528d45a933997a6fef143030d8ca8042f0dfaad753e2906398290e2870", + "sha256:f764ba54e33daf20e167915edc443b6f88956f37fb606449b4a5b10ba42235a5", + "sha256:fc4e7fa5414512b481a2483775a8e8be7803a35b30ca805afa4998a84f9fd9e8", + "sha256:ffefa1374cd508d633646d51a8e9277763a9b78ae71324183693959cf94635a7" + ], + "markers": "python_version >= '3.8'", + "version": "==12.0" + }, + "yarl": { + "hashes": [ + "sha256:008d3e808d03ef28542372d01057fd09168419cdc8f848efe2804f894ae03e51", + "sha256:03caa9507d3d3c83bca08650678e25364e1843b484f19986a527630ca376ecce", + "sha256:07574b007ee20e5c375a8fe4a0789fad26db905f9813be0f9fef5a68080de559", + "sha256:09efe4615ada057ba2d30df871d2f668af661e971dfeedf0c159927d48bbeff0", + "sha256:0d2454f0aef65ea81037759be5ca9947539667eecebca092733b2eb43c965a81", + "sha256:0e9d124c191d5b881060a9e5060627694c3bdd1fe24c5eecc8d5d7d0eb6faabc", + "sha256:18580f672e44ce1238b82f7fb87d727c4a131f3a9d33a5e0e82b793362bf18b4", + "sha256:1f23e4fe1e8794f74b6027d7cf19dc25f8b63af1483d91d595d4a07eca1fb26c", + "sha256:206a55215e6d05dbc6c98ce598a59e6fbd0c493e2de4ea6cc2f4934d5a18d130", + "sha256:23d32a2594cb5d565d358a92e151315d1b2268bc10f4610d098f96b147370136", + "sha256:26a1dc6285e03f3cc9e839a2da83bcbf31dcb0d004c72d0730e755b33466c30e", + "sha256:29e0f83f37610f173eb7e7b5562dd71467993495e568e708d99e9d1944f561ec", + "sha256:2b134fd795e2322b7684155b7855cc99409d10b2e408056db2b93b51a52accc7", + "sha256:2d47552b6e52c3319fede1b60b3de120fe83bde9b7bddad11a69fb0af7db32f1", + "sha256:357495293086c5b6d34ca9616a43d329317feab7917518bc97a08f9e55648455", + "sha256:35a2b9396879ce32754bd457d31a51ff0a9d426fd9e0e3c33394bf4b9036b099", + "sha256:3777ce5536d17989c91696db1d459574e9a9bd37660ea7ee4d3344579bb6f129", + "sha256:3986b6f41ad22988e53d5778f91855dc0399b043fc8946d4f2e68af22ee9ff10", + "sha256:44d8ffbb9c06e5a7f529f38f53eda23e50d1ed33c6c869e01481d3fafa6b8142", + "sha256:49a180c2e0743d5d6e0b4d1a9e5f633c62eca3f8a86ba5dd3c471060e352ca98", + "sha256:4aa9741085f635934f3a2583e16fcf62ba835719a8b2b28fb2917bb0537c1dfa", + "sha256:4b21516d181cd77ebd06ce160ef8cc2a5e9ad35fb1c5930882baff5ac865eee7", + "sha256:4b3c1ffe10069f655ea2d731808e76e0f452fc6c749bea04781daf18e6039525", + "sha256:4c7d56b293cc071e82532f70adcbd8b61909eec973ae9d2d1f9b233f3d943f2c", + "sha256:4e9035df8d0880b2f1c7f5031f33f69e071dfe72ee9310cfc76f7b605958ceb9", + "sha256:54525ae423d7b7a8ee81ba189f131054defdb122cde31ff17477951464c1691c", + "sha256:549d19c84c55d11687ddbd47eeb348a89df9cb30e1993f1b128f4685cd0ebbf8", + "sha256:54beabb809ffcacbd9d28ac57b0db46e42a6e341a030293fb3185c409e626b8b", + "sha256:566db86717cf8080b99b58b083b773a908ae40f06681e87e589a976faf8246bf", + "sha256:5a2e2433eb9344a163aced6a5f6c9222c0786e5a9e9cac2c89f0b28433f56e23", + "sha256:5aef935237d60a51a62b86249839b51345f47564208c6ee615ed2a40878dccdd", + "sha256:604f31d97fa493083ea21bd9b92c419012531c4e17ea6da0f65cacdcf5d0bd27", + "sha256:63b20738b5aac74e239622d2fe30df4fca4942a86e31bf47a81a0e94c14df94f", + "sha256:686a0c2f85f83463272ddffd4deb5e591c98aac1897d65e92319f729c320eece", + "sha256:6a962e04b8f91f8c4e5917e518d17958e3bdee71fd1d8b88cdce74dd0ebbf434", + "sha256:6ad6d10ed9b67a382b45f29ea028f92d25bc0bc1daf6c5b801b90b5aa70fb9ec", + "sha256:6f5cb257bc2ec58f437da2b37a8cd48f666db96d47b8a3115c29f316313654ff", + "sha256:6fe79f998a4052d79e1c30eeb7d6c1c1056ad33300f682465e1b4e9b5a188b78", + "sha256:7855426dfbddac81896b6e533ebefc0af2f132d4a47340cee6d22cac7190022d", + "sha256:7d5aaac37d19b2904bb9dfe12cdb08c8443e7ba7d2852894ad448d4b8f442863", + "sha256:801e9264d19643548651b9db361ce3287176671fb0117f96b5ac0ee1c3530d53", + "sha256:81eb57278deb6098a5b62e88ad8281b2ba09f2f1147c4767522353eaa6260b31", + "sha256:824d6c50492add5da9374875ce72db7a0733b29c2394890aef23d533106e2b15", + "sha256:8397a3817d7dcdd14bb266283cd1d6fc7264a48c186b986f32e86d86d35fbac5", + "sha256:848cd2a1df56ddbffeb375535fb62c9d1645dde33ca4d51341378b3f5954429b", + "sha256:84fc30f71689d7fc9168b92788abc977dc8cefa806909565fc2951d02f6b7d57", + "sha256:8619d6915b3b0b34420cf9b2bb6d81ef59d984cb0fde7544e9ece32b4b3043c3", + "sha256:8a854227cf581330ffa2c4824d96e52ee621dd571078a252c25e3a3b3d94a1b1", + "sha256:8be9e837ea9113676e5754b43b940b50cce76d9ed7d2461df1af39a8ee674d9f", + "sha256:928cecb0ef9d5a7946eb6ff58417ad2fe9375762382f1bf5c55e61645f2c43ad", + "sha256:957b4774373cf6f709359e5c8c4a0af9f6d7875db657adb0feaf8d6cb3c3964c", + "sha256:992f18e0ea248ee03b5a6e8b3b4738850ae7dbb172cc41c966462801cbf62cf7", + "sha256:9fc5fc1eeb029757349ad26bbc5880557389a03fa6ada41703db5e068881e5f2", + "sha256:a00862fb23195b6b8322f7d781b0dc1d82cb3bcac346d1e38689370cc1cc398b", + "sha256:a3a6ed1d525bfb91b3fc9b690c5a21bb52de28c018530ad85093cc488bee2dd2", + "sha256:a6327976c7c2f4ee6816eff196e25385ccc02cb81427952414a64811037bbc8b", + "sha256:a7409f968456111140c1c95301cadf071bd30a81cbd7ab829169fb9e3d72eae9", + "sha256:a825ec844298c791fd28ed14ed1bffc56a98d15b8c58a20e0e08c1f5f2bea1be", + "sha256:a8c1df72eb746f4136fe9a2e72b0c9dc1da1cbd23b5372f94b5820ff8ae30e0e", + "sha256:a9bd00dc3bc395a662900f33f74feb3e757429e545d831eef5bb280252631984", + "sha256:aa102d6d280a5455ad6a0f9e6d769989638718e938a6a0a2ff3f4a7ff8c62cc4", + "sha256:aaaea1e536f98754a6e5c56091baa1b6ce2f2700cc4a00b0d49eca8dea471074", + "sha256:ad4d7a90a92e528aadf4965d685c17dacff3df282db1121136c382dc0b6014d2", + "sha256:b8477c1ee4bd47c57d49621a062121c3023609f7a13b8a46953eb6c9716ca392", + "sha256:ba6f52cbc7809cd8d74604cce9c14868306ae4aa0282016b641c661f981a6e91", + "sha256:bac8d525a8dbc2a1507ec731d2867025d11ceadcb4dd421423a5d42c56818541", + "sha256:bef596fdaa8f26e3d66af846bbe77057237cb6e8efff8cd7cc8dff9a62278bbf", + "sha256:c0ec0ed476f77db9fb29bca17f0a8fcc7bc97ad4c6c1d8959c507decb22e8572", + "sha256:c38c9ddb6103ceae4e4498f9c08fac9b590c5c71b0370f98714768e22ac6fa66", + "sha256:c7224cab95645c7ab53791022ae77a4509472613e839dab722a72abe5a684575", + "sha256:c74018551e31269d56fab81a728f683667e7c28c04e807ba08f8c9e3bba32f14", + "sha256:ca06675212f94e7a610e85ca36948bb8fc023e458dd6c63ef71abfd482481aa5", + "sha256:d1d2532b340b692880261c15aee4dc94dd22ca5d61b9db9a8a361953d36410b1", + "sha256:d25039a474c4c72a5ad4b52495056f843a7ff07b632c1b92ea9043a3d9950f6e", + "sha256:d5ff2c858f5f6a42c2a8e751100f237c5e869cbde669a724f2062d4c4ef93551", + "sha256:d7d7f7de27b8944f1fee2c26a88b4dabc2409d2fea7a9ed3df79b67277644e17", + "sha256:d7eeb6d22331e2fd42fce928a81c697c9ee2d51400bd1a28803965883e13cead", + "sha256:d8a1c6c0be645c745a081c192e747c5de06e944a0d21245f4cf7c05e457c36e0", + "sha256:d8b889777de69897406c9fb0b76cdf2fd0f31267861ae7501d93003d55f54fbe", + "sha256:d9e09c9d74f4566e905a0b8fa668c58109f7624db96a2171f21747abc7524234", + "sha256:db8e58b9d79200c76956cefd14d5c90af54416ff5353c5bfd7cbe58818e26ef0", + "sha256:ddb2a5c08a4eaaba605340fdee8fc08e406c56617566d9643ad8bf6852778fc7", + "sha256:e0381b4ce23ff92f8170080c97678040fc5b08da85e9e292292aba67fdac6c34", + "sha256:e23a6d84d9d1738dbc6e38167776107e63307dfc8ad108e580548d1f2c587f42", + "sha256:e516dc8baf7b380e6c1c26792610230f37147bb754d6426462ab115a02944385", + "sha256:ea65804b5dc88dacd4a40279af0cdadcfe74b3e5b4c897aa0d81cf86927fee78", + "sha256:ec61d826d80fc293ed46c9dd26995921e3a82146feacd952ef0757236fc137be", + "sha256:ee04010f26d5102399bd17f8df8bc38dc7ccd7701dc77f4a68c5b8d733406958", + "sha256:f3bc6af6e2b8f92eced34ef6a96ffb248e863af20ef4fde9448cc8c9b858b749", + "sha256:f7d6b36dd2e029b6bcb8a13cf19664c7b8e19ab3a58e0fefbb5b8461447ed5ec" + ], + "markers": "python_version >= '3.7'", + "version": "==1.9.4" + } + }, + "develop": { + "alabaster": { + "hashes": [ + "sha256:75a8b99c28a5dad50dd7f8ccdd447a121ddb3892da9e53d1ca5cca3106d58d65", + "sha256:b46733c07dce03ae4e150330b975c75737fa60f0a7c591b6c8bf4928a28e2c92" + ], + "markers": "python_version >= '3.9'", + "version": "==0.7.16" + }, + "astroid": { + "hashes": [ + "sha256:951798f922990137ac090c53af473db7ab4e70c770e6d7fae0cec59f74411819", + "sha256:ac248253bfa4bd924a0de213707e7ebeeb3138abeb48d798784ead1e56d419d4" + ], + "markers": "python_full_version >= '3.8.0'", + "version": "==3.1.0" + }, + "attrs": { + "hashes": [ + "sha256:935dc3b529c262f6cf76e50877d35a4bd3c1de194fd41f47a2b7ae8f19971f30", + "sha256:99b87a485a5820b23b879f04c2305b44b951b502fd64be915879d77a7e8fc6f1" + ], + "markers": "python_version >= '3.7'", + "version": "==23.2.0" + }, + "babel": { + "hashes": [ + "sha256:6919867db036398ba21eb5c7a0f6b28ab8cbc3ae7a73a44ebe34ae74a4e7d363", + "sha256:efb1a25b7118e67ce3a259bed20545c29cb68be8ad2c784c83689981b7a57287" + ], + "markers": "python_version >= '3.7'", + "version": "==2.14.0" + }, + "black": { + "hashes": [ + "sha256:1bb9ca06e556a09f7f7177bc7cb604e5ed2d2df1e9119e4f7d2f1f7071c32e5d", + "sha256:21f9407063ec71c5580b8ad975653c66508d6a9f57bd008bb8691d273705adcd", + "sha256:4396ca365a4310beef84d446ca5016f671b10f07abdba3e4e4304218d2c71d33", + "sha256:44d99dfdf37a2a00a6f7a8dcbd19edf361d056ee51093b2445de7ca09adac965", + "sha256:5cd5b4f76056cecce3e69b0d4c228326d2595f506797f40b9233424e2524c070", + "sha256:64578cf99b6b46a6301bc28bdb89f9d6f9b592b1c5837818a177c98525dbe397", + "sha256:64e60a7edd71fd542a10a9643bf369bfd2644de95ec71e86790b063aa02ff745", + "sha256:652e55bb722ca026299eb74e53880ee2315b181dfdd44dca98e43448620ddec1", + "sha256:6644f97a7ef6f401a150cca551a1ff97e03c25d8519ee0bbc9b0058772882665", + "sha256:6ad001a9ddd9b8dfd1b434d566be39b1cd502802c8d38bbb1ba612afda2ef436", + "sha256:71d998b73c957444fb7c52096c3843875f4b6b47a54972598741fe9a7f737fcb", + "sha256:74eb9b5420e26b42c00a3ff470dc0cd144b80a766128b1771d07643165e08d0e", + "sha256:75a2d0b4f5eb81f7eebc31f788f9830a6ce10a68c91fbe0fade34fff7a2836e6", + "sha256:7852b05d02b5b9a8c893ab95863ef8986e4dda29af80bbbda94d7aee1abf8702", + "sha256:7f2966b9b2b3b7104fca9d75b2ee856fe3fdd7ed9e47c753a4bb1a675f2caab8", + "sha256:8e5537f456a22cf5cfcb2707803431d2feeb82ab3748ade280d6ccd0b40ed2e8", + "sha256:d4e71cdebdc8efeb6deaf5f2deb28325f8614d48426bed118ecc2dcaefb9ebf3", + "sha256:dae79397f367ac8d7adb6c779813328f6d690943f64b32983e896bcccd18cbad", + "sha256:e3a3a092b8b756c643fe45f4624dbd5a389f770a4ac294cf4d0fce6af86addaf", + "sha256:eb949f56a63c5e134dfdca12091e98ffb5fd446293ebae123d10fc1abad00b9e", + "sha256:f07b69fda20578367eaebbd670ff8fc653ab181e1ff95d84497f9fa20e7d0641", + "sha256:f95cece33329dc4aa3b0e1a771c41075812e46cf3d6e3f1dfe3d91ff09826ed2" + ], + "index": "pypi", + "markers": "python_version >= '3.8'", + "version": "==24.4.0" + }, + "certifi": { + "hashes": [ + "sha256:0569859f95fc761b18b45ef421b1290a0f65f147e92a1e5eb3e635f9a5e4e66f", + "sha256:dc383c07b76109f368f6106eee2b593b04a011ea4d55f652c6ca24a754d1cdd1" + ], + "markers": "python_version >= '3.6'", + "version": "==2024.2.2" + }, + "charset-normalizer": { + "hashes": [ + "sha256:06435b539f889b1f6f4ac1758871aae42dc3a8c0e24ac9e60c2384973ad73027", + "sha256:06a81e93cd441c56a9b65d8e1d043daeb97a3d0856d177d5c90ba85acb3db087", + "sha256:0a55554a2fa0d408816b3b5cedf0045f4b8e1a6065aec45849de2d6f3f8e9786", + "sha256:0b2b64d2bb6d3fb9112bafa732def486049e63de9618b5843bcdd081d8144cd8", + "sha256:10955842570876604d404661fbccbc9c7e684caf432c09c715ec38fbae45ae09", + "sha256:122c7fa62b130ed55f8f285bfd56d5f4b4a5b503609d181f9ad85e55c89f4185", + "sha256:1ceae2f17a9c33cb48e3263960dc5fc8005351ee19db217e9b1bb15d28c02574", + "sha256:1d3193f4a680c64b4b6a9115943538edb896edc190f0b222e73761716519268e", + "sha256:1f79682fbe303db92bc2b1136016a38a42e835d932bab5b3b1bfcfbf0640e519", + "sha256:2127566c664442652f024c837091890cb1942c30937add288223dc895793f898", + "sha256:22afcb9f253dac0696b5a4be4a1c0f8762f8239e21b99680099abd9b2b1b2269", + "sha256:25baf083bf6f6b341f4121c2f3c548875ee6f5339300e08be3f2b2ba1721cdd3", + "sha256:2e81c7b9c8979ce92ed306c249d46894776a909505d8f5a4ba55b14206e3222f", + "sha256:3287761bc4ee9e33561a7e058c72ac0938c4f57fe49a09eae428fd88aafe7bb6", + "sha256:34d1c8da1e78d2e001f363791c98a272bb734000fcef47a491c1e3b0505657a8", + "sha256:37e55c8e51c236f95b033f6fb391d7d7970ba5fe7ff453dad675e88cf303377a", + "sha256:3d47fa203a7bd9c5b6cee4736ee84ca03b8ef23193c0d1ca99b5089f72645c73", + "sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc", + "sha256:42cb296636fcc8b0644486d15c12376cb9fa75443e00fb25de0b8602e64c1714", + "sha256:45485e01ff4d3630ec0d9617310448a8702f70e9c01906b0d0118bdf9d124cf2", + "sha256:4a78b2b446bd7c934f5dcedc588903fb2f5eec172f3d29e52a9096a43722adfc", + "sha256:4ab2fe47fae9e0f9dee8c04187ce5d09f48eabe611be8259444906793ab7cbce", + "sha256:4d0d1650369165a14e14e1e47b372cfcb31d6ab44e6e33cb2d4e57265290044d", + "sha256:549a3a73da901d5bc3ce8d24e0600d1fa85524c10287f6004fbab87672bf3e1e", + "sha256:55086ee1064215781fff39a1af09518bc9255b50d6333f2e4c74ca09fac6a8f6", + "sha256:572c3763a264ba47b3cf708a44ce965d98555f618ca42c926a9c1616d8f34269", + "sha256:573f6eac48f4769d667c4442081b1794f52919e7edada77495aaed9236d13a96", + "sha256:5b4c145409bef602a690e7cfad0a15a55c13320ff7a3ad7ca59c13bb8ba4d45d", + "sha256:6463effa3186ea09411d50efc7d85360b38d5f09b870c48e4600f63af490e56a", + "sha256:65f6f63034100ead094b8744b3b97965785388f308a64cf8d7c34f2f2e5be0c4", + "sha256:663946639d296df6a2bb2aa51b60a2454ca1cb29835324c640dafb5ff2131a77", + "sha256:6897af51655e3691ff853668779c7bad41579facacf5fd7253b0133308cf000d", + "sha256:68d1f8a9e9e37c1223b656399be5d6b448dea850bed7d0f87a8311f1ff3dabb0", + "sha256:6ac7ffc7ad6d040517be39eb591cac5ff87416c2537df6ba3cba3bae290c0fed", + "sha256:6b3251890fff30ee142c44144871185dbe13b11bab478a88887a639655be1068", + "sha256:6c4caeef8fa63d06bd437cd4bdcf3ffefe6738fb1b25951440d80dc7df8c03ac", + "sha256:6ef1d82a3af9d3eecdba2321dc1b3c238245d890843e040e41e470ffa64c3e25", + "sha256:753f10e867343b4511128c6ed8c82f7bec3bd026875576dfd88483c5c73b2fd8", + "sha256:7cd13a2e3ddeed6913a65e66e94b51d80a041145a026c27e6bb76c31a853c6ab", + "sha256:7ed9e526742851e8d5cc9e6cf41427dfc6068d4f5a3bb03659444b4cabf6bc26", + "sha256:7f04c839ed0b6b98b1a7501a002144b76c18fb1c1850c8b98d458ac269e26ed2", + "sha256:802fe99cca7457642125a8a88a084cef28ff0cf9407060f7b93dca5aa25480db", + "sha256:80402cd6ee291dcb72644d6eac93785fe2c8b9cb30893c1af5b8fdd753b9d40f", + "sha256:8465322196c8b4d7ab6d1e049e4c5cb460d0394da4a27d23cc242fbf0034b6b5", + "sha256:86216b5cee4b06df986d214f664305142d9c76df9b6512be2738aa72a2048f99", + "sha256:87d1351268731db79e0f8e745d92493ee2841c974128ef629dc518b937d9194c", + "sha256:8bdb58ff7ba23002a4c5808d608e4e6c687175724f54a5dade5fa8c67b604e4d", + "sha256:8c622a5fe39a48f78944a87d4fb8a53ee07344641b0562c540d840748571b811", + "sha256:8d756e44e94489e49571086ef83b2bb8ce311e730092d2c34ca8f7d925cb20aa", + "sha256:8f4a014bc36d3c57402e2977dada34f9c12300af536839dc38c0beab8878f38a", + "sha256:9063e24fdb1e498ab71cb7419e24622516c4a04476b17a2dab57e8baa30d6e03", + "sha256:90d558489962fd4918143277a773316e56c72da56ec7aa3dc3dbbe20fdfed15b", + "sha256:923c0c831b7cfcb071580d3f46c4baf50f174be571576556269530f4bbd79d04", + "sha256:95f2a5796329323b8f0512e09dbb7a1860c46a39da62ecb2324f116fa8fdc85c", + "sha256:96b02a3dc4381e5494fad39be677abcb5e6634bf7b4fa83a6dd3112607547001", + "sha256:9f96df6923e21816da7e0ad3fd47dd8f94b2a5ce594e00677c0013018b813458", + "sha256:a10af20b82360ab00827f916a6058451b723b4e65030c5a18577c8b2de5b3389", + "sha256:a50aebfa173e157099939b17f18600f72f84eed3049e743b68ad15bd69b6bf99", + "sha256:a981a536974bbc7a512cf44ed14938cf01030a99e9b3a06dd59578882f06f985", + "sha256:a9a8e9031d613fd2009c182b69c7b2c1ef8239a0efb1df3f7c8da66d5dd3d537", + "sha256:ae5f4161f18c61806f411a13b0310bea87f987c7d2ecdbdaad0e94eb2e404238", + "sha256:aed38f6e4fb3f5d6bf81bfa990a07806be9d83cf7bacef998ab1a9bd660a581f", + "sha256:b01b88d45a6fcb69667cd6d2f7a9aeb4bf53760d7fc536bf679ec94fe9f3ff3d", + "sha256:b261ccdec7821281dade748d088bb6e9b69e6d15b30652b74cbbac25e280b796", + "sha256:b2b0a0c0517616b6869869f8c581d4eb2dd83a4d79e0ebcb7d373ef9956aeb0a", + "sha256:b4a23f61ce87adf89be746c8a8974fe1c823c891d8f86eb218bb957c924bb143", + "sha256:bd8f7df7d12c2db9fab40bdd87a7c09b1530128315d047a086fa3ae3435cb3a8", + "sha256:beb58fe5cdb101e3a055192ac291b7a21e3b7ef4f67fa1d74e331a7f2124341c", + "sha256:c002b4ffc0be611f0d9da932eb0f704fe2602a9a949d1f738e4c34c75b0863d5", + "sha256:c083af607d2515612056a31f0a8d9e0fcb5876b7bfc0abad3ecd275bc4ebc2d5", + "sha256:c180f51afb394e165eafe4ac2936a14bee3eb10debc9d9e4db8958fe36afe711", + "sha256:c235ebd9baae02f1b77bcea61bce332cb4331dc3617d254df3323aa01ab47bd4", + "sha256:cd70574b12bb8a4d2aaa0094515df2463cb429d8536cfb6c7ce983246983e5a6", + "sha256:d0eccceffcb53201b5bfebb52600a5fb483a20b61da9dbc885f8b103cbe7598c", + "sha256:d965bba47ddeec8cd560687584e88cf699fd28f192ceb452d1d7ee807c5597b7", + "sha256:db364eca23f876da6f9e16c9da0df51aa4f104a972735574842618b8c6d999d4", + "sha256:ddbb2551d7e0102e7252db79ba445cdab71b26640817ab1e3e3648dad515003b", + "sha256:deb6be0ac38ece9ba87dea880e438f25ca3eddfac8b002a2ec3d9183a454e8ae", + "sha256:e06ed3eb3218bc64786f7db41917d4e686cc4856944f53d5bdf83a6884432e12", + "sha256:e27ad930a842b4c5eb8ac0016b0a54f5aebbe679340c26101df33424142c143c", + "sha256:e537484df0d8f426ce2afb2d0f8e1c3d0b114b83f8850e5f2fbea0e797bd82ae", + "sha256:eb00ed941194665c332bf8e078baf037d6c35d7c4f3102ea2d4f16ca94a26dc8", + "sha256:eb6904c354526e758fda7167b33005998fb68c46fbc10e013ca97f21ca5c8887", + "sha256:eb8821e09e916165e160797a6c17edda0679379a4be5c716c260e836e122f54b", + "sha256:efcb3f6676480691518c177e3b465bcddf57cea040302f9f4e6e191af91174d4", + "sha256:f27273b60488abe721a075bcca6d7f3964f9f6f067c8c4c605743023d7d3944f", + "sha256:f30c3cb33b24454a82faecaf01b19c18562b1e89558fb6c56de4d9118a032fd5", + "sha256:fb69256e180cb6c8a894fee62b3afebae785babc1ee98b81cdf68bbca1987f33", + "sha256:fd1abc0d89e30cc4e02e4064dc67fcc51bd941eb395c502aac3ec19fab46b519", + "sha256:ff8fa367d09b717b2a17a052544193ad76cd49979c805768879cb63d9ca50561" + ], + "markers": "python_full_version >= '3.7.0'", + "version": "==3.3.2" + }, + "click": { + "hashes": [ + "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28", + "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de" + ], + "markers": "python_version >= '3.7'", + "version": "==8.1.7" + }, + "dill": { + "hashes": [ + "sha256:3ebe3c479ad625c4553aca177444d89b486b1d84982eeacded644afc0cf797ca", + "sha256:c36ca9ffb54365bdd2f8eb3eff7d2a21237f8452b57ace88b1ac615b7e815bd7" + ], + "markers": "python_version < '3.11'", + "version": "==0.3.8" + }, + "docutils": { + "hashes": [ + "sha256:96f387a2c5562db4476f09f13bbab2192e764cac08ebbf3a34a95d9b1e4a59d6", + "sha256:f08a4e276c3a1583a86dce3e34aba3fe04d02bba2dd51ed16106244e8a923e3b" + ], + "markers": "python_version >= '3.7'", + "version": "==0.20.1" + }, + "exceptiongroup": { + "hashes": [ + "sha256:4bfd3996ac73b41e9b9628b04e079f193850720ea5945fc96a08633c66912f14", + "sha256:91f5c769735f051a4290d52edd0858999b57e5876e9f85937691bd4c9fa3ed68" + ], + "markers": "python_version < '3.11'", + "version": "==1.2.0" + }, + "hypothesis": { + "hashes": [ + "sha256:3dacf6ec90e8d14aaee02cde081ac9a17d5b70105e45e6ac822db72052c0195b", + "sha256:ebff09d7fa4f1fb6a855a812baf17e578b4481b7b70ec6d96496210d1a4c6c35" + ], + "index": "pypi", + "markers": "python_version >= '3.8'", + "version": "==6.100.1" + }, + "idna": { + "hashes": [ + "sha256:028ff3aadf0609c1fd278d8ea3089299412a7a8b9bd005dd08b9f8285bcb5cfc", + "sha256:82fee1fc78add43492d3a1898bfa6d8a904cc97d8427f683ed8e798d07761aa0" + ], + "markers": "python_version >= '3.5'", + "version": "==3.7" + }, + "imagesize": { + "hashes": [ + "sha256:0d8d18d08f840c19d0ee7ca1fd82490fdc3729b7ac93f49870406ddde8ef8d8b", + "sha256:69150444affb9cb0d5cc5a92b3676f0b2fb7cd9ae39e947a5e11a36b4497cd4a" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "version": "==1.4.1" + }, + "iniconfig": { + "hashes": [ + "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3", + "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374" + ], + "markers": "python_version >= '3.7'", + "version": "==2.0.0" + }, + "isort": { + "hashes": [ + "sha256:48fdfcb9face5d58a4f6dde2e72a1fb8dcaf8ab26f95ab49fab84c2ddefb0109", + "sha256:8ca5e72a8d85860d5a3fa69b8745237f2939afe12dbf656afbcb47fe72d947a6" + ], + "markers": "python_full_version >= '3.8.0'", + "version": "==5.13.2" + }, + "jinja2": { + "hashes": [ + "sha256:7d6d50dd97d52cbc355597bd845fabfbac3f551e1f99619e39a35ce8c370b5fa", + "sha256:ac8bd6544d4bb2c9792bf3a159e80bba8fda7f07e81bc3aed565432d5925ba90" + ], + "markers": "python_version >= '3.7'", + "version": "==3.1.3" + }, + "markupsafe": { + "hashes": [ + "sha256:00e046b6dd71aa03a41079792f8473dc494d564611a8f89bbbd7cb93295ebdcf", + "sha256:075202fa5b72c86ad32dc7d0b56024ebdbcf2048c0ba09f1cde31bfdd57bcfff", + "sha256:0e397ac966fdf721b2c528cf028494e86172b4feba51d65f81ffd65c63798f3f", + "sha256:17b950fccb810b3293638215058e432159d2b71005c74371d784862b7e4683f3", + "sha256:1f3fbcb7ef1f16e48246f704ab79d79da8a46891e2da03f8783a5b6fa41a9532", + "sha256:2174c595a0d73a3080ca3257b40096db99799265e1c27cc5a610743acd86d62f", + "sha256:2b7c57a4dfc4f16f7142221afe5ba4e093e09e728ca65c51f5620c9aaeb9a617", + "sha256:2d2d793e36e230fd32babe143b04cec8a8b3eb8a3122d2aceb4a371e6b09b8df", + "sha256:30b600cf0a7ac9234b2638fbc0fb6158ba5bdcdf46aeb631ead21248b9affbc4", + "sha256:397081c1a0bfb5124355710fe79478cdbeb39626492b15d399526ae53422b906", + "sha256:3a57fdd7ce31c7ff06cdfbf31dafa96cc533c21e443d57f5b1ecc6cdc668ec7f", + "sha256:3c6b973f22eb18a789b1460b4b91bf04ae3f0c4234a0a6aa6b0a92f6f7b951d4", + "sha256:3e53af139f8579a6d5f7b76549125f0d94d7e630761a2111bc431fd820e163b8", + "sha256:4096e9de5c6fdf43fb4f04c26fb114f61ef0bf2e5604b6ee3019d51b69e8c371", + "sha256:4275d846e41ecefa46e2015117a9f491e57a71ddd59bbead77e904dc02b1bed2", + "sha256:4c31f53cdae6ecfa91a77820e8b151dba54ab528ba65dfd235c80b086d68a465", + "sha256:4f11aa001c540f62c6166c7726f71f7573b52c68c31f014c25cc7901deea0b52", + "sha256:5049256f536511ee3f7e1b3f87d1d1209d327e818e6ae1365e8653d7e3abb6a6", + "sha256:58c98fee265677f63a4385256a6d7683ab1832f3ddd1e66fe948d5880c21a169", + "sha256:598e3276b64aff0e7b3451b72e94fa3c238d452e7ddcd893c3ab324717456bad", + "sha256:5b7b716f97b52c5a14bffdf688f971b2d5ef4029127f1ad7a513973cfd818df2", + "sha256:5dedb4db619ba5a2787a94d877bc8ffc0566f92a01c0ef214865e54ecc9ee5e0", + "sha256:619bc166c4f2de5caa5a633b8b7326fbe98e0ccbfacabd87268a2b15ff73a029", + "sha256:629ddd2ca402ae6dbedfceeba9c46d5f7b2a61d9749597d4307f943ef198fc1f", + "sha256:656f7526c69fac7f600bd1f400991cc282b417d17539a1b228617081106feb4a", + "sha256:6ec585f69cec0aa07d945b20805be741395e28ac1627333b1c5b0105962ffced", + "sha256:72b6be590cc35924b02c78ef34b467da4ba07e4e0f0454a2c5907f473fc50ce5", + "sha256:7502934a33b54030eaf1194c21c692a534196063db72176b0c4028e140f8f32c", + "sha256:7a68b554d356a91cce1236aa7682dc01df0edba8d043fd1ce607c49dd3c1edcf", + "sha256:7b2e5a267c855eea6b4283940daa6e88a285f5f2a67f2220203786dfa59b37e9", + "sha256:823b65d8706e32ad2df51ed89496147a42a2a6e01c13cfb6ffb8b1e92bc910bb", + "sha256:8590b4ae07a35970728874632fed7bd57b26b0102df2d2b233b6d9d82f6c62ad", + "sha256:8dd717634f5a044f860435c1d8c16a270ddf0ef8588d4887037c5028b859b0c3", + "sha256:8dec4936e9c3100156f8a2dc89c4b88d5c435175ff03413b443469c7c8c5f4d1", + "sha256:97cafb1f3cbcd3fd2b6fbfb99ae11cdb14deea0736fc2b0952ee177f2b813a46", + "sha256:a17a92de5231666cfbe003f0e4b9b3a7ae3afb1ec2845aadc2bacc93ff85febc", + "sha256:a549b9c31bec33820e885335b451286e2969a2d9e24879f83fe904a5ce59d70a", + "sha256:ac07bad82163452a6884fe8fa0963fb98c2346ba78d779ec06bd7a6262132aee", + "sha256:ae2ad8ae6ebee9d2d94b17fb62763125f3f374c25618198f40cbb8b525411900", + "sha256:b91c037585eba9095565a3556f611e3cbfaa42ca1e865f7b8015fe5c7336d5a5", + "sha256:bc1667f8b83f48511b94671e0e441401371dfd0f0a795c7daa4a3cd1dde55bea", + "sha256:bec0a414d016ac1a18862a519e54b2fd0fc8bbfd6890376898a6c0891dd82e9f", + "sha256:bf50cd79a75d181c9181df03572cdce0fbb75cc353bc350712073108cba98de5", + "sha256:bff1b4290a66b490a2f4719358c0cdcd9bafb6b8f061e45c7a2460866bf50c2e", + "sha256:c061bb86a71b42465156a3ee7bd58c8c2ceacdbeb95d05a99893e08b8467359a", + "sha256:c8b29db45f8fe46ad280a7294f5c3ec36dbac9491f2d1c17345be8e69cc5928f", + "sha256:ce409136744f6521e39fd8e2a24c53fa18ad67aa5bc7c2cf83645cce5b5c4e50", + "sha256:d050b3361367a06d752db6ead6e7edeb0009be66bc3bae0ee9d97fb326badc2a", + "sha256:d283d37a890ba4c1ae73ffadf8046435c76e7bc2247bbb63c00bd1a709c6544b", + "sha256:d9fad5155d72433c921b782e58892377c44bd6252b5af2f67f16b194987338a4", + "sha256:daa4ee5a243f0f20d528d939d06670a298dd39b1ad5f8a72a4275124a7819eff", + "sha256:db0b55e0f3cc0be60c1f19efdde9a637c32740486004f20d1cff53c3c0ece4d2", + "sha256:e61659ba32cf2cf1481e575d0462554625196a1f2fc06a1c777d3f48e8865d46", + "sha256:ea3d8a3d18833cf4304cd2fc9cbb1efe188ca9b5efef2bdac7adc20594a0e46b", + "sha256:ec6a563cff360b50eed26f13adc43e61bc0c04d94b8be985e6fb24b81f6dcfdf", + "sha256:f5dfb42c4604dddc8e4305050aa6deb084540643ed5804d7455b5df8fe16f5e5", + "sha256:fa173ec60341d6bb97a89f5ea19c85c5643c1e7dedebc22f5181eb73573142c5", + "sha256:fa9db3f79de01457b03d4f01b34cf91bc0048eb2c3846ff26f66687c2f6d16ab", + "sha256:fce659a462a1be54d2ffcacea5e3ba2d74daa74f30f5f143fe0c58636e355fdd", + "sha256:ffee1f21e5ef0d712f9033568f8344d5da8cc2869dbd08d87c84656e6a2d2f68" + ], + "markers": "python_version >= '3.7'", + "version": "==2.1.5" + }, + "mccabe": { + "hashes": [ + "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325", + "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e" + ], + "markers": "python_version >= '3.6'", + "version": "==0.7.0" + }, + "mypy-extensions": { + "hashes": [ + "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d", + "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782" + ], + "markers": "python_version >= '3.5'", + "version": "==1.0.0" + }, + "numpy": { + "hashes": [ + "sha256:00236e0e8a588fef8f70e0535b898bcebd97becc0b27686d2fc7cb35b5d1ab91", + "sha256:015df68fd97bc00e1b7719e80cea401b23a601b639c6d6545922f7a21876b771", + "sha256:060635ab843ea0e2aa6ad153d5656193014eedd90ec4ef6e2b738d81bfe28170", + "sha256:070a8b1c93b0bf21c1a3c51514145acbba612e9f3fd86870c1ca37a36cebbfce", + "sha256:08d7d73d5b7d97decfb6584f41492f5584f81a3147514b67ac21ccccb3418b35", + "sha256:09bedcb99b9ac5472d2e63cd18be861750acc7570ae3661be7cb6018ce376694", + "sha256:09e7a6cab5eac8aca0f17ad29b42ee1cd357e09a76076d5f4cb90ca62a0229b8", + "sha256:1860507cb082ee8d9920db806d74d8a3936081b9ecf274b0fdb6d99b664680a1", + "sha256:1e2478ca8b4b0c5a7146fc316c83843bc47b2d73cf6c02000561794ae5dba537", + "sha256:25d43c681fefb4d7e0ffa949097b20eacbad4be9af7c136b1f69dc4c34c1f6d4", + "sha256:2b5f87d88212e54263f64257b28daa04f3fde627c204abd7557a80b582de4a63", + "sha256:39a65e8c127d51419942a9e0ec467273536acd373507ce64e63451690ed47bfc", + "sha256:5be315e916e7d4d372acf62dcc86900eb47b2f76c185d835634dd0503f441e35", + "sha256:5c62c0d071681391b9c73ba09b35cb46477659012fd88af2c877a2a9da84aa2f", + "sha256:5e289dafe89a0dd756430fa03332c428c897c41cc3143230c38d7d2bb9ad475e", + "sha256:67f9707c3df26ca5bce34162fe0721646504c5961ccfca94c294fbeaf42cfa5b", + "sha256:684eef178a2039cba72bce740cdf2f592e67a41885a0f09d5622380fc59af0f8", + "sha256:6e0438e248b5e7e46e80a686868d36d6a4ce875cedce87122d1616ffd8e2a669", + "sha256:706f66648712385f5ca5e22ad4f32d1a1a93c143882969d951122b5cf9e40a24", + "sha256:7511694264a1219458a4e77d185a7ee350506b4e1e3b2b82845a5e9db044b6f5", + "sha256:7517f752cad3d8bf297ed6421c63be769a03b8e3c34282eec803bae693dae67a", + "sha256:7d990411f2821bf2812ec66ae85e8f351103fe7c3a229152ab6f8c9a620e82eb", + "sha256:8798ee3db69d2f531b12897929583021206feb4d45234d035e5511a5bd0cee38", + "sha256:8a7c01e9c14216e386e42a0c75c76a015a002dd5ed833ffbdaa6a7f2aeed9258", + "sha256:8b510bab996ad7b7fa59ca14fdaae4c68a36ff0f71ccd9ddec769b58f9d19258", + "sha256:9085f9a3e4f994ee8027db503627ae34aa867dc5f00ee7fe2b930608534a9293", + "sha256:91103edc14b5b70bc25af26ea5d75a45b6490bed5f1da9478f5bbe82542ba1b5", + "sha256:9d96878db0d4f267e62e21f6feb7d0e7f07ec02784e705f37b7f6493a935c7fd", + "sha256:9da7cddeaf312a3645325a7da3b18bfad345cae5005cb4d6fcf24796bedaf239", + "sha256:afa4679bcbade6a4197c27874c0dacf5d45470d56cee8b1e2398e80859ab797c", + "sha256:b1bfbde0e9221920d02735ced823e53be46786589a5e8db91824bccd5115e5c8", + "sha256:c0af260d6818eab709b65953e1e5ce31a34d68230f488589b4bb96b13a28d18f", + "sha256:cdea89bba67157bd8ec2ba9613d9f5ba2d18deab113171ca106953fdf8f7f314", + "sha256:cf1b08d8ee6d24576c0552dee71f36859de157481ed283e839d630b50242bbe1", + "sha256:cfd4e2f1605e3a607674dd3173c03b2e2f8520fa3ec2db04f2da2a3d5339df1b", + "sha256:d4b56e9abe2c3cec5615725320e002396c1e4b78011831a78427c7ff7b185816", + "sha256:d93d29d07b2da78869793ec30321adda61a5a48b9e00d12160d0cd658f5f2e0b", + "sha256:dfcd76a018c728ce7a3e6e09717e7a3dfbffdf87a57118dbc5ddc2167a678258", + "sha256:f0e169ec6cbc1b8e5f6a235845a80961f76f88352082213a1728a0967a761ad2", + "sha256:f36b7ccac6a3bfb342a61dd08be73fbe0286d2cb64c976bb1ed22feda0deb16f", + "sha256:f6539759d26e9b60dd9691732528dda7fe46a8c82be6294d109203dce4a8b89c", + "sha256:f9e566457284cb55447eab7566fad2b59e17f01776bb1b76828a6a931d111c72", + "sha256:fb009d69b3a362240acc5155e3de8f90311eb7f9f3958803af866945b8c9ee43", + "sha256:fbee730ae5265735e2c9b006a0d3fe1443d08d9399d0103245b99ecba10ddff0", + "sha256:fe19044006aeaf783c64f22ee03330caccb4d3e54fe605b57444f448954b022d" + ], + "index": "pypi", + "markers": "python_version >= '3.9'", + "version": "==2.0.0rc1" + }, + "packaging": { + "hashes": [ + "sha256:2ddfb553fdf02fb784c234c7ba6ccc288296ceabec964ad2eae3777778130bc5", + "sha256:eb82c5e3e56209074766e6885bb04b8c38a0c015d0a30036ebe7ece34c9989e9" + ], + "markers": "python_version >= '3.7'", + "version": "==24.0" + }, + "pathspec": { + "hashes": [ + "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08", + "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712" + ], + "markers": "python_version >= '3.8'", + "version": "==0.12.1" + }, + "platformdirs": { + "hashes": [ + "sha256:0614df2a2f37e1a662acbd8e2b25b92ccf8632929bc6d43467e17fe89c75e068", + "sha256:ef0cc731df711022c174543cb70a9b5bd22e5a9337c8624ef2c2ceb8ddad8768" + ], + "markers": "python_version >= '3.8'", + "version": "==4.2.0" + }, + "pluggy": { + "hashes": [ + "sha256:7db9f7b503d67d1c5b95f59773ebb58a8c1c288129a88665838012cfb07b8981", + "sha256:8c85c2876142a764e5b7548e7d9a0e0ddb46f5185161049a79b7e974454223be" + ], + "markers": "python_version >= '3.8'", + "version": "==1.4.0" + }, + "pyerf": { + "hashes": [ + "sha256:68191839eedf566c031b4959fafff83fbd1c9022b42143ea3684c9b2d6e6371a", + "sha256:c0a9eef78cdb70550e08c08bd2d3cd3fb06ea9100de72d7c99ce9f329daf8797", + "sha256:c1039d9d84681b0b635f3290699b72884b0f615fdf3a4738ba3e96c5185f69d8" + ], + "index": "pypi", + "version": "==1.0.1" + }, + "pygments": { + "hashes": [ + "sha256:b27c2826c47d0f3219f29554824c30c5e8945175d888647acd804ddd04af846c", + "sha256:da46cec9fd2de5be3a8a784f434e4c4ab670b4ff54d605c4c2717e9d49c4c367" + ], + "markers": "python_version >= '3.7'", + "version": "==2.17.2" + }, + "pylint": { + "hashes": [ + "sha256:507a5b60953874766d8a366e8e8c7af63e058b26345cfcb5f91f89d987fd6b74", + "sha256:6a69beb4a6f63debebaab0a3477ecd0f559aa726af4954fc948c51f7a2549e23" + ], + "index": "pypi", + "markers": "python_full_version >= '3.8.0'", + "version": "==3.1.0" + }, + "pytest": { + "hashes": [ + "sha256:2a8386cfc11fa9d2c50ee7b2a57e7d898ef90470a7a34c4b949ff59662bb78b7", + "sha256:ac978141a75948948817d360297b7aae0fcb9d6ff6bc9ec6d514b85d5a65c044" + ], + "index": "pypi", + "markers": "python_version >= '3.8'", + "version": "==8.1.1" + }, + "requests": { + "hashes": [ + "sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f", + "sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1" + ], + "markers": "python_version >= '3.7'", + "version": "==2.31.0" + }, + "setuptools-pipfile": { + "hashes": [ + "sha256:54cb6bf6a662fe74951425d509772a5302d1cf723d9a3654d19c2468d3d80b6b", + "sha256:f6049892af8e8233a438cf00fb4477fe81de3ea0e8e90c1241d196cb40f703b5" + ], + "index": "pypi", + "version": "==0.7.0" + }, + "snowballstemmer": { + "hashes": [ + "sha256:09b16deb8547d3412ad7b590689584cd0fe25ec8db3be37788be3810cbf19cb1", + "sha256:c8e1716e83cc398ae16824e5572ae04e0d9fc2c6b985fb0f900f5f0c96ecba1a" + ], + "version": "==2.2.0" + }, + "sortedcontainers": { + "hashes": [ + "sha256:25caa5a06cc30b6b83d11423433f65d1f9d76c4c6a0c90e3379eaa43b9bfdb88", + "sha256:a163dcaede0f1c021485e957a39245190e74249897e2ae4b2aa38595db237ee0" + ], + "version": "==2.4.0" + }, + "sphinx": { + "hashes": [ + "sha256:1e09160a40b956dc623c910118fa636da93bd3ca0b9876a7b3df90f07d691560", + "sha256:9a5160e1ea90688d5963ba09a2dcd8bdd526620edbb65c328728f1b2228d5ab5" + ], + "index": "pypi", + "markers": "python_version >= '3.9'", + "version": "==7.2.6" + }, + "sphinx-autodoc-typehints": { + "hashes": [ + "sha256:60ed1e3b2c970acc0aa6e877be42d48029a9faec7378a17838716cacd8c10b12", + "sha256:f73ae89b43a799e587e39266672c1075b2ef783aeb382d3ebed77c38a3fc0149" + ], + "index": "pypi", + "markers": "python_version >= '3.8'", + "version": "==2.0.1" + }, + "sphinx-markdown-builder": { + "hashes": [ + "sha256:e6fd4626c6daf1c25a464fd7d6d64e4a97e69abca1684fb2a12fba44cb6db363", + "sha256:febd8e03e20e357e624c52efdc7ef9d5ab70d58549784158033c9657d040f44e" + ], + "index": "pypi", + "markers": "python_version >= '3.7'", + "version": "==0.6.6" + }, + "sphinxcontrib-applehelp": { + "hashes": [ + "sha256:c40a4f96f3776c4393d933412053962fac2b84f4c99a7982ba42e09576a70619", + "sha256:cb61eb0ec1b61f349e5cc36b2028e9e7ca765be05e49641c97241274753067b4" + ], + "markers": "python_version >= '3.9'", + "version": "==1.0.8" + }, + "sphinxcontrib-devhelp": { + "hashes": [ + "sha256:6485d09629944511c893fa11355bda18b742b83a2b181f9a009f7e500595c90f", + "sha256:9893fd3f90506bc4b97bdb977ceb8fbd823989f4316b28c3841ec128544372d3" + ], + "markers": "python_version >= '3.9'", + "version": "==1.0.6" + }, + "sphinxcontrib-htmlhelp": { + "hashes": [ + "sha256:0dc87637d5de53dd5eec3a6a01753b1ccf99494bd756aafecd74b4fa9e729015", + "sha256:393f04f112b4d2f53d93448d4bce35842f62b307ccdc549ec1585e950bc35e04" + ], + "markers": "python_version >= '3.9'", + "version": "==2.0.5" + }, + "sphinxcontrib-jsmath": { + "hashes": [ + "sha256:2ec2eaebfb78f3f2078e73666b1415417a116cc848b72e5172e596c871103178", + "sha256:a9925e4a4587247ed2191a22df5f6970656cb8ca2bd6284309578f2153e0c4b8" + ], + "markers": "python_version >= '3.5'", + "version": "==1.0.1" + }, + "sphinxcontrib-qthelp": { + "hashes": [ + "sha256:053dedc38823a80a7209a80860b16b722e9e0209e32fea98c90e4e6624588ed6", + "sha256:e2ae3b5c492d58fcbd73281fbd27e34b8393ec34a073c792642cd8e529288182" + ], + "markers": "python_version >= '3.9'", + "version": "==1.0.7" + }, + "sphinxcontrib-serializinghtml": { + "hashes": [ + "sha256:326369b8df80a7d2d8d7f99aa5ac577f51ea51556ed974e7716cfd4fca3f6cb7", + "sha256:93f3f5dc458b91b192fe10c397e324f262cf163d79f3282c158e8436a2c4511f" + ], + "markers": "python_version >= '3.9'", + "version": "==1.1.10" + }, + "tabulate": { + "hashes": [ + "sha256:0095b12bf5966de529c0feb1fa08671671b3368eec77d7ef7ab114be2c068b3c", + "sha256:024ca478df22e9340661486f85298cff5f6dcdba14f3813e8830015b9ed1948f" + ], + "markers": "python_version >= '3.7'", + "version": "==0.9.0" + }, + "toml": { + "hashes": [ + "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b", + "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f" + ], + "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2'", + "version": "==0.10.2" + }, + "tomli": { + "hashes": [ + "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc", + "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f" + ], + "markers": "python_version < '3.11'", + "version": "==2.0.1" + }, + "tomlkit": { + "hashes": [ + "sha256:5cd82d48a3dd89dee1f9d64420aa20ae65cfbd00668d6f094d7578a78efbb77b", + "sha256:7ca1cfc12232806517a8515047ba66a19369e71edf2439d0f5824f91032b6cc3" + ], + "markers": "python_version >= '3.7'", + "version": "==0.12.4" + }, + "typing-extensions": { + "hashes": [ + "sha256:83f085bd5ca59c80295fc2a82ab5dac679cbe02b9f33f7d83af68e241bea51b0", + "sha256:c1f94d72897edaf4ce775bb7558d5b79d8126906a14ea5ed1635921406c0387a" + ], + "markers": "python_version >= '3.8'", + "version": "==4.11.0" + }, + "urllib3": { + "hashes": [ + "sha256:450b20ec296a467077128bff42b73080516e71b56ff59a60a02bef2232c4fa9d", + "sha256:d0570876c61ab9e520d776c38acbbb5b05a776d3f9ff98a5c8fd5162a444cf19" + ], + "markers": "python_version >= '3.8'", + "version": "==2.2.1" + } + } +} From 2ab6e999c669d954ea606a865cf00caf697233e4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 16 Apr 2024 09:45:46 +0200 Subject: [PATCH 63/66] Bump @types/react-dom from 18.2.24 to 18.2.25 (#1865) Bumps [@types/react-dom](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/react-dom) from 18.2.24 to 18.2.25. - [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases) - [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/react-dom) --- updated-dependencies: - dependency-name: "@types/react-dom" dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- packages/apps/dashboard/ui/package.json | 2 +- packages/apps/job-launcher/client/package.json | 2 +- yarn.lock | 8 ++++---- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/apps/dashboard/ui/package.json b/packages/apps/dashboard/ui/package.json index 86beaae6e6..60574f0426 100644 --- a/packages/apps/dashboard/ui/package.json +++ b/packages/apps/dashboard/ui/package.json @@ -43,7 +43,7 @@ "@types/glob": "^8.1.0", "@types/numeral": "^2.0.2", "@types/react": "^18.2.43", - "@types/react-dom": "^18.2.14", + "@types/react-dom": "^18.2.25", "@types/react-gtm-module": "^2.0.3", "@types/react-test-renderer": "^18.0.0", "@vitejs/plugin-react": "^4.2.1", diff --git a/packages/apps/job-launcher/client/package.json b/packages/apps/job-launcher/client/package.json index 8c517620ce..e131067f5b 100644 --- a/packages/apps/job-launcher/client/package.json +++ b/packages/apps/job-launcher/client/package.json @@ -58,7 +58,7 @@ "@testing-library/jest-dom": "^6.4.2", "@testing-library/react": "^14.2.1", "@types/react": "^18.2.43", - "@types/react-dom": "^18.2.14", + "@types/react-dom": "^18.2.25", "@types/react-test-renderer": "^18.0.0", "@types/xml2js": "^0.4.14", "@vitejs/plugin-react": "^4.2.1", diff --git a/yarn.lock b/yarn.lock index 092ba16b34..fa3891b1fd 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6872,10 +6872,10 @@ resolved "https://registry.yarnpkg.com/@types/range-parser/-/range-parser-1.2.7.tgz#50ae4353eaaddc04044279812f52c8c65857dbcb" integrity sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ== -"@types/react-dom@^18.0.0", "@types/react-dom@^18.2.14": - version "18.2.24" - resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-18.2.24.tgz#8dda8f449ae436a7a6e91efed8035d4ab03ff759" - integrity sha512-cN6upcKd8zkGy4HU9F1+/s98Hrp6D4MOcippK4PoE8OZRngohHZpbJn1GsaDLz87MqvHNoT13nHvNqM9ocRHZg== +"@types/react-dom@^18.0.0", "@types/react-dom@^18.2.25": + version "18.2.25" + resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-18.2.25.tgz#2946a30081f53e7c8d585eb138277245caedc521" + integrity sha512-o/V48vf4MQh7juIKZU2QGDfli6p1+OOi5oXx36Hffpc9adsHeXjVp8rHuPkjd8VT8sOJ2Zp05HR7CdpGTIUFUA== dependencies: "@types/react" "*" From 350fca55dc8c8da29c8719f88ef332ab53cb1ea5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 16 Apr 2024 09:46:16 +0200 Subject: [PATCH 64/66] Bump openpgp from 5.10.2 to 5.11.1 (#1864) Bumps [openpgp](https://github.com/openpgpjs/openpgpjs) from 5.10.2 to 5.11.1. - [Release notes](https://github.com/openpgpjs/openpgpjs/releases) - [Commits](https://github.com/openpgpjs/openpgpjs/compare/v5.10.2...v5.11.1) --- updated-dependencies: - dependency-name: openpgp dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- packages/apps/dashboard/ui/package.json | 2 +- packages/core/package.json | 2 +- packages/sdk/typescript/human-protocol-sdk/package.json | 2 +- yarn.lock | 9 +-------- 4 files changed, 4 insertions(+), 11 deletions(-) diff --git a/packages/apps/dashboard/ui/package.json b/packages/apps/dashboard/ui/package.json index 60574f0426..a38dcde6b3 100644 --- a/packages/apps/dashboard/ui/package.json +++ b/packages/apps/dashboard/ui/package.json @@ -21,7 +21,7 @@ "jszip": "^3.10.1", "nft.storage": "^7.0.0", "numeral": "^2.0.6", - "openpgp": "^5.10.2", + "openpgp": "^5.11.1", "react": "^18.2.0", "react-dom": "^18.2.0", "react-gtm-module": "^2.0.11", diff --git a/packages/core/package.json b/packages/core/package.json index b5ab3a77ff..51a9645096 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -70,7 +70,7 @@ "hardhat-contract-sizer": "^2.6.1", "hardhat-dependency-compiler": "^1.1.4", "hardhat-gas-reporter": "^2.0.2", - "openpgp": "5.10.2", + "openpgp": "5.11.1", "prettier-plugin-solidity": "^1.3.1", "solidity-coverage": "^0.8.2", "tenderly": "^0.9.1", diff --git a/packages/sdk/typescript/human-protocol-sdk/package.json b/packages/sdk/typescript/human-protocol-sdk/package.json index 3b3f3651a7..294962fe33 100644 --- a/packages/sdk/typescript/human-protocol-sdk/package.json +++ b/packages/sdk/typescript/human-protocol-sdk/package.json @@ -46,7 +46,7 @@ "graphql-request": "^6.1.0", "graphql-tag": "^2.12.6", "minio": "^7.0.32", - "openpgp": "^5.10.2", + "openpgp": "^5.11.1", "secp256k1": "^4.0.3", "vitest": "^0.30.1", "winston": "^3.13.0" diff --git a/yarn.lock b/yarn.lock index fa3891b1fd..2a35396c46 100644 --- a/yarn.lock +++ b/yarn.lock @@ -19155,14 +19155,7 @@ opener@^1.5.2: resolved "https://registry.yarnpkg.com/opener/-/opener-1.5.2.tgz#5d37e1f35077b9dcac4301372271afdeb2a13598" integrity sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A== -openpgp@5.10.2: - version "5.10.2" - resolved "https://registry.yarnpkg.com/openpgp/-/openpgp-5.10.2.tgz#695112abbccbdf13a399094e2153dcb899c99ede" - integrity sha512-nRqMp4o31rBagWB02tgfKCsocXWq4uYobZf9GDVlD5rQXBq/wRIZHiDhGX1dlDAI2inkZcPd2dSZOqmtGnsK1A== - dependencies: - asn1.js "^5.0.0" - -openpgp@^5.10.2: +openpgp@5.11.1, openpgp@^5.11.1: version "5.11.1" resolved "https://registry.yarnpkg.com/openpgp/-/openpgp-5.11.1.tgz#97f3a1dfb3d3573a0a73fe2efb29e6b1f8fefb1c" integrity sha512-TynUBPuaSI7dN0gP+A38CjNRLxkOkkptefNanalDQ71BFAKKm+dLbksymSW5bUrB7RcAneMySL/Y+r/TbLpOnQ== From 101441717c8ddf6a73ed8f979383f471150b9326 Mon Sep 17 00:00:00 2001 From: Maxim Zhiltsov Date: Tue, 16 Apr 2024 20:54:58 +0300 Subject: [PATCH 65/66] [CVAT-M2] Return only tasks with active assignments in Exchange Oracle (#1879) * Return only projects with active assignments in /tasks?wallet_address * Update test --- .../examples/cvat/exchange-oracle/src/services/cvat.py | 9 ++------- .../cvat/exchange-oracle/src/services/exchange.py | 1 + .../tests/integration/services/test_cvat.py | 7 ++++--- 3 files changed, 7 insertions(+), 10 deletions(-) diff --git a/packages/examples/cvat/exchange-oracle/src/services/cvat.py b/packages/examples/cvat/exchange-oracle/src/services/cvat.py index c467244b69..db6c851a9c 100644 --- a/packages/examples/cvat/exchange-oracle/src/services/cvat.py +++ b/packages/examples/cvat/exchange-oracle/src/services/cvat.py @@ -172,13 +172,8 @@ def get_projects_by_assignee( Project.jobs.any( Job.assignments.any( (Assignment.user_wallet_address == wallet_address) - & Assignment.status.in_( - [ - AssignmentStatuses.created, - AssignmentStatuses.completed, - AssignmentStatuses.canceled, - ] - ) + & (Assignment.status == AssignmentStatuses.created) + & (utcnow() < Assignment.expires_at) ) ) ) diff --git a/packages/examples/cvat/exchange-oracle/src/services/exchange.py b/packages/examples/cvat/exchange-oracle/src/services/exchange.py index 5b1f41bce7..9795cb5db9 100644 --- a/packages/examples/cvat/exchange-oracle/src/services/exchange.py +++ b/packages/examples/cvat/exchange-oracle/src/services/exchange.py @@ -87,6 +87,7 @@ def get_tasks_by_assignee( cvat_projects=[p.cvat_id for p in cvat_projects], ) if assignment.status == AssignmentStatuses.created + if not assignment.is_finished } for project in cvat_projects: diff --git a/packages/examples/cvat/exchange-oracle/tests/integration/services/test_cvat.py b/packages/examples/cvat/exchange-oracle/tests/integration/services/test_cvat.py index b511849451..3cd57e4a79 100644 --- a/packages/examples/cvat/exchange-oracle/tests/integration/services/test_cvat.py +++ b/packages/examples/cvat/exchange-oracle/tests/integration/services/test_cvat.py @@ -391,7 +391,7 @@ def test_get_projects_by_assignee(self): session=self.session, wallet_address=wallet_address_1, cvat_job_id=cvat_id_1, - expires_at=datetime.now(), + expires_at=datetime.now() + timedelta(days=1), ) wallet_address_2 = "0x86e83d346041E8806e352681f3F14549C0d2BC61" @@ -418,8 +418,9 @@ def test_get_projects_by_assignee(self): projects = cvat_service.get_projects_by_assignee(self.session, wallet_address_2) - self.assertEqual(len(projects), 1) - self.assertEqual(projects[0].cvat_id, cvat_id_2) + self.assertEqual( + len(projects), 0 + ) # expired should not be shown, https://github.com/humanprotocol/human-protocol/pull/1879 def test_update_project_status(self): cvat_id = 1 From 65b7618e2daec07e328edf37e06c0535f24362c9 Mon Sep 17 00:00:00 2001 From: portuu3 <61605646+portuu3@users.noreply.github.com> Date: Wed, 17 Apr 2024 17:02:17 +0200 Subject: [PATCH 66/66] Dashboard - Fix strapi admin (#1881) Fix bigint parsing and disable mumbai and goerli --- packages/apps/dashboard/admin/config/cron-tasks.ts | 6 +----- packages/apps/dashboard/admin/package.json | 5 +++-- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/packages/apps/dashboard/admin/config/cron-tasks.ts b/packages/apps/dashboard/admin/config/cron-tasks.ts index f84e3dd29f..7f24f69150 100644 --- a/packages/apps/dashboard/admin/config/cron-tasks.ts +++ b/packages/apps/dashboard/admin/config/cron-tasks.ts @@ -5,11 +5,9 @@ import { createPublicClient, http } from 'viem'; import { bsc, bscTestnet, - goerli, mainnet, polygon, polygonAmoy, - polygonMumbai, moonbeam, moonbaseAlpha, celo, @@ -19,12 +17,10 @@ import { formatUnits, parseUnits } from 'viem/utils'; const SUPPORTED_CHAINS = { [ChainId.MAINNET]: mainnet, - [ChainId.GOERLI]: goerli, [ChainId.BSC_MAINNET]: bsc, [ChainId.BSC_TESTNET]: bscTestnet, [ChainId.POLYGON]: polygon, [ChainId.POLYGON_AMOY]: polygonAmoy, - [ChainId.POLYGON_MUMBAI]: polygonMumbai, [ChainId.MOONBEAM]: moonbeam, [ChainId.MOONBASE_ALPHA]: moonbaseAlpha, [ChainId.CELO]: celo, @@ -36,7 +32,7 @@ const addBigInts = (a: string, b: string, decimals = 18) => { }; const formatBigNumber = (n: any, decimals = 18) => { - return formatUnits(n.toBigInt(), decimals); + return formatUnits(BigInt(n), decimals); }; const fetchData = async () => { diff --git a/packages/apps/dashboard/admin/package.json b/packages/apps/dashboard/admin/package.json index a25ca9fd16..c982dbce57 100644 --- a/packages/apps/dashboard/admin/package.json +++ b/packages/apps/dashboard/admin/package.json @@ -27,7 +27,7 @@ "viem": "^2.9.17" }, "author": { - "name": "Tony Wen" + "name": "HUMAN Protocol" }, "strapi": { "uuid": "c9312fea-0fdd-4782-a77c-8484ae3a6abe" @@ -36,5 +36,6 @@ "node": ">=14.19.1 <=18.x.x", "npm": ">=6.0.0" }, - "license": "MIT" + "license": "MIT", + "readme": "README.md" }