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

AA-217: Charge a penalty payment for unused execution gas limit of a UserOp #356

Merged
merged 37 commits into from
Nov 2, 2023
Merged
Show file tree
Hide file tree
Changes from 28 commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
ebbf21a
Makes comments and formatting consistent
gigamesh Mar 3, 2023
64cbdf6
@inheritdoc IEntryPoint
gigamesh Mar 4, 2023
d43bc94
Reverts contracts/test/TestHelpers.sol
gigamesh Mar 6, 2023
105d122
fix: entrypoint does not revert even first postOp reverts with short …
leekt May 30, 2023
3a6b0a5
lint
leekt May 30, 2023
66688cb
gas-report
leekt May 30, 2023
b56ef7b
Merge branch 'next_v0.7' into gigamesh/comments
forshtat Jun 25, 2023
74ed53b
Update IEntryPoint.sol - add missing 'INonceManager'
forshtat Jun 25, 2023
649daf8
Update EntryPoint.sol - fix lint errors
forshtat Jun 25, 2023
aa20d5d
Update gas-checker.txt manually
forshtat Jun 25, 2023
e3111fe
Update gas-checker.txt - add newline for the 'diff'
forshtat Jun 25, 2023
10f0502
Merge pull request #235 from gigamesh/gigamesh/comments
forshtat Jun 25, 2023
6a5ce09
Merge branch 'next_v0.7' into develop
forshtat Jun 25, 2023
8116f81
Update gas-checker.txt manually
forshtat Jun 25, 2023
74a3c37
Merge pull request #293 from leekt/develop
forshtat Jun 25, 2023
f96de6c
Remove "postOp" retry call mode and adjust tests
forshtat Jun 26, 2023
4b76a32
Update gas-calc
forshtat Jun 26, 2023
8903ae1
Update gas-checker.txt
forshtat Jun 26, 2023
ce519f6
Merge branch 'develop' of github.com:eth-infinitism/account-abstracti…
forshtat Oct 18, 2023
9bd5263
Bump 'yarn.lock' versions
forshtat Oct 18, 2023
a218f79
Bump node version for Github Actions
forshtat Oct 18, 2023
318a7ac
Weird
forshtat Oct 18, 2023
0f24290
Disable new solhint rules
forshtat Oct 18, 2023
83cb4e2
Uncomment TestCounter deployment
forshtat Oct 18, 2023
53eda99
Fix
forshtat Oct 18, 2023
ba64217
Fix gas checks
forshtat Oct 18, 2023
30d24ac
Use up to two times 'verificationGasLimit' in the 'requiredGas' calcu…
forshtat Oct 18, 2023
b6a2a64
AA-217: Charge a penalty payment for unused execution gas limit of a …
forshtat Oct 18, 2023
c3e808d
Add test for unused gas limit penalty charge
forshtat Oct 20, 2023
60a0a1c
Fix
forshtat Oct 20, 2023
821f758
Merge branch 'develop' into AA-217-payment-for-unused-gas
forshtat Oct 22, 2023
e2809a7
Use gas estimation instead of a hardcoded value
forshtat Oct 30, 2023
6762541
Fix gas calc
forshtat Oct 30, 2023
cb4e7ca
wtf
forshtat Oct 30, 2023
710f9e9
AA-217: Add the unused gas penalty description to the EIP text (#361)
forshtat Nov 2, 2023
a35c3c3
better estimation gas calculation
drortirosh Nov 2, 2023
d3f1ede
fix warmup.
drortirosh Nov 2, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ jobs:
steps:
- uses: actions/setup-node@v1
with:
node-version: '14'
node-version: '16'
- uses: actions/checkout@v1
- uses: actions/cache@v2
with:
Expand All @@ -40,7 +40,7 @@ jobs:
steps:
- uses: actions/setup-node@v1
with:
node-version: '14'
node-version: '16'
- uses: actions/checkout@v1
- uses: actions/cache@v2
with:
Expand All @@ -55,7 +55,7 @@ jobs:
steps:
- uses: actions/setup-node@v1
with:
node-version: '14'
node-version: '16'
- uses: actions/checkout@v1
- uses: actions/cache@v2
with:
Expand All @@ -69,7 +69,7 @@ jobs:
steps:
- uses: actions/setup-node@v1
with:
node-version: '14'
node-version: '16'
- uses: actions/checkout@v1
- uses: actions/cache@v2
with:
Expand Down
4 changes: 4 additions & 0 deletions .solhint.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@
"rules": {
"compiler-version": ["error",">=0.7.5"],
"func-visibility": ["off",{"ignoreConstructors":true}],
"custom-errors": ["off"],
"explicit-types": ["off"],
"no-global-import": ["off"],
"immutable-vars-naming": ["off"],
"mark-callable-contracts": ["off"]
}
}
33 changes: 18 additions & 15 deletions contracts/core/EntryPoint.sol
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ import "./UserOperationLib.sol";
import "@openzeppelin/contracts/utils/introspection/ERC165.sol" as OpenZeppelin;
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";

import "hardhat/console.sol";

Choose a reason for hiding this comment

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

I would think you forgot the console.log


/*
* Account-Abstraction (EIP-4337) singleton EntryPoint implementation.
* Only one instance required on each chain.
Expand All @@ -33,6 +35,7 @@ contract EntryPoint is IEntryPoint, StakeManager, NonceManager, ReentrancyGuard,
bytes32 private constant INNER_OUT_OF_GAS = hex"deaddead";

uint256 private constant REVERT_REASON_MAX_LEN = 2048;
uint256 private constant PENALTY_PERCENT = 10;

/**
* For simulation purposes, validateUserOp (and validatePaymasterUserOp)
Expand Down Expand Up @@ -340,7 +343,7 @@ contract EntryPoint is IEntryPoint, StakeManager, NonceManager, ReentrancyGuard,
unchecked {
// When using a Paymaster, the verificationGasLimit is used also to as a limit for the postOp call.
// Our security model might call postOp eventually twice.
uint256 mul = mUserOp.paymaster != address(0) ? 3 : 1;
uint256 mul = mUserOp.paymaster != address(0) ? 2 : 1;
uint256 requiredGas = mUserOp.callGasLimit +
mUserOp.verificationGasLimit *
mul +
Expand Down Expand Up @@ -664,24 +667,24 @@ contract EntryPoint is IEntryPoint, StakeManager, NonceManager, ReentrancyGuard,
IPaymaster(paymaster).postOp{
gas: mUserOp.verificationGasLimit
}(mode, context, actualGasCost);
} else {
try
IPaymaster(paymaster).postOp{
gas: mUserOp.verificationGasLimit
}(mode, context, actualGasCost)
// solhint-disable-next-line no-empty-blocks
{} catch Error(string memory reason) {
revert FailedOp(
opIndex,
string.concat("AA50 postOp reverted: ", reason)
);
} catch {
revert FailedOp(opIndex, "AA50 postOp revert");
}
}
}
}
actualGas += preGas - gasleft();

// Calculating a penalty for unused execution gas
{
uint256 executionGasLimit = mUserOp.callGasLimit;
// Note that 'verificationGasLimit' here is the limit given to the 'postOp' which is part of execution
if (context.length > 0){
executionGasLimit += mUserOp.verificationGasLimit;
}
uint256 executionGasUsed = actualGas - opInfo.preOpGas;

This comment was marked as resolved.

uint256 unusedGas = executionGasLimit - executionGasUsed;
uint256 unusedGasPenalty = (unusedGas * PENALTY_PERCENT) / 100;
actualGas += unusedGasPenalty;
}

actualGasCost = actualGas * gasPrice;
if (opInfo.prefund < actualGasCost) {
revert FailedOp(opIndex, "AA51 prefund below actualGasCost");
Expand Down
2 changes: 1 addition & 1 deletion contracts/interfaces/IPaymaster.sol
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ interface IPaymaster {
// User op reverted. Still has to pay for gas.
opReverted,
// User op succeeded, but caused postOp to revert.
// Now it's a 2nd call, after user's op was deliberately reverted.
// Only used internally in the EntryPoint - Paymasters will not be called again.
postOpReverted
}

Expand Down
11 changes: 3 additions & 8 deletions contracts/samples/DepositPaymaster.sol
Original file line number Diff line number Diff line change
Expand Up @@ -151,18 +151,13 @@ contract DepositPaymaster is BasePaymaster {
* this time in *postOpReverted* mode.
* In this mode, we use the deposit to pay (which we validated to be large enough)
*/
function _postOp(PostOpMode mode, bytes calldata context, uint256 actualGasCost) internal override {
function _postOp(PostOpMode, bytes calldata context, uint256 actualGasCost) internal override {

(address account, IERC20 token, uint256 gasPricePostOp, uint256 maxTokenCost, uint256 maxCost) = abi.decode(context, (address, IERC20, uint256, uint256, uint256));
//use same conversion rate as used for validation.
uint256 actualTokenCost = (actualGasCost + COST_OF_POST * gasPricePostOp) * maxTokenCost / maxCost;
if (mode != PostOpMode.postOpReverted) {
// attempt to pay with tokens:
token.safeTransferFrom(account, address(this), actualTokenCost);
} else {
//in case above transferFrom failed, pay with deposit:
balances[token][account] -= actualTokenCost;
}
// attempt to pay with tokens:
token.safeTransferFrom(account, address(this), actualTokenCost);
balances[token][owner()] += actualTokenCost;
}
}
8 changes: 1 addition & 7 deletions contracts/samples/TokenPaymaster.sol
Original file line number Diff line number Diff line change
Expand Up @@ -150,10 +150,9 @@ contract TokenPaymaster is BasePaymaster, UniswapHelper, OracleHelper {

/// @notice Performs post-operation tasks, such as updating the token price and refunding excess tokens.
/// @dev This function is called after a user operation has been executed or reverted.
/// @param mode The post-operation mode (either successful or reverted).
/// @param context The context containing the token amount and user sender address.
/// @param actualGasCost The actual gas cost of the transaction.
function _postOp(PostOpMode mode, bytes calldata context, uint256 actualGasCost) internal override {
function _postOp(PostOpMode, bytes calldata context, uint256 actualGasCost) internal override {
unchecked {
uint256 priceMarkup = tokenPaymasterConfig.priceMarkup;
(
Expand All @@ -163,11 +162,6 @@ contract TokenPaymaster is BasePaymaster, UniswapHelper, OracleHelper {
address userOpSender
) = abi.decode(context, (uint256, uint256, uint256, address));
uint256 gasPrice = getGasPrice(maxFeePerGas, maxPriorityFeePerGas);
if (mode == PostOpMode.postOpReverted) {
emit PostOpReverted(userOpSender, preCharge);
// Do nothing here to not revert the whole bundle and harm reputation
return;
}
uint256 _cachedPrice = updateCachedPrice(false);
// note: as price is in ether-per-token and we want more tokens increasing it means dividing it by markup
uint256 cachedPriceWithMarkup = _cachedPrice * PRICE_DENOMINATOR / priceMarkup;
Expand Down
2 changes: 2 additions & 0 deletions contracts/test/BrokenBlsAccount.sol
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
// SPDX-License-Identifier: GPL-3.0

/* solhint-disable one-contract-per-file */
pragma solidity ^0.8.12;

import "@openzeppelin/contracts/utils/Create2.sol";
Expand Down
5 changes: 1 addition & 4 deletions contracts/test/TestPaymasterRevertCustomError.sol
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,7 @@ contract TestPaymasterRevertCustomError is BasePaymaster {
context = abi.encodePacked(userOp.sender);
}

function _postOp(PostOpMode mode, bytes calldata, uint256) internal pure override {
if(mode == PostOpMode.postOpReverted) {
return;
}
function _postOp(PostOpMode, bytes calldata, uint256) internal pure override {

revert CustomError();
}
Expand Down
5 changes: 2 additions & 3 deletions deploy/1_deploy_entrypoint.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,12 @@ const deployEntryPoint: DeployFunction = async function (hre: HardhatRuntimeEnvi
deterministicDeployment: true
})
console.log('==entrypoint addr=', ret.address)
/*

const entryPointAddress = ret.address
const w = await hre.deployments.deploy(
'SimpleAccount', {
from,
args: [entryPointAddress, from],
args: [entryPointAddress],
gasLimit: 2e6,
deterministicDeployment: true
})
Expand All @@ -33,7 +33,6 @@ const deployEntryPoint: DeployFunction = async function (hre: HardhatRuntimeEnvi
deterministicDeployment: true
})
console.log('==testCounter=', t.address)
*/
}

export default deployEntryPoint
2 changes: 1 addition & 1 deletion hardhat.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ const config: HardhatUserConfig = {
mocha: {
timeout: 10000
},

// @ts-ignore
etherscan: {
apiKey: process.env.ETHERSCAN_API_KEY
}
Expand Down
26 changes: 13 additions & 13 deletions reports/gas-checker.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,36 +4,36 @@
╔══════════════════════════╤════════╗
║ gas estimate "simple" │ 29014 ║
╟──────────────────────────┼────────╢
║ gas estimate "big tx 5k" │ 125248
║ gas estimate "big tx 5k" │ 125260
╚══════════════════════════╧════════╝

╔════════════════════════════════╤═══════╤═══════════════╤════════════════╤═════════════════════╗
║ handleOps description │ count │ total gasUsed │ per UserOp gas │ per UserOp overhead ║
║ │ │ │ (delta for │ (compared to ║
║ │ │ │ one UserOp) │ account.exec()) ║
╟────────────────────────────────┼───────┼───────────────┼────────────────┼─────────────────────╢
║ simple │ 1 │ 81954 │ │ ║
║ simple │ 1 │ 81838 │ │ ║
╟────────────────────────────────┼───────┼───────────────┼────────────────┼─────────────────────╢
║ simple - diff from previous │ 2 │ │ 4418715173
║ simple - diff from previous │ 2 │ │ 4409515081
╟────────────────────────────────┼───────┼───────────────┼────────────────┼─────────────────────╢
║ simple │ 10 │ 479898 │ │ ║
║ simple │ 10 │ 478846 │ │ ║
╟────────────────────────────────┼───────┼───────────────┼────────────────┼─────────────────────╢
║ simple - diff from previous │ 11 │ │ 4427115257
║ simple - diff from previous │ 11 │ │ 4408315069
╟────────────────────────────────┼───────┼───────────────┼────────────────┼─────────────────────╢
║ simple paymaster │ 1 │ 88237 │ │ ║
║ simple paymaster │ 1 │ 88121 │ │ ║
╟────────────────────────────────┼───────┼───────────────┼────────────────┼─────────────────────╢
║ simple paymaster with diff │ 2 │ │ 4320014186
║ simple paymaster with diff │ 2 │ │ 4308414070
╟────────────────────────────────┼───────┼───────────────┼────────────────┼─────────────────────╢
║ simple paymaster │ 10 │ 477146 │ │ ║
║ simple paymaster │ 10 │ 476046 │ │ ║
╟────────────────────────────────┼───────┼───────────────┼────────────────┼─────────────────────╢
║ simple paymaster with diff │ 11 │ │ 4327114257
║ simple paymaster with diff │ 11 │ │ 4314314129
╟────────────────────────────────┼───────┼───────────────┼────────────────┼─────────────────────╢
║ big tx 5k │ 1 │ 182975 │ │ ║
║ big tx 5k │ 1 │ 182895 │ │ ║
╟────────────────────────────────┼───────┼───────────────┼────────────────┼─────────────────────╢
║ big tx - diff from previous │ 2 │ │ 14471019462
║ big tx - diff from previous │ 2 │ │ 14460619346
╟────────────────────────────────┼───────┼───────────────┼────────────────┼─────────────────────╢
║ big tx 5k │ 10 │ 1485530 │ │ ║
║ big tx 5k │ 10 │ 1484502 │ │ ║
╟────────────────────────────────┼───────┼───────────────┼────────────────┼─────────────────────╢
║ big tx - diff from previous │ 11 │ │ 14478319535
║ big tx - diff from previous │ 11 │ │ 14455919299
╚════════════════════════════════╧═══════╧═══════════════╧════════════════╧═════════════════════╝

3 changes: 2 additions & 1 deletion test/entrypoint.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,13 @@ import {
TestSignatureAggregator__factory,
MaliciousAccount__factory,
TestWarmColdAccount__factory,
TestPaymasterRevertCustomError__factory,
IEntryPoint__factory,
SimpleAccountFactory__factory,
IStakeManager__factory,
INonceManager__factory,
EntryPoint__factory,
TestPaymasterRevertCustomError__factory, EntryPoint
EntryPoint
} from '../typechain'
import {
AddressZero,
Expand Down
14 changes: 9 additions & 5 deletions test/paymaster.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -231,14 +231,13 @@ describe('EntryPoint with paymaster', function () {
expect(await paymaster.allowance(account2.address, account.address)).to.eq(ethers.constants.MaxUint256)
})

it('griefing attempt should cause handleOp to revert', async () => {
it('griefing attempt in postOp should cause the execution part of UserOp to revert', async () => {
// account1 is approved to withdraw going to withdraw account2's balance

const account2Balance = await paymaster.balanceOf(account2.address)
const transferCost = parseEther('1').sub(account2Balance)
const withdrawAmount = account2Balance.sub(transferCost.mul(0))
const withdrawTokens = paymaster.interface.encodeFunctionData('transferFrom', [account2.address, account.address, withdrawAmount])
// const withdrawTokens = paymaster.interface.encodeFunctionData('transfer', [account.address, parseEther('0.1')])
const execFromEntryPoint = account.interface.encodeFunctionData('execute', [paymaster.address, 0, withdrawTokens])

const userOp1 = await fillAndSign({
Expand All @@ -255,12 +254,17 @@ describe('EntryPoint with paymaster', function () {
callGasLimit: 1e6
}, accountOwner, entryPoint)

await expect(
entryPoint.handleOps([
const rcpt =
await entryPoint.handleOps([
userOp1,
userOp2
], beneficiaryAddress)
).to.be.revertedWith('transfer amount exceeds balance')

const transferEvents = await paymaster.queryFilter(paymaster.filters.Transfer(), rcpt.blockHash)
const [log1, log2] = await entryPoint.queryFilter(entryPoint.filters.UserOperationEvent(), rcpt.blockHash)
expect(log1.args.success).to.eq(true)
expect(log2.args.success).to.eq(false)
expect(transferEvents.length).to.eq(2)
})
})
})
Expand Down
Loading
Loading