diff --git a/packages/core/contracts/Escrow.sol b/packages/core/contracts/Escrow.sol index 9d4872076a..bde20acbf7 100644 --- a/packages/core/contracts/Escrow.sol +++ b/packages/core/contracts/Escrow.sol @@ -254,7 +254,11 @@ contract Escrow is IEscrow, ReentrancyGuard { */ function complete() external override notExpired adminOrReputationOracle { require( - status == EscrowStatuses.Paid || status == EscrowStatuses.Partial, + status == EscrowStatuses.Paid || + status == EscrowStatuses.Partial || + (status == EscrowStatuses.Pending && + bytes(intermediateResultsUrl).length > 0 && + reservedFunds == 0), 'Invalid status' ); _finalize(); diff --git a/packages/core/test/Escrow.ts b/packages/core/test/Escrow.ts index 2ae8fbb40e..2850264373 100644 --- a/packages/core/test/Escrow.ts +++ b/packages/core/test/Escrow.ts @@ -1394,7 +1394,7 @@ describe('Escrow', function () { await setupEscrow(); }); describe('reverts', function () { - it('reverts when status is not Paid or Partial', async function () { + it('reverts when status is not Paid or Partial or intermediate results does not exist', async function () { await expect( escrow.connect(reputationOracle).complete() ).to.be.revertedWith('Invalid status'); @@ -1413,6 +1413,13 @@ describe('Escrow', function () { escrow.connect(recordingOracle).complete() ).to.be.revertedWith('Unauthorised'); }); + + it('reverts when intermediate results exist but reserved funds is not 0', async function () { + storeResults(); + await expect( + escrow.connect(reputationOracle).complete() + ).to.be.revertedWith('Invalid status'); + }); }); describe('succeeds', function () { @@ -1472,6 +1479,48 @@ describe('Escrow', function () { initialEscrowBalance - amounts[0] ); }); + + it('Reputation oracle: completes the escrow successfully without payouts', async function () { + const initialLauncherBalance = await token.balanceOf(launcherAddress); + const initialEscrowBalance = await token.balanceOf(escrow.getAddress()); + + await storeResults(FIXTURE_URL, FIXTURE_HASH, 0n); + + await expect(escrow.connect(reputationOracle).complete()).to.emit( + escrow, + 'Completed' + ); + + expect(await escrow.status()).to.equal(Status.Complete); + expect(await escrow.remainingFunds()).to.equal('0'); + + const finalLauncherBalance = await token.balanceOf(launcherAddress); + + expect(finalLauncherBalance - initialLauncherBalance).to.equal( + initialEscrowBalance + ); + }); + + it('Admin: completes the escrow successfully without payouts', async function () { + const initialLauncherBalance = await token.balanceOf(launcherAddress); + const initialEscrowBalance = await token.balanceOf(escrow.getAddress()); + + await storeResults(FIXTURE_URL, FIXTURE_HASH, 0n); + + await expect(escrow.connect(admin).complete()).to.emit( + escrow, + 'Completed' + ); + + expect(await escrow.status()).to.equal(Status.Complete); + expect(await escrow.remainingFunds()).to.equal('0'); + + const finalLauncherBalance = await token.balanceOf(launcherAddress); + + expect(finalLauncherBalance - initialLauncherBalance).to.equal( + initialEscrowBalance + ); + }); }); describe('cancel()', () => {