diff --git a/contracts/subscription/GenericSingleDatasetSubscriptionManager.sol b/contracts/subscription/GenericSingleDatasetSubscriptionManager.sol index 6ea5b15..488da85 100644 --- a/contracts/subscription/GenericSingleDatasetSubscriptionManager.sol +++ b/contracts/subscription/GenericSingleDatasetSubscriptionManager.sol @@ -49,6 +49,7 @@ abstract contract GenericSingleDatasetSubscriptionManager is error ARRAY_LENGTH_MISMATCH(); error NOTHING_TO_PAY(); error SUBSCRIPTION_FEE_EXCEEDS_LIMIT(); + error DATASET_OWNER_FORBIDDEN(address account); struct SubscriptionDetails { uint256 validSince; @@ -78,6 +79,12 @@ abstract contract GenericSingleDatasetSubscriptionManager is _; } + modifier forbidDatasetOwner() { + address msgSender = _msgSender(); + if (dataset.ownerOf(datasetId) == msgSender) revert DATASET_OWNER_FORBIDDEN(msgSender); + _; + } + /** * @notice Initialization function * @param dataset_ The address of the DatasetNFT contract @@ -124,6 +131,9 @@ abstract contract GenericSingleDatasetSubscriptionManager is */ function isSubscriptionPaidFor(uint256 ds, address consumer) external view returns (bool) { _requireCorrectDataset(ds); + + if (dataset.ownerOf(datasetId) == consumer) return true; + EnumerableSet.UintSet storage subscrs = _consumerSubscriptions[consumer]; uint256 totalSubscribers = subscrs.length(); for (uint256 i; i < totalSubscribers; ) { @@ -196,7 +206,7 @@ abstract contract GenericSingleDatasetSubscriptionManager is uint256 durationInDays, uint256 consumers, uint256 maxFee - ) external payable returns (uint256 sid) { + ) external payable forbidDatasetOwner returns (uint256 sid) { return _subscribe(ds, durationInDays, consumers, maxFee); } @@ -221,7 +231,7 @@ abstract contract GenericSingleDatasetSubscriptionManager is uint256 durationInDays, address[] calldata consumers, uint256 maxFee - ) external payable returns (uint256 sid) { + ) external payable forbidDatasetOwner returns (uint256 sid) { sid = _subscribe(ds, durationInDays, consumers.length, maxFee); _addConsumers(sid, consumers); } @@ -257,7 +267,7 @@ abstract contract GenericSingleDatasetSubscriptionManager is uint256 extraDurationInDays, uint256 extraConsumers, uint256 maxExtraFee - ) external payable { + ) external payable forbidDatasetOwner { _extendSubscription(subscription, extraDurationInDays, extraConsumers, maxExtraFee); } @@ -293,7 +303,7 @@ abstract contract GenericSingleDatasetSubscriptionManager is uint256 extraDurationInDays, address[] calldata extraConsumers, uint256 maxExtraFee - ) external payable { + ) external payable forbidDatasetOwner { _extendSubscription(subscription, extraDurationInDays, extraConsumers.length, maxExtraFee); if (extraConsumers.length > 0) { _addConsumers(subscription, extraConsumers); @@ -310,7 +320,7 @@ abstract contract GenericSingleDatasetSubscriptionManager is function addConsumers( uint256 subscription, address[] calldata consumers - ) external onlySubscriptionOwner(subscription) { + ) external forbidDatasetOwner onlySubscriptionOwner(subscription) { _addConsumers(subscription, consumers); } @@ -325,7 +335,7 @@ abstract contract GenericSingleDatasetSubscriptionManager is function removeConsumers( uint256 subscription, address[] calldata consumers - ) external onlySubscriptionOwner(subscription) { + ) external forbidDatasetOwner onlySubscriptionOwner(subscription) { _removeConsumers(subscription, consumers); } @@ -343,7 +353,7 @@ abstract contract GenericSingleDatasetSubscriptionManager is uint256 subscription, address[] calldata oldConsumers, address[] calldata newConsumers - ) external onlySubscriptionOwner(subscription) { + ) external forbidDatasetOwner onlySubscriptionOwner(subscription) { _replaceConsumers(subscription, oldConsumers, newConsumers); } @@ -461,6 +471,7 @@ abstract contract GenericSingleDatasetSubscriptionManager is revert MAX_CONSUMERS_ADDITION_REACHED(sd.paidConsumers, sd.consumers.length() + consumers.length); for (uint256 i; i < consumers.length; ) { address consumer = consumers[i]; + if (dataset.ownerOf(datasetId) == consumer) revert DATASET_OWNER_FORBIDDEN(consumer); bool added = sd.consumers.add(consumer); if (added) { _consumerSubscriptions[consumer].add(subscription); @@ -522,6 +533,7 @@ abstract contract GenericSingleDatasetSubscriptionManager is revert CONSUMER_NOT_FOUND(subscription, consumer); } consumer = newConsumers[i]; + if (dataset.ownerOf(datasetId) == consumer) revert DATASET_OWNER_FORBIDDEN(consumer); bool added = sd.consumers.add(consumer); if (added) { _consumerSubscriptions[consumer].add(subscription); diff --git a/tests/SubscriptionManager.spec.ts b/tests/SubscriptionManager.spec.ts index d23d514..41e31e6 100644 --- a/tests/SubscriptionManager.spec.ts +++ b/tests/SubscriptionManager.spec.ts @@ -7,7 +7,7 @@ import { FragmentNFT, VerifierManager, } from '@typechained'; -import { expect } from 'chai'; +import { expect, use } from 'chai'; import { ZeroAddress, ZeroHash, parseEther, parseUnits } from 'ethers'; import { deployments, ethers, network } from 'hardhat'; import { v4 as uuidv4 } from 'uuid'; @@ -377,6 +377,41 @@ export default async function suite(): Promise { ).to.emit(DatasetSubscriptionManager_, 'SubscriptionPaid'); }); + it('Should revert if dataset owner tries to call subscribe()', async function () { + await DatasetDistributionManager_.connect(users_.datasetOwner).setTagWeights( + [ZeroHash], + [parseUnits('1', 18)] + ); + + await DatasetDistributionManager_.connect(users_.datasetOwner).setDatasetOwnerPercentage( + ethers.parseUnits('0.01', 18) + ); + + const feeAmount = parseUnits('0.00864', 18); // totalFee for 1 day & 1 consumer :: 0.00864 * 1 * 1 = 0.00864 + + await DatasetSubscriptionManager_.connect(users_.datasetOwner).setFee( + await users_.datasetOwner.Token!.getAddress(), + feeAmount + ); + + const [, maxSubscriptionFee] = await DatasetSubscriptionManager_.subscriptionFee( + datasetId_, + 1, + 1 + ); + + await expect( + DatasetSubscriptionManager_.connect(users_.datasetOwner).subscribe( + datasetId_, + 1, // 1 day + 1, + maxSubscriptionFee + ) + ) + .to.be.revertedWithCustomError(DatasetSubscriptionManager_, 'DATASET_OWNER_FORBIDDEN') + .withArgs(users_.datasetOwner.address); + }); + it('Should revert data set subscription if max fee is too low', async function () { await DatasetDistributionManager_.connect(users_.datasetOwner).setTagWeights( [ZeroHash], @@ -636,12 +671,87 @@ export default async function suite(): Promise { DatasetSubscriptionManager_.connect(users_.subscriber).subscribeAndAddConsumers( datasetId_, 1, - [users_.subscriber.address, users_.datasetOwner.address], + [users_.subscriber.address, users_.secondSubscriber.address], maxSubscriptionFee ) ).to.emit(DatasetSubscriptionManager_, 'SubscriptionPaid'); }); + it('Should revert if dataset owner tries to call subscribeAndAddConsumers()', async function () { + await DatasetDistributionManager_.connect(users_.datasetOwner).setTagWeights( + [ZeroHash], + [parseUnits('1', 18)] + ); + + await DatasetDistributionManager_.connect(users_.datasetOwner).setDatasetOwnerPercentage( + ethers.parseUnits('0.01', 18) + ); + + const feeAmount = parseUnits('0.00864', 18); // totalSubscriptionFee for 1 day & 2 consumers :: 0.00864 * 1 * 2 = 0.01728d + + await DatasetSubscriptionManager_.connect(users_.datasetOwner).setFee( + await users_.datasetOwner.Token!.getAddress(), + feeAmount + ); + + const [, maxSubscriptionFee] = await DatasetSubscriptionManager_.subscriptionFee( + datasetId_, + 1, + 2 + ); + + await expect( + DatasetSubscriptionManager_.connect(users_.datasetOwner).subscribeAndAddConsumers( + datasetId_, + 1, + [users_.subscriber.address, users_.datasetOwner.address], + maxSubscriptionFee + ) + ) + .to.be.revertedWithCustomError(DatasetSubscriptionManager_, 'DATASET_OWNER_FORBIDDEN') + .withArgs(users_.datasetOwner.address); + }); + + it('Should revert if subscriber tries to add the dataset owner to the data set subscription', async function () { + await DatasetDistributionManager_.connect(users_.datasetOwner).setTagWeights( + [ZeroHash], + [parseUnits('1', 18)] + ); + + await users_.subscriber.Token!.approve( + await DatasetSubscriptionManager_.getAddress(), + parseUnits('0.01728', 18) + ); + + await DatasetDistributionManager_.connect(users_.datasetOwner).setDatasetOwnerPercentage( + ethers.parseUnits('0.01', 18) + ); + + const feeAmount = parseUnits('0.00864', 18); // totalSubscriptionFee for 1 day & 2 consumers :: 0.00864 * 1 * 2 = 0.01728d + + await DatasetSubscriptionManager_.connect(users_.datasetOwner).setFee( + await users_.datasetOwner.Token!.getAddress(), + feeAmount + ); + + const [, maxSubscriptionFee] = await DatasetSubscriptionManager_.subscriptionFee( + datasetId_, + 1, + 2 + ); + + await expect( + DatasetSubscriptionManager_.connect(users_.subscriber).subscribeAndAddConsumers( + datasetId_, + 1, + [users_.subscriber.address, users_.datasetOwner.address], + maxSubscriptionFee + ) + ) + .to.be.revertedWithCustomError(DatasetSubscriptionManager_, 'DATASET_OWNER_FORBIDDEN') + .withArgs(users_.datasetOwner.address); + }); + it('Should revert if subscriber tries to subscribe to the same data set', async function () { await DatasetDistributionManager_.connect(users_.datasetOwner).setTagWeights( [ZeroHash], @@ -767,8 +877,6 @@ export default async function suite(): Promise { let snapshotId = await DatasetFragment.currentSnapshotId(); let [tagsFromFragment, counts] = await DatasetFragment.tagCountAt(snapshotId); - console.log('tagsFromFragment :>> ', tagsFromFragment); - expect(tagsFromFragment.includes(tag1)).to.be.true; expect(tagsFromFragment.includes(tag2)).to.be.true; expect(tagsFromFragment.includes(tag3)).to.be.true; @@ -855,6 +963,15 @@ export default async function suite(): Promise { await ethers.provider.send('evm_revert', [snap]); }); + it('Should dataset owner be subscribed to its own dataset', async function () { + expect( + await DatasetSubscriptionManager_.isSubscriptionPaidFor( + datasetId_, + users_.datasetOwner.address + ) + ).to.be.true; + }); + it('Should consumer be added multiple times to a subscription', async () => { await DatasetSubscriptionManager_.connect(users_.subscriber).addConsumers(subscriptionId_, [ users_.consumer.address, @@ -977,6 +1094,17 @@ export default async function suite(): Promise { ).to.be.false; }); + it('Should revert if dataset owner tries to call removeConsumers()', async () => { + await expect( + DatasetSubscriptionManager_.connect(users_.datasetOwner).removeConsumers( + subscriptionId_, + [users_.consumer.address] + ) + ) + .to.be.revertedWithCustomError(DatasetSubscriptionManager_, 'DATASET_OWNER_FORBIDDEN') + .withArgs(users_.datasetOwner.address); + }); + it('Should revert subscription owner remove consumers if subscription does not exists', async () => { const wrongSubscriptionId = 12312312312n; @@ -1040,6 +1168,51 @@ export default async function suite(): Promise { ).to.be.true; }); + it('Should revert if subscription owner tries to replace consumers with dataset owner from the subscription', async () => { + await DatasetSubscriptionManager_.connect(users_.subscriber).addConsumers(subscriptionId_, [ + users_.consumer.address, + ]); + + await expect( + DatasetSubscriptionManager_.connect(users_.subscriber).replaceConsumers( + subscriptionId_, + [users_.consumer.address], + [users_.datasetOwner.address] + ) + ) + .to.be.revertedWithCustomError(DatasetSubscriptionManager_, 'DATASET_OWNER_FORBIDDEN') + .withArgs(users_.datasetOwner.address); + }); + + it('Should revert if dataset owner tries to call replaceConsumers()', async () => { + await DatasetSubscriptionManager_.connect(users_.subscriber).addConsumers(subscriptionId_, [ + users_.consumer.address, + ]); + + expect( + await DatasetSubscriptionManager_.isSubscriptionPaidFor( + datasetId_, + users_.consumer.address + ) + ).to.be.true; + expect( + await DatasetSubscriptionManager_.isSubscriptionPaidFor( + datasetId_, + users_.secondConsumer.address + ) + ).to.be.false; + + await expect( + DatasetSubscriptionManager_.connect(users_.datasetOwner).replaceConsumers( + subscriptionId_, + [users_.consumer.address], + [users_.secondConsumer.address] + ) + ) + .to.be.revertedWithCustomError(DatasetSubscriptionManager_, 'DATASET_OWNER_FORBIDDEN') + .withArgs(users_.datasetOwner.address); + }); + it('Should revert subscription owner consumers replace if old consumers and new consumers does not match length', async () => { await DatasetSubscriptionManager_.connect(users_.subscriber).addConsumers(subscriptionId_, [ users_.consumer.address, @@ -1129,6 +1302,50 @@ export default async function suite(): Promise { ).to.emit(DatasetSubscriptionManager_, 'SubscriptionPaid'); }); + it('Should revert if dataset owner tries to call extendSubscription()', async () => { + await time.increase(constants.ONE_DAY * 3); + + const [, maxSubscriptionFee] = await DatasetSubscriptionManager_.subscriptionFee( + datasetId_, + 7, + 1 + ); + + await expect( + DatasetSubscriptionManager_.connect(users_.datasetOwner).extendSubscription( + subscriptionId_, + 7, + 0, + maxSubscriptionFee + ) + ) + .to.be.revertedWithCustomError(DatasetSubscriptionManager_, 'DATASET_OWNER_FORBIDDEN') + .withArgs(users_.datasetOwner.address); + }); + + it('Should revert if dataset owner tries to call extendSubscriptionAndAddExtraConsumers()', async () => { + await time.increase(constants.ONE_DAY * 3); + + const [, maxSubscriptionFee] = await DatasetSubscriptionManager_.subscriptionFee( + datasetId_, + 7, + 1 + ); + + await expect( + DatasetSubscriptionManager_.connect( + users_.datasetOwner + ).extendSubscriptionAndAddExtraConsumers( + subscriptionId_, + 7, + [users_.secondSubscriber.address], + maxSubscriptionFee + ) + ) + .to.be.revertedWithCustomError(DatasetSubscriptionManager_, 'DATASET_OWNER_FORBIDDEN') + .withArgs(users_.datasetOwner.address); + }); + it('Should subscriber extend his subscription and add consumers if expired', async () => { await time.increase(constants.ONE_DAY * 3);