diff --git a/abi/IPaymaster.json b/abi/IPaymaster.json index 3506c7dc..81897d7d 100644 --- a/abi/IPaymaster.json +++ b/abi/IPaymaster.json @@ -18,6 +18,11 @@ "internalType": "uint256", "name": "amount", "type": "uint256" + }, + { + "internalType": "address", + "name": "originalSender", + "type": "address" } ], "name": "coverCost", diff --git a/abi/Paymaster.json b/abi/Paymaster.json index ab43392b..ad136726 100644 --- a/abi/Paymaster.json +++ b/abi/Paymaster.json @@ -5,6 +5,11 @@ "internalType": "address", "name": "hubAddress", "type": "address" + }, + { + "internalType": "address", + "name": "initialOwner", + "type": "address" } ], "stateMutability": "nonpayable", @@ -146,6 +151,11 @@ "internalType": "uint256", "name": "amount", "type": "uint256" + }, + { + "internalType": "address", + "name": "_originalSender", + "type": "address" } ], "name": "coverCost", diff --git a/contracts/KnowledgeCollection.sol b/contracts/KnowledgeCollection.sol index a6546b51..a0714823 100644 --- a/contracts/KnowledgeCollection.sol +++ b/contracts/KnowledgeCollection.sol @@ -118,7 +118,7 @@ contract KnowledgeCollection is INamed, IVersioned, ContractStatus, IInitializab es.addTokensToEpochRange(1, currentEpoch, currentEpoch + epochs + 1, tokenAmount); es.addEpochProducedKnowledgeValue(publisherNodeIdentityId, currentEpoch, tokenAmount); - _addTokens(tokenAmount, paymaster); + _addTokens(tokenAmount, paymaster, msg.sender); return id; } @@ -223,7 +223,7 @@ contract KnowledgeCollection is INamed, IVersioned, ContractStatus, IInitializab epochStorage.addTokensToEpochRange(1, endEpoch, endEpoch + epochs, tokenAmount); - _addTokens(tokenAmount, paymaster); + _addTokens(tokenAmount, paymaster, msg.sender); ParanetKnowledgeCollectionsRegistry pkar = paranetKnowledgeCollectionsRegistry; @@ -300,11 +300,11 @@ contract KnowledgeCollection is INamed, IVersioned, ContractStatus, IInitializab } } - function _addTokens(uint96 tokenAmount, address paymaster) internal { + function _addTokens(uint96 tokenAmount, address paymaster, address originalSender) internal { IERC20 token = tokenContract; if (paymasterManager.validPaymasters(paymaster)) { - IPaymaster(paymaster).coverCost(tokenAmount); + IPaymaster(paymaster).coverCost(tokenAmount, originalSender); } else { if (token.allowance(msg.sender, address(this)) < tokenAmount) { revert TokenLib.TooLowAllowance( diff --git a/contracts/Paymaster.sol b/contracts/Paymaster.sol index f213e315..7419651d 100644 --- a/contracts/Paymaster.sol +++ b/contracts/Paymaster.sol @@ -7,7 +7,7 @@ import {TokenLib} from "./libraries/TokenLib.sol"; import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -contract Paymaster is Ownable(msg.sender) { +contract Paymaster is Ownable { error NotAllowed(); Hub public hub; @@ -15,24 +15,31 @@ contract Paymaster is Ownable(msg.sender) { mapping(address => bool) public allowedAddresses; - modifier onlyAllowed() { - if (!allowedAddresses[msg.sender]) { + event AllowedAddressAdded(address _address); + event AllowedAddressRemoved(address _address); + event FundsAdded(address sender, uint256 amount); + event WhitdrawalMade(address recipient, uint256 amount); + + modifier onlyAllowed(address _originalSender) { + if (!allowedAddresses[_originalSender]) { revert NotAllowed(); } _; } - constructor(address hubAddress) { + constructor(address hubAddress, address initialOwner) Ownable(initialOwner) { hub = Hub(hubAddress); tokenContract = IERC20(hub.getContractAddress("Token")); } function addAllowedAddress(address _address) external onlyOwner { allowedAddresses[_address] = true; + emit AllowedAddressAdded(_address); } function removeAllowedAddress(address _address) external onlyOwner { allowedAddresses[_address] = false; + emit AllowedAddressRemoved(_address); } function fundPaymaster(uint256 amount) external { @@ -53,14 +60,23 @@ contract Paymaster is Ownable(msg.sender) { if (!tokenContract.transferFrom(msg.sender, address(this), amount)) { revert TokenLib.TransferFailed(); } + + emit FundsAdded(msg.sender, amount); } function withdraw(address recipient, uint256 amount) external onlyOwner { _transferTokens(recipient, amount); + emit WhitdrawalMade(recipient, amount); } - function coverCost(uint256 amount) external onlyAllowed { - _transferTokens(hub.getContractAddress("KnowledgeCollection"), amount); + function coverCost(uint256 amount, address _originalSender) external onlyAllowed(_originalSender) { + address knowledgeCollectionAddress = hub.getContractAddress("KnowledgeCollection"); + + if (msg.sender != knowledgeCollectionAddress) { + revert("Sender is not the KnowledgeCollection contract"); + } + + _transferTokens(knowledgeCollectionAddress, amount); } function _transferTokens(address to, uint256 amount) internal { diff --git a/contracts/interfaces/IPaymaster.sol b/contracts/interfaces/IPaymaster.sol index 48aa2929..3f404157 100644 --- a/contracts/interfaces/IPaymaster.sol +++ b/contracts/interfaces/IPaymaster.sol @@ -9,5 +9,5 @@ interface IPaymaster { function fundPaymaster(uint256 amount) external; - function coverCost(uint256 amount) external; + function coverCost(uint256 amount, address originalSender) external; } diff --git a/contracts/storage/PaymasterManager.sol b/contracts/storage/PaymasterManager.sol index 369505df..33fa05a0 100644 --- a/contracts/storage/PaymasterManager.sol +++ b/contracts/storage/PaymasterManager.sol @@ -27,7 +27,7 @@ contract PaymasterManager is INamed, IVersioned, ContractStatus { } function deployPaymaster() external { - address paymasterAddress = address(new Paymaster(address(hub))); + address paymasterAddress = address(new Paymaster(address(hub), msg.sender)); validPaymasters[paymasterAddress] = true; deployedPaymasters[msg.sender].push(paymasterAddress); diff --git a/test/helpers/paymaster-helpers.ts b/test/helpers/paymaster-helpers.ts new file mode 100644 index 00000000..00807aaf --- /dev/null +++ b/test/helpers/paymaster-helpers.ts @@ -0,0 +1,32 @@ +import { SignerWithAddress } from '@nomicfoundation/hardhat-ethers/signers'; +import { PaymasterManager } from '../../typechain'; + +export async function createPaymaster( + paymasterCreator: SignerWithAddress, + paymasterManager: PaymasterManager +) { + const tx = await paymasterManager.connect(paymasterCreator).deployPaymaster(); + const receipt = await tx.wait(); + + const paymasterDeployedEvent = receipt!.logs.find( + log => log.topics[0] === paymasterManager.interface.getEvent('PaymasterDeployed').topicHash + ); + + if (!paymasterDeployedEvent) { + throw new Error('PaymasterDeployed event not found in transaction logs'); + } + + const parsedEvent = paymasterManager.interface.parseLog({ + topics: paymasterDeployedEvent.topics as string[], + data: paymasterDeployedEvent.data + }); + + if (!parsedEvent) { + throw new Error('Failed to parse PaymasterDeployed event'); + } + + const deployer = parsedEvent.args.deployer; + const paymasterAddress = parsedEvent.args.paymasterAddress; + + return {deployer, paymasterAddress} +}; diff --git a/test/integration/Paymaster.test.ts b/test/integration/Paymaster.test.ts new file mode 100644 index 00000000..27ba6c51 --- /dev/null +++ b/test/integration/Paymaster.test.ts @@ -0,0 +1,432 @@ +import { SignerWithAddress } from '@nomicfoundation/hardhat-ethers/signers'; +import { loadFixture } from '@nomicfoundation/hardhat-network-helpers'; +import { expect } from 'chai'; +import { ethers } from 'ethers'; +import hre from 'hardhat'; + +import { + Paranet, + ParanetsRegistry, + ParanetServicesRegistry, + ParanetKnowledgeMinersRegistry, + ParanetKnowledgeCollectionsRegistry, + ParanetIncentivesPoolFactory, + KnowledgeCollection, + KnowledgeCollectionStorage, + Profile, + Token, + Hub, + EpochStorage, + ParanetIncentivesPoolFactoryHelper, + ParanetStagingRegistry, + IdentityStorage, + HubLib, + ParanetLib, + PaymasterManager, +} from '../../typechain'; +import { + createPaymaster, +} from '../helpers/paymaster-helpers'; +import { + getDefaultPublishingNode, + getDefaultReceivingNodes, + getDefaultKCCreator, +} from '../helpers/setup-helpers'; +import { + createProfile, createProfiles, +} from '../helpers/profile-helpers'; +import { + createKnowledgeCollection, +} from '../helpers/kc-helpers'; + + +// Fixture containing all contracts and accounts needed to test Paranet +type PaymasterFixture = { + accounts: SignerWithAddress[]; + Paranet: Paranet; + ParanetsRegistry: ParanetsRegistry; + ParanetServicesRegistry: ParanetServicesRegistry; + ParanetKnowledgeMinersRegistry: ParanetKnowledgeMinersRegistry; + ParanetKnowledgeCollectionsRegistry: ParanetKnowledgeCollectionsRegistry; + ParanetIncentivesPoolFactoryHelper: ParanetIncentivesPoolFactoryHelper; + ParanetIncentivesPoolFactory: ParanetIncentivesPoolFactory; + KnowledgeCollection: KnowledgeCollection; + KnowledgeCollectionStorage: KnowledgeCollectionStorage; + Profile: Profile; + Token: Token; + EpochStorage: EpochStorage; + ParanetStagingRegistry: ParanetStagingRegistry; + IdentityStorage: IdentityStorage; + HubLib: HubLib; + ParanetLib: ParanetLib; + PaymasterManager: PaymasterManager, +}; + +describe('@integration Paymaster', () => { + let accounts: SignerWithAddress[]; + let Paranet: Paranet; + let ParanetsRegistry: ParanetsRegistry; + let ParanetServicesRegistry: ParanetServicesRegistry; + let ParanetKnowledgeMinersRegistry: ParanetKnowledgeMinersRegistry; + let ParanetKnowledgeCollectionsRegistry: ParanetKnowledgeCollectionsRegistry; + let ParanetIncentivesPoolFactoryHelper: ParanetIncentivesPoolFactoryHelper; + let ParanetIncentivesPoolFactory: ParanetIncentivesPoolFactory; + let KnowledgeCollection: KnowledgeCollection; + let KnowledgeCollectionStorage: KnowledgeCollectionStorage; + let Profile: Profile; + let Token: Token; + let EpochStorage: EpochStorage; + let ParanetStagingRegistry: ParanetStagingRegistry; + let IdentityStorage: IdentityStorage; + let HubLib: HubLib; + let ParanetLib: ParanetLib; + let PaymasterManager: PaymasterManager; + + // Deploy all contracts, set the HubOwner and necessary accounts. Returns the PaymasterFixture + async function deployPaymasterFixture(): Promise { + await hre.deployments.fixture([ + 'Paranet', + 'ParanetsRegistry', + 'ParanetServicesRegistry', + 'ParanetKnowledgeMinersRegistry', + 'ParanetKnowledgeCollectionsRegistry', + 'ParanetIncentivesPoolFactoryHelper', + 'ParanetIncentivesPoolFactory', + 'KnowledgeCollection', + 'Profile', + 'Token', + 'EpochStorage', + 'ParanetStagingRegistry', + 'IdentityStorage', + 'PaymasterManager', + ]); + + accounts = await hre.ethers.getSigners(); + const Hub = await hre.ethers.getContract('Hub'); + await Hub.setContractAddress('HubOwner', accounts[0].address); + + EpochStorage = await hre.ethers.getContract('EpochStorageV8'); + Paranet = await hre.ethers.getContract('Paranet'); + ParanetsRegistry = + await hre.ethers.getContract('ParanetsRegistry'); + ParanetServicesRegistry = + await hre.ethers.getContract( + 'ParanetServicesRegistry', + ); + ParanetKnowledgeMinersRegistry = + await hre.ethers.getContract( + 'ParanetKnowledgeMinersRegistry', + ); + ParanetKnowledgeCollectionsRegistry = + await hre.ethers.getContract( + 'ParanetKnowledgeCollectionsRegistry', + ); + ParanetIncentivesPoolFactoryHelper = + await hre.ethers.getContract( + 'ParanetIncentivesPoolFactoryHelper', + ); + ParanetIncentivesPoolFactory = + await hre.ethers.getContract( + 'ParanetIncentivesPoolFactory', + ); + KnowledgeCollection = await hre.ethers.getContract( + 'KnowledgeCollection', + ); + KnowledgeCollectionStorage = + await hre.ethers.getContract( + 'KnowledgeCollectionStorage', + ); + Profile = await hre.ethers.getContract('Profile'); + // await hre.deployments.deploy('Token', { + // from: accounts[0].address, + // args: ['Neuro', 'NEURO'], + // log: true, + // }); + Token = await hre.ethers.getContract('Token'); + ParanetStagingRegistry = + await hre.ethers.getContract( + 'ParanetStagingRegistry', + ); + IdentityStorage = + await hre.ethers.getContract('IdentityStorage'); + + const hubLibDeployment = await hre.deployments.deploy('HubLib', { + from: accounts[0].address, + log: true, + }); + HubLib = await hre.ethers.getContract( + 'HubLib', + hubLibDeployment.address, + ); + + const paranetLibDeployment = await hre.deployments.deploy('ParanetLib', { + from: accounts[0].address, + log: true, + }); + ParanetLib = await hre.ethers.getContract( + 'ParanetLib', + paranetLibDeployment.address, + ); + PaymasterManager = await hre.ethers.getContract( + 'PaymasterManager', + ); + + return { + accounts, + Paranet, + ParanetsRegistry, + ParanetServicesRegistry, + ParanetKnowledgeMinersRegistry, + ParanetKnowledgeCollectionsRegistry, + ParanetIncentivesPoolFactoryHelper, + ParanetIncentivesPoolFactory, + KnowledgeCollection, + KnowledgeCollectionStorage, + Profile, + Token, + EpochStorage, + ParanetStagingRegistry, + IdentityStorage, + HubLib, + ParanetLib, + PaymasterManager, + }; + } + + // Before each test, deploy all contracts and necessary accounts. These variables can be used in the tests + beforeEach(async () => { + ({ + accounts, + Paranet, + ParanetsRegistry, + ParanetServicesRegistry, + ParanetKnowledgeMinersRegistry, + ParanetKnowledgeCollectionsRegistry, + ParanetIncentivesPoolFactoryHelper, + ParanetIncentivesPoolFactory, + KnowledgeCollection, + KnowledgeCollectionStorage, + Profile, + Token, + ParanetStagingRegistry, + HubLib, + ParanetLib, + } = await loadFixture(deployPaymasterFixture)); + }); + + it('Should deploy a KC with Paymaster passed', async () => { + const kcCreator = getDefaultKCCreator(accounts); + const paymasterCreator = kcCreator; + + const publishingNode = getDefaultPublishingNode(accounts); + const receivingNodes = getDefaultReceivingNodes(accounts); + + const { deployer, paymasterAddress } = await createPaymaster(paymasterCreator, PaymasterManager); + + const paymaster = await hre.ethers.getContractAt('Paymaster', paymasterAddress); + + await paymaster.connect(kcCreator).addAllowedAddress(kcCreator.address); + expect(await paymaster.allowedAddresses(kcCreator.address)).to.be.true; + + const tokenAmount = ethers.parseEther('100'); + await Token.connect(kcCreator).approve(paymasterAddress, tokenAmount); + await paymaster.connect(kcCreator).fundPaymaster(tokenAmount); + + expect(await Token.balanceOf(paymasterAddress)).to.equal(tokenAmount); + + const initialPaymasterBalance = await Token.balanceOf(paymasterAddress); + + const paymasterOwner = await paymaster.owner(); + + expect(deployer).to.equal(paymasterOwner); + expect(kcCreator.address).to.equal(paymasterOwner); + + const { identityId: publishingNodeIdentityId } = await createProfile( + Profile, + publishingNode, + ); + const receivingNodesIdentityIds = ( + await createProfiles(Profile, receivingNodes) + ).map((p) => p.identityId); + + const { collectionId } = await createKnowledgeCollection( + kcCreator, + publishingNode, + publishingNodeIdentityId, + receivingNodes, + receivingNodesIdentityIds, + { KnowledgeCollection, Token }, + undefined, + undefined, + undefined, + undefined, + undefined, + tokenAmount, + undefined, + paymasterAddress + ); + // Verify that the paymaster's token balance has decreased by the token amount + // This confirms that tokens were transferred from the paymaster to the KC contract + const finalPaymasterBalance = await Token.balanceOf(paymasterAddress); + expect(finalPaymasterBalance).to.equal(initialPaymasterBalance - tokenAmount); + + // Verify that the KC storage has the correct token amount for the collection + const storedTokenAmount = await KnowledgeCollectionStorage.getTokenAmount(collectionId); + expect(storedTokenAmount).to.equal(tokenAmount); + + // Verify that the KC was created with the correct token amount + const collectionData = await KnowledgeCollectionStorage.getKnowledgeCollection(collectionId); + expect(collectionData.tokenAmount).to.equal(tokenAmount); + + // Verify that the KC creator's balance should not have changed since the paymaster covered the cost + const kcCreatorBalance = await Token.balanceOf(kcCreator.address); + // The KC creator's balance should not have changed since the paymaster covered the cost + expect(kcCreatorBalance).to.be.gt(0); // Just verify the creator has tokens + }); + + it('Whitelisted users can publish and pay', async () => { + const kcCreator = getDefaultKCCreator(accounts); + const paymasterCreator = kcCreator; + const publishingNode = getDefaultPublishingNode(accounts); + const receivingNodes = getDefaultReceivingNodes(accounts); + + const whitelistedUser = accounts[8]; + + const { paymasterAddress } = await createPaymaster(paymasterCreator, PaymasterManager); + const paymaster = await hre.ethers.getContractAt('Paymaster', paymasterAddress); + + const tokenAmount = ethers.parseEther('100'); + await Token.connect(kcCreator).approve(paymasterAddress, tokenAmount); + await paymaster.connect(kcCreator).fundPaymaster(tokenAmount); + + expect(await paymaster.allowedAddresses(whitelistedUser.address)).to.be.false; + + await paymaster.connect(kcCreator).addAllowedAddress(whitelistedUser.address); + + expect(await paymaster.allowedAddresses(whitelistedUser.address)).to.be.true; + + await Token.connect(kcCreator).transfer(whitelistedUser.address, ethers.parseEther('10')); + + const { identityId: publishingNodeIdentityId } = await createProfile( + Profile, + publishingNode, + ); + const receivingNodesIdentityIds = ( + await createProfiles(Profile, receivingNodes) + ).map((p) => p.identityId); + + const { collectionId } = await createKnowledgeCollection( + whitelistedUser, + publishingNode, + publishingNodeIdentityId, + receivingNodes, + receivingNodesIdentityIds, + { KnowledgeCollection, Token }, + undefined, + undefined, + undefined, + undefined, + undefined, + tokenAmount, + undefined, + paymasterAddress + ); + // Check that we can retrieve the collection and it has valid data + const collectionData = await KnowledgeCollectionStorage.getKnowledgeCollection(collectionId); + expect(collectionData.tokenAmount).to.equal(tokenAmount); + + // Check that token amount is properly set + const storedTokenAmount = await KnowledgeCollectionStorage.getTokenAmount(collectionId); + expect(storedTokenAmount).to.equal(tokenAmount); + + // Make sure paymaster was used correctly + const finalPaymasterBalance = await Token.balanceOf(paymasterAddress); + expect(finalPaymasterBalance).to.equal(0); // All tokens should have been spent + }); + + it('Non-whitelisted accounts cannot use the paymaster', async () => { + const kcCreator = getDefaultKCCreator(accounts); + const publishingNode = getDefaultPublishingNode(accounts); + const receivingNodes = getDefaultReceivingNodes(accounts); + const nonWhitelistedUser = accounts[7]; // This user will not be whitelisted + // Deploy the paymaster owned by kcCreator + const { paymasterAddress } = await createPaymaster(kcCreator, PaymasterManager); + const paymaster = await hre.ethers.getContractAt('Paymaster', paymasterAddress); + + // Fund the paymaster with tokens + const tokenAmount = ethers.parseEther('100'); + await Token.connect(kcCreator).approve(paymasterAddress, tokenAmount); + await paymaster.connect(kcCreator).fundPaymaster(tokenAmount); + + // Verify the non-whitelisted user is indeed not whitelisted + expect(await paymaster.allowedAddresses(nonWhitelistedUser.address)).to.be.false; + + // Give the non-whitelisted user some tokens to create profiles + await Token.connect(kcCreator).transfer(nonWhitelistedUser.address, ethers.parseEther('10')); + + const { identityId: publishingNodeIdentityId } = await createProfile( + Profile, + publishingNode, + ); + const receivingNodesIdentityIds = ( + await createProfiles(Profile, receivingNodes) + ).map((p) => p.identityId); + + // When a non-whitelisted user tries to use the paymaster, the transaction should revert + await expect( + createKnowledgeCollection( + nonWhitelistedUser, + publishingNode, + publishingNodeIdentityId, + receivingNodes, + receivingNodesIdentityIds, + { KnowledgeCollection, Token }, + undefined, + undefined, + undefined, + undefined, + undefined, + tokenAmount, + undefined, + paymasterAddress + ) + ).to.be.revertedWithCustomError(paymaster, 'NotAllowed'); + // Verify paymaster's balance hasn't changed (no tokens were spent) + const finalPaymasterBalance = await Token.balanceOf(paymasterAddress); + expect(finalPaymasterBalance).to.equal(tokenAmount); + }); + + it('Non KnowledgeCollection address cant call coverCost', async () => { + const kcCreator = getDefaultKCCreator(accounts); + const nonKC = accounts[6]; // This will be our non-KnowledgeCollection address + + // Deploy the paymaster owned by kcCreator + const { paymasterAddress } = await createPaymaster(kcCreator, PaymasterManager); + const paymaster = await hre.ethers.getContractAt('Paymaster', paymasterAddress); + + // Fund the paymaster with tokens + const tokenAmount = ethers.parseEther('100'); + await Token.connect(kcCreator).approve(paymasterAddress, tokenAmount); + await paymaster.connect(kcCreator).fundPaymaster(tokenAmount); + + // Get the actual KnowledgeCollection address from the Hub + const hub = await hre.ethers.getContract('Hub'); + const knowledgeCollectionAddress = await hub.getContractAddress('KnowledgeCollection'); + + // Verify that nonKC is not the KnowledgeCollection address + expect(nonKC.address).to.not.equal(knowledgeCollectionAddress); + + await paymaster.connect(kcCreator).addAllowedAddress(nonKC.address); + + expect(await paymaster.allowedAddresses(nonKC.address)).to.be.true; + + // Try to call coverCost from nonKC address + await expect( + paymaster.connect(nonKC).coverCost(ethers.parseEther('10'), nonKC.address) + ).to.be.revertedWith('Sender is not the KnowledgeCollection contract'); + + // Verify paymaster's balance hasn't changed (no tokens were spent) + const finalPaymasterBalance = await Token.balanceOf(paymasterAddress); + expect(finalPaymasterBalance).to.equal(tokenAmount); + }); +}); diff --git a/test/unit/Paymaster.test.ts b/test/unit/Paymaster.test.ts index 547fd0bf..f5d33a8d 100644 --- a/test/unit/Paymaster.test.ts +++ b/test/unit/Paymaster.test.ts @@ -13,7 +13,7 @@ describe('@unit Paymaster', () => { let Paymaster: Paymaster; let owner: SignerWithAddress; let user: SignerWithAddress; - let knowledgeCollectionAddress: string; + let knowledgeCollection: SignerWithAddress; async function deployPaymasterFixture() { await hre.deployments.fixture(['Hub', 'Token']); @@ -26,13 +26,13 @@ describe('@unit Paymaster', () => { // Deploy Paymaster const PaymasterFactory = await hre.ethers.getContractFactory('Paymaster'); - Paymaster = await PaymasterFactory.deploy(Hub.getAddress()); + Paymaster = await PaymasterFactory.deploy(Hub.getAddress(), owner); // Set mock KnowledgeCollection address in Hub - knowledgeCollectionAddress = accounts[3].address; + knowledgeCollection = accounts[3]; await Hub.setContractAddress( 'KnowledgeCollection', - knowledgeCollectionAddress, + knowledgeCollection.address, ); // Reset user's balance to zero first @@ -179,32 +179,39 @@ describe('@unit Paymaster', () => { }); it('Should allow allowed address to cover cost', async () => { - await expect(Paymaster.connect(user).coverCost(coverAmount)) + await expect(Paymaster.connect(knowledgeCollection).coverCost(coverAmount, user.address)) .to.emit(Token, 'Transfer') .withArgs( await Paymaster.getAddress(), - knowledgeCollectionAddress, + knowledgeCollection.address, coverAmount, ); }); it('Should revert when non-allowed address tries to cover cost', async () => { - const nonAllowed = accounts[4]; + const notAllowed = accounts[4]; await expect( - Paymaster.connect(nonAllowed).coverCost(coverAmount), + Paymaster.connect(knowledgeCollection).coverCost(coverAmount, notAllowed), ).to.be.revertedWithCustomError(Paymaster, 'NotAllowed'); }); + it('Should revert when non-KnowledgeCollection contract address tries to cover cost', async () => { + const notKnowledgeCollection = accounts[4]; + await expect( + Paymaster.connect(notKnowledgeCollection).coverCost(coverAmount, user.address), + ).to.be.revertedWith('Sender is not the KnowledgeCollection contract'); + }); + it('Should revert with zero amount', async () => { await expect( - Paymaster.connect(user).coverCost(0), + Paymaster.connect(knowledgeCollection).coverCost(0, user.address), ).to.be.revertedWithCustomError(Paymaster, 'ZeroTokenAmount'); }); it('Should revert with insufficient balance', async () => { const tooMuch = parseEther('200'); await expect( - Paymaster.connect(user).coverCost(tooMuch), + Paymaster.connect(knowledgeCollection).coverCost(tooMuch, user.address), ).to.be.revertedWithCustomError(Paymaster, 'TooLowBalance'); }); });