diff --git a/plasma_framework/contracts/src/framework/ExitGameController.sol b/plasma_framework/contracts/src/framework/ExitGameController.sol index d5fc1f4c2..989c03664 100644 --- a/plasma_framework/contracts/src/framework/ExitGameController.sol +++ b/plasma_framework/contracts/src/framework/ExitGameController.sol @@ -104,6 +104,7 @@ contract ExitGameController is ExitGameRegistry { * priority queue to enforce the priority of exit during 'processExits' * @dev emits ExitQueued event, which can be used to back trace the priority inside the queue * @dev Caller of this function should add "pragma experimental ABIEncoderV2;" on top of file + * @dev Priority (exitableAt, txPos, exitId) must be unique per queue. Do not enqueue when the same priority is already in the queue. * @param vaultId Vault ID of the vault that stores exiting funds * @param token Token for the exit * @param exitableAt The earliest time a specified exit can be processed @@ -133,6 +134,7 @@ contract ExitGameController is ExitGameRegistry { queue.insert(priority); bytes32 delegationKey = getDelegationKey(priority, vaultId, token); + require(address(delegations[delegationKey]) == address(0), "The same priority is already enqueued"); delegations[delegationKey] = exitProcessor; emit ExitQueued(exitId, priority); diff --git a/plasma_framework/test/src/framework/ExitGameController.test.js b/plasma_framework/test/src/framework/ExitGameController.test.js index 84db6a122..b2ad5582b 100644 --- a/plasma_framework/test/src/framework/ExitGameController.test.js +++ b/plasma_framework/test/src/framework/ExitGameController.test.js @@ -134,7 +134,7 @@ contract('ExitGameController', () => { ); }); - it('can enqueue with the same exitable timestamp (priority) multiple times', async () => { + it('rejects when the same priority is already enqueued', async () => { await this.dummyExitGame.enqueue( VAULT_ID, this.dummyExit.token, @@ -144,6 +144,23 @@ contract('ExitGameController', () => { this.dummyExit.exitProcessor, ); + await expectRevert( + this.dummyExitGame.enqueue( + VAULT_ID, + this.dummyExit.token, + this.dummyExit.exitableAt, + this.dummyExit.txPos, + this.dummyExit.exitId, + this.dummyExit.exitProcessor, + ), + 'The same priority is already enqueued', + ); + }); + + it('can enqueue with the exact same priority to different priority queue', async () => { + const vaultId2 = VAULT_ID + 1; + await this.controller.addExitQueue(vaultId2, this.dummyToken); + await this.dummyExitGame.enqueue( VAULT_ID, this.dummyExit.token, @@ -153,6 +170,47 @@ contract('ExitGameController', () => { this.dummyExit.exitProcessor, ); + await this.dummyExitGame.enqueue( + vaultId2, + this.dummyExit.token, + this.dummyExit.exitableAt, + this.dummyExit.txPos, + this.dummyExit.exitId, + this.dummyExit.exitProcessor, + ); + + const key = exitQueueKey(VAULT_ID, this.dummyToken); + const priorityQueueAddress = await this.controller.exitsQueues(key); + const priorityQueue = await PriorityQueue.at(priorityQueueAddress); + expect(await priorityQueue.currentSize()).to.be.bignumber.equal(new BN(1)); + + const key2 = exitQueueKey(vaultId2, this.dummyToken); + const priorityQueueAddress2 = await this.controller.exitsQueues(key2); + const priorityQueue2 = await PriorityQueue.at(priorityQueueAddress2); + expect(await priorityQueue2.currentSize()).to.be.bignumber.equal(new BN(1)); + }); + + it('can enqueue with the same exitable timestamp and txPos but with different exitId multiple times to the same queue', async () => { + const exitId1 = 111111; + const exitId2 = 22222; + await this.dummyExitGame.enqueue( + VAULT_ID, + this.dummyExit.token, + this.dummyExit.exitableAt, + this.dummyExit.txPos, + exitId1, + this.dummyExit.exitProcessor, + ); + + await this.dummyExitGame.enqueue( + VAULT_ID, + this.dummyExit.token, + this.dummyExit.exitableAt, + this.dummyExit.txPos, + exitId2, + this.dummyExit.exitProcessor, + ); + const key = exitQueueKey(VAULT_ID, this.dummyToken); const priorityQueueAddress = await this.controller.exitsQueues(key); const priorityQueue = await PriorityQueue.at(priorityQueueAddress);