diff --git a/contracts/Feature.sol b/contracts/Feature.sol index 5fb2a07..17e38d2 100644 --- a/contracts/Feature.sol +++ b/contracts/Feature.sol @@ -812,7 +812,7 @@ contract Feature is Initializable, NativeMetaTransaction, ChainConstants, Contex claim.status = Status.Resolved; } else { - payable(claim.challenger).send(claim.challengerFee + transaction.deposit); + payable(claim.challenger).send(transaction.deposit); claim.status = Status.WaitingForChallenger; } diff --git a/contracts/FeatureERC20.sol b/contracts/FeatureERC20.sol index 177a56b..bb98200 100644 --- a/contracts/FeatureERC20.sol +++ b/contracts/FeatureERC20.sol @@ -805,7 +805,7 @@ contract FeatureERC20 is Initializable, NativeMetaTransaction, ChainConstants, C claim.status = Status.Resolved; } else { - payable(claim.challenger).send(claim.challengerFee + transaction.deposit); + payable(claim.challenger).send(transaction.deposit); claim.status = Status.WaitingForChallenger; } diff --git a/hardhat.config.js b/hardhat.config.js index 9a34d25..91f6f58 100644 --- a/hardhat.config.js +++ b/hardhat.config.js @@ -52,6 +52,11 @@ module.exports = { gasPrice: 8000000000, }, }), + hardhat: { + accounts: { + count: 60, // Number of unlocked accounts + }, + }, }, etherscan: { apiKey: ETHERSCAN_API_KEY, diff --git a/package-lock.json b/package-lock.json index 0eb62de..0447f73 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15086,6 +15086,20 @@ "typescript": ">=3.7.0" } }, + "node_modules/typescript": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.2.2.tgz", + "integrity": "sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==", + "dev": true, + "peer": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, "node_modules/typical": { "version": "2.6.1", "resolved": "https://registry.npmjs.org/typical/-/typical-2.6.1.tgz", @@ -27031,6 +27045,13 @@ } } }, + "typescript": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.2.2.tgz", + "integrity": "sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==", + "dev": true, + "peer": true + }, "typical": { "version": "2.6.1", "resolved": "https://registry.npmjs.org/typical/-/typical-2.6.1.tgz", diff --git a/test/feature-erc20.js b/test/feature-erc20.js index 6d69c59..8ccebea 100644 --- a/test/feature-erc20.js +++ b/test/feature-erc20.js @@ -21,7 +21,10 @@ let deployer, receiver6, challenger2, sender7, - receiver7; + receiver7, + sender8, + receiver8, + challenger3; let contractAsSignerDeployer, contractAsSignerSender0; beforeEach(async function () { @@ -53,6 +56,9 @@ beforeEach(async function () { challenger2, sender7, receiver7, + sender8, + receiver8, + challenger3, ] = await ethers.getSigners(); featureERC20 = await FeatureERC20.deploy(); @@ -72,6 +78,7 @@ beforeEach(async function () { contractAsSender5ERC20Deployer = erc20Mock.connect(sender5); contractAsSender6ERC20Deployer = erc20Mock.connect(sender6); contractAsSender7ERC20Deployer = erc20Mock.connect(sender7); + contractAsSender8ERC20Deployer = erc20Mock.connect(sender8); contractAsSignerDeployer = featureERC20.connect(deployer); contractAsSignerSender0 = featureERC20.connect(sender0); @@ -93,6 +100,9 @@ beforeEach(async function () { contractAsSignerChallenger2 = featureERC20.connect(challenger2); contractAsSignerSender7 = featureERC20.connect(sender7); contractAsSignerReceiver7 = featureERC20.connect(receiver7); + contractAsSignerSender8 = featureERC20.connect(sender8); + contractAsSignerReceiver8 = featureERC20.connect(receiver8); + contractAsSignerChallenger3 = featureERC20.connect(challenger3); contractAsSignerJuror = arbitrator.connect(deployer); @@ -132,7 +142,7 @@ describe('Feature ERC20', function () { const claimTx = await contractAsSignerReceiver0.claim( 0, // _transactionID - 'https://github.com/feature-sh/bot/pull/190', + 'https://github.com/feature-sh/bot/pull/42', { value: '120000000000000000', // 0.12eth gasPrice: 150000000000, @@ -146,7 +156,7 @@ describe('Feature ERC20', function () { /// Get proof from the claim const proofExpected = (await contractAsSignerReceiver1.claims(txID))[3]; - expect(proofExpected).to.equal('https://github.com/feature-sh/bot/pull/190'); + expect(proofExpected).to.equal('https://github.com/feature-sh/bot/pull/42'); expect((await featureERC20.transactions(0)).runningClaimCount).to.equal(1); const gasFeeClaimTx = transactionMinedClaimTx.gasUsed @@ -201,10 +211,10 @@ describe('Feature ERC20', function () { ); // Wait until the transaction is mined - const transactionMinedCreateTransactionTx = await createTransactionTx.wait(); - const gasFeeCreateTransactionTx = transactionMinedCreateTransactionTx.gasUsed - .valueOf() - .mul(150000000000); + const transactionMinedCreateTransactionTx = + await createTransactionTx.wait(); + const gasFeeCreateTransactionTx = + transactionMinedCreateTransactionTx.gasUsed.valueOf().mul(150000000000); await network.provider.send('evm_increaseTime', [864000]); await network.provider.send('evm_mine'); @@ -245,14 +255,14 @@ describe('Feature ERC20', function () { ); // Wait until the transaction is mined - const transactionMinedCreateTransactionTx = await createTransactionTx.wait(); - const gasFeeCreateTransactionTx = transactionMinedCreateTransactionTx.gasUsed - .valueOf() - .mul(150000000000); + const transactionMinedCreateTransactionTx = + await createTransactionTx.wait(); + const gasFeeCreateTransactionTx = + transactionMinedCreateTransactionTx.gasUsed.valueOf().mul(150000000000); const claimTx = await contractAsSignerReceiver1.claim( 0, // _transactionID - 'https://github.com/feature-sh/bot/pull/190', + 'https://github.com/feature-sh/bot/pull/42', { value: '120000000000000000', // 0.12eth gasPrice: 150000000000, @@ -294,14 +304,14 @@ describe('Feature ERC20', function () { ); // Wait until the transaction is mined - const transactionMinedCreateTransactionTx = await createTransactionTx.wait(); - const gasFeeCreateTransactionTx = transactionMinedCreateTransactionTx.gasUsed - .valueOf() - .mul(150000000000); + const transactionMinedCreateTransactionTx = + await createTransactionTx.wait(); + const gasFeeCreateTransactionTx = + transactionMinedCreateTransactionTx.gasUsed.valueOf().mul(150000000000); const claimTx = await contractAsSignerReceiver2.claim( 0, // _transactionID - 'https://github.com/feature-sh/bot/pull/190', + 'https://github.com/feature-sh/bot/pull/42', { value: '120000000000000000', // 0.12eth gasPrice: 150000000000, @@ -345,7 +355,7 @@ describe('Feature ERC20', function () { // Claim const claimTx = await contractAsSignerReceiver3.claim( 0, // _transactionID - 'https://github.com/feature-sh/bot/pull/190', + 'https://github.com/feature-sh/bot/pull/42', { value: '120000000000000000', // 0.12eth gasPrice: 150000000000, @@ -428,7 +438,7 @@ describe('Feature ERC20', function () { // Claim const claimTx = await contractAsSignerReceiver4.claim( 0, // _transactionID - 'https://github.com/feature-sh/bot/pull/190', + 'https://github.com/feature-sh/bot/pull/42', { value: '120000000000000000', // 0.12eth gasPrice: 150000000000, @@ -506,7 +516,7 @@ describe('Feature ERC20', function () { // Claim const claimTx = await contractAsSignerReceiver5.claim( 0, // _transactionID - 'https://github.com/feature-sh/bot/pull/190', + 'https://github.com/feature-sh/bot/pull/42', { value: '120000000000000000', // 0.12eth gasPrice: 150000000000, @@ -610,7 +620,7 @@ describe('Feature ERC20', function () { // 1st claim const claimTx1 = await contractAsSignerReceiver6.claim( 0, // _transactionID - 'https://github.com/feature-sh/bot/pull/190', + 'https://github.com/feature-sh/bot/pull/42', { value: '120000000000000000', // 0.12eth gasPrice: 150000000000, @@ -626,7 +636,7 @@ describe('Feature ERC20', function () { // 2nd claim const claimTx2 = await contractAsSignerReceiver7.claim( 0, // _transactionID - 'https://github.com/feature-sh/bot/pull/190', + 'https://github.com/feature-sh/bot/pull/42', { value: '120000000000000000', // 0.12eth gasPrice: 150000000000, @@ -668,4 +678,132 @@ describe('Feature ERC20', function () { newBalanceReceiver7Expected.toString(), ); }); + + it('Should refund the challenger deposit to the challenger and pay the claimant after a no ruling and timeout payment', async function () { + // Get balance of the contractAsSignerSender8 + const contractAsSignerSender8Balance = await provider.getBalance( + contractAsSignerSender8.address, + ); + + // Expected balance is 0 + expect(contractAsSignerSender8Balance.toString()).to.equal('0'); + + const createTransferTx = await contractAsSignerERC20Deployer.transfer( + sender8.address, + 100, + ); + + const createAllowERC20Tx = await contractAsSender8ERC20Deployer.approve( + featureERC20.address, + 100, + ); + + const createTransactionTx = await contractAsSignerSender8.createTransaction( + arbitrator.address, + 0x00, + erc20Mock.address, + 100, + '100000000000000000', // _deposit for claim : 0.1eth => 10% of amount + '864000', // _timeoutPayment => 10 days + '259200', // _challengePeriod => 3 days + '', // _metaEvidence + ); + + expect((await featureERC20.transactions(0)).sender).to.equal( + sender8.address, + ); + + // Claim + const claimTx = await contractAsSignerReceiver8.claim( + 0, // _transactionID + 'https://github.com/feature-sh/bot/pull/42', + { + value: '120000000000000000', // 0.12eth + gasPrice: 150000000000, + }, + ); + + // Wait until the transaction is mined + const transactionMinedClaimTx = await claimTx.wait(); + const txID = transactionMinedClaimTx.events[0].args[0].toString(); + + const gasFeeClaimTx = transactionMinedClaimTx.gasUsed + .valueOf() + .mul(150000000000); + + // Challenge claim + const challengeClaimTx = await contractAsSignerChallenger3.challengeClaim( + 0, // _claimID + { + value: '120000000000000000', // 0.12eth + gasPrice: 150000000000, + }, + ); + + // Wait until the transaction is mined + const transactionMinedChallengeClaimTx = await challengeClaimTx.wait(); + + const gasFeeChallengeClaimTx = transactionMinedChallengeClaimTx.gasUsed + .valueOf() + .mul(150000000000); + + // Give ruling + await contractAsSignerJuror.giveRuling( + 0, // _disputeID + 0, // No ruling + ); + + await network.provider.send('evm_increaseTime', [42]); + await network.provider.send('evm_mine'); + + // Execute ruling + await contractAsSignerJuror.giveRuling( + 0, // _disputeID + 0, // No ruling + ); + + const claim = await featureERC20.claims(0); + + // Claim status switch to WaitingForChallenger. + expect(parseInt(claim.status)).to.equal(0); + + const newBalanceChallenger3Expected = new ethers.BigNumber.from( + '10000000000000000000000', + ) + .sub('20000000000000000') // Challenger arbitration fee + .sub(gasFeeChallengeClaimTx); + + expect( + (await provider.getBalance(challenger3.address)).toString(), + ).to.equal(newBalanceChallenger3Expected.toString()); + + // Wait until the challenge period is over + await network.provider.send('evm_increaseTime', [259200]); + await network.provider.send('evm_mine'); + + // Pay the first claimer + const payTx = await contractAsSignerDeployer.pay( + 0, // _claimID + ); + + const newBalanceReceiver8Expected = new ethers.BigNumber.from( + '10000000000000000000000', + ).sub(gasFeeClaimTx); + + expect((await provider.getBalance(receiver8.address)).toString()).to.equal( + newBalanceReceiver8Expected.toString(), + ); + + // Check ERC20 balance of the receiver + expect((await erc20Mock.balanceOf(receiver8.address)).toString()).to.equal( + '100', + ); + + // Get balance of the contractAsSignerSender8 after the payment + const contractAsSignerSender8BalanceAfterPayment = + await provider.getBalance(contractAsSignerSender8.address); + + // Expected balance is 0 after the payment + expect(contractAsSignerSender8BalanceAfterPayment.toString()).to.equal('0'); + }); }); diff --git a/test/feature.js b/test/feature.js index 84f7f81..901350a 100644 --- a/test/feature.js +++ b/test/feature.js @@ -21,7 +21,10 @@ let deployer, receiver6, challenger2, sender7, - receiver7; + receiver7, + sender8, + receiver8, + challenger3; let contractAsSignerDeployer, contractAsSignerSender0; beforeEach(async function () { @@ -53,6 +56,9 @@ beforeEach(async function () { challenger2, sender7, receiver7, + sender8, + receiver8, + challenger3, ] = await ethers.getSigners(); feature = await Feature.deploy(); @@ -81,6 +87,9 @@ beforeEach(async function () { contractAsSignerChallenger2 = feature.connect(challenger2); contractAsSignerSender7 = feature.connect(sender7); contractAsSignerReceiver7 = feature.connect(receiver7); + contractAsSignerSender8 = feature.connect(sender8); + contractAsSignerReceiver8 = feature.connect(receiver8); + contractAsSignerChallenger3 = feature.connect(challenger3); contractAsSignerJuror = arbitrator.connect(deployer); @@ -106,7 +115,7 @@ describe('Feature', function () { const claimTx = await contractAsSignerReceiver0.claim( 0, // _transactionID - 'https://github.com/feature-sh/bot/pull/190', + 'https://github.com/feature-sh/bot/pull/42', { value: '120000000000000000', // 0.12eth gasPrice: 150000000000, @@ -120,7 +129,7 @@ describe('Feature', function () { // Get proof from the claim const proofExpected = (await contractAsSignerReceiver1.claims(txID))[3]; - expect(proofExpected).to.equal('https://github.com/feature-sh/bot/pull/190'); + expect(proofExpected).to.equal('https://github.com/feature-sh/bot/pull/42'); expect((await feature.transactions(0)).runningClaimCount).to.equal(1); const gasFeeClaimTx = transactionMinedClaimTx.gasUsed @@ -162,10 +171,10 @@ describe('Feature', function () { expect((await feature.transactions(0)).sender).to.equal(sender1.address); // Wait until the transaction is mined - const transactionMinedCreateTransactionTx = await createTransactionTx.wait(); - const gasFeeCreateTransactionTx = transactionMinedCreateTransactionTx.gasUsed - .valueOf() - .mul(150000000000); + const transactionMinedCreateTransactionTx = + await createTransactionTx.wait(); + const gasFeeCreateTransactionTx = + transactionMinedCreateTransactionTx.gasUsed.valueOf().mul(150000000000); await network.provider.send('evm_increaseTime', [864000]); await network.provider.send('evm_mine'); @@ -200,14 +209,14 @@ describe('Feature', function () { expect((await feature.transactions(0)).sender).to.equal(sender2.address); // Wait until the transaction is mined - const transactionMinedCreateTransactionTx = await createTransactionTx.wait(); - const gasFeeCreateTransactionTx = transactionMinedCreateTransactionTx.gasUsed - .valueOf() - .mul(150000000000); + const transactionMinedCreateTransactionTx = + await createTransactionTx.wait(); + const gasFeeCreateTransactionTx = + transactionMinedCreateTransactionTx.gasUsed.valueOf().mul(150000000000); const claimTx = await contractAsSignerReceiver1.claim( 0, // _transactionID - 'https://github.com/feature-sh/bot/pull/190', + 'https://github.com/feature-sh/bot/pull/42', { value: '120000000000000000', // 0.12eth gasPrice: 150000000000, @@ -239,14 +248,14 @@ describe('Feature', function () { expect((await feature.transactions(0)).sender).to.equal(sender3.address); // Wait until the transaction is mined - const transactionMinedCreateTransactionTx = await createTransactionTx.wait(); - const gasFeeCreateTransactionTx = transactionMinedCreateTransactionTx.gasUsed - .valueOf() - .mul(150000000000); + const transactionMinedCreateTransactionTx = + await createTransactionTx.wait(); + const gasFeeCreateTransactionTx = + transactionMinedCreateTransactionTx.gasUsed.valueOf().mul(150000000000); const claimTx = await contractAsSignerReceiver2.claim( 0, // _transactionID - 'https://github.com/feature-sh/bot/pull/190', + 'https://github.com/feature-sh/bot/pull/42', { value: '120000000000000000', // 0.12eth gasPrice: 150000000000, @@ -277,7 +286,7 @@ describe('Feature', function () { // Claim const claimTx = await contractAsSignerReceiver3.claim( 0, // _transactionID - 'https://github.com/feature-sh/bot/pull/190', + 'https://github.com/feature-sh/bot/pull/42', { value: '120000000000000000', // 0.12eth gasPrice: 150000000000, @@ -347,7 +356,7 @@ describe('Feature', function () { // Claim const claimTx = await contractAsSignerReceiver4.claim( 0, // _transactionID - 'https://github.com/feature-sh/bot/pull/190', + 'https://github.com/feature-sh/bot/pull/42', { value: '120000000000000000', // 0.12eth gasPrice: 150000000000, @@ -414,7 +423,7 @@ describe('Feature', function () { // Claim const claimTx = await contractAsSignerReceiver5.claim( 0, // _transactionID - 'https://github.com/feature-sh/bot/pull/190', + 'https://github.com/feature-sh/bot/pull/42', { value: '120000000000000000', // 0.12eth gasPrice: 150000000000, @@ -502,7 +511,7 @@ describe('Feature', function () { // 1st claim const claimTx1 = await contractAsSignerReceiver6.claim( 0, // _transactionID - 'https://github.com/feature-sh/bot/pull/190', + 'https://github.com/feature-sh/bot/pull/42', { value: '120000000000000000', // 0.12eth gasPrice: 150000000000, @@ -518,7 +527,7 @@ describe('Feature', function () { // 2nd claim const claimTx2 = await contractAsSignerReceiver7.claim( 0, // _transactionID - 'https://github.com/feature-sh/bot/pull/190', + 'https://github.com/feature-sh/bot/pull/42', { value: '120000000000000000', // 0.12eth gasPrice: 150000000000, @@ -560,4 +569,116 @@ describe('Feature', function () { newBalanceReceiver7Expected.toString(), ); }); + + it('Should refund the challenger deposit to the challenger and pay the claimant after a no ruling and timeout payment', async function () { + // Get balance of the contractAsSignerSender8 + const contractAsSignerSender8Balance = await provider.getBalance( + contractAsSignerSender8.address, + ); + + // Expected balance is 0 + expect(contractAsSignerSender8Balance.toString()).to.equal('0'); + + const createTransactionTx = await contractAsSignerSender8.createTransaction( + arbitrator.address, + 0x00, + '100000000000000000', // _deposit for claim : 0.1eth => 10% of amount + '864000', // _timeoutPayment => 10 days + '259200', // _timeoutClaim => 3 days + '', // _metaEvidence + { + value: '1000000000000000000', // 1eth in wei + }, + ); + + // Claim + const claimTx = await contractAsSignerReceiver8.claim( + 0, // _transactionID + 'https://github.com/feature-sh/bot/pull/42', + { + value: '120000000000000000', // 0.12eth + gasPrice: 150000000000, + }, + ); + + // Wait until the transaction is mined + const transactionMinedClaimTx = await claimTx.wait(); + const txID = transactionMinedClaimTx.events[0].args[0].toString(); + + const gasFeeClaimTx = transactionMinedClaimTx.gasUsed + .valueOf() + .mul(150000000000); + + // Challenge claim + const challengeClaimTx = await contractAsSignerChallenger3.challengeClaim( + 0, // _claimID + { + value: '120000000000000000', // 0.12eth + gasPrice: 150000000000, + }, + ); + + // Wait until the transaction is mined + const transactionMinedChallengeClaimTx = await challengeClaimTx.wait(); + + const gasFeeChallengeClaimTx = transactionMinedChallengeClaimTx.gasUsed + .valueOf() + .mul(150000000000); + + // Give ruling + await contractAsSignerJuror.giveRuling( + 0, // _disputeID + 0, // No ruling + ); + + await network.provider.send('evm_increaseTime', [42]); + await network.provider.send('evm_mine'); // this one will have 100s more + + // Execute ruling + await contractAsSignerJuror.giveRuling( + 0, // _disputeID + 0, // No ruling + ); + + const claim = await feature.claims(0); + + // Claim status switch to WaitingForChallenger. + expect(parseInt(claim.status)).to.equal(0); + + const newBalanceChallenger3Expected = new ethers.BigNumber.from( + '10000000000000000000000', + ) + .sub('20000000000000000') // Challenger arbitration fee + .sub(gasFeeChallengeClaimTx); + + expect( + (await provider.getBalance(challenger3.address)).toString(), + ).to.equal(newBalanceChallenger3Expected.toString()); + + // Wait until the challenge period is over + await network.provider.send('evm_increaseTime', [259200]); + await network.provider.send('evm_mine'); + + // Pay the first claimer + const payTx = await contractAsSignerDeployer.pay( + 0, // _claimID + ); + + const newBalanceReceiver8Expected = new ethers.BigNumber.from( + '10000000000000000000000', + ) + .add('1000000000000000000') // Transaction's value + .sub(gasFeeClaimTx); + + expect((await provider.getBalance(receiver8.address)).toString()).to.equal( + newBalanceReceiver8Expected.toString(), + ); + + // Get balance of the contractAsSignerSender8 after the payment + const contractAsSignerSender8BalanceAfterPayment = + await provider.getBalance(contractAsSignerSender8.address); + + // Expected balance is 0 after the payment + expect(contractAsSignerSender8BalanceAfterPayment.toString()).to.equal('0'); + }); });