-
Notifications
You must be signed in to change notification settings - Fork 436
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
Add ERC: Partial Gas Sponsorship #649
base: master
Are you sure you want to change the base?
Conversation
Co-authored-by: Andrew B Coathup <28278242+abcoathup@users.noreply.github.com>
ERCS/eip-7772.md
Outdated
- `uint256 amount`: The amount of gas to be sponsored. | ||
- Visibility: External | ||
- Usage: This function is called by the Paymaster during the post-operation (`postOp`) stage to claim the gas spent on the corresponding User Operation. | ||
- Function: `AuthoriseGas` |
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.
Calling this authoriseGas
during paymaster validation makes the paymaster fully depend on the sponsored app: the code of the "sponsoring app" is called in the context of the paymaster, and must obey all ERC-7562 rules. if it doesn't - the paymaster is "blamed", which can cause a denial of service on the paymaster (and all other apps using it)
ERCS/eip-7772.md
Outdated
- Function: `AuthoriseGas` | ||
- Role: Allows the Paymaster to invoke this function in the dApp's contract during validation phase as an alternative pathway to approve the amount of gas willing to be sponsored. This function can be used when additional sponsors are not part of the execution phase but still wish to sponsor the transaction. | ||
- Parameters: | ||
- `bytes calldata data`: Additional data that can be passed to the dApp to help the dApp determine the amount of gas it is willing to sponsor. |
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.
vague parameter, which is not clear how it is used. in the example below, the "paymasterAndData" is passed to each sponsor, which means they can't do much with it (since the paymaster also need it)
ERCS/eip-7772.md
Outdated
for (uint256 i = 0; i < sponsors.length; i += 20) { | ||
address gasSponsor = address(bytes20(sponsors[i:i + 20])); | ||
|
||
try IGasSponsor(gasSponsor).authoriseGas(userOp.paymasterAndData) { |
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.
how a sponsor can tell what portion of paymasterAndData it should use?
ERCS/eip-7772.md
Outdated
) external override { | ||
uint256 allowance; | ||
assembly { | ||
allowance := tload(msg.sender) |
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.
this will always return zero, since the msg.sender
is always the paymaster itself.
ERCS/eip-7772.md
Outdated
|
||
try IGasSponsor(gasSponsor).authoriseGas(userOp.paymasterAndData) { | ||
emit GasAuthorised(gasSponsor); | ||
} catch (bytes memory reason) { |
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.
The paymaster trusts the sponsor codes. Remember, it is the user who controls which sponsors are added to the list, and the paymaster just obey that list.
e.g. a sponsor can waste gas, forcing the paymaster to pay for it, and even force revert (by wasting gas, and/or by assembly { revert(0,100000) }
, causing the paymaster to waste a lot of gas to copy and emit this "error reason".
This interface is designed to be implemented by dApps that want to manage their gas funds internally and allow Paymasters to claim gas costs in a standardised way. | ||
|
||
``` solidity | ||
// SPDX-License-Identifier: MIT |
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.
Code inline in the document must be CC0-1.0
licensed. Code in your assets/
folder can be MIT
if you'd like.
|
||
### DApp Example | ||
```solidity | ||
//SPDX-License-Identifier: MIT |
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.
Same.
( | ||
uint48 validUntil, | ||
uint48 validAfter, | ||
bytes calldata signature | ||
bytes calldata sponsors | ||
) = parsePaymasterAndData(userOp.paymasterAndData); | ||
|
||
... | ||
|
||
// Iterate over the sponsors to collect gas | ||
for (uint256 i = 0; i < sponsors.length; i += 20) { | ||
address gasSponsor = address(bytes20(sponsors[i:i + 20])); | ||
|
||
try IGasSponsor(gasSponsor).authoriseGas(userOp.paymasterAndData) { | ||
emit GasAuthorised(gasSponsor); | ||
} catch (bytes memory reason) { | ||
// Log the error reason | ||
emit GasAuthorisedError(gasSponsor, reason); | ||
} | ||
} | ||
|
||
... | ||
// Set the first 20 bytes of context to the sender address | ||
address sender = userOp.sender; | ||
assembly { | ||
mstore(add(context, 0x20), sender) // Store sender at the beginning of the context (first 20 bytes) | ||
} | ||
|
||
// Copy sponsors into context starting at byte 20 | ||
for (uint256 i = 0; i < sponsors.length; i++) { | ||
context[20 + i] = sponsors[i]; | ||
} | ||
} | ||
|
||
|
||
function _postOp( | ||
PostOpMode mode, | ||
bytes calldata context, | ||
uint256 actualGasCost, | ||
uint256 actualUserOpFeePerGas | ||
) internal override { | ||
|
||
unchecked { | ||
uint256 priceMarkup = tokenPaymasterConfig.priceMarkup; | ||
( | ||
uint256 preCharge, | ||
address userOpSender | ||
bytes memory ... //TODO | ||
) = abi.decode(context, (uint256, address)); | ||
uint256 _cachedPrice = updateCachedPrice(false); | ||
// note: as price is in native-asset-per-token and we want more tokens increasing it means dividing it by markup | ||
uint256 cachedPriceWithMarkup = _cachedPrice * PRICE_DENOMINATOR / priceMarkup; | ||
|
||
uint256 gasCollected; | ||
address sender = address(bytes20(context[:20])); | ||
bytes calldata sponsors = context[72:]; | ||
// Iterate over the sponsors to collect gas | ||
for (uint256 i = 0; i < sponsors.length; i += 20) { | ||
address gasSponsor = address(bytes20(sponsors[i:i + 20])); | ||
uint256 balanceBeforeClaim = address(this).balance; | ||
|
||
try IGasSponsor(gasSponsor).sponsorGas(type(uint256).max) { | ||
uint256 balanceAfterClaim = address(this).balance; | ||
uint256 gasClaimed = balanceAfterClaim - balanceBeforeClaim; | ||
gasCollected += gasClaimed; | ||
|
||
emit GasClaimed(gasSponsor, gasClaimed); | ||
} catch (bytes memory reason) { | ||
// Log the error reason | ||
emit GasClaimedError(gasSponsor, reason); | ||
} | ||
} | ||
|
||
// Refund tokens based on actual gas cost | ||
uint256 actualChargeNative = actualGasCost - gasCollected + tokenPaymasterConfig.refundPostopCost * actualUserOpFeePerGas; | ||
|
||
uint256 actualTokenNeeded = weiToToken(actualChargeNative, cachedPriceWithMarkup); | ||
if (preCharge > actualTokenNeeded) { | ||
// If the initially provided token amount is greater than the actual amount needed, refund the difference | ||
SafeERC20.safeTransfer( | ||
token, | ||
userOpSender, | ||
preCharge - actualTokenNeeded | ||
); | ||
} else if (preCharge < actualTokenNeeded) { | ||
// Attempt to cover Paymaster's gas expenses by withdrawing the 'overdraft' from the client | ||
// If the transfer reverts also revert the 'postOp' to remove the incentive to cheat | ||
SafeERC20.safeTransferFrom( | ||
token, | ||
userOpSender, | ||
address(this), | ||
actualTokenNeeded - preCharge | ||
); | ||
} | ||
|
||
emit UserOperationSponsored(userOpSender, actualTokenNeeded, actualGasCost, cachedPriceWithMarkup); | ||
refillEntryPointDeposit(_cachedPrice); | ||
} | ||
} | ||
``` |
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.
Long examples like these belong in your Reference Implementation section.
## Reference Implementation | ||
TBD |
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.
This section is optional. You can omit it for now, and re-add it when you have something.
## Reference Implementation | |
TBD |
The commit e31da53 (as a parent of a686a9a) contains errors. |
} | ||
} | ||
|
||
function sponsorGas( |
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 prevent any contract in the batch to call sponsorGas ?
that is, if a user make a complex batch call that goes through defiA then defiB (using some paymasterX) - and defiB simply calls defiA.sponsorGas
(and for that matter "defiB" can be any contract executed in the flow, like transfer target)
This proposal defines the necessary interface that decentralized applications (dApps) must implement to sponsor a portion of the required gas for user operations utilizing a Paymaster that supports this standard. The proposal also provides a suggested code implementation that Paymasters can include in their current implementation to support dApp sponsorship. Partial sponsorship between more than one dApps may also be achieved through this proposal.