|
|
|
@ -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,7 +25,7 @@ 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; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
@ -56,40 +43,41 @@ function executionsCall(method f, env e, address target, uint256 value, bytes da |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
//////////////////////////////////////////////////////////////////////////// |
|
|
|
|
// Ghosts // |
|
|
|
|
//////////////////////////////////////////////////////////////////////////// |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
//////////////////////////////////////////////////////////////////////////// |
|
|
|
|
// Invariants // |
|
|
|
|
// Invariants // |
|
|
|
|
//////////////////////////////////////////////////////////////////////////// |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// STATUS - verified |
|
|
|
|
// `isOperation()` correctness check |
|
|
|
|
invariant operationCheck(bytes32 id) |
|
|
|
|
getTimestamp(id) > 0 <=> isOperation(id) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
//////////////////////////////////////////////////////////////////////////// |
|
|
|
|
// Rules // |
|
|
|
|
//////////////////////////////////////////////////////////////////////////// |
|
|
|
|
// STATUS - verified |
|
|
|
|
// `isOperationPending()` correctness check |
|
|
|
|
invariant pendingCheck(bytes32 id) |
|
|
|
|
getTimestamp(id) > _DONE_TIMESTAMP() <=> isOperationPending(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; |
|
|
|
|
// STATUS - verified |
|
|
|
|
// `isOperationReady()` correctness check |
|
|
|
|
invariant readyCheck(env e, bytes32 id) |
|
|
|
|
(e.block.timestamp >= getTimestamp(id) && getTimestamp(id) > 1) <=> isOperationReady(e, id) |
|
|
|
|
|
|
|
|
|
require data.length < 7; |
|
|
|
|
// uint256 freshIndex; |
|
|
|
|
// require freshIndex <= data.length |
|
|
|
|
|
|
|
|
|
// require target != targetRand || value != valueRand || data[freshIndex] != dataRand[freshIndex] || predecessor != predecessorRand || salt != saltRand; |
|
|
|
|
// STATUS - verified |
|
|
|
|
// `isOperationDone()` correctness check |
|
|
|
|
invariant doneCheck(bytes32 id) |
|
|
|
|
getTimestamp(id) == _DONE_TIMESTAMP() <=> isOperationDone(id) |
|
|
|
|
|
|
|
|
|
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 +86,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 +178,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 +185,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 +194,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 +209,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 +224,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 +276,86 @@ 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); |
|
|
|
|
schedule(e, target, value, data, predecessor, salt, delay); |
|
|
|
|
|
|
|
|
|
_checkRole@withrevert(e, PROPOSER_ROLE()); |
|
|
|
|
calldataarg args; |
|
|
|
|
f(e, args); |
|
|
|
|
|
|
|
|
|
bool isCheckRoleReverted = lastReverted; |
|
|
|
|
|
|
|
|
|
// schedule@withrevert(e, target, value, data, predecessor, salt, delay); |
|
|
|
|
scheduleBatch@withrevert(e, targets, values, datas, predecessor, salt, delay); |
|
|
|
|
assert isOperationReady(e2, id) => (e2.block.timestamp - e.block.timestamp >= minDelay), "No rush! When I'm ready, I'm ready"; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
bool isScheduleReverted = lastReverted; |
|
|
|
|
|
|
|
|
|
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 - 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); |
|
|
|
|
|
|
|
|
|
hashIdCorrelation(id, target, value, data, predecessor, salt); |
|
|
|
|
|
|
|
|
|
execute(e, target, value, data, predecessor, salt); |
|
|
|
|
|
|
|
|
|
assert id != otherId => otherIdTimestampBefore == getTimestamp(otherId), "Master of puppets, I'm pulling your strings"; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 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"; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// STATUS - in progress |
|
|
|
|
// Ready = has waited minimum period after pending |
|
|
|
|
rule cooldown(method f, env e, env e2){ |
|
|
|
|
// 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 unset(id); |
|
|
|
|
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); |
|
|
|
|
|
|
|
|
|
calldataarg args; |
|
|
|
|
f(e, args); |
|
|
|
|
hashIdCorrelation(id, target, value, data, predecessor, salt); |
|
|
|
|
|
|
|
|
|
executionsCall(f, e, target, value, data, predecessor, salt, delay, targets, values, datas); |
|
|
|
|
|
|
|
|
|
// 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"; |
|
|
|
|
} |
|
|
|
|
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?"; |
|
|
|
|
} |
|
|
|
|