mirror of openzeppelin-contracts
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
openzeppelin-contracts/certora/specs/AccessManager.spec

827 lines
41 KiB

import "helpers/helpers.spec";
import "methods/IAccessManager.spec";
methods {
// FV
function canCall_immediate(address,address,bytes4) external returns (bool);
function canCall_delay(address,address,bytes4) external returns (uint32);
function canCallExtended(address,address,bytes) external returns (bool,uint32);
function canCallExtended_immediate(address,address,bytes) external returns (bool);
function canCallExtended_delay(address,address,bytes) external returns (uint32);
function getAdminRestrictions_restricted(bytes) external returns (bool);
function getAdminRestrictions_roleAdminId(bytes) external returns (uint64);
function getAdminRestrictions_executionDelay(bytes) external returns (uint32);
function hasRole_isMember(uint64,address) external returns (bool);
function hasRole_executionDelay(uint64,address) external returns (uint32);
function getAccess_since(uint64,address) external returns (uint48);
function getAccess_currentDelay(uint64,address) external returns (uint32);
function getAccess_pendingDelay(uint64,address) external returns (uint32);
function getAccess_effect(uint64,address) external returns (uint48);
function getTargetAdminDelay_after(address target) external returns (uint32);
function getTargetAdminDelay_effect(address target) external returns (uint48);
function getRoleGrantDelay_after(uint64 roleId) external returns (uint32);
function getRoleGrantDelay_effect(uint64 roleId) external returns (uint48);
function hashExecutionId(address,bytes4) external returns (bytes32) envfree;
function executionId() external returns (bytes32) envfree;
function getSelector(bytes) external returns (bytes4) envfree;
function getFirstArgumentAsAddress(bytes) external returns (address) envfree;
function getFirstArgumentAsUint64(bytes) external returns (uint64) envfree;
}
/*
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
│ Helpers │
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
*/
definition isOnlyAuthorized(bytes4 selector) returns bool =
selector == to_bytes4(sig:labelRole(uint64,string).selector ) ||
selector == to_bytes4(sig:setRoleAdmin(uint64,uint64).selector ) ||
selector == to_bytes4(sig:setRoleGuardian(uint64,uint64).selector ) ||
selector == to_bytes4(sig:setGrantDelay(uint64,uint32).selector ) ||
selector == to_bytes4(sig:setTargetAdminDelay(address,uint32).selector ) ||
selector == to_bytes4(sig:updateAuthority(address,address).selector ) ||
selector == to_bytes4(sig:setTargetClosed(address,bool).selector ) ||
selector == to_bytes4(sig:setTargetFunctionRole(address,bytes4[],uint64).selector) ||
selector == to_bytes4(sig:grantRole(uint64,address,uint32).selector ) ||
selector == to_bytes4(sig:revokeRole(uint64,address).selector );
/*
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
│ Invariant: executionId must be clean when not in the middle of a call │
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
*/
invariant cleanExecutionId()
executionId() == to_bytes32(0);
/*
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
│ Invariant: public role │
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
*/
invariant publicRole(env e, address account)
hasRole_isMember(e, PUBLIC_ROLE(), account) &&
hasRole_executionDelay(e, PUBLIC_ROLE(), account) == 0 &&
getAccess_since(e, PUBLIC_ROLE(), account) == 0 &&
getAccess_currentDelay(e, PUBLIC_ROLE(), account) == 0 &&
getAccess_pendingDelay(e, PUBLIC_ROLE(), account) == 0 &&
getAccess_effect(e, PUBLIC_ROLE(), account) == 0;
/*
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
│ Invariant: hasRole is consistent with getAccess │
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
*/
invariant hasRoleGetAccessConsistency(env e, uint64 roleId, address account)
hasRole_isMember(e, roleId, account) == (roleId == PUBLIC_ROLE() || isSetAndPast(e, getAccess_since(e, roleId, account))) &&
hasRole_executionDelay(e, roleId, account) == getAccess_currentDelay(e, roleId, account);
/*
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
│ Functions: canCall, canCallExtended, getAccess, hasRole, isTargetClosed and getTargetFunctionRole do NOT revert │
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
*/
rule noRevert(env e) {
require nonpayable(e);
require sanity(e);
address caller;
address target;
bytes data;
bytes4 selector;
uint64 roleId;
canCall@withrevert(e, caller, target, selector);
assert !lastReverted;
// require data.length <= max_uint64;
//
// canCallExtended@withrevert(e, caller, target, data);
// assert !lastReverted;
getAccess@withrevert(e, roleId, caller);
assert !lastReverted;
hasRole@withrevert(e, roleId, caller);
assert !lastReverted;
isTargetClosed@withrevert(target);
assert !lastReverted;
getTargetFunctionRole@withrevert(target, selector);
assert !lastReverted;
// Not covered:
// - getAdminRestrictions (_1, _2 & _3)
// - getSelector
}
/*
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
│ Functions: admin restrictions are correct │
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
*/
rule getAdminRestrictions(env e, bytes data) {
bool restricted = getAdminRestrictions_restricted(e, data);
uint64 roleId = getAdminRestrictions_roleAdminId(e, data);
uint32 delay = getAdminRestrictions_executionDelay(e, data);
bytes4 selector = getSelector(data);
if (data.length < 4) {
assert restricted == false;
assert roleId == 0;
assert delay == 0;
} else {
assert restricted ==
isOnlyAuthorized(selector);
assert roleId == (
(restricted && selector == to_bytes4(sig:grantRole(uint64,address,uint32).selector)) ||
(restricted && selector == to_bytes4(sig:revokeRole(uint64,address).selector ))
? getRoleAdmin(getFirstArgumentAsUint64(data))
: ADMIN_ROLE()
);
assert delay == (
(restricted && selector == to_bytes4(sig:updateAuthority(address,address).selector )) ||
(restricted && selector == to_bytes4(sig:setTargetClosed(address,bool).selector )) ||
(restricted && selector == to_bytes4(sig:setTargetFunctionRole(address,bytes4[],uint64).selector))
? getTargetAdminDelay(e, getFirstArgumentAsAddress(data))
: 0
);
}
}
/*
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
│ Functions: canCall │
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
*/
rule canCall(env e) {
address caller;
address target;
bytes4 selector;
// Get relevant values
bool immediate = canCall_immediate(e, caller, target, selector);
uint32 delay = canCall_delay(e, caller, target, selector);
bool closed = isTargetClosed(target);
uint64 roleId = getTargetFunctionRole(target, selector);
bool isMember = hasRole_isMember(e, roleId, caller);
uint32 currentDelay = hasRole_executionDelay(e, roleId, caller);
// Can only execute without delay in specific cases:
// - target not closed
// - if self-execution: `executionId` must match
// - if third party execution: must be member with no delay
assert immediate <=> (
!closed &&
(
(caller == currentContract && executionId() == hashExecutionId(target, selector))
||
(caller != currentContract && isMember && currentDelay == 0)
)
);
// Can only execute with delay in specific cases:
// - target not closed
// - third party execution
// - caller is a member and has an execution delay
assert delay > 0 <=> (
!closed &&
caller != currentContract &&
isMember &&
currentDelay > 0
);
// If there is a delay, then it must be the caller's execution delay
assert delay > 0 => delay == currentDelay;
// Immediate execute means no delayed execution
assert immediate => delay == 0;
}
/*
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
│ Functions: canCallExtended │
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
*/
rule canCallExtended(env e) {
address caller;
address target;
bytes data;
bytes4 selector = getSelector(data);
bool immediate = canCallExtended_immediate(e, caller, target, data);
uint32 delay = canCallExtended_delay(e, caller, target, data);
bool enabled = getAdminRestrictions_restricted(e, data);
uint64 roleId = getAdminRestrictions_roleAdminId(e, data);
uint32 operationDelay = getAdminRestrictions_executionDelay(e, data);
bool inRole = hasRole_isMember(e, roleId, caller);
uint32 executionDelay = hasRole_executionDelay(e, roleId, caller);
if (target == currentContract) {
// Can only execute without delay in the specific cases:
// - caller is the AccessManager and the executionId is set
// or
// - data matches an admin restricted function
// - caller has the necessary role
// - operation delay is not set
// - execution delay is not set
assert immediate <=> (
(
caller == currentContract &&
data.length >= 4 &&
executionId() == hashExecutionId(target, selector)
) || (
caller != currentContract &&
enabled &&
inRole &&
operationDelay == 0 &&
executionDelay == 0
)
);
// Immediate execute means no delayed execution
// This is equivalent to "delay > 0 => !immediate"
assert immediate => delay == 0;
// Can only execute with delay in specific cases:
// - caller is a third party
// - data matches an admin restricted function
// - caller has the necessary role
// -operation delay or execution delay is set
assert delay > 0 <=> (
caller != currentContract &&
enabled &&
inRole &&
(operationDelay > 0 || executionDelay > 0)
);
// If there is a delay, then it must be the maximum of caller's execution delay and the operation delay
assert delay > 0 => to_mathint(delay) == max(operationDelay, executionDelay);
} else if (data.length < 4) {
assert immediate == false;
assert delay == 0;
} else {
// results are equivalent when targeting third party contracts
assert immediate == canCall_immediate(e, caller, target, selector);
assert delay == canCall_delay(e, caller, target, selector);
}
}
/*
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
│ State transitions: getAccess │
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
*/
rule getAccessChangeTime(uint64 roleId, address account) {
env e1;
env e2;
// values before
mathint getAccess1Before = getAccess_since(e1, roleId, account);
mathint getAccess2Before = getAccess_currentDelay(e1, roleId, account);
mathint getAccess3Before = getAccess_pendingDelay(e1, roleId, account);
mathint getAccess4Before = getAccess_effect(e1, roleId, account);
// time pass: e1 → e2
require clock(e1) <= clock(e2);
// values after
mathint getAccess1After = getAccess_since(e2, roleId, account);
mathint getAccess2After = getAccess_currentDelay(e2, roleId, account);
mathint getAccess3After = getAccess_pendingDelay(e2, roleId, account);
mathint getAccess4After = getAccess_effect(e2, roleId, account);
// member "since" cannot change as a consequence of time passing
assert getAccess1Before == getAccess1After;
// any change of any other value should be a consequence of the effect timepoint being reached
assert (
getAccess2Before != getAccess2After ||
getAccess3Before != getAccess3After ||
getAccess4Before != getAccess4After
) => (
getAccess4Before != 0 &&
getAccess4Before > clock(e1) &&
getAccess4Before <= clock(e2) &&
getAccess2After == getAccess3Before &&
getAccess3After == 0 &&
getAccess4After == 0
);
}
rule getAccessChangeCall(uint64 roleId, address account) {
env e;
// sanity
require sanity(e);
// values before
mathint getAccess1Before = getAccess_since(e, roleId, account);
mathint getAccess2Before = getAccess_currentDelay(e, roleId, account);
mathint getAccess3Before = getAccess_pendingDelay(e, roleId, account);
mathint getAccess4Before = getAccess_effect(e, roleId, account);
// arbitrary function call
method f; calldataarg args; f(e, args);
// values before
mathint getAccess1After = getAccess_since(e, roleId, account);
mathint getAccess2After = getAccess_currentDelay(e, roleId, account);
mathint getAccess3After = getAccess_pendingDelay(e, roleId, account);
mathint getAccess4After = getAccess_effect(e, roleId, account);
// transitions
assert (
getAccess1Before != getAccess1After ||
getAccess2Before != getAccess2After ||
getAccess3Before != getAccess3After ||
getAccess4Before != getAccess4After
) => (
(
f.selector == sig:grantRole(uint64,address,uint32).selector &&
getAccess1After > 0
) || (
(
f.selector == sig:revokeRole(uint64,address).selector ||
f.selector == sig:renounceRole(uint64,address).selector
) &&
getAccess1After == 0 &&
getAccess2After == 0 &&
getAccess3After == 0 &&
getAccess4After == 0
)
);
}
/*
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
│ State transitions: isTargetClosed │
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
*/
rule isTargetClosedChangeTime(address target) {
env e1;
env e2;
// values before
bool isClosedBefore = isTargetClosed(e1, target);
// time pass: e1 → e2
require clock(e1) <= clock(e2);
// values after
bool isClosedAfter = isTargetClosed(e2, target);
// transitions
assert isClosedBefore == isClosedAfter;
}
rule isTargetClosedChangeCall(address target) {
env e;
// values before
bool isClosedBefore = isTargetClosed(e, target);
// arbitrary function call
method f; calldataarg args; f(e, args);
// values after
bool isClosedAfter = isTargetClosed(e, target);
// transitions
assert isClosedBefore != isClosedAfter => (
f.selector == sig:setTargetClosed(address,bool).selector
);
}
/*
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
│ State transitions: getTargetFunctionRole │
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
*/
rule getTargetFunctionRoleChangeTime(address target, bytes4 selector) {
env e1;
env e2;
// values before
mathint roleIdBefore = getTargetFunctionRole(e1, target, selector);
// time pass: e1 → e2
require clock(e1) <= clock(e2);
// values after
mathint roleIdAfter = getTargetFunctionRole(e2, target, selector);
// transitions
assert roleIdBefore == roleIdAfter;
}
rule getTargetFunctionRoleChangeCall(address target, bytes4 selector) {
env e;
// values before
mathint roleIdBefore = getTargetFunctionRole(e, target, selector);
// arbitrary function call
method f; calldataarg args; f(e, args);
// values after
mathint roleIdAfter = getTargetFunctionRole(e, target, selector);
// transitions
assert roleIdBefore != roleIdAfter => (
f.selector == sig:setTargetFunctionRole(address,bytes4[],uint64).selector
);
}
/*
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
│ State transitions: getTargetAdminDelay │
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
*/
rule getTargetAdminDelayChangeTime(address target) {
env e1;
env e2;
// values before
mathint delayBefore = getTargetAdminDelay(e1, target);
mathint delayPendingBefore = getTargetAdminDelay_after(e1, target);
mathint delayEffectBefore = getTargetAdminDelay_effect(e1, target);
// time pass: e1 → e2
require clock(e1) <= clock(e2);
// values after
mathint delayAfter = getTargetAdminDelay(e2, target);
mathint delayPendingAfter = getTargetAdminDelay_after(e2, target);
mathint delayEffectAfter = getTargetAdminDelay_effect(e2, target);
assert (
delayBefore != delayAfter ||
delayPendingBefore != delayPendingAfter ||
delayEffectBefore != delayEffectAfter
) => (
delayEffectBefore > clock(e1) &&
delayEffectBefore <= clock(e2) &&
delayAfter == delayPendingBefore &&
delayPendingAfter == 0 &&
delayEffectAfter == 0
);
}
rule getTargetAdminDelayChangeCall(address target) {
env e;
// values before
mathint delayBefore = getTargetAdminDelay(e, target);
mathint delayPendingBefore = getTargetAdminDelay_after(e, target);
mathint delayEffectBefore = getTargetAdminDelay_effect(e, target);
// arbitrary function call
method f; calldataarg args; f(e, args);
// values after
mathint delayAfter = getTargetAdminDelay(e, target);
mathint delayPendingAfter = getTargetAdminDelay_after(e, target);
mathint delayEffectAfter = getTargetAdminDelay_effect(e, target);
// if anything changed ...
assert (
delayBefore != delayAfter ||
delayPendingBefore != delayPendingAfter ||
delayEffectBefore != delayEffectAfter
) => (
(
// ... it was the consequence of a call to setTargetAdminDelay
f.selector == sig:setTargetAdminDelay(address,uint32).selector
) && (
// ... delay cannot decrease instantly
delayAfter >= delayBefore
) && (
// ... if setback is not 0, value cannot change instantly
minSetback() > 0 => (
delayBefore == delayAfter
)
) && (
// ... if the value did not change and there is a minSetback, there must be something scheduled in the future
delayAfter == delayBefore && minSetback() > 0 => (
delayEffectAfter >= clock(e) + minSetback()
)
// note: if there is no minSetback, and if the caller "confirms" the current value,
// then this as immediate effect and nothing is scheduled
) && (
// ... if the value changed, then no further change should be scheduled
delayAfter != delayBefore => (
delayPendingAfter == 0 &&
delayEffectAfter == 0
)
)
);
}
/*
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
│ State transitions: getRoleGrantDelay │
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
*/
rule getRoleGrantDelayChangeTime(uint64 roleId) {
env e1;
env e2;
// values before
mathint delayBefore = getRoleGrantDelay(e1, roleId);
mathint delayPendingBefore = getRoleGrantDelay_after(e1, roleId);
mathint delayEffectBefore = getRoleGrantDelay_effect(e1, roleId);
// time pass: e1 → e2
require clock(e1) <= clock(e2);
// values after
mathint delayAfter = getRoleGrantDelay(e2, roleId);
mathint delayPendingAfter = getRoleGrantDelay_after(e2, roleId);
mathint delayEffectAfter = getRoleGrantDelay_effect(e2, roleId);
assert (
delayBefore != delayAfter ||
delayPendingBefore != delayPendingAfter ||
delayEffectBefore != delayEffectAfter
) => (
delayEffectBefore > clock(e1) &&
delayEffectBefore <= clock(e2) &&
delayAfter == delayPendingBefore &&
delayPendingAfter == 0 &&
delayEffectAfter == 0
);
}
rule getRoleGrantDelayChangeCall(uint64 roleId) {
env e;
// values before
mathint delayBefore = getRoleGrantDelay(e, roleId);
mathint delayPendingBefore = getRoleGrantDelay_after(e, roleId);
mathint delayEffectBefore = getRoleGrantDelay_effect(e, roleId);
// arbitrary function call
method f; calldataarg args; f(e, args);
// values after
mathint delayAfter = getRoleGrantDelay(e, roleId);
mathint delayPendingAfter = getRoleGrantDelay_after(e, roleId);
mathint delayEffectAfter = getRoleGrantDelay_effect(e, roleId);
// if anything changed ...
assert (
delayBefore != delayAfter ||
delayPendingBefore != delayPendingAfter ||
delayEffectBefore != delayEffectAfter
) => (
(
// ... it was the consequence of a call to setTargetAdminDelay
f.selector == sig:setGrantDelay(uint64,uint32).selector
) && (
// ... delay cannot decrease instantly
delayAfter >= delayBefore
) && (
// ... if setback is not 0, value cannot change instantly
minSetback() > 0 => (
delayBefore == delayAfter
)
) && (
// ... if the value did not change and there is a minSetback, there must be something scheduled in the future
delayAfter == delayBefore && minSetback() > 0 => (
delayEffectAfter >= clock(e) + minSetback()
)
// note: if there is no minSetback, and if the caller "confirms" the current value,
// then this as immediate effect and nothing is scheduled
) && (
// ... if the value changed, then no further change should be scheduled
delayAfter != delayBefore => (
delayPendingAfter == 0 &&
delayEffectAfter == 0
)
)
);
}
/*
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
│ State transitions: getRoleAdmin & getRoleGuardian │
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
*/
rule getRoleAdminChangeCall(uint64 roleId) {
// values before
mathint adminIdBefore = getRoleAdmin(roleId);
// arbitrary function call
env e; method f; calldataarg args; f(e, args);
// values after
mathint adminIdAfter = getRoleAdmin(roleId);
// transitions
assert adminIdBefore != adminIdAfter => f.selector == sig:setRoleAdmin(uint64,uint64).selector;
}
rule getRoleGuardianChangeCall(uint64 roleId) {
// values before
mathint guardianIdBefore = getRoleGuardian(roleId);
// arbitrary function call
env e; method f; calldataarg args; f(e, args);
// values after
mathint guardianIdAfter = getRoleGuardian(roleId);
// transitions
assert guardianIdBefore != guardianIdAfter => (
f.selector == sig:setRoleGuardian(uint64,uint64).selector
);
}
/*
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
│ State transitions: getNonce │
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
*/
rule getNonceChangeCall(bytes32 operationId) {
// values before
mathint nonceBefore = getNonce(operationId);
// reasonable assumption
require nonceBefore < max_uint32;
// arbitrary function call
env e; method f; calldataarg args; f(e, args);
// values after
mathint nonceAfter = getNonce(operationId);
// transitions
assert nonceBefore != nonceAfter => (
f.selector == sig:schedule(address,bytes,uint48).selector &&
nonceAfter == nonceBefore + 1
);
}
/*
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
│ State transitions: getSchedule │
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
*/
rule getScheduleChangeTime(bytes32 operationId) {
env e1;
env e2;
// values before
mathint scheduleBefore = getSchedule(e1, operationId);
// time pass: e1 → e2
require clock(e1) <= clock(e2);
// values after
mathint scheduleAfter = getSchedule(e2, operationId);
// transition
assert scheduleBefore != scheduleAfter => (
scheduleBefore + expiration() > clock(e1) &&
scheduleBefore + expiration() <= clock(e2) &&
scheduleAfter == 0
);
}
rule getScheduleChangeCall(bytes32 operationId) {
env e;
// values before
mathint scheduleBefore = getSchedule(e, operationId);
// arbitrary function call
method f; calldataarg args; f(e, args);
// values after
mathint scheduleAfter = getSchedule(e, operationId);
// transitions
assert scheduleBefore != scheduleAfter => (
(f.selector == sig:schedule(address,bytes,uint48).selector && scheduleAfter >= clock(e)) ||
(f.selector == sig:execute(address,bytes).selector && scheduleAfter == 0 ) ||
(f.selector == sig:cancel(address,address,bytes).selector && scheduleAfter == 0 ) ||
(f.selector == sig:consumeScheduledOp(address,bytes).selector && scheduleAfter == 0 ) ||
(isOnlyAuthorized(to_bytes4(f.selector)) && scheduleAfter == 0 )
);
}
/*
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
│ Functions: restricted functions can only be called by owner │
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
*/
rule restrictedFunctions(env e) {
require nonpayable(e);
require sanity(e);
method f;
calldataarg args;
f(e,args);
assert (
f.selector == sig:labelRole(uint64,string).selector ||
f.selector == sig:setRoleAdmin(uint64,uint64).selector ||
f.selector == sig:setRoleGuardian(uint64,uint64).selector ||
f.selector == sig:setGrantDelay(uint64,uint32).selector ||
f.selector == sig:setTargetAdminDelay(address,uint32).selector ||
f.selector == sig:updateAuthority(address,address).selector ||
f.selector == sig:setTargetClosed(address,bool).selector ||
f.selector == sig:setTargetFunctionRole(address,bytes4[],uint64).selector
) => (
hasRole_isMember(e, ADMIN_ROLE(), e.msg.sender) || e.msg.sender == currentContract
);
}
rule restrictedFunctionsGrantRole(env e) {
require nonpayable(e);
require sanity(e);
uint64 roleId;
address account;
uint32 executionDelay;
// We want to check that the caller has the admin role before we possibly grant it.
bool hasAdminRoleBefore = hasRole_isMember(e, getRoleAdmin(roleId), e.msg.sender);
grantRole(e, roleId, account, executionDelay);
assert hasAdminRoleBefore || e.msg.sender == currentContract;
}
rule restrictedFunctionsRevokeRole(env e) {
require nonpayable(e);
require sanity(e);
uint64 roleId;
address account;
// This is needed if roleId is self-administered, the `revokeRole` call could target
// e.msg.sender and remove the very role that is necessary for authorizing the call.
bool hasAdminRoleBefore = hasRole_isMember(e, getRoleAdmin(roleId), e.msg.sender);
revokeRole(e, roleId, account);
assert hasAdminRoleBefore || e.msg.sender == currentContract;
}
/*
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
│ Functions: canCall delay is enforced for calls to execute (only for others target) │
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
*/
// getScheduleChangeCall proves that only {schedule} can set an operation schedule to a non 0 value
rule callDelayEnforce_scheduleInTheFuture(env e) {
address target;
bytes data;
uint48 when;
// Condition: calling a third party with a delay
mathint delay = canCallExtended_delay(e, e.msg.sender, target, data);
require delay > 0;
// Schedule
schedule(e, target, data, when);
// Get operation schedule
mathint timepoint = getSchedule(e, hashOperation(e.msg.sender, target, data));
// Schedule is far enough in the future
assert timepoint == max(clock(e) + delay, when);
}
rule callDelayEnforce_executeAfterDelay(env e) {
address target;
bytes data;
// Condition: calling a third party with a delay
mathint delay = canCallExtended_delay(e, e.msg.sender, target, data);
// Get operation schedule before
mathint scheduleBefore = getSchedule(e, hashOperation(e.msg.sender, target, data));
// Do call
execute@withrevert(e, target, data);
bool success = !lastReverted;
// Get operation schedule after
mathint scheduleAfter = getSchedule(e, hashOperation(e.msg.sender, target, data));
// Can only execute if delay is set and has passed
assert success => (
delay > 0 => (
scheduleBefore != 0 &&
scheduleBefore <= clock(e)
) &&
scheduleAfter == 0
);
}