-
Notifications
You must be signed in to change notification settings - Fork 1
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Features: Gasless transactions. #89
Conversation
This pull request has been linked to Shortcut Story #9185: Gasless transactions. |
4ef9d74
to
44c55a8
Compare
8b3a932
to
ccca60d
Compare
Codecov ReportAttention: Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #89 +/- ##
==========================================
+ Coverage 7.52% 79.03% +71.50%
==========================================
Files 47 56 +9
Lines 797 1116 +319
Branches 210 234 +24
==========================================
+ Hits 60 882 +822
+ Misses 737 234 -503 ☔ View full report in Codecov by Sentry. |
} | ||
// Directly import IERC165 from OpenZeppelin contracts. | ||
// Solves an issue with 2 conflicting definitions of IERC165. | ||
import {IERC165} from "@openzeppelin/contracts/interfaces/IERC165.sol"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Very nice reusing something from libs here.
// The current version of the storage. | ||
uint16 internal constant STORAGE_VERSION = 1; | ||
// This is `keccak256('HasForwarder.storage.Main')`. | ||
bytes32 internal constant STORAGE_SLOT = 0xa9930c2ffa1b605b0243ba36b3020146bcba5a29c05a711f5ca7c705a8e851ca; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Correct.
// ------------------------------------- // | ||
|
||
// Initialize forwarder storage. | ||
LibHasForwarder.data().forwarderAddress = params.trustedForwarder; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't know how this will work in production.
Adding to the initialize
function will work locally or when deploying the Smart Contract from scratch...
But in production you can't really call initialize
again.
Maybe what you could do is add a function to the InitFacet
such as migrateV1ToV2
and add the code there?
Then your initialize
method could call migrateV1ToV2
, and in production we would simply call migrateV1ToV2
once the facet is upgraded.
This leaves with the modifiers question. migrateV1ToV2
should probably allow for only the deployer to call it? I think it would cover both cases... I'm not 100% sure on this one.
abstract contract BasePaymaster is IPaymaster, ERC165 { | ||
using ERC165Checker for address; | ||
|
||
IRelayHub internal relayHub; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes.
} | ||
|
||
//overhead of forwarder verify+signature, plus hub overhead. | ||
uint256 public constant FORWARDER_HUB_OVERHEAD = 50000; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are those the real costs? I thought it was much cheaper to go with this pattern. Oh well.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Everything in BasePaymaster.sol
is copied from their version: https://github.com/opengsn/gsn/blob/master/packages/contracts/src/BasePaymaster.sol. Why is it copied? Their version uses Ownable
from OZ which I didn't want to bring in.
OpenGSN's implementation instructions say to grab BasePaymaster.sol
and base our base paymaster from that. See here for sample paymaster contracts: https://github.com/opengsn/gsn/tree/master/packages/paymasters/contracts
Ideally I wouldn't copy this in...
* - preRelayedCall | ||
* - postRelayedCall | ||
*/ | ||
abstract contract BasePaymaster is IPaymaster, ERC165 { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So this contract won't ever be upgraded, correct?
|
||
/// @inheritdoc IERC165 | ||
function supportsInterface(bytes4 interfaceId) public view virtual override(IERC165, ERC165) returns (bool) { | ||
return interfaceId == type(IPaymaster).interfaceId || super.supportsInterface(interfaceId); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am not 100% sure what super
refers to here... Is it ERC165
? Let's just make sure that whichever parent gets called, it also has supportsInterface(bytes4)
...
} | ||
|
||
function _verifyRelayHubOnly() internal view virtual { | ||
require(msg.sender == getRelayHub(), "can only be called by RelayHub"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would suggest that all these require
are changed to if
statements and proper revert
exception primitives.
They give you more flexibility for tests (you can have exception take known arguments, and pass these as informations to the client or the test).
deploy/20_deployMarketplace.ts
Outdated
@@ -6,7 +6,10 @@ import { deployments } from "hardhat"; | |||
const func: DeployFunction = async (hre: HardhatRuntimeEnvironment) => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am strongly against changing migrations that have been followed to production.
The outcome of doing so is an environment during tests that differs from production.
So all your tests might pass - but won't be testing what's in production.
In this case, you're deploying a Marketplace
contract that has an extra facet in test
environment...
So unless you make absolutelly 100% sure that you have a migration function to bring production to the same state, it's tricky.
How about having a migration which reflects the steps that will need to be taken in production, for example calling an upgrade and a separate migration function such as migrateV1toV2
?
deploy/20_deployMarketplace.ts
Outdated
// TODO: From a lookup... | ||
const trustedForwarderAddr = "0xB2b5841DBeF766d4b521221732F9B618fCf34A87"; | ||
|
||
await deployMarketplace(hre, (await deployments.get("Issuer")).address, trustedForwarderAddr); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Big red flag here...
Deterministic deploys via factories hash the arguments.
It means that a Marketplace
contract with a different trustedForwarderAddr
would get deployed to a completelly different address than another.
What I would suggest is the following:
- Have the Marketplace deployment not take an extra argument for
trustedForwarderAddr
. - Internally, the
trustedForwarderAddr
would be set to0x0
.
At this point you still have deterministic deployments.
Then:
- Account for
trustedForwarderAddr
being0x0
in theMarketplace
code - so that it won't function until it's set. - Have a method to change
trustedForwarderAddr
(Which I believe I've seen)...
Or am I missing something?
@@ -0,0 +1,12 @@ | |||
import { HardhatRuntimeEnvironment } from "hardhat/types"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, justified - new contract, new migration.
@@ -20,29 +20,30 @@ | |||
}, | |||
"dependencies": { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hell. Good job.
package.json
Outdated
@@ -20,29 +20,30 @@ | |||
}, | |||
"dependencies": { | |||
"@openzeppelin/contracts": "^4.9.2", | |||
"@typechain/ethers-v5": "^11.0.0", | |||
"@typechain/hardhat": "^8.0.0", | |||
"@opengsn/contracts": "^3.0.0-beta.6", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are we sure about this? Them being in beta means not audited yet - no?
I'd either wait for 3.0.0
to be a thing, or use 2.0.0
unless there are major changes between the two.
We could discuss this one.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For example... They're already at 3.0.0-beta.9
(published) and 3.0.0-beta.9
(unpublished). I'd be a bit scared to go for a production deploy with a beta.
tasks/marketplace.ts
Outdated
|
||
task("marketplace-deploy", "Deploys the main Marketplace contract").setAction( | ||
async (_params: MarketplaceDeployParams, hre) => { | ||
const { address: issuerAddr } = await hre.deployments.get("Issuer"); | ||
await deployMarketplace(hre, issuerAddr); | ||
// TODO: From a lookup... |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What lookup? :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's yet to exist :) - this was a note-to-self to add one... the Relays are already deployed on the chain but we need to add a lookup (or similar) for their addresses.
|
||
interface PaymasterFundParams { } | ||
|
||
task("paymaster-fund", "Funds the Paymaster") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good job sticking to the weird naming convention I chose...
My logic was that I wanted tasks groupped by contract. Contract name first, task name after. I know it's weird, but it makes it easier to find what you're looking for...
tasks/paymaster.ts
Outdated
const { address: paymasterAddress } = await deployments.get("Paymaster"); | ||
|
||
const RelayHub = await hre.ethers.getContractFactory("RelayHub"); | ||
const relayHub = await RelayHub.attach(relayHubAddress); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why not directly const relayHub = await hre.ethers.getContractAt(...)
?
|
||
// params... | ||
const tx = await relayHub.depositFor(paymasterAddress, { value: parseEther("0.1") }); | ||
await tx.wait(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yep.
const { deployer } = await getNamedAccounts(); | ||
|
||
let deploy = await deployments.getOrNull("Paymaster"); | ||
if (deploy) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Very good job conserving the style of "check if it's done first".
You will see that it will save you a lot of time when a deployment fails midway.
test/fast/Crowdfund.test.ts
Outdated
@@ -44,7 +44,7 @@ describe("Crowdfunds", () => { | |||
issuer = await smock.fake("Issuer"); | |||
marketplace = await smock.fake("Marketplace"); | |||
fast = await smock.fake("Fast"); | |||
erc20 = await smock.fake("IERC20"); | |||
erc20 = await smock.fake("contracts/interfaces/IERC20.sol:IERC20"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wait. What?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yep, super annoying... example of what happens without this:
4) FastDistributionsFacet
"before all" hook in "FastDistributionsFacet":
Error: unable to generate smock spec from contract name.
HH701: There are multiple artifacts for contract "IERC20", please use a fully qualified name.
Please replace IERC20 for one of these options wherever you are trying to read its artifact:
@openzeppelin/contracts/token/ERC20/IERC20.sol:IERC20
contracts/interfaces/IERC20.sol:IERC20
My understanding of this is that in the artifacts, there are essentially two IERC20 definitions so it can't work it out.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hold on. If it picked up the OZ version, it means that something more concerning is going on. It could mean that a contract you use from OZ is referencing its own ERC20 interface.
Otherwise it wouldn't get built and artifacts generated.
I would aim at having a single source of truth here - in case something changes in our ERC20 or theirs, it could lead to something nasty.
Don't worry about this one though - it's a tooling thing we can resolve in different ways.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Handled in a4797ed for now...
OZ's is referened within IStakeManager
(referenced from IRelayHub
) within Open GSN.
5f6dcc8
to
e20c81e
Compare
618d4eb
to
bdaf271
Compare
🚧 WIP 🚧
Known issues to fix:
Actually securing the functions... modifiers etc💥Working out how to deal with testing locally (the RelayHub etc).Fixing and static stubbed values (contract addresses etc).Tests in general.supportsInterface
forced byIPaymaster
interface... but this is on the facet. 💥OpenGSN ref: https://docs.opengsn.org/contracts/#paying-for-your-user-s-meta-transaction
BasePaymaster.sol
: https://github.com/opengsn/gsn/blob/master/packages/contracts/src/BasePaymaster.solNotes
Currently includes...
AHasContext
- Implementation for_isTrustedForwarder
,_msgSender()
and_msgData()
. Used to override base classes.AHasForwarder
- Implementation of the Forwardable behaviour. Should be inherited from when adding a "Forwardable" facet. Adds setters, getters, events and errors.LibHasForwarder
- Diamond storage forAHasForwarder
.AHasMembers
- updated to point to_msgSender()
, provides default implementation which points tomsg.sender
- i.e. it's not aware of anything forwardable etc.MarketplaceAccessFacet
- Inherits fromAHasMembers
andAHasContext
.MarketplaceForwardableFacet
- Inherits fromAHasForwarder
andAHasContext
.MarketplaceAccessFacet
example: