diff --git a/contracts/core/EntryPoint.sol b/contracts/core/EntryPoint.sol index af9fcbf4..8b5fd764 100644 --- a/contracts/core/EntryPoint.sol +++ b/contracts/core/EntryPoint.sol @@ -26,10 +26,7 @@ contract EntryPoint is IEntryPoint, StakeManager, NonceManager, ReentrancyGuard, using UserOperationLib for UserOperation; - SenderCreator private immutable senderCreator = new SenderCreator(); - - // Internal value used during simulation: need to query aggregator. - address private constant SIMULATE_FIND_AGGREGATOR = address(1); + SenderCreator private senderCreator = new SenderCreator(); // Marker for inner call revert on out of gas bytes32 private constant INNER_OUT_OF_GAS = hex"deaddead"; @@ -74,7 +71,10 @@ contract EntryPoint is IEntryPoint, StakeManager, NonceManager, ReentrancyGuard, uint256 opIndex, UserOperation calldata userOp, UserOpInfo memory opInfo - ) private returns (uint256 collected) { + ) + internal + returns + (uint256 collected) { uint256 preGas = gasleft(); bytes memory context = getMemoryBytesFromOffset(opInfo.contextOffset); @@ -217,41 +217,6 @@ contract EntryPoint is IEntryPoint, StakeManager, NonceManager, ReentrancyGuard, _compensate(beneficiary, collected); } - /// @inheritdoc IEntryPoint - function simulateHandleOp( - UserOperation calldata op, - address target, - bytes calldata targetCallData - ) external override { - UserOpInfo memory opInfo; - _simulationOnlyValidations(op); - ( - uint256 validationData, - uint256 paymasterValidationData - ) = _validatePrepayment(0, op, opInfo); - ValidationData memory data = _intersectTimeRange( - validationData, - paymasterValidationData - ); - - numberMarker(); - uint256 paid = _executeUserOp(0, op, opInfo); - numberMarker(); - bool targetSuccess; - bytes memory targetResult; - if (target != address(0)) { - (targetSuccess, targetResult) = target.call(targetCallData); - } - revert ExecutionResult( - opInfo.preOpGas, - paid, - data.validAfter, - data.validUntil, - targetSuccess, - targetResult - ); - } - /** * A memory copy of UserOp static fields only. * Excluding: callData, initCode and signature. Replacing paymasterAndData with paymaster. @@ -364,64 +329,6 @@ contract EntryPoint is IEntryPoint, StakeManager, NonceManager, ReentrancyGuard, } } - /// @inheritdoc IEntryPoint - function simulateValidation(UserOperation calldata userOp) external { - UserOpInfo memory outOpInfo; - - _simulationOnlyValidations(userOp); - ( - uint256 validationData, - uint256 paymasterValidationData - ) = _validatePrepayment(0, userOp, outOpInfo); - StakeInfo memory paymasterInfo = _getStakeInfo( - outOpInfo.mUserOp.paymaster - ); - StakeInfo memory senderInfo = _getStakeInfo(outOpInfo.mUserOp.sender); - StakeInfo memory factoryInfo; - { - bytes calldata initCode = userOp.initCode; - address factory = initCode.length >= 20 - ? address(bytes20(initCode[0:20])) - : address(0); - factoryInfo = _getStakeInfo(factory); - } - - ValidationData memory data = _intersectTimeRange( - validationData, - paymasterValidationData - ); - address aggregator = data.aggregator; - bool sigFailed = aggregator == address(1); - ReturnInfo memory returnInfo = ReturnInfo( - outOpInfo.preOpGas, - outOpInfo.prefund, - sigFailed, - data.validAfter, - data.validUntil, - getMemoryBytesFromOffset(outOpInfo.contextOffset) - ); - - if (aggregator != address(0) && aggregator != address(1)) { - AggregatorStakeInfo memory aggregatorInfo = AggregatorStakeInfo( - aggregator, - _getStakeInfo(aggregator) - ); - revert ValidationResultWithAggregation( - returnInfo, - senderInfo, - factoryInfo, - paymasterInfo, - aggregatorInfo - ); - } - revert ValidationResult( - returnInfo, - senderInfo, - factoryInfo, - paymasterInfo - ); - } - /** * Get the required prefunded gas fee amount for an operation. * @param mUserOp - The user operation in memory. @@ -482,50 +389,6 @@ contract EntryPoint is IEntryPoint, StakeManager, NonceManager, ReentrancyGuard, revert SenderAddressResult(sender); } - function _simulationOnlyValidations( - UserOperation calldata userOp - ) internal view { - try - this._validateSenderAndPaymaster( - userOp.initCode, - userOp.sender, - userOp.paymasterAndData - ) - // solhint-disable-next-line no-empty-blocks - {} catch Error(string memory revertReason) { - if (bytes(revertReason).length != 0) { - revert FailedOp(0, revertReason); - } - } - } - - /** - * Called only during simulation. - * This function always reverts to prevent warm/cold storage differentiation in simulation vs execution. - * @param initCode - The smart account constructor code. - * @param sender - The sender address. - * @param paymasterAndData - The paymaster address followed by the token address to use. - */ - function _validateSenderAndPaymaster( - bytes calldata initCode, - address sender, - bytes calldata paymasterAndData - ) external view { - if (initCode.length == 0 && sender.code.length == 0) { - // it would revert anyway. but give a meaningful message - revert("AA20 account not deployed"); - } - if (paymasterAndData.length >= 20) { - address paymaster = address(bytes20(paymasterAndData[0:20])); - if (paymaster.code.length == 0) { - // It would revert anyway. but give a meaningful message. - revert("AA30 paymaster not deployed"); - } - } - // always revert - revert(""); - } - /** * Call account.validateUserOp. * Revert (with FailedOp) in case validateUserOp reverts, or account didn't send required prefund. @@ -707,7 +570,7 @@ contract EntryPoint is IEntryPoint, StakeManager, NonceManager, ReentrancyGuard, UserOperation calldata userOp, UserOpInfo memory outOpInfo ) - private + internal returns (uint256 validationData, uint256 paymasterValidationData) { uint256 preGas = gasleft(); diff --git a/contracts/core/EntryPointSimulations.sol b/contracts/core/EntryPointSimulations.sol new file mode 100644 index 00000000..9a5f50c9 --- /dev/null +++ b/contracts/core/EntryPointSimulations.sol @@ -0,0 +1,185 @@ +// SPDX-License-Identifier: GPL-3.0 +pragma solidity ^0.8.12; + +/* solhint-disable avoid-low-level-calls */ +/* solhint-disable no-inline-assembly */ + +import "./EntryPoint.sol"; +import "../interfaces/IEntryPointSimulations.sol"; + +/* + * This contract inherits the EntryPoint and extends it with the view-only methods that are executed by + * the bundler in order to check UserOperation validity and estimate its gas consumption. + * This contract should never be deployed on-chain and is only used as a parameter for the "eth_call" request. + */ +contract EntryPointSimulations is EntryPoint, IEntryPointSimulations { + // solhint-disable-next-line var-name-mixedcase + AggregatorStakeInfo private NOT_AGGREGATED = AggregatorStakeInfo(address(0), StakeInfo(0, 0)); + + /** + * simulation contract should not be deployed, and specifically, accounts should not trust + * it as entrypoint, since the simulation functions don't check the signatures + */ + constructor() { + require(block.number < 100, "should not be deployed"); + } + + /// @inheritdoc IEntryPointSimulations + function simulateValidation( + UserOperation calldata userOp + ) + external + returns ( + ValidationResult memory + ){ + UserOpInfo memory outOpInfo; + + _simulationOnlyValidations(userOp); + ( + uint256 validationData, + uint256 paymasterValidationData + ) = _validatePrepayment(0, userOp, outOpInfo); + StakeInfo memory paymasterInfo = _getStakeInfo( + outOpInfo.mUserOp.paymaster + ); + StakeInfo memory senderInfo = _getStakeInfo(outOpInfo.mUserOp.sender); + StakeInfo memory factoryInfo; + { + bytes calldata initCode = userOp.initCode; + address factory = initCode.length >= 20 + ? address(bytes20(initCode[0 : 20])) + : address(0); + factoryInfo = _getStakeInfo(factory); + } + + ValidationData memory data = _intersectTimeRange( + validationData, + paymasterValidationData + ); + address aggregator = data.aggregator; + bool sigFailed = aggregator == address(1); + ReturnInfo memory returnInfo = ReturnInfo( + outOpInfo.preOpGas, + outOpInfo.prefund, + sigFailed, + data.validAfter, + data.validUntil, + getMemoryBytesFromOffset(outOpInfo.contextOffset) + ); + + AggregatorStakeInfo memory aggregatorInfo = NOT_AGGREGATED; + if (aggregator != address(0) && aggregator != address(1)) { + aggregatorInfo = AggregatorStakeInfo( + aggregator, + _getStakeInfo(aggregator) + ); + } + return ValidationResult( + returnInfo, + senderInfo, + factoryInfo, + paymasterInfo, + aggregatorInfo + ); + } + + /// @inheritdoc IEntryPointSimulations + function simulateHandleOp( + UserOperation calldata op, + address target, + bytes calldata targetCallData + ) + external nonReentrant + returns ( + ExecutionResult memory + ){ + UserOpInfo memory opInfo; + _simulationOnlyValidations(op); + ( + uint256 validationData, + uint256 paymasterValidationData + ) = _validatePrepayment(0, op, opInfo); + ValidationData memory data = _intersectTimeRange( + validationData, + paymasterValidationData + ); + + numberMarker(); + uint256 paid = _executeUserOp(0, op, opInfo); + numberMarker(); + bool targetSuccess; + bytes memory targetResult; + if (target != address(0)) { + (targetSuccess, targetResult) = target.call(targetCallData); + } + return ExecutionResult( + opInfo.preOpGas, + paid, + data.validAfter, + data.validUntil, + targetSuccess, + targetResult + ); + } + + function _simulationOnlyValidations( + UserOperation calldata userOp + ) + internal + view + { + try + this._validateSenderAndPaymaster( + userOp.initCode, + userOp.sender, + userOp.paymasterAndData + ) + // solhint-disable-next-line no-empty-blocks + {} catch Error(string memory revertReason) { + if (bytes(revertReason).length != 0) { + revert FailedOp(0, revertReason); + } + } + } + + /** + * Called only during simulation. + * This function always reverts to prevent warm/cold storage differentiation in simulation vs execution. + * @param initCode - The smart account constructor code. + * @param sender - The sender address. + * @param paymasterAndData - The paymaster address followed by the token address to use. + */ + function _validateSenderAndPaymaster( + bytes calldata initCode, + address sender, + bytes calldata paymasterAndData + ) external view { + if (initCode.length == 0 && sender.code.length == 0) { + // it would revert anyway. but give a meaningful message + revert("AA20 account not deployed"); + } + if (paymasterAndData.length >= 20) { + address paymaster = address(bytes20(paymasterAndData[0 : 20])); + if (paymaster.code.length == 0) { + // It would revert anyway. but give a meaningful message. + revert("AA30 paymaster not deployed"); + } + } + // always revert + revert(""); + } + + //make sure depositTo cost is more than normal EntryPoint's cost. + // empiric test showed that without this wrapper, simulation depositTo costs less.. + function depositTo(address account) public override(IStakeManager, StakeManager) payable { + uint x; + assembly { + //some silly code to waste ~200 gas + x := exp(mload(0),100) + } + if (x == 123) { + return; + } + StakeManager.depositTo(account); + } +} diff --git a/contracts/core/StakeManager.sol b/contracts/core/StakeManager.sol index eada9e53..ae62bdc7 100644 --- a/contracts/core/StakeManager.sol +++ b/contracts/core/StakeManager.sol @@ -62,7 +62,7 @@ abstract contract StakeManager is IStakeManager { * Add to the deposit of the given account. * @param account - The account to add to. */ - function depositTo(address account) public payable { + function depositTo(address account) public virtual payable { _incrementDeposit(account, msg.value); DepositInfo storage info = deposits[account]; emit Deposited(account, info.deposit); diff --git a/contracts/interfaces/IEntryPoint.sol b/contracts/interfaces/IEntryPoint.sol index ddb2693d..bace9a64 100644 --- a/contracts/interfaces/IEntryPoint.sol +++ b/contracts/interfaces/IEntryPoint.sol @@ -94,50 +94,9 @@ interface IEntryPoint is IStakeManager, INonceManager { */ error SignatureValidationFailed(address aggregator); - /** - * Successful result from simulateValidation. - * @param returnInfo - Gas and time-range returned values. - * @param senderInfo - Stake information about the sender. - * @param factoryInfo - Stake information about the factory (if any). - * @param paymasterInfo - Stake information about the paymaster (if any). - */ - error ValidationResult( - ReturnInfo returnInfo, - StakeInfo senderInfo, - StakeInfo factoryInfo, - StakeInfo paymasterInfo - ); - - /** - * Successful result from simulateValidation, if the account returns a signature aggregator. - * @param returnInfo Gas and time-range returned values - * @param senderInfo Stake information about the sender - * @param factoryInfo Stake information about the factory (if any) - * @param paymasterInfo Stake information about the paymaster (if any) - * @param aggregatorInfo Signature aggregation info (if the account requires signature aggregator) - * Bundler MUST use it to verify the signature, or reject the UserOperation. - */ - error ValidationResultWithAggregation( - ReturnInfo returnInfo, - StakeInfo senderInfo, - StakeInfo factoryInfo, - StakeInfo paymasterInfo, - AggregatorStakeInfo aggregatorInfo - ); - // Return value of getSenderAddress. error SenderAddressResult(address sender); - // Return value of simulateHandleOp. - error ExecutionResult( - uint256 preOpGas, - uint256 paid, - uint48 validAfter, - uint48 validUntil, - bool targetSuccess, - bytes targetResult - ); - // UserOps handled, per aggregator. struct UserOpsPerAggregator { UserOperation[] userOps; @@ -179,15 +138,6 @@ interface IEntryPoint is IStakeManager, INonceManager { UserOperation calldata userOp ) external view returns (bytes32); - /** - * Simulate a call to account.validateUserOp and paymaster.validatePaymasterUserOp. - * @dev This method always reverts. Successful result is ValidationResult error. other errors are failures. - * @dev The node must also verify it doesn't use banned opcodes, and that it doesn't reference storage - * outside the account's data. - * @param userOp - The user operation to validate. - */ - function simulateValidation(UserOperation calldata userOp) external; - /** * Gas and return values during simulation. * @param preOpGas - The gas used for validation (including preValidationGas) @@ -223,22 +173,4 @@ interface IEntryPoint is IStakeManager, INonceManager { */ function getSenderAddress(bytes memory initCode) external; - /** - * Simulate full execution of a UserOperation (including both validation and target execution) - * This method will always revert with "ExecutionResult". - * It performs full validation of the UserOperation, but ignores signature error. - * An optional target address is called after the userop succeeds, - * and its value is returned (before the entire call is reverted). - * Note that in order to collect the the success/failure of the target call, it must be executed - * with trace enabled to track the emitted events. - * @param op The UserOperation to simulate. - * @param target - If nonzero, a target address to call after userop simulation. If called, - * the targetSuccess and targetResult are set to the return from that call. - * @param targetCallData - CallData to pass to target address. - */ - function simulateHandleOp( - UserOperation calldata op, - address target, - bytes calldata targetCallData - ) external; } diff --git a/contracts/interfaces/IEntryPointSimulations.sol b/contracts/interfaces/IEntryPointSimulations.sol new file mode 100644 index 00000000..bcc822c5 --- /dev/null +++ b/contracts/interfaces/IEntryPointSimulations.sol @@ -0,0 +1,73 @@ +// SPDX-License-Identifier: GPL-3.0 +pragma solidity ^0.8.12; + +import "./UserOperation.sol"; +import "./IEntryPoint.sol"; + +interface IEntryPointSimulations is IEntryPoint { + // Return value of simulateHandleOp. + struct ExecutionResult { + uint256 preOpGas; + uint256 paid; + uint48 validAfter; + uint48 validUntil; + bool targetSuccess; + bytes targetResult; + } + + /** + * Successful result from simulateValidation. + * If the account returns a signature aggregator the "aggregatorInfo" struct is filled in as well. + * @param returnInfo Gas and time-range returned values + * @param senderInfo Stake information about the sender + * @param factoryInfo Stake information about the factory (if any) + * @param paymasterInfo Stake information about the paymaster (if any) + * @param aggregatorInfo Signature aggregation info (if the account requires signature aggregator) + * Bundler MUST use it to verify the signature, or reject the UserOperation. + */ + struct ValidationResult { + ReturnInfo returnInfo; + StakeInfo senderInfo; + StakeInfo factoryInfo; + StakeInfo paymasterInfo; + AggregatorStakeInfo aggregatorInfo; + } + + /** + * Simulate a call to account.validateUserOp and paymaster.validatePaymasterUserOp. + * @dev This method always reverts. Successful result is ValidationResult error. other errors are failures. + * @dev The node must also verify it doesn't use banned opcodes, and that it doesn't reference storage + * outside the account's data. + * @param userOp - The user operation to validate. + */ + function simulateValidation( + UserOperation calldata userOp + ) + external + returns ( + ValidationResult memory + ); + + /** + * Simulate full execution of a UserOperation (including both validation and target execution) + * This method will always revert with "ExecutionResult". + * It performs full validation of the UserOperation, but ignores signature error. + * An optional target address is called after the userop succeeds, + * and its value is returned (before the entire call is reverted). + * Note that in order to collect the the success/failure of the target call, it must be executed + * with trace enabled to track the emitted events. + * @param op The UserOperation to simulate. + * @param target - If nonzero, a target address to call after userop simulation. If called, + * the targetSuccess and targetResult are set to the return from that call. + * @param targetCallData - CallData to pass to target address. + */ + function simulateHandleOp( + UserOperation calldata op, + address target, + bytes calldata targetCallData + ) + external + returns ( + ExecutionResult memory + ); +} diff --git a/eip/EIPS/eip-4337.md b/eip/EIPS/eip-4337.md index cccc9e16..c311c47f 100644 --- a/eip/EIPS/eip-4337.md +++ b/eip/EIPS/eip-4337.md @@ -82,39 +82,11 @@ function handleAggregatedOps( address payable beneficiary ); - struct UserOpsPerAggregator { UserOperation[] userOps; IAggregator aggregator; bytes signature; } -function simulateValidation(UserOperation calldata userOp); - -error ValidationResult(ReturnInfo returnInfo, - StakeInfo senderInfo, StakeInfo factoryInfo, StakeInfo paymasterInfo); - -error ValidationResultWithAggregation(ReturnInfo returnInfo, - StakeInfo senderInfo, StakeInfo factoryInfo, StakeInfo paymasterInfo, - AggregatorStakeInfo aggregatorInfo); - -struct ReturnInfo { - uint256 preOpGas; - uint256 prefund; - bool sigFailed; - uint48 validAfter; - uint48 validUntil; - bytes paymasterContext; -} - -struct StakeInfo { - uint256 stake; - uint256 unstakeDelaySec; -} - -struct AggregatorStakeInfo { - address actualAggregator; - StakeInfo stakeInfo; -} ``` The core interface required for an account to have is: @@ -133,7 +105,7 @@ The account: * MUST validate the caller is a trusted EntryPoint * If the account does not support signature aggregation, it MUST validate the signature is a valid signature of the `userOpHash`, and - SHOULD return SIG_VALIDATION_FAILED (and not revert) on signature mismatch. Any other error should revert. + SHOULD return SIG_VALIDATION_FAILED (and not revert) on signature mismatch. Any other error MUST revert. * MUST pay the entryPoint (caller) at least the "missingAccountFunds" (which might be zero, in case current account's deposit is high enough) * The account MAY pay more than this minimum, to cover future transactions (it can always issue `withdrawTo` to retrieve it) * The return value MUST be packed of `authorizer`, `validUntil` and `validAfter` timestamps. @@ -318,7 +290,7 @@ function balanceOf(address account) public view returns (uint256) // add to the deposit of the given account function depositTo(address account) public payable -// withdraw from the deposit +// withdraw from the deposit of the current account function withdrawTo(address payable withdrawAddress, uint256 withdrawAmount) external ``` @@ -349,10 +321,50 @@ Each of these contracts is also restricted in its storage access, to make sure U #### Specification: -To simulate a `UserOperation` validation, the client makes a view call to `simulateValidation(userop)` +To simulate a `UserOperation` validation, the client makes a view call to `simulateValidation(userop)`. + +The EntryPoint itself does not implement the simulation methods. Instead, when making the simulation view call, +The bundler should provide the alternate EntryPointSimulations code, which extends the EntryPoint with the simulation methods. + +The simulation core methods: + +```solidity + +struct ValidationResult { + ReturnInfo returnInfo; + StakeInfo senderInfo; + StakeInfo factoryInfo; + StakeInfo paymasterInfo; + AggregatorStakeInfo aggregatorInfo; +} + +function simulateValidation(UserOperation calldata userOp) +external returns (ValidationResult memory); + +struct ReturnInfo { + uint256 preOpGas; + uint256 prefund; + bool sigFailed; + uint48 validAfter; + uint48 validUntil; + bytes paymasterContext; +} + +struct AggregatorStakeInfo { + address aggregator; + StakeInfo stakeInfo; +} + +struct StakeInfo { + uint256 stake; + uint256 unstakeDelaySec; +} + + +``` -This method always revert with `ValidationResult` as successful response. -If the call reverts with other error, the client rejects this `userOp`. +This method returns `ValidationResult` or revert on validation failure. +The node should drop the UserOperation if the simulation fails (either by revert or by "signature failure") The simulated call performs the full validation, by calling: diff --git a/package.json b/package.json index 42a8d2fd..400563c5 100644 --- a/package.json +++ b/package.json @@ -27,9 +27,9 @@ "author": "", "license": "ISC", "devDependencies": { + "@nomicfoundation/hardhat-chai-matchers": "^1.0.6", "@nomiclabs/hardhat-ethers": "^2.0.2", "@nomiclabs/hardhat-waffle": "^2.0.1", - "@nomicfoundation/hardhat-chai-matchers": "^1.0.6", "@typechain/ethers-v5": "^10.1.0", "@types/chai": "^4.2.21", "@types/node": "^16.4.12", @@ -46,7 +46,7 @@ "eslint-plugin-standard": "^5.0.0", "ethereum-waffle": "^3.4.0", "ethers": "^5.4.2", - "hardhat": "^2.6.6", + "hardhat": "^2.17.2", "solhint": "^3.3.7", "ts-generator": "^0.1.1", "ts-mocha": "^10.0.0", @@ -64,7 +64,7 @@ "ethereumjs-wallet": "^1.0.1", "hardhat-deploy": "^0.11.23", "hardhat-deploy-ethers": "^0.3.0-beta.11", - "solidity-coverage": "^0.8.2", + "solidity-coverage": "^0.8.4", "source-map-support": "^0.5.19", "table": "^6.8.0", "typescript": "^4.3.5" diff --git a/reports/gas-checker.txt b/reports/gas-checker.txt index 9fd84fa8..867078e4 100644 --- a/reports/gas-checker.txt +++ b/reports/gas-checker.txt @@ -2,7 +2,7 @@ the destination is "account.entryPoint()", which is known to be "hot" address used by this account it little higher than EOA call: its an exec from entrypoint (or account owner) into account contract, verifying msg.sender and exec to target) ╔══════════════════════════╤════════╗ -║ gas estimate "simple" │ 29014 ║ +║ gas estimate "simple" │ 28990 ║ ╟──────────────────────────┼────────╢ ║ gas estimate "big tx 5k" │ 125260 ║ ╚══════════════════════════╧════════╝ @@ -12,28 +12,28 @@ ║ │ │ │ (delta for │ (compared to ║ ║ │ │ │ one UserOp) │ account.exec()) ║ ╟────────────────────────────────┼───────┼───────────────┼────────────────┼─────────────────────╢ -║ simple │ 1 │ 81943 │ │ ║ +║ simple │ 1 │ 81954 │ │ ║ ╟────────────────────────────────┼───────┼───────────────┼────────────────┼─────────────────────╢ -║ simple - diff from previous │ 2 │ │ 44208 │ 15194 ║ +║ simple - diff from previous │ 2 │ │ 44175 │ 15185 ║ ╟────────────────────────────────┼───────┼───────────────┼────────────────┼─────────────────────╢ -║ simple │ 10 │ 479920 │ │ ║ +║ simple │ 10 │ 480006 │ │ ║ ╟────────────────────────────────┼───────┼───────────────┼────────────────┼─────────────────────╢ -║ simple - diff from previous │ 11 │ │ 44280 │ 15266 ║ +║ simple - diff from previous │ 11 │ │ 44259 │ 15269 ║ ╟────────────────────────────────┼───────┼───────────────┼────────────────┼─────────────────────╢ -║ simple paymaster │ 1 │ 88202 │ │ ║ +║ simple paymaster │ 1 │ 88237 │ │ ║ ╟────────────────────────────────┼───────┼───────────────┼────────────────┼─────────────────────╢ -║ simple paymaster with diff │ 2 │ │ 43197 │ 14183 ║ +║ simple paymaster with diff │ 2 │ │ 43200 │ 14210 ║ ╟────────────────────────────────┼───────┼───────────────┼────────────────┼─────────────────────╢ -║ simple paymaster │ 10 │ 477156 │ │ ║ +║ simple paymaster │ 10 │ 477158 │ │ ║ ╟────────────────────────────────┼───────┼───────────────┼────────────────┼─────────────────────╢ -║ simple paymaster with diff │ 11 │ │ 43184 │ 14170 ║ +║ simple paymaster with diff │ 11 │ │ 43271 │ 14281 ║ ╟────────────────────────────────┼───────┼───────────────┼────────────────┼─────────────────────╢ -║ big tx 5k │ 1 │ 183000 │ │ ║ +║ big tx 5k │ 1 │ 183011 │ │ ║ ╟────────────────────────────────┼───────┼───────────────┼────────────────┼─────────────────────╢ -║ big tx - diff from previous │ 2 │ │ 144719 │ 19459 ║ +║ big tx - diff from previous │ 2 │ │ 144722 │ 19462 ║ ╟────────────────────────────────┼───────┼───────────────┼────────────────┼─────────────────────╢ -║ big tx 5k │ 10 │ 1485612 │ │ ║ +║ big tx 5k │ 10 │ 1485614 │ │ ║ ╟────────────────────────────────┼───────┼───────────────┼────────────────┼─────────────────────╢ -║ big tx - diff from previous │ 11 │ │ 144720 │ 19460 ║ +║ big tx - diff from previous │ 11 │ │ 144735 │ 19475 ║ ╚════════════════════════════════╧═══════╧═══════════════╧════════════════╧═════════════════════╝ diff --git a/scripts/solcErrors b/scripts/solcErrors index 3307c757..39a0f29d 100755 --- a/scripts/solcErrors +++ b/scripts/solcErrors @@ -4,7 +4,7 @@ export LC_CTYPE= #js error (recognized) # at SimpleWallet._validateSignature (contracts/samples/SimpleWallet.sol:76) -perl -pe 's/--> (\S+):(\d+):(\d+):/at ($1:$2)/;' \ +perl -pe 's/-->.*? (\S+):(\d+):(\d+):/at ($1:$2)/;' \ -e "s/.*(Solidity 0.8.\d+ is not fully supported yet|Learn.more at.*solidity-support).*\s*//;" \ -e "s/^\s*$//;" \ -e "s/\s*at .*node_modules.*\s*//;" \ diff --git a/test/create2factory.test.ts b/test/0-create2factory.test.ts similarity index 96% rename from test/create2factory.test.ts rename to test/0-create2factory.test.ts index c67bae7c..d95a76c5 100644 --- a/test/create2factory.test.ts +++ b/test/0-create2factory.test.ts @@ -7,6 +7,11 @@ import { Provider } from '@ethersproject/providers' describe('test Create2Factory', () => { let factory: Create2Factory let provider: Provider + + if (process.env.COVERAGE != null) { + return + } + before(async () => { provider = ethers.provider factory = new Create2Factory(provider) diff --git a/test/UserOp.ts b/test/UserOp.ts index c391f1e9..daa0decd 100644 --- a/test/UserOp.ts +++ b/test/UserOp.ts @@ -8,10 +8,15 @@ import { BigNumber, Contract, Signer, Wallet } from 'ethers' import { AddressZero, callDataCost, rethrow } from './testutils' import { ecsign, toRpcSig, keccak256 as keccak256_buffer } from 'ethereumjs-util' import { - EntryPoint + EntryPoint, EntryPointSimulations__factory } from '../typechain' import { UserOperation } from './UserOperation' import { Create2Factory } from '../src/Create2Factory' +import { TransactionRequest } from '@ethersproject/abstract-provider' + +import EntryPointSimulationsJson from '../artifacts/contracts/core/EntryPointSimulations.sol/EntryPointSimulations.json' +import { ethers } from 'hardhat' +import { IEntryPointSimulations } from '../typechain/contracts/core/EntryPointSimulations' export function packUserOp (op: UserOperation, forSignature = true): string { if (forSignature) { @@ -198,8 +203,88 @@ export async function fillAndSign (op: Partial, signer: Wallet | const chainId = await provider!.getNetwork().then(net => net.chainId) const message = arrayify(getUserOpHash(op2, entryPoint!.address, chainId)) + let signature + try { + signature = await signer.signMessage(message) + } catch (err: any) { + // attempt to use 'eth_sign' instead of 'personal_sign' which is not supported by Foundry Anvil + signature = await (signer as any)._legacySignMessage(message) + } return { ...op2, - signature: await signer.signMessage(message) + signature + } +} + +/** + * This function relies on a "state override" functionality of the 'eth_call' RPC method + * in order to provide the details of a simulated validation call to the bundler + * @param userOp + * @param entryPointAddress + * @param txOverrides + */ +export async function simulateValidation ( + userOp: UserOperation, + entryPointAddress: string, + txOverrides?: any): Promise { + const entryPointSimulations = EntryPointSimulations__factory.createInterface() + const data = entryPointSimulations.encodeFunctionData('simulateValidation', [userOp]) + const tx: TransactionRequest = { + to: entryPointAddress, + data, + ...txOverrides + } + const stateOverride = { + [entryPointAddress]: { + code: EntryPointSimulationsJson.deployedBytecode + } + } + try { + const simulationResult = await ethers.provider.send('eth_call', [tx, 'latest', stateOverride]) + const res = entryPointSimulations.decodeFunctionResult('simulateValidation', simulationResult) + // note: here collapsing the returned "tuple of one" into a single value - will break for returning actual tuples + return res[0] + } catch (error: any) { + const revertData = error?.data + if (revertData != null) { + // note: this line throws the revert reason instead of returning it + entryPointSimulations.decodeFunctionResult('simulateValidation', revertData) + } + throw error + } +} + +// TODO: this code is very much duplicated but "encodeFunctionData" is based on 20 overloads +// TypeScript is not able to resolve overloads with variables: https://github.com/microsoft/TypeScript/issues/14107 +export async function simulateHandleOp ( + userOp: UserOperation, + target: string, + targetCallData: string, + entryPointAddress: string, + txOverrides?: any): Promise { + const entryPointSimulations = EntryPointSimulations__factory.createInterface() + const data = entryPointSimulations.encodeFunctionData('simulateHandleOp', [userOp, target, targetCallData]) + const tx: TransactionRequest = { + to: entryPointAddress, + data, + ...txOverrides + } + const stateOverride = { + [entryPointAddress]: { + code: EntryPointSimulationsJson.deployedBytecode + } + } + try { + const simulationResult = await ethers.provider.send('eth_call', [tx, 'latest', stateOverride]) + const res = entryPointSimulations.decodeFunctionResult('simulateHandleOp', simulationResult) + // note: here collapsing the returned "tuple of one" into a single value - will break for returning actual tuples + return res[0] + } catch (error: any) { + const revertData = error?.data + if (revertData != null) { + // note: this line throws the revert reason instead of returning it + entryPointSimulations.decodeFunctionResult('simulateHandleOp', revertData) + } + throw error } } diff --git a/test/deposit-paymaster.test.ts b/test/deposit-paymaster.test.ts index 9b745aad..2e7ca959 100644 --- a/test/deposit-paymaster.test.ts +++ b/test/deposit-paymaster.test.ts @@ -15,9 +15,9 @@ import { import { AddressZero, createAddress, createAccountOwner, - deployEntryPoint, FIVE_ETH, ONE_ETH, simulationResultCatch, userOpsWithoutAgg, createAccount + deployEntryPoint, FIVE_ETH, ONE_ETH, userOpsWithoutAgg, createAccount } from './testutils' -import { fillAndSign } from './UserOp' +import { fillAndSign, simulateValidation } from './UserOp' import { hexConcat, hexZeroPad, parseEther } from 'ethers/lib/utils' // TODO: fails after unrelated change in the repo @@ -89,7 +89,7 @@ describe.skip('DepositPaymaster', () => { sender: account.address, paymasterAndData: paymaster.address }, ethersSigner, entryPoint) - await expect(entryPoint.callStatic.simulateValidation(userOp)).to.be.revertedWith('paymasterAndData must specify token') + await expect(simulateValidation(userOp, entryPoint.address)).to.be.revertedWith('paymasterAndData must specify token') }) it('should fail with wrong token', async () => { @@ -97,7 +97,7 @@ describe.skip('DepositPaymaster', () => { sender: account.address, paymasterAndData: hexConcat([paymaster.address, hexZeroPad('0x1234', 20)]) }, ethersSigner, entryPoint) - await expect(entryPoint.callStatic.simulateValidation(userOp, { gasPrice })).to.be.revertedWith('DepositPaymaster: unsupported token') + await expect(simulateValidation(userOp, entryPoint.address, { gasPrice })).to.be.revertedWith('DepositPaymaster: unsupported token') }) it('should reject if no deposit', async () => { @@ -105,7 +105,7 @@ describe.skip('DepositPaymaster', () => { sender: account.address, paymasterAndData: hexConcat([paymaster.address, hexZeroPad(token.address, 20)]) }, ethersSigner, entryPoint) - await expect(entryPoint.callStatic.simulateValidation(userOp, { gasPrice })).to.be.revertedWith('DepositPaymaster: deposit too low') + await expect(simulateValidation(userOp, entryPoint.address, { gasPrice })).to.be.revertedWith('DepositPaymaster: deposit too low') }) it('should reject if deposit is not locked', async () => { @@ -118,7 +118,7 @@ describe.skip('DepositPaymaster', () => { sender: account.address, paymasterAndData: hexConcat([paymaster.address, hexZeroPad(token.address, 20)]) }, ethersSigner, entryPoint) - await expect(entryPoint.callStatic.simulateValidation(userOp, { gasPrice })).to.be.revertedWith('not locked') + await expect(simulateValidation(userOp, entryPoint.address, { gasPrice })).to.be.revertedWith('not locked') }) it('succeed with valid deposit', async () => { @@ -130,7 +130,7 @@ describe.skip('DepositPaymaster', () => { sender: account.address, paymasterAndData: hexConcat([paymaster.address, hexZeroPad(token.address, 20)]) }, ethersSigner, entryPoint) - await entryPoint.callStatic.simulateValidation(userOp).catch(simulationResultCatch) + await simulateValidation(userOp, entryPoint.address) }) }) describe('#handleOps', () => { diff --git a/test/entrypoint.test.ts b/test/entrypoint.test.ts index 281288ce..59ae22a0 100644 --- a/test/entrypoint.test.ts +++ b/test/entrypoint.test.ts @@ -2,7 +2,6 @@ import './aa.init' import { BigNumber, Event, Wallet } from 'ethers' import { expect } from 'chai' import { - EntryPoint, SimpleAccount, SimpleAccountFactory, TestAggregatedAccount__factory, @@ -26,7 +25,7 @@ import { IStakeManager__factory, INonceManager__factory, EntryPoint__factory, - TestPaymasterRevertCustomError__factory + TestPaymasterRevertCustomError__factory, EntryPoint } from '../typechain' import { AddressZero, @@ -37,7 +36,6 @@ import { tostr, getAccountInitCode, calcGasUsage, - checkForBannedOps, ONE_ETH, TWO_ETH, deployEntryPoint, @@ -45,12 +43,11 @@ import { createAddress, getAccountAddress, HashZero, - simulationResultCatch, createAccount, getAggregatedAccountInitCode, - simulationResultWithAggregationCatch, decodeRevertReason + decodeRevertReason } from './testutils' -import { DefaultsForUserOp, fillAndSign, getUserOpHash } from './UserOp' +import { DefaultsForUserOp, fillAndSign, getUserOpHash, simulateValidation } from './UserOp' import { UserOperation } from './UserOperation' import { PopulatedTransaction } from 'ethers/lib/ethers' import { ethers } from 'hardhat' @@ -227,183 +224,32 @@ describe('EntryPoint', function () { }) }) }) - describe('#simulateValidation', () => { const accountOwner1 = createAccountOwner() - let account1: SimpleAccount - - before(async () => { - ({ proxy: account1 } = await createAccount(ethersSigner, await accountOwner1.getAddress(), entryPoint.address)) - }) - - it('should fail if validateUserOp fails', async () => { - // using wrong nonce - const op = await fillAndSign({ sender: account.address, nonce: 1234 }, accountOwner, entryPoint) - await expect(entryPoint.callStatic.simulateValidation(op)).to - .revertedWith('AA25 invalid account nonce') - }) - - it('should report signature failure without revert', async () => { - // (this is actually a feature of the wallet, not the entrypoint) - // using wrong owner for account1 - // (zero gas price so it doesn't fail on prefund) - const op = await fillAndSign({ sender: account1.address, maxFeePerGas: 0 }, accountOwner, entryPoint) - const { returnInfo } = await entryPoint.callStatic.simulateValidation(op).catch(simulationResultCatch) - expect(returnInfo.sigFailed).to.be.true - }) - - it('should revert if wallet not deployed (and no initcode)', async () => { - const op = await fillAndSign({ - sender: createAddress(), - nonce: 0, - verificationGasLimit: 1000 - }, accountOwner, entryPoint) - await expect(entryPoint.callStatic.simulateValidation(op)).to - .revertedWith('AA20 account not deployed') - }) - - it('should revert on oog if not enough verificationGas', async () => { - const op = await fillAndSign({ sender: account.address, verificationGasLimit: 1000 }, accountOwner, entryPoint) - await expect(entryPoint.callStatic.simulateValidation(op)).to - .revertedWith('AA23 reverted (or OOG)') - }) - - it('should succeed if validateUserOp succeeds', async () => { - const op = await fillAndSign({ sender: account1.address }, accountOwner1, entryPoint) - await fund(account1) - await entryPoint.callStatic.simulateValidation(op).catch(simulationResultCatch) - }) - - it('should return empty context if no paymaster', async () => { - const op = await fillAndSign({ sender: account1.address, maxFeePerGas: 0 }, accountOwner1, entryPoint) - const { returnInfo } = await entryPoint.callStatic.simulateValidation(op).catch(simulationResultCatch) - expect(returnInfo.paymasterContext).to.eql('0x') - }) - - it('should return stake of sender', async () => { - const stakeValue = BigNumber.from(123) - const unstakeDelay = 3 - const { proxy: account2 } = await createAccount(ethersSigner, await ethersSigner.getAddress(), entryPoint.address) - await fund(account2) - await account2.execute(entryPoint.address, stakeValue, entryPoint.interface.encodeFunctionData('addStake', [unstakeDelay])) - const op = await fillAndSign({ sender: account2.address }, ethersSigner, entryPoint) - const result = await entryPoint.callStatic.simulateValidation(op).catch(simulationResultCatch) - expect(result.senderInfo).to.eql({ stake: stakeValue, unstakeDelaySec: unstakeDelay }) - }) - - it('should prevent overflows: fail if any numeric value is more than 120 bits', async () => { - const op = await fillAndSign({ - preVerificationGas: BigNumber.from(2).pow(130), - sender: account1.address - }, accountOwner1, entryPoint) - await expect( - entryPoint.callStatic.simulateValidation(op) - ).to.revertedWith('gas values overflow') - }) - - it('should fail creation for wrong sender', async () => { - const op1 = await fillAndSign({ - initCode: getAccountInitCode(accountOwner1.address, simpleAccountFactory), - sender: '0x'.padEnd(42, '1'), - verificationGasLimit: 3e6 - }, accountOwner1, entryPoint) - await expect(entryPoint.callStatic.simulateValidation(op1)) - .to.revertedWith('AA14 initCode must return sender') - }) - - it('should report failure on insufficient verificationGas (OOG) for creation', async () => { - const initCode = getAccountInitCode(accountOwner1.address, simpleAccountFactory) - const sender = await entryPoint.callStatic.getSenderAddress(initCode).catch(e => e.errorArgs.sender) - const op0 = await fillAndSign({ - initCode, - sender, - verificationGasLimit: 5e5, - maxFeePerGas: 0 - }, accountOwner1, entryPoint) - // must succeed with enough verification gas. - await expect(entryPoint.callStatic.simulateValidation(op0, { gasLimit: 1e6 })) - .to.revertedWith('ValidationResult') - - const op1 = await fillAndSign({ - initCode, - sender, - verificationGasLimit: 1e5, - maxFeePerGas: 0 - }, accountOwner1, entryPoint) - await expect(entryPoint.callStatic.simulateValidation(op1, { gasLimit: 1e6 })) - .to.revertedWith('AA13 initCode failed or OOG') - }) - - it('should succeed for creating an account', async () => { - const sender = await getAccountAddress(accountOwner1.address, simpleAccountFactory) - const op1 = await fillAndSign({ - sender, - initCode: getAccountInitCode(accountOwner1.address, simpleAccountFactory) - }, accountOwner1, entryPoint) - await fund(op1.sender) - - await entryPoint.callStatic.simulateValidation(op1).catch(simulationResultCatch) - }) - - it('should not call initCode from entrypoint', async () => { - // a possible attack: call an account's execFromEntryPoint through initCode. This might lead to stolen funds. - const { proxy: account } = await createAccount(ethersSigner, await accountOwner.getAddress(), entryPoint.address) - const sender = createAddress() - const op1 = await fillAndSign({ - initCode: hexConcat([ - account.address, - account.interface.encodeFunctionData('execute', [sender, 0, '0x']) - ]), - sender - }, accountOwner, entryPoint) - const error = await entryPoint.callStatic.simulateValidation(op1).catch(e => e) - expect(error.message).to.match(/initCode failed or OOG/, error) - }) + // note: for the actual opcode and storage rule restrictions see the reference bundler ValidationManager it('should not use banned ops during simulateValidation', async () => { const op1 = await fillAndSign({ initCode: getAccountInitCode(accountOwner1.address, simpleAccountFactory), sender: await getAccountAddress(accountOwner1.address, simpleAccountFactory) }, accountOwner1, entryPoint) await fund(op1.sender) - await entryPoint.simulateValidation(op1, { gasLimit: 10e6 }).catch(e => e) - const block = await ethers.provider.getBlock('latest') - const hash = block.transactions[0] - await checkForBannedOps(hash, false) - }) - }) - - describe('#simulateHandleOp', () => { - it('should simulate execution', async () => { - const accountOwner1 = createAccountOwner() - const { proxy: account } = await createAccount(ethersSigner, await accountOwner.getAddress(), entryPoint.address) - await fund(account) - const counter = await new TestCounter__factory(ethersSigner).deploy() - - const count = counter.interface.encodeFunctionData('count') - const callData = account.interface.encodeFunctionData('execute', [counter.address, 0, count]) - // deliberately broken signature.. simulate should work with it too. - const userOp = await fillAndSign({ - sender: account.address, - callData - }, accountOwner1, entryPoint) - - const ret = await entryPoint.callStatic.simulateHandleOp(userOp, - counter.address, - counter.interface.encodeFunctionData('counters', [account.address]) - ).catch(e => e.errorArgs) - - const [countResult] = counter.interface.decodeFunctionResult('counters', ret.targetResult) - expect(countResult).to.eql(1) - expect(ret.targetSuccess).to.be.true - - // actual counter is zero - expect(await counter.counters(account.address)).to.eql(0) + await simulateValidation(op1, entryPoint.address, { gasLimit: 10e6 }) + // TODO: can't do opcode banning with EntryPointSimulations (since its not on-chain) add when we can debug_traceCall + // const block = await ethers.provider.getBlock('latest') + // const hash = block.transactions[0] + // await checkForBannedOps(hash, false) }) }) describe('flickering account validation', () => { - it('should prevent leakage of basefee', async () => { + it('should prevent leakage of basefee', async function () { + if (process.env.COVERAGE != null) { + // coverage disables block.baseFee, which breaks this test... + // it also doesn't add to EntryPoint's coverage + this.skip() + } + const maliciousAccount = await new MaliciousAccount__factory(ethersSigner).deploy(entryPoint.address, { value: parseEther('1') }) @@ -431,11 +277,11 @@ describe('EntryPoint', function () { paymasterAndData: '0x' } try { - await expect(entryPoint.simulateValidation(userOp, { gasLimit: 1e6 })) - .to.revertedWith('ValidationResult') + await simulateValidation(userOp, entryPoint.address, { gasLimit: 1e6 }) + console.log('after first simulation') await ethers.provider.send('evm_mine', []) - await expect(entryPoint.simulateValidation(userOp, { gasLimit: 1e6 })) + await expect(simulateValidation(userOp, entryPoint.address, { gasLimit: 1e6 })) .to.revertedWith('Revert after first validation') // if we get here, it means the userOp passed first sim and reverted second expect.fail(null, null, 'should fail on first simulation') @@ -459,8 +305,8 @@ describe('EntryPoint', function () { callData: badData.data! } const beneficiaryAddress = createAddress() - await expect(entryPoint.simulateValidation(badOp, { gasLimit: 3e5 })) - .to.revertedWith('ValidationResult') + await simulateValidation(badOp, entryPoint.address, { gasLimit: 3e5 }) + const tx = await entryPoint.handleOps([badOp], beneficiaryAddress) // { gasLimit: 3e5 }) const receipt = await tx.wait() const userOperationRevertReasonEvent = receipt.events?.find(event => event.event === 'UserOperationRevertReason') @@ -481,13 +327,14 @@ describe('EntryPoint', function () { } const beneficiaryAddress = createAddress() try { - await entryPoint.simulateValidation(badOp, { gasLimit: 1e6 }) + await simulateValidation(badOp, entryPoint.address, { gasLimit: 1e6 }) + throw new Error('should revert') } catch (e: any) { if ((e as Error).message.includes('ValidationResult')) { const tx = await entryPoint.handleOps([badOp], beneficiaryAddress, { gasLimit: 1e6 }) await tx.wait() } else { - expect(e.message).to.include('FailedOp(0, "AA23 reverted (or OOG)")') + expect(e.message).to.include('AA23 reverted (or OOG)') } } }) @@ -505,13 +352,14 @@ describe('EntryPoint', function () { } const beneficiaryAddress = createAddress() try { - await entryPoint.simulateValidation(badOp, { gasLimit: 1e6 }) + await simulateValidation(badOp, entryPoint.address, { gasLimit: 1e6 }) + throw new Error('should revert') } catch (e: any) { if ((e as Error).message.includes('ValidationResult')) { const tx = await entryPoint.handleOps([badOp], beneficiaryAddress, { gasLimit: 1e6 }) await tx.wait() } else { - expect(e.message).to.include('FailedOp(0, "AA23 reverted (or OOG)")') + expect(e.message).to.include('AA23 reverted (or OOG)') } } }) @@ -813,14 +661,13 @@ describe('EntryPoint', function () { verificationGasLimit: 5e5 }, accountOwner, entryPoint) // must succeed with enough verification gas - await expect(entryPoint.callStatic.simulateValidation(op0)) - .to.revertedWith('ValidationResult') + await simulateValidation(op0, entryPoint.address) const op1 = await fillAndSign({ sender: account.address, verificationGasLimit: 10000 }, accountOwner, entryPoint) - await expect(entryPoint.callStatic.simulateValidation(op1)) + await expect(simulateValidation(op1, entryPoint.address)) .to.revertedWith('AA23 reverted (or OOG)') }) }) @@ -938,7 +785,7 @@ describe('EntryPoint', function () { verificationGasLimit: 76000 }, accountOwner2, entryPoint) - await entryPoint.callStatic.simulateValidation(op2, { gasPrice: 1e9 }).catch(simulationResultCatch) + await simulateValidation(op2, entryPoint.address) await fund(op1.sender) await fund(account2.address) @@ -1121,7 +968,7 @@ describe('EntryPoint', function () { }) it('simulateValidation should return aggregator and its stake', async () => { await aggregator.addStake(entryPoint.address, 3, { value: TWO_ETH }) - const { aggregatorInfo } = await entryPoint.callStatic.simulateValidation(userOp).catch(simulationResultWithAggregationCatch) + const { aggregatorInfo } = await simulateValidation(userOp, entryPoint.address) expect(aggregatorInfo.aggregator).to.equal(aggregator.address) expect(aggregatorInfo.stakeInfo.stake).to.equal(TWO_ETH) expect(aggregatorInfo.stakeInfo.unstakeDelaySec).to.equal(3) @@ -1162,7 +1009,7 @@ describe('EntryPoint', function () { verificationGasLimit: 3e6, callGasLimit: 1e6 }, account2Owner, entryPoint) - await expect(entryPoint.simulateValidation(op)).to.revertedWith('"AA30 paymaster not deployed"') + await expect(simulateValidation(op, entryPoint.address)).to.revertedWith('"AA30 paymaster not deployed"') }) it('should fail if paymaster has no deposit', async function () { @@ -1221,7 +1068,7 @@ describe('EntryPoint', function () { initCode: getAccountInitCode(anOwner.address, simpleAccountFactory) }, anOwner, entryPoint) - const { paymasterInfo } = await entryPoint.callStatic.simulateValidation(op).catch(simulationResultCatch) + const { paymasterInfo } = await simulateValidation(op, entryPoint.address) const { stake: simRetStake, unstakeDelaySec: simRetDelay @@ -1252,7 +1099,7 @@ describe('EntryPoint', function () { const userOp = await fillAndSign({ sender: account.address }, sessionOwner, entryPoint) - const ret = await entryPoint.callStatic.simulateValidation(userOp).catch(simulationResultCatch) + const ret = await simulateValidation(userOp, entryPoint.address) expect(ret.returnInfo.validUntil).to.eql(now + 60) expect(ret.returnInfo.validAfter).to.eql(100) }) @@ -1263,7 +1110,7 @@ describe('EntryPoint', function () { const userOp = await fillAndSign({ sender: account.address }, expiredOwner, entryPoint) - const ret = await entryPoint.callStatic.simulateValidation(userOp).catch(simulationResultCatch) + const ret = await simulateValidation(userOp, entryPoint.address) expect(ret.returnInfo.validUntil).eql(now - 60) expect(ret.returnInfo.validAfter).to.eql(123) }) @@ -1286,7 +1133,7 @@ describe('EntryPoint', function () { sender: account.address, paymasterAndData: hexConcat([paymaster.address, timeRange]) }, ethersSigner, entryPoint) - const ret = await entryPoint.callStatic.simulateValidation(userOp).catch(simulationResultCatch) + const ret = await simulateValidation(userOp, entryPoint.address) expect(ret.returnInfo.validUntil).to.eql(now + 60) expect(ret.returnInfo.validAfter).to.eql(123) }) @@ -1297,7 +1144,7 @@ describe('EntryPoint', function () { sender: account.address, paymasterAndData: hexConcat([paymaster.address, timeRange]) }, ethersSigner, entryPoint) - const ret = await entryPoint.callStatic.simulateValidation(userOp).catch(simulationResultCatch) + const ret = await simulateValidation(userOp, entryPoint.address) expect(ret.returnInfo.validUntil).to.eql(now - 60) expect(ret.returnInfo.validAfter).to.eql(321) }) @@ -1320,7 +1167,7 @@ describe('EntryPoint', function () { async function simulateWithPaymasterParams (after: number, until: number): Promise { const userOp = await createOpWithPaymasterParams(owner, after, until) - const ret = await entryPoint.callStatic.simulateValidation(userOp).catch(simulationResultCatch) + const ret = await simulateValidation(userOp, entryPoint.address) return ret.returnInfo } diff --git a/test/entrypointsimulations.test.ts b/test/entrypointsimulations.test.ts new file mode 100644 index 00000000..8920057a --- /dev/null +++ b/test/entrypointsimulations.test.ts @@ -0,0 +1,270 @@ +import { ethers } from 'hardhat' +import { expect } from 'chai' + +import { + EntryPoint, EntryPointSimulations, EntryPointSimulations__factory, + SimpleAccount, + SimpleAccountFactory, + TestCounter__factory +} from '../typechain' +import { + ONE_ETH, + createAccount, + createAccountOwner, + createAddress, + fund, + getAccountAddress, + getAccountInitCode, + getBalance, deployEntryPoint +} from './testutils' + +import { fillAndSign, simulateHandleOp, simulateValidation } from './UserOp' +import { BigNumber, Wallet } from 'ethers' +import { hexConcat } from 'ethers/lib/utils' + +const provider = ethers.provider +describe('EntryPointSimulations', function () { + const ethersSigner = ethers.provider.getSigner() + + let account: SimpleAccount + let accountOwner: Wallet + let simpleAccountFactory: SimpleAccountFactory + + let entryPoint: EntryPoint + let epSimulation: EntryPointSimulations + + before(async function () { + entryPoint = await deployEntryPoint() + epSimulation = await new EntryPointSimulations__factory(provider.getSigner()).deploy() + + accountOwner = createAccountOwner(); + ({ + proxy: account, + accountFactory: simpleAccountFactory + } = await createAccount(ethersSigner, await accountOwner.getAddress(), entryPoint.address)) + + // await checkStateDiffSupported() + }) + + describe('Simulation Contract Sanity checks', () => { + const addr = createAddress() + + // coverage skews gas checks. + if (process.env.COVERAGE != null) { + return + } + + function costInRange (simCost: BigNumber, epCost: BigNumber, message: string): void { + const diff = simCost.sub(epCost).toNumber() + expect(diff).to.be.within(0, 300, + `${message} cost ${simCost.toNumber()} should be (up to 200) above ep cost ${epCost.toNumber()}`) + } + it('deposit on simulation must be >= real entrypoint', async () => { + costInRange( + await epSimulation.estimateGas.depositTo(addr, { value: 1 }), + await entryPoint.estimateGas.depositTo(addr, { value: 1 }), 'deposit with value') + }) + it('deposit without value on simulation must be >= real entrypoint', async () => { + costInRange( + await epSimulation.estimateGas.depositTo(addr, { value: 0 }), + await entryPoint.estimateGas.depositTo(addr, { value: 0 }), 'deposit without value') + }) + it('eth transfer on simulation must be >= real entrypoint', async () => { + costInRange( + await provider.estimateGas({ to: epSimulation.address, value: 1 }), + await provider.estimateGas({ to: entryPoint.address, value: 1 }), 'eth transfer with value') + }) + it('eth transfer (even without value) on simulation must be >= real entrypoint', async () => { + costInRange( + await provider.estimateGas({ to: epSimulation.address, value: 0 }), + await provider.estimateGas({ to: entryPoint.address, value: 0 }), 'eth transfer with value') + }) + }) + /* + async function checkStateDiffSupported (): Promise { + const tx: TransactionRequest = { + to: entryPoint.address, + data: '0x' + } + const stateOverride = { + [entryPoint.address]: { + code: '0x61030960005260206000f3' + // 0000 61 PUSH2 0x0309 | value 777 + // 0003 60 PUSH1 0x00 | offset 0 + // 0005 52 MSTORE | + // 0006 60 PUSH1 0x20 | size 32 + // 0008 60 PUSH1 0x00 | offset 0 + // 000A F3 RETURN | + } + } + const simulationResult = await ethers.provider.send('eth_call', [tx, 'latest', stateOverride]) + expect(parseInt(simulationResult, 16)).to.equal(777) + } +*/ + + describe('#simulateValidation', () => { + const accountOwner1 = createAccountOwner() + let account1: SimpleAccount + + before(async () => { + ({ proxy: account1 } = await createAccount(ethersSigner, await accountOwner1.getAddress(), entryPoint.address)) + await account.addDeposit({ value: ONE_ETH }) + expect(await getBalance(account.address)).to.equal(0) + expect(await account.getDeposit()).to.eql(ONE_ETH) + }) + + it('should fail if validateUserOp fails', async () => { + // using wrong nonce + const op = await fillAndSign({ sender: account.address, nonce: 1234 }, accountOwner, entryPoint) + await expect(simulateValidation(op, entryPoint.address)).to + .revertedWith('AA25 invalid account nonce') + }) + + it('should report signature failure without revert', async () => { + // (this is actually a feature of the wallet, not the entrypoint) + // using wrong owner for account1 + // (zero gas price so that it doesn't fail on prefund) + const op = await fillAndSign({ sender: account1.address, maxFeePerGas: 0 }, accountOwner, entryPoint) + const { returnInfo } = await simulateValidation(op, entryPoint.address) + expect(returnInfo.sigFailed).to.be.true + }) + + it('should revert if wallet not deployed (and no initCode)', async () => { + const op = await fillAndSign({ + sender: createAddress(), + nonce: 0, + verificationGasLimit: 1000 + }, accountOwner, entryPoint) + await expect(simulateValidation(op, entryPoint.address)).to + .revertedWith('AA20 account not deployed') + }) + + it('should revert on oog if not enough verificationGas', async () => { + const op = await fillAndSign({ sender: account.address, verificationGasLimit: 1000 }, accountOwner, entryPoint) + await expect(simulateValidation(op, entryPoint.address)).to + .revertedWith('AA23 reverted (or OOG)') + }) + + it('should succeed if validateUserOp succeeds', async () => { + const op = await fillAndSign({ sender: account1.address }, accountOwner1, entryPoint) + await fund(account1) + await simulateValidation(op, entryPoint.address) + }) + + it('should return empty context if no paymaster', async () => { + const op = await fillAndSign({ sender: account1.address, maxFeePerGas: 0 }, accountOwner1, entryPoint) + const { returnInfo } = await simulateValidation(op, entryPoint.address) + expect(returnInfo.paymasterContext).to.eql('0x') + }) + + it('should return stake of sender', async () => { + const stakeValue = BigNumber.from(123) + const unstakeDelay = 3 + const { proxy: account2 } = await createAccount(ethersSigner, await ethersSigner.getAddress(), entryPoint.address) + await fund(account2) + await account2.execute(entryPoint.address, stakeValue, entryPoint.interface.encodeFunctionData('addStake', [unstakeDelay])) + const op = await fillAndSign({ sender: account2.address }, ethersSigner, entryPoint) + const result = await simulateValidation(op, entryPoint.address) + expect(result.senderInfo.stake).to.equal(stakeValue) + expect(result.senderInfo.unstakeDelaySec).to.equal(unstakeDelay) + }) + + it('should prevent overflows: fail if any numeric value is more than 120 bits', async () => { + const op = await fillAndSign({ + preVerificationGas: BigNumber.from(2).pow(130), + sender: account1.address + }, accountOwner1, entryPoint) + await expect( + simulateValidation(op, entryPoint.address) + ).to.revertedWith('gas values overflow') + }) + + it('should fail creation for wrong sender', async () => { + const op1 = await fillAndSign({ + initCode: getAccountInitCode(accountOwner1.address, simpleAccountFactory), + sender: '0x'.padEnd(42, '1'), + verificationGasLimit: 30e6 + }, accountOwner1, entryPoint) + await expect(simulateValidation(op1, entryPoint.address)) + .to.revertedWith('AA14 initCode must return sender') + }) + + it('should report failure on insufficient verificationGas (OOG) for creation', async () => { + const initCode = getAccountInitCode(accountOwner1.address, simpleAccountFactory) + const sender = await entryPoint.callStatic.getSenderAddress(initCode).catch(e => e.errorArgs.sender) + const op0 = await fillAndSign({ + initCode, + sender, + verificationGasLimit: 5e5, + maxFeePerGas: 0 + }, accountOwner1, entryPoint) + // must succeed with enough verification gas. + await simulateValidation(op0, entryPoint.address, { gas: '0xF4240' }) + + const op1 = await fillAndSign({ + initCode, + sender, + verificationGasLimit: 1e5, + maxFeePerGas: 0 + }, accountOwner1, entryPoint) + await expect(simulateValidation(op1, entryPoint.address, { gas: '0xF4240' })) + .to.revertedWith('AA13 initCode failed or OOG') + }) + + it('should succeed for creating an account', async () => { + const sender = await getAccountAddress(accountOwner1.address, simpleAccountFactory) + const op1 = await fillAndSign({ + sender, + initCode: getAccountInitCode(accountOwner1.address, simpleAccountFactory) + }, accountOwner1, entryPoint) + await fund(op1.sender) + + await simulateValidation(op1, entryPoint.address) + }) + + it('should not call initCode from entrypoint', async () => { + // a possible attack: call an account's execFromEntryPoint through initCode. This might lead to stolen funds. + const { proxy: account } = await createAccount(ethersSigner, await accountOwner.getAddress(), entryPoint.address) + const sender = createAddress() + const op1 = await fillAndSign({ + initCode: hexConcat([ + account.address, + account.interface.encodeFunctionData('execute', [sender, 0, '0x']) + ]), + sender + }, accountOwner, entryPoint) + const error = await simulateValidation(op1, entryPoint.address).catch(e => e) + expect(error.message).to.match(/initCode failed or OOG/, error) + }) + }) + + describe('#simulateHandleOp', () => { + it('should simulate execution', async () => { + const accountOwner1 = createAccountOwner() + const { proxy: account } = await createAccount(ethersSigner, await accountOwner.getAddress(), entryPoint.address) + await fund(account) + const counter = await new TestCounter__factory(ethersSigner).deploy() + + const count = counter.interface.encodeFunctionData('count') + const callData = account.interface.encodeFunctionData('execute', [counter.address, 0, count]) + // deliberately broken signature. simulate should work with it too. + const userOp = await fillAndSign({ + sender: account.address, + callData + }, accountOwner1, entryPoint) + + const ret = await simulateHandleOp(userOp, + counter.address, + counter.interface.encodeFunctionData('counters', [account.address]), + entryPoint.address + ) + + const [countResult] = counter.interface.decodeFunctionResult('counters', ret.targetResult) + expect(countResult).to.equal(1) + expect(ret.targetSuccess).to.be.true + + // actual counter is zero + expect(await counter.counters(account.address)).to.equal(0) + }) + }) +}) diff --git a/test/paymaster.test.ts b/test/paymaster.test.ts index ea788f92..d3d25d4f 100644 --- a/test/paymaster.test.ts +++ b/test/paymaster.test.ts @@ -3,12 +3,11 @@ import { ethers } from 'hardhat' import { expect } from 'chai' import { SimpleAccount, - EntryPoint, LegacyTokenPaymaster, LegacyTokenPaymaster__factory, TestCounter__factory, SimpleAccountFactory, - SimpleAccountFactory__factory + SimpleAccountFactory__factory, EntryPoint } from '../typechain' import { AddressZero, @@ -20,13 +19,12 @@ import { checkForGeth, calcGasUsage, deployEntryPoint, - checkForBannedOps, createAddress, ONE_ETH, createAccount, getAccountAddress } from './testutils' -import { fillAndSign } from './UserOp' +import { fillAndSign, simulateValidation } from './UserOp' import { hexConcat, parseEther } from 'ethers/lib/utils' import { UserOperation } from './UserOperation' import { hexValue } from '@ethersproject/bytes' @@ -139,9 +137,12 @@ describe('EntryPoint with paymaster', function () { await paymaster.mintTokens(preAddr, parseEther('1')) // paymaster is the token, so no need for "approve" or any init function... - await entryPoint.simulateValidation(createOp, { gasLimit: 5e6 }).catch(e => e.message) - const [tx] = await ethers.provider.getBlock('latest').then(block => block.transactions) - await checkForBannedOps(tx, true) + // const snapshot = await ethers.provider.send('evm_snapshot', []) + await simulateValidation(createOp, entryPoint.address, { gasLimit: 5e6 }) + // TODO: can't do opcode banning with EntryPointSimulations (since its not on-chain) add when we can debug_traceCall + // const [tx] = await ethers.provider.getBlock('latest').then(block => block.transactions) + // await checkForBannedOps(tx, true) + // await ethers.provider.send('evm_revert', [snapshot]) const rcpt = await entryPoint.handleOps([createOp], beneficiaryAddress, { gasLimit: 1e7 @@ -246,7 +247,7 @@ describe('EntryPoint with paymaster', function () { paymasterAndData: paymaster.address }, accountOwner, entryPoint) - // account2's operation is unimportant, as it is going to be reverted - but the paymaster will have to pay for it.. + // account2's operation is unimportant, as it is going to be reverted - but the paymaster will have to pay for it. const userOp2 = await fillAndSign({ sender: account2.address, callData: execFromEntryPoint, diff --git a/test/simple-wallet.test.ts b/test/simple-wallet.test.ts index 3ce2a42b..12d2ca70 100644 --- a/test/simple-wallet.test.ts +++ b/test/simple-wallet.test.ts @@ -15,11 +15,10 @@ import { createAccount, createAddress, createAccountOwner, - deployEntryPoint, getBalance, isDeployed, ONE_ETH, - HashZero + HashZero, deployEntryPoint } from './testutils' import { fillUserOpDefaults, getUserOpHash, packUserOp, signUserOp } from './UserOp' import { parseEther } from 'ethers/lib/utils' diff --git a/test/testutils.ts b/test/testutils.ts index 5204ca57..a773f364 100644 --- a/test/testutils.ts +++ b/test/testutils.ts @@ -13,7 +13,9 @@ import { IEntryPoint, SimpleAccount, SimpleAccountFactory__factory, - SimpleAccount__factory, SimpleAccountFactory, TestAggregatedAccountFactory + SimpleAccount__factory, + SimpleAccountFactory, + TestAggregatedAccountFactory } from '../typechain' import { BytesLike } from '@ethersproject/bytes' import { expect } from 'chai' @@ -241,32 +243,9 @@ export async function checkForBannedOps (txHash: string, checkPaymaster: boolean } } -/** - * process exception of ValidationResult - * usage: entryPoint.simulationResult(..).catch(simulationResultCatch) - */ -export function simulationResultCatch (e: any): any { - if (e.errorName !== 'ValidationResult') { - throw e - } - return e.errorArgs -} - -/** - * process exception of ValidationResultWithAggregation - * usage: entryPoint.simulationResult(..).catch(simulationResultWithAggregation) - */ -export function simulationResultWithAggregationCatch (e: any): any { - if (e.errorName !== 'ValidationResultWithAggregation') { - throw e - } - return e.errorArgs -} - export async function deployEntryPoint (provider = ethers.provider): Promise { const create2factory = new Create2Factory(provider) - const epf = new EntryPoint__factory(provider.getSigner()) - const addr = await create2factory.deploy(epf.bytecode, 0, process.env.COVERAGE != null ? 20e6 : 8e6) + const addr = await create2factory.deploy(EntryPoint__factory.bytecode, 0, process.env.COVERAGE != null ? 20e6 : 8e6) return EntryPoint__factory.connect(addr, provider.getSigner()) } diff --git a/test/verifying_paymaster.test.ts b/test/verifying_paymaster.test.ts index 7a06b066..5103f2cb 100644 --- a/test/verifying_paymaster.test.ts +++ b/test/verifying_paymaster.test.ts @@ -2,17 +2,17 @@ import { Wallet } from 'ethers' import { ethers } from 'hardhat' import { expect } from 'chai' import { - SimpleAccount, EntryPoint, + SimpleAccount, VerifyingPaymaster, VerifyingPaymaster__factory } from '../typechain' import { createAccount, createAccountOwner, createAddress, - deployEntryPoint, simulationResultCatch + deployEntryPoint } from './testutils' -import { fillAndSign } from './UserOp' +import { fillAndSign, simulateValidation } from './UserOp' import { arrayify, defaultAbiCoder, hexConcat, parseEther } from 'ethers/lib/utils' import { UserOperation } from './UserOperation' @@ -58,7 +58,7 @@ describe('EntryPoint with VerifyingPaymaster', function () { sender: account.address, paymasterAndData: hexConcat([paymaster.address, defaultAbiCoder.encode(['uint48', 'uint48'], [MOCK_VALID_UNTIL, MOCK_VALID_AFTER]), '0x1234']) }, accountOwner, entryPoint) - await expect(entryPoint.callStatic.simulateValidation(userOp)).to.be.revertedWith('invalid signature length in paymasterAndData') + await expect(simulateValidation(userOp, entryPoint.address)).to.be.revertedWith('invalid signature length in paymasterAndData') }) it('should reject on invalid signature', async () => { @@ -66,7 +66,7 @@ describe('EntryPoint with VerifyingPaymaster', function () { sender: account.address, paymasterAndData: hexConcat([paymaster.address, defaultAbiCoder.encode(['uint48', 'uint48'], [MOCK_VALID_UNTIL, MOCK_VALID_AFTER]), '0x' + '00'.repeat(65)]) }, accountOwner, entryPoint) - await expect(entryPoint.callStatic.simulateValidation(userOp)).to.be.revertedWith('ECDSA: invalid signature') + await expect(simulateValidation(userOp, entryPoint.address)).to.be.revertedWith('ECDSA: invalid signature') }) describe('with wrong signature', () => { @@ -81,7 +81,7 @@ describe('EntryPoint with VerifyingPaymaster', function () { }) it('should return signature error (no revert) on wrong signer signature', async () => { - const ret = await entryPoint.callStatic.simulateValidation(wrongSigUserOp).catch(simulationResultCatch) + const ret = await simulateValidation(wrongSigUserOp, entryPoint.address) expect(ret.returnInfo.sigFailed).to.be.true }) @@ -101,7 +101,7 @@ describe('EntryPoint with VerifyingPaymaster', function () { ...userOp1, paymasterAndData: hexConcat([paymaster.address, defaultAbiCoder.encode(['uint48', 'uint48'], [MOCK_VALID_UNTIL, MOCK_VALID_AFTER]), sig]) }, accountOwner, entryPoint) - const res = await entryPoint.callStatic.simulateValidation(userOp).catch(simulationResultCatch) + const res = await simulateValidation(userOp, entryPoint.address) expect(res.returnInfo.sigFailed).to.be.false expect(res.returnInfo.validAfter).to.be.equal(ethers.BigNumber.from(MOCK_VALID_AFTER)) expect(res.returnInfo.validUntil).to.be.equal(ethers.BigNumber.from(MOCK_VALID_UNTIL)) diff --git a/test/y.bls.test.ts b/test/y.bls.test.ts index 6dae29ee..19eac878 100644 --- a/test/y.bls.test.ts +++ b/test/y.bls.test.ts @@ -12,8 +12,8 @@ import { EntryPoint } from '../typechain' import { ethers } from 'hardhat' -import { createAddress, deployEntryPoint, fund, ONE_ETH, simulationResultWithAggregationCatch } from './testutils' -import { DefaultsForUserOp, fillUserOp } from './UserOp' +import { createAddress, deployEntryPoint, fund, ONE_ETH } from './testutils' +import { DefaultsForUserOp, fillUserOp, simulateValidation } from './UserOp' import { expect } from 'chai' import { keccak256 } from 'ethereumjs-util' import { hashToPoint } from '@thehubbleproject/bls/dist/mcl' @@ -190,7 +190,7 @@ describe('bls account', function () { const sigParts = signer3.sign(requestHash) userOp.signature = hexConcat(sigParts) - const { aggregatorInfo } = await entrypoint.callStatic.simulateValidation(userOp).catch(simulationResultWithAggregationCatch) + const { aggregatorInfo } = await simulateValidation(userOp, entrypoint.address) expect(aggregatorInfo.aggregator).to.eq(blsAgg.address) expect(aggregatorInfo.stakeInfo.stake).to.eq(ONE_ETH) expect(aggregatorInfo.stakeInfo.unstakeDelaySec).to.eq(2) diff --git a/yarn.lock b/yarn.lock index bf1f9b8f..d9ae9819 100644 --- a/yarn.lock +++ b/yarn.lock @@ -23,6 +23,42 @@ chalk "^2.0.0" js-tokens "^4.0.0" +"@chainsafe/as-sha256@^0.3.1": + version "0.3.1" + resolved "https://registry.yarnpkg.com/@chainsafe/as-sha256/-/as-sha256-0.3.1.tgz#3639df0e1435cab03f4d9870cc3ac079e57a6fc9" + integrity sha512-hldFFYuf49ed7DAakWVXSJODuq3pzJEguD8tQ7h+sGkM18vja+OFoJI9krnGmgzyuZC2ETX0NOIcCTy31v2Mtg== + +"@chainsafe/persistent-merkle-tree@^0.4.2": + version "0.4.2" + resolved "https://registry.yarnpkg.com/@chainsafe/persistent-merkle-tree/-/persistent-merkle-tree-0.4.2.tgz#4c9ee80cc57cd3be7208d98c40014ad38f36f7ff" + integrity sha512-lLO3ihKPngXLTus/L7WHKaw9PnNJWizlOF1H9NNzHP6Xvh82vzg9F2bzkXhYIFshMZ2gTCEz8tq6STe7r5NDfQ== + dependencies: + "@chainsafe/as-sha256" "^0.3.1" + +"@chainsafe/persistent-merkle-tree@^0.5.0": + version "0.5.0" + resolved "https://registry.yarnpkg.com/@chainsafe/persistent-merkle-tree/-/persistent-merkle-tree-0.5.0.tgz#2b4a62c9489a5739dedd197250d8d2f5427e9f63" + integrity sha512-l0V1b5clxA3iwQLXP40zYjyZYospQLZXzBVIhhr9kDg/1qHZfzzHw0jj4VPBijfYCArZDlPkRi1wZaV2POKeuw== + dependencies: + "@chainsafe/as-sha256" "^0.3.1" + +"@chainsafe/ssz@^0.10.0": + version "0.10.2" + resolved "https://registry.yarnpkg.com/@chainsafe/ssz/-/ssz-0.10.2.tgz#c782929e1bb25fec66ba72e75934b31fd087579e" + integrity sha512-/NL3Lh8K+0q7A3LsiFq09YXS9fPE+ead2rr7vM2QK8PLzrNsw3uqrif9bpRX5UxgeRjM+vYi+boCM3+GM4ovXg== + dependencies: + "@chainsafe/as-sha256" "^0.3.1" + "@chainsafe/persistent-merkle-tree" "^0.5.0" + +"@chainsafe/ssz@^0.9.2": + version "0.9.4" + resolved "https://registry.yarnpkg.com/@chainsafe/ssz/-/ssz-0.9.4.tgz#696a8db46d6975b600f8309ad3a12f7c0e310497" + integrity sha512-77Qtg2N1ayqs4Bg/wvnWfg5Bta7iy7IRh8XqXh7oNMeP2HBbBwx8m6yTpA8p0EHItWPEBkgZd5S5/LSlp3GXuQ== + dependencies: + "@chainsafe/as-sha256" "^0.3.1" + "@chainsafe/persistent-merkle-tree" "^0.4.2" + case "^1.6.3" + "@cspotcode/source-map-support@^0.8.0": version "0.8.1" resolved "https://registry.yarnpkg.com/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz#00629c35a688e05a88b1cda684fb9d5e73f000a1" @@ -320,7 +356,7 @@ dependencies: "@ethersproject/logger" "^5.7.0" -"@ethersproject/providers@5.7.2": +"@ethersproject/providers@5.7.2", "@ethersproject/providers@^5.7.1", "@ethersproject/providers@^5.7.2": version "5.7.2" resolved "https://registry.yarnpkg.com/@ethersproject/providers/-/providers-5.7.2.tgz#f8b1a4f275d7ce58cf0a2eec222269a08beb18cb" integrity sha512-g34EWZ1WWAVgr4aptGlVBF8mhl3VWjv+8hoAnzStu8Ah22VHBsuGzP17eb6xDVRzw895G4W7vvx60lFFur/1Rg== @@ -560,29 +596,31 @@ "@nodelib/fs.scandir" "2.1.5" fastq "^1.6.0" -"@nomicfoundation/ethereumjs-block@^4.0.0": - version "4.0.0" - resolved "https://registry.yarnpkg.com/@nomicfoundation/ethereumjs-block/-/ethereumjs-block-4.0.0.tgz#fdd5c045e7baa5169abeed0e1202bf94e4481c49" - integrity sha512-bk8uP8VuexLgyIZAHExH1QEovqx0Lzhc9Ntm63nCRKLHXIZkobaFaeCVwTESV7YkPKUk7NiK11s8ryed4CS9yA== - dependencies: - "@nomicfoundation/ethereumjs-common" "^3.0.0" - "@nomicfoundation/ethereumjs-rlp" "^4.0.0" - "@nomicfoundation/ethereumjs-trie" "^5.0.0" - "@nomicfoundation/ethereumjs-tx" "^4.0.0" - "@nomicfoundation/ethereumjs-util" "^8.0.0" +"@nomicfoundation/ethereumjs-block@5.0.2": + version "5.0.2" + resolved "https://registry.yarnpkg.com/@nomicfoundation/ethereumjs-block/-/ethereumjs-block-5.0.2.tgz#13a7968f5964f1697da941281b7f7943b0465d04" + integrity sha512-hSe6CuHI4SsSiWWjHDIzWhSiAVpzMUcDRpWYzN0T9l8/Rz7xNn3elwVOJ/tAyS0LqL6vitUD78Uk7lQDXZun7Q== + dependencies: + "@nomicfoundation/ethereumjs-common" "4.0.2" + "@nomicfoundation/ethereumjs-rlp" "5.0.2" + "@nomicfoundation/ethereumjs-trie" "6.0.2" + "@nomicfoundation/ethereumjs-tx" "5.0.2" + "@nomicfoundation/ethereumjs-util" "9.0.2" ethereum-cryptography "0.1.3" + ethers "^5.7.1" -"@nomicfoundation/ethereumjs-blockchain@^6.0.0": - version "6.0.0" - resolved "https://registry.yarnpkg.com/@nomicfoundation/ethereumjs-blockchain/-/ethereumjs-blockchain-6.0.0.tgz#1a8c243a46d4d3691631f139bfb3a4a157187b0c" - integrity sha512-pLFEoea6MWd81QQYSReLlLfH7N9v7lH66JC/NMPN848ySPPQA5renWnE7wPByfQFzNrPBuDDRFFULMDmj1C0xw== - dependencies: - "@nomicfoundation/ethereumjs-block" "^4.0.0" - "@nomicfoundation/ethereumjs-common" "^3.0.0" - "@nomicfoundation/ethereumjs-ethash" "^2.0.0" - "@nomicfoundation/ethereumjs-rlp" "^4.0.0" - "@nomicfoundation/ethereumjs-trie" "^5.0.0" - "@nomicfoundation/ethereumjs-util" "^8.0.0" +"@nomicfoundation/ethereumjs-blockchain@7.0.2": + version "7.0.2" + resolved "https://registry.yarnpkg.com/@nomicfoundation/ethereumjs-blockchain/-/ethereumjs-blockchain-7.0.2.tgz#45323b673b3d2fab6b5008535340d1b8fea7d446" + integrity sha512-8UUsSXJs+MFfIIAKdh3cG16iNmWzWC/91P40sazNvrqhhdR/RtGDlFk2iFTGbBAZPs2+klZVzhRX8m2wvuvz3w== + dependencies: + "@nomicfoundation/ethereumjs-block" "5.0.2" + "@nomicfoundation/ethereumjs-common" "4.0.2" + "@nomicfoundation/ethereumjs-ethash" "3.0.2" + "@nomicfoundation/ethereumjs-rlp" "5.0.2" + "@nomicfoundation/ethereumjs-trie" "6.0.2" + "@nomicfoundation/ethereumjs-tx" "5.0.2" + "@nomicfoundation/ethereumjs-util" "9.0.2" abstract-level "^1.0.3" debug "^4.3.3" ethereum-cryptography "0.1.3" @@ -590,105 +628,105 @@ lru-cache "^5.1.1" memory-level "^1.0.0" -"@nomicfoundation/ethereumjs-common@^3.0.0": - version "3.0.0" - resolved "https://registry.yarnpkg.com/@nomicfoundation/ethereumjs-common/-/ethereumjs-common-3.0.0.tgz#f6bcc7753994555e49ab3aa517fc8bcf89c280b9" - integrity sha512-WS7qSshQfxoZOpHG/XqlHEGRG1zmyjYrvmATvc4c62+gZXgre1ymYP8ZNgx/3FyZY0TWe9OjFlKOfLqmgOeYwA== +"@nomicfoundation/ethereumjs-common@4.0.2": + version "4.0.2" + resolved "https://registry.yarnpkg.com/@nomicfoundation/ethereumjs-common/-/ethereumjs-common-4.0.2.tgz#a15d1651ca36757588fdaf2a7d381a150662a3c3" + integrity sha512-I2WGP3HMGsOoycSdOTSqIaES0ughQTueOsddJ36aYVpI3SN8YSusgRFLwzDJwRFVIYDKx/iJz0sQ5kBHVgdDwg== dependencies: - "@nomicfoundation/ethereumjs-util" "^8.0.0" + "@nomicfoundation/ethereumjs-util" "9.0.2" crc-32 "^1.2.0" -"@nomicfoundation/ethereumjs-ethash@^2.0.0": - version "2.0.0" - resolved "https://registry.yarnpkg.com/@nomicfoundation/ethereumjs-ethash/-/ethereumjs-ethash-2.0.0.tgz#11539c32fe0990e1122ff987d1b84cfa34774e81" - integrity sha512-WpDvnRncfDUuXdsAXlI4lXbqUDOA+adYRQaEezIkxqDkc+LDyYDbd/xairmY98GnQzo1zIqsIL6GB5MoMSJDew== +"@nomicfoundation/ethereumjs-ethash@3.0.2": + version "3.0.2" + resolved "https://registry.yarnpkg.com/@nomicfoundation/ethereumjs-ethash/-/ethereumjs-ethash-3.0.2.tgz#da77147f806401ee996bfddfa6487500118addca" + integrity sha512-8PfoOQCcIcO9Pylq0Buijuq/O73tmMVURK0OqdjhwqcGHYC2PwhbajDh7GZ55ekB0Px197ajK3PQhpKoiI/UPg== dependencies: - "@nomicfoundation/ethereumjs-block" "^4.0.0" - "@nomicfoundation/ethereumjs-rlp" "^4.0.0" - "@nomicfoundation/ethereumjs-util" "^8.0.0" + "@nomicfoundation/ethereumjs-block" "5.0.2" + "@nomicfoundation/ethereumjs-rlp" "5.0.2" + "@nomicfoundation/ethereumjs-util" "9.0.2" abstract-level "^1.0.3" bigint-crypto-utils "^3.0.23" ethereum-cryptography "0.1.3" -"@nomicfoundation/ethereumjs-evm@^1.0.0": - version "1.0.0" - resolved "https://registry.yarnpkg.com/@nomicfoundation/ethereumjs-evm/-/ethereumjs-evm-1.0.0.tgz#99cd173c03b59107c156a69c5e215409098a370b" - integrity sha512-hVS6qRo3V1PLKCO210UfcEQHvlG7GqR8iFzp0yyjTg2TmJQizcChKgWo8KFsdMw6AyoLgLhHGHw4HdlP8a4i+Q== +"@nomicfoundation/ethereumjs-evm@2.0.2": + version "2.0.2" + resolved "https://registry.yarnpkg.com/@nomicfoundation/ethereumjs-evm/-/ethereumjs-evm-2.0.2.tgz#4c2f4b84c056047102a4fa41c127454e3f0cfcf6" + integrity sha512-rBLcUaUfANJxyOx9HIdMX6uXGin6lANCulIm/pjMgRqfiCRMZie3WKYxTSd8ZE/d+qT+zTedBF4+VHTdTSePmQ== dependencies: - "@nomicfoundation/ethereumjs-common" "^3.0.0" - "@nomicfoundation/ethereumjs-util" "^8.0.0" - "@types/async-eventemitter" "^0.2.1" - async-eventemitter "^0.2.4" + "@ethersproject/providers" "^5.7.1" + "@nomicfoundation/ethereumjs-common" "4.0.2" + "@nomicfoundation/ethereumjs-tx" "5.0.2" + "@nomicfoundation/ethereumjs-util" "9.0.2" debug "^4.3.3" ethereum-cryptography "0.1.3" mcl-wasm "^0.7.1" rustbn.js "~0.2.0" -"@nomicfoundation/ethereumjs-rlp@^4.0.0", "@nomicfoundation/ethereumjs-rlp@^4.0.0-beta.2": - version "4.0.0" - resolved "https://registry.yarnpkg.com/@nomicfoundation/ethereumjs-rlp/-/ethereumjs-rlp-4.0.0.tgz#d9a9c5f0f10310c8849b6525101de455a53e771d" - integrity sha512-GaSOGk5QbUk4eBP5qFbpXoZoZUj/NrW7MRa0tKY4Ew4c2HAS0GXArEMAamtFrkazp0BO4K5p2ZCG3b2FmbShmw== +"@nomicfoundation/ethereumjs-rlp@5.0.2": + version "5.0.2" + resolved "https://registry.yarnpkg.com/@nomicfoundation/ethereumjs-rlp/-/ethereumjs-rlp-5.0.2.tgz#4fee8dc58a53ac6ae87fb1fca7c15dc06c6b5dea" + integrity sha512-QwmemBc+MMsHJ1P1QvPl8R8p2aPvvVcKBbvHnQOKBpBztEo0omN0eaob6FeZS/e3y9NSe+mfu3nNFBHszqkjTA== -"@nomicfoundation/ethereumjs-statemanager@^1.0.0": - version "1.0.0" - resolved "https://registry.yarnpkg.com/@nomicfoundation/ethereumjs-statemanager/-/ethereumjs-statemanager-1.0.0.tgz#14a9d4e1c828230368f7ab520c144c34d8721e4b" - integrity sha512-jCtqFjcd2QejtuAMjQzbil/4NHf5aAWxUc+CvS0JclQpl+7M0bxMofR2AJdtz+P3u0ke2euhYREDiE7iSO31vQ== +"@nomicfoundation/ethereumjs-statemanager@2.0.2": + version "2.0.2" + resolved "https://registry.yarnpkg.com/@nomicfoundation/ethereumjs-statemanager/-/ethereumjs-statemanager-2.0.2.tgz#3ba4253b29b1211cafe4f9265fee5a0d780976e0" + integrity sha512-dlKy5dIXLuDubx8Z74sipciZnJTRSV/uHG48RSijhgm1V7eXYFC567xgKtsKiVZB1ViTP9iFL4B6Je0xD6X2OA== dependencies: - "@nomicfoundation/ethereumjs-common" "^3.0.0" - "@nomicfoundation/ethereumjs-rlp" "^4.0.0" - "@nomicfoundation/ethereumjs-trie" "^5.0.0" - "@nomicfoundation/ethereumjs-util" "^8.0.0" + "@nomicfoundation/ethereumjs-common" "4.0.2" + "@nomicfoundation/ethereumjs-rlp" "5.0.2" debug "^4.3.3" ethereum-cryptography "0.1.3" - functional-red-black-tree "^1.0.1" + ethers "^5.7.1" + js-sdsl "^4.1.4" -"@nomicfoundation/ethereumjs-trie@^5.0.0": - version "5.0.0" - resolved "https://registry.yarnpkg.com/@nomicfoundation/ethereumjs-trie/-/ethereumjs-trie-5.0.0.tgz#dcfbe3be53a94bc061c9767a396c16702bc2f5b7" - integrity sha512-LIj5XdE+s+t6WSuq/ttegJzZ1vliwg6wlb+Y9f4RlBpuK35B9K02bO7xU+E6Rgg9RGptkWd6TVLdedTI4eNc2A== +"@nomicfoundation/ethereumjs-trie@6.0.2": + version "6.0.2" + resolved "https://registry.yarnpkg.com/@nomicfoundation/ethereumjs-trie/-/ethereumjs-trie-6.0.2.tgz#9a6dbd28482dca1bc162d12b3733acab8cd12835" + integrity sha512-yw8vg9hBeLYk4YNg5MrSJ5H55TLOv2FSWUTROtDtTMMmDGROsAu+0tBjiNGTnKRi400M6cEzoFfa89Fc5k8NTQ== dependencies: - "@nomicfoundation/ethereumjs-rlp" "^4.0.0" - "@nomicfoundation/ethereumjs-util" "^8.0.0" + "@nomicfoundation/ethereumjs-rlp" "5.0.2" + "@nomicfoundation/ethereumjs-util" "9.0.2" + "@types/readable-stream" "^2.3.13" ethereum-cryptography "0.1.3" readable-stream "^3.6.0" -"@nomicfoundation/ethereumjs-tx@^4.0.0": - version "4.0.0" - resolved "https://registry.yarnpkg.com/@nomicfoundation/ethereumjs-tx/-/ethereumjs-tx-4.0.0.tgz#59dc7452b0862b30342966f7052ab9a1f7802f52" - integrity sha512-Gg3Lir2lNUck43Kp/3x6TfBNwcWC9Z1wYue9Nz3v4xjdcv6oDW9QSMJxqsKw9QEGoBBZ+gqwpW7+F05/rs/g1w== - dependencies: - "@nomicfoundation/ethereumjs-common" "^3.0.0" - "@nomicfoundation/ethereumjs-rlp" "^4.0.0" - "@nomicfoundation/ethereumjs-util" "^8.0.0" +"@nomicfoundation/ethereumjs-tx@5.0.2": + version "5.0.2" + resolved "https://registry.yarnpkg.com/@nomicfoundation/ethereumjs-tx/-/ethereumjs-tx-5.0.2.tgz#117813b69c0fdc14dd0446698a64be6df71d7e56" + integrity sha512-T+l4/MmTp7VhJeNloMkM+lPU3YMUaXdcXgTGCf8+ZFvV9NYZTRLFekRwlG6/JMmVfIfbrW+dRRJ9A6H5Q/Z64g== + dependencies: + "@chainsafe/ssz" "^0.9.2" + "@ethersproject/providers" "^5.7.2" + "@nomicfoundation/ethereumjs-common" "4.0.2" + "@nomicfoundation/ethereumjs-rlp" "5.0.2" + "@nomicfoundation/ethereumjs-util" "9.0.2" ethereum-cryptography "0.1.3" -"@nomicfoundation/ethereumjs-util@^8.0.0": - version "8.0.0" - resolved "https://registry.yarnpkg.com/@nomicfoundation/ethereumjs-util/-/ethereumjs-util-8.0.0.tgz#deb2b15d2c308a731e82977aefc4e61ca0ece6c5" - integrity sha512-2emi0NJ/HmTG+CGY58fa+DQuAoroFeSH9gKu9O6JnwTtlzJtgfTixuoOqLEgyyzZVvwfIpRueuePb8TonL1y+A== +"@nomicfoundation/ethereumjs-util@9.0.2": + version "9.0.2" + resolved "https://registry.yarnpkg.com/@nomicfoundation/ethereumjs-util/-/ethereumjs-util-9.0.2.tgz#16bdc1bb36f333b8a3559bbb4b17dac805ce904d" + integrity sha512-4Wu9D3LykbSBWZo8nJCnzVIYGvGCuyiYLIJa9XXNVt1q1jUzHdB+sJvx95VGCpPkCT+IbLecW6yfzy3E1bQrwQ== dependencies: - "@nomicfoundation/ethereumjs-rlp" "^4.0.0-beta.2" + "@chainsafe/ssz" "^0.10.0" + "@nomicfoundation/ethereumjs-rlp" "5.0.2" ethereum-cryptography "0.1.3" -"@nomicfoundation/ethereumjs-vm@^6.0.0": - version "6.0.0" - resolved "https://registry.yarnpkg.com/@nomicfoundation/ethereumjs-vm/-/ethereumjs-vm-6.0.0.tgz#2bb50d332bf41790b01a3767ffec3987585d1de6" - integrity sha512-JMPxvPQ3fzD063Sg3Tp+UdwUkVxMoo1uML6KSzFhMH3hoQi/LMuXBoEHAoW83/vyNS9BxEe6jm6LmT5xdeEJ6w== - dependencies: - "@nomicfoundation/ethereumjs-block" "^4.0.0" - "@nomicfoundation/ethereumjs-blockchain" "^6.0.0" - "@nomicfoundation/ethereumjs-common" "^3.0.0" - "@nomicfoundation/ethereumjs-evm" "^1.0.0" - "@nomicfoundation/ethereumjs-rlp" "^4.0.0" - "@nomicfoundation/ethereumjs-statemanager" "^1.0.0" - "@nomicfoundation/ethereumjs-trie" "^5.0.0" - "@nomicfoundation/ethereumjs-tx" "^4.0.0" - "@nomicfoundation/ethereumjs-util" "^8.0.0" - "@types/async-eventemitter" "^0.2.1" - async-eventemitter "^0.2.4" +"@nomicfoundation/ethereumjs-vm@7.0.2": + version "7.0.2" + resolved "https://registry.yarnpkg.com/@nomicfoundation/ethereumjs-vm/-/ethereumjs-vm-7.0.2.tgz#3b0852cb3584df0e18c182d0672a3596c9ca95e6" + integrity sha512-Bj3KZT64j54Tcwr7Qm/0jkeZXJMfdcAtRBedou+Hx0dPOSIgqaIr0vvLwP65TpHbak2DmAq+KJbW2KNtIoFwvA== + dependencies: + "@nomicfoundation/ethereumjs-block" "5.0.2" + "@nomicfoundation/ethereumjs-blockchain" "7.0.2" + "@nomicfoundation/ethereumjs-common" "4.0.2" + "@nomicfoundation/ethereumjs-evm" "2.0.2" + "@nomicfoundation/ethereumjs-rlp" "5.0.2" + "@nomicfoundation/ethereumjs-statemanager" "2.0.2" + "@nomicfoundation/ethereumjs-trie" "6.0.2" + "@nomicfoundation/ethereumjs-tx" "5.0.2" + "@nomicfoundation/ethereumjs-util" "9.0.2" debug "^4.3.3" ethereum-cryptography "0.1.3" - functional-red-black-tree "^1.0.1" mcl-wasm "^0.7.1" rustbn.js "~0.2.0" @@ -949,6 +987,13 @@ dependencies: antlr4ts "^0.5.0-alpha.4" +"@solidity-parser/parser@^0.16.0": + version "0.16.1" + resolved "https://registry.yarnpkg.com/@solidity-parser/parser/-/parser-0.16.1.tgz#f7c8a686974e1536da0105466c4db6727311253c" + integrity sha512-PdhRFNhbTtu3x8Axm0uYpqOy/lODYQK+MlYSgqIsq2L8SFYEHJPHNUiOTAJbDGzNjjr1/n9AcIayxafR/fWmYw== + dependencies: + antlr4ts "^0.5.0-alpha.4" + "@szmarczak/http-timer@^1.1.2": version "1.1.2" resolved "https://registry.yarnpkg.com/@szmarczak/http-timer/-/http-timer-1.1.2.tgz#b1665e2c461a2cd92f4c1bbf50d5454de0d4b421" @@ -1013,11 +1058,6 @@ dependencies: fs-extra "^9.1.0" -"@types/async-eventemitter@^0.2.1": - version "0.2.1" - resolved "https://registry.yarnpkg.com/@types/async-eventemitter/-/async-eventemitter-0.2.1.tgz#f8e6280e87e8c60b2b938624b0a3530fb3e24712" - integrity sha512-M2P4Ng26QbAeITiH7w1d7OxtldgfAe0wobpyJzVK/XOb0cUGKU2R4pfAhqcJBXAe2ife5ZOhSv4wk7p+ffURtg== - "@types/bn.js@*", "@types/bn.js@^5.1.0": version "5.1.1" resolved "https://registry.yarnpkg.com/@types/bn.js/-/bn.js-5.1.1.tgz#b51e1b55920a4ca26e9285ff79936bbdec910682" @@ -1146,6 +1186,14 @@ resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.9.7.tgz#63bb7d067db107cc1e457c303bc25d511febf6cb" integrity sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw== +"@types/readable-stream@^2.3.13": + version "2.3.15" + resolved "https://registry.yarnpkg.com/@types/readable-stream/-/readable-stream-2.3.15.tgz#3d79c9ceb1b6a57d5f6e6976f489b9b5384321ae" + integrity sha512-oM5JSKQCcICF1wvGgmecmHldZ48OZamtMxcGGVICOJA8o8cahXC1zEVAif8iwoc5j8etxFaRFnf095+CDsuoFQ== + dependencies: + "@types/node" "*" + safe-buffer "~5.1.1" + "@types/resolve@^0.0.8": version "0.0.8" resolved "https://registry.yarnpkg.com/@types/resolve/-/resolve-0.0.8.tgz#f26074d238e02659e323ce1a13d041eee280e194" @@ -1373,13 +1421,6 @@ abbrev@1.0.x: resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.0.9.tgz#91b4792588a7738c25f35dd6f63752a2f8776135" integrity sha512-LEyx4aLEC3x6T0UguF6YILf+ntvmOaWsVfENmIW0E9H09vKlLDGelMjjSm0jkDHALj8A8quZ/HapKNigzwge+Q== -abort-controller@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/abort-controller/-/abort-controller-3.0.0.tgz#eaf54d53b62bae4138e809ca225c8439a6efb392" - integrity sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg== - dependencies: - event-target-shim "^5.0.0" - abstract-level@^1.0.0, abstract-level@^1.0.2, abstract-level@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/abstract-level/-/abstract-level-1.0.3.tgz#78a67d3d84da55ee15201486ab44c09560070741" @@ -1748,7 +1789,7 @@ astral-regex@^2.0.0: resolved "https://registry.yarnpkg.com/astral-regex/-/astral-regex-2.0.0.tgz#483143c567aeed4785759c0865786dc77d7d2e31" integrity sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ== -async-eventemitter@^0.2.2, async-eventemitter@^0.2.4: +async-eventemitter@^0.2.2: version "0.2.4" resolved "https://registry.yarnpkg.com/async-eventemitter/-/async-eventemitter-0.2.4.tgz#f5e7c8ca7d3e46aab9ec40a292baf686a0bafaca" integrity sha512-pd20BwL7Yt1zwDFy+8MX8F1+WCT8aQeKj0kQnTrH9WaeRETlRamVhD0JtRPmrV4GfOJ2F9CvdQkZeZhnh2TuHw== @@ -2774,6 +2815,11 @@ caniuse-lite@^1.0.30000844: resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001439.tgz#ab7371faeb4adff4b74dad1718a6fd122e45d9cb" integrity sha512-1MgUzEkoMO6gKfXflStpYgZDlFM7M/ck/bgfVCACO5vnAf0fXoNVHdWtqGU+MYca+4bL9Z5bpOVmR33cWW9G2A== +case@^1.6.3: + version "1.6.3" + resolved "https://registry.yarnpkg.com/case/-/case-1.6.3.tgz#0a4386e3e9825351ca2e6216c60467ff5f1ea1c9" + integrity sha512-mzDSXIPaFwVDvZAHqZ9VlbyF4yyXRuX6IvB06WvPYkqJVO24kX1PPhv9bfpKNFZyxYFmmgo03HUiD8iklmJYRQ== + caseless@~0.12.0: version "0.12.0" resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc" @@ -4466,7 +4512,7 @@ ethereumjs-wallet@^1.0.1: utf8 "^3.0.0" uuid "^8.3.2" -ethers@^5.0.1, ethers@^5.0.2, ethers@^5.4.2, ethers@^5.5.2, ethers@^5.5.3: +ethers@^5.0.1, ethers@^5.0.2, ethers@^5.4.2, ethers@^5.5.2, ethers@^5.5.3, ethers@^5.7.1: version "5.7.2" resolved "https://registry.yarnpkg.com/ethers/-/ethers-5.7.2.tgz#3a7deeabbb8c030d4126b24f84e525466145872e" integrity sha512-wswUsmWo1aOK8rR7DIKiWSw9DbLWe6x98Jrn8wcTflTVvaXhAMaB5zGAXy0GYQEQp9iO1iSHWVyARQm11zUtyg== @@ -4518,11 +4564,6 @@ ethjs-util@0.1.6, ethjs-util@^0.1.3, ethjs-util@^0.1.6: is-hex-prefixed "1.0.0" strip-hex-prefix "1.0.0" -event-target-shim@^5.0.0: - version "5.0.1" - resolved "https://registry.yarnpkg.com/event-target-shim/-/event-target-shim-5.0.1.tgz#5d4d3ebdf9583d63a5333ce2deb7480ab2b05789" - integrity sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ== - eventemitter3@4.0.4: version "4.0.4" resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.4.tgz#b5463ace635a083d018bdc7c917b4c5f10a85384" @@ -5393,28 +5434,27 @@ hardhat-deploy@^0.11.23: qs "^6.9.4" zksync-web3 "^0.8.1" -hardhat@^2.6.6: - version "2.12.4" - resolved "https://registry.yarnpkg.com/hardhat/-/hardhat-2.12.4.tgz#e539ba58bee9ba1a1ced823bfdcec0b3c5a3e70f" - integrity sha512-rc9S2U/4M+77LxW1Kg7oqMMmjl81tzn5rNFARhbXKUA1am/nhfMJEujOjuKvt+ZGMiZ11PYSe8gyIpB/aRNDgw== +hardhat@^2.17.2: + version "2.17.2" + resolved "https://registry.yarnpkg.com/hardhat/-/hardhat-2.17.2.tgz#250a8c8e76029e9bfbfb9b9abee68d5b350b5d4a" + integrity sha512-oUv40jBeHw0dKpbyQ+iH9cmNMziweLoTW3MnkNxJ2Gc0KGLrQR/1n4vV4xY60zn2LdmRgnwPqy3CgtY0mfwIIA== dependencies: "@ethersproject/abi" "^5.1.2" "@metamask/eth-sig-util" "^4.0.0" - "@nomicfoundation/ethereumjs-block" "^4.0.0" - "@nomicfoundation/ethereumjs-blockchain" "^6.0.0" - "@nomicfoundation/ethereumjs-common" "^3.0.0" - "@nomicfoundation/ethereumjs-evm" "^1.0.0" - "@nomicfoundation/ethereumjs-rlp" "^4.0.0" - "@nomicfoundation/ethereumjs-statemanager" "^1.0.0" - "@nomicfoundation/ethereumjs-trie" "^5.0.0" - "@nomicfoundation/ethereumjs-tx" "^4.0.0" - "@nomicfoundation/ethereumjs-util" "^8.0.0" - "@nomicfoundation/ethereumjs-vm" "^6.0.0" + "@nomicfoundation/ethereumjs-block" "5.0.2" + "@nomicfoundation/ethereumjs-blockchain" "7.0.2" + "@nomicfoundation/ethereumjs-common" "4.0.2" + "@nomicfoundation/ethereumjs-evm" "2.0.2" + "@nomicfoundation/ethereumjs-rlp" "5.0.2" + "@nomicfoundation/ethereumjs-statemanager" "2.0.2" + "@nomicfoundation/ethereumjs-trie" "6.0.2" + "@nomicfoundation/ethereumjs-tx" "5.0.2" + "@nomicfoundation/ethereumjs-util" "9.0.2" + "@nomicfoundation/ethereumjs-vm" "7.0.2" "@nomicfoundation/solidity-analyzer" "^0.1.0" "@sentry/node" "^5.18.1" "@types/bn.js" "^5.1.0" "@types/lru-cache" "^5.1.0" - abort-controller "^3.0.0" adm-zip "^0.4.16" aggregate-error "^3.0.0" ansi-escapes "^4.3.0" @@ -5437,7 +5477,6 @@ hardhat@^2.6.6: mnemonist "^0.38.0" mocha "^10.0.0" p-map "^4.0.0" - qs "^6.7.0" raw-body "^2.4.1" resolve "1.17.0" semver "^6.3.0" @@ -5445,7 +5484,7 @@ hardhat@^2.6.6: source-map-support "^0.5.13" stacktrace-parser "^0.1.10" tsort "0.0.1" - undici "^5.4.0" + undici "^5.14.0" uuid "^8.3.2" ws "^7.4.6" @@ -7925,7 +7964,7 @@ punycode@^2.1.0, punycode@^2.1.1: resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== -qs@6.11.0, qs@^6.7.0, qs@^6.9.4: +qs@6.11.0, qs@^6.9.4: version "6.11.0" resolved "https://registry.yarnpkg.com/qs/-/qs-6.11.0.tgz#fd0d963446f7a65e1367e01abd85429453f0c37a" integrity sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q== @@ -8747,13 +8786,13 @@ solhint@^3.3.7: optionalDependencies: prettier "^1.14.3" -solidity-coverage@^0.8.2: - version "0.8.2" - resolved "https://registry.yarnpkg.com/solidity-coverage/-/solidity-coverage-0.8.2.tgz#bc39604ab7ce0a3fa7767b126b44191830c07813" - integrity sha512-cv2bWb7lOXPE9/SSleDO6czkFiMHgP4NXPj+iW9W7iEKLBk7Cj0AGBiNmGX3V1totl9wjPrT0gHmABZKZt65rQ== +solidity-coverage@^0.8.4: + version "0.8.4" + resolved "https://registry.yarnpkg.com/solidity-coverage/-/solidity-coverage-0.8.4.tgz#c57a21979f5e86859c5198de9fbae2d3bc6324a5" + integrity sha512-xeHOfBOjdMF6hWTbt42iH4x+7j1Atmrf5OldDPMxI+i/COdExUxszOswD9qqvcBTaLGiOrrpnh9UZjSpt4rBsg== dependencies: "@ethersproject/abi" "^5.0.9" - "@solidity-parser/parser" "^0.14.1" + "@solidity-parser/parser" "^0.16.0" chalk "^2.4.2" death "^1.1.0" detect-port "^1.3.0" @@ -9591,10 +9630,10 @@ underscore@1.9.1: resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.9.1.tgz#06dce34a0e68a7babc29b365b8e74b8925203961" integrity sha512-5/4etnCkd9c8gwgowi5/om/mYO5ajCaOgdzj/oW+0eQV9WxKBDZw5+ycmKmeaTXjInS/W0BzpGLo2xR2aBwZdg== -undici@^5.4.0: - version "5.14.0" - resolved "https://registry.yarnpkg.com/undici/-/undici-5.14.0.tgz#1169d0cdee06a4ffdd30810f6228d57998884d00" - integrity sha512-yJlHYw6yXPPsuOH0x2Ib1Km61vu4hLiRRQoafs+WUgX1vO64vgnxiCEN9dpIrhZyHFsai3F0AEj4P9zy19enEQ== +undici@^5.14.0: + version "5.23.0" + resolved "https://registry.yarnpkg.com/undici/-/undici-5.23.0.tgz#e7bdb0ed42cebe7b7aca87ced53e6eaafb8f8ca0" + integrity sha512-1D7w+fvRsqlQ9GscLBwcAJinqcZGHUKjbOmXdlE/v8BvEGXjeWAax+341q44EuTcHXXnfyKNbKRq4Lg7OzhMmg== dependencies: busboy "^1.6.0"