123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275 |
- import "helpers.spec"
- import "methods/IAccessControl.spec"
- methods {
- TIMELOCK_ADMIN_ROLE() returns (bytes32) envfree
- PROPOSER_ROLE() returns (bytes32) envfree
- EXECUTOR_ROLE() returns (bytes32) envfree
- CANCELLER_ROLE() returns (bytes32) envfree
- isOperation(bytes32) returns (bool) envfree
- isOperationPending(bytes32) returns (bool) envfree
- isOperationReady(bytes32) returns (bool)
- isOperationDone(bytes32) returns (bool) envfree
- getTimestamp(bytes32) returns (uint256) envfree
- getMinDelay() returns (uint256) envfree
- hashOperation(address, uint256, bytes, bytes32, bytes32) returns(bytes32) envfree
- hashOperationBatch(address[], uint256[], bytes[], bytes32, bytes32) returns(bytes32) envfree
- schedule(address, uint256, bytes, bytes32, bytes32, uint256)
- scheduleBatch(address[], uint256[], bytes[], bytes32, bytes32, uint256)
- execute(address, uint256, bytes, bytes32, bytes32)
- executeBatch(address[], uint256[], bytes[], bytes32, bytes32)
- cancel(bytes32)
- updateDelay(uint256)
- }
- /*
- โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
- โ Helpers โ
- โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
- */
- // Uniformly handle scheduling of batched and non-batched operations.
- function helperScheduleWithRevert(env e, method f, bytes32 id, uint256 delay) {
- if (f.selector == schedule(address, uint256, bytes, bytes32, bytes32, uint256).selector) {
- address target; uint256 value; bytes data; bytes32 predecessor; bytes32 salt;
- require hashOperation(target, value, data, predecessor, salt) == id; // Correlation
- schedule@withrevert(e, target, value, data, predecessor, salt, delay);
- } else if (f.selector == scheduleBatch(address[], uint256[], bytes[], bytes32, bytes32, uint256).selector) {
- address[] targets; uint256[] values; bytes[] payloads; bytes32 predecessor; bytes32 salt;
- require hashOperationBatch(targets, values, payloads, predecessor, salt) == id; // Correlation
- scheduleBatch@withrevert(e, targets, values, payloads, predecessor, salt, delay);
- } else {
- calldataarg args;
- f@withrevert(e, args);
- }
- }
- // Uniformly handle execution of batched and non-batched operations.
- function helperExecuteWithRevert(env e, method f, bytes32 id, bytes32 predecessor) {
- if (f.selector == execute(address, uint256, bytes, bytes32, bytes32).selector) {
- address target; uint256 value; bytes data; bytes32 salt;
- require hashOperation(target, value, data, predecessor, salt) == id; // Correlation
- execute@withrevert(e, target, value, data, predecessor, salt);
- } else if (f.selector == executeBatch(address[], uint256[], bytes[], bytes32, bytes32).selector) {
- address[] targets; uint256[] values; bytes[] payloads; bytes32 salt;
- require hashOperationBatch(targets, values, payloads, predecessor, salt) == id; // Correlation
- executeBatch@withrevert(e, targets, values, payloads, predecessor, salt);
- } else {
- calldataarg args;
- f@withrevert(e, args);
- }
- }
- /*
- โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
- โ Definitions โ
- โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
- */
- definition DONE_TIMESTAMP() returns uint256 = 1;
- definition UNSET() returns uint8 = 0x1;
- definition PENDING() returns uint8 = 0x2;
- definition DONE() returns uint8 = 0x4;
- definition isUnset(bytes32 id) returns bool = !isOperation(id);
- definition isPending(bytes32 id) returns bool = isOperationPending(id);
- definition isDone(bytes32 id) returns bool = isOperationDone(id);
- definition state(bytes32 id) returns uint8 = (isUnset(id) ? UNSET() : 0) | (isPending(id) ? PENDING() : 0) | (isDone(id) ? DONE() : 0);
- /*
- โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
- โ Invariants: consistency of accessors โ
- โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
- */
- invariant isOperationCheck(bytes32 id)
- isOperation(id) <=> getTimestamp(id) > 0
- filtered { f -> !f.isView }
- invariant isOperationPendingCheck(bytes32 id)
- isOperationPending(id) <=> getTimestamp(id) > DONE_TIMESTAMP()
- filtered { f -> !f.isView }
- invariant isOperationDoneCheck(bytes32 id)
- isOperationDone(id) <=> getTimestamp(id) == DONE_TIMESTAMP()
- filtered { f -> !f.isView }
- invariant isOperationReadyCheck(env e, bytes32 id)
- isOperationReady(e, id) <=> (isOperationPending(id) && getTimestamp(id) <= e.block.timestamp)
- filtered { f -> !f.isView }
- /*
- โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
- โ Invariant: a proposal id is either unset, pending or done โ
- โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
- */
- invariant stateConsistency(bytes32 id, env e)
- // Check states are mutually exclusive
- (isUnset(id) <=> (!isPending(id) && !isDone(id) )) &&
- (isPending(id) <=> (!isUnset(id) && !isDone(id) )) &&
- (isDone(id) <=> (!isUnset(id) && !isPending(id))) &&
- // Check that the state helper behaves as expected:
- (isUnset(id) <=> state(id) == UNSET() ) &&
- (isPending(id) <=> state(id) == PENDING() ) &&
- (isDone(id) <=> state(id) == DONE() ) &&
- // Check substate
- isOperationReady(e, id) => isPending(id)
- filtered { f -> !f.isView }
- /*
- โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
- โ Rule: state transition rules โ
- โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
- */
- rule stateTransition(bytes32 id, env e, method f, calldataarg args) {
- require e.block.timestamp > 1; // Sanity
- uint8 stateBefore = state(id);
- f(e, args);
- uint8 stateAfter = state(id);
- // Cannot jump from UNSET to DONE
- assert stateBefore == UNSET() => stateAfter != DONE();
- // UNSET โ PENDING: schedule or scheduleBatch
- assert stateBefore == UNSET() && stateAfter == PENDING() => (
- f.selector == schedule(address, uint256, bytes, bytes32, bytes32, uint256).selector ||
- f.selector == scheduleBatch(address[], uint256[], bytes[], bytes32, bytes32, uint256).selector
- );
- // PENDING โ UNSET: cancel
- assert stateBefore == PENDING() && stateAfter == UNSET() => (
- f.selector == cancel(bytes32).selector
- );
- // PENDING โ DONE: execute or executeBatch
- assert stateBefore == PENDING() && stateAfter == DONE() => (
- f.selector == execute(address, uint256, bytes, bytes32, bytes32).selector ||
- f.selector == executeBatch(address[], uint256[], bytes[], bytes32, bytes32).selector
- );
- // DONE is final
- assert stateBefore == DONE() => stateAfter == DONE();
- }
- /*
- โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
- โ Rule: minimum delay can only be updated through a timelock execution โ
- โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
- */
- rule minDelayOnlyChange(env e) {
- uint256 delayBefore = getMinDelay();
- method f; calldataarg args;
- f(e, args);
- assert delayBefore != getMinDelay() => (e.msg.sender == currentContract && f.selector == updateDelay(uint256).selector), "Unauthorized delay update";
- }
- /*
- โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
- โ Rule: schedule liveness and effects โ
- โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
- */
- rule schedule(env e, method f, bytes32 id, uint256 delay) filtered { f ->
- f.selector == schedule(address, uint256, bytes, bytes32, bytes32, uint256).selector ||
- f.selector == scheduleBatch(address[], uint256[], bytes[], bytes32, bytes32, uint256).selector
- } {
- require nonpayable(e);
- // Basic timestamp assumptions
- require e.block.timestamp > 1;
- require e.block.timestamp + delay < max_uint256;
- require e.block.timestamp + getMinDelay() < max_uint256;
- bytes32 otherId; uint256 otherTimestamp = getTimestamp(otherId);
- uint8 stateBefore = state(id);
- bool isDelaySufficient = delay >= getMinDelay();
- bool isProposerBefore = hasRole(PROPOSER_ROLE(), e.msg.sender);
- helperScheduleWithRevert(e, f, id, delay);
- bool success = !lastReverted;
- // liveness
- assert success <=> (
- stateBefore == UNSET() &&
- isDelaySufficient &&
- isProposerBefore
- );
- // effect
- assert success => state(id) == PENDING(), "State transition violation";
- assert success => getTimestamp(id) == to_uint256(e.block.timestamp + delay), "Proposal timestamp not correctly set";
- // no side effect
- assert otherTimestamp != getTimestamp(otherId) => id == otherId, "Other proposal affected";
- }
- /*
- โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
- โ Rule: execute liveness and effects โ
- โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
- */
- rule execute(env e, method f, bytes32 id, bytes32 predecessor) filtered { f ->
- f.selector == execute(address, uint256, bytes, bytes32, bytes32).selector ||
- f.selector == executeBatch(address[], uint256[], bytes[], bytes32, bytes32).selector
- } {
- bytes32 otherId; uint256 otherTimestamp = getTimestamp(otherId);
- uint8 stateBefore = state(id);
- bool isOperationReadyBefore = isOperationReady(e, id);
- bool isExecutorOrOpen = hasRole(EXECUTOR_ROLE(), e.msg.sender) || hasRole(EXECUTOR_ROLE(), 0);
- bool predecessorDependency = predecessor == 0 || isDone(predecessor);
- helperExecuteWithRevert(e, f, id, predecessor);
- bool success = !lastReverted;
- // The underlying transaction can revert, and that would cause the execution to revert. We can check that all non
- // reverting calls meet the requirements in terms of proposal readiness, access control and predecessor dependency.
- // We can't however guarantee that these requirements being meet ensure liveness of the proposal, because the
- // proposal can revert for reasons beyond our control.
- // liveness, should be `<=>` but can only check `=>` (see comment above)
- assert success => (
- stateBefore == PENDING() &&
- isOperationReadyBefore &&
- predecessorDependency &&
- isExecutorOrOpen
- );
- // effect
- assert success => state(id) == DONE(), "State transition violation";
- // no side effect
- assert otherTimestamp != getTimestamp(otherId) => id == otherId, "Other proposal affected";
- }
- /*
- โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
- โ Rule: cancel liveness and effects โ
- โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
- */
- rule cancel(env e, bytes32 id) {
- require nonpayable(e);
- bytes32 otherId; uint256 otherTimestamp = getTimestamp(otherId);
- uint8 stateBefore = state(id);
- bool isCanceller = hasRole(CANCELLER_ROLE(), e.msg.sender);
- cancel@withrevert(e, id);
- bool success = !lastReverted;
- // liveness
- assert success <=> (
- stateBefore == PENDING() &&
- isCanceller
- );
- // effect
- assert success => state(id) == UNSET(), "State transition violation";
- // no side effect
- assert otherTimestamp != getTimestamp(otherId) => id == otherId, "Other proposal affected";
- }
|