diff --git a/contracts/StateChannel.sol b/contracts/StateChannel.sol index 86c780d0..34aad9e1 100644 --- a/contracts/StateChannel.sol +++ b/contracts/StateChannel.sol @@ -183,10 +183,12 @@ contract StateChannel is Initializable, OwnableUpgradeable, SQParameter { require(channels[channelId].status == ChannelStatus.Finalized, 'SC001'); // check indexer registered - IIndexerRegistry indexerRegistry = IIndexerRegistry( - settings.getContractAddress(SQContracts.IndexerRegistry) + require( + IIndexerRegistry(settings.getContractAddress(SQContracts.IndexerRegistry)).isIndexer( + indexer + ), + 'G002' ); - require(indexerRegistry.isIndexer(indexer), 'G002'); // check sign bytes32 payload = keccak256( @@ -203,9 +205,7 @@ contract StateChannel is Initializable, OwnableUpgradeable, SQParameter { ); if (_isContract(consumer)) { require(consumer.supportsInterface(type(IConsumer).interfaceId), 'G018'); - IConsumer cConsumer = IConsumer(consumer); - require(cConsumer.checkSign(channelId, payload, consumerSign), 'C006'); - cConsumer.paid(channelId, msg.sender, amount, callback); + require(IConsumer(consumer).checkSign(channelId, payload, consumerSign), 'C006'); } else { _checkSign(payload, consumerSign, consumer, false); require(msg.sender == consumer, 'SC111'); @@ -213,25 +213,23 @@ contract StateChannel is Initializable, OwnableUpgradeable, SQParameter { _checkSign(payload, indexerSign, indexer, true); - // transfer the balance to contract - IERC20(settings.getContractAddress(SQContracts.SQToken)).safeTransferFrom( - consumer, - address(this), - amount - ); - // initial the channel ChannelState storage state = channels[channelId]; state.status = ChannelStatus.Open; state.indexer = indexer; state.consumer = consumer; state.expiredAt = block.timestamp + expiration; - state.realTotal = amount; - state.total = amount; + // commented by @ian, now they will be set in _fundChannel() + // state.realTotal = real; + // state.total = amount; state.spent = 0; state.terminatedAt = 0; state.deploymentId = deploymentId; state.terminateByIndexer = false; + + // transfer the rewards to channel + _fundChannel(channelId, deploymentId, consumer, amount, callback); + // set channel price channelPrice[channelId] = price; @@ -308,42 +306,16 @@ contract StateChannel is Initializable, OwnableUpgradeable, SQParameter { bytes32 payload = keccak256( abi.encode(channelId, indexer, consumer, preTotal, amount, callback) ); - address realConsumer = consumer; // check sign if (_isContract(consumer)) { - IConsumer cConsumer = IConsumer(consumer); - require(cConsumer.checkSign(channelId, payload, sign), 'C006'); - realConsumer = cConsumer.channelConsumer(channelId); + require(IConsumer(consumer).checkSign(channelId, payload, sign), 'C006'); } else { _checkSign(payload, sign, consumer, false); } // transfer the rewards to channel - address rbAddress = settings.getContractAddress(SQContracts.RewardsBooster); - uint256 rewardsAmount = IRewardsBooster(rbAddress).spendQueryRewards( - channels[channelId].deploymentId, - realConsumer, - amount, - abi.encode(channelId) - ); - - if (rewardsAmount < amount) { - // transfer the balance to contract - uint256 realAmount = amount - rewardsAmount; - if (_isContract(consumer)) { - IConsumer(consumer).paid(channelId, msg.sender, realAmount, callback); - } - IERC20(settings.getContractAddress(SQContracts.SQToken)).safeTransferFrom( - consumer, - address(this), - realAmount - ); - - channels[channelId].realTotal += realAmount; - } - - channels[channelId].total += amount; + _fundChannel(channelId, channels[channelId].deploymentId, consumer, amount, callback); emit ChannelFund(channelId, channels[channelId].realTotal, channels[channelId].total); } @@ -662,4 +634,37 @@ contract StateChannel is Initializable, OwnableUpgradeable, SQParameter { } return (size > 0); } + + function _fundChannel( + uint256 channelId, + bytes32 deploymentId, + address consumer, + uint256 amount, + bytes memory callback + ) internal { + address realConsumer = consumer; + if (_isContract(consumer)) { + IConsumer cConsumer = IConsumer(consumer); + realConsumer = cConsumer.channelConsumer(channelId); + } + // transfer the rewards to channel + uint256 fundByReward = IRewardsBooster( + settings.getContractAddress(SQContracts.RewardsBooster) + ).spendQueryRewards(deploymentId, realConsumer, amount, abi.encode(channelId)); + uint256 realAmount = 0; + if (fundByReward < amount) { + realAmount = amount - fundByReward; + if (_isContract(consumer)) { + IConsumer(consumer).paid(channelId, msg.sender, realAmount, callback); + } + IERC20(settings.getContractAddress(SQContracts.SQToken)).safeTransferFrom( + consumer, + address(this), + realAmount + ); + } + + channels[channelId].realTotal += realAmount; + channels[channelId].total += amount; + } } diff --git a/test/RewardsBooster.test.ts b/test/RewardsBooster.test.ts index 5e57f892..aee2eac3 100644 --- a/test/RewardsBooster.test.ts +++ b/test/RewardsBooster.test.ts @@ -342,6 +342,7 @@ describe('RewardsBooster Contract', () => { // spend query rewards await token.connect(consumer0).increaseAllowance(stateChannel.address, etherParse('5')); + // open channel will consume query rewards now await openChannel( stateChannel, defaultChannelId, @@ -352,6 +353,9 @@ describe('RewardsBooster Contract', () => { etherParse('1'), 60 ); + const channel = await stateChannel.channel(defaultChannelId); + const consumedQueryReward = channel.total.sub(channel.realTotal); + const abi = ethers.utils.defaultAbiCoder; const msg = abi.encode( ['uint256', 'address', 'address', 'uint256', 'uint256', 'bytes'], @@ -366,7 +370,9 @@ describe('RewardsBooster Contract', () => { const queryReward1 = await rewardsBooster.getQueryRewards(deploymentId3, consumer0.address); const reward1 = await rewardsBooster.getAccRewardsForDeployment(deploymentId3); // has at least 1 block's reward, not zero - expect(queryReward1).to.eq(getQueryReward(reward1.sub(reward0), queryRewardRatePerMill)); + expect(queryReward1.add(consumedQueryReward)).to.eq( + getQueryReward(reward1.sub(reward0), queryRewardRatePerMill) + ); }); it('can add/remove booster - 1 booster account', async () => { diff --git a/test/StateChannel.test.ts b/test/StateChannel.test.ts index 3bcc375f..3c912aef 100644 --- a/test/StateChannel.test.ts +++ b/test/StateChannel.test.ts @@ -299,6 +299,80 @@ describe('StateChannel Contract', () => { }); }); + describe('State Channel Rewards Open', () => { + const consumerInit = etherParse('10005'); + beforeEach(async () => { + await registerRunner(token, indexerRegistry, staking, wallet_0, runner, etherParse('2000')); + await token.connect(wallet_0).transfer(treasury.address, etherParse('100000')); + await token.connect(wallet_0).transfer(consumer.address, consumerInit); + await token.connect(consumer).increaseAllowance(stateChannel.address, etherParse('5')); + await token.connect(wallet_0).transfer(rewardsBooster.address, etherParse('5')); + + await boosterDeployment(token, rewardsBooster, consumer, deploymentId, etherParse('10000')); + }); + + it('open State Channel with booster rewards more than channel amount should work', async () => { + // 1000 blocks passed + await blockTravel(1000); + const queryRewardsBeforeCreating = await rewardsBooster.getQueryRewards(deploymentId, consumer.address); + // one block passed + await blockTravel(1); + const oneBlockRewards = (await rewardsBooster.getQueryRewards(deploymentId, consumer.address)).sub( + queryRewardsBeforeCreating + ); + // one block passed + await openChannel( + stateChannel, + defaultChannelId, + deploymentId, + runner, + consumer, + etherParse('1'), + etherParse('1'), + time.duration.days(1).toString() + ); + + const queryRewardsAfterCreating = await rewardsBooster.getQueryRewards(deploymentId, consumer.address); + + expect((await stateChannel.channel(defaultChannelId)).realTotal).to.equal(etherParse('0')); + expect((await stateChannel.channel(defaultChannelId)).total).to.equal(etherParse('1')); + + expect(await token.balanceOf(consumer.address)).to.equal(etherParse('5')); + expect(queryRewardsAfterCreating as BigNumber).to.equal( + queryRewardsBeforeCreating.sub(etherParse('1')).add(oneBlockRewards.mul(2)) + ); + }); + + it('open State Channel with booster rewards less than channel amount should work', async () => { + // one block passed + await blockTravel(1); + const oneBlockRewards = await rewardsBooster.getQueryRewards(deploymentId, consumer.address); + // one block passed + await openChannel( + stateChannel, + defaultChannelId, + deploymentId, + runner, + consumer, + etherParse('1'), + etherParse('1'), + time.duration.days(1).toString() + ); + + const queryRewardsAfterCreating = await rewardsBooster.getQueryRewards(deploymentId, consumer.address); + + expect((await stateChannel.channel(defaultChannelId)).realTotal).to.equal( + etherParse('1').sub(oneBlockRewards.mul(2)) + ); + expect((await stateChannel.channel(defaultChannelId)).total).to.equal(etherParse('1')); + + expect(await token.balanceOf(consumer.address)).to.equal( + etherParse('5').sub(etherParse('1').sub(oneBlockRewards.mul(2))) + ); + expect(queryRewardsAfterCreating as BigNumber).to.equal(etherParse('0')); + }); + }); + describe('State Channel Rewards Fund', () => { const consumerInit = etherParse('10005'); beforeEach(async () => { diff --git a/test/StateChannelFlow.test.ts b/test/StateChannelFlow.test.ts index 384636e1..1598fde4 100644 --- a/test/StateChannelFlow.test.ts +++ b/test/StateChannelFlow.test.ts @@ -1,7 +1,7 @@ import { expect } from 'chai'; import { ethers, waffle } from 'hardhat'; import { deployContracts } from './setup'; -import { deploymentIds } from './constants'; +import { deploymentIds, deploymentMetadatas, projectMetadatas } from './constants'; import { IndexerRegistry, RewardsPool, @@ -12,8 +12,10 @@ import { StateChannel, StakingManager, RewardsHelper, + ProjectRegistry, + ProjectType, } from '../src'; -import { registerRunner, startNewEra, time, etherParse } from './helper'; +import { registerRunner, startNewEra, time, etherParse, createProject } from './helper'; import { Wallet, BigNumber } from 'ethers'; describe('StateChannel Workflow Tests', () => { @@ -23,6 +25,7 @@ describe('StateChannel Workflow Tests', () => { let token: ERC20; let staking: Staking; let indexerRegistry: IndexerRegistry; + let projectRegistry: ProjectRegistry; let eraManager: EraManager; let rewardsDistributor: RewardsDistributor; let rewardsPool: RewardsPool; @@ -105,6 +108,7 @@ describe('StateChannel Workflow Tests', () => { beforeEach(async () => { const deployment = await waffle.loadFixture(deployer); indexerRegistry = deployment.indexerRegistry; + projectRegistry = deployment.projectRegistry; staking = deployment.staking; token = deployment.token; rewardsDistributor = deployment.rewardsDistributor; @@ -126,6 +130,24 @@ describe('StateChannel Workflow Tests', () => { await eraManager.connect(wallet_0).updateEraPeriod(time.duration.days(1).toString()); await startNewEra(eraManager); + // create projects + await createProject( + projectRegistry, + wallet_0, + projectMetadatas[0], + deploymentMetadatas[0], + deploymentIds[0], + ProjectType.SUBQUERY + ); + await createProject( + projectRegistry, + wallet_0, + projectMetadatas[0], + deploymentMetadatas[0], + deploymentIds[1], + ProjectType.SUBQUERY + ); + //create statechannels channelId = ethers.utils.randomBytes(32); channelId2 = ethers.utils.randomBytes(32);