|
@@ -5,7 +5,11 @@ methods {
|
|
|
_minDelay() returns(uint256) envfree
|
|
|
getMinDelay() returns(uint256) envfree
|
|
|
hashOperation(address target, uint256 value, bytes data, bytes32 predecessor, bytes32 salt) returns(bytes32) envfree
|
|
|
-
|
|
|
+ isOperation(bytes32) returns(bool) envfree
|
|
|
+ isOperationPending(bytes32) returns(bool) envfree
|
|
|
+ isOperationDone(bytes32) returns(bool) envfree
|
|
|
+
|
|
|
+ isOperationReady(bytes32) returns(bool)
|
|
|
cancel(bytes32)
|
|
|
schedule(address, uint256, bytes32, bytes32, bytes32, uint256)
|
|
|
execute(address, uint256, bytes, bytes32, bytes32)
|
|
@@ -13,23 +17,6 @@ methods {
|
|
|
_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();
|
|
|
-
|
|
|
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////
|
|
@@ -38,58 +25,45 @@ definition done(bytes32 id) returns bool =
|
|
|
|
|
|
|
|
|
function hashIdCorrelation(bytes32 id, address target, uint256 value, bytes data, bytes32 predecessor, bytes32 salt){
|
|
|
- require data.length < 7;
|
|
|
+ require data.length < 32;
|
|
|
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 //
|
|
|
////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
|
|
|
-
|
|
|
-////////////////////////////////////////////////////////////////////////////
|
|
|
-// Invariants //
|
|
|
-////////////////////////////////////////////////////////////////////////////
|
|
|
+// STATUS - verified
|
|
|
+// `isOperation()` correctness check
|
|
|
+invariant operationCheck(bytes32 id)
|
|
|
+ getTimestamp(id) > 0 <=> isOperation(id)
|
|
|
|
|
|
|
|
|
+// STATUS - verified
|
|
|
+// `isOperationPending()` correctness check
|
|
|
+invariant pendingCheck(bytes32 id)
|
|
|
+ getTimestamp(id) > _DONE_TIMESTAMP() <=> isOperationPending(id)
|
|
|
|
|
|
-////////////////////////////////////////////////////////////////////////////
|
|
|
-// Rules //
|
|
|
-////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
+// STATUS - verified
|
|
|
+// `isOperationReady()` correctness check
|
|
|
+invariant readyCheck(env e, bytes32 id)
|
|
|
+ (e.block.timestamp >= getTimestamp(id) && getTimestamp(id) > 1) <=> isOperationReady(e, id)
|
|
|
|
|
|
-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
|
|
|
+// STATUS - verified
|
|
|
+// `isOperationDone()` correctness check
|
|
|
+invariant doneCheck(bytes32 id)
|
|
|
+ getTimestamp(id) == _DONE_TIMESTAMP() <=> isOperationDone(id)
|
|
|
|
|
|
- // 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";
|
|
|
-}
|
|
|
+////////////////////////////////////////////////////////////////////////////
|
|
|
+// Rules //
|
|
|
+////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
|
|
|
/////////////////////////////////////////////////////////////
|
|
@@ -98,77 +72,75 @@ rule keccakCheck(method f, env e){
|
|
|
|
|
|
|
|
|
// STATUS - verified
|
|
|
-// unset() -> unset() || pending() only
|
|
|
+// Possible transitions: form `!isOperation()` to `!isOperation()` or `isOperationPending()` only
|
|
|
rule unsetPendingTransitionGeneral(method f, env e){
|
|
|
bytes32 id;
|
|
|
|
|
|
- require unset(id);
|
|
|
+ require !isOperation(id);
|
|
|
require e.block.timestamp > 1;
|
|
|
|
|
|
calldataarg args;
|
|
|
f(e, args);
|
|
|
|
|
|
- assert pending(id) || unset(id);
|
|
|
+ assert isOperationPending(id) || !isOperation(id);
|
|
|
}
|
|
|
|
|
|
|
|
|
// STATUS - verified
|
|
|
-// unset() -> pending() via schedule() and scheduleBatch() only
|
|
|
+// Possible transitions: form `!isOperation()` to `isOperationPending()` via `schedule()` and `scheduleBatch()` only
|
|
|
rule unsetPendingTransitionMethods(method f, env e){
|
|
|
bytes32 id;
|
|
|
|
|
|
- require unset(id);
|
|
|
+ require !isOperation(id);
|
|
|
|
|
|
calldataarg args;
|
|
|
f(e, args);
|
|
|
|
|
|
- bool tmp = pending(id);
|
|
|
-
|
|
|
- assert pending(id) => (f.selector == schedule(address, uint256, bytes, bytes32, bytes32, uint256).selector
|
|
|
+ assert isOperationPending(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
|
|
|
+// Possible transitions: form `ready()` to `isOperationDone()` via `execute()` and `executeBatch()` only
|
|
|
rule readyDoneTransition(method f, env e){
|
|
|
bytes32 id;
|
|
|
|
|
|
- require ready(id, e);
|
|
|
+ require isOperationReady(e, id);
|
|
|
|
|
|
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!";
|
|
|
+ assert isOperationDone(id) => f.selector == execute(address, uint256, bytes, bytes32, bytes32).selector
|
|
|
+ || f.selector == executeBatch(address[], uint256[], bytes[], bytes32, bytes32).selector , "It's not isOperationDone yet!";
|
|
|
}
|
|
|
|
|
|
|
|
|
// STATUS - verified
|
|
|
-// pending() -> cancelled() via cancel() only
|
|
|
+// isOperationPending() -> cancelled() via cancel() only
|
|
|
rule pendingCancelledTransition(method f, env e){
|
|
|
bytes32 id;
|
|
|
|
|
|
- require pending(id);
|
|
|
+ require isOperationPending(id);
|
|
|
|
|
|
calldataarg args;
|
|
|
f(e, args);
|
|
|
|
|
|
- assert unset(id) => f.selector == cancel(bytes32).selector, "How you dare to cancel me?";
|
|
|
+ assert !isOperation(id) => f.selector == cancel(bytes32).selector, "How you dare to cancel me?";
|
|
|
}
|
|
|
|
|
|
|
|
|
// STATUS - verified
|
|
|
-// done() -> nowhere
|
|
|
+// isOperationDone() -> nowhere
|
|
|
rule doneToNothingTransition(method f, env e){
|
|
|
bytes32 id;
|
|
|
|
|
|
- require done(id);
|
|
|
+ require isOperationDone(id);
|
|
|
|
|
|
calldataarg args;
|
|
|
f(e, args);
|
|
|
|
|
|
- assert done(id), "Did you find a way to escape? There is no way! HA-HA-HA";
|
|
|
+ assert isOperationDone(id), "Did you find a way to escape? There is no way! HA-HA-HA";
|
|
|
}
|
|
|
|
|
|
|
|
@@ -192,26 +164,6 @@ rule minDelayOnlyChange(method f, env e){
|
|
|
}
|
|
|
|
|
|
|
|
|
-// 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){
|
|
@@ -219,7 +171,6 @@ rule scheduleCheck(method f, env e){
|
|
|
|
|
|
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);
|
|
@@ -229,13 +180,13 @@ rule scheduleCheck(method f, env e){
|
|
|
|
|
|
|
|
|
// STATUS - verified
|
|
|
-// Cannot call execute on a pending (not ready) operation
|
|
|
+// Cannot call `execute()` on a isOperationPending (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);
|
|
|
+ require isOperationPending(id) && !isOperationReady(e, id);
|
|
|
|
|
|
execute@withrevert(e, target, value, data, predecessor, salt);
|
|
|
|
|
@@ -244,13 +195,13 @@ rule cannotCallExecute(method f, env e){
|
|
|
|
|
|
|
|
|
// STATUS - verified
|
|
|
-// in unset() execute() reverts
|
|
|
+// Cannot call `execute()` on a !isOperation operation
|
|
|
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);
|
|
|
+ require !isOperation(id);
|
|
|
|
|
|
execute@withrevert(e, target, value, data, predecessor, salt);
|
|
|
|
|
@@ -259,51 +210,47 @@ rule executeRevertsFromUnset(method f, env e, env e2){
|
|
|
|
|
|
|
|
|
// STATUS - verified
|
|
|
-// Execute reverts => state returns to pending
|
|
|
+// Execute reverts => state returns to isOperationPending
|
|
|
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);
|
|
|
+ require isOperationPending(id) && !isOperationReady(e, id);
|
|
|
|
|
|
execute@withrevert(e, target, value, data, predecessor, salt);
|
|
|
bool reverted = lastReverted;
|
|
|
|
|
|
- assert lastReverted => pending(id) && !ready(id, e), "you go against execution nature";
|
|
|
+ assert lastReverted => isOperationPending(id) && !isOperationReady(e, id), "you go against execution nature";
|
|
|
}
|
|
|
|
|
|
|
|
|
// STATUS - verified
|
|
|
-// Canceled operations cannot be executed → can’t move from canceled to ready
|
|
|
+// Canceled operations cannot be executed → can’t move from canceled to isOperationDone
|
|
|
rule cancelledNotExecuted(method f, env e){
|
|
|
bytes32 id;
|
|
|
|
|
|
- require unset(id);
|
|
|
+ require !isOperation(id);
|
|
|
require e.block.timestamp > 1;
|
|
|
|
|
|
calldataarg args;
|
|
|
f(e, args);
|
|
|
|
|
|
- assert !done(id), "The ship is not a creature of the air";
|
|
|
+ assert !isOperationDone(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
|
|
|
+// STATUS - verified
|
|
|
+// Only proposers can schedule
|
|
|
+rule onlyProposer(method f, env e) filtered { f -> f.selector == schedule(address, uint256, bytes, 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);
|
|
@@ -315,38 +262,62 @@ rule onlyProposerCertorafallbackFail(method f, env e) filtered { f -> f.selector
|
|
|
|
|
|
|
|
|
// STATUS - verified
|
|
|
-// Only proposers can schedule an operation
|
|
|
-rule onlyProposer1(method f, env e){
|
|
|
+// if `ready` then has waited minimum period after isOperationPending()
|
|
|
+rule cooldown(method f, env e, env e2){
|
|
|
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;
|
|
|
+ address target; uint256 value; bytes data ;bytes32 predecessor; bytes32 salt; uint256 delay;
|
|
|
+ uint256 minDelay = getMinDelay();
|
|
|
|
|
|
- // hashIdCorrelation(id, target, value, data, predecessor, salt);
|
|
|
+ hashIdCorrelation(id, target, value, data, predecessor, salt);
|
|
|
|
|
|
- _checkRole@withrevert(e, PROPOSER_ROLE());
|
|
|
+ schedule(e, target, value, data, predecessor, salt, delay);
|
|
|
|
|
|
- bool isCheckRoleReverted = lastReverted;
|
|
|
-
|
|
|
- // schedule@withrevert(e, target, value, data, predecessor, salt, delay);
|
|
|
- scheduleBatch@withrevert(e, targets, values, datas, predecessor, salt, delay);
|
|
|
+ calldataarg args;
|
|
|
+ f(e, args);
|
|
|
|
|
|
- bool isScheduleReverted = lastReverted;
|
|
|
+ assert isOperationReady(e2, id) => (e2.block.timestamp - e.block.timestamp >= minDelay), "No rush! When I'm ready, I'm ready";
|
|
|
+}
|
|
|
|
|
|
- assert isCheckRoleReverted => isScheduleReverted, "Enemy was detected";
|
|
|
+
|
|
|
+// STATUS - verified
|
|
|
+// `schedule()` should change only one id's timestamp
|
|
|
+rule scheduleChange(env e){
|
|
|
+ bytes32 id; bytes32 otherId;
|
|
|
+ address target; uint256 value; bytes data ;bytes32 predecessor; bytes32 salt; uint256 delay;
|
|
|
+
|
|
|
+ uint256 otherIdTimestampBefore = getTimestamp(otherId);
|
|
|
+
|
|
|
+ hashIdCorrelation(id, target, value, data, predecessor, salt);
|
|
|
+
|
|
|
+ schedule(e, target, value, data, predecessor, salt, delay);
|
|
|
+
|
|
|
+ assert id != otherId => otherIdTimestampBefore == getTimestamp(otherId), "Master of puppets, I'm pulling your strings";
|
|
|
}
|
|
|
|
|
|
|
|
|
-// STATUS - in progress
|
|
|
-// Ready = has waited minimum period after pending
|
|
|
-rule cooldown(method f, env e, env e2){
|
|
|
- bytes32 id;
|
|
|
+// STATUS - verified
|
|
|
+// `execute()` should change only one id's timestamp
|
|
|
+rule executeChange(env e){
|
|
|
+ bytes32 id; bytes32 otherId;
|
|
|
+ address target; uint256 value; bytes data ;bytes32 predecessor; bytes32 salt;
|
|
|
+ uint256 otherIdTimestampBefore = getTimestamp(otherId);
|
|
|
|
|
|
- require unset(id);
|
|
|
+ hashIdCorrelation(id, target, value, data, predecessor, salt);
|
|
|
|
|
|
- calldataarg args;
|
|
|
- f(e, args);
|
|
|
+ execute(e, target, value, data, predecessor, salt);
|
|
|
+
|
|
|
+ assert id != otherId => otherIdTimestampBefore == getTimestamp(otherId), "Master of puppets, I'm pulling your strings";
|
|
|
+}
|
|
|
|
|
|
- // 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";
|
|
|
-}
|
|
|
+
|
|
|
+// STATUS - verified
|
|
|
+// `cancel()` should change only one id's timestamp
|
|
|
+rule cancelChange(env e){
|
|
|
+ bytes32 id; bytes32 otherId;
|
|
|
+
|
|
|
+ uint256 otherIdTimestampBefore = getTimestamp(otherId);
|
|
|
+
|
|
|
+ cancel(e, id);
|
|
|
+
|
|
|
+ assert id != otherId => otherIdTimestampBefore == getTimestamp(otherId), "Master of puppets, I'm pulling your strings";
|
|
|
+}
|