Add FV specs for Ownable and Ownable2Steps (#4094)
Co-authored-by: Santiago Palladino <spalladino@gmail.com> Co-authored-by: Francisco <fg@frang.io>pull/4101/head
parent
4fb6833e32
commit
aaad1f4a4f
@ -0,0 +1,9 @@ |
||||
// SPDX-License-Identifier: MIT |
||||
|
||||
pragma solidity ^0.8.0; |
||||
|
||||
import "../patched/access/Ownable2Step.sol"; |
||||
|
||||
contract Ownable2StepHarness is Ownable2Step { |
||||
function restricted() external onlyOwner {} |
||||
} |
@ -0,0 +1,9 @@ |
||||
// SPDX-License-Identifier: MIT |
||||
|
||||
pragma solidity ^0.8.0; |
||||
|
||||
import "../patched/access/Ownable.sol"; |
||||
|
||||
contract OwnableHarness is Ownable { |
||||
function restricted() external onlyOwner {} |
||||
} |
@ -0,0 +1,78 @@ |
||||
import "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, method f) { |
||||
address oldCurrent = owner(); |
||||
|
||||
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) |
||||
); |
||||
} |
@ -0,0 +1,108 @@ |
||||
import "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) |
||||
); |
||||
} |
@ -0,0 +1,5 @@ |
||||
methods { |
||||
owner() returns (address) envfree |
||||
transferOwnership(address) |
||||
renounceOwnership() |
||||
} |
@ -0,0 +1,7 @@ |
||||
methods { |
||||
owner() returns (address) envfree |
||||
pendingOwner() returns (address) envfree |
||||
transferOwnership(address) |
||||
acceptOwnership() |
||||
renounceOwnership() |
||||
} |
Loading…
Reference in new issue