-
Notifications
You must be signed in to change notification settings - Fork 670
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-173: Remove simulation and view-only methods from the EntryPoint contract #321
Conversation
Makes comments and formatting consistent
fix EntryPoint revert if 'postOp' reverts with short revert reason
maxFeePerGas: 0 | ||
}, accountOwner1, entryPoint) | ||
// must succeed with enough verification gas. | ||
await simulateValidation(op0, entryPoint.address, { gas: '0xF4240' }) |
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 hex?
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 third parameter of simulateValidation
is a transaction details object and is fed directly to the eth_call
RPC method as {...tx}
. It does not pass through the Ethers.js where gasLimit?: string | number | BigNumber;
is translated into a hex gas
field.
expect(error.message).to.match(/initCode failed or OOG/, error) | ||
}) | ||
|
||
// TODO: this test is impossible to do with the "state override" approach |
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.
since anvil does support debug_traceCall, it is possible to write it better.
but this test is very partial.
should instead say:
for actual opcode and storage rule restrictions, see the reference bundler ValidationManager
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.
Added comment.
}) | ||
}) | ||
|
||
// describe('#simulateHandleOp', () => { |
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 one thing missing from simulateHandleOp (which is why we need this "Simulation" contract) is to return the actualGasUsed and success/revert reason of actual call.
they are dumped in an event deep inside innerHandleOp, and the only simple way to propagate them out to the calling "simulateHandleOp" is using storage - a big no-no for a real production code, but good enough for simulate function.
contracts/interfaces/IEntryPoint.sol
Outdated
@@ -87,57 +87,17 @@ interface IEntryPoint is IStakeManager, INonceManager { | |||
* so a failure can be attributed to the correct entity. | |||
*/ | |||
error FailedOp(uint256 opIndex, string reason); | |||
error FailedOp2(address opIndex, string 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.
what is this for?
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.
Removed.
also, to fix the code-coverage, check the COVERAGE env. variable to skip the failed test |
test/entrypoint.test.ts
Outdated
await expect(entryPoint.simulateValidation(userOp, { gasLimit: 1e6 })) | ||
.to.revertedWith('ValidationResult') | ||
const snapshot = await ethers.provider.send('evm_snapshot', []) | ||
await entryPoint.simulateValidation(userOp, { gasLimit: 1e6 }) |
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.
use callStatic, and no need for snapshot/revert
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.
Fixed.
test/testutils.ts
Outdated
} | ||
|
||
export async function deployEntryPoint (provider = ethers.provider): Promise<EntryPoint> { | ||
export async function deployEntryPointSimulations (provider = ethers.provider): Promise<EntryPointSimulations> { |
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 rename the method? leave the name "deployEntryPoint", and it would avoid a lot of source modifications...
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.
Fixed.
test/entrypoint.test.ts
Outdated
await expect(entryPoint.simulateValidation(badOp, { gasLimit: 3e5 })) | ||
.to.revertedWith('ValidationResult') | ||
const snapshot = await ethers.provider.send('evm_snapshot', []) | ||
await entryPoint.simulateValidation(badOp, { gasLimit: 3e5 }) |
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.
use callStatic, and no need for snapshot/revert
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.
Fixed.
aggregator, | ||
_getStakeInfo(aggregator) | ||
); | ||
return ValidationResult( |
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.
better "merge" the 2 separate struct returns.
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.
Fixed.
|
||
function _simulationOnlyValidations( | ||
UserOperation calldata userOp | ||
) private view { |
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.
internal
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.
Changed.
Comments by @yoavw : A couple of questions:
|
|
There is one way that is a potential exploit: The new approach means that there are two different EntryPoint contracts, one for simulation and one for execution. In the case where EntryPointSimulations.depositTo() costs 30 gas less than EntryPoint.depositTo(), for instance, an account can call depositTo() during validation 20 times, amplifying the diff to 600 gas, then emit an event. This will pass in simulation since it's cheaper, but fail on actual EntryPoint. A possible mitigation is to allow only one call to depositTo() during validation, but I think that with gas calculation precise enough, even 30 gas diff can be abused (e.g. through emitting events). Wdyt? |
@shahafn , @drortirosh and I discussed this a couple of days ago and I believe the conclusion was that every function in EntryPointSimulations must take at least the same amount of gas as its EntryPoint implementation. If it's marginal, add an extra SSTORE and deduct this fixed cost from the reported gas cost. This way there are no cases where something results in OOG on chain after passing simulation successfully. We're ok if OOG happens during simulation - we just increase the gas limit. What we're trying to prevent is on-chain OOG. |
fb0261d
to
e2f4703
Compare
f43a535
to
c254faf
Compare
a9caa9c
to
ac5a4c9
Compare
don't assume EntryPointSimulations is installed.
No description provided.