diff --git a/packages/apps/job-launcher/server/package.json b/packages/apps/job-launcher/server/package.json index 11f11e9a82..d4fb740efc 100644 --- a/packages/apps/job-launcher/server/package.json +++ b/packages/apps/job-launcher/server/package.json @@ -70,7 +70,7 @@ "zxcvbn": "^4.4.2" }, "devDependencies": { - "@faker-js/faker": "^9.5.0", + "@faker-js/faker": "^9.8.0", "@golevelup/ts-jest": "^0.6.1", "@nestjs/cli": "^10.3.2", "@nestjs/schematics": "^11.0.2", diff --git a/packages/apps/reputation-oracle/server/package.json b/packages/apps/reputation-oracle/server/package.json index 2ebd5736bb..e203f501ba 100644 --- a/packages/apps/reputation-oracle/server/package.json +++ b/packages/apps/reputation-oracle/server/package.json @@ -72,7 +72,7 @@ "zxcvbn": "^4.4.2" }, "devDependencies": { - "@faker-js/faker": "^9.4.0", + "@faker-js/faker": "^9.8.0", "@golevelup/ts-jest": "^0.6.1", "@nestjs/cli": "^10.3.2", "@nestjs/schematics": "^11.0.2", diff --git a/packages/core/contracts/Escrow.sol b/packages/core/contracts/Escrow.sol index 4ef4096557..1c3866431d 100644 --- a/packages/core/contracts/Escrow.sol +++ b/packages/core/contracts/Escrow.sol @@ -8,13 +8,18 @@ import '@openzeppelin/contracts/utils/ReentrancyGuard.sol'; import './interfaces/IEscrow.sol'; +/** + * @title Escrow Contract + * @dev This contract manages the lifecycle of an escrow, including funding, + * setup, payouts, and completion. It supports trusted handlers and oracles + * for managing the escrow process. + */ contract Escrow is IEscrow, ReentrancyGuard { bytes4 private constant FUNC_SELECTOR_BALANCE_OF = bytes4(keccak256('balanceOf(address)')); string constant ERROR_ZERO_ADDRESS = 'Escrow: zero address'; - uint256 private constant BULK_MAX_VALUE = 1e9 * (10 ** 18); uint32 private constant BULK_MAX_COUNT = 100; event TrustedHandlerAdded(address _handler); @@ -73,9 +78,16 @@ contract Escrow is IEscrow, ReentrancyGuard { mapping(address => bool) public areTrustedHandlers; uint256 public remainingFunds; - uint256 public reservedFunds; + /** + * @dev Constructor to initialize the escrow contract. + * @param _token Address of the token used in the escrow. + * @param _launcher Address of the launcher (creator) of the escrow. + * @param _canceler Address of the canceler who can cancel the escrow. + * @param _duration Duration of the escrow in seconds. + * @param _handlers Array of trusted handler addresses. + */ constructor( address _token, address _launcher, @@ -97,10 +109,17 @@ contract Escrow is IEscrow, ReentrancyGuard { _addTrustedHandlers(_handlers); } + /** + * @dev Returns the balance of the escrow contract for the main token. + */ function getBalance() public view returns (uint256) { return getTokenBalance(token); } + /** + * @dev Returns the balance of the escrow contract for a specific token. + * @param _token Address of the token to check the balance for. + */ function getTokenBalance(address _token) public view returns (uint256) { (bool success, bytes memory returnData) = _token.staticcall( abi.encodeWithSelector(FUNC_SELECTOR_BALANCE_OF, address(this)) @@ -108,12 +127,20 @@ contract Escrow is IEscrow, ReentrancyGuard { return success ? abi.decode(returnData, (uint256)) : 0; } + /** + * @dev Adds trusted handlers to the contract. + * @param _handlers Array of addresses to be added as trusted handlers. + */ function addTrustedHandlers( address[] memory _handlers ) public override trusted { _addTrustedHandlers(_handlers); } + /** + * @dev Internal function to add trusted handlers. + * @param _handlers Array of addresses to be added as trusted handlers. + */ function _addTrustedHandlers(address[] memory _handlers) internal { for (uint256 i = 0; i < _handlers.length; i++) { require(_handlers[i] != address(0), ERROR_ZERO_ADDRESS); @@ -122,9 +149,17 @@ contract Escrow is IEscrow, ReentrancyGuard { } } - // The escrower puts the Token in the contract without an agentless - // and assigsn a reputation oracle to payout the bounty of size of the - // amount specified + /** + * @dev Sets up the escrow with oracles and manifest details. + * @param _reputationOracle Address of the reputation oracle. + * @param _recordingOracle Address of the recording oracle. + * @param _exchangeOracle Address of the exchange oracle. + * @param _reputationOracleFeePercentage Fee percentage for the reputation oracle. + * @param _recordingOracleFeePercentage Fee percentage for the recording oracle. + * @param _exchangeOracleFeePercentage Fee percentage for the exchange oracle. + * @param _url URL of the manifest. + * @param _hash Hash of the manifest. + */ function setup( address _reputationOracle, address _recordingOracle, @@ -182,6 +217,10 @@ contract Escrow is IEscrow, ReentrancyGuard { emit Fund(remainingFunds); } + /** + * @dev Cancels the escrow and transfers remaining funds to the canceler. + * @return bool indicating success of the cancellation. + */ function cancel() public override @@ -195,6 +234,11 @@ contract Escrow is IEscrow, ReentrancyGuard { return true; } + /** + * @dev Withdraws excess funds from the escrow for a specific token. + * @param _token Address of the token to withdraw. + * @return bool indicating success of the withdrawal. + */ function withdraw( address _token ) public override trusted nonReentrant returns (bool) { @@ -213,15 +257,18 @@ contract Escrow is IEscrow, ReentrancyGuard { return true; } + /** + * @dev Completes the escrow, transferring remaining funds to the launcher. + */ function complete() external override notExpired trustedOrReputationOracle { require( status == EscrowStatuses.Paid || status == EscrowStatuses.Partial, 'Escrow not in Paid or Partial state' ); - _complete(); + _finalize(); } - function _complete() private { + function _finalize() private { if (remainingFunds > 0) { _safeTransfer(token, launcher, remainingFunds); remainingFunds = 0; @@ -236,6 +283,11 @@ contract Escrow is IEscrow, ReentrancyGuard { } } + /** + * @dev Stores intermediate results during the escrow process. + * @param _url URL of the intermediate results. + * @param _hash Hash of the intermediate results. + */ function storeResults( string memory _url, string memory _hash, @@ -270,22 +322,13 @@ contract Escrow is IEscrow, ReentrancyGuard { } /** - * @dev Performs bulk payout to multiple workers - * Escrow needs to be completed / cancelled, so that it can be paid out. - * Every recipient is paid with the amount after reputation and recording oracle fees taken out. - * If the amount is less than the fee, the recipient is not paid. - * If the fee is zero, reputation, and recording oracle are not paid. - * Payout will fail if any of the transaction fails. - * If the escrow is fully paid out, meaning that the balance of the escrow is 0, it'll set as Paid. - * If the escrow is partially paid out, meaning that the escrow still has remaining balance, it'll set as Partial. - * This contract is only callable if the contract is not broke, not launched, not paid, not expired, by trusted parties. - * - * @param _recipients Array of recipients + * @dev Performs bulk payout to multiple recipients with oracle fees deducted. + * @param _recipients Array of recipient addresses. * @param _amounts Array of amounts to be paid to each recipient. - * @param _url URL storing results as transaction details - * @param _hash Hash of the results - * @param _txId Transaction ID - * @param forceComplete Boolean parameter indicating if remaining balance should be transferred to the escrow creator + * @param _url URL storing results as transaction details. + * @param _hash Hash of the results. + * @param _txId Transaction ID. + * @param forceComplete Boolean indicating if remaining balance should be transferred to the launcher. */ function bulkPayOut( address[] memory _recipients, @@ -314,60 +357,80 @@ contract Escrow is IEscrow, ReentrancyGuard { status != EscrowStatuses.Cancelled, 'Invalid status' ); - - uint256 aggregatedBulkAmount = 0; - for (uint256 i = 0; i < _amounts.length; i++) { - uint256 amount = _amounts[i]; - require(amount > 0, 'Amount should be greater than zero'); - aggregatedBulkAmount += amount; - } - require(aggregatedBulkAmount < BULK_MAX_VALUE, 'Bulk value too high'); require( - aggregatedBulkAmount <= reservedFunds, - 'Not enough reserved funds' + bytes(_url).length != 0 && bytes(_hash).length != 0, + 'URL or hash is empty' ); - reservedFunds -= aggregatedBulkAmount; - remainingFunds -= aggregatedBulkAmount; - - require(bytes(_url).length != 0, "URL can't be empty"); - require(bytes(_hash).length != 0, "Hash can't be empty"); + uint256 totalBulkAmount = 0; + uint256 totalReputationOracleFee = 0; + uint256 totalRecordingOracleFee = 0; + uint256 totalExchangeOracleFee = 0; - finalResultsUrl = _url; - finalResultsHash = _hash; + for (uint256 i = 0; i < _recipients.length; i++) { + uint256 amount = _amounts[i]; + require(amount > 0, 'Amount should be greater than zero'); + totalBulkAmount += amount; + totalReputationOracleFee += + (reputationOracleFeePercentage * amount) / + 100; + totalRecordingOracleFee += + (recordingOracleFeePercentage * amount) / + 100; + totalExchangeOracleFee += + (exchangeOracleFeePercentage * amount) / + 100; + } + require(totalBulkAmount <= reservedFunds, 'Not enough reserved funds'); - uint256 totalFeePercentage = reputationOracleFeePercentage + - recordingOracleFeePercentage + - exchangeOracleFeePercentage; + uint256 paidReputation = 0; + uint256 paidRecording = 0; + uint256 paidExchange = 0; for (uint256 i = 0; i < _recipients.length; i++) { uint256 amount = _amounts[i]; - uint256 amountFee = (totalFeePercentage * amount) / 100; - _safeTransfer(token, _recipients[i], amount - amountFee); - } + uint256 reputationOracleFee = (reputationOracleFeePercentage * + amount) / 100; + uint256 recordingOracleFee = (recordingOracleFeePercentage * + amount) / 100; + uint256 exchangeOracleFee = (exchangeOracleFeePercentage * amount) / + 100; + + if (i == _recipients.length - 1) { + reputationOracleFee = totalReputationOracleFee - paidReputation; + recordingOracleFee = totalRecordingOracleFee - paidRecording; + exchangeOracleFee = totalExchangeOracleFee - paidExchange; + } + + paidReputation += reputationOracleFee; + paidRecording += recordingOracleFee; + paidExchange += exchangeOracleFee; - // Transfer oracle fees - if (reputationOracleFeePercentage > 0) { _safeTransfer( token, - reputationOracle, - (reputationOracleFeePercentage * aggregatedBulkAmount) / 100 + _recipients[i], + amount - + reputationOracleFee - + recordingOracleFee - + exchangeOracleFee ); } + + // Transfer oracle fees + if (reputationOracleFeePercentage > 0) { + _safeTransfer(token, reputationOracle, totalReputationOracleFee); + } if (recordingOracleFeePercentage > 0) { - _safeTransfer( - token, - recordingOracle, - (recordingOracleFeePercentage * aggregatedBulkAmount) / 100 - ); + _safeTransfer(token, recordingOracle, totalRecordingOracleFee); } if (exchangeOracleFeePercentage > 0) { - _safeTransfer( - token, - exchangeOracle, - (exchangeOracleFeePercentage * aggregatedBulkAmount) / 100 - ); + _safeTransfer(token, exchangeOracle, totalExchangeOracleFee); } + remainingFunds -= totalBulkAmount; + reservedFunds -= totalBulkAmount; + + finalResultsUrl = _url; + finalResultsHash = _hash; if (remainingFunds == 0 || forceComplete) { emit BulkTransferV2( @@ -377,7 +440,7 @@ contract Escrow is IEscrow, ReentrancyGuard { false, finalResultsUrl ); - _complete(); + _finalize(); } else { if (status != EscrowStatuses.ToCancel) { status = EscrowStatuses.Partial; @@ -394,13 +457,11 @@ contract Escrow is IEscrow, ReentrancyGuard { /** * @dev Overloaded function to perform bulk payout with default forceComplete set to false. - * Calls the main bulkPayout function with forceComplete as false. - * - * @param _recipients Array of recipients + * @param _recipients Array of recipient addresses. * @param _amounts Array of amounts to be paid to each recipient. - * @param _url URL storing results as transaction details - * @param _hash Hash of the results - * @param _txId Transaction ID + * @param _url URL storing results as transaction details. + * @param _hash Hash of the results. + * @param _txId Transaction ID. */ function bulkPayOut( address[] memory _recipients, @@ -412,6 +473,12 @@ contract Escrow is IEscrow, ReentrancyGuard { bulkPayOut(_recipients, _amounts, _url, _hash, _txId, false); } + /** + * @dev Internal function to safely transfer tokens. + * @param _token Address of the token to transfer. + * @param to Address of the recipient. + * @param value Amount to transfer. + */ function _safeTransfer(address _token, address to, uint256 value) internal { SafeERC20.safeTransfer(IERC20(_token), to, value); } diff --git a/packages/core/package.json b/packages/core/package.json index 1d41ca699f..eff69bc410 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -51,6 +51,7 @@ ], "license": "MIT", "devDependencies": { + "@faker-js/faker": "^9.8.0", "@nomicfoundation/hardhat-chai-matchers": "^2.0.7", "@nomicfoundation/hardhat-ethers": "^3.0.5", "@nomicfoundation/hardhat-network-helpers": "^1.0.12", diff --git a/packages/core/test/Escrow-USDT.ts b/packages/core/test/Escrow-USDT.ts index 35a3ecd39a..7c3fefa4f4 100644 --- a/packages/core/test/Escrow-USDT.ts +++ b/packages/core/test/Escrow-USDT.ts @@ -592,7 +592,7 @@ describe('Escrow with USDT', function () { }); it('Should revert with the right error if address calling is not trusted', async function () { - const recepients = [await restAccounts[0].getAddress()]; + const recipients = [await restAccounts[0].getAddress()]; const amounts = [10]; await expect( @@ -600,12 +600,12 @@ describe('Escrow with USDT', function () { .connect(externalAddress) [ 'bulkPayOut(address[],uint256[],string,string,uint256)' - ](recepients, amounts, MOCK_URL, MOCK_HASH, '000') + ](recipients, amounts, MOCK_URL, MOCK_HASH, '000') ).to.be.revertedWith('Address calling not trusted'); }); it('Should revert with the right error if address calling is recording oracle', async function () { - const recepients = [await restAccounts[0].getAddress()]; + const recipients = [await restAccounts[0].getAddress()]; const amounts = [10]; await expect( @@ -613,12 +613,12 @@ describe('Escrow with USDT', function () { .connect(recordingOracle) [ 'bulkPayOut(address[],uint256[],string,string,uint256)' - ](recepients, amounts, MOCK_URL, MOCK_HASH, '000') + ](recipients, amounts, MOCK_URL, MOCK_HASH, '000') ).to.be.revertedWith('Address calling not trusted'); }); it('Should revert with the right error if amount of recipients more then amount of values', async function () { - const recepients = [ + const recipients = [ await restAccounts[0].getAddress(), await restAccounts[1].getAddress(), await restAccounts[2].getAddress(), @@ -630,12 +630,12 @@ describe('Escrow with USDT', function () { .connect(reputationOracle) [ 'bulkPayOut(address[],uint256[],string,string,uint256)' - ](recepients, amounts, MOCK_URL, MOCK_HASH, '000') + ](recipients, amounts, MOCK_URL, MOCK_HASH, '000') ).to.be.revertedWith("Amount of recipients and values don't match"); }); it('Should revert with the right error if amount of recipients less then amount of values', async function () { - const recepients = [ + const recipients = [ await restAccounts[0].getAddress(), await restAccounts[1].getAddress(), ]; @@ -646,12 +646,12 @@ describe('Escrow with USDT', function () { .connect(reputationOracle) [ 'bulkPayOut(address[],uint256[],string,string,uint256)' - ](recepients, amounts, MOCK_URL, MOCK_HASH, '000') + ](recipients, amounts, MOCK_URL, MOCK_HASH, '000') ).to.be.revertedWith("Amount of recipients and values don't match"); }); it('Should revert with the right error if too many recipients', async function () { - const recepients = Array.from( + const recipients = Array.from( new Array(BULK_MAX_COUNT + 1), () => ethers.ZeroAddress ); @@ -662,7 +662,7 @@ describe('Escrow with USDT', function () { .connect(reputationOracle) [ 'bulkPayOut(address[],uint256[],string,string,uint256)' - ](recepients, amounts, MOCK_URL, MOCK_HASH, '000') + ](recipients, amounts, MOCK_URL, MOCK_HASH, '000') ).to.be.revertedWith('Too many recipients'); }); @@ -693,18 +693,18 @@ describe('Escrow with USDT', function () { }); it('Should emit bulkPayOut and Completed events for complete bulkPayOut', async function () { - const recepients = [await restAccounts[0].getAddress()]; + const recipients = [await restAccounts[0].getAddress()]; const amounts = [100]; const tx = await escrow .connect(owner) [ 'bulkPayOut(address[],uint256[],string,string,uint256)' - ](recepients, amounts, MOCK_URL, MOCK_HASH, '000'); + ](recipients, amounts, MOCK_URL, MOCK_HASH, '000'); await expect(tx) .to.emit(escrow, 'BulkTransferV2') - .withArgs(anyValue, recepients, [100], false, MOCK_URL); + .withArgs(anyValue, recipients, [100], false, MOCK_URL); await expect(tx).to.emit(escrow, 'Completed'); }); @@ -729,35 +729,35 @@ describe('Escrow with USDT', function () { }); it('Should emit only bulkPayOut event for partial bulkPayOut', async function () { - const recepients = [await restAccounts[0].getAddress()]; + const recipients = [await restAccounts[0].getAddress()]; const amounts = [10]; const tx = await escrow .connect(owner) [ 'bulkPayOut(address[],uint256[],string,string,uint256)' - ](recepients, amounts, MOCK_URL, MOCK_HASH, '000'); + ](recipients, amounts, MOCK_URL, MOCK_HASH, '000'); await expect(tx) .to.emit(escrow, 'BulkTransferV2') - .withArgs(anyValue, recepients, [10], true, MOCK_URL); + .withArgs(anyValue, recipients, [10], true, MOCK_URL); await expect(tx).not.to.emit(escrow, 'Completed'); }); it('Should emit bulkPayOut and Completed events for partial bulkPayOut with forceComplete option', async function () { - const recepients = [await restAccounts[0].getAddress()]; + const recipients = [await restAccounts[0].getAddress()]; const amounts = [10]; const tx = await escrow .connect(owner) [ 'bulkPayOut(address[],uint256[],string,string,uint256,bool)' - ](recepients, amounts, MOCK_URL, MOCK_HASH, '000', true); + ](recipients, amounts, MOCK_URL, MOCK_HASH, '000', true); await expect(tx) .to.emit(escrow, 'BulkTransferV2') - .withArgs(anyValue, recepients, [10], false, MOCK_URL); + .withArgs(anyValue, recipients, [10], false, MOCK_URL); await expect(tx).to.emit(escrow, 'Completed'); }); @@ -814,14 +814,14 @@ describe('Escrow with USDT', function () { .connect(owner) .balanceOf(await exchangeOracle.getAddress()); - const recepients = [account1, account2, account3]; + const recipients = [account1, account2, account3]; const amounts = [10, 20, 30]; await escrow .connect(reputationOracle) [ 'bulkPayOut(address[],uint256[],string,string,uint256)' - ](recepients, amounts, MOCK_URL, MOCK_HASH, '000'); + ](recipients, amounts, MOCK_URL, MOCK_HASH, '000'); const finalBalanceAccount1 = await usdt .connect(owner) @@ -896,14 +896,14 @@ describe('Escrow with USDT', function () { .connect(owner) .balanceOf(await exchangeOracle.getAddress()); - const recepients = [account1, account2, account3]; + const recipients = [account1, account2, account3]; const amounts = [10, 20, 30]; await escrow .connect(reputationOracle) [ 'bulkPayOut(address[],uint256[],string,string,uint256,bool)' - ](recepients, amounts, MOCK_URL, MOCK_HASH, '000', true); + ](recipients, amounts, MOCK_URL, MOCK_HASH, '000', true); const finalBalanceAccount1 = await usdt .connect(owner) @@ -958,7 +958,7 @@ describe('Escrow with USDT', function () { }); it('Should runs from setup to bulkPayOut to complete correctly', async () => { - const recepients = [await restAccounts[3].getAddress()]; + const recipients = [await restAccounts[3].getAddress()]; const amounts = [100]; expect(await escrow.status()).to.equal(Status.Pending); @@ -967,12 +967,12 @@ describe('Escrow with USDT', function () { .connect(reputationOracle) [ 'bulkPayOut(address[],uint256[],string,string,uint256)' - ](recepients, amounts, MOCK_URL, MOCK_HASH, '000'); + ](recipients, amounts, MOCK_URL, MOCK_HASH, '000'); expect(await escrow.status()).to.equal(Status.Complete); }); it('Should runs from setup to bulkPayOut to complete correctly with multiple addresses', async () => { - const recepients = [ + const recipients = [ await restAccounts[3].getAddress(), await restAccounts[4].getAddress(), await restAccounts[5].getAddress(), @@ -985,7 +985,7 @@ describe('Escrow with USDT', function () { .connect(reputationOracle) [ 'bulkPayOut(address[],uint256[],string,string,uint256)' - ](recepients, amounts, MOCK_URL, MOCK_HASH, '000'); + ](recipients, amounts, MOCK_URL, MOCK_HASH, '000'); expect(await escrow.status()).to.equal(Status.Complete); }); @@ -1010,7 +1010,7 @@ describe('Escrow with USDT', function () { }); it('Should runs from setup to bulkPayOut to partial correctly', async () => { - const recepients = [await restAccounts[3].getAddress()]; + const recipients = [await restAccounts[3].getAddress()]; const amounts = [80]; expect(await escrow.status()).to.equal(Status.Pending); @@ -1019,7 +1019,7 @@ describe('Escrow with USDT', function () { .connect(reputationOracle) [ 'bulkPayOut(address[],uint256[],string,string,uint256)' - ](recepients, amounts, MOCK_URL, MOCK_HASH, '000'); + ](recipients, amounts, MOCK_URL, MOCK_HASH, '000'); expect(await escrow.status()).to.equal(Status.Partial); }); @@ -1040,7 +1040,7 @@ describe('Escrow with USDT', function () { }); it('Should runs from setup to bulkPayOut to partial correctly with multiple addresses', async () => { - const recepients = [ + const recipients = [ await restAccounts[3].getAddress(), await restAccounts[4].getAddress(), await restAccounts[5].getAddress(), @@ -1053,7 +1053,7 @@ describe('Escrow with USDT', function () { .connect(reputationOracle) [ 'bulkPayOut(address[],uint256[],string,string,uint256)' - ](recepients, amounts, MOCK_URL, MOCK_HASH, '000'); + ](recipients, amounts, MOCK_URL, MOCK_HASH, '000'); expect(await escrow.status()).to.equal(Status.Partial); }); }); diff --git a/packages/core/test/Escrow.ts b/packages/core/test/Escrow.ts index 4ddbf8ed8e..1a65033cb1 100644 --- a/packages/core/test/Escrow.ts +++ b/packages/core/test/Escrow.ts @@ -4,9 +4,10 @@ import { expect } from 'chai'; import { ethers } from 'hardhat'; import { EventLog, Signer } from 'ethers'; import { Escrow, HMToken } from '../typechain-types'; +import { faker } from '@faker-js/faker'; -const MOCK_URL = 'http://google.com/fake'; -const MOCK_HASH = 'kGKmnj9BRf'; +const MOCK_URL = faker.internet.url(); +const MOCK_HASH = faker.string.alphanumeric(10); const BULK_MAX_COUNT = 100; enum Status { @@ -51,27 +52,30 @@ async function setupEscrow() { await reputationOracle.getAddress(), await recordingOracle.getAddress(), await exchangeOracle.getAddress(), - 10, - 10, - 10, + 3, + 3, + 3, MOCK_URL, MOCK_HASH ); } -async function fundEscrow() { - const amount = 100; +async function fundEscrow(): Promise { + const amount = ethers.parseEther( + faker.number.int({ min: 50, max: 200 }).toString() + ); await token.connect(owner).transfer(escrow.getAddress(), amount); + return amount; } -async function storeResults(amount = 50) { +async function storeResults(amount: bigint) { await escrow .connect(restAccounts[0]) .storeResults(MOCK_URL, MOCK_HASH, amount); } describe('Escrow', function () { - this.beforeAll(async () => { + before(async () => { [ owner, launcher, @@ -127,7 +131,9 @@ describe('Escrow', function () { }); it('Should topup and return the right escrow balance', async () => { - const amount = 1000; + const amount = ethers.parseEther( + faker.number.int({ min: 500, max: 2000 }).toString() + ); await token.connect(owner).transfer(escrow.getAddress(), amount); const result = await escrow.connect(launcher).getBalance(); @@ -230,7 +236,7 @@ describe('Escrow', function () { }); it('Should revert with the right error if amount is higher than unreserved funds', async function () { - await fundEscrow(); + const fundAmount = await fundEscrow(); await setupEscrow(); await escrow @@ -239,7 +245,7 @@ describe('Escrow', function () { await expect( escrow .connect(reputationOracle) - .storeResults(MOCK_URL, MOCK_HASH, 150) + .storeResults(MOCK_URL, MOCK_HASH, fundAmount * 2n) ).to.be.revertedWith('Not enough unreserved funds'); }); }); @@ -261,9 +267,10 @@ describe('Escrow', function () { }); describe('Store results', async function () { + let fundAmount: bigint; beforeEach(async () => { await deployEscrow(); - await fundEscrow(); + fundAmount = await fundEscrow(); await setupEscrow(); }); @@ -271,10 +278,11 @@ describe('Escrow', function () { const initialOwnerBalance = await token .connect(owner) .balanceOf(launcher.getAddress()); + const result = await ( await escrow .connect(recordingOracle) - .storeResults(MOCK_URL, MOCK_HASH, 50) + .storeResults(MOCK_URL, MOCK_HASH, fundAmount / 2n) ).wait(); const finalOwnerBalance = await token @@ -283,8 +291,8 @@ describe('Escrow', function () { expect((result?.logs[0] as EventLog).args).to.contain(MOCK_URL); expect((result?.logs[0] as EventLog).args).to.contain(MOCK_HASH); expect(finalOwnerBalance - initialOwnerBalance).to.equal(0); - expect(await escrow.remainingFunds()).to.equal(100); - expect(await escrow.reservedFunds()).to.equal(50); + expect(await escrow.remainingFunds()).to.equal(fundAmount); + expect(await escrow.reservedFunds()).to.equal(fundAmount / 2n); }); it('Should succeed when a trusted handler stores results', async () => { @@ -294,7 +302,7 @@ describe('Escrow', function () { const result = await ( await escrow .connect(trustedHandlers[0]) - .storeResults(MOCK_URL, MOCK_HASH, 50) + .storeResults(MOCK_URL, MOCK_HASH, fundAmount / 2n) ).wait(); const finalOwnerBalance = await token @@ -303,8 +311,8 @@ describe('Escrow', function () { expect((result?.logs[0] as EventLog).args).to.contain(MOCK_URL); expect((result?.logs[0] as EventLog).args).to.contain(MOCK_HASH); expect(finalOwnerBalance - initialOwnerBalance).to.equal(0); - expect(await escrow.remainingFunds()).to.equal(100); - expect(await escrow.reservedFunds()).to.equal(50); + expect(await escrow.remainingFunds()).to.equal(fundAmount); + expect(await escrow.reservedFunds()).to.equal(fundAmount / 2n); }); it('Should return unreserved funds to escrow launcher when status is ToCancel', async () => { @@ -315,7 +323,7 @@ describe('Escrow', function () { const result = await ( await escrow .connect(recordingOracle) - .storeResults(MOCK_URL, MOCK_HASH, 50) + .storeResults(MOCK_URL, MOCK_HASH, fundAmount / 2n) ).wait(); expect((result?.logs[0] as EventLog).args).to.contain(MOCK_URL); @@ -323,9 +331,11 @@ describe('Escrow', function () { const finalOwnerBalance = await token .connect(owner) .balanceOf(launcher.getAddress()); - expect(finalOwnerBalance - initialOwnerBalance).to.equal(50); - expect(await escrow.remainingFunds()).to.equal(50); - expect(await escrow.reservedFunds()).to.equal(50); + expect(finalOwnerBalance - initialOwnerBalance).to.equal( + fundAmount / 2n + ); + expect(await escrow.remainingFunds()).to.equal(fundAmount / 2n); + expect(await escrow.reservedFunds()).to.equal(fundAmount / 2n); }); }); }); @@ -423,9 +433,10 @@ describe('Escrow', function () { }); describe('Events', function () { + let fundAmount: bigint; before(async () => { await deployEscrow(); - await fundEscrow(); + fundAmount = await fundEscrow(); }); it('Should emit an event on pending', async function () { @@ -452,7 +463,7 @@ describe('Escrow', function () { await exchangeOracle.getAddress() ) .to.emit(escrow, 'Fund') - .withArgs(100); + .withArgs(fundAmount); }); }); @@ -524,15 +535,15 @@ describe('Escrow', function () { describe('Validations', function () { before(async () => { await deployEscrow(); - await fundEscrow(); + const fundAmount = await fundEscrow(); await setupEscrow(); - await storeResults(100); + await storeResults(fundAmount); await escrow .connect(owner) [ 'bulkPayOut(address[],uint256[],string,string,uint256)' - ]([await restAccounts[0].getAddress()], [100], MOCK_URL, MOCK_HASH, '000'); + ]([await restAccounts[0].getAddress()], [fundAmount], MOCK_URL, MOCK_HASH, '000'); }); it('Should revert with the right error if address calling not trusted', async function () { @@ -589,74 +600,86 @@ describe('Escrow', function () { describe('bulkPayOut', () => { describe('Validations', function () { + let fundAmount: bigint; before(async () => { await deployEscrow(); - await fundEscrow(); + fundAmount = await fundEscrow(); await setupEscrow(); - await storeResults(); + await storeResults(fundAmount); }); it('Should revert with the right error if address calling is not trusted', async function () { - const recepients = [await restAccounts[0].getAddress()]; - const amounts = [10]; + const recipients = [await restAccounts[0].getAddress()]; + const amounts = [ + fundAmount / 4n, //1/4 + ]; await expect( escrow .connect(externalAddress) [ 'bulkPayOut(address[],uint256[],string,string,uint256)' - ](recepients, amounts, MOCK_URL, MOCK_HASH, '000') + ](recipients, amounts, MOCK_URL, MOCK_HASH, '000') ).to.be.revertedWith('Address calling not trusted'); }); it('Should revert with the right error if address calling is recording oracle', async function () { - const recepients = [await restAccounts[0].getAddress()]; - const amounts = [10]; + const recipients = [await restAccounts[0].getAddress()]; + const amounts = [ + fundAmount / 4n, //1/4 + ]; await expect( escrow .connect(recordingOracle) [ 'bulkPayOut(address[],uint256[],string,string,uint256)' - ](recepients, amounts, MOCK_URL, MOCK_HASH, '000') + ](recipients, amounts, MOCK_URL, MOCK_HASH, '000') ).to.be.revertedWith('Address calling not trusted'); }); it('Should revert with the right error if amount of recipients more then amount of values', async function () { - const recepients = [ + const recipients = [ await restAccounts[0].getAddress(), await restAccounts[1].getAddress(), await restAccounts[2].getAddress(), ]; - const amounts = [10, 20]; + const amounts = [ + fundAmount / 4n, //1/4 + fundAmount / 2n, //1/2 + ]; await expect( escrow .connect(reputationOracle) [ 'bulkPayOut(address[],uint256[],string,string,uint256)' - ](recepients, amounts, MOCK_URL, MOCK_HASH, '000') + ](recipients, amounts, MOCK_URL, MOCK_HASH, '000') ).to.be.revertedWith("Amount of recipients and values don't match"); }); it('Should revert with the right error if amount of recipients less then amount of values', async function () { - const recepients = [ + const recipients = [ await restAccounts[0].getAddress(), await restAccounts[1].getAddress(), ]; - const amounts = [10, 20, 30]; + const amounts = [ + fundAmount / 4n, //1/4 + fundAmount / 2n, //1/2 + fundAmount / 4n, //1/4 + ]; await expect( escrow .connect(reputationOracle) [ 'bulkPayOut(address[],uint256[],string,string,uint256)' - ](recepients, amounts, MOCK_URL, MOCK_HASH, '000') + ](recipients, amounts, MOCK_URL, MOCK_HASH, '000') ).to.be.revertedWith("Amount of recipients and values don't match"); }); it('Should revert with the right error if too many recipients', async function () { - const recepients = Array.from( + const recipients = Array.from( new Array(BULK_MAX_COUNT + 1), () => ethers.ZeroAddress ); @@ -667,56 +690,61 @@ describe('Escrow', function () { .connect(reputationOracle) [ 'bulkPayOut(address[],uint256[],string,string,uint256)' - ](recepients, amounts, MOCK_URL, MOCK_HASH, '000') + ](recipients, amounts, MOCK_URL, MOCK_HASH, '000') ).to.be.revertedWith('Too many recipients'); }); it('Should revert with the right error if trying to payout more than reservedFunds', async function () { - const recepients = [ + const recipients = [ await restAccounts[0].getAddress(), await restAccounts[1].getAddress(), await restAccounts[2].getAddress(), ]; - const amounts = [10, 20, 30]; + const amounts = [ + fundAmount / 4n, //1/4 + fundAmount / 2n, //1/2 + fundAmount / 2n, //1/2 + ]; await expect( escrow .connect(reputationOracle) [ 'bulkPayOut(address[],uint256[],string,string,uint256)' - ](recepients, amounts, MOCK_URL, MOCK_HASH, '000') + ](recipients, amounts, MOCK_URL, MOCK_HASH, '000') ).to.be.revertedWith('Not enough reserved funds'); }); }); describe('Events', function () { + let fundAmount: bigint; beforeEach(async () => { await deployEscrow(); - await fundEscrow(); + fundAmount = await fundEscrow(); await setupEscrow(); - await storeResults(100); + await storeResults(fundAmount); }); it('Should emit bulkPayOut and Completed events for complete bulkPayOut', async function () { - const recepients = [await restAccounts[0].getAddress()]; - const amounts = [100]; + const recipients = [await restAccounts[0].getAddress()]; + const amounts = [fundAmount]; const tx = await escrow .connect(owner) [ 'bulkPayOut(address[],uint256[],string,string,uint256)' - ](recepients, amounts, MOCK_URL, MOCK_HASH, '000'); + ](recipients, amounts, MOCK_URL, MOCK_HASH, '000'); await expect(tx) .to.emit(escrow, 'BulkTransferV2') - .withArgs(anyValue, recepients, [100], false, MOCK_URL); + .withArgs(anyValue, recipients, [fundAmount], false, MOCK_URL); await expect(tx).to.emit(escrow, 'Completed'); }); it('Should emit bulkPayOut and Cancelled events for complete bulkPayOut with ToCancel status', async function () { - const recepients = [await restAccounts[0].getAddress()]; - const amounts = [100]; + const recipients = [await restAccounts[0].getAddress()]; + const amounts = [fundAmount]; await escrow.connect(owner).cancel(); @@ -724,52 +752,58 @@ describe('Escrow', function () { .connect(owner) [ 'bulkPayOut(address[],uint256[],string,string,uint256)' - ](recepients, amounts, MOCK_URL, MOCK_HASH, '000'); + ](recipients, amounts, MOCK_URL, MOCK_HASH, '000'); await expect(tx) .to.emit(escrow, 'BulkTransferV2') - .withArgs(anyValue, recepients, [100], false, MOCK_URL); + .withArgs(anyValue, recipients, [fundAmount], false, MOCK_URL); await expect(tx).to.emit(escrow, 'Cancelled'); }); it('Should emit only bulkPayOut event for partial bulkPayOut', async function () { - const recepients = [await restAccounts[0].getAddress()]; - const amounts = [10]; + const recipients = [await restAccounts[0].getAddress()]; + const amounts = [ + fundAmount / 4n, //1/4 + ]; const tx = await escrow .connect(owner) [ 'bulkPayOut(address[],uint256[],string,string,uint256)' - ](recepients, amounts, MOCK_URL, MOCK_HASH, '000'); + ](recipients, amounts, MOCK_URL, MOCK_HASH, '000'); await expect(tx) .to.emit(escrow, 'BulkTransferV2') - .withArgs(anyValue, recepients, [10], true, MOCK_URL); + .withArgs(anyValue, recipients, [fundAmount / 4n], true, MOCK_URL); await expect(tx).not.to.emit(escrow, 'Completed'); }); it('Should emit bulkPayOut and Completed events for partial bulkPayOut with forceComplete option', async function () { - const recepients = [await restAccounts[0].getAddress()]; - const amounts = [10]; + const recipients = [await restAccounts[0].getAddress()]; + const amounts = [ + fundAmount / 4n, //1/4 + ]; const tx = await escrow .connect(owner) [ 'bulkPayOut(address[],uint256[],string,string,uint256,bool)' - ](recepients, amounts, MOCK_URL, MOCK_HASH, '000', true); + ](recipients, amounts, MOCK_URL, MOCK_HASH, '000', true); await expect(tx) .to.emit(escrow, 'BulkTransferV2') - .withArgs(anyValue, recepients, [10], false, MOCK_URL); + .withArgs(anyValue, recipients, [fundAmount / 4n], false, MOCK_URL); await expect(tx).to.emit(escrow, 'Completed'); }); it('Should emit bulkPayOut and Cancelled events for partial bulkPayOut with forceComplete option and ToCancel status', async function () { - const recepients = [await restAccounts[0].getAddress()]; - const amounts = [10]; + const recipients = [await restAccounts[0].getAddress()]; + const amounts = [ + fundAmount / 4n, //1/4 + ]; await escrow.connect(owner).cancel(); @@ -777,212 +811,173 @@ describe('Escrow', function () { .connect(owner) [ 'bulkPayOut(address[],uint256[],string,string,uint256,bool)' - ](recepients, amounts, MOCK_URL, MOCK_HASH, '000', true); + ](recipients, amounts, MOCK_URL, MOCK_HASH, '000', true); await expect(tx) .to.emit(escrow, 'BulkTransferV2') - .withArgs(anyValue, recepients, [10], false, MOCK_URL); + .withArgs(anyValue, recipients, [fundAmount / 4n], false, MOCK_URL); await expect(tx).to.emit(escrow, 'Cancelled'); }); }); describe('Bulk payout for recipients', async function () { + let fundAmount: bigint; beforeEach(async () => { await deployEscrow(); - await fundEscrow(); + fundAmount = await fundEscrow(); await setupEscrow(); - await storeResults(100); + await storeResults(fundAmount); }); - it('Should pays each recipient their corresponding amount', async () => { - const account1 = await restAccounts[0].getAddress(); - const account2 = await restAccounts[1].getAddress(); - const account3 = await restAccounts[2].getAddress(); + it('Should pay each recipient their corresponding amount', async () => { + const recipients = await Promise.all( + restAccounts.slice(0, 3).map(async (account) => account.getAddress()) + ); - const initialBalanceAccount1 = await token - .connect(owner) - .balanceOf(account1); - const initialBalanceAccount2 = await token - .connect(owner) - .balanceOf(account2); - const initialBalanceAccount3 = await token - .connect(owner) - .balanceOf(account3); - const initialBalanceRecordingOracle = await token - .connect(owner) - .balanceOf(await recordingOracle.getAddress()); - const initialBalanceReputationOracle = await token - .connect(owner) - .balanceOf(await reputationOracle.getAddress()); - const initialBalanceExchangeOracle = await token - .connect(owner) - .balanceOf(await exchangeOracle.getAddress()); + const initialBalances = await Promise.all( + recipients.map(async (account) => + token.connect(owner).balanceOf(account) + ) + ); - const recepients = [account1, account2, account3]; - const amounts = [10, 20, 30]; + const initialOracleBalances = await Promise.all( + [recordingOracle, reputationOracle, exchangeOracle].map( + async (oracle) => + token.connect(owner).balanceOf(await oracle.getAddress()) + ) + ); + + const amounts = recipients.map(() => + ethers + .parseEther(faker.number.int({ min: 1, max: 20 }).toString()) + .toString() + ); await escrow .connect(reputationOracle) [ 'bulkPayOut(address[],uint256[],string,string,uint256)' - ](recepients, amounts, MOCK_URL, MOCK_HASH, '000'); + ](recipients, amounts, faker.internet.url(), faker.string.alphanumeric(10), faker.string.numeric(3)); - const finalBalanceAccount1 = await token - .connect(owner) - .balanceOf(account1); - const finalBalanceAccount2 = await token - .connect(owner) - .balanceOf(account2); - const finalBalanceAccount3 = await token - .connect(owner) - .balanceOf(account3); - const finalBalanceRecordingOracle = await token - .connect(owner) - .balanceOf(await recordingOracle.getAddress()); - const finalBalanceReputationOracle = await token - .connect(owner) - .balanceOf(await reputationOracle.getAddress()); - const finalBalanceExchangeOracle = await token - .connect(owner) - .balanceOf(await exchangeOracle.getAddress()); - - expect( - (finalBalanceAccount1 - initialBalanceAccount1).toString() - ).to.equal('7'); - expect( - (finalBalanceAccount2 - initialBalanceAccount2).toString() - ).to.equal('14'); - expect( - (finalBalanceAccount3 - initialBalanceAccount3).toString() - ).to.equal('21'); - expect( - ( - finalBalanceRecordingOracle - initialBalanceRecordingOracle - ).toString() - ).to.equal('6'); - expect( - ( - finalBalanceReputationOracle - initialBalanceReputationOracle - ).toString() - ).to.equal('6'); - - expect( - (finalBalanceExchangeOracle - initialBalanceExchangeOracle).toString() - ).to.equal('6'); - - expect(await escrow.remainingFunds()).to.equal('40'); - }); - - it('Should pays each recipient their corresponding amount and return the remaining to launcher with force option', async () => { - const account1 = await restAccounts[0].getAddress(); - const account2 = await restAccounts[1].getAddress(); - const account3 = await restAccounts[2].getAddress(); - - const initialBalanceAccount1 = await token - .connect(owner) - .balanceOf(account1); - const initialBalanceAccount2 = await token - .connect(owner) - .balanceOf(account2); - const initialBalanceAccount3 = await token - .connect(owner) - .balanceOf(account3); - const initialBalanceLauncher = await token - .connect(owner) - .balanceOf(await launcher.getAddress()); - const initialBalanceRecordingOracle = await token - .connect(owner) - .balanceOf(await recordingOracle.getAddress()); - const initialBalanceReputationOracle = await token - .connect(owner) - .balanceOf(await reputationOracle.getAddress()); - const initialBalanceExchangeOracle = await token - .connect(owner) - .balanceOf(await exchangeOracle.getAddress()); + const finalBalances = await Promise.all( + recipients.map(async (account) => + token.connect(owner).balanceOf(account) + ) + ); - const recepients = [account1, account2, account3]; - const amounts = [10, 20, 30]; + const finalOracleBalances = await Promise.all( + [recordingOracle, reputationOracle, exchangeOracle].map( + async (oracle) => + token.connect(owner).balanceOf(await oracle.getAddress()) + ) + ); - await escrow - .connect(reputationOracle) - [ - 'bulkPayOut(address[],uint256[],string,string,uint256,bool)' - ](recepients, amounts, MOCK_URL, MOCK_HASH, '000', true); + const totalPayout = amounts.reduce( + (acc, amount) => acc + BigInt(amount), + 0n + ); + const oracleExpectedFee = (totalPayout * 3n) / 100n; // 3% fee + + recipients.forEach((_, index) => { + const expectedAmount = (BigInt(amounts[index]) * 91n) / 100n; // 91% after all 3 oracle fees + expect( + (finalBalances[index] - initialBalances[index]).toString() + ).to.equal(expectedAmount.toString()); + }); + + initialOracleBalances.forEach((initialBalance, index) => { + expect( + (finalOracleBalances[index] - initialBalance).toString() + ).to.equal(oracleExpectedFee.toString()); + }); + + expect(await escrow.remainingFunds()).to.equal( + await escrow.getBalance() + ); + expect(await escrow.status()).to.equal(Status.Partial); + }); - const finalBalanceAccount1 = await token - .connect(owner) - .balanceOf(account1); - const finalBalanceAccount2 = await token - .connect(owner) - .balanceOf(account2); - const finalBalanceAccount3 = await token - .connect(owner) - .balanceOf(account3); - const finalBalanceLauncher = await token - .connect(owner) - .balanceOf(await launcher.getAddress()); - const finalBalanceRecordingOracle = await token - .connect(owner) - .balanceOf(await recordingOracle.getAddress()); - const finalBalanceReputationOracle = await token - .connect(owner) - .balanceOf(await reputationOracle.getAddress()); - const finalBalanceExchangeOracle = await token - .connect(owner) - .balanceOf(await exchangeOracle.getAddress()); - - expect( - (finalBalanceAccount1 - initialBalanceAccount1).toString() - ).to.equal('7'); - expect( - (finalBalanceAccount2 - initialBalanceAccount2).toString() - ).to.equal('14'); - expect( - (finalBalanceAccount3 - initialBalanceAccount3).toString() - ).to.equal('21'); - expect( - (finalBalanceLauncher - initialBalanceLauncher).toString() - ).to.equal('40'); - expect( - ( - finalBalanceRecordingOracle - initialBalanceRecordingOracle - ).toString() - ).to.equal('6'); - expect( - ( - finalBalanceReputationOracle - initialBalanceReputationOracle - ).toString() - ).to.equal('6'); - - expect( - (finalBalanceExchangeOracle - initialBalanceExchangeOracle).toString() - ).to.equal('6'); + it('Should pay each recipient their corresponding amount and return the remaining to launcher with force complete option', async () => { + const recipients = await Promise.all( + restAccounts.slice(0, 3).map(async (account) => account.getAddress()) + ); - expect(await escrow.remainingFunds()).to.equal('0'); - }); + const initialBalances = await Promise.all( + recipients.map(async (account) => + token.connect(owner).balanceOf(account) + ) + ); - it('Should runs from setup to bulkPayOut to complete correctly', async () => { - const recepients = [await restAccounts[3].getAddress()]; - const amounts = [100]; + const initialOracleBalances = await Promise.all( + [recordingOracle, reputationOracle, exchangeOracle].map( + async (oracle) => + token.connect(owner).balanceOf(await oracle.getAddress()) + ) + ); - expect(await escrow.status()).to.equal(Status.Pending); + const amounts = recipients.map(() => + ethers + .parseEther(faker.number.int({ min: 1, max: 20 }).toString()) + .toString() + ); await escrow .connect(reputationOracle) [ - 'bulkPayOut(address[],uint256[],string,string,uint256)' - ](recepients, amounts, MOCK_URL, MOCK_HASH, '000'); + 'bulkPayOut(address[],uint256[],string,string,uint256,bool)' + ](recipients, amounts, faker.internet.url(), faker.string.alphanumeric(10), faker.string.numeric(3), true); + + const finalBalances = await Promise.all( + recipients.map(async (account) => + token.connect(owner).balanceOf(account) + ) + ); + + const finalOracleBalances = await Promise.all( + [recordingOracle, reputationOracle, exchangeOracle].map( + async (oracle) => + token.connect(owner).balanceOf(await oracle.getAddress()) + ) + ); + + const totalPayout = amounts.reduce( + (acc, amount) => acc + BigInt(amount), + 0n + ); + const oracleExpectedFee = (totalPayout * 3n) / 100n; // 3% fee + + recipients.forEach((_, index) => { + const expectedAmount = (BigInt(amounts[index]) * 91n) / 100n; // 91% after all 3 oracle fees + expect( + (finalBalances[index] - initialBalances[index]).toString() + ).to.equal(expectedAmount.toString()); + }); + + initialOracleBalances.forEach((initialBalance, index) => { + expect( + (finalOracleBalances[index] - initialBalance).toString() + ).to.equal(oracleExpectedFee.toString()); + }); + expect(await escrow.remainingFunds()).to.equal('0'); + expect(await escrow.remainingFunds()).to.equal( + await escrow.getBalance() + ); + expect(await escrow.status()).to.equal(Status.Complete); }); - it('Should runs from setup to bulkPayOut to complete correctly with multiple addresses', async () => { - const recepients = [ + it('Should be completed when amount of payouts is equal to the balance', async () => { + const recipients = [ await restAccounts[3].getAddress(), await restAccounts[4].getAddress(), await restAccounts[5].getAddress(), ]; - const amounts = [10, 20, 70]; + const amounts = [ + fundAmount / 4n, //1/4 + fundAmount / 2n, //1/2 + fundAmount / 4n, //1/4 + ]; expect(await escrow.status()).to.equal(Status.Pending); @@ -990,17 +985,21 @@ describe('Escrow', function () { .connect(reputationOracle) [ 'bulkPayOut(address[],uint256[],string,string,uint256)' - ](recepients, amounts, MOCK_URL, MOCK_HASH, '000'); + ](recipients, amounts, MOCK_URL, MOCK_HASH, '000'); expect(await escrow.status()).to.equal(Status.Complete); }); it('Should runs from setup to bulkPayOut to Cancelled correctly with multiple addresses', async () => { - const recepients = [ + const recipients = [ await restAccounts[3].getAddress(), await restAccounts[4].getAddress(), await restAccounts[5].getAddress(), ]; - const amounts = [10, 20, 70]; + const amounts = [ + fundAmount / 4n, //1/4 + fundAmount / 2n, //1/2 + fundAmount / 4n, //1/4 + ]; await escrow.connect(owner).cancel(); @@ -1010,13 +1009,15 @@ describe('Escrow', function () { .connect(reputationOracle) [ 'bulkPayOut(address[],uint256[],string,string,uint256)' - ](recepients, amounts, MOCK_URL, MOCK_HASH, '000'); + ](recipients, amounts, MOCK_URL, MOCK_HASH, '000'); expect(await escrow.status()).to.equal(Status.Cancelled); }); it('Should runs from setup to bulkPayOut to partial correctly', async () => { - const recepients = [await restAccounts[3].getAddress()]; - const amounts = [80]; + const recipients = [await restAccounts[3].getAddress()]; + const amounts = [ + fundAmount - fundAmount / 5n, //4/5 + ]; expect(await escrow.status()).to.equal(Status.Pending); @@ -1024,13 +1025,15 @@ describe('Escrow', function () { .connect(reputationOracle) [ 'bulkPayOut(address[],uint256[],string,string,uint256)' - ](recepients, amounts, MOCK_URL, MOCK_HASH, '000'); + ](recipients, amounts, MOCK_URL, MOCK_HASH, '000'); expect(await escrow.status()).to.equal(Status.Partial); }); it('Should runs partial bulkPayOut without modifying status if status is ToCancel', async () => { - const recepients = [await restAccounts[3].getAddress()]; - const amounts = [80]; + const recipients = [await restAccounts[3].getAddress()]; + const amounts = [ + fundAmount - fundAmount / 5n, //4/5 + ]; await escrow.connect(owner).cancel(); @@ -1040,17 +1043,21 @@ describe('Escrow', function () { .connect(reputationOracle) [ 'bulkPayOut(address[],uint256[],string,string,uint256)' - ](recepients, amounts, MOCK_URL, MOCK_HASH, '000'); + ](recipients, amounts, MOCK_URL, MOCK_HASH, '000'); expect(await escrow.status()).to.equal(Status.ToCancel); }); it('Should runs from setup to bulkPayOut to partial correctly with multiple addresses', async () => { - const recepients = [ + const recipients = [ await restAccounts[3].getAddress(), await restAccounts[4].getAddress(), await restAccounts[5].getAddress(), ]; - const amounts = [10, 20, 50]; + const amounts = [ + fundAmount / 4n, //1/4 + fundAmount / 2n, //1/2 + fundAmount / 4n, //1/4 + ]; expect(await escrow.status()).to.equal(Status.Pending); @@ -1058,8 +1065,8 @@ describe('Escrow', function () { .connect(reputationOracle) [ 'bulkPayOut(address[],uint256[],string,string,uint256)' - ](recepients, amounts, MOCK_URL, MOCK_HASH, '000'); - expect(await escrow.status()).to.equal(Status.Partial); + ](recipients, amounts, MOCK_URL, MOCK_HASH, '000'); + expect(await escrow.status()).to.equal(Status.Complete); }); }); }); @@ -1068,9 +1075,9 @@ describe('Escrow', function () { describe('Validations', function () { beforeEach(async () => { await deployEscrow(); - await fundEscrow(); + const fundAmount = await fundEscrow(); await setupEscrow(); - await storeResults(); + await storeResults(fundAmount); }); it('Should revert with the right error if escrow not in Paid, Partial or ToCancel state', async function () { @@ -1081,16 +1088,19 @@ describe('Escrow', function () { }); describe('Events', function () { + let fundAmount: bigint; beforeEach(async () => { await deployEscrow(); - await fundEscrow(); + fundAmount = await fundEscrow(); await setupEscrow(); - await storeResults(); + await storeResults(fundAmount / 4n); }); it('Should emit a Completed event when escrow is completed', async function () { const recipients = [await restAccounts[0].getAddress()]; - const amounts = [10]; + const amounts = [ + fundAmount / 4n, //1/4 + ]; await escrow .connect(owner) @@ -1106,16 +1116,19 @@ describe('Escrow', function () { }); describe('Complete escrow', async function () { + let fundAmount: bigint; beforeEach(async () => { await deployEscrow(); - await fundEscrow(); + fundAmount = await fundEscrow(); await setupEscrow(); - await storeResults(); + await storeResults(fundAmount / 2n); }); it('Should succeed if escrow is in Partial state', async function () { const recipients = [await restAccounts[0].getAddress()]; - const amounts = [10]; + const amounts = [ + fundAmount / 4n, //1/4 + ]; await escrow .connect(owner) [ @@ -1134,7 +1147,7 @@ describe('Escrow', function () { .balanceOf(await launcher.getAddress()); const recipients = [await restAccounts[0].getAddress()]; - const amounts = [10]; + const amounts = [fundAmount / 2n]; await escrow .connect(owner) [ @@ -1146,7 +1159,9 @@ describe('Escrow', function () { .connect(owner) .balanceOf(await launcher.getAddress()); - expect(finalLauncherBalance - initialLauncherBalance).to.equal('90'); + expect(finalLauncherBalance - initialLauncherBalance).to.equal( + fundAmount - amounts[0] + ); }); }); });