From 6c7603449a2fbba531abe0b9ee9d658d45587605 Mon Sep 17 00:00:00 2001 From: Zeeshan Akram <97m.zeeshan@gmail.com> Date: Tue, 25 Jan 2022 00:46:58 +0500 Subject: [PATCH 01/16] remaining TS types generation for bounty module --- types/augment-codec/all.ts | 4 +-- types/augment-codec/augment-api-query.ts | 3 +-- types/augment-codec/augment-types.ts | 8 +++++- types/augment/all/defs.json | 14 ++++++++++ types/augment/all/types.ts | 29 +++++++++++++++++++++ types/augment/augment-types.ts | 8 +++++- types/src/bounty.ts | 33 ++++++++++++++++++++++++ 7 files changed, 93 insertions(+), 6 deletions(-) diff --git a/types/augment-codec/all.ts b/types/augment-codec/all.ts index 6ca594129c..b80cbbce7d 100644 --- a/types/augment-codec/all.ts +++ b/types/augment-codec/all.ts @@ -9,7 +9,7 @@ import { ProposalId, ProposalStatus, Proposal as ProposalOf, ProposalDetails, Pr import { ReferendumStageVoting, ReferendumStageRevealing, ReferendumStage, OptionResult, VotePower } from '../referendum'; import { ConstitutionInfo } from '../constitution'; import { ParticipantId, Title, UpdatedTitle, UpdatedBody, ReplyId, Reply, ReplyToDelete } from '../blog'; -import { BountyId, EntryId, BountyActor, AssuranceContractType, FundingType_Limited, FundingType_Perpetual, FundingType, BountyCreationParameters, OracleJudgment_Winner, OracleJudgment, Entry } from '../bounty'; +import { BountyId, JSBounty, BountyMilestone, EntryId, BountyActor, AssuranceContractType, FundingType_Limited, FundingType_Perpetual, FundingType, BountyCreationParameters, OracleJudgment_Winner, OracleJudgment, Entry } from '../bounty'; import { CuratorId, CuratorGroupId, CuratorGroup, ContentActor, NewAsset, Channel, ChannelOwner, ChannelCategoryId, ChannelCategory, ChannelCategoryCreationParameters, ChannelCategoryUpdateParameters, ChannelCreationParameters, ChannelUpdateParameters, ChannelOwnershipTransferRequestId, ChannelOwnershipTransferRequest, Video, VideoId, VideoCategoryId, VideoCategory, VideoCategoryCreationParameters, VideoCategoryUpdateParameters, VideoCreationParameters, VideoUpdateParameters, Person, PersonId, PersonController, PersonActor, PersonCreationParameters, PersonUpdateParameters, Playlist, PlaylistId, PlaylistCreationParameters, PlaylistUpdateParameters, SeriesId, Series, Season, SeriesParameters, SeasonParameters, EpisodeParemters, MaxNumber, IsCensored } from '../content'; -export { ActorId, MemberId, BlockAndTime, ThreadId, PostId, InputValidationLengthConstraint, WorkingGroup, MemoText, BalanceKind, Address, LookupSource, ChannelId, DAOId, Url, Membership, StakingAccountMemberBinding, BuyMembershipParameters, InviteMembershipParameters, CouncilStageAnnouncing, CouncilStageElection, CouncilStageUpdate, CouncilStage, Candidate, CouncilMemberOf, CastVoteOf, ForumUserId, ModeratorId, CategoryId, PostReactionId, Category, Thread, Post, PollAlternative, Poll, PrivilegedActor, PollInput, ThreadOf, ExtendedPostId, ApplicationId, Application, ApplicationInfo, ApplicationIdSet, ApplicationIdToWorkerIdMap, WorkerId, Worker, WorkerInfo, Opening, OpeningId, StakePolicy, StakeParameters, StorageProviderId, OpeningType, ApplyOnOpeningParameters, Penalty, RewardPaymentType, ContentId, LiaisonJudgement, DataObject, DataObjectStorageRelationshipId, DataObjectStorageRelationship, DataObjectTypeId, DataObjectType, DataObjectsMap, ContentParameters, StorageObjectOwner, ObjectOwner, Voucher, VoucherLimit, UploadingStatus, ProposalId, ProposalStatus, ProposalOf, ProposalDetails, ProposalDetailsOf, VotingResults, ProposalParameters, GeneralProposalParameters, VoteKind, DiscussionThread, DiscussionPost, CreateOpeningParameters, FillOpeningParameters, TerminateRoleParameters, ProposalDecision, ExecutionFailed, Approved, SetLeadParams, ThreadMode, ExecutionStatus, FundingRequestParameters, ReferendumStageVoting, ReferendumStageRevealing, ReferendumStage, OptionResult, VotePower, ConstitutionInfo, ParticipantId, Title, UpdatedTitle, UpdatedBody, ReplyId, Reply, ReplyToDelete, BountyId, EntryId, BountyActor, AssuranceContractType, FundingType_Limited, FundingType_Perpetual, FundingType, BountyCreationParameters, OracleJudgment_Winner, OracleJudgment, Entry, CuratorId, CuratorGroupId, CuratorGroup, ContentActor, NewAsset, Channel, ChannelOwner, ChannelCategoryId, ChannelCategory, ChannelCategoryCreationParameters, ChannelCategoryUpdateParameters, ChannelCreationParameters, ChannelUpdateParameters, ChannelOwnershipTransferRequestId, ChannelOwnershipTransferRequest, Video, VideoId, VideoCategoryId, VideoCategory, VideoCategoryCreationParameters, VideoCategoryUpdateParameters, VideoCreationParameters, VideoUpdateParameters, Person, PersonId, PersonController, PersonActor, PersonCreationParameters, PersonUpdateParameters, Playlist, PlaylistId, PlaylistCreationParameters, PlaylistUpdateParameters, SeriesId, Series, Season, SeriesParameters, SeasonParameters, EpisodeParemters, MaxNumber, IsCensored }; \ No newline at end of file +export { ActorId, MemberId, BlockAndTime, ThreadId, PostId, InputValidationLengthConstraint, WorkingGroup, MemoText, BalanceKind, Address, LookupSource, ChannelId, DAOId, Url, Membership, StakingAccountMemberBinding, BuyMembershipParameters, InviteMembershipParameters, CouncilStageAnnouncing, CouncilStageElection, CouncilStageUpdate, CouncilStage, Candidate, CouncilMemberOf, CastVoteOf, ForumUserId, ModeratorId, CategoryId, PostReactionId, Category, Thread, Post, PollAlternative, Poll, PrivilegedActor, PollInput, ThreadOf, ExtendedPostId, ApplicationId, Application, ApplicationInfo, ApplicationIdSet, ApplicationIdToWorkerIdMap, WorkerId, Worker, WorkerInfo, Opening, OpeningId, StakePolicy, StakeParameters, StorageProviderId, OpeningType, ApplyOnOpeningParameters, Penalty, RewardPaymentType, ContentId, LiaisonJudgement, DataObject, DataObjectStorageRelationshipId, DataObjectStorageRelationship, DataObjectTypeId, DataObjectType, DataObjectsMap, ContentParameters, StorageObjectOwner, ObjectOwner, Voucher, VoucherLimit, UploadingStatus, ProposalId, ProposalStatus, ProposalOf, ProposalDetails, ProposalDetailsOf, VotingResults, ProposalParameters, GeneralProposalParameters, VoteKind, DiscussionThread, DiscussionPost, CreateOpeningParameters, FillOpeningParameters, TerminateRoleParameters, ProposalDecision, ExecutionFailed, Approved, SetLeadParams, ThreadMode, ExecutionStatus, FundingRequestParameters, ReferendumStageVoting, ReferendumStageRevealing, ReferendumStage, OptionResult, VotePower, ConstitutionInfo, ParticipantId, Title, UpdatedTitle, UpdatedBody, ReplyId, Reply, ReplyToDelete, BountyId, JSBounty, BountyMilestone, EntryId, BountyActor, AssuranceContractType, FundingType_Limited, FundingType_Perpetual, FundingType, BountyCreationParameters, OracleJudgment_Winner, OracleJudgment, Entry, CuratorId, CuratorGroupId, CuratorGroup, ContentActor, NewAsset, Channel, ChannelOwner, ChannelCategoryId, ChannelCategory, ChannelCategoryCreationParameters, ChannelCategoryUpdateParameters, ChannelCreationParameters, ChannelUpdateParameters, ChannelOwnershipTransferRequestId, ChannelOwnershipTransferRequest, Video, VideoId, VideoCategoryId, VideoCategory, VideoCategoryCreationParameters, VideoCategoryUpdateParameters, VideoCreationParameters, VideoUpdateParameters, Person, PersonId, PersonController, PersonActor, PersonCreationParameters, PersonUpdateParameters, Playlist, PlaylistId, PlaylistCreationParameters, PlaylistUpdateParameters, SeriesId, Series, Season, SeriesParameters, SeasonParameters, EpisodeParemters, MaxNumber, IsCensored }; \ No newline at end of file diff --git a/types/augment-codec/augment-api-query.ts b/types/augment-codec/augment-api-query.ts index 53b08a7568..34fd6b27b7 100644 --- a/types/augment-codec/augment-api-query.ts +++ b/types/augment-codec/augment-api-query.ts @@ -3,7 +3,7 @@ import type { Bytes, Option, Vec, bool, u16, u32, u64, u8 } from '@polkadot/types'; import type { AnyNumber, ITuple, Observable } from '@polkadot/types/types'; -import type { Application, ApplicationId, BountyActor, BountyId, Candidate, CastVoteOf, Category, CategoryId, Channel, ChannelCategory, ChannelCategoryId, ChannelId, ChannelOwnershipTransferRequest, ChannelOwnershipTransferRequestId, ConstitutionInfo, ContentId, CouncilMemberOf, CouncilStageUpdate, CuratorGroup, CuratorGroupId, DataObject, DataObjectStorageRelationship, DataObjectStorageRelationshipId, DataObjectType, DataObjectTypeId, DiscussionPost, DiscussionThread, Entry, EntryId, ForumUserId, MemberId, Membership, MemoText, ModeratorId, ObjectOwner, Opening, OpeningId, Person, PersonId, Playlist, PlaylistId, Post, PostId, ProposalId, ProposalOf, ReferendumStage, Reply, ReplyId, Series, SeriesId, StakingAccountMemberBinding, ThreadId, ThreadOf, Video, VideoCategory, VideoCategoryId, VideoId, VoteKind, Voucher, Worker, WorkerId } from './all'; +import type { Application, ApplicationId, BountyActor, BountyId, Candidate, CastVoteOf, Category, CategoryId, Channel, ChannelCategory, ChannelCategoryId, ChannelId, ChannelOwnershipTransferRequest, ChannelOwnershipTransferRequestId, ConstitutionInfo, ContentId, CouncilMemberOf, CouncilStageUpdate, CuratorGroup, CuratorGroupId, DataObject, DataObjectStorageRelationship, DataObjectStorageRelationshipId, DataObjectType, DataObjectTypeId, DiscussionPost, DiscussionThread, Entry, EntryId, ForumUserId, JSBounty as Bounty, MemberId, Membership, MemoText, ModeratorId, ObjectOwner, Opening, OpeningId, Person, PersonId, Playlist, PlaylistId, Post, PostId, ProposalId, ProposalOf, ReferendumStage, Reply, ReplyId, Series, SeriesId, StakingAccountMemberBinding, ThreadId, ThreadOf, Video, VideoCategory, VideoCategoryId, VideoId, VoteKind, Voucher, Worker, WorkerId } from './all'; import type { UncleEntryItem } from '@polkadot/types/interfaces/authorship'; import type { BabeAuthorityWeight, MaybeRandomness, NextConfigDescriptor, Randomness } from '@polkadot/types/interfaces/babe'; import type { AccountData, BalanceLock } from '@polkadot/types/interfaces/balances'; @@ -15,7 +15,6 @@ import type { AccountId, Balance, BalanceOf, BlockNumber, ExtrinsicsWeight, Hash import type { Keys, SessionIndex } from '@polkadot/types/interfaces/session'; import type { ActiveEraInfo, ElectionResult, ElectionScore, ElectionStatus, EraIndex, EraRewardPoints, Exposure, Forcing, Nominations, RewardDestination, SlashingSpans, SpanIndex, SpanRecord, StakingLedger, UnappliedSlash, ValidatorPrefs } from '@polkadot/types/interfaces/staking'; import type { AccountInfo, DigestOf, EventIndex, EventRecord, LastRuntimeUpgradeInfo, Phase } from '@polkadot/types/interfaces/system'; -import type { Bounty } from '@polkadot/types/interfaces/treasury'; import type { Multiplier } from '@polkadot/types/interfaces/txpayment'; import type { ApiTypes } from '@polkadot/api/types'; diff --git a/types/augment-codec/augment-types.ts b/types/augment-codec/augment-types.ts index b8ba57bb8f..8f9929b8d8 100644 --- a/types/augment-codec/augment-types.ts +++ b/types/augment-codec/augment-types.ts @@ -2,7 +2,7 @@ /* eslint-disable */ import type { BitVec, Bool, Bytes, Compact, Data, I128, I16, I256, I32, I64, I8, Json, Null, Option, Raw, StorageKey, Text, Type, U128, U16, U256, U32, U64, U8, USize, Vec, bool, i128, i16, i256, i32, i64, i8, u128, u16, u256, u32, u64, u8, usize } from '@polkadot/types'; -import type { ActorId, Application, ApplicationId, ApplicationIdSet, ApplicationIdToWorkerIdMap, ApplicationInfo, ApplyOnOpeningParameters, Approved, AssuranceContractType, BalanceKind, BlockAndTime, BountyActor, BountyCreationParameters, BountyId, BuyMembershipParameters, Candidate, CastVoteOf, Category, CategoryId, Channel, ChannelCategory, ChannelCategoryCreationParameters, ChannelCategoryId, ChannelCategoryUpdateParameters, ChannelCreationParameters, ChannelId, ChannelOwner, ChannelOwnershipTransferRequest, ChannelOwnershipTransferRequestId, ChannelUpdateParameters, ConstitutionInfo, ContentActor, ContentId, ContentParameters, CouncilMemberOf, CouncilStage, CouncilStageAnnouncing, CouncilStageElection, CouncilStageUpdate, CreateOpeningParameters, CuratorGroup, CuratorGroupId, CuratorId, DAOId, DataObject, DataObjectStorageRelationship, DataObjectStorageRelationshipId, DataObjectType, DataObjectTypeId, DataObjectsMap, DiscussionPost, DiscussionThread, Entry, EntryId, EpisodeParemters, ExecutionFailed, ExecutionStatus, ExtendedPostId, FillOpeningParameters, ForumUserId, FundingRequestParameters, FundingType, FundingType_Limited, FundingType_Perpetual, GeneralProposalParameters, InputValidationLengthConstraint, InviteMembershipParameters, IsCensored, LiaisonJudgement, MaxNumber, MemberId, Membership, MemoText, ModeratorId, NewAsset, ObjectOwner, Opening, OpeningId, OpeningType, OptionResult, OracleJudgment, OracleJudgment_Winner, ParticipantId, Penalty, Person, PersonActor, PersonController, PersonCreationParameters, PersonId, PersonUpdateParameters, Playlist, PlaylistCreationParameters, PlaylistId, PlaylistUpdateParameters, Poll, PollAlternative, PollInput, Post, PostId, PostReactionId, PrivilegedActor, ProposalDecision, ProposalDetails, ProposalDetailsOf, ProposalId, ProposalOf, ProposalParameters, ProposalStatus, ReferendumStage, ReferendumStageRevealing, ReferendumStageVoting, Reply, ReplyId, ReplyToDelete, RewardPaymentType, Season, SeasonParameters, Series, SeriesId, SeriesParameters, SetLeadParams, StakeParameters, StakePolicy, StakingAccountMemberBinding, StorageObjectOwner, StorageProviderId, TerminateRoleParameters, Thread, ThreadId, ThreadMode, ThreadOf, Title, UpdatedBody, UpdatedTitle, UploadingStatus, Url, Video, VideoCategory, VideoCategoryCreationParameters, VideoCategoryId, VideoCategoryUpdateParameters, VideoCreationParameters, VideoId, VideoUpdateParameters, VoteKind, VotePower, VotingResults, Voucher, VoucherLimit, Worker, WorkerId, WorkerInfo, WorkingGroup } from './all'; +import type { ActorId, Application, ApplicationId, ApplicationIdSet, ApplicationIdToWorkerIdMap, ApplicationInfo, ApplyOnOpeningParameters, Approved, AssuranceContractType, BalanceKind, BlockAndTime, BountyActor, BountyCreationParameters, BountyId, BountyMilestone, BuyMembershipParameters, Candidate, CastVoteOf, Category, CategoryId, Channel, ChannelCategory, ChannelCategoryCreationParameters, ChannelCategoryId, ChannelCategoryUpdateParameters, ChannelCreationParameters, ChannelId, ChannelOwner, ChannelOwnershipTransferRequest, ChannelOwnershipTransferRequestId, ChannelUpdateParameters, ConstitutionInfo, ContentActor, ContentId, ContentParameters, CouncilMemberOf, CouncilStage, CouncilStageAnnouncing, CouncilStageElection, CouncilStageUpdate, CreateOpeningParameters, CuratorGroup, CuratorGroupId, CuratorId, DAOId, DataObject, DataObjectStorageRelationship, DataObjectStorageRelationshipId, DataObjectType, DataObjectTypeId, DataObjectsMap, DiscussionPost, DiscussionThread, Entry, EntryId, EpisodeParemters, ExecutionFailed, ExecutionStatus, ExtendedPostId, FillOpeningParameters, ForumUserId, FundingRequestParameters, FundingType, FundingType_Limited, FundingType_Perpetual, GeneralProposalParameters, InputValidationLengthConstraint, InviteMembershipParameters, IsCensored, JSBounty, LiaisonJudgement, MaxNumber, MemberId, Membership, MemoText, ModeratorId, NewAsset, ObjectOwner, Opening, OpeningId, OpeningType, OptionResult, OracleJudgment, OracleJudgment_Winner, ParticipantId, Penalty, Person, PersonActor, PersonController, PersonCreationParameters, PersonId, PersonUpdateParameters, Playlist, PlaylistCreationParameters, PlaylistId, PlaylistUpdateParameters, Poll, PollAlternative, PollInput, Post, PostId, PostReactionId, PrivilegedActor, ProposalDecision, ProposalDetails, ProposalDetailsOf, ProposalId, ProposalOf, ProposalParameters, ProposalStatus, ReferendumStage, ReferendumStageRevealing, ReferendumStageVoting, Reply, ReplyId, ReplyToDelete, RewardPaymentType, Season, SeasonParameters, Series, SeriesId, SeriesParameters, SetLeadParams, StakeParameters, StakePolicy, StakingAccountMemberBinding, StorageObjectOwner, StorageProviderId, TerminateRoleParameters, Thread, ThreadId, ThreadMode, ThreadOf, Title, UpdatedBody, UpdatedTitle, UploadingStatus, Url, Video, VideoCategory, VideoCategoryCreationParameters, VideoCategoryId, VideoCategoryUpdateParameters, VideoCreationParameters, VideoId, VideoUpdateParameters, VoteKind, VotePower, VotingResults, Voucher, VoucherLimit, Worker, WorkerId, WorkerInfo, WorkingGroup } from './all'; import type { AssetApproval, AssetApprovalKey, AssetBalance, AssetDestroyWitness, AssetDetails, AssetMetadata, TAssetBalance, TAssetDepositBalance } from '@polkadot/types/interfaces/assets'; import type { BlockAttestations, IncludedBlocks, MoreAttestations } from '@polkadot/types/interfaces/attestations'; import type { RawAuraPreDigest } from '@polkadot/types/interfaces/aura'; @@ -311,6 +311,7 @@ declare module '@polkadot/types/types/registry' { 'Option': Option; 'Option': Option; 'Option': Option; + 'Option': Option; 'Option': Option; 'Option': Option; 'Option': Option; @@ -682,6 +683,7 @@ declare module '@polkadot/types/types/registry' { 'Option': Option; 'Option': Option; 'Option': Option; + 'Option': Option; 'Option': Option; 'Option': Option; 'Option': Option; @@ -1403,6 +1405,7 @@ declare module '@polkadot/types/types/registry' { 'Vec': Vec; 'Vec': Vec; 'Vec': Vec; + 'Vec': Vec; 'Vec': Vec; 'Vec': Vec; 'Vec': Vec; @@ -1774,6 +1777,7 @@ declare module '@polkadot/types/types/registry' { 'Vec': Vec; 'Vec': Vec; 'Vec': Vec; + 'Vec': Vec; 'Vec': Vec; 'Vec': Vec; 'Vec': Vec; @@ -2495,6 +2499,7 @@ declare module '@polkadot/types/types/registry' { BountyCreationParameters: BountyCreationParameters; BountyId: BountyId; BountyIndex: BountyIndex; + BountyMilestone: BountyMilestone; BountyStatus: BountyStatus; BountyStatusActive: BountyStatusActive; BountyStatusCuratorProposed: BountyStatusCuratorProposed; @@ -2866,6 +2871,7 @@ declare module '@polkadot/types/types/registry' { InvalidTransaction: InvalidTransaction; InviteMembershipParameters: InviteMembershipParameters; IsCensored: IsCensored; + JSBounty: JSBounty; Json: Json; Junction: Junction; Justification: Justification; diff --git a/types/augment/all/defs.json b/types/augment/all/defs.json index 7ab9d07070..fc3130812d 100644 --- a/types/augment/all/defs.json +++ b/types/augment/all/defs.json @@ -500,6 +500,20 @@ "text_hash": "Hash" }, "BountyId": "u32", + "JSBounty": { + "creation_params": "BountyCreationParameters", + "total_funding": "u128", + "milestone": "BountyMilestone", + "active_work_entry_count": "u32" + }, + "BountyMilestone": { + "_enum": { + "Created": "{\"created_at\":\"u32\",\"has_contributions\":\"bool\"}", + "BountyMaxFundingReached": "{\"max_funding_reached_at\":\"u32\"}", + "WorkSubmitted": "{\"work_period_started_at\":\"u32\"}", + "JudgmentSubmitted": "{\"successful_bounty\":\"bool\"}" + } + }, "EntryId": "u32", "BountyActor": { "_enum": { diff --git a/types/augment/all/types.ts b/types/augment/all/types.ts index 64a0123a36..b468e0e7f2 100644 --- a/types/augment/all/types.ts +++ b/types/augment/all/types.ts @@ -97,6 +97,27 @@ export interface BountyCreationParameters extends Struct { /** @name BountyId */ export interface BountyId extends u32 {} +/** @name BountyMilestone */ +export interface BountyMilestone extends Enum { + readonly isCreated: boolean; + readonly asCreated: { + readonly created_at: u32; + readonly has_contributions: bool; + } & Struct; + readonly isBountyMaxFundingReached: boolean; + readonly asBountyMaxFundingReached: { + readonly max_funding_reached_at: u32; + } & Struct; + readonly isWorkSubmitted: boolean; + readonly asWorkSubmitted: { + readonly work_period_started_at: u32; + } & Struct; + readonly isJudgmentSubmitted: boolean; + readonly asJudgmentSubmitted: { + readonly successful_bounty: bool; + } & Struct; +} + /** @name BuyMembershipParameters */ export interface BuyMembershipParameters extends Struct { readonly root_account: AccountId; @@ -431,6 +452,14 @@ export interface InviteMembershipParameters extends Struct { /** @name IsCensored */ export interface IsCensored extends bool {} +/** @name JSBounty */ +export interface JSBounty extends Struct { + readonly creation_params: BountyCreationParameters; + readonly total_funding: u128; + readonly milestone: BountyMilestone; + readonly active_work_entry_count: u32; +} + /** @name LiaisonJudgement */ export interface LiaisonJudgement extends Enum { readonly isPending: boolean; diff --git a/types/augment/augment-types.ts b/types/augment/augment-types.ts index b8ba57bb8f..8f9929b8d8 100644 --- a/types/augment/augment-types.ts +++ b/types/augment/augment-types.ts @@ -2,7 +2,7 @@ /* eslint-disable */ import type { BitVec, Bool, Bytes, Compact, Data, I128, I16, I256, I32, I64, I8, Json, Null, Option, Raw, StorageKey, Text, Type, U128, U16, U256, U32, U64, U8, USize, Vec, bool, i128, i16, i256, i32, i64, i8, u128, u16, u256, u32, u64, u8, usize } from '@polkadot/types'; -import type { ActorId, Application, ApplicationId, ApplicationIdSet, ApplicationIdToWorkerIdMap, ApplicationInfo, ApplyOnOpeningParameters, Approved, AssuranceContractType, BalanceKind, BlockAndTime, BountyActor, BountyCreationParameters, BountyId, BuyMembershipParameters, Candidate, CastVoteOf, Category, CategoryId, Channel, ChannelCategory, ChannelCategoryCreationParameters, ChannelCategoryId, ChannelCategoryUpdateParameters, ChannelCreationParameters, ChannelId, ChannelOwner, ChannelOwnershipTransferRequest, ChannelOwnershipTransferRequestId, ChannelUpdateParameters, ConstitutionInfo, ContentActor, ContentId, ContentParameters, CouncilMemberOf, CouncilStage, CouncilStageAnnouncing, CouncilStageElection, CouncilStageUpdate, CreateOpeningParameters, CuratorGroup, CuratorGroupId, CuratorId, DAOId, DataObject, DataObjectStorageRelationship, DataObjectStorageRelationshipId, DataObjectType, DataObjectTypeId, DataObjectsMap, DiscussionPost, DiscussionThread, Entry, EntryId, EpisodeParemters, ExecutionFailed, ExecutionStatus, ExtendedPostId, FillOpeningParameters, ForumUserId, FundingRequestParameters, FundingType, FundingType_Limited, FundingType_Perpetual, GeneralProposalParameters, InputValidationLengthConstraint, InviteMembershipParameters, IsCensored, LiaisonJudgement, MaxNumber, MemberId, Membership, MemoText, ModeratorId, NewAsset, ObjectOwner, Opening, OpeningId, OpeningType, OptionResult, OracleJudgment, OracleJudgment_Winner, ParticipantId, Penalty, Person, PersonActor, PersonController, PersonCreationParameters, PersonId, PersonUpdateParameters, Playlist, PlaylistCreationParameters, PlaylistId, PlaylistUpdateParameters, Poll, PollAlternative, PollInput, Post, PostId, PostReactionId, PrivilegedActor, ProposalDecision, ProposalDetails, ProposalDetailsOf, ProposalId, ProposalOf, ProposalParameters, ProposalStatus, ReferendumStage, ReferendumStageRevealing, ReferendumStageVoting, Reply, ReplyId, ReplyToDelete, RewardPaymentType, Season, SeasonParameters, Series, SeriesId, SeriesParameters, SetLeadParams, StakeParameters, StakePolicy, StakingAccountMemberBinding, StorageObjectOwner, StorageProviderId, TerminateRoleParameters, Thread, ThreadId, ThreadMode, ThreadOf, Title, UpdatedBody, UpdatedTitle, UploadingStatus, Url, Video, VideoCategory, VideoCategoryCreationParameters, VideoCategoryId, VideoCategoryUpdateParameters, VideoCreationParameters, VideoId, VideoUpdateParameters, VoteKind, VotePower, VotingResults, Voucher, VoucherLimit, Worker, WorkerId, WorkerInfo, WorkingGroup } from './all'; +import type { ActorId, Application, ApplicationId, ApplicationIdSet, ApplicationIdToWorkerIdMap, ApplicationInfo, ApplyOnOpeningParameters, Approved, AssuranceContractType, BalanceKind, BlockAndTime, BountyActor, BountyCreationParameters, BountyId, BountyMilestone, BuyMembershipParameters, Candidate, CastVoteOf, Category, CategoryId, Channel, ChannelCategory, ChannelCategoryCreationParameters, ChannelCategoryId, ChannelCategoryUpdateParameters, ChannelCreationParameters, ChannelId, ChannelOwner, ChannelOwnershipTransferRequest, ChannelOwnershipTransferRequestId, ChannelUpdateParameters, ConstitutionInfo, ContentActor, ContentId, ContentParameters, CouncilMemberOf, CouncilStage, CouncilStageAnnouncing, CouncilStageElection, CouncilStageUpdate, CreateOpeningParameters, CuratorGroup, CuratorGroupId, CuratorId, DAOId, DataObject, DataObjectStorageRelationship, DataObjectStorageRelationshipId, DataObjectType, DataObjectTypeId, DataObjectsMap, DiscussionPost, DiscussionThread, Entry, EntryId, EpisodeParemters, ExecutionFailed, ExecutionStatus, ExtendedPostId, FillOpeningParameters, ForumUserId, FundingRequestParameters, FundingType, FundingType_Limited, FundingType_Perpetual, GeneralProposalParameters, InputValidationLengthConstraint, InviteMembershipParameters, IsCensored, JSBounty, LiaisonJudgement, MaxNumber, MemberId, Membership, MemoText, ModeratorId, NewAsset, ObjectOwner, Opening, OpeningId, OpeningType, OptionResult, OracleJudgment, OracleJudgment_Winner, ParticipantId, Penalty, Person, PersonActor, PersonController, PersonCreationParameters, PersonId, PersonUpdateParameters, Playlist, PlaylistCreationParameters, PlaylistId, PlaylistUpdateParameters, Poll, PollAlternative, PollInput, Post, PostId, PostReactionId, PrivilegedActor, ProposalDecision, ProposalDetails, ProposalDetailsOf, ProposalId, ProposalOf, ProposalParameters, ProposalStatus, ReferendumStage, ReferendumStageRevealing, ReferendumStageVoting, Reply, ReplyId, ReplyToDelete, RewardPaymentType, Season, SeasonParameters, Series, SeriesId, SeriesParameters, SetLeadParams, StakeParameters, StakePolicy, StakingAccountMemberBinding, StorageObjectOwner, StorageProviderId, TerminateRoleParameters, Thread, ThreadId, ThreadMode, ThreadOf, Title, UpdatedBody, UpdatedTitle, UploadingStatus, Url, Video, VideoCategory, VideoCategoryCreationParameters, VideoCategoryId, VideoCategoryUpdateParameters, VideoCreationParameters, VideoId, VideoUpdateParameters, VoteKind, VotePower, VotingResults, Voucher, VoucherLimit, Worker, WorkerId, WorkerInfo, WorkingGroup } from './all'; import type { AssetApproval, AssetApprovalKey, AssetBalance, AssetDestroyWitness, AssetDetails, AssetMetadata, TAssetBalance, TAssetDepositBalance } from '@polkadot/types/interfaces/assets'; import type { BlockAttestations, IncludedBlocks, MoreAttestations } from '@polkadot/types/interfaces/attestations'; import type { RawAuraPreDigest } from '@polkadot/types/interfaces/aura'; @@ -311,6 +311,7 @@ declare module '@polkadot/types/types/registry' { 'Option': Option; 'Option': Option; 'Option': Option; + 'Option': Option; 'Option': Option; 'Option': Option; 'Option': Option; @@ -682,6 +683,7 @@ declare module '@polkadot/types/types/registry' { 'Option': Option; 'Option': Option; 'Option': Option; + 'Option': Option; 'Option': Option; 'Option': Option; 'Option': Option; @@ -1403,6 +1405,7 @@ declare module '@polkadot/types/types/registry' { 'Vec': Vec; 'Vec': Vec; 'Vec': Vec; + 'Vec': Vec; 'Vec': Vec; 'Vec': Vec; 'Vec': Vec; @@ -1774,6 +1777,7 @@ declare module '@polkadot/types/types/registry' { 'Vec': Vec; 'Vec': Vec; 'Vec': Vec; + 'Vec': Vec; 'Vec': Vec; 'Vec': Vec; 'Vec': Vec; @@ -2495,6 +2499,7 @@ declare module '@polkadot/types/types/registry' { BountyCreationParameters: BountyCreationParameters; BountyId: BountyId; BountyIndex: BountyIndex; + BountyMilestone: BountyMilestone; BountyStatus: BountyStatus; BountyStatusActive: BountyStatusActive; BountyStatusCuratorProposed: BountyStatusCuratorProposed; @@ -2866,6 +2871,7 @@ declare module '@polkadot/types/types/registry' { InvalidTransaction: InvalidTransaction; InviteMembershipParameters: InviteMembershipParameters; IsCensored: IsCensored; + JSBounty: JSBounty; Json: Json; Junction: Junction; Justification: Justification; diff --git a/types/src/bounty.ts b/types/src/bounty.ts index 7975b3f421..4eb4bd994d 100644 --- a/types/src/bounty.ts +++ b/types/src/bounty.ts @@ -57,8 +57,41 @@ export class Entry extends JoyStructDecorated({ oracle_judgment_result: Option.with(OracleJudgment), }) {} +export class BountyMilestone_Created extends JoyStructDecorated({ + created_at: u32, // BlockNumber + has_contributions: bool, +}) {} + +export class BountyMilestone_BountyMaxFundingReached extends JoyStructDecorated({ + max_funding_reached_at: u32, // BlockNumber +}) {} + +export class BountyMilestone_WorkSubmitted extends JoyStructDecorated({ + work_period_started_at: u32, // BlockNumber +}) {} + +export class BountyMilestone_JudgmentSubmitted extends JoyStructDecorated({ + successful_bounty: bool, +}) {} + +export class BountyMilestone extends JoyEnum({ + Created: BountyMilestone_Created, + BountyMaxFundingReached: BountyMilestone_BountyMaxFundingReached, + WorkSubmitted: BountyMilestone_WorkSubmitted, + JudgmentSubmitted: BountyMilestone_JudgmentSubmitted, +}) {} + +export class JSBounty extends JoyStructDecorated({ + creation_params: BountyCreationParameters, + total_funding: u128, + milestone: BountyMilestone, + active_work_entry_count: u32, +}) {} + export const bountyTypes = { BountyId, + JSBounty, + BountyMilestone, EntryId, BountyActor, AssuranceContractType, From d4c8b31ac0af5c411982ab6965ca779b62789375 Mon Sep 17 00:00:00 2001 From: Zeeshan Akram <97m.zeeshan@gmail.com> Date: Tue, 25 Jan 2022 01:05:01 +0500 Subject: [PATCH 02/16] fix: catch block unknown type error --- cli/src/base/AccountsCommandBase.ts | 16 +++++++++++++++- cli/src/base/UploadCommandBase.ts | 4 ++-- cli/src/commands/api/inspect.ts | 2 +- 3 files changed, 18 insertions(+), 4 deletions(-) diff --git a/cli/src/base/AccountsCommandBase.ts b/cli/src/base/AccountsCommandBase.ts index 421139b719..e9226e1218 100644 --- a/cli/src/base/AccountsCommandBase.ts +++ b/cli/src/base/AccountsCommandBase.ts @@ -18,6 +18,7 @@ import { mnemonicGenerate } from '@polkadot/util-crypto' import { validateAddress } from '../helpers/validation' import slug from 'slug' import { Membership } from '@joystream/types/members' +import { CouncilMemberOf } from '@joystream/types/council' import BN from 'bn.js' const ACCOUNTS_DIRNAME = 'accounts' @@ -158,7 +159,7 @@ export default abstract class AccountsCommandBase extends ApiCommandBase { // Try adding and retrieving the keys in order to validate that the backup file is correct keyring.addFromJson(accountJsonObj) account = keyring.getPair(accountJsonObj.address) as NamedKeyringPair // We can be sure it's named, because we forced it before - } catch (e) { + } catch (e: any) { throw new CLIError(`Provided backup file is not valid (${e.message})`, { exit: ExitCodes.InvalidFile }) } @@ -325,6 +326,19 @@ export default abstract class AccountsCommandBase extends ApiCommandBase { } } + // TODO: check if the logic to validate Council is correct. + // get the context of council member that will take action on behalf of the Council + async getCouncilMemberContext(): Promise { + const member = await this.getRequiredMemberContext() + const councilMembers = await this.getOriginalApi().query.council.councilMembers() + + const councilMember = councilMembers.find((cm) => cm.membership_id === member.id) + if (councilMember === undefined) { + this.error('No council member controller key available!', { exit: ExitCodes.AccessDenied }) + } + return councilMember + } + async getRequiredMemberContext(): Promise { // TODO: Limit only to a set of members provided by the user? const allMembers = await this.getApi().allMembers() diff --git a/cli/src/base/UploadCommandBase.ts b/cli/src/base/UploadCommandBase.ts index 10588d3076..401504bc17 100644 --- a/cli/src/base/UploadCommandBase.ts +++ b/cli/src/base/UploadCommandBase.ts @@ -102,7 +102,7 @@ export default abstract class UploadCommandBase extends ContentDirectoryCommandB let ffProbeMetadata: VideoFFProbeMetadata = {} try { ffProbeMetadata = await this.getVideoFFProbeMetadata(filePath) - } catch (e) { + } catch (e: any) { const message = e.message || e this.warn(`Failed to get video metadata via ffprobe (${message})`) } @@ -208,7 +208,7 @@ export default abstract class UploadCommandBase extends ContentDirectoryCommandB maxBodyLength: fileSize, } await axios.put(uploadUrl, fileStream, config) - } catch (e) { + } catch (e: any) { progressBar.stop() const msg = (e.response && e.response.data && e.response.data.message) || e.message || e this.error(`Unexpected error when trying to upload a file: ${msg}`, { diff --git a/cli/src/commands/api/inspect.ts b/cli/src/commands/api/inspect.ts index 3c43726c1d..6f57a96ec3 100644 --- a/cli/src/commands/api/inspect.ts +++ b/cli/src/commands/api/inspect.ts @@ -80,7 +80,7 @@ export default class ApiInspect extends ApiCommandBase { return this.getUnaugmentedApi().query[apiModule][apiMethod].creator.meta } else { // Currently the only other optoin is api.consts - const method = (this.getUnaugmentedApi().consts[apiModule][apiMethod] as unknown) as AugmentedConst<'promise'> + const method = this.getUnaugmentedApi().consts[apiModule][apiMethod] as unknown as AugmentedConst<'promise'> return method.meta } } From cd2c61b543a9194f171f26f32815de361d154108 Mon Sep 17 00:00:00 2001 From: Zeeshan Akram <97m.zeeshan@gmail.com> Date: Tue, 25 Jan 2022 13:25:37 +0500 Subject: [PATCH 03/16] feat: bounty module cli commands (bounty related) --- cli/examples/bounty/CreateBounty.json | 13 ++ cli/package.json | 3 + cli/src/Api.ts | 16 ++- cli/src/Types.ts | 26 ++++ cli/src/base/BountyCommandBase.ts | 157 ++++++++++++++++++++++++ cli/src/commands/bounty/bounties.ts | 30 +++++ cli/src/commands/bounty/bounty.ts | 36 ++++++ cli/src/commands/bounty/cancelBounty.ts | 45 +++++++ cli/src/commands/bounty/createBounty.ts | 80 ++++++++++++ cli/src/commands/bounty/vetoBounty.ts | 35 ++++++ cli/src/json-schemas/Bounty.ts | 28 +++++ 11 files changed, 468 insertions(+), 1 deletion(-) create mode 100644 cli/examples/bounty/CreateBounty.json create mode 100644 cli/src/base/BountyCommandBase.ts create mode 100644 cli/src/commands/bounty/bounties.ts create mode 100644 cli/src/commands/bounty/bounty.ts create mode 100644 cli/src/commands/bounty/cancelBounty.ts create mode 100644 cli/src/commands/bounty/createBounty.ts create mode 100644 cli/src/commands/bounty/vetoBounty.ts create mode 100644 cli/src/json-schemas/Bounty.ts diff --git a/cli/examples/bounty/CreateBounty.json b/cli/examples/bounty/CreateBounty.json new file mode 100644 index 0000000000..1204be6ffe --- /dev/null +++ b/cli/examples/bounty/CreateBounty.json @@ -0,0 +1,13 @@ +{ + "title": "Video like feature", + "description": "Members can like the videos", + "discussionThread": "https://testnet.joystream.org/#/bounty", + "bannerImageUri": "", + "contractTypeInput": [], + "oracle": 3, + "cherry": 100, + "entrantStake": 100, + "fundingTypeInput": { "target": 20 }, + "workPeriod": 50, + "judgementPeriod": 50 +} diff --git a/cli/package.json b/cli/package.json index 707c1f6e64..d917350511 100644 --- a/cli/package.json +++ b/cli/package.json @@ -114,6 +114,9 @@ }, "content": { "description": "Interactions with content directory module - managing vidoes, channels, assets, categories and curator groups" + }, + "bounty": { + "description": "Bounty management - create bounties, submit work and win rewards" } } }, diff --git a/cli/src/Api.ts b/cli/src/Api.ts index d317dc5a62..d4db9150dd 100644 --- a/cli/src/Api.ts +++ b/cli/src/Api.ts @@ -42,6 +42,7 @@ import { VideoCategory, } from '@joystream/types/content' import { ContentId, DataObject } from '@joystream/types/storage' +import { BountyId, JSBounty as Bounty } from '@joystream/types/bounty' import { ApolloClient, InMemoryCache, HttpLink, NormalizedCacheObject, DocumentNode } from '@apollo/client/core' import fetch from 'cross-fetch' import { Maybe } from './graphql/generated/schema' @@ -95,7 +96,7 @@ export default class Api { // Get api for use-cases where no type augmentations are desirable public getUnaugmentedApi(): UnaugmentedApiPromise { - return (this._api as unknown) as UnaugmentedApiPromise + return this._api as unknown as UnaugmentedApiPromise } private static async initApi(apiUri: string = DEFAULT_API_URI, metadataCache: Record) { @@ -458,6 +459,19 @@ export default class Api { return this.entriesByIds(this._api.query.members.membershipById) } + // Bounty + async availableBounties(): Promise<[BountyId, Bounty][]> { + return await this.entriesByIds(this._api.query.bounty.bounties) + } + + async bountyById(bountyId: BountyId | number | string): Promise { + const exists = !!(await this._api.query.bounty.bounties.size(bountyId)).toNumber() + if (!exists) { + throw new CLIError(`Bonuty by id ${bountyId.toString()} not found!`) + } + return await this._api.query.bounty.bounties(bountyId) + } + // Content directory async availableChannels(): Promise<[ChannelId, Channel][]> { return await this.entriesByIds(this._api.query.content.channelById) diff --git a/cli/src/Types.ts b/cli/src/Types.ts index afe2a5cf32..2ef00c166b 100644 --- a/cli/src/Types.ts +++ b/cli/src/Types.ts @@ -16,6 +16,7 @@ import { IVideoMetadata, IVideoCategoryMetadata, IChannelCategoryMetadata, + IBountyMetadata, } from '@joystream/metadata-protobuf' // KeyringPair type extended with mandatory "meta.name" @@ -177,6 +178,31 @@ export type ChannelCategoryInputParameters = IChannelCategoryMetadata export type VideoCategoryInputParameters = IVideoCategoryMetadata +export type FundingTypeLimited = { + minFundingAmount: number + maxFundingAmount: number + fundingPeriod: number +} + +export type FundingTypePrepetual = { + target: number +} + +export type BountyInputParameters = IBountyMetadata & { + // oracle should undefined if bounty actor is Council, and + // a valid member id if bounty actor is a Member + oracle?: number + // contractTypeInput should be emply list in case of Open contract and + // contain list of members allowed to submit work in case of Closed contarct + contractTypeInput: string[] + cherry: number + entrantStake: number + // TODO: can this be improved? + fundingType: FundingTypeLimited & FundingTypePrepetual + workPeriod: number + judgementPeriod: number +} + type AnyNonObject = string | number | boolean | any[] | Long // JSONSchema utility types diff --git a/cli/src/base/BountyCommandBase.ts b/cli/src/base/BountyCommandBase.ts new file mode 100644 index 0000000000..e5222dc3a2 --- /dev/null +++ b/cli/src/base/BountyCommandBase.ts @@ -0,0 +1,157 @@ +import { RolesCommandBase } from './WorkingGroupsCommandBase' +import { flags } from '@oclif/command' +import { CLIError } from '@oclif/errors' +import ExitCodes from '../ExitCodes' +import { AssuranceContractType, BountyActor, FundingType, BountyId } from '@joystream/types/bounty' +import { FundingTypeLimited, FundingTypePrepetual } from 'src/Types' + +const CREATOR_CONTEXTS = ['Member', 'Council'] as const +const CONTRACT_TYPE_CONTEXTS = ['Open', 'Closed'] as const +const FUNDING_TYPE_CONTEXTS = ['Perpetual', 'Limited'] as const + +type CreatorContext = typeof CREATOR_CONTEXTS[number] +type ContractTypeContext = typeof CONTRACT_TYPE_CONTEXTS[number] +type FundingTypeContext = typeof FUNDING_TYPE_CONTEXTS[number] + +/** + * Abstract base class for commands related to bounty module. + */ +export default abstract class BountyCommandBase extends RolesCommandBase { + static creatorContextFlag = flags.enum({ + name: 'creatorContext', + required: false, + description: `Actor context to execute the command in (${CREATOR_CONTEXTS.join('/')})`, + options: [...CREATOR_CONTEXTS], + }) + + static contractTypeFlag = flags.enum({ + name: 'contractType', + required: false, + description: `Assurance contract type for bounty is (${CONTRACT_TYPE_CONTEXTS.join('/')})`, + options: [...CONTRACT_TYPE_CONTEXTS], + }) + + static fundingTypeFlag = flags.enum({ + name: 'fundingType', + required: false, + description: `Funding type for bounty is (${FUNDING_TYPE_CONTEXTS.join('/')})`, + options: [...FUNDING_TYPE_CONTEXTS], + }) + + async promptForCreatorContext( + message = 'Choose in which context you wish to execute the command' + ): Promise { + return this.simplePrompt({ + message, + type: 'list', + choices: CREATOR_CONTEXTS.map((c) => ({ name: c, value: c })), + }) + } + + async promptForContractType( + message = 'Choose contract type to select who can submit work for the bounty' + ): Promise { + return this.simplePrompt({ + message, + type: 'list', + choices: CONTRACT_TYPE_CONTEXTS.map((c) => ({ name: c, value: c })), + }) + } + + async promptForFundingType( + message = 'Choose funding type for the bounty based on time limitation preferences' + ): Promise { + return this.simplePrompt({ + message, + type: 'list', + choices: FUNDING_TYPE_CONTEXTS.map((c) => ({ name: c, value: c })), + }) + } + + // validate funding type input passed as json object key value + async validateAndPrepareFundingTypeInput( + fundingType: typeof FUNDING_TYPE_CONTEXTS[number], + fundingInput: FundingTypePrepetual | FundingTypeLimited + ): Promise { + const target = (fundingInput as FundingTypePrepetual).target + const maxFundingAmount = (fundingInput as FundingTypeLimited).maxFundingAmount + const minFundingAmount = (fundingInput as FundingTypeLimited).minFundingAmount + const fundingPeriod = (fundingInput as FundingTypeLimited).fundingPeriod + + if (fundingType === 'Perpetual' && target > 0) { + return this.createType('FundingType', { Perpetual: { target } }) + } else if ( + maxFundingAmount > 0 && + minFundingAmount > 0 && + fundingPeriod > 0 && + maxFundingAmount > minFundingAmount + ) { + return this.createType('FundingType', { + Limited: { + max_funding_amount: maxFundingAmount, + min_funding_amount: minFundingAmount, + funding_period: fundingPeriod, + }, + }) + } else { + throw new CLIError('Invalid funding type', { exit: ExitCodes.InvalidInput }) + } + } + + // prepare 'AssuranceContractType' based on contract type input i.e. Open | Closed. if contract type + // is open then contractTypeInput field in json input to createBounty command supposed to be empty array. + // Otherwise it would contain list of member id that can submit work. + async prepareContractTypeInput( + contractType: typeof CONTRACT_TYPE_CONTEXTS[number], + contractInput?: string[] + ): Promise { + if (contractType === 'Open') { + return this.createType('AssuranceContractType', { Open: null }) + } else { + if (contractInput !== undefined && contractInput.length === 0) { + throw new Error('Closed contract member list is empty') + } + return this.createType('AssuranceContractType', { Closed: contractInput }) + } + } + + async ensureBountyCanBeCanceled(bountyId: BountyId, creator: BountyActor): Promise { + const bounty = await this.getApi().bountyById(bountyId) + + // Only check if the creator trying to cancel the bounty + // is Member, Council can always veto the bounty + if (creator.isOfType('Member') && bounty.creation_params.creator.asType('Member') !== creator.asType('Member')) { + throw new CLIError(`Provided bounty actor cannot cancel the bounty!`) + } + + if (!bounty.milestone.isOfType('Created') || !bounty.milestone.asType('Created').has_contributions) { + throw new CLIError(`Bounty cannot be cancelled at this stage`) + } + } + + async validateAndPrepareOracleInput(oracle: number | undefined): Promise { + if (!oracle) { + return this.createType('BountyActor', { Council: null }) + } else { + // ensure that memver id is valid + await this.getOriginalApi().query.members.membershipById(oracle) + + return this.createType('BountyActor', { Member: oracle }) + } + } + + async getBountyActor(context: typeof CREATOR_CONTEXTS[number]): Promise<[BountyActor, string]> { + let bountyActorContext: [BountyActor, string] + + if (context === 'Member') { + const { id, membership } = await this.getRequiredMemberContext() + bountyActorContext = [this.createType('BountyActor', { Member: id }), membership.controller_account.toString()] + } else { + // TODO: check if the logic to validate Council is correct. + const council = await this.getCouncilMemberContext() + bountyActorContext = [this.createType('BountyActor', { Council: null }), council.membership_id.toString()] + } + + return bountyActorContext + } +} diff --git a/cli/src/commands/bounty/bounties.ts b/cli/src/commands/bounty/bounties.ts new file mode 100644 index 0000000000..3fff4712f3 --- /dev/null +++ b/cli/src/commands/bounty/bounties.ts @@ -0,0 +1,30 @@ +import BountyCommandBase from '../../base/BountyCommandBase' +import { displayTable } from '../../helpers/display' + +export default class BountiesCommand extends BountyCommandBase { + static description = 'List all existing bounties.' + + async run() { + const bounties = await this.getApi().availableBounties() + if (bounties.length > 0) { + displayTable( + bounties.map(([id, b]) => ({ + 'ID': id.toString(), + 'Bounty Creator': b.creation_params.creator.toString(), + 'Oracle': b.creation_params.oracle.toString(), + 'Contract Type': b.creation_params.contract_type.toString(), + 'Bounty Cherry': b.creation_params.cherry.toString(), + 'Entrant Stake': b.creation_params.entrant_stake.toString(), + 'Funding Type': b.creation_params.funding_type.toString(), + 'Work Period': b.creation_params.work_period.toString(), + 'Judging Period': b.creation_params.judging_period.toString(), + 'Total Funding': b.total_funding.toString(), + 'Milestone': b.milestone.toString(), + 'Active Work Entry Count': b.active_work_entry_count.toString(), + })) + ) + } else { + this.log('There are no bounties yet') + } + } +} diff --git a/cli/src/commands/bounty/bounty.ts b/cli/src/commands/bounty/bounty.ts new file mode 100644 index 0000000000..8c7e208763 --- /dev/null +++ b/cli/src/commands/bounty/bounty.ts @@ -0,0 +1,36 @@ +import BountyCommandBase from '../../base/BountyCommandBase' +import { displayCollapsedRow } from '../../helpers/display' + +export default class BountyCommand extends BountyCommandBase { + static description = 'Show Bounty details by id.' + static args = [ + { + name: 'bountyId', + required: true, + description: 'ID of the Bounty', + }, + ] + + async run() { + const { bountyId } = this.parse(BountyCommand).args + const bounty = await this.getApi().bountyById(bountyId) + if (bounty) { + displayCollapsedRow({ + 'ID': bountyId.toString(), + 'Bounty Creator': bounty.creation_params.creator.toString(), + 'Oracle': bounty.creation_params.oracle.toString(), + 'Contract Type': bounty.creation_params.contract_type.toString(), + 'Bounty Cherry': bounty.creation_params.cherry.toString(), + 'Entrant Stake': bounty.creation_params.entrant_stake.toString(), + 'Funding Type': bounty.creation_params.funding_type.toString(), + 'Work Period': bounty.creation_params.work_period.toString(), + 'Judging Period': bounty.creation_params.judging_period.toString(), + 'Total Funding': bounty.total_funding.toString(), + 'Milestone': bounty.milestone.toString(), + 'Active Work Entry Count': bounty.active_work_entry_count.toString(), + }) + } else { + this.error(`Bounty not found by channel id: "${bountyId}"!`) + } + } +} diff --git a/cli/src/commands/bounty/cancelBounty.ts b/cli/src/commands/bounty/cancelBounty.ts new file mode 100644 index 0000000000..9985f2ca9d --- /dev/null +++ b/cli/src/commands/bounty/cancelBounty.ts @@ -0,0 +1,45 @@ +import chalk from 'chalk' +import BountyCommandBase from '../../base/BountyCommandBase' + +export default class CreateBountyCommand extends BountyCommandBase { + static description = 'Cancel a bounty.' + static args = [ + { + name: 'bountyId', + required: true, + description: 'ID of the Bounty to cancel', + }, + ] + + static flags = { + context: BountyCommandBase.creatorContextFlag, + } + + async run() { + let { context } = this.parse(CreateBountyCommand).flags + const { bountyId } = this.parse(CreateBountyCommand).args + + // Context + if (!context) { + context = await this.promptForCreatorContext() + } + + const [creator, creatorAddress] = await this.getBountyActor(context) + await this.ensureBountyCanBeCanceled(bountyId, creator) + + this.jsonPrettyPrint(JSON.stringify({ bountyId })) + + await this.requireConfirmation('Do you confirm the the bountId?', true) + + const result = await this.sendAndFollowNamedTx( + await this.getDecodedPair(creatorAddress), + 'bounty', + 'cancelBounty', + [creator, bountyId] + ) + if (result) { + const event = this.findEvent(result, 'bounty', 'BountyCanceled') + this.log(chalk.green(`Bounty with id ${chalk.cyanBright(event?.data[1].toString())} successfully canceled!`)) + } + } +} diff --git a/cli/src/commands/bounty/createBounty.ts b/cli/src/commands/bounty/createBounty.ts new file mode 100644 index 0000000000..fd9bfb4026 --- /dev/null +++ b/cli/src/commands/bounty/createBounty.ts @@ -0,0 +1,80 @@ +import { getInputJson } from '../../helpers/InputOutput' +import { BountyCreationParameters } from '@joystream/types/bounty' +import { flags } from '@oclif/command' +import { CreateInterface } from '@joystream/types' +import { BountyInputSchema } from '../../json-schemas/Bounty' +import { BountyInputParameters } from '../../Types' +import { asValidatedMetadata, metadataToBytes } from '../../helpers/serialization' +import BountyCommandBase from '../../base/BountyCommandBase' +import chalk from 'chalk' +import { BountyMetadata } from '@joystream/metadata-protobuf' + +export default class CreateBountyCommand extends BountyCommandBase { + static description = 'Create bounty by member or council.' + static flags = { + creatorContext: BountyCommandBase.creatorContextFlag, + contract: BountyCommandBase.contractTypeFlag, + funding: BountyCommandBase.fundingTypeFlag, + input: flags.string({ + char: 'i', + required: true, + description: `Path to JSON file to use as input`, + }), + } + + async run() { + let { creatorContext, contract, funding, input } = this.parse(CreateBountyCommand).flags + + // TODO: is there a better way to to get the input wothout the prompt? + // TODO: maybe defining'enum' json schema? and then passing as input file + // Context + if (!creatorContext) { + creatorContext = await this.promptForCreatorContext() + } + + // Contract Type + if (!contract) { + contract = await this.promptForContractType() + } + + // Funding Type + if (!funding) { + funding = await this.promptForFundingType() + } + + const [creator, creatorAddress] = await this.getBountyActor(creatorContext) + + const bountyInput = await getInputJson(input, BountyInputSchema) + const metadata = asValidatedMetadata(BountyMetadata, bountyInput) + + const oracle = await this.validateAndPrepareOracleInput(bountyInput.oracle) + const contractType = await this.prepareContractTypeInput(contract, bountyInput.contractTypeInput) + const fundingType = await this.validateAndPrepareFundingTypeInput(funding, bountyInput.fundingType) + + const bountyCreationParameters: CreateInterface = { + oracle, + contract_type: contractType, + creator, + cherry: bountyInput.cherry, + entrant_stake: bountyInput.entrantStake, + funding_type: fundingType, + work_period: bountyInput.workPeriod, + judging_period: bountyInput.judgementPeriod, + } + + this.jsonPrettyPrint(JSON.stringify({ metadata })) + + await this.requireConfirmation('Do you confirm the provided input?', true) + + const result = await this.sendAndFollowNamedTx( + await this.getDecodedPair(creatorAddress), + 'bounty', + 'createBounty', + [bountyCreationParameters, metadataToBytes(BountyMetadata, metadata)] + ) + if (result) { + const event = this.findEvent(result, 'bounty', 'BountyCreated') + this.log(chalk.green(`Bounty with id ${chalk.cyanBright(event?.data[1].toString())} successfully created!`)) + } + } +} diff --git a/cli/src/commands/bounty/vetoBounty.ts b/cli/src/commands/bounty/vetoBounty.ts new file mode 100644 index 0000000000..d0ffddf1a9 --- /dev/null +++ b/cli/src/commands/bounty/vetoBounty.ts @@ -0,0 +1,35 @@ +import chalk from 'chalk' +import BountyCommandBase from '../../base/BountyCommandBase' + +export default class CreateBountyCommand extends BountyCommandBase { + static description = 'Veto bounty by the Council.' + static args = [ + { + name: 'bountyId', + required: true, + description: 'ID of the Bounty to veto', + }, + ] + + async run() { + const { bountyId } = this.parse(CreateBountyCommand).args + + const [councilMember, councilMemberAddress] = await this.getBountyActor('Council') + await this.ensureBountyCanBeCanceled(bountyId, councilMember) + + this.jsonPrettyPrint(JSON.stringify({ bountyId })) + + await this.requireConfirmation('Do you confirm the the bountId?', true) + + const result = await this.sendAndFollowNamedTx( + await this.getDecodedPair(councilMemberAddress), + 'bounty', + 'vetoBounty', + [bountyId] + ) + if (result) { + const event = this.findEvent(result, 'bounty', 'BountyVetoed') + this.log(chalk.green(`Bounty with id ${chalk.cyanBright(event?.data[1].toString())} successfully vetoed!`)) + } + } +} diff --git a/cli/src/json-schemas/Bounty.ts b/cli/src/json-schemas/Bounty.ts new file mode 100644 index 0000000000..8cbc97e205 --- /dev/null +++ b/cli/src/json-schemas/Bounty.ts @@ -0,0 +1,28 @@ +import { BountyInputParameters, JsonSchema } from '../Types' + +export const BountyInputSchema: JsonSchema = { + type: 'object', + additionalProperties: false, + properties: { + title: { type: 'string' }, + description: { type: 'string' }, + discussionThread: { type: 'number' }, + bannerImageUri: { type: 'string' }, + oracle: { type: 'number' }, + contractTypeInput: { type: 'array' }, + cherry: { type: 'number' }, + entrantStake: { type: 'number' }, + fundingType: { + type: 'object', + additionalProperties: false, + properties: { + minFundingAmount: { type: 'number' }, + maxFundingAmount: { type: 'number' }, + fundingPeriod: { type: 'number' }, + target: { type: 'number' }, + }, + }, + workPeriod: { type: 'number' }, + judgementPeriod: { type: 'number' }, + }, +} From 3ae1b70d9b7cb64d8f9a9963a02bfe6e0687c17c Mon Sep 17 00:00:00 2001 From: Zeeshan Akram <97m.zeeshan@gmail.com> Date: Tue, 25 Jan 2022 13:27:37 +0500 Subject: [PATCH 04/16] bounties commands usage docs --- cli/README.md | 72 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 72 insertions(+) diff --git a/cli/README.md b/cli/README.md index 3c7d65981b..0410e565f2 100644 --- a/cli/README.md +++ b/cli/README.md @@ -79,6 +79,11 @@ When using the CLI for the first time there are a few common steps you might wan * [`joystream-cli api:setQueryNodeEndpoint [ENDPOINT]`](#joystream-cli-apisetquerynodeendpoint-endpoint) * [`joystream-cli api:setUri [URI]`](#joystream-cli-apiseturi-uri) * [`joystream-cli autocomplete [SHELL]`](#joystream-cli-autocomplete-shell) +* [`joystream-cli bounty:bounty BOUNTYID`](#joystream-cli-bountybounty-bountyid) +* [`joystream-cli bounty:bounties`](#joystream-cli-bountybounties) +* [`joystream-cli bounty:createBounty`](#joystream-cli-bountycreatebounty) +* [`joystream-cli bounty:cancelBounty BOUNTYID`](#joystream-cli-bountycancelbounty-bountyid) +* [`joystream-cli bounty:vetoBounty BOUNTYID`](#joystream-cli-bountyvetobounty-bountyid) * [`joystream-cli content:addCuratorToGroup [GROUPID] [CURATORID]`](#joystream-cli-contentaddcuratortogroup-groupid-curatorid) * [`joystream-cli content:channel CHANNELID`](#joystream-cli-contentchannel-channelid) * [`joystream-cli content:channels`](#joystream-cli-contentchannels) @@ -347,6 +352,73 @@ EXAMPLES _See code: [@oclif/plugin-autocomplete](https://github.com/oclif/plugin-autocomplete/blob/v0.2.1/src/commands/autocomplete/index.ts)_ +## `joystream-cli bounty:bounty BOUNTYID` + +Show Bounty details by id. + +``` +USAGE + $ joystream-cli bounty:bounty BOUNTYID + +ARGUMENTS + BOUNTYID ID of the Bounty +``` + +## `joystream-cli bounty:bounties` + +List all existing bounties. + +``` +USAGE + $ joystream-cli bounty:bounties +``` + +## `joystream-cli bounty:createBounty` + +Create bounty by a member or council. + +``` +USAGE + $ joystream-cli bounty:createBounty + + +OPTIONS + -i, --input=input (required) Path to JSON file to use as input + --creatorContext=(Member|Council) Actor context to execute the command in (Member/Council) + --contract=(Perpetual|Limited) Contract type to use in Bounty is (Perpetual/Limited) + --funding=(Open|Closed) Funding type to use in Bounty is (Open/Closed) +``` +`contractTypeInput` property of JSON input should be emply in case of `--contract=Open` and +contain list of members allowed to submit work in case of `--contarct=Closed` +`oracle` property of JSON input should be undefined(omitted from the input) if oracle bounty +actor is `Council`, and a vaild memberId if oracle is `Member` +## `joystream-cli bounty:cancelBounty BOUNTYID` + +Cancel a bounty. + +``` +USAGE + $ joystream-cli bounty:cancelBounty BOUNTYID + +ARGUMENTS + BOUNTYID ID of the Bounty to cancel + +OPTIONS + --context=(Member|Council) Actor context to execute the command in (Member/Council) +``` + +## `joystream-cli bounty:vetoBounty BOUNTYID` + +Veto bounty by the Council. + +``` +USAGE + $ joystream-cli bounty:vetoBounty BOUNTYID + +ARGUMENTS + BOUNTYID ID of the Bounty to veto +``` + ## `joystream-cli content:addCuratorToGroup [GROUPID] [CURATORID]` Add Curator to existing Curator Group. From b90e79c1ff4ee09202c10bfd57d5e4b4e41817fc Mon Sep 17 00:00:00 2001 From: Zeeshan Akram <97m.zeeshan@gmail.com> Date: Thu, 27 Jan 2022 01:12:07 +0500 Subject: [PATCH 05/16] implemented remaining validations on create bounty input --- cli/src/base/BountyCommandBase.ts | 39 +++++++++++++++---------- cli/src/commands/bounty/createBounty.ts | 17 ++++++++++- 2 files changed, 40 insertions(+), 16 deletions(-) diff --git a/cli/src/base/BountyCommandBase.ts b/cli/src/base/BountyCommandBase.ts index e5222dc3a2..8745f7b561 100644 --- a/cli/src/base/BountyCommandBase.ts +++ b/cli/src/base/BountyCommandBase.ts @@ -68,6 +68,17 @@ export default abstract class BountyCommandBase extends RolesCommandBase { }) } + async validateAndPrepareOracleInput(oracle: number | undefined): Promise { + if (!oracle) { + return this.createType('BountyActor', { Council: null }) + } else { + // ensure that member id is valid + await this.getOriginalApi().query.members.membershipById(oracle) + + return this.createType('BountyActor', { Member: oracle }) + } + } + // validate funding type input passed as json object key value async validateAndPrepareFundingTypeInput( fundingType: typeof FUNDING_TYPE_CONTEXTS[number], @@ -100,17 +111,26 @@ export default abstract class BountyCommandBase extends RolesCommandBase { // prepare 'AssuranceContractType' based on contract type input i.e. Open | Closed. if contract type // is open then contractTypeInput field in json input to createBounty command supposed to be empty array. - // Otherwise it would contain list of member id that can submit work. - async prepareContractTypeInput( + // Otherwise it would contain list of member ids that can submit work. + async validateAndPrepareContractTypeInput( contractType: typeof CONTRACT_TYPE_CONTEXTS[number], - contractInput?: string[] + contractInput: string[] ): Promise { if (contractType === 'Open') { return this.createType('AssuranceContractType', { Open: null }) } else { - if (contractInput !== undefined && contractInput.length === 0) { + if (contractInput.length === 0) { throw new Error('Closed contract member list is empty') } + if (contractInput.length > this.getOriginalApi().consts.bounty.closedContractSizeLimit.toNumber()) { + throw new CLIError(`Judging period cannot be zero`) + } + + // Checks that each member id id valid + contractInput.forEach(async (each) => { + const memberId = await this.createType('MemberId', each) + await this.getApi().expectedMembershipById(memberId) + }) return this.createType('AssuranceContractType', { Closed: contractInput }) } } @@ -129,17 +149,6 @@ export default abstract class BountyCommandBase extends RolesCommandBase { } } - async validateAndPrepareOracleInput(oracle: number | undefined): Promise { - if (!oracle) { - return this.createType('BountyActor', { Council: null }) - } else { - // ensure that memver id is valid - await this.getOriginalApi().query.members.membershipById(oracle) - - return this.createType('BountyActor', { Member: oracle }) - } - } - async getBountyActor(context: typeof CREATOR_CONTEXTS[number]): Promise<[BountyActor, string]> { let bountyActorContext: [BountyActor, string] diff --git a/cli/src/commands/bounty/createBounty.ts b/cli/src/commands/bounty/createBounty.ts index fd9bfb4026..3c13718781 100644 --- a/cli/src/commands/bounty/createBounty.ts +++ b/cli/src/commands/bounty/createBounty.ts @@ -1,5 +1,6 @@ import { getInputJson } from '../../helpers/InputOutput' import { BountyCreationParameters } from '@joystream/types/bounty' +import ExitCodes from '../../ExitCodes' import { flags } from '@oclif/command' import { CreateInterface } from '@joystream/types' import { BountyInputSchema } from '../../json-schemas/Bounty' @@ -48,9 +49,23 @@ export default class CreateBountyCommand extends BountyCommandBase { const metadata = asValidatedMetadata(BountyMetadata, bountyInput) const oracle = await this.validateAndPrepareOracleInput(bountyInput.oracle) - const contractType = await this.prepareContractTypeInput(contract, bountyInput.contractTypeInput) + const contractType = await this.validateAndPrepareContractTypeInput(contract, bountyInput.contractTypeInput) const fundingType = await this.validateAndPrepareFundingTypeInput(funding, bountyInput.fundingType) + // Do remaining validations on input + if (bountyInput.cherry < this.getOriginalApi().consts.bounty.minCherryLimit.toNumber()) { + this.error('Attached cherry is less that minimum cherry limit', { exit: ExitCodes.InvalidInput }) + } + if (bountyInput.entrantStake < this.getOriginalApi().consts.bounty.minWorkEntrantStake.toNumber()) { + this.error('Attached entrant stake is less that minimum work entrant stake', { exit: ExitCodes.InvalidInput }) + } + if (bountyInput.workPeriod !== 0) { + this.error('Work period cannot be zero', { exit: ExitCodes.InvalidInput }) + } + if (bountyInput.judgementPeriod !== 0) { + this.error('Judging period cannot be zero', { exit: ExitCodes.InvalidInput }) + } + const bountyCreationParameters: CreateInterface = { oracle, contract_type: contractType, From 2c51bdebe20da922009fecf38e19e0e3caa94acc Mon Sep 17 00:00:00 2001 From: Zeeshan Akram <97m.zeeshan@gmail.com> Date: Thu, 27 Jan 2022 01:24:55 +0500 Subject: [PATCH 06/16] implemented get entry & entries commands --- cli/src/Api.ts | 16 ++++++++++++++-- cli/src/commands/bounty/entries.ts | 24 ++++++++++++++++++++++++ cli/src/commands/bounty/entry.ts | 30 ++++++++++++++++++++++++++++++ 3 files changed, 68 insertions(+), 2 deletions(-) create mode 100644 cli/src/commands/bounty/entries.ts create mode 100644 cli/src/commands/bounty/entry.ts diff --git a/cli/src/Api.ts b/cli/src/Api.ts index d4db9150dd..68509ffc83 100644 --- a/cli/src/Api.ts +++ b/cli/src/Api.ts @@ -42,7 +42,7 @@ import { VideoCategory, } from '@joystream/types/content' import { ContentId, DataObject } from '@joystream/types/storage' -import { BountyId, JSBounty as Bounty } from '@joystream/types/bounty' +import { BountyId, EntryId, JSBounty as Bounty, Entry } from '@joystream/types/bounty' import { ApolloClient, InMemoryCache, HttpLink, NormalizedCacheObject, DocumentNode } from '@apollo/client/core' import fetch from 'cross-fetch' import { Maybe } from './graphql/generated/schema' @@ -265,7 +265,7 @@ export default class Api { return membership.isEmpty ? null : await this.memberDetails(memberId, membership) } - protected async expectedMembershipById(memberId: MemberId): Promise { + async expectedMembershipById(memberId: MemberId): Promise { const member = await this.membershipById(memberId) if (!member) { throw new CLIError(`Expected member was not found by id: ${memberId.toString()}`) @@ -472,6 +472,18 @@ export default class Api { return await this._api.query.bounty.bounties(bountyId) } + async entryById(entryId: EntryId | number | string): Promise { + const exists = !!(await this._api.query.bounty.entries.size(entryId)).toNumber() + if (!exists) { + throw new CLIError(`Bonuty by id ${entryId.toString()} not found!`) + } + return await this._api.query.bounty.entries(entryId) + } + + async availableEntries(): Promise<[EntryId, Entry][]> { + return await this.entriesByIds(this._api.query.bounty.entries) + } + // Content directory async availableChannels(): Promise<[ChannelId, Channel][]> { return await this.entriesByIds(this._api.query.content.channelById) diff --git a/cli/src/commands/bounty/entries.ts b/cli/src/commands/bounty/entries.ts new file mode 100644 index 0000000000..47b6b96024 --- /dev/null +++ b/cli/src/commands/bounty/entries.ts @@ -0,0 +1,24 @@ +import BountyCommandBase from '../../base/BountyCommandBase' +import { displayTable } from '../../helpers/display' + +export default class EntriesCommand extends BountyCommandBase { + static description = 'List all existing work entries.' + + async run() { + const bounties = await this.getApi().availableEntries() + if (bounties.length > 0) { + displayTable( + bounties.map(([id, e]) => ({ + 'ID': id.toString(), + 'Member ID': e.member_id.toString(), + 'Staking Account ID': e.staking_account_id.toString(), + 'Submitted At': e.submitted_at.toString(), + 'Work Submitted': e.work_submitted.toString(), + 'Oracle Judgement': e.oracle_judgment_result.unwrapOr('None').toString(), + })) + ) + } else { + this.log('There are no work entries yet') + } + } +} diff --git a/cli/src/commands/bounty/entry.ts b/cli/src/commands/bounty/entry.ts new file mode 100644 index 0000000000..3a4e4e24d3 --- /dev/null +++ b/cli/src/commands/bounty/entry.ts @@ -0,0 +1,30 @@ +import BountyCommandBase from '../../base/BountyCommandBase' +import { displayCollapsedRow } from '../../helpers/display' + +export default class EntryCommand extends BountyCommandBase { + static description = 'Show Work entry details by id.' + static args = [ + { + name: 'entryId', + required: true, + description: 'ID of the work entry', + }, + ] + + async run() { + const { entryId } = this.parse(EntryCommand).args + const entry = await this.getApi().entryById(entryId) + if (entry) { + displayCollapsedRow({ + 'ID': entryId.toString(), + 'Member ID': entry.member_id.toString(), + 'Staking Account ID': entry.staking_account_id.toString(), + 'Submitted At': entry.submitted_at.toString(), + 'Work Submitted': entry.work_submitted.toString(), + 'Oracle Judgement': entry.oracle_judgment_result.unwrapOr('None').toString(), + }) + } else { + this.error(`Work entry not found by channel id: "${entryId}"!`) + } + } +} From c2983576defbd508f2529d8304e79745f1a28469 Mon Sep 17 00:00:00 2001 From: Zeeshan Akram <97m.zeeshan@gmail.com> Date: Fri, 28 Jan 2022 16:31:47 +0500 Subject: [PATCH 07/16] rename creatorContext to bountyActorContext flag --- cli/src/base/BountyCommandBase.ts | 196 ++++++++++++++++++++++-- cli/src/commands/bounty/cancelBounty.ts | 2 +- cli/src/commands/bounty/createBounty.ts | 2 +- 3 files changed, 185 insertions(+), 15 deletions(-) diff --git a/cli/src/base/BountyCommandBase.ts b/cli/src/base/BountyCommandBase.ts index 8745f7b561..723e4b6f00 100644 --- a/cli/src/base/BountyCommandBase.ts +++ b/cli/src/base/BountyCommandBase.ts @@ -1,15 +1,23 @@ -import { RolesCommandBase } from './WorkingGroupsCommandBase' +import { + AssuranceContractType, + BountyActor, + BountyId, + FundingType, + JSBounty as Bounty, + OracleJudgment, + OracleWorkEntryJudgment, +} from '@joystream/types/bounty' import { flags } from '@oclif/command' import { CLIError } from '@oclif/errors' import ExitCodes from '../ExitCodes' -import { AssuranceContractType, BountyActor, FundingType, BountyId } from '@joystream/types/bounty' -import { FundingTypeLimited, FundingTypePrepetual } from 'src/Types' +import { FundingTypeLimited, FundingTypePrepetual, OracleJudgmentInputParameters, Winner } from '../Types' +import { RolesCommandBase } from './WorkingGroupsCommandBase' -const CREATOR_CONTEXTS = ['Member', 'Council'] as const +const BOUNTY_ACTOR_CONTEXTS = ['Member', 'Council'] as const const CONTRACT_TYPE_CONTEXTS = ['Open', 'Closed'] as const const FUNDING_TYPE_CONTEXTS = ['Perpetual', 'Limited'] as const -type CreatorContext = typeof CREATOR_CONTEXTS[number] +type BountyActorContext = typeof BOUNTY_ACTOR_CONTEXTS[number] type ContractTypeContext = typeof CONTRACT_TYPE_CONTEXTS[number] type FundingTypeContext = typeof FUNDING_TYPE_CONTEXTS[number] @@ -17,11 +25,11 @@ type FundingTypeContext = typeof FUNDING_TYPE_CONTEXTS[number] * Abstract base class for commands related to bounty module. */ export default abstract class BountyCommandBase extends RolesCommandBase { - static creatorContextFlag = flags.enum({ + static bountyActorContextFlag = flags.enum({ name: 'creatorContext', required: false, - description: `Actor context to execute the command in (${CREATOR_CONTEXTS.join('/')})`, - options: [...CREATOR_CONTEXTS], + description: `Actor context to execute the command in (${BOUNTY_ACTOR_CONTEXTS.join('/')})`, + options: [...BOUNTY_ACTOR_CONTEXTS], }) static contractTypeFlag = flags.enum({ @@ -40,11 +48,11 @@ export default abstract class BountyCommandBase extends RolesCommandBase { async promptForCreatorContext( message = 'Choose in which context you wish to execute the command' - ): Promise { + ): Promise { return this.simplePrompt({ message, type: 'list', - choices: CREATOR_CONTEXTS.map((c) => ({ name: c, value: c })), + choices: BOUNTY_ACTOR_CONTEXTS.map((c) => ({ name: c, value: c })), }) } @@ -73,7 +81,8 @@ export default abstract class BountyCommandBase extends RolesCommandBase { return this.createType('BountyActor', { Council: null }) } else { // ensure that member id is valid - await this.getOriginalApi().query.members.membershipById(oracle) + const oracleId = await this.createType('MemberId', oracle) + await this.getApi().expectedMembershipById(oracleId) return this.createType('BountyActor', { Member: oracle }) } @@ -120,7 +129,7 @@ export default abstract class BountyCommandBase extends RolesCommandBase { return this.createType('AssuranceContractType', { Open: null }) } else { if (contractInput.length === 0) { - throw new Error('Closed contract member list is empty') + throw new CLIError('Closed contract member list is empty') } if (contractInput.length > this.getOriginalApi().consts.bounty.closedContractSizeLimit.toNumber()) { throw new CLIError(`Judging period cannot be zero`) @@ -135,6 +144,167 @@ export default abstract class BountyCommandBase extends RolesCommandBase { } } + async validateAndPrepareOracleJudgement( + bounty: Bounty, + judgementInput: OracleJudgmentInputParameters + ): Promise { + const oracleJudgmentMap = {} as OracleJudgment + let rewardSumFromJudgment = 0 + + judgementInput.map(async ({ entryId, judgment }) => { + // ensure that entry exists + await this.getApi().entryById(entryId) + + let workEntryJudgment: OracleWorkEntryJudgment + const workEntryId = this.createType('EntryId', entryId) + + // if oracle judgment is Winner + if (judgment.hasOwnProperty('reward')) { + const reward = (judgment as Winner).reward + + if (reward === 0) { + throw new CLIError(`Judgment reward cannot be zero`) + } + // calculate total reward money + rewardSumFromJudgment += reward + + workEntryJudgment = this.createType( + 'OracleWorkEntryJudgment', + this.createType('OracleJudgment_Winner', { Winner: { reward } }) + ) + } + + // if oracle judgment is Rejected + workEntryJudgment = this.createType('OracleWorkEntryJudgment', { Rejected: null }) + // insert entry id, work entry judgment pair to oracleJudgmentMap + oracleJudgmentMap.set(workEntryId, workEntryJudgment) + }) + + if (rewardSumFromJudgment !== bounty.total_funding.toNumber()) { + throw new CLIError(`Total reward should be equal to total funding`) + } + return oracleJudgmentMap + } + + // ----------------------------------- + // BOUNTY STAGE CALCULATION FUNCTIONS + // ----------------------------------- + + async isFundingStage(bounty: Bounty): Promise { + const fundingPeriodIsNotExpired = !(await this.fundingPeriodExpired(bounty)) + return fundingPeriodIsNotExpired + } + + async isFundingExpiredStage(bounty: Bounty): Promise { + if (bounty.milestone.isOfType('Created')) { + const hasNoContributions = bounty.milestone.asType('Created').has_contributions.isFalse + const fundingPeriodIsExpired = await this.fundingPeriodExpired(bounty) + return fundingPeriodIsExpired && hasNoContributions + } + return false + } + + async isWorkSubmissionStage(bounty: Bounty): Promise { + // Funding period is over. Minimum funding reached. Work period is not expired. + if (bounty.milestone.isOfType('Created')) { + // Never expires + if (bounty.creation_params.funding_type.isOfType('Perpetual')) { + return false + } + const createdAt = bounty.milestone.asType('Created').created_at.toNumber() + const fundingPeriod = bounty.creation_params.funding_type.asType('Limited').funding_period.toNumber() + if ( + this.minimumFundingReached(bounty) && + (await this.fundingPeriodExpired(bounty)) && + !(await this.workPeriodExpired(bounty, createdAt + fundingPeriod)) + ) { + return true + } + } + + // Maximum funding reached. Work period is not expired. + if (bounty.milestone.isOfType('BountyMaxFundingReached')) { + const maxFundingReachedAt = bounty.milestone.asType('BountyMaxFundingReached').max_funding_reached_at.toNumber() + const workPeriodIsNotExpired = !(await this.workPeriodExpired(bounty, maxFundingReachedAt)) + return workPeriodIsNotExpired + } + + // Work in progress. Work period is not expired. + if (bounty.milestone.isOfType('WorkSubmitted')) { + const workPeriodStartedAt = bounty.milestone.asType('WorkSubmitted').work_period_started_at.toNumber() + const workPeriodIsNotExpired = !(await this.workPeriodExpired(bounty, workPeriodStartedAt)) + return workPeriodIsNotExpired + } + return false + } + + async isJudgmentStage(bounty: Bounty): Promise { + if (bounty.milestone.isOfType('WorkSubmitted')) { + const workPeriodStartedAt = bounty.milestone.asType('WorkSubmitted').work_period_started_at.toNumber() + const workPeriodExpired = await this.workPeriodExpired(bounty, workPeriodStartedAt) + const judgmentPeriodIsNotExpired = !(await this.judgmentPeriodExpired(bounty, workPeriodStartedAt)) + + return workPeriodExpired && judgmentPeriodIsNotExpired + } + return false + } + + async isSuccessfulBountyWithdrawalStage(bounty: Bounty): Promise { + if (bounty.milestone.isOfType('JudgmentSubmitted')) { + const successfulBounty = bounty.milestone.asType('JudgmentSubmitted').successful_bounty + return successfulBounty.isTrue + } + return false + } + + // --------------------------------- + // BOUNTY STAGE CALCULATION HELPERS + // --------------------------------- + + // Checks whether the minimum funding reached for the bounty. + protected minimumFundingReached(bounty: Bounty): boolean { + if (bounty.creation_params.funding_type.isOfType('Perpetual')) { + // There is no minimum for the perpetual + // funding type - only maximum (target) + return false + } + const minFundingAmount = bounty.creation_params.funding_type.asType('Limited').min_funding_amount.toNumber() + return bounty.total_funding.toNumber() >= minFundingAmount + } + + async fundingPeriodExpired(bounty: Bounty): Promise { + // Prepetual funding never expires + if (bounty.creation_params.funding_type.isOfType('Perpetual')) { + return false + } + // Ensure if limited funding has expired + const createdAt = bounty.milestone.asType('Created').created_at.toNumber() + const currentBlock = (await this.getOriginalApi().query.system.number()).toNumber() + const fundingPeriod = bounty.creation_params.funding_type.asType('Limited').funding_period.toNumber() + + return createdAt + fundingPeriod < currentBlock + } + + // Checks whether the work period expired by now starting from the provided block number. + async workPeriodExpired(bounty: Bounty, workPeriodStartedAt: number): Promise { + const currentBlock = (await this.getOriginalApi().query.system.number()).toNumber() + return workPeriodStartedAt + bounty.creation_params.work_period.toNumber() < currentBlock + } + + // Checks whether the judgment period expired by now when + // work period start from the provided block number. + async judgmentPeriodExpired(bounty: Bounty, workPeriodStartedAt: number): Promise { + const currentBlock = (await this.getOriginalApi().query.system.number()).toNumber() + return ( + workPeriodStartedAt + + bounty.creation_params.work_period.toNumber() + + bounty.creation_params.judging_period.toNumber() < + currentBlock + ) + } + + // ---------------------------------------------------------------- + async ensureBountyCanBeCanceled(bountyId: BountyId, creator: BountyActor): Promise { const bounty = await this.getApi().bountyById(bountyId) @@ -149,7 +319,7 @@ export default abstract class BountyCommandBase extends RolesCommandBase { } } - async getBountyActor(context: typeof CREATOR_CONTEXTS[number]): Promise<[BountyActor, string]> { + async getBountyActor(context: typeof BOUNTY_ACTOR_CONTEXTS[number]): Promise<[BountyActor, string]> { let bountyActorContext: [BountyActor, string] if (context === 'Member') { diff --git a/cli/src/commands/bounty/cancelBounty.ts b/cli/src/commands/bounty/cancelBounty.ts index 9985f2ca9d..198be020d4 100644 --- a/cli/src/commands/bounty/cancelBounty.ts +++ b/cli/src/commands/bounty/cancelBounty.ts @@ -12,7 +12,7 @@ export default class CreateBountyCommand extends BountyCommandBase { ] static flags = { - context: BountyCommandBase.creatorContextFlag, + context: BountyCommandBase.bountyActorContextFlag, } async run() { diff --git a/cli/src/commands/bounty/createBounty.ts b/cli/src/commands/bounty/createBounty.ts index 3c13718781..11ecf1faf2 100644 --- a/cli/src/commands/bounty/createBounty.ts +++ b/cli/src/commands/bounty/createBounty.ts @@ -13,7 +13,7 @@ import { BountyMetadata } from '@joystream/metadata-protobuf' export default class CreateBountyCommand extends BountyCommandBase { static description = 'Create bounty by member or council.' static flags = { - creatorContext: BountyCommandBase.creatorContextFlag, + creatorContext: BountyCommandBase.bountyActorContextFlag, contract: BountyCommandBase.contractTypeFlag, funding: BountyCommandBase.fundingTypeFlag, input: flags.string({ From aff1a06a07963df6cb845629e7aa2f061d0d2fa0 Mon Sep 17 00:00:00 2001 From: Zeeshan Akram <97m.zeeshan@gmail.com> Date: Fri, 28 Jan 2022 16:34:23 +0500 Subject: [PATCH 08/16] generated correct OracleJudgment TS type --- types/augment-codec/all.ts | 4 ++-- types/augment-codec/augment-api-tx.ts | 2 +- types/augment-codec/augment-types.ts | 5 ++++- types/augment/all/defs.json | 3 ++- types/augment/all/types.ts | 13 ++++++++----- types/augment/augment-api-tx.ts | 2 +- types/augment/augment-types.ts | 5 ++++- types/src/bounty.ts | 6 ++++-- 8 files changed, 26 insertions(+), 14 deletions(-) diff --git a/types/augment-codec/all.ts b/types/augment-codec/all.ts index b80cbbce7d..0a93d96c50 100644 --- a/types/augment-codec/all.ts +++ b/types/augment-codec/all.ts @@ -9,7 +9,7 @@ import { ProposalId, ProposalStatus, Proposal as ProposalOf, ProposalDetails, Pr import { ReferendumStageVoting, ReferendumStageRevealing, ReferendumStage, OptionResult, VotePower } from '../referendum'; import { ConstitutionInfo } from '../constitution'; import { ParticipantId, Title, UpdatedTitle, UpdatedBody, ReplyId, Reply, ReplyToDelete } from '../blog'; -import { BountyId, JSBounty, BountyMilestone, EntryId, BountyActor, AssuranceContractType, FundingType_Limited, FundingType_Perpetual, FundingType, BountyCreationParameters, OracleJudgment_Winner, OracleJudgment, Entry } from '../bounty'; +import { BountyId, JSBounty, BountyMilestone, EntryId, BountyActor, AssuranceContractType, FundingType_Limited, FundingType_Perpetual, FundingType, BountyCreationParameters, OracleJudgment_Winner, OracleWorkEntryJudgment, OracleJudgment, Entry } from '../bounty'; import { CuratorId, CuratorGroupId, CuratorGroup, ContentActor, NewAsset, Channel, ChannelOwner, ChannelCategoryId, ChannelCategory, ChannelCategoryCreationParameters, ChannelCategoryUpdateParameters, ChannelCreationParameters, ChannelUpdateParameters, ChannelOwnershipTransferRequestId, ChannelOwnershipTransferRequest, Video, VideoId, VideoCategoryId, VideoCategory, VideoCategoryCreationParameters, VideoCategoryUpdateParameters, VideoCreationParameters, VideoUpdateParameters, Person, PersonId, PersonController, PersonActor, PersonCreationParameters, PersonUpdateParameters, Playlist, PlaylistId, PlaylistCreationParameters, PlaylistUpdateParameters, SeriesId, Series, Season, SeriesParameters, SeasonParameters, EpisodeParemters, MaxNumber, IsCensored } from '../content'; -export { ActorId, MemberId, BlockAndTime, ThreadId, PostId, InputValidationLengthConstraint, WorkingGroup, MemoText, BalanceKind, Address, LookupSource, ChannelId, DAOId, Url, Membership, StakingAccountMemberBinding, BuyMembershipParameters, InviteMembershipParameters, CouncilStageAnnouncing, CouncilStageElection, CouncilStageUpdate, CouncilStage, Candidate, CouncilMemberOf, CastVoteOf, ForumUserId, ModeratorId, CategoryId, PostReactionId, Category, Thread, Post, PollAlternative, Poll, PrivilegedActor, PollInput, ThreadOf, ExtendedPostId, ApplicationId, Application, ApplicationInfo, ApplicationIdSet, ApplicationIdToWorkerIdMap, WorkerId, Worker, WorkerInfo, Opening, OpeningId, StakePolicy, StakeParameters, StorageProviderId, OpeningType, ApplyOnOpeningParameters, Penalty, RewardPaymentType, ContentId, LiaisonJudgement, DataObject, DataObjectStorageRelationshipId, DataObjectStorageRelationship, DataObjectTypeId, DataObjectType, DataObjectsMap, ContentParameters, StorageObjectOwner, ObjectOwner, Voucher, VoucherLimit, UploadingStatus, ProposalId, ProposalStatus, ProposalOf, ProposalDetails, ProposalDetailsOf, VotingResults, ProposalParameters, GeneralProposalParameters, VoteKind, DiscussionThread, DiscussionPost, CreateOpeningParameters, FillOpeningParameters, TerminateRoleParameters, ProposalDecision, ExecutionFailed, Approved, SetLeadParams, ThreadMode, ExecutionStatus, FundingRequestParameters, ReferendumStageVoting, ReferendumStageRevealing, ReferendumStage, OptionResult, VotePower, ConstitutionInfo, ParticipantId, Title, UpdatedTitle, UpdatedBody, ReplyId, Reply, ReplyToDelete, BountyId, JSBounty, BountyMilestone, EntryId, BountyActor, AssuranceContractType, FundingType_Limited, FundingType_Perpetual, FundingType, BountyCreationParameters, OracleJudgment_Winner, OracleJudgment, Entry, CuratorId, CuratorGroupId, CuratorGroup, ContentActor, NewAsset, Channel, ChannelOwner, ChannelCategoryId, ChannelCategory, ChannelCategoryCreationParameters, ChannelCategoryUpdateParameters, ChannelCreationParameters, ChannelUpdateParameters, ChannelOwnershipTransferRequestId, ChannelOwnershipTransferRequest, Video, VideoId, VideoCategoryId, VideoCategory, VideoCategoryCreationParameters, VideoCategoryUpdateParameters, VideoCreationParameters, VideoUpdateParameters, Person, PersonId, PersonController, PersonActor, PersonCreationParameters, PersonUpdateParameters, Playlist, PlaylistId, PlaylistCreationParameters, PlaylistUpdateParameters, SeriesId, Series, Season, SeriesParameters, SeasonParameters, EpisodeParemters, MaxNumber, IsCensored }; \ No newline at end of file +export { ActorId, MemberId, BlockAndTime, ThreadId, PostId, InputValidationLengthConstraint, WorkingGroup, MemoText, BalanceKind, Address, LookupSource, ChannelId, DAOId, Url, Membership, StakingAccountMemberBinding, BuyMembershipParameters, InviteMembershipParameters, CouncilStageAnnouncing, CouncilStageElection, CouncilStageUpdate, CouncilStage, Candidate, CouncilMemberOf, CastVoteOf, ForumUserId, ModeratorId, CategoryId, PostReactionId, Category, Thread, Post, PollAlternative, Poll, PrivilegedActor, PollInput, ThreadOf, ExtendedPostId, ApplicationId, Application, ApplicationInfo, ApplicationIdSet, ApplicationIdToWorkerIdMap, WorkerId, Worker, WorkerInfo, Opening, OpeningId, StakePolicy, StakeParameters, StorageProviderId, OpeningType, ApplyOnOpeningParameters, Penalty, RewardPaymentType, ContentId, LiaisonJudgement, DataObject, DataObjectStorageRelationshipId, DataObjectStorageRelationship, DataObjectTypeId, DataObjectType, DataObjectsMap, ContentParameters, StorageObjectOwner, ObjectOwner, Voucher, VoucherLimit, UploadingStatus, ProposalId, ProposalStatus, ProposalOf, ProposalDetails, ProposalDetailsOf, VotingResults, ProposalParameters, GeneralProposalParameters, VoteKind, DiscussionThread, DiscussionPost, CreateOpeningParameters, FillOpeningParameters, TerminateRoleParameters, ProposalDecision, ExecutionFailed, Approved, SetLeadParams, ThreadMode, ExecutionStatus, FundingRequestParameters, ReferendumStageVoting, ReferendumStageRevealing, ReferendumStage, OptionResult, VotePower, ConstitutionInfo, ParticipantId, Title, UpdatedTitle, UpdatedBody, ReplyId, Reply, ReplyToDelete, BountyId, JSBounty, BountyMilestone, EntryId, BountyActor, AssuranceContractType, FundingType_Limited, FundingType_Perpetual, FundingType, BountyCreationParameters, OracleJudgment_Winner, OracleWorkEntryJudgment, OracleJudgment, Entry, CuratorId, CuratorGroupId, CuratorGroup, ContentActor, NewAsset, Channel, ChannelOwner, ChannelCategoryId, ChannelCategory, ChannelCategoryCreationParameters, ChannelCategoryUpdateParameters, ChannelCreationParameters, ChannelUpdateParameters, ChannelOwnershipTransferRequestId, ChannelOwnershipTransferRequest, Video, VideoId, VideoCategoryId, VideoCategory, VideoCategoryCreationParameters, VideoCategoryUpdateParameters, VideoCreationParameters, VideoUpdateParameters, Person, PersonId, PersonController, PersonActor, PersonCreationParameters, PersonUpdateParameters, Playlist, PlaylistId, PlaylistCreationParameters, PlaylistUpdateParameters, SeriesId, Series, Season, SeriesParameters, SeasonParameters, EpisodeParemters, MaxNumber, IsCensored }; \ No newline at end of file diff --git a/types/augment-codec/augment-api-tx.ts b/types/augment-codec/augment-api-tx.ts index 8b70751d53..e2a63ebc7f 100644 --- a/types/augment-codec/augment-api-tx.ts +++ b/types/augment-codec/augment-api-tx.ts @@ -273,7 +273,7 @@ declare module '@polkadot/api/types/submittable' { * - `O(N)` * # **/ - submitOracleJudgment: AugmentedSubmittable<(oracle: BountyActor | { Council: any } | { Member: any } | string | Uint8Array, bountyId: BountyId | AnyNumber | Uint8Array, judgment: OracleJudgment | { Winner: any } | { Rejected: any } | string | Uint8Array) => SubmittableExtrinsic, [BountyActor, BountyId, OracleJudgment]>; + submitOracleJudgment: AugmentedSubmittable<(oracle: BountyActor | { Council: any } | { Member: any } | string | Uint8Array, bountyId: BountyId | AnyNumber | Uint8Array, judgment: OracleJudgment) => SubmittableExtrinsic, [BountyActor, BountyId, OracleJudgment]>; /** * Submit work for a bounty. * # diff --git a/types/augment-codec/augment-types.ts b/types/augment-codec/augment-types.ts index 8f9929b8d8..c334c20cc0 100644 --- a/types/augment-codec/augment-types.ts +++ b/types/augment-codec/augment-types.ts @@ -2,7 +2,7 @@ /* eslint-disable */ import type { BitVec, Bool, Bytes, Compact, Data, I128, I16, I256, I32, I64, I8, Json, Null, Option, Raw, StorageKey, Text, Type, U128, U16, U256, U32, U64, U8, USize, Vec, bool, i128, i16, i256, i32, i64, i8, u128, u16, u256, u32, u64, u8, usize } from '@polkadot/types'; -import type { ActorId, Application, ApplicationId, ApplicationIdSet, ApplicationIdToWorkerIdMap, ApplicationInfo, ApplyOnOpeningParameters, Approved, AssuranceContractType, BalanceKind, BlockAndTime, BountyActor, BountyCreationParameters, BountyId, BountyMilestone, BuyMembershipParameters, Candidate, CastVoteOf, Category, CategoryId, Channel, ChannelCategory, ChannelCategoryCreationParameters, ChannelCategoryId, ChannelCategoryUpdateParameters, ChannelCreationParameters, ChannelId, ChannelOwner, ChannelOwnershipTransferRequest, ChannelOwnershipTransferRequestId, ChannelUpdateParameters, ConstitutionInfo, ContentActor, ContentId, ContentParameters, CouncilMemberOf, CouncilStage, CouncilStageAnnouncing, CouncilStageElection, CouncilStageUpdate, CreateOpeningParameters, CuratorGroup, CuratorGroupId, CuratorId, DAOId, DataObject, DataObjectStorageRelationship, DataObjectStorageRelationshipId, DataObjectType, DataObjectTypeId, DataObjectsMap, DiscussionPost, DiscussionThread, Entry, EntryId, EpisodeParemters, ExecutionFailed, ExecutionStatus, ExtendedPostId, FillOpeningParameters, ForumUserId, FundingRequestParameters, FundingType, FundingType_Limited, FundingType_Perpetual, GeneralProposalParameters, InputValidationLengthConstraint, InviteMembershipParameters, IsCensored, JSBounty, LiaisonJudgement, MaxNumber, MemberId, Membership, MemoText, ModeratorId, NewAsset, ObjectOwner, Opening, OpeningId, OpeningType, OptionResult, OracleJudgment, OracleJudgment_Winner, ParticipantId, Penalty, Person, PersonActor, PersonController, PersonCreationParameters, PersonId, PersonUpdateParameters, Playlist, PlaylistCreationParameters, PlaylistId, PlaylistUpdateParameters, Poll, PollAlternative, PollInput, Post, PostId, PostReactionId, PrivilegedActor, ProposalDecision, ProposalDetails, ProposalDetailsOf, ProposalId, ProposalOf, ProposalParameters, ProposalStatus, ReferendumStage, ReferendumStageRevealing, ReferendumStageVoting, Reply, ReplyId, ReplyToDelete, RewardPaymentType, Season, SeasonParameters, Series, SeriesId, SeriesParameters, SetLeadParams, StakeParameters, StakePolicy, StakingAccountMemberBinding, StorageObjectOwner, StorageProviderId, TerminateRoleParameters, Thread, ThreadId, ThreadMode, ThreadOf, Title, UpdatedBody, UpdatedTitle, UploadingStatus, Url, Video, VideoCategory, VideoCategoryCreationParameters, VideoCategoryId, VideoCategoryUpdateParameters, VideoCreationParameters, VideoId, VideoUpdateParameters, VoteKind, VotePower, VotingResults, Voucher, VoucherLimit, Worker, WorkerId, WorkerInfo, WorkingGroup } from './all'; +import type { ActorId, Application, ApplicationId, ApplicationIdSet, ApplicationIdToWorkerIdMap, ApplicationInfo, ApplyOnOpeningParameters, Approved, AssuranceContractType, BalanceKind, BlockAndTime, BountyActor, BountyCreationParameters, BountyId, BountyMilestone, BuyMembershipParameters, Candidate, CastVoteOf, Category, CategoryId, Channel, ChannelCategory, ChannelCategoryCreationParameters, ChannelCategoryId, ChannelCategoryUpdateParameters, ChannelCreationParameters, ChannelId, ChannelOwner, ChannelOwnershipTransferRequest, ChannelOwnershipTransferRequestId, ChannelUpdateParameters, ConstitutionInfo, ContentActor, ContentId, ContentParameters, CouncilMemberOf, CouncilStage, CouncilStageAnnouncing, CouncilStageElection, CouncilStageUpdate, CreateOpeningParameters, CuratorGroup, CuratorGroupId, CuratorId, DAOId, DataObject, DataObjectStorageRelationship, DataObjectStorageRelationshipId, DataObjectType, DataObjectTypeId, DataObjectsMap, DiscussionPost, DiscussionThread, Entry, EntryId, EpisodeParemters, ExecutionFailed, ExecutionStatus, ExtendedPostId, FillOpeningParameters, ForumUserId, FundingRequestParameters, FundingType, FundingType_Limited, FundingType_Perpetual, GeneralProposalParameters, InputValidationLengthConstraint, InviteMembershipParameters, IsCensored, JSBounty, LiaisonJudgement, MaxNumber, MemberId, Membership, MemoText, ModeratorId, NewAsset, ObjectOwner, Opening, OpeningId, OpeningType, OptionResult, OracleJudgment, OracleJudgment_Winner, OracleWorkEntryJudgment, ParticipantId, Penalty, Person, PersonActor, PersonController, PersonCreationParameters, PersonId, PersonUpdateParameters, Playlist, PlaylistCreationParameters, PlaylistId, PlaylistUpdateParameters, Poll, PollAlternative, PollInput, Post, PostId, PostReactionId, PrivilegedActor, ProposalDecision, ProposalDetails, ProposalDetailsOf, ProposalId, ProposalOf, ProposalParameters, ProposalStatus, ReferendumStage, ReferendumStageRevealing, ReferendumStageVoting, Reply, ReplyId, ReplyToDelete, RewardPaymentType, Season, SeasonParameters, Series, SeriesId, SeriesParameters, SetLeadParams, StakeParameters, StakePolicy, StakingAccountMemberBinding, StorageObjectOwner, StorageProviderId, TerminateRoleParameters, Thread, ThreadId, ThreadMode, ThreadOf, Title, UpdatedBody, UpdatedTitle, UploadingStatus, Url, Video, VideoCategory, VideoCategoryCreationParameters, VideoCategoryId, VideoCategoryUpdateParameters, VideoCreationParameters, VideoId, VideoUpdateParameters, VoteKind, VotePower, VotingResults, Voucher, VoucherLimit, Worker, WorkerId, WorkerInfo, WorkingGroup } from './all'; import type { AssetApproval, AssetApprovalKey, AssetBalance, AssetDestroyWitness, AssetDetails, AssetMetadata, TAssetBalance, TAssetDepositBalance } from '@polkadot/types/interfaces/assets'; import type { BlockAttestations, IncludedBlocks, MoreAttestations } from '@polkadot/types/interfaces/attestations'; import type { RawAuraPreDigest } from '@polkadot/types/interfaces/aura'; @@ -804,6 +804,7 @@ declare module '@polkadot/types/types/registry' { 'Option': Option; 'Option': Option; 'Option': Option; + 'Option': Option; 'Option': Option; 'Option': Option; 'Option': Option; @@ -1898,6 +1899,7 @@ declare module '@polkadot/types/types/registry' { 'Vec': Vec; 'Vec': Vec; 'Vec': Vec; + 'Vec': Vec; 'Vec': Vec; 'Vec': Vec; 'Vec': Vec; @@ -2992,6 +2994,7 @@ declare module '@polkadot/types/types/registry' { OptionResult: OptionResult; OracleJudgment_Winner: OracleJudgment_Winner; OracleJudgment: OracleJudgment; + OracleWorkEntryJudgment: OracleWorkEntryJudgment; Origin: Origin; OriginCaller: OriginCaller; OutboundHrmpMessage: OutboundHrmpMessage; diff --git a/types/augment/all/defs.json b/types/augment/all/defs.json index fc3130812d..13518c5161 100644 --- a/types/augment/all/defs.json +++ b/types/augment/all/defs.json @@ -554,12 +554,13 @@ "OracleJudgment_Winner": { "reward": "u128" }, - "OracleJudgment": { + "OracleWorkEntryJudgment": { "_enum": { "Winner": "OracleJudgment_Winner", "Rejected": "Null" } }, + "OracleJudgment": "BTreeMap", "Entry": { "member_id": "MemberId", "staking_account_id": "AccountId", diff --git a/types/augment/all/types.ts b/types/augment/all/types.ts index b468e0e7f2..f9ec7b0837 100644 --- a/types/augment/all/types.ts +++ b/types/augment/all/types.ts @@ -537,17 +537,20 @@ export interface OptionResult extends Struct { } /** @name OracleJudgment */ -export interface OracleJudgment extends Enum { - readonly isWinner: boolean; - readonly asWinner: OracleJudgment_Winner; - readonly isRejected: boolean; -} +export interface OracleJudgment extends BTreeMap {} /** @name OracleJudgment_Winner */ export interface OracleJudgment_Winner extends Struct { readonly reward: u128; } +/** @name OracleWorkEntryJudgment */ +export interface OracleWorkEntryJudgment extends Enum { + readonly isWinner: boolean; + readonly asWinner: OracleJudgment_Winner; + readonly isRejected: boolean; +} + /** @name ParticipantId */ export interface ParticipantId extends u64 {} diff --git a/types/augment/augment-api-tx.ts b/types/augment/augment-api-tx.ts index 8b70751d53..e2a63ebc7f 100644 --- a/types/augment/augment-api-tx.ts +++ b/types/augment/augment-api-tx.ts @@ -273,7 +273,7 @@ declare module '@polkadot/api/types/submittable' { * - `O(N)` * # **/ - submitOracleJudgment: AugmentedSubmittable<(oracle: BountyActor | { Council: any } | { Member: any } | string | Uint8Array, bountyId: BountyId | AnyNumber | Uint8Array, judgment: OracleJudgment | { Winner: any } | { Rejected: any } | string | Uint8Array) => SubmittableExtrinsic, [BountyActor, BountyId, OracleJudgment]>; + submitOracleJudgment: AugmentedSubmittable<(oracle: BountyActor | { Council: any } | { Member: any } | string | Uint8Array, bountyId: BountyId | AnyNumber | Uint8Array, judgment: OracleJudgment) => SubmittableExtrinsic, [BountyActor, BountyId, OracleJudgment]>; /** * Submit work for a bounty. * # diff --git a/types/augment/augment-types.ts b/types/augment/augment-types.ts index 8f9929b8d8..c334c20cc0 100644 --- a/types/augment/augment-types.ts +++ b/types/augment/augment-types.ts @@ -2,7 +2,7 @@ /* eslint-disable */ import type { BitVec, Bool, Bytes, Compact, Data, I128, I16, I256, I32, I64, I8, Json, Null, Option, Raw, StorageKey, Text, Type, U128, U16, U256, U32, U64, U8, USize, Vec, bool, i128, i16, i256, i32, i64, i8, u128, u16, u256, u32, u64, u8, usize } from '@polkadot/types'; -import type { ActorId, Application, ApplicationId, ApplicationIdSet, ApplicationIdToWorkerIdMap, ApplicationInfo, ApplyOnOpeningParameters, Approved, AssuranceContractType, BalanceKind, BlockAndTime, BountyActor, BountyCreationParameters, BountyId, BountyMilestone, BuyMembershipParameters, Candidate, CastVoteOf, Category, CategoryId, Channel, ChannelCategory, ChannelCategoryCreationParameters, ChannelCategoryId, ChannelCategoryUpdateParameters, ChannelCreationParameters, ChannelId, ChannelOwner, ChannelOwnershipTransferRequest, ChannelOwnershipTransferRequestId, ChannelUpdateParameters, ConstitutionInfo, ContentActor, ContentId, ContentParameters, CouncilMemberOf, CouncilStage, CouncilStageAnnouncing, CouncilStageElection, CouncilStageUpdate, CreateOpeningParameters, CuratorGroup, CuratorGroupId, CuratorId, DAOId, DataObject, DataObjectStorageRelationship, DataObjectStorageRelationshipId, DataObjectType, DataObjectTypeId, DataObjectsMap, DiscussionPost, DiscussionThread, Entry, EntryId, EpisodeParemters, ExecutionFailed, ExecutionStatus, ExtendedPostId, FillOpeningParameters, ForumUserId, FundingRequestParameters, FundingType, FundingType_Limited, FundingType_Perpetual, GeneralProposalParameters, InputValidationLengthConstraint, InviteMembershipParameters, IsCensored, JSBounty, LiaisonJudgement, MaxNumber, MemberId, Membership, MemoText, ModeratorId, NewAsset, ObjectOwner, Opening, OpeningId, OpeningType, OptionResult, OracleJudgment, OracleJudgment_Winner, ParticipantId, Penalty, Person, PersonActor, PersonController, PersonCreationParameters, PersonId, PersonUpdateParameters, Playlist, PlaylistCreationParameters, PlaylistId, PlaylistUpdateParameters, Poll, PollAlternative, PollInput, Post, PostId, PostReactionId, PrivilegedActor, ProposalDecision, ProposalDetails, ProposalDetailsOf, ProposalId, ProposalOf, ProposalParameters, ProposalStatus, ReferendumStage, ReferendumStageRevealing, ReferendumStageVoting, Reply, ReplyId, ReplyToDelete, RewardPaymentType, Season, SeasonParameters, Series, SeriesId, SeriesParameters, SetLeadParams, StakeParameters, StakePolicy, StakingAccountMemberBinding, StorageObjectOwner, StorageProviderId, TerminateRoleParameters, Thread, ThreadId, ThreadMode, ThreadOf, Title, UpdatedBody, UpdatedTitle, UploadingStatus, Url, Video, VideoCategory, VideoCategoryCreationParameters, VideoCategoryId, VideoCategoryUpdateParameters, VideoCreationParameters, VideoId, VideoUpdateParameters, VoteKind, VotePower, VotingResults, Voucher, VoucherLimit, Worker, WorkerId, WorkerInfo, WorkingGroup } from './all'; +import type { ActorId, Application, ApplicationId, ApplicationIdSet, ApplicationIdToWorkerIdMap, ApplicationInfo, ApplyOnOpeningParameters, Approved, AssuranceContractType, BalanceKind, BlockAndTime, BountyActor, BountyCreationParameters, BountyId, BountyMilestone, BuyMembershipParameters, Candidate, CastVoteOf, Category, CategoryId, Channel, ChannelCategory, ChannelCategoryCreationParameters, ChannelCategoryId, ChannelCategoryUpdateParameters, ChannelCreationParameters, ChannelId, ChannelOwner, ChannelOwnershipTransferRequest, ChannelOwnershipTransferRequestId, ChannelUpdateParameters, ConstitutionInfo, ContentActor, ContentId, ContentParameters, CouncilMemberOf, CouncilStage, CouncilStageAnnouncing, CouncilStageElection, CouncilStageUpdate, CreateOpeningParameters, CuratorGroup, CuratorGroupId, CuratorId, DAOId, DataObject, DataObjectStorageRelationship, DataObjectStorageRelationshipId, DataObjectType, DataObjectTypeId, DataObjectsMap, DiscussionPost, DiscussionThread, Entry, EntryId, EpisodeParemters, ExecutionFailed, ExecutionStatus, ExtendedPostId, FillOpeningParameters, ForumUserId, FundingRequestParameters, FundingType, FundingType_Limited, FundingType_Perpetual, GeneralProposalParameters, InputValidationLengthConstraint, InviteMembershipParameters, IsCensored, JSBounty, LiaisonJudgement, MaxNumber, MemberId, Membership, MemoText, ModeratorId, NewAsset, ObjectOwner, Opening, OpeningId, OpeningType, OptionResult, OracleJudgment, OracleJudgment_Winner, OracleWorkEntryJudgment, ParticipantId, Penalty, Person, PersonActor, PersonController, PersonCreationParameters, PersonId, PersonUpdateParameters, Playlist, PlaylistCreationParameters, PlaylistId, PlaylistUpdateParameters, Poll, PollAlternative, PollInput, Post, PostId, PostReactionId, PrivilegedActor, ProposalDecision, ProposalDetails, ProposalDetailsOf, ProposalId, ProposalOf, ProposalParameters, ProposalStatus, ReferendumStage, ReferendumStageRevealing, ReferendumStageVoting, Reply, ReplyId, ReplyToDelete, RewardPaymentType, Season, SeasonParameters, Series, SeriesId, SeriesParameters, SetLeadParams, StakeParameters, StakePolicy, StakingAccountMemberBinding, StorageObjectOwner, StorageProviderId, TerminateRoleParameters, Thread, ThreadId, ThreadMode, ThreadOf, Title, UpdatedBody, UpdatedTitle, UploadingStatus, Url, Video, VideoCategory, VideoCategoryCreationParameters, VideoCategoryId, VideoCategoryUpdateParameters, VideoCreationParameters, VideoId, VideoUpdateParameters, VoteKind, VotePower, VotingResults, Voucher, VoucherLimit, Worker, WorkerId, WorkerInfo, WorkingGroup } from './all'; import type { AssetApproval, AssetApprovalKey, AssetBalance, AssetDestroyWitness, AssetDetails, AssetMetadata, TAssetBalance, TAssetDepositBalance } from '@polkadot/types/interfaces/assets'; import type { BlockAttestations, IncludedBlocks, MoreAttestations } from '@polkadot/types/interfaces/attestations'; import type { RawAuraPreDigest } from '@polkadot/types/interfaces/aura'; @@ -804,6 +804,7 @@ declare module '@polkadot/types/types/registry' { 'Option': Option; 'Option': Option; 'Option': Option; + 'Option': Option; 'Option': Option; 'Option': Option; 'Option': Option; @@ -1898,6 +1899,7 @@ declare module '@polkadot/types/types/registry' { 'Vec': Vec; 'Vec': Vec; 'Vec': Vec; + 'Vec': Vec; 'Vec': Vec; 'Vec': Vec; 'Vec': Vec; @@ -2992,6 +2994,7 @@ declare module '@polkadot/types/types/registry' { OptionResult: OptionResult; OracleJudgment_Winner: OracleJudgment_Winner; OracleJudgment: OracleJudgment; + OracleWorkEntryJudgment: OracleWorkEntryJudgment; Origin: Origin; OriginCaller: OriginCaller; OutboundHrmpMessage: OutboundHrmpMessage; diff --git a/types/src/bounty.ts b/types/src/bounty.ts index 4eb4bd994d..69202a457d 100644 --- a/types/src/bounty.ts +++ b/types/src/bounty.ts @@ -1,4 +1,4 @@ -import { Null, u32, u128, bool, Option, BTreeSet } from '@polkadot/types' +import { Null, u32, u128, bool, Option, BTreeSet, BTreeMap } from '@polkadot/types' import { JoyEnum, JoyStructDecorated, MemberId, AccountId } from './common' export class BountyId extends u32 {} @@ -44,11 +44,12 @@ export class OracleJudgment_Winner extends JoyStructDecorated({ reward: u128, // Balance }) {} -export class OracleJudgment extends JoyEnum({ +export class OracleWorkEntryJudgment extends JoyEnum({ Winner: OracleJudgment_Winner, Rejected: Null, }) {} +export class OracleJudgment extends BTreeMap.with(EntryId, OracleWorkEntryJudgment) {} export class Entry extends JoyStructDecorated({ member_id: MemberId, staking_account_id: AccountId, @@ -100,6 +101,7 @@ export const bountyTypes = { FundingType, BountyCreationParameters, OracleJudgment_Winner, + OracleWorkEntryJudgment, OracleJudgment, Entry, } From 5855ab37080b4de48d3382f87ec3e69fc42a4f95 Mon Sep 17 00:00:00 2001 From: Zeeshan Akram <97m.zeeshan@gmail.com> Date: Fri, 28 Jan 2022 17:05:28 +0500 Subject: [PATCH 09/16] generated BountyInputParameters & OracleJudgmentInputParameters schemas for JSON input --- cli/src/Types.ts | 16 ++- cli/src/json-schemas/Bounty.ts | 191 +++++++++++++++++++++++++++++---- 2 files changed, 183 insertions(+), 24 deletions(-) diff --git a/cli/src/Types.ts b/cli/src/Types.ts index 2ef00c166b..1f9a843263 100644 --- a/cli/src/Types.ts +++ b/cli/src/Types.ts @@ -17,6 +17,7 @@ import { IVideoCategoryMetadata, IChannelCategoryMetadata, IBountyMetadata, + IBountyWorkData, } from '@joystream/metadata-protobuf' // KeyringPair type extended with mandatory "meta.name" @@ -198,11 +199,24 @@ export type BountyInputParameters = IBountyMetadata & { cherry: number entrantStake: number // TODO: can this be improved? - fundingType: FundingTypeLimited & FundingTypePrepetual + fundingType: FundingTypeLimited | FundingTypePrepetual workPeriod: number judgementPeriod: number } +export type BountyWorkDataInputParameters = IBountyWorkData + +export type Winner = { + reward: number +} + +export enum Rejected {} + +export type OracleJudgmentInputParameters = { + entryId: number + judgment: Winner | Rejected +}[] + type AnyNonObject = string | number | boolean | any[] | Long // JSONSchema utility types diff --git a/cli/src/json-schemas/Bounty.ts b/cli/src/json-schemas/Bounty.ts index 8cbc97e205..5dd6babe2f 100644 --- a/cli/src/json-schemas/Bounty.ts +++ b/cli/src/json-schemas/Bounty.ts @@ -1,28 +1,173 @@ -import { BountyInputParameters, JsonSchema } from '../Types' +// schema are generated by Typescript JSON schema generator VSCode extension -export const BountyInputSchema: JsonSchema = { - type: 'object', - additionalProperties: false, - properties: { - title: { type: 'string' }, - description: { type: 'string' }, - discussionThread: { type: 'number' }, - bannerImageUri: { type: 'string' }, - oracle: { type: 'number' }, - contractTypeInput: { type: 'array' }, - cherry: { type: 'number' }, - entrantStake: { type: 'number' }, - fundingType: { - type: 'object', - additionalProperties: false, - properties: { - minFundingAmount: { type: 'number' }, - maxFundingAmount: { type: 'number' }, - fundingPeriod: { type: 'number' }, - target: { type: 'number' }, +export const BountyInputSchema = { + '$schema': 'http://json-schema.org/draft-07/schema#', + '$ref': '#/definitions/BountyInputParameters', + 'definitions': { + 'BountyInputParameters': { + 'type': 'object', + 'additionalProperties': false, + 'properties': { + 'oracle': { + 'type': 'number', + }, + 'contractTypeInput': { + 'type': 'array', + 'items': { + 'type': 'string', + }, + }, + 'cherry': { + 'type': 'number', + }, + 'entrantStake': { + 'type': 'number', + }, + 'fundingType': { + 'anyOf': [ + { + '$ref': '#/definitions/FundingTypeLimited', + }, + { + '$ref': '#/definitions/FundingTypePrepetual', + }, + ], + }, + 'workPeriod': { + 'type': 'number', + }, + 'judgementPeriod': { + 'type': 'number', + }, + 'title': { + 'type': ['string', 'null'], + }, + 'description': { + 'type': ['string', 'null'], + }, + 'discussionThread': { + 'anyOf': [ + { + '$ref': '#/definitions/Long.Long', + }, + { + 'type': 'null', + }, + ], + }, + 'bannerImageUri': { + 'type': ['string', 'null'], + }, }, + 'required': ['cherry', 'contractTypeInput', 'entrantStake', 'fundingType', 'judgementPeriod', 'workPeriod'], + }, + 'FundingTypeLimited': { + 'type': 'object', + 'properties': { + 'minFundingAmount': { + 'type': 'number', + }, + 'maxFundingAmount': { + 'type': 'number', + }, + 'fundingPeriod': { + 'type': 'number', + }, + }, + 'required': ['minFundingAmount', 'maxFundingAmount', 'fundingPeriod'], + 'additionalProperties': false, + }, + 'FundingTypePrepetual': { + 'type': 'object', + 'properties': { + 'target': { + 'type': 'number', + }, + }, + 'required': ['target'], + 'additionalProperties': false, + }, + 'Long.Long': { + 'type': 'object', + 'properties': { + 'high': { + 'type': 'number', + }, + 'low': { + 'type': 'number', + }, + 'unsigned': { + 'type': 'boolean', + }, + }, + 'required': ['high', 'low', 'unsigned'], + 'additionalProperties': false, + }, + }, +} + +export const BountyWorkDataInputSchema = { + '$schema': 'http://json-schema.org/draft-07/schema#', + '$ref': '#/definitions/BountyWorkDataInputParameters', + 'definitions': { + 'BountyWorkDataInputParameters': { + '$ref': '#/definitions/IBountyWorkData', + }, + 'IBountyWorkData': { + 'type': 'object', + 'properties': { + 'title': { + 'type': ['string', 'null'], + }, + 'description': { + 'type': ['string', 'null'], + }, + }, + 'additionalProperties': false, + }, + }, +} + +export const OracleJudgmentInputSchema = { + '$schema': 'http://json-schema.org/draft-07/schema#', + '$ref': '#/definitions/OracleJudgmentInputParameters', + 'definitions': { + 'OracleJudgmentInputParameters': { + 'type': 'array', + 'items': { + 'type': 'object', + 'properties': { + 'entryId': { + 'type': 'number', + }, + 'judgment': { + 'anyOf': [ + { + '$ref': '#/definitions/Winner', + }, + { + '$ref': '#/definitions/Rejected', + }, + ], + }, + }, + 'required': ['entryId', 'judgment'], + 'additionalProperties': false, + }, + }, + 'Winner': { + 'type': 'object', + 'properties': { + 'reward': { + 'type': 'number', + }, + }, + 'required': ['reward'], + 'additionalProperties': false, + }, + 'Rejected': { + 'type': [], + 'enum': [], }, - workPeriod: { type: 'number' }, - judgementPeriod: { type: 'number' }, }, } From 3641c8561510425a4f17d2c0508ce3dfae0e0225 Mon Sep 17 00:00:00 2001 From: Zeeshan Akram <97m.zeeshan@gmail.com> Date: Fri, 28 Jan 2022 17:21:58 +0500 Subject: [PATCH 10/16] fixed veto bounty command class name & implemented fund bounty command --- cli/src/commands/bounty/fundBounty.ts | 63 +++++++++++++++++++++++++++ cli/src/commands/bounty/vetoBounty.ts | 4 +- 2 files changed, 65 insertions(+), 2 deletions(-) create mode 100644 cli/src/commands/bounty/fundBounty.ts diff --git a/cli/src/commands/bounty/fundBounty.ts b/cli/src/commands/bounty/fundBounty.ts new file mode 100644 index 0000000000..2ee45bd821 --- /dev/null +++ b/cli/src/commands/bounty/fundBounty.ts @@ -0,0 +1,63 @@ +import BN from 'bn.js' +import chalk from 'chalk' +import BountyCommandBase from '../../base/BountyCommandBase' +import ExitCodes from '../../ExitCodes' +import { checkBalance, isValidBalance } from '../../helpers/validation' + +export default class FundBountyCommand extends BountyCommandBase { + static description = 'Provide funding to bounty.' + static args = [ + { + name: 'bountyId', + required: true, + description: 'ID of the Bounty to fund', + }, + { + name: 'amount', + required: true, + description: 'Amount to be contributed towards bounty by funder', + }, + ] + + static flags = { + funderContext: BountyCommandBase.bountyActorContextFlag, + } + + async run() { + let { funderContext } = this.parse(FundBountyCommand).flags + const { bountyId, amount } = this.parse(FundBountyCommand).args + + // Context + if (!funderContext) { + funderContext = await this.promptForCreatorContext() + } + + const [funder, funderAddress] = await this.getBountyActor(funderContext) + const bounty = await this.getApi().bountyById(bountyId) + const funderBalance = (await this.getApi().getAccountSummary(funderAddress)).balances + const amountBN = new BN(amount) + + if (!isValidBalance(amount) || amountBN < this.getOriginalApi().consts.bounty.minFundingLimit.toBn()) { + this.error('Funding input is invalid or less than the minimum funding limit', { exit: ExitCodes.InvalidInput }) + } + + checkBalance(funderBalance, amountBN) + if (await this.fundingPeriodExpired(bounty)) { + this.error('Funding period expired', { exit: ExitCodes.ApiError }) + } + + this.jsonPrettyPrint(JSON.stringify({ bountyId, amount })) + + await this.requireConfirmation('Do you confirm the the input?', true) + + const result = await this.sendAndFollowNamedTx(await this.getDecodedPair(funderAddress), 'bounty', 'fundBounty', [ + funder, + bountyId, + amount, + ]) + if (result) { + const event = this.findEvent(result, 'bounty', 'BountyCanceled') + this.log(chalk.green(`Bounty with id ${chalk.cyanBright(event?.data[1].toString())} successfully funded!`)) + } + } +} diff --git a/cli/src/commands/bounty/vetoBounty.ts b/cli/src/commands/bounty/vetoBounty.ts index d0ffddf1a9..660924db52 100644 --- a/cli/src/commands/bounty/vetoBounty.ts +++ b/cli/src/commands/bounty/vetoBounty.ts @@ -1,7 +1,7 @@ import chalk from 'chalk' import BountyCommandBase from '../../base/BountyCommandBase' -export default class CreateBountyCommand extends BountyCommandBase { +export default class VetoBountyCommand extends BountyCommandBase { static description = 'Veto bounty by the Council.' static args = [ { @@ -12,7 +12,7 @@ export default class CreateBountyCommand extends BountyCommandBase { ] async run() { - const { bountyId } = this.parse(CreateBountyCommand).args + const { bountyId } = this.parse(VetoBountyCommand).args const [councilMember, councilMemberAddress] = await this.getBountyActor('Council') await this.ensureBountyCanBeCanceled(bountyId, councilMember) From 7e6d17f44e35621aabaf010aea5c45a317373370 Mon Sep 17 00:00:00 2001 From: Zeeshan Akram <97m.zeeshan@gmail.com> Date: Fri, 28 Jan 2022 17:23:13 +0500 Subject: [PATCH 11/16] implemented annoucne work entry command --- cli/src/commands/bounty/announceWorkEntry.ts | 63 ++++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 cli/src/commands/bounty/announceWorkEntry.ts diff --git a/cli/src/commands/bounty/announceWorkEntry.ts b/cli/src/commands/bounty/announceWorkEntry.ts new file mode 100644 index 0000000000..4419e4d4b0 --- /dev/null +++ b/cli/src/commands/bounty/announceWorkEntry.ts @@ -0,0 +1,63 @@ +import chalk from 'chalk' +import BountyCommandBase from '../../base/BountyCommandBase' +import ExitCodes from '../../ExitCodes' + +export default class AnnounceWorkEntryCommand extends BountyCommandBase { + static description = 'Announce work entry for a bounty.' + static args = [ + { + name: 'bountyId', + required: true, + description: 'ID for the bounty', + }, + ] + + async run() { + const { bountyId } = this.parse(AnnounceWorkEntryCommand).args + + const bounty = await this.getApi().bountyById(bountyId) + const memberContext = await this.getRequiredMemberContext() + // TODO: check if the function properly ensures + // TODO: staking requirements for this usecase + const stakingAccount = await this.promptForStakingAccount( + bounty.creation_params.entrant_stake, + memberContext.id, + memberContext.membership + ) + + if (bounty.creation_params.contract_type.isOfType('Closed')) { + const isMemberAllowedToToWork = Array.from(bounty.creation_params.contract_type.asType('Closed')).includes( + memberContext.id + ) + if (!isMemberAllowedToToWork) { + this.error('Member is not allowed to work in this Closed bounty', { exit: ExitCodes.ApiError }) + } + } + + const canWorkEntryBeAnnounced = await this.isWorkSubmissionStage(bounty) + if (!canWorkEntryBeAnnounced) { + this.error('Work entry cannot be announced in this stage', { exit: ExitCodes.ApiError }) + } + + this.jsonPrettyPrint(JSON.stringify({ memberId: memberContext.id, bountyId, stakingAccount })) + + await this.requireConfirmation('Do you confirm the the input?', true) + + const result = await this.sendAndFollowNamedTx( + await this.getDecodedPair(memberContext.membership.controller_account.toString()), + 'bounty', + 'announceWorkEntry', + [memberContext.id, bountyId, stakingAccount] + ) + if (result) { + const event = this.findEvent(result, 'bounty', 'WorkEntryAnnounced') + this.log( + chalk.green( + `Work entry with id ${chalk.cyanBright(event?.data[1].toString())} for Bounty with id ${chalk.cyanBright( + event?.data[0].toString() + )} successfully created!` + ) + ) + } + } +} From a39e43be3ca8ccb15ffec62e73d94515c8b90693 Mon Sep 17 00:00:00 2001 From: Zeeshan Akram <97m.zeeshan@gmail.com> Date: Fri, 28 Jan 2022 17:25:54 +0500 Subject: [PATCH 12/16] implemented withdraw funding commands --- cli/src/commands/bounty/withdrawFunding.ts | 65 +++++++++++++++++++ .../bounty/withdrawWorkEntrantFunds.ts | 57 ++++++++++++++++ 2 files changed, 122 insertions(+) create mode 100644 cli/src/commands/bounty/withdrawFunding.ts create mode 100644 cli/src/commands/bounty/withdrawWorkEntrantFunds.ts diff --git a/cli/src/commands/bounty/withdrawFunding.ts b/cli/src/commands/bounty/withdrawFunding.ts new file mode 100644 index 0000000000..eecf6b207a --- /dev/null +++ b/cli/src/commands/bounty/withdrawFunding.ts @@ -0,0 +1,65 @@ +import chalk from 'chalk' +import BountyCommandBase from '../../base/BountyCommandBase' +import ExitCodes from '../../ExitCodes' + +export default class WithdrawFundingCommand extends BountyCommandBase { + static description = 'Withdraw funding from the bounty.' + static args = [ + { + name: 'bountyId', + required: true, + description: 'ID of the Bounty', + }, + ] + + static flags = { + funderContext: BountyCommandBase.bountyActorContextFlag, + } + + async run() { + let { funderContext } = this.parse(WithdrawFundingCommand).flags + const { bountyId } = this.parse(WithdrawFundingCommand).args + + // Context + if (!funderContext) { + funderContext = await this.promptForCreatorContext() + } + + const [funder, funderAddress] = await this.getBountyActor(funderContext) + const bounty = await this.getApi().bountyById(bountyId) + + const isFundingStage = await this.isFundingStage(bounty) + const isFundingExpiredStage = await this.isFundingExpiredStage(bounty) + const isWorkSubmissionStage = await this.isWorkSubmissionStage(bounty) + const isJudgmentStage = await this.isJudgmentStage(bounty) + const isSuccessfulBountyWithdrawlStage = await this.isSuccessfulBountyWithdrawalStage(bounty) + + // if bounty is in any of the following stages funds cannot be withdrawn + if ( + isFundingStage || + isFundingExpiredStage || + isWorkSubmissionStage || + isJudgmentStage || + isSuccessfulBountyWithdrawlStage + ) { + this.error('Funds cannot be withdrawn in this stage', { exit: ExitCodes.AccessDenied }) + } + + this.jsonPrettyPrint(JSON.stringify({ bountyId })) + + await this.requireConfirmation('Do you confirm to withdraw funding from bounty?', true) + + const result = await this.sendAndFollowNamedTx( + await this.getDecodedPair(funderAddress), + 'bounty', + 'withdrawFunding', + [funder, bountyId] + ) + if (result) { + const event = this.findEvent(result, 'bounty', 'BountyFundingWithdrawal') + this.log( + chalk.green(`Amount successfully refunded from bounty with id ${chalk.cyanBright(event?.data[1].toString())} !`) + ) + } + } +} diff --git a/cli/src/commands/bounty/withdrawWorkEntrantFunds.ts b/cli/src/commands/bounty/withdrawWorkEntrantFunds.ts new file mode 100644 index 0000000000..90a17458f1 --- /dev/null +++ b/cli/src/commands/bounty/withdrawWorkEntrantFunds.ts @@ -0,0 +1,57 @@ +import chalk from 'chalk' +import BountyCommandBase from '../../base/BountyCommandBase' +import ExitCodes from '../../ExitCodes' + +export default class WithdrawWorkEntrantCommand extends BountyCommandBase { + static description = 'Cashout work entrant funds.' + static args = [ + { + name: 'bountyId', + required: true, + description: 'Identifier for bounty to which work entry corresponds.', + }, + { + name: 'entryId', + required: true, + description: 'Identifier for work entry.', + }, + ] + + async run() { + const { bountyId, entryId } = this.parse(WithdrawWorkEntrantCommand).args + + const bounty = await this.getApi().bountyById(bountyId) + await this.getApi().entryById(entryId) + // Get member context + const memberContext = await this.getRequiredMemberContext() + + const isFundingStage = await this.isFundingStage(bounty) + const isFundingExpiredStage = await this.isFundingExpiredStage(bounty) + const isWorkSubmissionStage = await this.isWorkSubmissionStage(bounty) + const isJudgmentStage = await this.isJudgmentStage(bounty) + + // if bounty is in any of the following stages work entrant funds cannot be withdrawn + if (isFundingStage || isFundingExpiredStage || isWorkSubmissionStage || isJudgmentStage) { + this.error('Work entrant funds cannot be withdrawn in this stage', { exit: ExitCodes.AccessDenied }) + } + + this.jsonPrettyPrint(JSON.stringify({ memberId: memberContext.id, bountyId, entryId })) + + await this.requireConfirmation('Do you confirm the the input?', true) + + const result = await this.sendAndFollowNamedTx( + await this.getDecodedPair(memberContext.membership.controller_account.toString()), + 'bounty', + 'withdrawWorkEntrantFunds', + [memberContext.id, bountyId, entryId] + ) + if (result) { + const event = this.findEvent(result, 'bounty', 'WorkEntrantFundsWithdrawn') + this.log( + chalk.green( + `Work entrant funds successfully cashed out for bounty with id ${chalk.cyanBright(event?.data[0].toString())}` + ) + ) + } + } +} From 43d259536fb0f9fd41a9b2abfc1eb44dacbe5a73 Mon Sep 17 00:00:00 2001 From: Zeeshan Akram <97m.zeeshan@gmail.com> Date: Fri, 28 Jan 2022 17:26:50 +0500 Subject: [PATCH 13/16] implemented submit work command --- cli/src/commands/bounty/submitWork.ts | 70 +++++++++++++++++++++++++++ 1 file changed, 70 insertions(+) create mode 100644 cli/src/commands/bounty/submitWork.ts diff --git a/cli/src/commands/bounty/submitWork.ts b/cli/src/commands/bounty/submitWork.ts new file mode 100644 index 0000000000..3a40e74388 --- /dev/null +++ b/cli/src/commands/bounty/submitWork.ts @@ -0,0 +1,70 @@ +import { BountyWorkData } from '@joystream/metadata-protobuf' +import { flags } from '@oclif/command' +import chalk from 'chalk' +import BountyCommandBase from '../../base/BountyCommandBase' +import ExitCodes from '../../ExitCodes' +import { getInputJson } from '../../helpers/InputOutput' +import { asValidatedMetadata, metadataToBytes } from '../../helpers/serialization' +import { BountyWorkDataInputSchema } from '../../json-schemas/Bounty' +import { BountyWorkDataInputParameters } from '../../Types' + +export default class SubmitWorkCommand extends BountyCommandBase { + static description = 'Submit work for a bounty.' + static args = [ + { + name: 'bountyId', + required: true, + description: 'ID for the bounty', + }, + { + name: 'entryId', + required: true, + description: 'ID for work entry.', + }, + ] + + static flags = { + input: flags.string({ + char: 'i', + required: true, + description: `Path to work data JSON file to use as input`, + }), + } + + async run() { + const { input } = this.parse(SubmitWorkCommand).flags + const { bountyId, entryId } = this.parse(SubmitWorkCommand).args + + const bounty = await this.getApi().bountyById(bountyId) + // ensure that entry exists + await this.getApi().entryById(entryId) + const memberContext = await this.getRequiredMemberContext() + const canWorkbeSubmitted = await this.isWorkSubmissionStage(bounty) + + if (!canWorkbeSubmitted) { + this.error('Work cannot be submitted in this stage', { exit: ExitCodes.ApiError }) + } + + const workDataInput = await getInputJson(input, BountyWorkDataInputSchema) + const workData = asValidatedMetadata(BountyWorkData, workDataInput) + + this.jsonPrettyPrint(JSON.stringify({ memberId: memberContext.id, bountyId, entryId })) + + await this.requireConfirmation('Do you confirm the the input?', true) + + const result = await this.sendAndFollowNamedTx( + await this.getDecodedPair(memberContext.membership.controller_account.toString()), + 'bounty', + 'submitWork', + [memberContext.id, bountyId, entryId, metadataToBytes(BountyWorkData, workData)] + ) + if (result) { + const event = this.findEvent(result, 'bounty', 'WorkSubmitted') + this.log( + chalk.green( + `Work data successfully submitted for bounty with id ${chalk.cyanBright(event?.data[0].toString())}` + ) + ) + } + } +} From e14bbb5279e26bcf90958fa246013a27ee27df5d Mon Sep 17 00:00:00 2001 From: Zeeshan Akram <97m.zeeshan@gmail.com> Date: Fri, 28 Jan 2022 17:28:08 +0500 Subject: [PATCH 14/16] implemented submit oracle judgment & withdraw work entry commands --- .../commands/bounty/submitOracleJudgment.ts | 75 +++++++++++++++++++ cli/src/commands/bounty/withdrawWorkEntry.ts | 49 ++++++++++++ 2 files changed, 124 insertions(+) create mode 100644 cli/src/commands/bounty/submitOracleJudgment.ts create mode 100644 cli/src/commands/bounty/withdrawWorkEntry.ts diff --git a/cli/src/commands/bounty/submitOracleJudgment.ts b/cli/src/commands/bounty/submitOracleJudgment.ts new file mode 100644 index 0000000000..14fd89b227 --- /dev/null +++ b/cli/src/commands/bounty/submitOracleJudgment.ts @@ -0,0 +1,75 @@ +import { flags } from '@oclif/command' +import chalk from 'chalk' +import { OracleJudgmentInputSchema } from 'src/json-schemas/Bounty' +import BountyCommandBase from '../../base/BountyCommandBase' +import ExitCodes from '../../ExitCodes' +import { getInputJson } from '../../helpers/InputOutput' +import { OracleJudgmentInputParameters } from '../../Types' + +export default class SubmitOracleJudgmentCommand extends BountyCommandBase { + static description = 'Submit judgment for a bounty.' + static args = [ + { + name: 'bountyId', + required: true, + description: 'ID of bounty for which judgement is being submitted.', + }, + ] + + static flags = { + oracleContext: BountyCommandBase.bountyActorContextFlag, + input: flags.string({ + char: 'i', + required: true, + description: `Path to oracle judgment JSON file to use as input`, + }), + } + + async run() { + let { oracleContext, input } = this.parse(SubmitOracleJudgmentCommand).flags + const { bountyId } = this.parse(SubmitOracleJudgmentCommand).args + + // Context + if (!oracleContext) { + oracleContext = await this.promptForCreatorContext() + } + + const [oracle, oracleAddress] = await this.getBountyActor(oracleContext) + const bounty = await this.getApi().bountyById(bountyId) + const judgmentInput = await getInputJson(input, OracleJudgmentInputSchema) + const oracleJudgment = await this.validateAndPrepareOracleJudgement(bounty, judgmentInput) + + if (bounty.creation_params.oracle !== oracle) { + this.error('Bounty actor is not allowed to act as oracle', { exit: ExitCodes.AccessDenied }) + } + + if (!(await this.isJudgmentStage(bounty))) { + this.error('Judgment cannot be submitted in this stage', { exit: ExitCodes.ApiError }) + } + + if (bounty.active_work_entry_count.toNumber() === 0) { + this.error('No active work entires exist', { exit: ExitCodes.ApiError }) + } + + this.jsonPrettyPrint(JSON.stringify({ oracle, bountyId, oracleJudgment })) + + await this.requireConfirmation('Do you confirm the provided input?', true) + + const result = await this.sendAndFollowNamedTx( + await this.getDecodedPair(oracleAddress), + 'bounty', + 'submitOracleJudgment', + [oracle, bountyId, oracleJudgment] + ) + if (result) { + const event = this.findEvent(result, 'bounty', 'OracleJudgmentSubmitted') + this.log( + chalk.green( + `Oracle judgment for bounty with id ${chalk.cyanBright( + event?.data[0].toString() + )} was successfully submitted!` + ) + ) + } + } +} diff --git a/cli/src/commands/bounty/withdrawWorkEntry.ts b/cli/src/commands/bounty/withdrawWorkEntry.ts new file mode 100644 index 0000000000..b3b81891bb --- /dev/null +++ b/cli/src/commands/bounty/withdrawWorkEntry.ts @@ -0,0 +1,49 @@ +import chalk from 'chalk' +import BountyCommandBase from '../../base/BountyCommandBase' +import ExitCodes from '../../ExitCodes' + +export default class AnnounceWorkEntryCommand extends BountyCommandBase { + static description = 'Withdraw work entry from a bounty.' + static args = [ + { + name: 'bountyId', + required: true, + description: 'Identifier for bounty from which to withdraw work entry.', + }, + { + name: 'entryId', + required: true, + description: 'Identifier for work entry.', + }, + ] + + async run() { + const { bountyId, entryId } = this.parse(AnnounceWorkEntryCommand).args + + const bounty = await this.getApi().bountyById(bountyId) + await this.getApi().entryById(entryId) + const memberContext = await this.getRequiredMemberContext() + const canWorkEntryBeAnnounced = await this.isWorkSubmissionStage(bounty) + + if (!canWorkEntryBeAnnounced) { + this.error('Work entry cannot be withdrawn in this stage', { exit: ExitCodes.ApiError }) + } + + this.jsonPrettyPrint(JSON.stringify({ memberId: memberContext.id, bountyId, entryId })) + + await this.requireConfirmation('Do you confirm the the input?', true) + + const result = await this.sendAndFollowNamedTx( + await this.getDecodedPair(memberContext.membership.controller_account.toString()), + 'bounty', + 'withdrawWorkEntry', + [memberContext.id, bountyId, entryId] + ) + if (result) { + const event = this.findEvent(result, 'bounty', 'WorkEntryWithdrawn') + this.log( + chalk.green(`Work entry with id ${chalk.cyanBright(event?.data[1].toString())} successfully withdrawn!`) + ) + } + } +} From 78f0b7c2067172a9b1dce67a232f50b00244070b Mon Sep 17 00:00:00 2001 From: Zeeshan Akram <97m.zeeshan@gmail.com> Date: Fri, 28 Jan 2022 17:32:58 +0500 Subject: [PATCH 15/16] added bounty cli commands usage to README.md --- cli/README.md | 135 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 135 insertions(+) diff --git a/cli/README.md b/cli/README.md index 0410e565f2..e7ca81a09a 100644 --- a/cli/README.md +++ b/cli/README.md @@ -84,6 +84,15 @@ When using the CLI for the first time there are a few common steps you might wan * [`joystream-cli bounty:createBounty`](#joystream-cli-bountycreatebounty) * [`joystream-cli bounty:cancelBounty BOUNTYID`](#joystream-cli-bountycancelbounty-bountyid) * [`joystream-cli bounty:vetoBounty BOUNTYID`](#joystream-cli-bountyvetobounty-bountyid) +* [`joystream-cli bounty:entry ENTRYID`](#joystream-cli-bountyentry-entryid) +* [`joystream-cli bounty:entries`](#joystream-cli-bountyentries) +* [`joystream-cli bounty:fundBounty BOUNTYID AMOUNT`](#joystream-cli-bountyfundbounty-bountyid-amount) +* [`joystream-cli bounty:withdrawFunding BOUNTYID`](#joystream-cli-bountywithdrawfunding-bountyid) +* [`joystream-cli bounty:announceWorkEntry BOUNTYID`](#joystream-cli-bountyannounceworkentry-bountyid) +* [`joystream-cli bounty:withdrawWorkEntry BOUNTYID ENTRYID`](#joystream-cli-bountywithdrawworkentry-bountyid-entryid) +* [`joystream-cli bounty:submitWork BOUNTYID ENTRYID`](#joystream-cli-bountysubmitwork-bountyid-entryid) +* [`joystream-cli bounty:submitOracleJudgment BOUNTYID`](#joystream-cli-bountysubmitoraclejudgment-bountyid) +* [`joystream-cli bounty:withdrawWorkEntrantFunds BOUNTYID ENTRYID`](#joystream-cli-bountywithdrawworkentrantfunds-bountyid-entryid) * [`joystream-cli content:addCuratorToGroup [GROUPID] [CURATORID]`](#joystream-cli-contentaddcuratortogroup-groupid-curatorid) * [`joystream-cli content:channel CHANNELID`](#joystream-cli-contentchannel-channelid) * [`joystream-cli content:channels`](#joystream-cli-contentchannels) @@ -415,9 +424,135 @@ Veto bounty by the Council. USAGE $ joystream-cli bounty:vetoBounty BOUNTYID +ARGUMENTS + BOUNTYID ID of the Bounty +``` + +## `joystream-cli bounty:fundBounty BOUNTYID AMOUNT` + +Provide funding to bounty. + +``` +USAGE + $ joystream-cli bounty:fundBounty BOUNTYID AMOUNT + ARGUMENTS BOUNTYID ID of the Bounty to veto + AMOUNT Amount to be contributed towards bounty by funder + +OPTIONS + --funderContext=(Member|Council) Actor context to execute the command in (Member/Council) +``` + +## `joystream-cli bounty:withdrawFunding BOUNTYID` + +Withdraw funding from the bounty. + +``` +USAGE + $ joystream-cli bounty:withdrawFunding BOUNTYID + +ARGUMENTS + BOUNTYID ID of the Bounty + +OPTIONS + --funderContext=(Member|Council) Actor context to execute the command in (Member/Council) +``` + +## `joystream-cli bounty:entry ENTRYID` + +Show work entry details by id. + +``` +USAGE + $ joystream-cli bounty:entry ENTRYID + +ARGUMENTS + ENTRYID ID of the Work entry +``` + +## `joystream-cli bounty:entries` + +List all existing entries. + +``` +USAGE + $ joystream-cli bounty:entries +``` + +## `joystream-cli bounty:announceWorkEntry BOUNTYID` + +Announce work entry for a bounty. + +``` +USAGE + $ joystream-cli bounty:announceWorkEntry BOUNTYID + +ARGUMENTS + BOUNTYID ID of the Bounty +``` +This command will ask for member account and staking account. + +## `joystream-cli bounty:withdrawWorkEntry BOUNTYID ENTRYID` + +Withdraw work entry from a bounty. + +``` +USAGE + $ joystream-cli bounty:withdrawWorkEntry BOUNTYID ENTRYID + +ARGUMENTS + BOUNTYID ID of the Bounty + ENTRYID ID of the Work entry +``` +This command will ask for member account. + +## `joystream-cli bounty:submitWork BOUNTYID ENTRYID` + +Submit work for a bounty. + +``` +USAGE + $ joystream-cli bounty:submitWork BOUNTYID ENTRYID + +ARGUMENTS + BOUNTYID ID of the Bounty + ENTRYID ID of the Work entry + +OPTIONS + -i, --input=input (required) Path to work data JSON file to use as input +``` +This command will ask for member account. + +## `joystream-cli bounty:submitOracleJudgment BOUNTYID` + +Submit judgment for a bounty. + +``` +USAGE + $ joystream-cli bounty:submitOracleJudgment BOUNTYID + +ARGUMENTS + BOUNTYID ID of the Bounty + +OPTIONS + -i, --input=input (required) Path to judgments data JSON file to use as input + --oracleContext=(Member|Council) Actor context to execute the command in (Member/Council) +``` + +## `joystream-cli bounty:withdrawWorkEntrantFunds BOUNTYID ENTRYID` + +Withdraw work entrant funds from a bounty. + +``` +USAGE + $ joystream-cli bounty:withdrawWorkEntrantFunds BOUNTYID ENTRYID + +ARGUMENTS + BOUNTYID ID of the Bounty + ENTRYID ID of the Work entry ``` +This command will ask for member account. ## `joystream-cli content:addCuratorToGroup [GROUPID] [CURATORID]` From 0a0fd8c1e0aac2dc198ace096b41e8770c8c1a15 Mon Sep 17 00:00:00 2001 From: Zeeshan Akram <97m.zeeshan@gmail.com> Date: Fri, 28 Jan 2022 18:15:23 +0500 Subject: [PATCH 16/16] changed OracleJudgmentInputSchema import to relative --- cli/src/commands/bounty/submitOracleJudgment.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cli/src/commands/bounty/submitOracleJudgment.ts b/cli/src/commands/bounty/submitOracleJudgment.ts index 14fd89b227..94fa87edd9 100644 --- a/cli/src/commands/bounty/submitOracleJudgment.ts +++ b/cli/src/commands/bounty/submitOracleJudgment.ts @@ -1,6 +1,6 @@ import { flags } from '@oclif/command' import chalk from 'chalk' -import { OracleJudgmentInputSchema } from 'src/json-schemas/Bounty' +import { OracleJudgmentInputSchema } from '../../json-schemas/Bounty' import BountyCommandBase from '../../base/BountyCommandBase' import ExitCodes from '../../ExitCodes' import { getInputJson } from '../../helpers/InputOutput'