Skip to content

Commit

Permalink
Adds tests and refactors paymaster.
Browse files Browse the repository at this point in the history
  • Loading branch information
jamesduncombe committed Oct 30, 2023
1 parent 3f9b859 commit b9cbec1
Show file tree
Hide file tree
Showing 8 changed files with 665 additions and 93 deletions.
2 changes: 0 additions & 2 deletions contracts/interfaces/ICustomErrors.sol
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,7 @@ interface ICustomErrors {
error InconsistentParameter(string param);
error InsufficientFunds(uint256 amount);
error InternalMethod();
error InvalidApprovalDataLength();
error InvalidCrowdfundBasisPointsFee(uint32 fee);
error InvalidPaymasterDataLength();
error InvalidPhase();
error NonExistentEntry();
error OutOfBounds();
Expand Down
173 changes: 87 additions & 86 deletions contracts/paymaster/PaymasterTopFacet.sol
Original file line number Diff line number Diff line change
@@ -1,37 +1,29 @@
// SPDX-License-Identifier: GPL-3.0-only
pragma solidity 0.8.10;

/**
* Note this is an "unwrapped" version of `BasePaymaster.sol` from the OpenGSN repo.
* Original license: GPL-3.0-only.
*/

import "./lib/LibPaymaster.sol";
import "../common/AHasMembers.sol";
import "../interfaces/ICustomErrors.sol";
import "./lib/APaymasterFacet.sol";
import "./lib/IPaymasterErrors.sol";

import "@openzeppelin/contracts/utils/introspection/ERC165Checker.sol";
import "@openzeppelin/contracts/utils/introspection/IERC165.sol";

import "@opengsn/contracts/src/utils/GsnTypes.sol";
import "@opengsn/contracts/src/interfaces/IPaymaster.sol";
import "@opengsn/contracts/src/interfaces/IRelayHub.sol";
import "@opengsn/contracts/src/utils/GsnEip712Library.sol";
import "@opengsn/contracts/src/forwarder/IForwarder.sol";

// import "hardhat/console.sol";

contract PaymasterTopFacet is IPaymaster {
using ERC165Checker for address;

/// Errors.
// TODO: Move to ICustomErrors?

error ApprovalDataNotEmpty();
error ForwarderNotTrusted(address);
error InterfaceNotSupported(string);
error RelayHubAddressNotSet();
error RequiresRelayHubCaller();
error ValueTransferNotSupported();
import "hardhat/console.sol";

/**
*
* @notice The top-level Paymaster contract.
* Note this is an "unwrapped" version of `BasePaymaster.sol` from the OpenGSN repo.
* Original license: GPL-3.0-only.
*
*/
contract PaymasterTopFacet is APaymasterFacet, IPaymaster {
IRelayHub internal relayHub;
address private _trustedForwarder;

Expand Down Expand Up @@ -60,6 +52,11 @@ contract PaymasterTopFacet is IPaymaster {
return address(relayHub);
}

/// @inheritdoc IPaymaster
function getTrustedForwarder() public view override(IPaymaster) returns (address) {
return _trustedForwarder;
}

/// @inheritdoc IPaymaster
function getGasAndDataLimits() public pure override(IPaymaster) returns (IPaymaster.GasAndDataLimits memory limits) {
return
Expand All @@ -71,36 +68,84 @@ contract PaymasterTopFacet is IPaymaster {
);
}

/**
* This method must be called from preRelayedCall to validate that the forwarder
* is approved by the paymaster as well as by the recipient contract.
* @inheritdoc IPaymaster
*/
function preRelayedCall(
GsnTypes.RelayRequest calldata relayRequest,
bytes calldata /* signature */,
bytes calldata approvalData,
uint256 /* maxPossibleGas */
) external view override(IPaymaster) onlyRelayHub returns (bytes memory, bool) {
if (getTrustedForwarder() != relayRequest.relayData.forwarder)
revert IPaymasterErrors.ForwarderNotTrusted(relayRequest.relayData.forwarder);

// SEE: `verifyForwarderTrusted`:
GsnEip712Library.verifyForwarderTrusted(relayRequest);

if (relayRequest.request.value != 0) revert IPaymasterErrors.ValueTransferNotSupported();
if (relayRequest.relayData.paymasterData.length != 0) revert IPaymasterErrors.InvalidPaymasterDataLength();
if (approvalData.length != 0) revert IPaymasterErrors.InvalidApprovalDataLength();

// /=-@=-@=-@=-@=-@=-@=-@=-@=-@=-@=-@=-@=-@=-@/

// Check for marketplace membership.
LibPaymaster.Data storage s = LibPaymaster.data();
if (!AHasMembers(s.marketplace).isMember(relayRequest.request.from))
revert ICustomErrors.RequiresMarketplaceMembership(relayRequest.request.from);

// /~@^~@^~@^~@^~@^~@^~@^~@^~@^~@^~@^~@^~@^~@^/

// See the documentation for `IPaymaster::preRelayedCall` for details.
return ("", useRejectOnRecipientRevert);
}

/// @inheritdoc IPaymaster
function postRelayedCall(
bytes calldata context,
bool success,
uint256 gasUseWithoutPost,
GsnTypes.RelayData calldata relayData
) external view override(IPaymaster) onlyRelayHub {
// See the documentation for `IPaymaster::postRelayedCall` for details.
(context, success, gasUseWithoutPost, relayData);
}

/// @inheritdoc IPaymaster
function versionPaymaster() external pure override(IPaymaster) returns (string memory) {
return "3.0.0-beta.9+opengsn.tokensphere.ipaymaster";
}

/// Setters and utility methods.

/**
* @notice The owner of the Paymaster can change the instance of the RelayHub this Paymaster works with.
* :warning: **Warning** :warning: The deposit on the previous RelayHub must be withdrawn first.
*/
// TODO: Add MODIFIER - ONLY OWNER
function setRelayHub(IRelayHub hub) public {
if (!address(hub).supportsInterface(type(IRelayHub).interfaceId)) revert InterfaceNotSupported("IRelayHub");
function setRelayHub(IRelayHub hub) public onlyOwner {
if (!IERC165(address(hub)).supportsInterface(type(IRelayHub).interfaceId))
revert IPaymasterErrors.InterfaceNotSupported("IRelayHub");
relayHub = hub;
}

/**
* @notice The owner of the Paymaster can change the instance of the Forwarder this Paymaster works with.
* @notice the Recipients must trust this Forwarder as well in order for the configuration to remain functional.
*/
// TODO: Add MODIFIER - ONLY OWNER
function setTrustedForwarder(address forwarder) public {
if (!forwarder.supportsInterface(type(IForwarder).interfaceId)) revert InterfaceNotSupported("IForwarder");
function setTrustedForwarder(address forwarder) public onlyOwner {
if (!IERC165(forwarder).supportsInterface(type(IForwarder).interfaceId))
revert IPaymasterErrors.InterfaceNotSupported("IForwarder");
_trustedForwarder = forwarder;
}

function getTrustedForwarder() public view override returns (address) {
return _trustedForwarder;
}

/**
* @notice Any native Ether/MATIC transferred into the paymaster is transferred as a deposit to the RelayHub.
* @notice Any native Ether/MATIC transferred into the Paymaster is transferred as a deposit to the RelayHub.
* This way, we don't need to understand the RelayHub API in order to replenish the paymaster.
*/
receive() external payable {
if (address(relayHub) == address(0)) revert RelayHubAddressNotSet();
function deposit() external payable {
if (address(relayHub) == address(0)) revert IPaymasterErrors.RelayHubAddressNotSet();
relayHub.depositFor{value: msg.value}(address(this));
}

Expand All @@ -109,64 +154,20 @@ contract PaymasterTopFacet is IPaymaster {
* @param amount The amount to be subtracted from the sender.
* @param target The target to which the amount will be transferred.
*/
// TODO: Add MODIFIER - ONLY OWNER
function withdrawRelayHubDepositTo(uint256 amount, address payable target) public {
function withdrawRelayHubDepositTo(uint256 amount, address payable target) public onlyOwner {
relayHub.withdraw(target, amount);
}

/// @inheritdoc IPaymaster
function preRelayedCall(
GsnTypes.RelayRequest calldata relayRequest,
bytes calldata /* signature */,
bytes calldata approvalData,
uint256 /* maxPossibleGas */
) external view override(IPaymaster) verifyRelayHubOnly returns (bytes memory, bool) {
/**
* This method must be called from preRelayedCall to validate that the forwarder
* is approved by the paymaster as well as by the recipient contract.
*/

if (getTrustedForwarder() != relayRequest.relayData.forwarder)
revert ForwarderNotTrusted(relayRequest.relayData.forwarder);
// TODO: Check what this calls...
GsnEip712Library.verifyForwarderTrusted(relayRequest);

if (relayRequest.request.value != 0) revert ValueTransferNotSupported();
if (relayRequest.relayData.paymasterData.length != 0) revert ICustomErrors.InvalidPaymasterDataLength();
if (approvalData.length != 0) revert ApprovalDataNotEmpty();

/**
* Internal logic the paymasters need to provide to select which transactions they are willing to pay for
* see the documentation for `IPaymaster::preRelayedCall` for details
*/
if (approvalData.length == 0) revert ICustomErrors.InvalidApprovalDataLength();
if (relayRequest.relayData.paymasterData.length == 0) revert ICustomErrors.InvalidPaymasterDataLength();

return ("", useRejectOnRecipientRevert);
}

/// @inheritdoc IPaymaster
function postRelayedCall(
bytes calldata context,
bool success,
uint256 gasUseWithoutPost,
GsnTypes.RelayData calldata relayData
) external view override(IPaymaster) verifyRelayHubOnly {
/**
* Internal logic the paymasters need to provide if they need to take some action after the transaction
* see the documentation for `IPaymaster::postRelayedCall` for details
*/
(context, success, gasUseWithoutPost, relayData);
}
/// Modifiers.

function versionPaymaster() external pure override(IPaymaster) returns (string memory) {
return "3.0.0-beta.9+opengsn.tokensphere.ipaymaster";
modifier onlyRelayHub() virtual {
if (msg.sender != getRelayHub()) revert IPaymasterErrors.RequiresRelayHubCaller();
_;
}

/// Modifiers.

modifier verifyRelayHubOnly() virtual {
if (msg.sender != getRelayHub()) revert RequiresRelayHubCaller();
// TODO: Who is owner?
modifier onlyOwner() {
// if (msg.sender != LibPaymaster.data().owner) revert ICustomErrors.RequiresOwner();
_;
}
}
5 changes: 2 additions & 3 deletions contracts/paymaster/lib/APaymasterFacet.sol
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.10;

import "../../common/AHasMembers.sol";
import "../../interfaces/ICustomErrors.sol";
import "./LibPaymaster.sol";
import "../../lib/LibHelpers.sol";
import "../lib/LibPaymaster.sol";
import "../../interfaces/ICustomErrors.sol";

/**
* @notice This contract is a group of modifiers that can be used by any Paymaster facets to guard against
Expand Down
15 changes: 15 additions & 0 deletions contracts/paymaster/lib/IPaymasterErrors.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.10;

/**
* @notice Errors that can occur within the paymaster.
*/
interface IPaymasterErrors {
error ForwarderNotTrusted(address);
error InterfaceNotSupported(string);
error InvalidPaymasterDataLength();
error InvalidApprovalDataLength();
error RelayHubAddressNotSet();
error RequiresRelayHubCaller();
error ValueTransferNotSupported();
}
3 changes: 1 addition & 2 deletions hardhat.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -127,8 +127,7 @@ const config: HardhatUserConfig = {
// Event types.
// ...
// Error types.
["Facet$", "InvalidApprovalDataLength()"],
["Facet$", "InvalidPaymasterDataLength()"],
// ...
]),
include: [
"IERC173",
Expand Down
79 changes: 79 additions & 0 deletions test/fixtures/paymaster.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import { ethers } from "hardhat";
import { MockContract } from "@defi-wonderland/smock";
import { FixtureFunc } from "hardhat-deploy/dist/types";
import { deploymentSalt, ZERO_ADDRESS } from "../../src/utils";
import { facetMock } from "../utils";
import {
PaymasterTopFacet,
PaymasterInitFacet,
PaymasterTopFacet__factory,
} from "../../typechain";
import { PAYMASTER_FACETS } from "../../tasks/paymaster";
import { Paymaster } from "../../typechain/hardhat-diamond-abi/HardhatDiamondABI.sol";

export const PAYMASTER_INIT_DEFAULTS: PaymasterInitFacet.InitializerParamsStruct =
{
marketplace: ZERO_ADDRESS,
};

interface PaymasterFixtureResult {
paymaster: Paymaster;
topMock: MockContract<PaymasterTopFacet>;
}
interface PaymasterFixtureOpts {
readonly name: string;
readonly deployer: string;
readonly afterDeploy: (result: PaymasterFixtureResult) => void;
}
interface PaymasterFixtureFuncArgs {
readonly initWith: {};
readonly opts: PaymasterFixtureOpts;
}

export const paymasterFixtureFunc: FixtureFunc<
PaymasterFixtureResult,
PaymasterFixtureFuncArgs
> = async (hre, opts) => {
// opts could be `undefined`.
if (!opts) throw Error("You must provide Paymaster fixture options.");
const {
opts: { deployer, name, afterDeploy },
initWith,
} = opts;
// Deploy diamond.
const { address: paymasterAddr } = await hre.deployments.diamond.deploy(
name,
{
from: deployer,
owner: deployer,
facets: [...PAYMASTER_FACETS, "PaymasterInitFacet"],
execute: {
contract: "PaymasterInitFacet",
methodName: "initialize",
args: [{ ...PAYMASTER_INIT_DEFAULTS, ...initWith }],
},
deterministicSalt: deploymentSalt(hre),
excludeSelectors: {
"PaymasterTopFacet": ["supportsInterface"]
}
}
);

// Get a Paymaster typed pointer.
const paymaster = await ethers.getContractAt<Paymaster>(
"Paymaster",
paymasterAddr
);
// Build result.
const result: PaymasterFixtureResult = {
paymaster,
topMock: await facetMock<PaymasterTopFacet__factory>(
paymaster,
"PaymasterTopFacet"
),
};
// Callback!
await afterDeploy.apply(this, [result]);
// Final return.
return result;
};
Loading

0 comments on commit b9cbec1

Please sign in to comment.