Skip to content
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

Open
wants to merge 5 commits into
base: master
Choose a base branch
from

Conversation

limyeechern
Copy link

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.

@eip-review-bot
Copy link
Collaborator

eip-review-bot commented Sep 20, 2024

File ERCS/erc-7772.md

Requires 1 more reviewers from @axic, @g11tech, @SamWilsn, @xinbenlv

ERCS/eip-####.md Outdated Show resolved Hide resolved
ERCS/eip-####.md Outdated Show resolved Hide resolved
ERCS/eip-####.md Outdated Show resolved Hide resolved
limyeechern and others added 2 commits September 21, 2024 07:12
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`
Copy link
Contributor

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.
Copy link
Contributor

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) {
Copy link
Contributor

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)
Copy link
Contributor

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) {
Copy link
Contributor

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".

@ethereum ethereum deleted a comment from GIgako19929 Oct 11, 2024
@ethereum ethereum deleted a comment from GIgako19929 Oct 11, 2024
@ethereum ethereum deleted a comment from GIgako19929 Oct 11, 2024
@ethereum ethereum deleted a comment from GIgako19929 Oct 11, 2024
@eip-review-bot eip-review-bot changed the title Add ERC: Partial Gas Sponsorship Interface Add ERC: Partial Gas Sponsorship Oct 11, 2024
@github-actions github-actions bot removed the w-ci label Oct 11, 2024
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
Copy link
Collaborator

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
Copy link
Collaborator

Choose a reason for hiding this comment

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

Same.

Comment on lines +62 to +229
(
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);
}
}
```
Copy link
Collaborator

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.

Comment on lines +262 to +263
## Reference Implementation
TBD
Copy link
Collaborator

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.

Suggested change
## Reference Implementation
TBD

Copy link

The commit e31da53 (as a parent of a686a9a) contains errors.
Please inspect the Run Summary for details.

@github-actions github-actions bot added the w-ci label Oct 11, 2024
}
}

function sponsorGas(
Copy link
Contributor

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)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants