Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions abi/IPaymaster.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,11 @@
"internalType": "uint256",
"name": "amount",
"type": "uint256"
},
{
"internalType": "address",
"name": "originalSender",
"type": "address"
}
],
"name": "coverCost",
Expand Down
10 changes: 10 additions & 0 deletions abi/Paymaster.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,11 @@
"internalType": "address",
"name": "hubAddress",
"type": "address"
},
{
"internalType": "address",
"name": "initialOwner",
"type": "address"
}
],
"stateMutability": "nonpayable",
Expand Down Expand Up @@ -146,6 +151,11 @@
"internalType": "uint256",
"name": "amount",
"type": "uint256"
},
{
"internalType": "address",
"name": "_originalSender",
"type": "address"
}
],
"name": "coverCost",
Expand Down
8 changes: 4 additions & 4 deletions contracts/KnowledgeCollection.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down Expand Up @@ -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;

Expand Down Expand Up @@ -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(
Expand Down
28 changes: 22 additions & 6 deletions contracts/Paymaster.sol
Original file line number Diff line number Diff line change
Expand Up @@ -7,32 +7,39 @@ 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 {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be good to emit events when adding or removing from allowed addresses. Same for funding and withdrawing

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should add the transfer ownership method to Paymaster

error NotAllowed();

Hub public hub;
IERC20 public tokenContract;

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 {
Expand All @@ -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);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't we transfer tokens to the StakingStorage like we are doing in KnowledgeCollection.sol if paymaster is not valid?

}

function _transferTokens(address to, uint256 amount) internal {
Expand Down
2 changes: 1 addition & 1 deletion contracts/interfaces/IPaymaster.sol
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,5 @@ interface IPaymaster {

function fundPaymaster(uint256 amount) external;

function coverCost(uint256 amount) external;
function coverCost(uint256 amount, address originalSender) external;
}
2 changes: 1 addition & 1 deletion contracts/storage/PaymasterManager.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can set a paymaster at deployment and add it to validPaymasters and deployedPaymasters. Can we add a function to deactivate/remove them as well? Sounds like a good thing to have in case of compromise or if the owner decides to shut it down

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Btw what do we used deployedPaymasters for? Cannot seem to find the use case for it anywhere


validPaymasters[paymasterAddress] = true;
deployedPaymasters[msg.sender].push(paymasterAddress);
Expand Down
32 changes: 32 additions & 0 deletions test/helpers/paymaster-helpers.ts
Original file line number Diff line number Diff line change
@@ -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}
};
Loading