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

_callValidateUserOp to wrap sender.validateUserOp #533

Open
wants to merge 7 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
76 changes: 51 additions & 25 deletions contracts/core/EntryPoint.sol
Original file line number Diff line number Diff line change
Expand Up @@ -92,10 +92,7 @@ contract EntryPoint is IEntryPoint, StakeManager, NonceManager, ReentrancyGuardT
bytes memory context = getMemoryBytesFromOffset(opInfo.contextOffset);
bool success;
{
uint256 saveFreePtr;
assembly ("memory-safe") {
saveFreePtr := mload(0x40)
}
uint256 saveFreePtr = getFreePtr();
bytes calldata callData = userOp.callData;
bytes memory innerCall;
bytes4 methodSig;
Expand All @@ -115,8 +112,8 @@ contract EntryPoint is IEntryPoint, StakeManager, NonceManager, ReentrancyGuardT
assembly ("memory-safe") {
success := call(gas(), address(), 0, add(innerCall, 0x20), mload(innerCall), 0, 32)
collected := mload(0)
mstore(0x40, saveFreePtr)
}
restoreFreePtr(saveFreePtr);
}
if (!success) {
bytes32 innerRevertCode;
Expand Down Expand Up @@ -227,7 +224,7 @@ contract EntryPoint is IEntryPoint, StakeManager, NonceManager, ReentrancyGuardT
//address(1) is special marker of "signature error"
require(
address(aggregator) != address(1),
"AA96 invalid aggregator"
FailedOp(totalOps + i, "AA96 invalid aggregator")
);

if (address(aggregator) != address(0)) {
Expand Down Expand Up @@ -481,8 +478,7 @@ contract EntryPoint is IEntryPoint, StakeManager, NonceManager, ReentrancyGuardT
uint256 opIndex,
PackedUserOperation calldata op,
UserOpInfo memory opInfo,
uint256 requiredPrefund,
uint256 verificationGasLimit
uint256 requiredPrefund
)
internal
returns (
Expand All @@ -501,15 +497,7 @@ contract EntryPoint is IEntryPoint, StakeManager, NonceManager, ReentrancyGuardT
? 0
: requiredPrefund - bal;
}
try
IAccount(sender).validateUserOp{
gas: verificationGasLimit
}(op, opInfo.userOpHash, missingAccountFunds)
returns (uint256 _validationData) {
validationData = _validationData;
} catch {
revert FailedOpWithRevert(opIndex, "AA23 reverted", Exec.getReturnData(REVERT_REASON_MAX_LEN));
}
validationData = _callValidateUserOp(op, opInfo, missingAccountFunds, opIndex);
if (paymaster == address(0)) {
DepositInfo storage senderInfo = deposits[sender];
uint256 deposit = senderInfo.deposit;
Expand All @@ -521,6 +509,30 @@ contract EntryPoint is IEntryPoint, StakeManager, NonceManager, ReentrancyGuardT
}
}

// call sender.validateUserOp()
// handle wrong output size with FailedOp
function _callValidateUserOp(PackedUserOperation calldata op, UserOpInfo memory opInfo, uint256 missingAccountFunds, uint256 opIndex)
internal returns (uint256 validationData) {
uint256 saveFreePtr = getFreePtr();
bytes memory callData = abi.encodeCall(IAccount.validateUserOp, (op, opInfo.userOpHash, missingAccountFunds));
uint256 gasLimit = opInfo.mUserOp.verificationGasLimit;
address sender = opInfo.mUserOp.sender;
uint256 dataSize;
assembly ("memory-safe"){
let success := call(gasLimit, sender, 0, add(callData, 0x20), mload(callData), 0, 32)
dataSize := mul(returndatasize(), success)
validationData := mload(0)
}
restoreFreePtr(saveFreePtr);
if (dataSize != 32) {
if(sender.code.length == 0) {
revert FailedOp(opIndex, "AA20 account not deployed");
} else {
revert FailedOpWithRevert(opIndex, "AA23 reverted", Exec.getReturnData(REVERT_REASON_MAX_LEN));
}
}
}

/**
* In case the request has a paymaster:
* - Validate paymaster has enough deposit.
Expand Down Expand Up @@ -651,15 +663,14 @@ contract EntryPoint is IEntryPoint, StakeManager, NonceManager, ReentrancyGuardT
mUserOp.paymasterPostOpGasLimit |
mUserOp.maxFeePerGas |
mUserOp.maxPriorityFeePerGas;
require(maxGasValues <= type(uint120).max, "AA94 gas values overflow");
require(maxGasValues <= type(uint120).max, FailedOp(opIndex, "AA94 gas values overflow"));

uint256 requiredPreFund = _getRequiredPrefund(mUserOp);
validationData = _validateAccountPrepayment(
opIndex,
userOp,
outOpInfo,
requiredPreFund,
verificationGasLimit
requiredPreFund
);

if (!_validateAndUpdateNonce(mUserOp.sender, mUserOp.nonce)) {
Expand Down Expand Up @@ -772,7 +783,7 @@ contract EntryPoint is IEntryPoint, StakeManager, NonceManager, ReentrancyGuardT

/**
* The gas price this UserOp agrees to pay.
* Relayer/block builder might submit the TX with higher priorityFee, but the user should not.
* Relayer/block builder might submit the TX with higher priorityFee, but the user should not be affected.
* @param mUserOp - The userOp to get the gas price from.
*/
function getUserOpGasPrice(
Expand All @@ -789,6 +800,12 @@ contract EntryPoint is IEntryPoint, StakeManager, NonceManager, ReentrancyGuardT
}
}

/// @inheritdoc IEntryPoint
function delegateAndRevert(address target, bytes calldata data) external {
(bool success, bytes memory ret) = target.delegatecall(data);
revert DelegateAndRevert(success, ret);
}

/**
* The offset of the given bytes in memory.
* @param data - The bytes to get the offset of.
Expand All @@ -813,9 +830,18 @@ contract EntryPoint is IEntryPoint, StakeManager, NonceManager, ReentrancyGuardT
}
}

/// @inheritdoc IEntryPoint
function delegateAndRevert(address target, bytes calldata data) external {
(bool success, bytes memory ret) = target.delegatecall(data);
revert DelegateAndRevert(success, ret);
// safe free memory pointer.
function getFreePtr() internal pure returns (uint256 ptr) {
assembly ("memory-safe") {
ptr := mload(0x40)
}
}

// restore free memory pointer.
// no allocated memory since saveFreePtr was called is allowed to be accessed after this call.
function restoreFreePtr(uint256 ptr) internal pure {
assembly ("memory-safe") {
mstore(0x40, ptr)
}
}
}
32 changes: 16 additions & 16 deletions reports/gas-checker.txt
Original file line number Diff line number Diff line change
Expand Up @@ -12,36 +12,36 @@
║ │ │ │ (delta for │ (compared to ║
║ │ │ │ one UserOp) │ account.exec()) ║
╟────────────────────────────────┼───────┼───────────────┼────────────────┼─────────────────────╢
║ simple │ 1 │ 77450 │ │ ║
║ simple │ 1 │ 77383 │ │ ║
╟────────────────────────────────┼───────┼───────────────┼────────────────┼─────────────────────╢
║ simple - diff from previous │ 2 │ │ 4157412315
║ simple - diff from previous │ 2 │ │ 4151812259
╟────────────────────────────────┼───────┼───────────────┼────────────────┼─────────────────────╢
║ simple │ 10 │ 451932 │ │ ║
║ simple │ 10 │ 451262 │ │ ║
╟────────────────────────────────┼───────┼───────────────┼────────────────┼─────────────────────╢
║ simple - diff from previous │ 11 │ │ 4165912400
║ simple - diff from previous │ 11 │ │ 4159012331
╟────────────────────────────────┼───────┼───────────────┼────────────────┼─────────────────────╢
║ simple paymaster │ 1 │ 83281 │ │ ║
║ simple paymaster │ 1 │ 83224 │ │ ║
╟────────────────────────────────┼───────┼───────────────┼────────────────┼─────────────────────╢
║ simple paymaster with diff │ 2 │ │ 4010610847
║ simple paymaster with diff │ 2 │ │ 4008410825
╟────────────────────────────────┼───────┼───────────────┼────────────────┼─────────────────────╢
║ simple paymaster │ 10 │ 444654 │ │ ║
║ simple paymaster │ 10 │ 444107 │ │ ║
╟────────────────────────────────┼───────┼───────────────┼────────────────┼─────────────────────╢
║ simple paymaster with diff │ 11 │ │ 4019910940
║ simple paymaster with diff │ 11 │ │ 4005510796
╟────────────────────────────────┼───────┼───────────────┼────────────────┼─────────────────────╢
║ big tx 5k │ 1 │ 167209 │ │ ║
║ big tx 5k │ 1 │ 167129 │ │ ║
╟────────────────────────────────┼───────┼───────────────┼────────────────┼─────────────────────╢
║ big tx - diff from previous │ 2 │ │ 13081116109
║ big tx - diff from previous │ 2 │ │ 13075516053
╟────────────────────────────────┼───────┼───────────────┼────────────────┼─────────────────────╢
║ big tx 5k │ 10 │ 1344642 │ │ ║
║ big tx 5k │ 10 │ 1344002 │ │ ║
╟────────────────────────────────┼───────┼───────────────┼────────────────┼─────────────────────╢
║ big tx - diff from previous │ 11 │ │ 13078816086
║ big tx - diff from previous │ 11 │ │ 13070616004
╟────────────────────────────────┼───────┼───────────────┼────────────────┼─────────────────────╢
║ paymaster+postOp │ 1 │ 84458 │ │ ║
║ paymaster+postOp │ 1 │ 84377 │ │ ║
╟────────────────────────────────┼───────┼───────────────┼────────────────┼─────────────────────╢
║ paymaster+postOp with diff │ 2 │ │ 4131812059
║ paymaster+postOp with diff │ 2 │ │ 4123711978
╟────────────────────────────────┼───────┼───────────────┼────────────────┼─────────────────────╢
║ paymaster+postOp │ 10 │ 456453 │ │ ║
║ paymaster+postOp │ 10 │ 455642 │ │ ║
╟────────────────────────────────┼───────┼───────────────┼────────────────┼─────────────────────╢
║ paymaster+postOp with diff │ 11 │ │ 4136912110
║ paymaster+postOp with diff │ 11 │ │ 4124911990
╚════════════════════════════════╧═══════╧═══════════════╧════════════════╧═════════════════════╝

2 changes: 1 addition & 1 deletion scripts/prepack-contracts-package.sh
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,4 @@ cd contracts
rm -rf artifacts

mkdir -p artifacts
cp `find ../artifacts/contracts -type f | grep -v -E 'test|Test|dbg|bls|IOracle'` artifacts/
cp `find ../artifacts/contracts -type f | grep -v -E 'test|Test|dbg|bls|IOracle|v06'` artifacts/
1 change: 0 additions & 1 deletion test/UserOp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -224,7 +224,6 @@ export async function fillAndPack (op: Partial<UserOperation>, entryPoint?: Entr

export function getDomainSeparator (entryPoint: string, chainId: number): string {
const domainData = getErc4337TypedDataDomain(entryPoint, chainId)
console.log('data=', domainData)
return keccak256(defaultAbiCoder.encode(
['bytes32', 'bytes32', 'bytes32', 'uint256', 'address'],
[
Expand Down
35 changes: 34 additions & 1 deletion test/entrypoint.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@ import {
TestRevertAccount__factory,
TestSignatureAggregator,
TestSignatureAggregator__factory,
TestWarmColdAccount__factory
TestWarmColdAccount__factory,
SimpleAccount__factory
} from '../typechain'
import {
AddressZero,
Expand All @@ -54,6 +55,7 @@ import {
DefaultsForUserOp,
fillAndSign,
fillSignAndPack,
fillUserOp,
getUserOpHash,
packUserOp,
simulateValidation
Expand Down Expand Up @@ -649,6 +651,37 @@ describe('EntryPoint', function () {
expect(await getBalance(account.address)).to.eq(inititalAccountBalance)
})

it('should fail with AA20 if account not deployed', async () => {
const userop = await fillUserOp({
sender: createAddress(),
nonce: 0
}, entryPoint)
const beneficiary = createAddress()
await expect(entryPoint.handleOps([packUserOp(userop)], beneficiary)).to.revertedWith('AA20 account not deployed')
})

it('should fail with AA23 if account reverts', async () => {
const userop = await fillUserOp({
sender: entryPoint.address, // existing but not a real account
nonce: 0
}, entryPoint)
const beneficiary = createAddress()
await expect(entryPoint.handleOps([packUserOp(userop)], beneficiary).catch(rethrow())).to.be
.revertedWith('FailedOpWithRevert(0,"AA23 reverted",)')
})

it('should fail with AA23 (and original error) if account reverts', async () => {
// deploy an account with broken entrypoint, so it always reverts with "not from EntryPoint"
const revertingAccount = await new SimpleAccount__factory(ethersSigner).deploy(createAddress())
const userop = await fillUserOp({
sender: revertingAccount.address,
nonce: 0
}, entryPoint)
const beneficiary = createAddress()
await expect(entryPoint.handleOps([packUserOp(userop)], beneficiary).catch(rethrow())).to.be
.revertedWith('FailedOpWithRevert(0,"AA23 reverted",Error(account: not from EntryPoint)')
})

it('account should pay a penalty for requiring too much gas and leaving it unused', async function () {
if (process.env.COVERAGE != null) {
return
Expand Down
Loading