123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490 |
- import "helpers/helpers.spec";
- import "methods/IAccount.spec";
- methods {
- function getDataSelector(bytes) external returns (bytes4) envfree;
- function getFallbackHandler(bytes4) external returns (address) envfree;
- function _validatorLength() external returns (uint256) envfree;
- function _validatorContains(address) external returns (bool) envfree;
- function _validatorAt(uint256) external returns (address) envfree;
- function _validatorAtFull(uint256) external returns (bytes32) envfree;
- function _validatorPositionOf(address) external returns (uint256) envfree;
- function _executorLength() external returns (uint256) envfree;
- function _executorContains(address) external returns (bool) envfree;
- function _executorAt(uint256) external returns (address) envfree;
- function _executorAtFull(uint256) external returns (bytes32) envfree;
- function _executorPositionOf(address) external returns (uint256) envfree;
- function _.onInstall(bytes) external => NONDET;
- function _.onUninstall(bytes) external => NONDET;
- }
- definition VALIDATOR_TYPE returns uint256 = 1;
- definition EXECUTOR_TYPE returns uint256 = 2;
- definition FALLBACK_TYPE returns uint256 = 3;
- definition isEntryPoint(env e) returns bool =
- e.msg.sender == entryPoint();
- definition isEntryPointOrSelf(env e) returns bool =
- isEntryPoint(e) || e.msg.sender == currentContract;
- definition isExecutionModule(env e, bytes context) returns bool =
- isModuleInstalled(EXECUTOR_TYPE(), e.msg.sender, context);
- /*
- ┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
- │ Storage consistency │
- └─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
- */
- definition moduleLengthSanity() returns bool =
- _validatorLength() < max_uint256 && _executorLength() < max_uint256;
- // Dirty upper bits could cause issues. We need to prove stored values are clean
- invariant cleanStorageValidator(uint256 index)
- index < _validatorLength() => to_bytes32(_validatorAt(index)) == _validatorAtFull(index)
- filtered { f -> f.selector != sig:execute(bytes32,bytes).selector && f.selector != sig:executeFromExecutor(bytes32,bytes).selector }
- {
- preserved {
- requireInvariant cleanStorageValidator(require_uint256(_validatorLength() - 1));
- }
- }
- // Dirty upper bits could cause issues. We need to prove stored values are clean
- invariant cleanStorageExecutor(uint256 index)
- index < _executorLength() => to_bytes32(_executorAt(index)) == _executorAtFull(index)
- filtered { f -> f.selector != sig:execute(bytes32,bytes).selector && f.selector != sig:executeFromExecutor(bytes32,bytes).selector }
- {
- preserved {
- requireInvariant cleanStorageExecutor(require_uint256(_executorLength() - 1));
- }
- }
- invariant indexedContainedValidator(uint256 index)
- index < _validatorLength() => _validatorContains(_validatorAt(index))
- filtered { f -> f.selector != sig:execute(bytes32,bytes).selector && f.selector != sig:executeFromExecutor(bytes32,bytes).selector }
- {
- preserved {
- requireInvariant consistencyIndexValidator(index);
- requireInvariant consistencyIndexValidator(require_uint256(_validatorLength() - 1));
- }
- }
- invariant indexedContainedExecutor(uint256 index)
- index < _executorLength() => _executorContains(_executorAt(index))
- filtered { f -> f.selector != sig:execute(bytes32,bytes).selector && f.selector != sig:executeFromExecutor(bytes32,bytes).selector }
- {
- preserved {
- requireInvariant consistencyIndexExecutor(index);
- requireInvariant consistencyIndexExecutor(require_uint256(_executorLength() - 1));
- }
- }
- invariant atUniquenessValidator(uint256 index1, uint256 index2)
- (index1 < _validatorLength() && index2 < _validatorLength()) =>
- (index1 == index2 <=> _validatorAt(index1) == _validatorAt(index2))
- filtered { f -> f.selector != sig:execute(bytes32,bytes).selector && f.selector != sig:executeFromExecutor(bytes32,bytes).selector }
- {
- preserved {
- requireInvariant consistencyIndexValidator(index1);
- requireInvariant consistencyIndexValidator(index2);
- }
- preserved uninstallModule(uint256 moduleTypeId, address module, bytes deInitData) with (env e) {
- requireInvariant atUniquenessValidator(index1, require_uint256(_validatorLength() - 1));
- requireInvariant atUniquenessValidator(index2, require_uint256(_validatorLength() - 1));
- }
- }
- invariant atUniquenessExecutor(uint256 index1, uint256 index2)
- (index1 < _executorLength() && index2 < _executorLength()) =>
- (index1 == index2 <=> _executorAt(index1) == _executorAt(index2))
- filtered { f -> f.selector != sig:execute(bytes32,bytes).selector && f.selector != sig:executeFromExecutor(bytes32,bytes).selector }
- {
- preserved {
- requireInvariant consistencyIndexExecutor(index1);
- requireInvariant consistencyIndexExecutor(index2);
- }
- preserved uninstallModule(uint256 moduleTypeId, address module, bytes deInitData) with (env e) {
- requireInvariant atUniquenessExecutor(index1, require_uint256(_executorLength() - 1));
- requireInvariant atUniquenessExecutor(index2, require_uint256(_executorLength() - 1));
- }
- }
- invariant consistencyIndexValidator(uint256 index)
- index < _validatorLength() => _validatorPositionOf(_validatorAt(index)) == require_uint256(index + 1)
- filtered { f -> f.selector != sig:execute(bytes32,bytes).selector && f.selector != sig:executeFromExecutor(bytes32,bytes).selector }
- {
- preserved uninstallModule(uint256 moduleTypeId, address otherModule, bytes deInitData) with (env e) {
- requireInvariant consistencyIndexValidator(require_uint256(_validatorLength() - 1));
- requireInvariant cleanStorageValidator(require_uint256(_validatorLength() - 1));
- }
- }
- invariant consistencyIndexExecutor(uint256 index)
- index < _executorLength() => _executorPositionOf(_executorAt(index)) == require_uint256(index + 1)
- filtered { f -> f.selector != sig:execute(bytes32,bytes).selector && f.selector != sig:executeFromExecutor(bytes32,bytes).selector }
- {
- preserved uninstallModule(uint256 moduleTypeId, address otherModule, bytes deInitData) with (env e) {
- requireInvariant consistencyIndexExecutor(require_uint256(_executorLength() - 1));
- requireInvariant cleanStorageExecutor(require_uint256(_executorLength() - 1));
- }
- }
- invariant consistencyKeyValidator(address module)
- _validatorContains(module) => (
- _validatorPositionOf(module) > 0 &&
- _validatorPositionOf(module) <= _validatorLength() &&
- _validatorAt(require_uint256(_validatorPositionOf(module) - 1)) == module
- )
- filtered { f -> f.selector != sig:execute(bytes32,bytes).selector && f.selector != sig:executeFromExecutor(bytes32,bytes).selector }
- {
- preserved {
- require moduleLengthSanity();
- }
- preserved uninstallModule(uint256 moduleTypeId, address otherModule, bytes deInitData) with (env e) {
- requireInvariant consistencyKeyValidator(otherModule);
- requireInvariant atUniquenessValidator(
- require_uint256(_validatorPositionOf(module) - 1),
- require_uint256(_validatorPositionOf(otherModule) - 1)
- );
- requireInvariant cleanStorageValidator(require_uint256(_validatorLength() - 1));
- }
- }
- invariant consistencyKeyExecutor(address module)
- _executorContains(module) => (
- _executorPositionOf(module) > 0 &&
- _executorPositionOf(module) <= _executorLength() &&
- _executorAt(require_uint256(_executorPositionOf(module) - 1)) == module
- )
- filtered { f -> f.selector != sig:execute(bytes32,bytes).selector && f.selector != sig:executeFromExecutor(bytes32,bytes).selector }
- {
- preserved {
- require moduleLengthSanity();
- }
- preserved uninstallModule(uint256 moduleTypeId, address otherModule, bytes deInitData) with (env e) {
- requireInvariant consistencyKeyExecutor(otherModule);
- requireInvariant atUniquenessExecutor(
- require_uint256(_executorPositionOf(module) - 1),
- require_uint256(_executorPositionOf(otherModule) - 1)
- );
- requireInvariant cleanStorageExecutor(require_uint256(_executorLength() - 1));
- }
- }
- invariant absentValidatorIsNotStored(address module, uint256 index)
- index < _validatorLength() => (!_validatorContains(module) => _validatorAt(index) != module)
- filtered { f -> f.selector != sig:execute(bytes32,bytes).selector && f.selector != sig:executeFromExecutor(bytes32,bytes).selector }
- {
- preserved uninstallModule(uint256 moduleTypeId, address otherModule, bytes deInitData) with (env e) {
- requireInvariant consistencyIndexValidator(index);
- requireInvariant consistencyKeyValidator(module);
- requireInvariant atUniquenessValidator(index, require_uint256(_validatorLength() - 1));
- requireInvariant cleanStorageValidator(require_uint256(_validatorLength() - 1));
- }
- }
- invariant absentExecutorIsNotStored(address module, uint256 index)
- index < _executorLength() => (!_executorContains(module) => _executorAt(index) != module)
- filtered { f -> f.selector != sig:execute(bytes32,bytes).selector && f.selector != sig:executeFromExecutor(bytes32,bytes).selector }
- {
- preserved uninstallModule(uint256 moduleTypeId, address otherModule, bytes deInitData) with (env e) {
- requireInvariant consistencyIndexExecutor(index);
- requireInvariant consistencyKeyExecutor(module);
- requireInvariant atUniquenessExecutor(index, require_uint256(_executorLength() - 1));
- requireInvariant cleanStorageExecutor(require_uint256(_executorLength() - 1));
- }
- }
- /*
- ┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
- │ Module management │
- └─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
- */
- // This guarantees that at most one fallback module is active for a given initData (i.e. selector)
- rule fallbackModule(address module, bytes initData) {
- assert isModuleInstalled(FALLBACK_TYPE(), module, initData) <=> getFallbackHandler(getDataSelector(initData)) == module;
- }
- rule moduleManagementRule(env e, method f, calldataarg args, uint256 moduleTypeId, address module, bytes additionalContext)
- filtered { f -> !f.isView }
- {
- bytes context;
- require context.length == 0;
- bool isEntryPoint = isEntryPoint(e);
- bool isEntryPointOrSelf = isEntryPointOrSelf(e);
- bool isExecutionModule = isExecutionModule(e, context);
- bool isModuleInstalledBefore = isModuleInstalled(moduleTypeId, module, additionalContext);
- f(e, args);
- bool isModuleInstalledAfter = isModuleInstalled(moduleTypeId, module, additionalContext);
- assert (
- isModuleInstalledBefore != isModuleInstalledAfter
- ) => (
- (
- f.selector == sig:execute(bytes32,bytes).selector &&
- isEntryPointOrSelf
- ) || (
- f.selector == sig:executeFromExecutor(bytes32,bytes).selector &&
- isExecutionModule
- ) || (
- f.selector == sig:installModule(uint256,address,bytes).selector &&
- isEntryPointOrSelf
- ) || (
- f.selector == sig:uninstallModule(uint256,address,bytes).selector &&
- isEntryPointOrSelf
- )
- );
- }
- rule installModuleRule(env e, uint256 moduleTypeId, address module, bytes initData) {
- uint256 otherModuleTypeId;
- address otherModule;
- bytes otherInitData;
- require moduleLengthSanity();
- requireInvariant consistencyKeyExecutor(module);
- requireInvariant consistencyKeyValidator(module);
- requireInvariant consistencyKeyExecutor(otherModule);
- requireInvariant consistencyKeyValidator(otherModule);
- bool isModuleInstalledBefore = isModuleInstalled(moduleTypeId, module, initData);
- bool isOtherModuleInstalledBefore = isModuleInstalled(otherModuleTypeId, otherModule, otherInitData);
- installModule(e, moduleTypeId, module, initData);
- bool isModuleInstalledAfter = isModuleInstalled(moduleTypeId, module, initData);
- bool isOtherModuleInstalledAfter = isModuleInstalled(otherModuleTypeId, otherModule, otherInitData);
- // Module is installed correctly
- assert !isModuleInstalledBefore && isModuleInstalledAfter;
- // No side effect on other modules
- assert isOtherModuleInstalledBefore != isOtherModuleInstalledAfter => (
- (
- moduleTypeId == otherModuleTypeId &&
- module == otherModule
- ) || (
- moduleTypeId == FALLBACK_TYPE() &&
- otherModuleTypeId == FALLBACK_TYPE() &&
- otherModule == 0 && // when a fallback module is installed, the 0 module is "removed" for that selector
- getDataSelector(otherInitData) == getDataSelector(initData) &&
- isOtherModuleInstalledBefore &&
- !isOtherModuleInstalledAfter
- )
- );
- }
- rule uninstallModuleRule(env e, uint256 moduleTypeId, address module, bytes initData) {
- uint256 otherModuleTypeId;
- address otherModule;
- bytes otherInitData;
- requireInvariant consistencyKeyExecutor(module);
- requireInvariant consistencyKeyValidator(module);
- requireInvariant consistencyKeyExecutor(otherModule);
- requireInvariant consistencyKeyValidator(otherModule);
- requireInvariant consistencyIndexExecutor(require_uint256(_executorLength() - 1));
- requireInvariant consistencyIndexValidator(require_uint256(_validatorLength() - 1));
- bool isModuleInstalledBefore = isModuleInstalled(moduleTypeId, module, initData);
- bool isOtherModuleInstalledBefore = isModuleInstalled(otherModuleTypeId, otherModule, otherInitData);
- uninstallModule(e, moduleTypeId, module, initData);
- bool isModuleInstalledAfter = isModuleInstalled(moduleTypeId, module, initData);
- bool isOtherModuleInstalledAfter = isModuleInstalled(otherModuleTypeId, otherModule, otherInitData);
- // Module is installed correctly
- assert isModuleInstalledBefore && !isModuleInstalledAfter;
- // No side effect on other modules
- assert isOtherModuleInstalledBefore != isOtherModuleInstalledAfter => (
- (
- moduleTypeId == otherModuleTypeId &&
- module == otherModule
- ) || (
- moduleTypeId == FALLBACK_TYPE() &&
- otherModuleTypeId == FALLBACK_TYPE() &&
- otherModule == 0 && // when a fallback module is uninstalled, the 0 module is "added" for that selector
- getDataSelector(otherInitData) == getDataSelector(initData) &&
- !isOtherModuleInstalledBefore &&
- isOtherModuleInstalledAfter
- )
- );
- }
- /*
- ┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
- │ CALL OPCODE │
- └─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
- */
- persistent ghost mathint calls;
- persistent ghost address lastcall_target;
- persistent ghost uint32 lastcall_selector;
- persistent ghost uint256 lastcall_value;
- persistent ghost uint256 lastcall_argsLength;
- hook CALL(uint256 gas, address target, uint256 value, uint256 argsOffset, uint256 argsLength, uint256 retOffset, uint256 retLength) uint256 rc {
- if (executingContract == currentContract) {
- calls = calls + 1;
- lastcall_target = target;
- lastcall_selector = selector;
- lastcall_value = value;
- lastcall_argsLength = argsLength;
- }
- }
- rule callOpcodeRule(env e, method f, calldataarg args)
- filtered { f -> !f.isView }
- {
- require calls == 0;
- bytes context;
- require context.length == 0;
- bool isEntryPoint = isEntryPoint(e);
- bool isEntryPointOrSelf = isEntryPointOrSelf(e);
- bool isExecutionModule = isExecutionModule(e, context);
- f(e, args);
- assert calls > 0 => (
- (
- // Can call any target with any data and value
- f.selector == sig:execute(bytes32,bytes).selector &&
- isEntryPointOrSelf
- ) || (
- // Can call any target with any data and value
- f.selector == sig:executeFromExecutor(bytes32,bytes).selector &&
- isExecutionModule
- ) || (
- // Can only call a module without any value. Target is verified by `callInstallModule`.
- f.selector == sig:installModule(uint256,address,bytes).selector &&
- isEntryPointOrSelf &&
- calls == 1 &&
- lastcall_selector == 0x6d61fe70 && // onInstall(bytes)
- lastcall_value == 0
- ) || (
- // Can only call a module without any value. Target is verified by `callInstallModule`.
- f.selector == sig:uninstallModule(uint256,address,bytes).selector &&
- isEntryPointOrSelf &&
- calls == 1 &&
- lastcall_selector == 0x8a91b0e3 && // onUninstall(bytes)
- lastcall_value == 0
- ) || (
- // Can send payment to the entrypoint or perform an external signature verification.
- f.selector == sig:validateUserOp(Account.PackedUserOperation,bytes32,uint256).selector &&
- isEntryPoint &&
- calls <= 2 &&
- (
- (
- // payPrefund (target is entryPoint and argsLength is 0)
- lastcall_target == entryPoint() &&
- lastcall_value > 0 &&
- lastcall_argsLength == 0
- ) || (
- // isValidSignatureWithSender (target is as validation module)
- isModuleInstalled(VALIDATOR_TYPE(), lastcall_target, context) &&
- lastcall_selector == 0x97003203 && // validateUserOp(Account.PackedUserOperation,bytes32)
- lastcall_value == 0
- )
- )
- ) || (
- // Arbitrary fallback, to the correct fallback handler
- f.isFallback &&
- calls == 1 &&
- lastcall_target == getFallbackHandler(to_bytes4(lastcall_selector)) &&
- lastcall_value == e.msg.value
- )
- );
- }
- rule callInstallModule(env e, uint256 moduleTypeId, address module, bytes initData) {
- require calls == 0;
- installModule(e, moduleTypeId, module, initData);
- assert calls == 1;
- assert lastcall_target == module;
- assert lastcall_selector == 0x6d61fe70; // onInstall(bytes)
- assert lastcall_value == 0;
- }
- rule callUninstallModule(env e, uint256 moduleTypeId, address module, bytes deInitData) {
- require calls == 0;
- uninstallModule(e, moduleTypeId, module, deInitData);
- assert calls == 1;
- assert lastcall_target == module;
- assert lastcall_selector == 0x8a91b0e3; // onUninstall(bytes)
- assert lastcall_value == 0;
- }
- /*
- ┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
- │ DELEGATECALL OPCODE │
- └─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
- */
- persistent ghost mathint delegatecalls;
- persistent ghost address lastdelegatecall_target;
- persistent ghost uint32 lastdelegatecall_selector;
- persistent ghost uint256 lastdelegatecall_argsLength;
- hook DELEGATECALL(uint256 gas, address target, uint256 argsOffset, uint256 argsLength, uint256 retOffset, uint256 retLength) uint256 rc {
- if (executingContract == currentContract) {
- delegatecalls = delegatecalls + 1;
- lastdelegatecall_target = target;
- lastdelegatecall_selector = selector;
- lastdelegatecall_argsLength = argsLength;
- }
- }
- rule delegatecallOpcodeRule(env e, method f, calldataarg args)
- filtered { f -> !f.isView }
- {
- require delegatecalls == 0;
- bytes context;
- require context.length == 0;
- bool isEntryPoint = isEntryPoint(e);
- bool isEntryPointOrSelf = isEntryPointOrSelf(e);
- bool isExecutionModule = isExecutionModule(e, context);
- f(e, args);
- assert delegatecalls > 0 => (
- (
- // Can delegatecall to target with any data
- f.selector == sig:execute(bytes32,bytes).selector &&
- isEntryPointOrSelf
- ) || (
- // Can delegatecall to target with any data
- f.selector == sig:executeFromExecutor(bytes32,bytes).selector &&
- isExecutionModule
- )
- );
- }
- /*
- ┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
- │ ERC-4337 │
- └─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
- */
- rule validateUserOpPayment(env e){
- Account.PackedUserOperation userOp;
- bytes32 userOpHash;
- uint256 missingAccountFunds;
- uint256 balanceBefore = nativeBalances[currentContract];
- validateUserOp(e, userOp, userOpHash, missingAccountFunds);
- uint256 balanceAfter = nativeBalances[currentContract];
- assert balanceBefore - balanceAfter <= missingAccountFunds;
- }
|