diff --git a/src/client.ts b/src/client.ts index 674bc73b..690d3cc2 100644 --- a/src/client.ts +++ b/src/client.ts @@ -714,10 +714,31 @@ export class VocdoniSDKClient { if (!this.electionId && !electionId) { throw Error('No election set'); } + if (!censusId || !censusURI) { + throw Error('No census information set'); + } + return this.modifyElectionCensus(electionId ?? this.electionId, censusId, censusURI, maxCensusSize); + } + + /** + * Changes the census of an election. + * @category Election + * + * @param electionId - The id of the election + * @param censusId - The new census id (root) + * @param censusURI - The new census URI + * @param maxCensusSize - The new max census size + */ + private modifyElectionCensus( + electionId: string, + censusId?: string, + censusURI?: string, + maxCensusSize?: number + ): Promise { return this.fetchAccount() .then((accountData) => { const setElectionCensusTx = ElectionCore.generateSetElectionCensusTransaction( - electionId ?? this.electionId, + electionId, accountData.nonce, censusId, censusURI, @@ -741,14 +762,28 @@ export class VocdoniSDKClient { throw Error('No election set'); } - return this.fetchElection(electionId ?? this.electionId).then((election) => - this.changeElectionCensus( - electionId ?? this.electionId, - election.census.censusId, - election.census.censusURI, - maxCensusSize - ) - ); + return this.modifyElectionCensus(electionId ?? this.electionId, null, null, maxCensusSize); + } + + /** + * Changes the duration of an election. + * @category Election + * + * @param electionId - The id of the election + * @param duration - The new duration of the election + */ + public changeElectionDuration(electionId: string, duration: number): Promise { + return this.fetchAccount() + .then((accountData) => { + const setElectionCensusTx = ElectionCore.generateSetElectionDurationTransaction( + electionId, + accountData.nonce, + duration + ); + return this.electionService.signTransaction(setElectionCensusTx.tx, setElectionCensusTx.message, this.wallet); + }) + .then((signedTx) => this.chainService.submitTx(signedTx)) + .then((hash) => this.chainService.waitForTransaction(hash)); } /** diff --git a/src/core/election.ts b/src/core/election.ts index 2e55fcef..8326291a 100644 --- a/src/core/election.ts +++ b/src/core/election.ts @@ -59,7 +59,7 @@ export abstract class ElectionCore extends TransactionCore { txtype: TxType.SET_PROCESS_CENSUS, nonce: accountNonce, processId: Uint8Array.from(Buffer.from(strip0x(electionId), 'hex')), - censusRoot: Uint8Array.from(Buffer.from(strip0x(censusId), 'hex')), + censusRoot: censusId ? Uint8Array.from(Buffer.from(strip0x(censusId), 'hex')) : null, censusURI: censusURI, censusSize: maxCensusSize, }); @@ -70,6 +70,25 @@ export abstract class ElectionCore extends TransactionCore { return { tx, message }; } + public static generateSetElectionDurationTransaction( + electionId: string, + accountNonce: number, + duration: number + ): { tx: Uint8Array; message: string } { + const message = TxMessage.SET_PROCESS.replace('{type}', 'SET_PROCESS_DURATION').replace('{processId}', electionId); + const setProcess = SetProcessTx.fromPartial({ + txtype: TxType.SET_PROCESS_DURATION, + nonce: accountNonce, + processId: Uint8Array.from(Buffer.from(strip0x(electionId), 'hex')), + duration: duration, + }); + const tx = Tx.encode({ + payload: { $case: 'setProcess', setProcess }, + }).finish(); + + return { tx, message }; + } + public static generateNewElectionTransaction( election: UnpublishedElection, cid: string, diff --git a/test/integration/election.test.ts b/test/integration/election.test.ts index a82ee729..c8700db3 100644 --- a/test/integration/election.test.ts +++ b/test/integration/election.test.ts @@ -1352,9 +1352,7 @@ describe('Election integration tests', () => { census.add(await Wallet.createRandom().getAddress()); - const election = createElection(census, { - dynamicCensus: true, - }); + const election = createElection(census); await client.createAccount(); @@ -1384,4 +1382,45 @@ describe('Election integration tests', () => { }).rejects.toThrow(); }); }, 185000); + it('should change the duration of an election correctly', async () => { + const census = new PlainCensus(); + + census.add(await Wallet.createRandom().getAddress()); + + const startDateStride = 100000; + const election = createElection(census); + const startDate = new Date().getTime() + startDateStride; + const endDate = new Date().getTime() + 100000000; + election.startDate = new Date(startDate); + election.endDate = new Date(endDate); + + await client.createAccount(); + + let account, publishedElection; + + await client + .createElection(election) + .then((electionId) => { + client.setElectionId(electionId); + return waitForElectionReady(client, electionId); + }) + .then(() => client.fetchElection()) + .then(async (election) => { + publishedElection = election; + account = await client.fetchAccount(); + expect(election.startDate.toISOString()).toEqual(new Date(startDate).toISOString().split('.')[0] + '.000Z'); + return client.changeElectionDuration(election.id, startDateStride + 60 * 60); + }) + .then(() => client.fetchElection()) + .then(async (election) => { + expect(new Date(publishedElection.endDate).getTime()).toBeLessThan(new Date(election.endDate).getTime()); + expect(new Date(election.endDate).getTime()).toBeGreaterThan( + new Date(publishedElection.endDate).getTime() + startDateStride + 60 * 60 + ); + expect(account.balance).toBeGreaterThan((await client.fetchAccount()).balance); + await expect(async () => { + await client.changeElectionDuration(election.id, -(startDateStride + 60 * 60)); + }).rejects.toThrow(); + }); + }, 185000); });