import "helpers/helpers.spec";
import "methods/IAccessControlDefaultAdminRules.spec";
import "methods/IAccessControl.spec";
import "AccessControl.spec";

use rule onlyGrantCanGrant filtered {
  f -> f.selector != sig:acceptDefaultAdminTransfer().selector
}

/*
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
│ Definitions                                                                                                             │
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
*/
definition timeSanity(env e) returns bool =
  e.block.timestamp > 0 && e.block.timestamp + defaultAdminDelay(e) < max_uint48;

definition delayChangeWaitSanity(env e, uint48 newDelay) returns bool =
  e.block.timestamp + delayChangeWait_(e, newDelay) < max_uint48;

definition isSet(uint48 schedule) returns bool =
  schedule != 0;

definition hasPassed(env e, uint48 schedule) returns bool =
  assert_uint256(schedule) < e.block.timestamp;

definition increasingDelaySchedule(env e, uint48 newDelay) returns mathint =
  e.block.timestamp + min(newDelay, defaultAdminDelayIncreaseWait());

definition decreasingDelaySchedule(env e, uint48 newDelay) returns mathint =
  e.block.timestamp + defaultAdminDelay(e) - newDelay;

/*
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
│ Invariant: defaultAdmin holds the DEFAULT_ADMIN_ROLE                                                                │
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
*/
invariant defaultAdminConsistency(address account)
  (account == defaultAdmin() && account != 0) <=> hasRole(DEFAULT_ADMIN_ROLE(), account)
  {
    preserved with (env e) {
      require nonzerosender(e);
    }
  }

/*
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
│ Invariant: Only one account holds the DEFAULT_ADMIN_ROLE                                                            │
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
*/
invariant singleDefaultAdmin(address account, address another)
  hasRole(DEFAULT_ADMIN_ROLE(), account) && hasRole(DEFAULT_ADMIN_ROLE(), another) => another == account
  {
    preserved {
      requireInvariant defaultAdminConsistency(account);
      requireInvariant defaultAdminConsistency(another);
    }
  }

/*
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
│ Invariant: DEFAULT_ADMIN_ROLE's admin is always DEFAULT_ADMIN_ROLE                                                  │
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
*/
invariant defaultAdminRoleAdminConsistency()
  getRoleAdmin(DEFAULT_ADMIN_ROLE()) == DEFAULT_ADMIN_ROLE();

/*
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
│ Invariant: owner is the defaultAdmin                                                                                │
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
*/
invariant ownerConsistency()
  defaultAdmin() == owner();

/*
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
│ Function correctness: revokeRole only affects the specified user/role combo                                         │
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
*/
rule revokeRoleEffect(env e, bytes32 role) {
    require nonpayable(e);

    bytes32 otherRole;
    address account;
    address otherAccount;

    bool isCallerAdmin = hasRole(getRoleAdmin(role), e.msg.sender);
    bool hasOtherRoleBefore = hasRole(otherRole, otherAccount);

    revokeRole@withrevert(e, role, account);
    bool success = !lastReverted;

    bool hasOtherRoleAfter = hasRole(otherRole, otherAccount);

    // liveness
    assert success <=> isCallerAdmin && role != DEFAULT_ADMIN_ROLE(),
      "roles can only be revoked by their owner except for the default admin role";

    // effect
    assert success => !hasRole(role, account),
      "role is revoked";

    // no side effect
    assert hasOtherRoleBefore != hasOtherRoleAfter => (role == otherRole && account == otherAccount),
      "no other role is affected";
}

/*
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
│ Function correctness: renounceRole only affects the specified user/role combo                                       │
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
*/
rule renounceRoleEffect(env e, bytes32 role) {
    require nonpayable(e);

    bytes32 otherRole;
    address account;
    address otherAccount;

    bool    hasOtherRoleBefore = hasRole(otherRole, otherAccount);
    address adminBefore        = defaultAdmin();
    address pendingAdminBefore = pendingDefaultAdmin_();
    uint48  scheduleBefore     = pendingDefaultAdminSchedule_();

    renounceRole@withrevert(e, role, account);
    bool success = !lastReverted;

    bool    hasOtherRoleAfter = hasRole(otherRole, otherAccount);
    address adminAfter        = defaultAdmin();
    address pendingAdminAfter = pendingDefaultAdmin_();
    uint48  scheduleAfter     = pendingDefaultAdminSchedule_();

    // liveness
    assert success <=> (
      account == e.msg.sender &&
      (
        role    != DEFAULT_ADMIN_ROLE() ||
        account != adminBefore        ||
        (
          pendingAdminBefore == 0 &&
          isSet(scheduleBefore) &&
          hasPassed(e, scheduleBefore)
        )
      )
    ),
      "an account only can renounce by itself with a delay for the default admin role";

    // effect
    assert success => !hasRole(role, account),
      "role is renounced";

    assert success => (
      (
        role    == DEFAULT_ADMIN_ROLE() &&
        account == adminBefore
      ) ? (
        adminAfter        == 0 &&
        pendingAdminAfter == 0 &&
        scheduleAfter     == 0
      ) : (
        adminAfter        == adminBefore        &&
        pendingAdminAfter == pendingAdminBefore &&
        scheduleAfter     == scheduleBefore
      )
    ),
      "renouncing default admin role cleans state iff called by previous admin";

    // no side effect
    assert hasOtherRoleBefore != hasOtherRoleAfter => (
      role == otherRole &&
      account == otherAccount
    ),
      "no other role is affected";
}

/*
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
│ Rule: defaultAdmin is only affected by accepting an admin transfer or renouncing                                    │
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
*/
rule noDefaultAdminChange(env e, method f, calldataarg args) {
  address adminBefore = defaultAdmin();
  f(e, args);
  address adminAfter = defaultAdmin();

  assert adminBefore != adminAfter => (
    f.selector == sig:acceptDefaultAdminTransfer().selector ||
    f.selector == sig:renounceRole(bytes32,address).selector
  ),
    "default admin is only affected by accepting an admin transfer or renouncing";
}

/*
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
│ Rule: pendingDefaultAdmin is only affected by beginning, completing (accept or renounce), or canceling an admin     │
│ transfer                                                                                                            │
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
*/
rule noPendingDefaultAdminChange(env e, method f, calldataarg args) {
  address pendingAdminBefore = pendingDefaultAdmin_();
  uint48 scheduleBefore = pendingDefaultAdminSchedule_();
  f(e, args);
  address pendingAdminAfter = pendingDefaultAdmin_();
  uint48 scheduleAfter = pendingDefaultAdminSchedule_();

  assert (
    pendingAdminBefore != pendingAdminAfter ||
    scheduleBefore != scheduleAfter
  ) => (
    f.selector == sig:beginDefaultAdminTransfer(address).selector ||
    f.selector == sig:acceptDefaultAdminTransfer().selector ||
    f.selector == sig:cancelDefaultAdminTransfer().selector ||
    f.selector == sig:renounceRole(bytes32,address).selector
  ),
    "pending admin and its schedule is only affected by beginning, completing, or cancelling an admin transfer";
}

/*
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
│ Rule: defaultAdminDelay can't be changed atomically by any function                                                 │
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
*/
rule noDefaultAdminDelayChange(env e, method f, calldataarg args) {
  uint48 delayBefore = defaultAdminDelay(e);
  f(e, args);
  uint48 delayAfter = defaultAdminDelay(e);

  assert delayBefore == delayAfter,
    "delay can't be changed atomically by any function";
}

/*
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
│ Rule: pendingDefaultAdminDelay is only affected by changeDefaultAdminDelay or rollbackDefaultAdminDelay             │
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
*/
rule noPendingDefaultAdminDelayChange(env e, method f, calldataarg args) {
  uint48 pendingDelayBefore = pendingDelay_(e);
  f(e, args);
  uint48 pendingDelayAfter = pendingDelay_(e);

  assert pendingDelayBefore != pendingDelayAfter => (
    f.selector == sig:changeDefaultAdminDelay(uint48).selector ||
    f.selector == sig:rollbackDefaultAdminDelay().selector
  ),
    "pending delay is only affected by changeDefaultAdminDelay or rollbackDefaultAdminDelay";
}

/*
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
│ Rule: defaultAdminDelayIncreaseWait can't be changed atomically by any function                                     │
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
*/
rule noDefaultAdminDelayIncreaseWaitChange(env e, method f, calldataarg args) {
  uint48 delayIncreaseWaitBefore = defaultAdminDelayIncreaseWait();
  f(e, args);
  uint48 delayIncreaseWaitAfter = defaultAdminDelayIncreaseWait();

  assert delayIncreaseWaitBefore == delayIncreaseWaitAfter,
    "delay increase wait can't be changed atomically by any function";
}

/*
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
│ Function correctness: beginDefaultAdminTransfer sets a pending default admin and its schedule                       │
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
*/
rule beginDefaultAdminTransfer(env e, address newAdmin) {
  require timeSanity(e);
  require nonpayable(e);
  require nonzerosender(e);
  requireInvariant defaultAdminConsistency(e.msg.sender);

  beginDefaultAdminTransfer@withrevert(e, newAdmin);
  bool success = !lastReverted;

  // liveness
  assert success <=> e.msg.sender == defaultAdmin(),
    "only the current default admin can begin a transfer";

  // effect
  assert success => pendingDefaultAdmin_() == newAdmin,
    "pending default admin is set";
  assert success => to_mathint(pendingDefaultAdminSchedule_()) == e.block.timestamp + defaultAdminDelay(e),
    "pending default admin delay is set";
}

/*
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
│ Rule: A default admin can't change in less than the applied schedule                                                │
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
*/
rule pendingDefaultAdminDelayEnforced(env e1, env e2, method f, calldataarg args, address newAdmin) {
  require e1.block.timestamp <= e2.block.timestamp;

  uint48 delayBefore = defaultAdminDelay(e1);
  address adminBefore = defaultAdmin();

  // There might be a better way to generalize this without requiring `beginDefaultAdminTransfer`, but currently
  // it's the only way in which we can attest that only `delayBefore` has passed before a change.
  beginDefaultAdminTransfer(e1, newAdmin);
  f(e2, args);

  address adminAfter = defaultAdmin();

  // change can only happen towards the newAdmin, with the delay
  assert adminAfter != adminBefore => (
    adminAfter == newAdmin &&
    to_mathint(e2.block.timestamp) >= e1.block.timestamp + delayBefore
  ),
    "The admin can only change after the enforced delay and to the previously scheduled new admin";
}

/*
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
│ Function correctness: acceptDefaultAdminTransfer updates defaultAdmin resetting the pending admin and its schedule  │
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
*/
rule acceptDefaultAdminTransfer(env e) {
  require nonpayable(e);

  address pendingAdminBefore = pendingDefaultAdmin_();
  uint48 scheduleBefore = pendingDefaultAdminSchedule_();

  acceptDefaultAdminTransfer@withrevert(e);
  bool success = !lastReverted;

  // liveness
  assert success <=> (
    e.msg.sender == pendingAdminBefore &&
    isSet(scheduleBefore) &&
    hasPassed(e, scheduleBefore)
  ),
    "only the pending default admin can accept the role after the schedule has been set and passed";

  // effect
  assert success => defaultAdmin() == pendingAdminBefore,
    "Default admin is set to the previous pending default admin";
  assert success => pendingDefaultAdmin_() == 0,
    "Pending default admin is reset";
  assert success => pendingDefaultAdminSchedule_() == 0,
    "Pending default admin delay is reset";
}

/*
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
│ Function correctness: cancelDefaultAdminTransfer resets pending default admin and its schedule                      │
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
*/
rule cancelDefaultAdminTransfer(env e) {
  require nonpayable(e);
  require nonzerosender(e);
  requireInvariant defaultAdminConsistency(e.msg.sender);

  cancelDefaultAdminTransfer@withrevert(e);
  bool success = !lastReverted;

  // liveness
  assert success <=> e.msg.sender == defaultAdmin(),
    "only the current default admin can cancel a transfer";

  // effect
  assert success => pendingDefaultAdmin_() == 0,
    "Pending default admin is reset";
  assert success => pendingDefaultAdminSchedule_() == 0,
    "Pending default admin delay is reset";
}

/*
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
│ Function correctness: changeDefaultAdminDelay sets a pending default admin delay and its schedule                   │
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
*/
rule changeDefaultAdminDelay(env e, uint48 newDelay) {
  require timeSanity(e);
  require nonpayable(e);
  require nonzerosender(e);
  require delayChangeWaitSanity(e, newDelay);
  requireInvariant defaultAdminConsistency(e.msg.sender);

  uint48 delayBefore = defaultAdminDelay(e);

  changeDefaultAdminDelay@withrevert(e, newDelay);
  bool success = !lastReverted;

  // liveness
  assert success <=> e.msg.sender == defaultAdmin(),
    "only the current default admin can begin a delay change";

  // effect
  assert success => pendingDelay_(e) == newDelay,
    "pending delay is set";

  assert success => (
    assert_uint256(pendingDelaySchedule_(e)) > e.block.timestamp ||
    delayBefore == newDelay || // Interpreted as decreasing, x - x = 0
    defaultAdminDelayIncreaseWait() == 0
  ),
    "pending delay schedule is set in the future unless accepted edge cases";
}

/*
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
│ Rule: A delay can't change in less than the applied schedule                                                        │
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
*/
rule pendingDelayWaitEnforced(env e1, env e2, method f, calldataarg args, uint48 newDelay) {
  require e1.block.timestamp <= e2.block.timestamp;

  uint48 delayBefore = defaultAdminDelay(e1);

  changeDefaultAdminDelay(e1, newDelay);
  f(e2, args);

  uint48 delayAfter = defaultAdminDelay(e2);

  mathint delayWait = newDelay > delayBefore ? increasingDelaySchedule(e1, newDelay) : decreasingDelaySchedule(e1, newDelay);

  assert delayAfter != delayBefore => (
    delayAfter == newDelay &&
    to_mathint(e2.block.timestamp) >= delayWait
  ),
    "A delay can only change after the applied schedule";
}

/*
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
│ Rule: pending delay wait is set depending on increasing or decreasing the delay                                     │
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
*/
rule pendingDelayWait(env e, uint48 newDelay) {
  uint48 oldDelay = defaultAdminDelay(e);
  changeDefaultAdminDelay(e, newDelay);

  assert newDelay > oldDelay => to_mathint(pendingDelaySchedule_(e)) == increasingDelaySchedule(e, newDelay),
    "Delay wait is the minimum between the new delay and a threshold when the delay is increased";
  assert newDelay <= oldDelay => to_mathint(pendingDelaySchedule_(e)) == decreasingDelaySchedule(e, newDelay),
    "Delay wait is the difference between the current and the new delay when the delay is decreased";
}

/*
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
│ Function correctness: rollbackDefaultAdminDelay resets the delay and its schedule                                   │
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
*/
rule rollbackDefaultAdminDelay(env e) {
  require nonpayable(e);
  require nonzerosender(e);
  requireInvariant defaultAdminConsistency(e.msg.sender);

  rollbackDefaultAdminDelay@withrevert(e);
  bool success = !lastReverted;

  // liveness
  assert success <=> e.msg.sender == defaultAdmin(),
    "only the current default admin can rollback a delay change";

  // effect
  assert success => pendingDelay_(e) == 0,
    "Pending default admin is reset";
  assert success => pendingDelaySchedule_(e) == 0,
    "Pending default admin delay is reset";
}
