diff --git a/contracts/fast/FastInitFacet.sol b/contracts/fast/FastInitFacet.sol index 001c844b..d30b0b35 100644 --- a/contracts/fast/FastInitFacet.sol +++ b/contracts/fast/FastInitFacet.sol @@ -7,6 +7,7 @@ import "../common/lib/LibHasAutomatons.sol"; import "../common/AHasGovernors.sol"; import "../common/AHasMembers.sol"; import "../common/AHasAutomatons.sol"; +import "../interfaces/IERC165.sol"; // Interface detection. import "../interfaces/IERC173.sol"; // Ownership. import "../interfaces/IDiamondCut.sol"; // Facet management. import "../interfaces/IDiamondLoupe.sol"; // Facet introspection. @@ -21,8 +22,6 @@ import "./lib/LibFastHistory.sol"; import "./lib/LibFastDistributions.sol"; import "./lib/LibFastCrowdfunds.sol"; -import "@openzeppelin/contracts/interfaces/IERC165.sol"; - /** * @notice NotAlthough this contract doesn't explicitelly inherit from IERC173, ERC165, IDiamondLoupe etc, all * methods are in fact implemented by the underlaying Diamond proxy. It is therefore safe to diff --git a/contracts/interfaces/IERC165.sol b/contracts/interfaces/IERC165.sol new file mode 100644 index 00000000..e5885239 --- /dev/null +++ b/contracts/interfaces/IERC165.sol @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.10; + +/// @title ERC165 definition - interface implementation queryability. +interface IERC165 { + /// @notice Queries if a contract implements an interface + /// @param interfaceId The interface identifier, as specified in ERC165. + /// @notice Interface identification is specified in ERC-165. This method uses less than 30,000 gas. + /// @return A `bool` set to `true` if the contract implements `interfaceID` and + /// `interfaceID` is not 0xffffffff. + function supportsInterface(bytes4 interfaceId) external view returns (bool); +} diff --git a/contracts/issuer/IssuerInitFacet.sol b/contracts/issuer/IssuerInitFacet.sol index 97d7e534..cf374681 100644 --- a/contracts/issuer/IssuerInitFacet.sol +++ b/contracts/issuer/IssuerInitFacet.sol @@ -3,6 +3,7 @@ pragma solidity 0.8.10; import "../common/AHasMembers.sol"; import "../common/AHasAutomatons.sol"; +import "../interfaces/IERC165.sol"; // Interface detection. import "../interfaces/IERC173.sol"; // Ownership. import "../interfaces/IDiamondCut.sol"; // Facet management. import "../interfaces/IDiamondLoupe.sol"; // Facet introspection. @@ -13,8 +14,6 @@ import "./lib/AIssuerFacet.sol"; import "./lib/LibIssuer.sol"; import "./lib/LibIssuerAccess.sol"; -import "@openzeppelin/contracts/interfaces/IERC165.sol"; - /** * @title The Issuer Smart Contract. * @notice The marketplace contract is in charge of keeping track of marketplace members and has logic diff --git a/contracts/marketplace/MarketplaceInitFacet.sol b/contracts/marketplace/MarketplaceInitFacet.sol index a752f269..36db5aab 100644 --- a/contracts/marketplace/MarketplaceInitFacet.sol +++ b/contracts/marketplace/MarketplaceInitFacet.sol @@ -7,6 +7,7 @@ import "../common/lib/LibHasForwarder.sol"; import "../common/AHasMembers.sol"; import "../common/AHasAutomatons.sol"; import "../common/AHasForwarder.sol"; +import "../interfaces/IERC165.sol"; // Interface detection. import "../interfaces/IERC173.sol"; // Ownership. import "../interfaces/IDiamondCut.sol"; // Facet management. import "../interfaces/IDiamondLoupe.sol"; // Facet introspection. @@ -17,8 +18,6 @@ import "./lib/LibMarketplace.sol"; import "./lib/LibMarketplaceAccess.sol"; import "./lib/LibMarketplaceTokenHolders.sol"; -import "@openzeppelin/contracts/interfaces/IERC165.sol"; - /// @notice The Marketplace initialization facet. contract MarketplaceInitFacet is AMarketplaceFacet { /// Initializers. diff --git a/contracts/paymaster/PaymasterInitFacet.sol b/contracts/paymaster/PaymasterInitFacet.sol index b67e07d3..ad96d675 100644 --- a/contracts/paymaster/PaymasterInitFacet.sol +++ b/contracts/paymaster/PaymasterInitFacet.sol @@ -1,6 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.10; +import "../interfaces/IERC165.sol"; // Interface detection. import "../interfaces/IERC173.sol"; // Ownership. import "../interfaces/IDiamondCut.sol"; // Facet management. import "../interfaces/IDiamondLoupe.sol"; // Facet introspection. @@ -9,8 +10,6 @@ import "../lib/LibDiamond.sol"; import "./lib/APaymasterFacet.sol"; import "./lib/LibPaymaster.sol"; -import "@openzeppelin/contracts/interfaces/IERC165.sol"; - /// @notice The Paymaster initialization facet. contract PaymasterInitFacet is APaymasterFacet { /// Initializers. diff --git a/contracts/paymaster/PaymasterTopFacet.sol b/contracts/paymaster/PaymasterTopFacet.sol index 3d58b62c..ac2f354f 100644 --- a/contracts/paymaster/PaymasterTopFacet.sol +++ b/contracts/paymaster/PaymasterTopFacet.sol @@ -4,9 +4,131 @@ pragma solidity 0.8.10; import "./lib/LibPaymaster.sol"; import "./lib/APaymasterFacet.sol"; -import "@opengsn/contracts/src/BasePaymaster.sol"; +import "@openzeppelin/contracts/utils/introspection/ERC165Checker.sol"; + +import "@opengsn/contracts/src/interfaces/IPaymaster.sol"; +import "@opengsn/contracts/src/interfaces/IRelayHub.sol"; +import "@opengsn/contracts/src/forwarder/IForwarder.sol"; +import "@opengsn/contracts/src/utils/GsnEip712Library.sol"; + +contract PaymasterTopFacet is APaymasterFacet, IPaymaster { + using ERC165Checker for address; + + IRelayHub internal relayHub; + address private _trustedForwarder; + + /// @inheritdoc IPaymaster + function getRelayHub() public view returns (address) { + return address(relayHub); + } + + //overhead of forwarder verify+signature, plus hub overhead. + uint256 public constant FORWARDER_HUB_OVERHEAD = 50000; + + //These parameters are documented in IPaymaster.GasAndDataLimits + uint256 public constant PRE_RELAYED_CALL_GAS_LIMIT = 100000; + uint256 public constant POST_RELAYED_CALL_GAS_LIMIT = 110000; + uint256 public constant PAYMASTER_ACCEPTANCE_BUDGET = PRE_RELAYED_CALL_GAS_LIMIT + FORWARDER_HUB_OVERHEAD; + uint256 public constant CALLDATA_SIZE_LIMIT = 10500; + + /// @inheritdoc IERC165 + function supportsInterface(bytes4 interfaceId) public view virtual returns (bool) { + return interfaceId == type(IPaymaster).interfaceId; + } + + /// @inheritdoc IPaymaster + function getGasAndDataLimits() public view virtual returns (IPaymaster.GasAndDataLimits memory limits) { + return + IPaymaster.GasAndDataLimits( + PAYMASTER_ACCEPTANCE_BUDGET, + PRE_RELAYED_CALL_GAS_LIMIT, + POST_RELAYED_CALL_GAS_LIMIT, + CALLDATA_SIZE_LIMIT + ); + } + + /** + * @notice this method must be called from preRelayedCall to validate that the forwarder + * is approved by the paymaster as well as by the recipient contract. + */ + function _verifyForwarder(GsnTypes.RelayRequest calldata relayRequest) internal view virtual { + require(getTrustedForwarder() == relayRequest.relayData.forwarder, "Forwarder is not trusted"); + GsnEip712Library.verifyForwarderTrusted(relayRequest); + } + + function _verifyRelayHubOnly() internal view virtual { + require(msg.sender == getRelayHub(), "can only be called by RelayHub"); + } + + function _verifyValue(GsnTypes.RelayRequest calldata relayRequest) internal view virtual { + require(relayRequest.request.value == 0, "value transfer not supported"); + } + + function _verifyPaymasterData(GsnTypes.RelayRequest calldata relayRequest) internal view virtual { + require(relayRequest.relayData.paymasterData.length == 0, "should have no paymasterData"); + } + + function _verifyApprovalData(bytes calldata approvalData) internal view virtual { + require(approvalData.length == 0, "should have no approvalData"); + } + + /** + * @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. + */ + function setRelayHub(IRelayHub hub) public { + require(address(hub).supportsInterface(type(IRelayHub).interfaceId), "target is not a valid 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. + */ + function setTrustedForwarder(address forwarder) public virtual { + require(forwarder.supportsInterface(type(IForwarder).interfaceId), "target is not a valid IForwarder"); + _trustedForwarder = forwarder; + } + + function getTrustedForwarder() public view virtual returns (address) { + return _trustedForwarder; + } + + /** + * @notice Any native Ether 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 virtual { + require(address(relayHub) != address(0), "relay hub address not set"); + relayHub.depositFor{value: msg.value}(address(this)); + } + + /** + * @notice Withdraw deposit from the RelayHub. + * @param amount The amount to be subtracted from the sender. + * @param target The target to which the amount will be transferred. + */ + function withdrawRelayHubDepositTo(uint256 amount, address payable target) public { + relayHub.withdraw(target, amount); + } + + /// @inheritdoc IPaymaster + function preRelayedCall( + GsnTypes.RelayRequest calldata relayRequest, + bytes calldata signature, + bytes calldata approvalData, + uint256 maxPossibleGas + ) external returns (bytes memory, bool) { + _verifyRelayHubOnly(); + _verifyForwarder(relayRequest); + _verifyValue(relayRequest); + _verifyPaymasterData(relayRequest); + _verifyApprovalData(approvalData); + return _preRelayedCall(relayRequest, signature, approvalData, maxPossibleGas); + } + + ///// OLD SHIZZLE ///// -contract PaymasterTopFacet is APaymasterFacet, BasePaymaster { bool public useRejectOnRecipientRevert = false; // TODO: Do we use the Marketplace at this point to check that the sender is allowed? @@ -15,7 +137,7 @@ contract PaymasterTopFacet is APaymasterFacet, BasePaymaster { bytes calldata signature, bytes calldata approvalData, uint256 maxPossibleGas - ) internal virtual override returns (bytes memory context, bool revertOnRecipientRevert) { + ) internal returns (bytes memory context, bool revertOnRecipientRevert) { (signature, maxPossibleGas); if (approvalData.length == 0) revert ICustomErrors.InvalidApprovalDataLength(); if (relayRequest.relayData.paymasterData.length == 0) revert ICustomErrors.InvalidPaymasterDataLength(); @@ -23,16 +145,27 @@ contract PaymasterTopFacet is APaymasterFacet, BasePaymaster { return ("", useRejectOnRecipientRevert); } + /// @inheritdoc IPaymaster + function postRelayedCall( + bytes calldata context, + bool success, + uint256 gasUseWithoutPost, + GsnTypes.RelayData calldata relayData + ) external { + _verifyRelayHubOnly(); + _postRelayedCall(context, success, gasUseWithoutPost, relayData); + } + function _postRelayedCall( bytes calldata context, bool success, uint256 gasUseWithoutPost, GsnTypes.RelayData calldata relayData - ) internal virtual override { + ) internal { (context, success, gasUseWithoutPost, relayData); } - function versionPaymaster() external view virtual override returns (string memory) { + function versionPaymaster() external view returns (string memory) { return "3.0.0-beta.3+opengsn.accepteverything.ipaymaster"; } } diff --git a/hardhat.config.ts b/hardhat.config.ts index 8603ade1..b15321f8 100644 --- a/hardhat.config.ts +++ b/hardhat.config.ts @@ -52,7 +52,6 @@ const config: HardhatUserConfig = { ["Facet$", "RequiresFastContractCaller()"], ]), include: [ - "IERC165", "IERC173", "IDiamondCut", "IDiamondLoupe", @@ -76,7 +75,6 @@ const config: HardhatUserConfig = { ["Facet$", "RequiresMarketplaceMembership(address)"], ]), include: [ - "IERC165", "IERC173", "IDiamondCut", "IDiamondLoupe", @@ -115,7 +113,6 @@ const config: HardhatUserConfig = { ["Facet$", "InvalidCrowdfundBasisPointFee(uint32)"], ]), include: [ - "IERC165", "IERC173", "IDiamondCut", "IDiamondLoupe", @@ -133,8 +130,7 @@ const config: HardhatUserConfig = { ["Facet$", "InvalidPaymasterDataLength()"], ]), include: [ - // "IERC165", - // "IERC173", + "IERC173", "IDiamondCut", "IDiamondLoupe", ...PAYMASTER_FACETS, diff --git a/tasks/paymaster.ts b/tasks/paymaster.ts index 16f07d37..63ced915 100644 --- a/tasks/paymaster.ts +++ b/tasks/paymaster.ts @@ -86,6 +86,9 @@ const deployPaymaster = async ( }, deterministicSalt: deploymentSalt(hre), log: true, + excludeSelectors: { + "PaymasterTopFacet": ["supportsInterface"] + } }); } // Return a handle to the diamond.