Add FV specs for Ownable and Ownable2Steps (#4094)
Co-authored-by: Santiago Palladino <spalladino@gmail.com> Co-authored-by: Francisco <fg@frang.io>
This commit is contained in:
9
certora/harnesses/Ownable2StepHarness.sol
Normal file
9
certora/harnesses/Ownable2StepHarness.sol
Normal file
@ -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 {}
|
||||||
|
}
|
||||||
9
certora/harnesses/OwnableHarness.sol
Normal file
9
certora/harnesses/OwnableHarness.sol
Normal file
@ -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 {}
|
||||||
|
}
|
||||||
@ -64,7 +64,7 @@ async function runCertora(spec, contract, files, options = []) {
|
|||||||
stream.end();
|
stream.end();
|
||||||
|
|
||||||
// write results in markdown format
|
// write results in markdown format
|
||||||
writeEntry(spec, contract, code || signal, (await output).match(/https:\S*/)[0]);
|
writeEntry(spec, contract, code || signal, (await output).match(/https:\S*/)?.[0]);
|
||||||
|
|
||||||
// write all details
|
// write all details
|
||||||
console.error(`+ certoraRun ${args.join(' ')}\n` + (await output));
|
console.error(`+ certoraRun ${args.join(' ')}\n` + (await output));
|
||||||
@ -103,7 +103,7 @@ function writeEntry(spec, contract, success, url) {
|
|||||||
contract,
|
contract,
|
||||||
success ? ':x:' : ':heavy_check_mark:',
|
success ? ':x:' : ':heavy_check_mark:',
|
||||||
`[link](${url})`,
|
`[link](${url})`,
|
||||||
`[link](${url.replace('/jobStatus/', '/output/')})`,
|
`[link](${url?.replace('/jobStatus/', '/output/')})`,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,6 +4,16 @@
|
|||||||
"contract": "AccessControlHarness",
|
"contract": "AccessControlHarness",
|
||||||
"files": ["certora/harnesses/AccessControlHarness.sol"]
|
"files": ["certora/harnesses/AccessControlHarness.sol"]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"spec": "Ownable",
|
||||||
|
"contract": "OwnableHarness",
|
||||||
|
"files": ["certora/harnesses/OwnableHarness.sol"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"spec": "Ownable2Step",
|
||||||
|
"contract": "Ownable2StepHarness",
|
||||||
|
"files": ["certora/harnesses/Ownable2StepHarness.sol"]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"spec": "ERC20",
|
"spec": "ERC20",
|
||||||
"contract": "ERC20PermitHarness",
|
"contract": "ERC20PermitHarness",
|
||||||
|
|||||||
78
certora/specs/Ownable.spec
Normal file
78
certora/specs/Ownable.spec
Normal file
@ -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)
|
||||||
|
);
|
||||||
|
}
|
||||||
108
certora/specs/Ownable2Step.spec
Normal file
108
certora/specs/Ownable2Step.spec
Normal file
@ -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)
|
||||||
|
);
|
||||||
|
}
|
||||||
5
certora/specs/methods/IOwnable.spec
Normal file
5
certora/specs/methods/IOwnable.spec
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
methods {
|
||||||
|
owner() returns (address) envfree
|
||||||
|
transferOwnership(address)
|
||||||
|
renounceOwnership()
|
||||||
|
}
|
||||||
7
certora/specs/methods/IOwnable2Step.spec
Normal file
7
certora/specs/methods/IOwnable2Step.spec
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
methods {
|
||||||
|
owner() returns (address) envfree
|
||||||
|
pendingOwner() returns (address) envfree
|
||||||
|
transferOwnership(address)
|
||||||
|
acceptOwnership()
|
||||||
|
renounceOwnership()
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user