diff --git a/.travis.yml b/.travis.yml index f432c484..43021d33 100644 --- a/.travis.yml +++ b/.travis.yml @@ -27,10 +27,8 @@ script: - docker-compose logs graph-node - echo -en 'travis_fold:end:script.3\\r' - - # sleep a long time to wait fo rthe graph-node to finish indexing - # until https://github.com/daostack/subgraph/issues/241 is resolved - - sleep 60 +# sleep a bit to make sure the subgraph is ready + - sleep 5 - echo -en 'travis_fold:end:script.2\\r' - npm run lint - npm run test diff --git a/package-lock.json b/package-lock.json index 5a795ac3..dd0ff2d7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "@daostack/client", - "version": "0.2.43", + "version": "0.2.44", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 6f11cf92..7a952235 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@daostack/client", - "version": "0.2.43", + "version": "0.2.44", "description": "", "keywords": [], "main": "dist/lib/index.js", diff --git a/src/index.ts b/src/index.ts index 90225f47..01381f9f 100644 --- a/src/index.ts +++ b/src/index.ts @@ -13,9 +13,14 @@ export { Reward, IRewardState, IRewardStaticState, IRewardQueryOptions } from '. export { Scheme, ISchemeState, ISchemeStaticState, ISchemeQueryOptions } from './scheme' export { ReputationFromTokenScheme } from './schemes/reputationFromToken' export { IContributionReward} from './schemes/contributionReward' -export { hasCompetitionContract, IProposalCreateOptionsCompetition, Competition, CompetitionScheme, +export { hasCompetitionContract, isCompetitionScheme, + IProposalCreateOptionsCompetition, + ICompetitionSuggestionQueryOptions, + ICompetitionVoteQueryOptions, + Competition, + CompetitionScheme, CompetitionSuggestion, CompetitionVote, - ICompetitionProposal, ICompetitionVote, ICompetitionSuggestion } from './schemes/competition' + ICompetitionProposalState, ICompetitionVoteState, ICompetitionSuggestionState } from './schemes/competition' export { IContributionRewardExt, IProposalCreateOptionsContributionRewardExt } from './schemes/contributionRewardExt' export { IGenericScheme } from './schemes/genericScheme' export { IUGenericScheme } from './schemes/uGenericScheme' diff --git a/src/proposal.ts b/src/proposal.ts index 967b3d9b..18005e10 100644 --- a/src/proposal.ts +++ b/src/proposal.ts @@ -10,7 +10,7 @@ import { Operation, toIOperationObservable } from './operation' import { IQueueState } from './queue' import { IRewardQueryOptions, Reward } from './reward' import { ISchemeState, Scheme } from './scheme' -import { ICompetitionProposal, IProposalCreateOptionsCompetition } from './schemes/competition' +import { ICompetitionProposalState, IProposalCreateOptionsCompetition } from './schemes/competition' import * as ContributionReward from './schemes/contributionReward' import * as ContributionRewardExt from './schemes/contributionRewardExt' import * as GenericScheme from './schemes/genericScheme' @@ -69,7 +69,7 @@ export interface IProposalState extends IProposalStaticState { accountsWithUnclaimedRewards: Address[], boostedAt: Date contributionReward: ContributionReward.IContributionReward|null - competition: ICompetitionProposal|null + competition: ICompetitionProposalState|null confidenceThreshold: number closingAt: Date createdAt: Date @@ -391,7 +391,7 @@ export class Proposal implements IStateful { } let contributionReward: ContributionReward.IContributionReward|null = null - let competition: ICompetitionProposal|null = null + let competition: ICompetitionProposalState|null = null let type: IProposalType let genericScheme: GenericScheme.IGenericScheme|null = null let schemeRegistrar: SchemeRegistrar.ISchemeRegistrar|null = null diff --git a/src/schemes/competition.ts b/src/schemes/competition.ts index 9dd7d8ae..1f8776bd 100644 --- a/src/schemes/competition.ts +++ b/src/schemes/competition.ts @@ -19,7 +19,7 @@ import { ISchemeState, SchemeBase } from './base' const Web3 = require('web3') -export interface ICompetitionProposal { +export interface ICompetitionProposalState { id: string contract: Address endTime: Date @@ -49,7 +49,7 @@ export interface IProposalCreateOptionsCompetition extends IProposalBaseCreateOp votingStartTime: Date, } -export interface ICompetitionSuggestion { +export interface ICompetitionSuggestionState { id: string suggestionId: number proposal: string @@ -66,9 +66,10 @@ export interface ICompetitionSuggestion { redeemedAt: Date|null rewardPercentage: number positionInWinnerList: number|null // 0 is the first, null means it is not winning + isWinner: boolean } -export interface ICompetitionVote { +export interface ICompetitionVoteState { id?: string // proposal: CompetitionProposal! // suggestion: CompetitionSuggestion! @@ -552,7 +553,9 @@ export class CompetitionSuggestion { apolloQueryOptions: IApolloQueryOptions = {} ): Observable { - const itemMap = (item: any) => this.mapItemToObject(item, context) + const itemMap = (item: any) => { + return new CompetitionSuggestion(this.mapItemToObject(item, context) as ICompetitionSuggestionState, context) + } const query = gql`query CompetitionSuggestionSearch { @@ -570,7 +573,7 @@ export class CompetitionSuggestion { ) as Observable } - private static mapItemToObject(item: any, context: Arc): ICompetitionSuggestion|null { + private static mapItemToObject(item: any, context: Arc): ICompetitionSuggestionState|null { if (item === null) { return null } @@ -583,11 +586,12 @@ export class CompetitionSuggestion { if (item.positionInWinnerList !== null) { positionInWinnerList = Number(item.positionInWinnerList) } - return { + return { createdAt: secondSinceEpochToDate(item.createdAt), description: item.description, descriptionHash: item.descriptionHash, id: item.id, + isWinner: positionInWinnerList !== null, positionInWinnerList, proposal: item.proposal.id, redeemedAt, @@ -604,9 +608,12 @@ export class CompetitionSuggestion { public id: string public suggestionId?: number - public staticState?: ICompetitionSuggestion + public staticState?: ICompetitionSuggestionState - constructor(idOrOpts: string|{ suggestionId: number, scheme: string}|ICompetitionSuggestion, public context: Arc) { + constructor( + idOrOpts: string|{ suggestionId: number, scheme: string}|ICompetitionSuggestionState, + public context: Arc + ) { if (typeof idOrOpts === 'string') { this.id = idOrOpts } else { @@ -617,22 +624,22 @@ export class CompetitionSuggestion { this.id = CompetitionSuggestion.calculateId(idOrOpts as { suggestionId: number, scheme: string}) this.suggestionId = idOrOpts.suggestionId } else { - const opts = idOrOpts as ICompetitionSuggestion + const opts = idOrOpts as ICompetitionSuggestionState this.id = opts.id this.setStaticState(opts) } } } - public setStaticState(opts: ICompetitionSuggestion) { + public setStaticState(opts: ICompetitionSuggestionState) { this.staticState = opts } - public async fetchStaticState(): Promise { + public async fetchStaticState(): Promise { return this.state({ fetchPolicy: 'cache-first'}).pipe(first()).toPromise() } - public state(apolloQueryOptions: IApolloQueryOptions = {}): Observable { + public state(apolloQueryOptions: IApolloQueryOptions = {}): Observable { const query = gql`query SchemeState { competitionSuggestion (id: "${this.id}") { @@ -674,8 +681,8 @@ export class CompetitionSuggestion { public async isWinner() { console.warn(`This method is deprecated - please use the positionInWinnerList !== from the proposal state`) - const position = await this.getPosition() - return position !== null + const suggestionState = await this.state().pipe(first()).toPromise() + return suggestionState.isWinner } public redeem(beneficiary: Address = NULL_ADDRESS): Operation { @@ -742,19 +749,19 @@ export class CompetitionVote { ) as Observable } public id?: string - public staticState?: ICompetitionVote + public staticState?: ICompetitionVoteState - constructor(idOrOpts: string|ICompetitionVote, public context: Arc) { + constructor(idOrOpts: string|ICompetitionVoteState, public context: Arc) { if (typeof idOrOpts === 'string') { this.id = idOrOpts } else { - const opts = idOrOpts as ICompetitionVote + const opts = idOrOpts as ICompetitionVoteState // this.id = opts.id this.setStaticState(opts) } } - public setStaticState(opts: ICompetitionVote) { + public setStaticState(opts: ICompetitionVoteState) { this.staticState = opts } } diff --git a/test/proposal-competition.spec.ts b/test/proposal-competition.spec.ts index 505d5929..6f5ebc72 100644 --- a/test/proposal-competition.spec.ts +++ b/test/proposal-competition.spec.ts @@ -7,8 +7,8 @@ import { CompetitionSuggestion, CompetitionVote, DAO, - ICompetitionProposal, - ICompetitionSuggestion, + ICompetitionProposalState, + ICompetitionSuggestionState, IProposalStage, IProposalState, ISchemeState, @@ -49,6 +49,16 @@ describe('Competition Proposal', () => { return result } + async function getPosition(suggestion: CompetitionSuggestion) { + const state = await suggestion.state().pipe(first()).toPromise() + return state.positionInWinnerList + } + + async function isWinner(suggestion: CompetitionSuggestion) { + const state = await suggestion.state().pipe(first()).toPromise() + return state.isWinner + } + beforeEach(async () => { // @ts-ignore // snapshotId = (await takeSnapshot()).result @@ -134,7 +144,7 @@ describe('Competition Proposal', () => { expect(lastState()).toMatchObject({ stage: IProposalStage.Queued }) - expect((lastState().competition as ICompetitionProposal).startTime).toBeDefined() + expect((lastState().competition as ICompetitionProposalState).startTime).toBeDefined() }) it('Create a competition proposal, compete, win the competition..', async () => { @@ -327,7 +337,7 @@ describe('Competition Proposal', () => { expect(balanceDelta.toString()).not.toEqual('0') }) - async function createCompetition() { + async function createCompetition(options: { rewardSplit?: number[]} = {}) { const scheme = new CompetitionScheme(contributionRewardExt.id, arc) const ethReward = new BN(10000000000) // make sure that the DAO has enough Ether to pay forthe reward @@ -342,7 +352,7 @@ describe('Competition Proposal', () => { const nativeTokenReward = new BN(0) const now = await getBlockTime(arc.web3) const startTime = addSeconds(now, 2) - const rewardSplit = [80, 20] + const rewardSplit = options.rewardSplit || [80, 20] const proposalOptions = { dao: dao.id, endTime: addSeconds(startTime, 200), @@ -410,8 +420,8 @@ describe('Competition Proposal', () => { it(`No votes is no winners`, async () => { // before any votes are cast, all suggesitons are winnners await createCompetition() - expect(await suggestion1.getPosition()).toEqual(null) - expect(await suggestion4.getPosition()).toEqual(null) + expect(await getPosition(suggestion1)).toEqual(null) + expect(await getPosition(suggestion4)).toEqual(null) // let's try to redeem await advanceTimeAndBlock(2000) expect(suggestion1.redeem().send()).rejects.toThrow('not in winners list') @@ -424,26 +434,26 @@ describe('Competition Proposal', () => { // vote and wait until it is indexed await suggestion1.vote().send() voteIsIndexed = false - suggestion1.state().subscribe((s: ICompetitionSuggestion) => { + suggestion1.state().subscribe((s: ICompetitionSuggestionState) => { voteIsIndexed = (s.positionInWinnerList !== null) }) await waitUntilTrue(() => voteIsIndexed) - expect(await suggestion1.getPosition()).toEqual(0) - expect(await suggestion4.getPosition()).toEqual(null) + expect(await getPosition(suggestion1)).toEqual(0) + expect(await getPosition(suggestion4)).toEqual(null) // vote and wait until it is indexed voteIsIndexed = false await suggestion2.vote().send() - suggestion2.state().subscribe((s: ICompetitionSuggestion) => { + suggestion2.state().subscribe((s: ICompetitionSuggestionState) => { voteIsIndexed = (s.positionInWinnerList !== null) }) await waitUntilTrue(() => voteIsIndexed) - expect(await suggestion1.getPosition()).toEqual(0) - expect(await suggestion2.getPosition()).toEqual(0) - expect(await suggestion3.getPosition()).toEqual(null) - expect(await suggestion4.getPosition()).toEqual(null) + expect(await getPosition(suggestion1)).toEqual(0) + expect(await getPosition(suggestion2)).toEqual(0) + expect(await getPosition(suggestion3)).toEqual(null) + expect(await getPosition(suggestion4)).toEqual(null) await advanceTimeAndBlock(2000) @@ -469,10 +479,10 @@ describe('Competition Proposal', () => { balanceDelta = balanceAfter.sub(balanceBefore) expect(balanceDelta.toString()).toEqual('5000000000') - expect(await suggestion1.isWinner()).toEqual(true) - expect(await suggestion2.isWinner()).toEqual(true) - expect(await suggestion3.isWinner()).toEqual(false) - expect(await suggestion4.isWinner()).toEqual(false) + expect(await isWinner(suggestion1)).toEqual(true) + expect(await isWinner(suggestion2)).toEqual(true) + expect(await isWinner(suggestion3)).toEqual(false) + expect(await isWinner(suggestion4)).toEqual(false) }) it('position is calculated correctly (2)', async () => { @@ -487,15 +497,15 @@ describe('Competition Proposal', () => { // wait until last vote is indexed let voteIsIndexed = false - suggestion2.state().subscribe((s: ICompetitionSuggestion) => { + suggestion2.state().subscribe((s: ICompetitionSuggestionState) => { voteIsIndexed = (s.positionInWinnerList !== null) }) await waitUntilTrue(() => voteIsIndexed) - expect(await suggestion1.getPosition()).toEqual(1) - expect(await suggestion2.getPosition()).toEqual(1) - expect(await suggestion3.getPosition()).toEqual(0) - expect(await suggestion4.getPosition()).toEqual(null) + expect(await getPosition(suggestion1)).toEqual(1) + expect(await getPosition(suggestion2)).toEqual(1) + expect(await getPosition(suggestion3)).toEqual(0) + expect(await getPosition(suggestion4)).toEqual(null) await advanceTimeAndBlock(2000) @@ -514,10 +524,10 @@ describe('Competition Proposal', () => { expect(suggestion4.redeem(beneficiary).send()).rejects.toThrow('not in winners list') - expect(await suggestion1.isWinner()).toEqual(true) - expect(await suggestion2.isWinner()).toEqual(true) - expect(await suggestion3.isWinner()).toEqual(true) - expect(await suggestion4.isWinner()).toEqual(false) + expect(await isWinner(suggestion1)).toEqual(true) + expect(await isWinner(suggestion2)).toEqual(true) + expect(await isWinner(suggestion3)).toEqual(true) + expect(await isWinner(suggestion4)).toEqual(false) // if we get the list of winners, it should contain exactly these 3 suggestions const winnerList = await competition.suggestions({where: {positionInWinnerList_not: null}}) @@ -528,6 +538,36 @@ describe('Competition Proposal', () => { }) + it('winner is identified correctly also if there are less actual than possible winners', async () => { + await createCompetition({ rewardSplit: [40, 40, 20]}) + await suggestion1.vote().send() + // wait until the vote is indexed + let voteIsIndexed = false + suggestion1.state().subscribe((s: ICompetitionSuggestionState) => { + voteIsIndexed = (s.positionInWinnerList !== null) + }) + await waitUntilTrue(() => voteIsIndexed) + + const suggestion1State = await suggestion1.state().pipe(first()).toPromise() + expect(suggestion1State.positionInWinnerList).toEqual(0) + expect(suggestion1State.totalVotes).not.toEqual(new BN(0)) + expect(suggestion1State.isWinner).toEqual(true) + + const suggestion2State = await suggestion2.state().pipe(first()).toPromise() + expect(suggestion2State.positionInWinnerList).toEqual(null) + expect(suggestion2State.totalVotes).toEqual(new BN(0)) + + const suggestion3State = await suggestion3.state().pipe(first()).toPromise() + expect(suggestion3State.positionInWinnerList).toEqual(null) + expect(suggestion3State.totalVotes).toEqual(new BN(0)) + expect(suggestion3State.isWinner).toEqual(false) + + const suggestion4State = await suggestion4.state().pipe(first()).toPromise() + expect(suggestion4State.positionInWinnerList).toEqual(null) + expect(suggestion4State.totalVotes).toEqual(new BN(0)) + + }) + it('CompetionScheme is recognized', async () => { // we'll get a `ContributionRewardExt` contract that has a Compietion contract as a rewarder const ARC_VERSION = '0.0.1-rc.36'