Migrate FV specs to CVL2 (#4527)
Co-authored-by: Hadrien Croubois <hadrien.croubois@gmail.com>pull/4585/head^2
parent
b6111faac8
commit
36bf1e46fa
@ -1,14 +0,0 @@ |
||||
--- token/ERC721/ERC721.sol 2023-03-07 10:48:47.736822221 +0100
|
||||
+++ token/ERC721/ERC721.sol 2023-03-09 19:49:39.669338673 +0100
|
||||
@@ -199,6 +199,11 @@
|
||||
return _owners[tokenId];
|
||||
}
|
||||
|
||||
+ // FV
|
||||
+ function _getApproved(uint256 tokenId) internal view returns (address) {
|
||||
+ return _tokenApprovals[tokenId];
|
||||
+ }
|
||||
+
|
||||
/**
|
||||
* @dev Returns whether `tokenId` exists.
|
||||
*
|
@ -1,7 +1,6 @@ |
||||
// SPDX-License-Identifier: MIT |
||||
|
||||
pragma solidity ^0.8.20; |
||||
|
||||
import "../patched/access/AccessControl.sol"; |
||||
import {AccessControl} from "../patched/access/AccessControl.sol"; |
||||
|
||||
contract AccessControlHarness is AccessControl {} |
||||
|
@ -1,9 +1,10 @@ |
||||
// SPDX-License-Identifier: MIT |
||||
|
||||
pragma solidity ^0.8.20; |
||||
|
||||
import "../patched/access/Ownable2Step.sol"; |
||||
import {Ownable2Step, Ownable} from "../patched/access/Ownable2Step.sol"; |
||||
|
||||
contract Ownable2StepHarness is Ownable2Step { |
||||
function restricted() external onlyOwner {} |
||||
constructor(address initialOwner) Ownable(initialOwner) {} |
||||
|
||||
function restricted() external onlyOwner {} |
||||
} |
||||
|
@ -1,9 +1,10 @@ |
||||
// SPDX-License-Identifier: MIT |
||||
|
||||
pragma solidity ^0.8.20; |
||||
|
||||
import "../patched/access/Ownable.sol"; |
||||
import {Ownable} from "../patched/access/Ownable.sol"; |
||||
|
||||
contract OwnableHarness is Ownable { |
||||
function restricted() external onlyOwner {} |
||||
constructor(address initialOwner) Ownable(initialOwner) {} |
||||
|
||||
function restricted() external onlyOwner {} |
||||
} |
||||
|
@ -1,126 +1,119 @@ |
||||
import "helpers/helpers.spec" |
||||
import "methods/IAccessControl.spec" |
||||
|
||||
/* |
||||
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ |
||||
│ Definitions │ |
||||
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ |
||||
*/ |
||||
definition DEFAULT_ADMIN_ROLE() returns bytes32 = 0x0000000000000000000000000000000000000000000000000000000000000000; |
||||
|
||||
/* |
||||
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ |
||||
│ Identify entrypoints: only grantRole, revokeRole and renounceRole can alter permissions │ |
||||
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ |
||||
*/ |
||||
rule onlyGrantCanGrant(env e, method f, bytes32 role, address account) { |
||||
calldataarg args; |
||||
|
||||
bool hasRoleBefore = hasRole(role, account); |
||||
f(e, args); |
||||
bool hasRoleAfter = hasRole(role, account); |
||||
|
||||
assert ( |
||||
!hasRoleBefore && |
||||
hasRoleAfter |
||||
) => ( |
||||
f.selector == grantRole(bytes32, address).selector |
||||
); |
||||
|
||||
assert ( |
||||
hasRoleBefore && |
||||
!hasRoleAfter |
||||
) => ( |
||||
f.selector == revokeRole(bytes32, address).selector || |
||||
f.selector == renounceRole(bytes32, address).selector |
||||
); |
||||
} |
||||
|
||||
/* |
||||
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ |
||||
│ Function correctness: grantRole only affects the specified user/role combo │ |
||||
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ |
||||
*/ |
||||
rule grantRoleEffect(env e, bytes32 role) { |
||||
require nonpayable(e); |
||||
|
||||
bytes32 otherRole; |
||||
address account; |
||||
address otherAccount; |
||||
|
||||
bool isCallerAdmin = hasRole(getRoleAdmin(role), e.msg.sender); |
||||
bool hasOtherRoleBefore = hasRole(otherRole, otherAccount); |
||||
|
||||
grantRole@withrevert(e, role, account); |
||||
bool success = !lastReverted; |
||||
|
||||
bool hasOtherRoleAfter = hasRole(otherRole, otherAccount); |
||||
|
||||
// liveness |
||||
assert success <=> isCallerAdmin; |
||||
|
||||
// effect |
||||
assert success => hasRole(role, account); |
||||
|
||||
// no side effect |
||||
assert hasOtherRoleBefore != hasOtherRoleAfter => (role == otherRole && account == otherAccount); |
||||
} |
||||
|
||||
/* |
||||
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ |
||||
│ Function correctness: revokeRole only affects the specified user/role combo │ |
||||
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ |
||||
*/ |
||||
rule revokeRoleEffect(env e, bytes32 role) { |
||||
require nonpayable(e); |
||||
|
||||
bytes32 otherRole; |
||||
address account; |
||||
address otherAccount; |
||||
|
||||
bool isCallerAdmin = hasRole(getRoleAdmin(role), e.msg.sender); |
||||
bool hasOtherRoleBefore = hasRole(otherRole, otherAccount); |
||||
|
||||
revokeRole@withrevert(e, role, account); |
||||
bool success = !lastReverted; |
||||
|
||||
bool hasOtherRoleAfter = hasRole(otherRole, otherAccount); |
||||
|
||||
// liveness |
||||
assert success <=> isCallerAdmin; |
||||
|
||||
// effect |
||||
assert success => !hasRole(role, account); |
||||
|
||||
// no side effect |
||||
assert hasOtherRoleBefore != hasOtherRoleAfter => (role == otherRole && account == otherAccount); |
||||
} |
||||
|
||||
/* |
||||
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ |
||||
│ Function correctness: renounceRole only affects the specified user/role combo │ |
||||
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ |
||||
*/ |
||||
rule renounceRoleEffect(env e, bytes32 role) { |
||||
require nonpayable(e); |
||||
|
||||
bytes32 otherRole; |
||||
address account; |
||||
address otherAccount; |
||||
|
||||
bool hasOtherRoleBefore = hasRole(otherRole, otherAccount); |
||||
|
||||
renounceRole@withrevert(e, role, account); |
||||
bool success = !lastReverted; |
||||
|
||||
bool hasOtherRoleAfter = hasRole(otherRole, otherAccount); |
||||
|
||||
// liveness |
||||
assert success <=> account == e.msg.sender; |
||||
|
||||
// effect |
||||
assert success => !hasRole(role, account); |
||||
|
||||
// no side effect |
||||
assert hasOtherRoleBefore != hasOtherRoleAfter => (role == otherRole && account == otherAccount); |
||||
} |
||||
import "helpers/helpers.spec"; |
||||
import "methods/IAccessControl.spec"; |
||||
|
||||
/* |
||||
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ |
||||
│ Identify entrypoints: only grantRole, revokeRole and renounceRole can alter permissions │ |
||||
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ |
||||
*/ |
||||
rule onlyGrantCanGrant(env e, method f, bytes32 role, address account) { |
||||
calldataarg args; |
||||
|
||||
bool hasRoleBefore = hasRole(role, account); |
||||
f(e, args); |
||||
bool hasRoleAfter = hasRole(role, account); |
||||
|
||||
assert ( |
||||
!hasRoleBefore && |
||||
hasRoleAfter |
||||
) => ( |
||||
f.selector == sig:grantRole(bytes32, address).selector |
||||
); |
||||
|
||||
assert ( |
||||
hasRoleBefore && |
||||
!hasRoleAfter |
||||
) => ( |
||||
f.selector == sig:revokeRole(bytes32, address).selector || |
||||
f.selector == sig:renounceRole(bytes32, address).selector |
||||
); |
||||
} |
||||
|
||||
/* |
||||
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ |
||||
│ Function correctness: grantRole only affects the specified user/role combo │ |
||||
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ |
||||
*/ |
||||
rule grantRoleEffect(env e, bytes32 role) { |
||||
require nonpayable(e); |
||||
|
||||
bytes32 otherRole; |
||||
address account; |
||||
address otherAccount; |
||||
|
||||
bool isCallerAdmin = hasRole(getRoleAdmin(role), e.msg.sender); |
||||
bool hasOtherRoleBefore = hasRole(otherRole, otherAccount); |
||||
|
||||
grantRole@withrevert(e, role, account); |
||||
bool success = !lastReverted; |
||||
|
||||
bool hasOtherRoleAfter = hasRole(otherRole, otherAccount); |
||||
|
||||
// liveness |
||||
assert success <=> isCallerAdmin; |
||||
|
||||
// effect |
||||
assert success => hasRole(role, account); |
||||
|
||||
// no side effect |
||||
assert hasOtherRoleBefore != hasOtherRoleAfter => (role == otherRole && account == otherAccount); |
||||
} |
||||
|
||||
/* |
||||
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ |
||||
│ Function correctness: revokeRole only affects the specified user/role combo │ |
||||
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ |
||||
*/ |
||||
rule revokeRoleEffect(env e, bytes32 role) { |
||||
require nonpayable(e); |
||||
|
||||
bytes32 otherRole; |
||||
address account; |
||||
address otherAccount; |
||||
|
||||
bool isCallerAdmin = hasRole(getRoleAdmin(role), e.msg.sender); |
||||
bool hasOtherRoleBefore = hasRole(otherRole, otherAccount); |
||||
|
||||
revokeRole@withrevert(e, role, account); |
||||
bool success = !lastReverted; |
||||
|
||||
bool hasOtherRoleAfter = hasRole(otherRole, otherAccount); |
||||
|
||||
// liveness |
||||
assert success <=> isCallerAdmin; |
||||
|
||||
// effect |
||||
assert success => !hasRole(role, account); |
||||
|
||||
// no side effect |
||||
assert hasOtherRoleBefore != hasOtherRoleAfter => (role == otherRole && account == otherAccount); |
||||
} |
||||
|
||||
/* |
||||
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ |
||||
│ Function correctness: renounceRole only affects the specified user/role combo │ |
||||
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ |
||||
*/ |
||||
rule renounceRoleEffect(env e, bytes32 role) { |
||||
require nonpayable(e); |
||||
|
||||
bytes32 otherRole; |
||||
address account; |
||||
address otherAccount; |
||||
|
||||
bool hasOtherRoleBefore = hasRole(otherRole, otherAccount); |
||||
|
||||
renounceRole@withrevert(e, role, account); |
||||
bool success = !lastReverted; |
||||
|
||||
bool hasOtherRoleAfter = hasRole(otherRole, otherAccount); |
||||
|
||||
// liveness |
||||
assert success <=> account == e.msg.sender; |
||||
|
||||
// effect |
||||
assert success => !hasRole(role, account); |
||||
|
||||
// no side effect |
||||
assert hasOtherRoleBefore != hasOtherRoleAfter => (role == otherRole && account == otherAccount); |
||||
} |
||||
|
@ -1,78 +1,77 @@ |
||||
import "helpers/helpers.spec" |
||||
import "methods/IOwnable.spec" |
||||
|
||||
methods { |
||||
restricted() |
||||
} |
||||
|
||||
/* |
||||
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ |
||||
│ Function correctness: transferOwnership changes ownership │ |
||||
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ |
||||
*/ |
||||
rule transferOwnership(env e) { |
||||
require nonpayable(e); |
||||
|
||||
address newOwner; |
||||
address current = owner(); |
||||
|
||||
transferOwnership@withrevert(e, newOwner); |
||||
bool success = !lastReverted; |
||||
|
||||
assert success <=> (e.msg.sender == current && newOwner != 0), "unauthorized caller or invalid arg"; |
||||
assert success => owner() == newOwner, "current owner changed"; |
||||
} |
||||
|
||||
/* |
||||
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ |
||||
│ Function correctness: renounceOwnership removes the owner │ |
||||
|
||||
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ |
||||
*/ |
||||
rule renounceOwnership(env e) { |
||||
require nonpayable(e); |
||||
|
||||
address current = owner(); |
||||
|
||||
renounceOwnership@withrevert(e); |
||||
bool success = !lastReverted; |
||||
|
||||
assert success <=> e.msg.sender == current, "unauthorized caller"; |
||||
assert success => owner() == 0, "owner not cleared"; |
||||
} |
||||
|
||||
/* |
||||
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ |
||||
│ Access control: only current owner can call restricted functions │ |
||||
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ |
||||
*/ |
||||
rule onlyCurrentOwnerCanCallOnlyOwner(env e) { |
||||
require nonpayable(e); |
||||
|
||||
address current = owner(); |
||||
|
||||
calldataarg args; |
||||
restricted@withrevert(e, args); |
||||
|
||||
assert !lastReverted <=> e.msg.sender == current, "access control failed"; |
||||
} |
||||
|
||||
/* |
||||
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ |
||||
│ Rule: ownership can only change in specific ways │ |
||||
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ |
||||
*/ |
||||
rule onlyOwnerOrPendingOwnerCanChangeOwnership(env e) { |
||||
address oldCurrent = owner(); |
||||
|
||||
method f; calldataarg args; |
||||
f(e, args); |
||||
|
||||
address newCurrent = owner(); |
||||
|
||||
// If owner changes, must be either transferOwnership or renounceOwnership |
||||
assert oldCurrent != newCurrent => ( |
||||
(e.msg.sender == oldCurrent && newCurrent != 0 && f.selector == transferOwnership(address).selector) || |
||||
(e.msg.sender == oldCurrent && newCurrent == 0 && f.selector == renounceOwnership().selector) |
||||
); |
||||
} |
||||
import "helpers/helpers.spec"; |
||||
import "methods/IOwnable.spec"; |
||||
|
||||
methods { |
||||
function restricted() external; |
||||
} |
||||
|
||||
/* |
||||
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ |
||||
│ Function correctness: transferOwnership changes ownership │ |
||||
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ |
||||
*/ |
||||
rule transferOwnership(env e) { |
||||
require nonpayable(e); |
||||
|
||||
address newOwner; |
||||
address current = owner(); |
||||
|
||||
transferOwnership@withrevert(e, newOwner); |
||||
bool success = !lastReverted; |
||||
|
||||
assert success <=> (e.msg.sender == current && newOwner != 0), "unauthorized caller or invalid arg"; |
||||
assert success => owner() == newOwner, "current owner changed"; |
||||
} |
||||
|
||||
/* |
||||
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ |
||||
│ Function correctness: renounceOwnership removes the owner │ |
||||
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ |
||||
*/ |
||||
rule renounceOwnership(env e) { |
||||
require nonpayable(e); |
||||
|
||||
address current = owner(); |
||||
|
||||
renounceOwnership@withrevert(e); |
||||
bool success = !lastReverted; |
||||
|
||||
assert success <=> e.msg.sender == current, "unauthorized caller"; |
||||
assert success => owner() == 0, "owner not cleared"; |
||||
} |
||||
|
||||
/* |
||||
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ |
||||
│ Access control: only current owner can call restricted functions │ |
||||
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ |
||||
*/ |
||||
rule onlyCurrentOwnerCanCallOnlyOwner(env e) { |
||||
require nonpayable(e); |
||||
|
||||
address current = owner(); |
||||
|
||||
calldataarg args; |
||||
restricted@withrevert(e, args); |
||||
|
||||
assert !lastReverted <=> e.msg.sender == current, "access control failed"; |
||||
} |
||||
|
||||
/* |
||||
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ |
||||
│ Rule: ownership can only change in specific ways │ |
||||
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ |
||||
*/ |
||||
rule onlyOwnerOrPendingOwnerCanChangeOwnership(env e) { |
||||
address oldCurrent = owner(); |
||||
|
||||
method f; calldataarg args; |
||||
f(e, args); |
||||
|
||||
address newCurrent = owner(); |
||||
|
||||
// If owner changes, must be either transferOwnership or renounceOwnership |
||||
assert oldCurrent != newCurrent => ( |
||||
(e.msg.sender == oldCurrent && newCurrent != 0 && f.selector == sig:transferOwnership(address).selector) || |
||||
(e.msg.sender == oldCurrent && newCurrent == 0 && f.selector == sig:renounceOwnership().selector) |
||||
); |
||||
} |
||||
|
@ -1,108 +1,108 @@ |
||||
import "helpers/helpers.spec" |
||||
import "methods/IOwnable2Step.spec" |
||||
|
||||
methods { |
||||
restricted() |
||||
} |
||||
|
||||
/* |
||||
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ |
||||
│ Function correctness: transferOwnership sets the pending owner │ |
||||
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ |
||||
*/ |
||||
rule transferOwnership(env e) { |
||||
require nonpayable(e); |
||||
|
||||
address newOwner; |
||||
address current = owner(); |
||||
|
||||
transferOwnership@withrevert(e, newOwner); |
||||
bool success = !lastReverted; |
||||
|
||||
assert success <=> e.msg.sender == current, "unauthorized caller"; |
||||
assert success => pendingOwner() == newOwner, "pending owner not set"; |
||||
assert success => owner() == current, "current owner changed"; |
||||
} |
||||
|
||||
/* |
||||
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ |
||||
│ Function correctness: renounceOwnership removes the owner and the pendingOwner │ |
||||
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ |
||||
*/ |
||||
rule renounceOwnership(env e) { |
||||
require nonpayable(e); |
||||
|
||||
address current = owner(); |
||||
|
||||
renounceOwnership@withrevert(e); |
||||
bool success = !lastReverted; |
||||
|
||||
assert success <=> e.msg.sender == current, "unauthorized caller"; |
||||
assert success => pendingOwner() == 0, "pending owner not cleared"; |
||||
assert success => owner() == 0, "owner not cleared"; |
||||
} |
||||
|
||||
/* |
||||
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ |
||||
│ Function correctness: acceptOwnership changes owner and reset pending owner │ |
||||
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ |
||||
*/ |
||||
rule acceptOwnership(env e) { |
||||
|
||||
require nonpayable(e); |
||||
|
||||
address current = owner(); |
||||
address pending = pendingOwner(); |
||||
|
||||
acceptOwnership@withrevert(e); |
||||
bool success = !lastReverted; |
||||
|
||||
assert success <=> e.msg.sender == pending, "unauthorized caller"; |
||||
assert success => pendingOwner() == 0, "pending owner not cleared"; |
||||
assert success => owner() == pending, "owner not transferred"; |
||||
} |
||||
|
||||
/* |
||||
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ |
||||
│ Access control: only current owner can call restricted functions │ |
||||
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ |
||||
*/ |
||||
rule onlyCurrentOwnerCanCallOnlyOwner(env e) { |
||||
require nonpayable(e); |
||||
|
||||
address current = owner(); |
||||
|
||||
calldataarg args; |
||||
restricted@withrevert(e, args); |
||||
|
||||
assert !lastReverted <=> e.msg.sender == current, "access control failed"; |
||||
} |
||||
|
||||
/* |
||||
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ |
||||
│ Rule: ownership and pending ownership can only change in specific ways │ |
||||
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ |
||||
*/ |
||||
rule ownerOrPendingOwnerChange(env e, method f) { |
||||
address oldCurrent = owner(); |
||||
address oldPending = pendingOwner(); |
||||
|
||||
calldataarg args; |
||||
f(e, args); |
||||
|
||||
address newCurrent = owner(); |
||||
address newPending = pendingOwner(); |
||||
|
||||
// If owner changes, must be either acceptOwnership or renounceOwnership |
||||
assert oldCurrent != newCurrent => ( |
||||
(e.msg.sender == oldPending && newCurrent == oldPending && newPending == 0 && f.selector == acceptOwnership().selector) || |
||||
(e.msg.sender == oldCurrent && newCurrent == 0 && newPending == 0 && f.selector == renounceOwnership().selector) |
||||
); |
||||
|
||||
// If pending changes, must be either acceptance or reset |
||||
assert oldPending != newPending => ( |
||||
(e.msg.sender == oldCurrent && newCurrent == oldCurrent && f.selector == transferOwnership(address).selector) || |
||||
(e.msg.sender == oldPending && newCurrent == oldPending && newPending == 0 && f.selector == acceptOwnership().selector) || |
||||
(e.msg.sender == oldCurrent && newCurrent == 0 && newPending == 0 && f.selector == renounceOwnership().selector) |
||||
); |
||||
} |
||||
import "helpers/helpers.spec"; |
||||
import "methods/IOwnable2Step.spec"; |
||||
|
||||
methods { |
||||
function restricted() external; |
||||
} |
||||
|
||||
/* |
||||
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ |
||||
│ Function correctness: transferOwnership sets the pending owner │ |
||||
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ |
||||
*/ |
||||
rule transferOwnership(env e) { |
||||
require nonpayable(e); |
||||
|
||||
address newOwner; |
||||
address current = owner(); |
||||
|
||||
transferOwnership@withrevert(e, newOwner); |
||||
bool success = !lastReverted; |
||||
|
||||
assert success <=> e.msg.sender == current, "unauthorized caller"; |
||||
assert success => pendingOwner() == newOwner, "pending owner not set"; |
||||
assert success => owner() == current, "current owner changed"; |
||||
} |
||||
|
||||
/* |
||||
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ |
||||
│ Function correctness: renounceOwnership removes the owner and the pendingOwner │ |
||||
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ |
||||
*/ |
||||
rule renounceOwnership(env e) { |
||||
require nonpayable(e); |
||||
|
||||
address current = owner(); |
||||
|
||||
renounceOwnership@withrevert(e); |
||||
bool success = !lastReverted; |
||||
|
||||
assert success <=> e.msg.sender == current, "unauthorized caller"; |
||||
assert success => pendingOwner() == 0, "pending owner not cleared"; |
||||
assert success => owner() == 0, "owner not cleared"; |
||||
} |
||||
|
||||
/* |
||||
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ |
||||
│ Function correctness: acceptOwnership changes owner and reset pending owner │ |
||||
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ |
||||
*/ |
||||
rule acceptOwnership(env e) { |
||||
|
||||
require nonpayable(e); |
||||
|
||||
address current = owner(); |
||||
address pending = pendingOwner(); |
||||
|
||||
acceptOwnership@withrevert(e); |
||||
bool success = !lastReverted; |
||||
|
||||
assert success <=> e.msg.sender == pending, "unauthorized caller"; |
||||
assert success => pendingOwner() == 0, "pending owner not cleared"; |
||||
assert success => owner() == pending, "owner not transferred"; |
||||
} |
||||
|
||||
/* |
||||
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ |
||||
│ Access control: only current owner can call restricted functions │ |
||||
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ |
||||
*/ |
||||
rule onlyCurrentOwnerCanCallOnlyOwner(env e) { |
||||
require nonpayable(e); |
||||
|
||||
address current = owner(); |
||||
|
||||
calldataarg args; |
||||
restricted@withrevert(e, args); |
||||
|
||||
assert !lastReverted <=> e.msg.sender == current, "access control failed"; |
||||
} |
||||
|
||||
/* |
||||
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ |
||||
│ Rule: ownership and pending ownership can only change in specific ways │ |
||||
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ |
||||
*/ |
||||
rule ownerOrPendingOwnerChange(env e, method f) { |
||||
address oldCurrent = owner(); |
||||
address oldPending = pendingOwner(); |
||||
|
||||
calldataarg args; |
||||
f(e, args); |
||||
|
||||
address newCurrent = owner(); |
||||
address newPending = pendingOwner(); |
||||
|
||||
// If owner changes, must be either acceptOwnership or renounceOwnership |
||||
assert oldCurrent != newCurrent => ( |
||||
(e.msg.sender == oldPending && newCurrent == oldPending && newPending == 0 && f.selector == sig:acceptOwnership().selector) || |
||||
(e.msg.sender == oldCurrent && newCurrent == 0 && newPending == 0 && f.selector == sig:renounceOwnership().selector) |
||||
); |
||||
|
||||
// If pending changes, must be either acceptance or reset |
||||
assert oldPending != newPending => ( |
||||
(e.msg.sender == oldCurrent && newCurrent == oldCurrent && f.selector == sig:transferOwnership(address).selector) || |
||||
(e.msg.sender == oldPending && newCurrent == oldPending && newPending == 0 && f.selector == sig:acceptOwnership().selector) || |
||||
(e.msg.sender == oldCurrent && newCurrent == 0 && newPending == 0 && f.selector == sig:renounceOwnership().selector) |
||||
); |
||||
} |
||||
|
@ -1,96 +1,96 @@ |
||||
import "helpers/helpers.spec" |
||||
|
||||
methods { |
||||
paused() returns (bool) envfree |
||||
pause() |
||||
unpause() |
||||
onlyWhenPaused() |
||||
onlyWhenNotPaused() |
||||
} |
||||
|
||||
/* |
||||
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ |
||||
│ Function correctness: _pause pauses the contract │ |
||||
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ |
||||
*/ |
||||
rule pause(env e) { |
||||
require nonpayable(e); |
||||
|
||||
bool pausedBefore = paused(); |
||||
|
||||
pause@withrevert(e); |
||||
bool success = !lastReverted; |
||||
|
||||
bool pausedAfter = paused(); |
||||
|
||||
// liveness |
||||
assert success <=> !pausedBefore, "works if and only if the contract was not paused before"; |
||||
|
||||
// effect |
||||
assert success => pausedAfter, "contract must be paused after a successful call"; |
||||
} |
||||
|
||||
/* |
||||
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ |
||||
│ Function correctness: _unpause unpauses the contract │ |
||||
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ |
||||
*/ |
||||
rule unpause(env e) { |
||||
require nonpayable(e); |
||||
|
||||
bool pausedBefore = paused(); |
||||
|
||||
unpause@withrevert(e); |
||||
bool success = !lastReverted; |
||||
|
||||
bool pausedAfter = paused(); |
||||
|
||||
// liveness |
||||
assert success <=> pausedBefore, "works if and only if the contract was paused before"; |
||||
|
||||
// effect |
||||
assert success => !pausedAfter, "contract must be unpaused after a successful call"; |
||||
} |
||||
|
||||
/* |
||||
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ |
||||
│ Function correctness: whenPaused modifier can only be called if the contract is paused │ |
||||
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ |
||||
*/ |
||||
rule whenPaused(env e) { |
||||
require nonpayable(e); |
||||
|
||||
onlyWhenPaused@withrevert(e); |
||||
assert !lastReverted <=> paused(), "works if and only if the contract is paused"; |
||||
} |
||||
|
||||
/* |
||||
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ |
||||
│ Function correctness: whenNotPaused modifier can only be called if the contract is not paused │ |
||||
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ |
||||
*/ |
||||
rule whenNotPaused(env e) { |
||||
require nonpayable(e); |
||||
|
||||
onlyWhenNotPaused@withrevert(e); |
||||
assert !lastReverted <=> !paused(), "works if and only if the contract is not paused"; |
||||
} |
||||
|
||||
/* |
||||
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ |
||||
│ Rules: only _pause and _unpause can change paused status │ |
||||
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ |
||||
*/ |
||||
rule noPauseChange(env e) { |
||||
method f; |
||||
calldataarg args; |
||||
|
||||
bool pausedBefore = paused(); |
||||
f(e, args); |
||||
bool pausedAfter = paused(); |
||||
|
||||
assert pausedBefore != pausedAfter => ( |
||||
(!pausedAfter && f.selector == unpause().selector) || |
||||
(pausedAfter && f.selector == pause().selector) |
||||
), "contract's paused status can only be changed by _pause() or _unpause()"; |
||||
} |
||||
import "helpers/helpers.spec"; |
||||
|
||||
methods { |
||||
function paused() external returns (bool) envfree; |
||||
function pause() external; |
||||
function unpause() external; |
||||
function onlyWhenPaused() external; |
||||
function onlyWhenNotPaused() external; |
||||
} |
||||
|
||||
/* |
||||
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ |
||||
│ Function correctness: _pause pauses the contract │ |
||||
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ |
||||
*/ |
||||
rule pause(env e) { |
||||
require nonpayable(e); |
||||
|
||||
bool pausedBefore = paused(); |
||||
|
||||
pause@withrevert(e); |
||||
bool success = !lastReverted; |
||||
|
||||
bool pausedAfter = paused(); |
||||
|
||||
// liveness |
||||
assert success <=> !pausedBefore, "works if and only if the contract was not paused before"; |
||||
|
||||
// effect |
||||
assert success => pausedAfter, "contract must be paused after a successful call"; |
||||
} |
||||
|
||||
/* |
||||
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ |
||||
│ Function correctness: _unpause unpauses the contract │ |
||||
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ |
||||
*/ |
||||
rule unpause(env e) { |
||||
require nonpayable(e); |
||||
|
||||
bool pausedBefore = paused(); |
||||
|
||||
unpause@withrevert(e); |
||||
bool success = !lastReverted; |
||||
|
||||
bool pausedAfter = paused(); |
||||
|
||||
// liveness |
||||
assert success <=> pausedBefore, "works if and only if the contract was paused before"; |
||||
|
||||
// effect |
||||
assert success => !pausedAfter, "contract must be unpaused after a successful call"; |
||||
} |
||||
|
||||
/* |
||||
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ |
||||
│ Function correctness: whenPaused modifier can only be called if the contract is paused │ |
||||
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ |
||||
*/ |
||||
rule whenPaused(env e) { |
||||
require nonpayable(e); |
||||
|
||||
onlyWhenPaused@withrevert(e); |
||||
assert !lastReverted <=> paused(), "works if and only if the contract is paused"; |
||||
} |
||||
|
||||
/* |
||||
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ |
||||
│ Function correctness: whenNotPaused modifier can only be called if the contract is not paused │ |
||||
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ |
||||
*/ |
||||
rule whenNotPaused(env e) { |
||||
require nonpayable(e); |
||||
|
||||
onlyWhenNotPaused@withrevert(e); |
||||
assert !lastReverted <=> !paused(), "works if and only if the contract is not paused"; |
||||
} |
||||
|
||||
/* |
||||
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ |
||||
│ Rules: only _pause and _unpause can change paused status │ |
||||
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ |
||||
*/ |
||||
rule noPauseChange(env e) { |
||||
method f; |
||||
calldataarg args; |
||||
|
||||
bool pausedBefore = paused(); |
||||
f(e, args); |
||||
bool pausedAfter = paused(); |
||||
|
||||
assert pausedBefore != pausedAfter => ( |
||||
(!pausedAfter && f.selector == sig:unpause().selector) || |
||||
(pausedAfter && f.selector == sig:pause().selector) |
||||
), "contract's paused status can only be changed by _pause() or _unpause()"; |
||||
} |
||||
|
@ -1,7 +1,8 @@ |
||||
methods { |
||||
hasRole(bytes32, address) returns(bool) envfree |
||||
getRoleAdmin(bytes32) returns(bytes32) envfree |
||||
grantRole(bytes32, address) |
||||
revokeRole(bytes32, address) |
||||
renounceRole(bytes32, address) |
||||
function DEFAULT_ADMIN_ROLE() external returns (bytes32) envfree; |
||||
function hasRole(bytes32, address) external returns(bool) envfree; |
||||
function getRoleAdmin(bytes32) external returns(bytes32) envfree; |
||||
function grantRole(bytes32, address) external; |
||||
function revokeRole(bytes32, address) external; |
||||
function renounceRole(bytes32, address) external; |
||||
} |
||||
|
@ -1,36 +1,36 @@ |
||||
import "./IERC5313.spec" |
||||
import "./IERC5313.spec"; |
||||
|
||||
methods { |
||||
// === View == |
||||
|
||||
// Default Admin |
||||
defaultAdmin() returns(address) envfree |
||||
pendingDefaultAdmin() returns(address, uint48) envfree |
||||
function defaultAdmin() external returns(address) envfree; |
||||
function pendingDefaultAdmin() external returns(address, uint48) envfree; |
||||
|
||||
// Default Admin Delay |
||||
defaultAdminDelay() returns(uint48) |
||||
pendingDefaultAdminDelay() returns(uint48, uint48) |
||||
defaultAdminDelayIncreaseWait() returns(uint48) envfree |
||||
function defaultAdminDelay() external returns(uint48); |
||||
function pendingDefaultAdminDelay() external returns(uint48, uint48); |
||||
function defaultAdminDelayIncreaseWait() external returns(uint48) envfree; |
||||
|
||||
// === Mutations == |
||||
|
||||
// Default Admin |
||||
beginDefaultAdminTransfer(address) |
||||
cancelDefaultAdminTransfer() |
||||
acceptDefaultAdminTransfer() |
||||
function beginDefaultAdminTransfer(address) external; |
||||
function cancelDefaultAdminTransfer() external; |
||||
function acceptDefaultAdminTransfer() external; |
||||
|
||||
// Default Admin Delay |
||||
changeDefaultAdminDelay(uint48) |
||||
rollbackDefaultAdminDelay() |
||||
function changeDefaultAdminDelay(uint48) external; |
||||
function rollbackDefaultAdminDelay() external; |
||||
|
||||
// == FV == |
||||
|
||||
// Default Admin |
||||
pendingDefaultAdmin_() returns (address) envfree |
||||
pendingDefaultAdminSchedule_() returns (uint48) envfree |
||||
function pendingDefaultAdmin_() external returns (address) envfree; |
||||
function pendingDefaultAdminSchedule_() external returns (uint48) envfree; |
||||
|
||||
// Default Admin Delay |
||||
pendingDelay_() returns (uint48) |
||||
pendingDelaySchedule_() returns (uint48) |
||||
delayChangeWait_(uint48) returns (uint48) |
||||
function pendingDelay_() external returns (uint48); |
||||
function pendingDelaySchedule_() external returns (uint48); |
||||
function delayChangeWait_(uint48) external returns (uint48); |
||||
} |
||||
|
@ -1,11 +1,11 @@ |
||||
methods { |
||||
name() returns (string) envfree => DISPATCHER(true) |
||||
symbol() returns (string) envfree => DISPATCHER(true) |
||||
decimals() returns (uint8) envfree => DISPATCHER(true) |
||||
totalSupply() returns (uint256) envfree => DISPATCHER(true) |
||||
balanceOf(address) returns (uint256) envfree => DISPATCHER(true) |
||||
allowance(address,address) returns (uint256) envfree => DISPATCHER(true) |
||||
approve(address,uint256) returns (bool) => DISPATCHER(true) |
||||
transfer(address,uint256) returns (bool) => DISPATCHER(true) |
||||
transferFrom(address,address,uint256) returns (bool) => DISPATCHER(true) |
||||
function name() external returns (string) envfree; |
||||
function symbol() external returns (string) envfree; |
||||
function decimals() external returns (uint8) envfree; |
||||
function totalSupply() external returns (uint256) envfree; |
||||
function balanceOf(address) external returns (uint256) envfree; |
||||
function allowance(address,address) external returns (uint256) envfree; |
||||
function approve(address,uint256) external returns (bool); |
||||
function transfer(address,uint256) external returns (bool); |
||||
function transferFrom(address,address,uint256) external returns (bool); |
||||
} |
||||
|
@ -1,5 +1,5 @@ |
||||
methods { |
||||
permit(address,address,uint256,uint256,uint8,bytes32,bytes32) => DISPATCHER(true) |
||||
nonces(address) returns (uint256) envfree => DISPATCHER(true) |
||||
DOMAIN_SEPARATOR() returns (bytes32) envfree => DISPATCHER(true) |
||||
function permit(address,address,uint256,uint256,uint8,bytes32,bytes32) external; |
||||
function nonces(address) external returns (uint256) envfree; |
||||
function DOMAIN_SEPARATOR() external returns (bytes32) envfree; |
||||
} |
||||
|
@ -1,5 +0,0 @@ |
||||
methods { |
||||
maxFlashLoan(address) returns (uint256) envfree => DISPATCHER(true) |
||||
flashFee(address,uint256) returns (uint256) envfree => DISPATCHER(true) |
||||
flashLoan(address,address,uint256,bytes) returns (bool) => DISPATCHER(true) |
||||
} |
@ -0,0 +1,3 @@ |
||||
methods { |
||||
function _.onFlashLoan(address,address,uint256,uint256,bytes) external => DISPATCHER(true); |
||||
} |
@ -0,0 +1,5 @@ |
||||
methods { |
||||
function maxFlashLoan(address) external returns (uint256) envfree; |
||||
function flashFee(address,uint256) external returns (uint256) envfree; |
||||
function flashLoan(address,address,uint256,bytes) external returns (bool); |
||||
} |
@ -1,3 +1,3 @@ |
||||
methods { |
||||
owner() returns (address) envfree |
||||
function owner() external returns (address) envfree; |
||||
} |
||||
|
@ -1,20 +1,17 @@ |
||||
methods { |
||||
// IERC721 |
||||
balanceOf(address) returns (uint256) envfree => DISPATCHER(true) |
||||
ownerOf(uint256) returns (address) envfree => DISPATCHER(true) |
||||
getApproved(uint256) returns (address) envfree => DISPATCHER(true) |
||||
isApprovedForAll(address,address) returns (bool) envfree => DISPATCHER(true) |
||||
safeTransferFrom(address,address,uint256,bytes) => DISPATCHER(true) |
||||
safeTransferFrom(address,address,uint256) => DISPATCHER(true) |
||||
transferFrom(address,address,uint256) => DISPATCHER(true) |
||||
approve(address,uint256) => DISPATCHER(true) |
||||
setApprovalForAll(address,bool) => DISPATCHER(true) |
||||
function balanceOf(address) external returns (uint256) envfree; |
||||
function ownerOf(uint256) external returns (address) envfree; |
||||
function getApproved(uint256) external returns (address) envfree; |
||||
function isApprovedForAll(address,address) external returns (bool) envfree; |
||||
function safeTransferFrom(address,address,uint256,bytes) external; |
||||
function safeTransferFrom(address,address,uint256) external; |
||||
function transferFrom(address,address,uint256) external; |
||||
function approve(address,uint256) external; |
||||
function setApprovalForAll(address,bool) external; |
||||
|
||||
// IERC721Metadata |
||||
name() returns (string) => DISPATCHER(true) |
||||
symbol() returns (string) => DISPATCHER(true) |
||||
tokenURI(uint256) returns (string) => DISPATCHER(true) |
||||
|
||||
// IERC721Receiver |
||||
onERC721Received(address,address,uint256,bytes) returns (bytes4) => DISPATCHER(true) |
||||
function name() external returns (string); |
||||
function symbol() external returns (string); |
||||
function tokenURI(uint256) external returns (string); |
||||
} |
||||
|
@ -0,0 +1,3 @@ |
||||
methods { |
||||
function _.onERC721Received(address,address,uint256,bytes) external => DISPATCHER(true); |
||||
} |
@ -1,5 +1,5 @@ |
||||
methods { |
||||
owner() returns (address) envfree |
||||
transferOwnership(address) |
||||
renounceOwnership() |
||||
function owner() external returns (address) envfree; |
||||
function transferOwnership(address) external; |
||||
function renounceOwnership() external; |
||||
} |
||||
|
@ -1,7 +1,7 @@ |
||||
methods { |
||||
owner() returns (address) envfree |
||||
pendingOwner() returns (address) envfree |
||||
transferOwnership(address) |
||||
acceptOwnership() |
||||
renounceOwnership() |
||||
function owner() external returns (address) envfree; |
||||
function pendingOwner() external returns (address) envfree; |
||||
function transferOwnership(address) external; |
||||
function acceptOwnership() external; |
||||
function renounceOwnership() external; |
||||
} |
||||
|
@ -1 +1 @@ |
||||
certora-cli==4.3.1 |
||||
certora-cli==4.8.0 |
||||
|
Loading…
Reference in new issue