Files
openzeppelin-contracts/certora/specs/TimelockController.spec
Aleksander Kryukov 479118fcd1 push to report issues
2022-04-04 21:33:31 +01:00

352 lines
11 KiB
Ruby
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

methods {
getTimestamp(bytes32) returns(uint256) envfree
_DONE_TIMESTAMP() returns(uint256) envfree
PROPOSER_ROLE() returns(bytes32) envfree
_minDelay() returns(uint256) envfree
getMinDelay() returns(uint256) envfree
hashOperation(address target, uint256 value, bytes data, bytes32 predecessor, bytes32 salt) returns(bytes32) envfree
cancel(bytes32)
schedule(address, uint256, bytes32, bytes32, bytes32, uint256)
execute(address, uint256, bytes, bytes32, bytes32)
executeBatch(address[], uint256[], bytes[], bytes32, bytes32)
_checkRole(bytes32) => DISPATCHER(true)
}
////////////////////////////////////////////////////////////////////////////
// Definitions //
////////////////////////////////////////////////////////////////////////////
definition unset(bytes32 id) returns bool =
getTimestamp(id) == 0;
definition pending(bytes32 id) returns bool =
getTimestamp(id) > _DONE_TIMESTAMP();
definition ready(bytes32 id, env e) returns bool =
getTimestamp(id) > _DONE_TIMESTAMP() && getTimestamp(id) <= e.block.timestamp;
definition done(bytes32 id) returns bool =
getTimestamp(id) == _DONE_TIMESTAMP();
////////////////////////////////////////////////////////////////////////////
// Functions //
////////////////////////////////////////////////////////////////////////////
function hashIdCorrelation(bytes32 id, address target, uint256 value, bytes data, bytes32 predecessor, bytes32 salt){
require data.length < 7;
require hashOperation(target, value, data, predecessor, salt) == id;
}
function executionsCall(method f, env e, address target, uint256 value, bytes data,
bytes32 predecessor, bytes32 salt, uint256 delay,
address[] targets, uint256[] values, bytes[] datas) {
if (f.selector == execute(address, uint256, bytes, bytes32, bytes32).selector) {
execute(e, target, value, data, predecessor, salt);
} else if (f.selector == executeBatch(address[], uint256[], bytes[], bytes32, bytes32).selector) {
executeBatch(e, targets, values, datas, predecessor, salt);
} else {
calldataarg args;
f(e, args);
}
}
////////////////////////////////////////////////////////////////////////////
// Ghosts //
////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////
// Invariants //
////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////
// Rules //
////////////////////////////////////////////////////////////////////////////
rule keccakCheck(method f, env e){
address target; uint256 value; bytes data; bytes32 predecessor; bytes32 salt;
address targetRand; uint256 valueRand; bytes dataRand; bytes32 predecessorRand; bytes32 saltRand;
require data.length < 7;
// uint256 freshIndex;
// require freshIndex <= data.length
// require target != targetRand || value != valueRand || data[freshIndex] != dataRand[freshIndex] || predecessor != predecessorRand || salt != saltRand;
bytes32 a = hashOperation(target, value, data, predecessor, salt);
bytes32 b = hashOperation(target, value, data, predecessor, salt);
// bytes32 c = hashOperation(targetRand, valueRand, dataRand, predecessorRand, saltRand);
assert a == b, "hashes are different";
// assert a != c, "hashes are the same";
}
/////////////////////////////////////////////////////////////
// STATE TRANSITIONS
/////////////////////////////////////////////////////////////
// STATUS - verified
// unset() -> unset() || pending() only
rule unsetPendingTransitionGeneral(method f, env e){
bytes32 id;
require unset(id);
require e.block.timestamp > 1;
calldataarg args;
f(e, args);
assert pending(id) || unset(id);
}
// STATUS - verified
// unset() -> pending() via schedule() and scheduleBatch() only
rule unsetPendingTransitionMethods(method f, env e){
bytes32 id;
require unset(id);
calldataarg args;
f(e, args);
bool tmp = pending(id);
assert pending(id) => (f.selector == schedule(address, uint256, bytes, bytes32, bytes32, uint256).selector
|| f.selector == scheduleBatch(address[], uint256[], bytes[], bytes32, bytes32, uint256).selector), "Why do we need to follow the schedule?";
}
// STATUS - verified
// ready() -> done() via execute() and executeBatch() only
rule readyDoneTransition(method f, env e){
bytes32 id;
require ready(id, e);
calldataarg args;
f(e, args);
assert done(id) => f.selector == execute(address, uint256, bytes, bytes32, bytes32).selector
|| f.selector == executeBatch(address[], uint256[], bytes[], bytes32, bytes32).selector , "It's not done yet!";
}
// STATUS - verified
// pending() -> cancelled() via cancel() only
rule pendingCancelledTransition(method f, env e){
bytes32 id;
require pending(id);
calldataarg args;
f(e, args);
assert unset(id) => f.selector == cancel(bytes32).selector, "How you dare to cancel me?";
}
// STATUS - verified
// done() -> nowhere
rule doneToNothingTransition(method f, env e){
bytes32 id;
require done(id);
calldataarg args;
f(e, args);
assert done(id), "Did you find a way to escape? There is no way! HA-HA-HA";
}
/////////////////////////////////////////////////////////////
// THE REST
/////////////////////////////////////////////////////////////
// STATUS - verified
// only TimelockController contract can change minDelay
rule minDelayOnlyChange(method f, env e){
uint256 delayBefore = _minDelay();
calldataarg args;
f(e, args);
uint256 delayAfter = _minDelay();
assert delayBefore != delayAfter => e.msg.sender == currentContract, "You cannot change your destiny! Only I can!";
}
// STATUS - in progress
// execute() is the only way to set timestamp to 1
rule getTimestampOnlyChange(method f, env e){
bytes32 id;
address target; uint256 value; bytes data; bytes32 predecessor; bytes32 salt; uint256 delay;
address[] targets; uint256[] values; bytes[] datas;
require (targets[0] == target && values[0] == value && datas[0] == data)
|| (targets[1] == target && values[1] == value && datas[1] == data)
|| (targets[2] == target && values[2] == value && datas[2] == data);
hashIdCorrelation(id, target, value, data, predecessor, salt);
executionsCall(f, e, target, value, data, predecessor, salt, delay, targets, values, datas);
assert getTimestamp(id) == 1 => f.selector == execute(address, uint256, bytes, bytes32, bytes32).selector
|| f.selector == executeBatch(address[], uint256[], bytes[], bytes32, bytes32).selector, "Did you find a way to break the system?";
}
// STATUS - verified
// scheduled operation timestamp == block.timestamp + delay (kind of unit test)
rule scheduleCheck(method f, env e){
bytes32 id;
address target; uint256 value; bytes data ;bytes32 predecessor; bytes32 salt; uint256 delay;
require getTimestamp(id) < e.block.timestamp;
hashIdCorrelation(id, target, value, data, predecessor, salt);
schedule(e, target, value, data, predecessor, salt, delay);
assert getTimestamp(id) == to_uint256(e.block.timestamp + delay), "Time doesn't obey to mortal souls";
}
// STATUS - verified
// Cannot call execute on a pending (not ready) operation
rule cannotCallExecute(method f, env e){
address target; uint256 value; bytes data; bytes32 predecessor; bytes32 salt;
bytes32 id;
hashIdCorrelation(id, target, value, data, predecessor, salt);
require pending(id) && !ready(id, e);
execute@withrevert(e, target, value, data, predecessor, salt);
assert lastReverted, "you go against execution nature";
}
// STATUS - verified
// in unset() execute() reverts
rule executeRevertsFromUnset(method f, env e, env e2){
address target; uint256 value; bytes data; bytes32 predecessor; bytes32 salt;
bytes32 id;
hashIdCorrelation(id, target, value, data, predecessor, salt);
require unset(id);
execute@withrevert(e, target, value, data, predecessor, salt);
assert lastReverted, "you go against execution nature";
}
// STATUS - verified
// Execute reverts => state returns to pending
rule executeRevertsEffectCheck(method f, env e){
address target; uint256 value; bytes data; bytes32 predecessor; bytes32 salt;
bytes32 id;
hashIdCorrelation(id, target, value, data, predecessor, salt);
require pending(id) && !ready(id, e);
execute@withrevert(e, target, value, data, predecessor, salt);
bool reverted = lastReverted;
assert lastReverted => pending(id) && !ready(id, e), "you go against execution nature";
}
// STATUS - verified
// Canceled operations cannot be executed cant move from canceled to ready
rule cancelledNotExecuted(method f, env e){
bytes32 id;
require unset(id);
require e.block.timestamp > 1;
calldataarg args;
f(e, args);
assert !done(id), "The ship is not a creature of the air";
}
// STATUS - broken
// Only proposers can schedule an operation
rule onlyProposerCertorafallbackFail(method f, env e) filtered { f -> f.selector == schedule(address, uint256, bytes32, bytes32, bytes32, uint256).selector
|| f.selector == scheduleBatch(address[], uint256[], bytes[], bytes32, bytes32, uint256).selector } {
bytes32 id;
bytes32 role;
address target; uint256 value; bytes data ;bytes32 predecessor; bytes32 salt; uint256 delay;
// hashIdCorrelation(id, target, value, data, predecessor, salt);
_checkRole@withrevert(e, PROPOSER_ROLE());
bool isCheckRoleReverted = lastReverted;
// schedule@withrevert(e, target, value, data, predecessor, salt, delay);
calldataarg args;
f@withrevert(e, args);
bool isScheduleReverted = lastReverted;
assert isCheckRoleReverted => isScheduleReverted, "Enemy was detected";
}
// STATUS - verified
// Only proposers can schedule an operation
rule onlyProposer1(method f, env e){
bytes32 id;
bytes32 role;
// address target; uint256 value; bytes data ;bytes32 predecessor; bytes32 salt; uint256 delay;
address[] targets; uint256[] values; bytes[] datas; bytes32 predecessor; bytes32 salt; uint256 delay;
// hashIdCorrelation(id, target, value, data, predecessor, salt);
_checkRole@withrevert(e, PROPOSER_ROLE());
bool isCheckRoleReverted = lastReverted;
// schedule@withrevert(e, target, value, data, predecessor, salt, delay);
scheduleBatch@withrevert(e, targets, values, datas, predecessor, salt, delay);
bool isScheduleReverted = lastReverted;
assert isCheckRoleReverted => isScheduleReverted, "Enemy was detected";
}
// STATUS - in progress
// Ready = has waited minimum period after pending
rule cooldown(method f, env e, env e2){
bytes32 id;
require unset(id);
calldataarg args;
f(e, args);
// e.block.timestamp - delay > time scheduled => ready()
assert e.block.timestamp >= getTimestamp(id) => ready(id, e), "No rush! When I'm ready, I'm ready";
}