From af870c6530fcaf74961ca76ecab4d83a59852ffd Mon Sep 17 00:00:00 2001 From: alcueca Date: Sun, 7 Sep 2025 07:56:39 +0100 Subject: [PATCH 01/20] Merge of liveness module and timelock guard --- specs/protocol/safe-extensions-v2.md | 435 +++++++++++++++++++++++++++ 1 file changed, 435 insertions(+) create mode 100644 specs/protocol/safe-extensions-v2.md diff --git a/specs/protocol/safe-extensions-v2.md b/specs/protocol/safe-extensions-v2.md new file mode 100644 index 000000000..d83997220 --- /dev/null +++ b/specs/protocol/safe-extensions-v2.md @@ -0,0 +1,435 @@ +# Safe Contract Extensions V2 + + + +**Table of Contents** + +- [Security Council Liveness Checking Extensions](#security-council-liveness-checking-extensions) + - [The Liveness Guard](#the-liveness-guard) + - [The Liveness Module](#the-liveness-module) + - [Owner Removal Call Flow](#owner-removal-call-flow) + - [Shutdown](#shutdown) + - [Liveness Security Properties](#liveness-security-properties) + - [Liveness Guard Security Properties](#liveness-guard-security-properties) + - [Liveness Module Security Properties](#liveness-module-security-properties) + - [Interdependency between the Liveness Guard and Liveness Module](#interdependency-between-the-liveness-guard-and-liveness-module) +- [Operational Considerations](#operational-considerations) + - [Manual validation of new owner liveness](#manual-validation-of-new-owner-liveness) + - [Deploying the Liveness Checking System](#deploying-the-liveness-checking-system) + - [Modifying the Liveness Checking System](#modifying-the-liveness-checking-system) + - [Replacing the Liveness Module](#replacing-the-liveness-module) + - [Replacing the Liveness Guard](#replacing-the-liveness-guard) + + +This document describes extensions to the Security Council and Guardian Safe contracts, which +provide additional functionality and security guarantees on top of those provided by the Safe +contract. + +These extensions are developed using a singleton contract that can be enabled simultanesouly as a +([module](https://docs.safe.global/advanced/smart-account-modules) and a +[guard](https://docs.safe.global/advanced/smart-account-guards)) which the Safe contract has +built-in support for: + +1. **Guard contracts:** can execute pre- and post- transaction checks. +2. **Module contracts:** a contract which is + authorized to execute transactions via the Safe. This means the liveness module must properly implement + auth conditions internally. + +When enabled as a guard, the extension contract implements a mandatory delay on the execution of transactions. When +enabled as a module, the extension contract ensures a multisig remains operable by allowing challenges when it becomes +unresponsive. If the multisig fails to prove liveness within a set period, ownership transfers to a trusted fallback + owner to prevent deadlock. + +The extensions in this document are intended to replace the extensions in the +[Safe Contract Extensions](./safe-extensions.md) document. + +For more information about the Security Council and Guardian roles, refer to the +[Stage One Roles and Requirements](./stage-1.md) document. + +## Definitions + +The following list defines variables that will be used in the Safe Extensions V2. + +### Quorum + +The number of owners required to execute a transaction. + +### Blocking Threshold + +The minimum number of owners not willing to execute a transaction, that by not approving the transaction guarantee +that `quorum` is not met. It is defined as `min(quorum, total_owners - quorum + 1)`. + +### Active Owner + +An owner that is able to approve transactions. + +### Honest Owner + +An owner that is never willing to execute transactions outside of governance processes. + +### Malicious Owner + +An owner that is willing to execute transactions outside of governance processes. + +### Full Key Control + +An actor has full key control of an owner if it is solely able to approve transactions as that owner. + +### Joint Key Control + +An actor has joint key control of an owner if it is able, along with other actors, to approve transactions as that +owner. + +### Temporary Key Control + +An actor has temporary key control of an owner if it is able to approve a limited set or number of transactions as +that owner. + +### Multisig Liveness Failure + +A multisig is considered to be in a liveness failure state if the number of active honest owners is less than the +`quorum`, or if the number of malicious active owners is equal or greater than the `blocking_threshold`. + +### Multisig Safety Failure + +A multisig is considered to be in a safety failure state if the number of active malicious owners is equal or greater +than `quorum`. + +## Definitions in Liveness Module + +The following lists definitions that will be used in LivenessModule specifically. + +### Fallback Owner + +The owner that is appointed to take control of a multisig in case of a liveness or safety failure. + +### Liveness Challenge + +A challenge to a multisig to prove that it is not in a liveness failure state. + +### Liveness Challenge Period + +The `liveness_challenge_period(safe)` is the minimum period of time that must pass without a response since a liveness +challenge for a liveness failure state to be assumed, which is equal to the sum of the `timelock_delay(safe)` plus the +`liveness_challenge_period(safe)`. + +### Successful Challenge + +A challenge is considered to be successful if `liveness_challenge_period(safe)` has passed and the multisig did not +prove that it is not in a liveness failure state. + +## Definitions in Timelock Guard + +The following list defines variables that will be used in TimelockGuard specifically. + +### Scheduled Transaction + +A specific transaction that has been stored in the Timelock, for execution from a given safe. + +### Scheduling Time + +The `scheduling_time(safe, tx)` is the time in seconds of the block in which a transaction was scheduled for execution +from a given safe. + +### Timelock Delay Period + +Given a scheduled transaction, the `timelock_delay(safe)` is the minimum time in seconds after +`scheduling_time(safe, tx)` that execution is allowed. + +### Rejected Transaction + +Given a transaction scheduled to execute from given safe, owners for the safe reject the transaction to signal they +think the transaction should not be executed after the `timelock_delay(safe)`. + +### Rejecting Owners + +Given a transaction scheduled to execute from given safe, the `rejecting_owners(safe, tx)` are the owners for the safe +that rejected the transaction. + +### Cancellation Threshold + +The `cancellation_threshold(safe)` is the number of owners that must reject a given transaction for it not to be +executed after the `timelock_delay(safe)`. It starts at 1 for each safe, increasing by 1 with each consecutive +cancelled transacton by the given safe up to `blocking_threshold(safe)`, resetting to 1 with each successfully +executed transaction by the given safe. + +## Assumptions + +### aSS-001: Dishonest Users Don't Have Permanent Key Control Over a Quorum of Keys + +We assume that dishonest users have at most a temporary joint key control over a quorum of keys. + +#### Severity: Critical + +If this invariant is broken, honest control of the multisig is lost. + +### aSS-002: The Fallback Owner is Honest + +The fallback owner is chosen by the multisig which can verify that it is honest. + +#### Severity: Medium to High + +If this assumption is false and not known to be false, the LivenessModule is not able to recover the multisig from a +liveness failure state until a new fallback owner is chosen. + +If this assumption is false but not known to be false, and a challenge is successful, the multisig would enter into a +safety failure state. + +### aSS-003: The Fallback Owner is Active + +The fallback owner is assumed to be active. + +## Invariants + +### iSS-001: Honest Users Can Recover From Temporary Key Control Over a Quorum of Keys + +If an attacker has full, joint, or temporary key control over less than a quorum of keys, honest users should always +be able to recover the account by transferring ownership to the fallback owner + +#### Severity: High + +If this invariant is broken, an attacker with temporary key control over less than a quorum of keys could force the +multisig into a temporary safety failure state. + +### iSS-002: The `safe` Chooses The Fallback + +Each `safe` MUST choose its fallback address. No other account should be allowed to do this. + +#### Severity: High + +If this invariant is broken, control over the multisig would be lost in the case of a liveness failure. + +### iSS-003: A Quorum Of Honest Users Retains Ownership + +While a quorum of honest users exist, they should remain in control of the account. + +#### Severity: Medium + +If this invariant is broken, there would be an operational and possibly reputational impact while the ownership of the +account is restablished to the account owners. + +### iSS-004: The Liveness Challenge Period Is Greater Than The Timelock Delay + +The `liveness_challenge_period(safe)` MUST be greater than the `timelock_delay(safe)`. + +#### Severity: Medium + +If this invariant is broken, it would not be possible to respond to challenges, and ownership could be transferred to the fallback owner even if the multisig is not in a liveness failure state. + +### iSS-005: Allowing Challenges Is Elective + +A `safe` MUST choose to be open to liveness challenges at any time, and CAN choose to stop accepting liveness +challenges at any time. + +#### Severity: Medium + +If this invariant is broken, there would be an operational and possibly reputational impact while the ownership of the +account is restablished to the account owners. + +### iSS-006: No Challenge Spam + +For an enabled `safe`, there can't be more challenges that are needed to guarantee liveness. + +#### Severity: Medium + +If this invariant is broken, an attacker could spam the multisig with challenges, driving it into a temporary liveness +failure state, which could be used to force ownership into the fallback address. + +### iSS-007: Honest Users Can Recover From Temporary Key Control Over a Quorum of Keys + +If an attacker has joint or temporary key control over a quorum of keys, honest users should always be able to recover +the account. + +#### Severity: Critical + +If this invariant is broken, honest control of the multisig is lost. + +### iSS-008: Owners Of Child Safes Can Signal Rejection Of Transactions + +A nested safe setup is a safe (parent safe) in which one or more owners are a Gnosis Safe (child safes). There can be +multiple levels of nesting. The one parent safe that is not a child of any other safe is called a root safe. + +For a given transaction in a root safe within a nested safe setup, a scheduled transaction could be rejected by owners +in child safes an arbitrary number of levels away. The `cancellation_threshold(safe)` of a child safe must be +considered before registering that the child safe is rejecting the transaction. + +#### Severity: Medium + +If this invariant is broken, the cancellation flow would be not operational for nested setups, making the timelock less +useful. + +### iSS-009: The Signatures For A Cancelled Transaction Can Not Be Reused + +The signatures for a cancelled transaction can not be reused. + +#### Severity: Medium + +If this invariant is broken, a malicious user could spam the multisig with transactions that must be cancelled, +causing an impact close to a liveness failure through operational overhead. + +### iSS-010: Only Owners Can Signal Rejection Of Transactions + +To avoid rejection spamming, it must be verified upon rejection that the rejecting owner is an owner in the safe +scheduled to execute the transaction, or in one of its child safes. + +#### Severity: Low + +If this invariant is broken, the event history of the timelock would contain useless events. + +## Function Specification + +### `configureLivenessModule` + +Configure the contract as a liveness module by setting the `liveness_challenge_period` and `fallback_owner`. + +- MUST allow an arbitrary number of `safe` contracts to use the contract as a module. +- The contract MUST be enabled as a module on the `safe`. +- MUST set the caller as a `safe`. +- MUST take `liveness_challenge_period` and `fallback_owner` as parameters and store them as related to the `safe`. +- If a challenge exists, it MUST be canceled, including emitting the appropriate events. +- MUST emit a `ModuleConfigured` event with at least `liveness_challenge_period` and `fallback_owner` as parameters. + +### `clearLivenessModule` + +Removes the liveness module configuration by a previously enabled `safe`. + +- The contract MUST NOT be enabled as a module on the `safe`. +- MUST erase the existing `liveness_challenge_period` and `fallback_owner` data related to the calling `safe`. +- If a challenge exists, it MUST be cancelled, including emitting the appropriate events. +- MUST emit a `ModuleCleared` event. + +### `configureTimelockGuard` + +Configure the contract as a timelock guard by setting the `timelock_delay`. + +- MUST allow an arbitrary number of `safe` contracts to use the contract as a guard. +- The contract MUST be enabled as a guard for the `safe`. +- MUST set the caller as a `safe`. +- MUST take `timelock_delay` as a parameter and store is as related to the `safe`. +- MUST emit a `GuardConfigured` event with at least `timelock_delay` as a parameter. + +### `clearTimelockGuard` + +Remove the timelock guard configuration by a previously enabled `safe`. + +- The contract MUST NOT be enabled as a guard for the `safe`. +- MUST erase the existing `timelock_delay` data related to the calling `safe`. +- If a challenge exists, it MUST be cancelled, including emitting the appropriate events. +- MUST emit a `GuardCleared` event. + +### `viewLivenessModuleConfiguration` + +Returns the `liveness_challenge_period` and `fallback_owner` for a given `safe`. + +- MUST never revert. + +### `viewTimelockGuardConfiguration` + +Returns the `timelock_delay` for a given `safe`. + +- MUST never revert. + +### `getLivenessChallengePeriodEnd` + +Returns `challenge_start_time + liveness_challenge_period + timelock_delay` if there is a challenge for the given `safe`, or 0 if not. + +- MUST never revert. + +### `challenge` + +Challenges an enabled `safe` to prove that it is not in a liveness failure state. + +- MUST only be executable by `fallback` owner of the challenged `safe`. +- MUST revert if the `safe` hasn't enabled the contract as a module. +- MUST revert if the `safe` hasn't configured the contract as a module. +- MUST revert if a challenge for the `safe` exists. +- MUST set `challenge_start_time` to the current block time. +- MUST emit the `ChallengeStarted` event. + +### `respond` + +Cancels a challenge for an enabled `safe`. + +- MUST only be executable by an enabled `safe`. +- MUST revert if the `safe` hasn't enabled the contract as a module. +- MUST revert if the `safe` hasn't configured the contract as a module. +- MUST revert if there isn't a challenge for the calling `safe`. +- MUST reset `challenge_start_time` to 0. +- MUST emit the `ChallengeCancelled` event. + +### `changeOwnershipToFallback` + +With a successful challenge, removes all current owners from an enabled `safe`, appoints `fallback` as its sole owner, +and sets its quorum to 1. + +- MUST be executable by anyone. +- MUST revert if the `safe` hasn't enabled the contract as a module. +- MUST revert if the `safe` hasn't configured the contract as a module. +- MUST revert if there isn't a successful challenge for the given `safe`. +- MUST reset `challenge_start_time` to 0 to enable the fallback to start a new challenge. +- MUST emit the `ChallengeExecuted` event. + +### `cancellationThreshold` + +Returns the `cancellation_threshold` for a given safe. + +- MUST return 0 if the contract is not enabled as a guard for the safe. + +### `scheduleTransaction` + +Called by anyone using signatures from Safe owners, registers a transaction in the TimelockGuard for execution after +the `timelock_delay`. + +- MUST revert if the contract is not enabled as a guard for the safe. +- MUST take the same parameters as `execTransaction`. +- MUST revert if an identical transaction has already been scheduled. +- MUST revert if an identical transaction was cancelled. +- MUST emit a `TransactionScheduled` event, with at least `safe` and relevant transaction data. + +To allow for identical transactions to be scheduled more than once, but requiring different signatures for each one, a +`salt` parameter can be included in `data` with the sole purpose of differentiating otherwise identical transactions. + +### `checkTransaction` + +Called by anyone, and also by the Safe in `execTransaction`, verifies if a given transaction was scheduled and the +delay period has passed. + +- MUST revert if the contract is not enabled as a guard for the safe. +- MUST take the exact parameters from the `ITransactionGuard.checkTransaction` interface. +- MUST revert if `scheduling_time(safe, tx) + timelock_delay(safe) < block.timestamp`. +- MUST revert if the scheduled transaction was cancelled. + +### `checkPendingTransactions` + +Called by anyone, returns the list of all scheduled but not cancelled transactions for a given safe. + +*Note:* If we want to exclude executed transactions from this list, the TimelockGuard would need to query into the +storage of the safe, and if a module that overrides replayability is implemented, the TimelockGuard would need to look +into its storage as well. + +### `rejectTransaction` + +Called by a Safe owner, signal the rejection of a scheduled transaction. + +- MUST revert if the contract is not enabled as a guard for the safe. +- MUST revert if not called by an owner of the safe scheduled to execute the transaction, or in one of its child safes. +- MUST be able to reference a transaction scheduled in a different safe. The transaction might not exist. +- MUST emit a `TransactionRejected` event, with at least `safe` and a transaction identifier. + +### `rejectTransactionWithSignature` + +Called by anyone, using signatures form one or more owners, signal the rejection of a scheduled transaction. Can reuse +the `rejectTransaction` function name. + +- MUST revert if the contract is not enabled as a guard for the safe. +- MUST reuse the same internal logic as `rejectTransaction`. + +### `cancelTransaction` + +Called by anyone, verify that the `cancellation_threshold` has been met for the Safe to cancel a given scheduled +transaction. + +- MUST revert if the contract is not enabled as a guard for the safe. +- MUST revert if `scheduling_time(safe, tx) + timelock_delay(safe) >= block.timestamp`. +- MUST revert if `sum(rejecting_owners(safe, tx)) < cancellation_threshold(safe)`. +- MUST emit a `TransactionCancelled` event, with at least `safe` and a transaction identifier. From 93121f877828c4d079686bdfb268010eb4e5097c Mon Sep 17 00:00:00 2001 From: alcueca Date: Sun, 7 Sep 2025 08:04:32 +0100 Subject: [PATCH 02/20] lint and toc --- specs/protocol/safe-extensions-v2.md | 96 ++++++++++++++++++++++------ 1 file changed, 75 insertions(+), 21 deletions(-) diff --git a/specs/protocol/safe-extensions-v2.md b/specs/protocol/safe-extensions-v2.md index d83997220..5169cddd0 100644 --- a/specs/protocol/safe-extensions-v2.md +++ b/specs/protocol/safe-extensions-v2.md @@ -4,31 +4,83 @@ **Table of Contents** -- [Security Council Liveness Checking Extensions](#security-council-liveness-checking-extensions) - - [The Liveness Guard](#the-liveness-guard) - - [The Liveness Module](#the-liveness-module) - - [Owner Removal Call Flow](#owner-removal-call-flow) - - [Shutdown](#shutdown) - - [Liveness Security Properties](#liveness-security-properties) - - [Liveness Guard Security Properties](#liveness-guard-security-properties) - - [Liveness Module Security Properties](#liveness-module-security-properties) - - [Interdependency between the Liveness Guard and Liveness Module](#interdependency-between-the-liveness-guard-and-liveness-module) -- [Operational Considerations](#operational-considerations) - - [Manual validation of new owner liveness](#manual-validation-of-new-owner-liveness) - - [Deploying the Liveness Checking System](#deploying-the-liveness-checking-system) - - [Modifying the Liveness Checking System](#modifying-the-liveness-checking-system) - - [Replacing the Liveness Module](#replacing-the-liveness-module) - - [Replacing the Liveness Guard](#replacing-the-liveness-guard) +- [Definitions](#definitions) + - [Quorum](#quorum) + - [Blocking Threshold](#blocking-threshold) + - [Active Owner](#active-owner) + - [Honest Owner](#honest-owner) + - [Malicious Owner](#malicious-owner) + - [Full Key Control](#full-key-control) + - [Joint Key Control](#joint-key-control) + - [Temporary Key Control](#temporary-key-control) + - [Multisig Liveness Failure](#multisig-liveness-failure) + - [Multisig Safety Failure](#multisig-safety-failure) +- [Definitions in Liveness Module](#definitions-in-liveness-module) + - [Fallback Owner](#fallback-owner) + - [Liveness Challenge](#liveness-challenge) + - [Liveness Challenge Period](#liveness-challenge-period) + - [Successful Challenge](#successful-challenge) +- [Definitions in Timelock Guard](#definitions-in-timelock-guard) + - [Scheduled Transaction](#scheduled-transaction) + - [Scheduling Time](#scheduling-time) + - [Timelock Delay Period](#timelock-delay-period) + - [Rejected Transaction](#rejected-transaction) + - [Rejecting Owners](#rejecting-owners) + - [Cancellation Threshold](#cancellation-threshold) +- [Assumptions](#assumptions) + - [aSS-001: Dishonest Users Don't Have Permanent Key Control Over a Quorum of Keys](#ass-001-dishonest-users-dont-have-permanent-key-control-over-a-quorum-of-keys) + - [Severity: Critical](#severity-critical) + - [aSS-002: The Fallback Owner is Honest](#ass-002-the-fallback-owner-is-honest) + - [Severity: Medium to High](#severity-medium-to-high) + - [aSS-003: The Fallback Owner is Active](#ass-003-the-fallback-owner-is-active) +- [Invariants](#invariants) + - [iSS-001: Honest Users Can Recover From Temporary Key Control Over a Quorum of Keys](#iss-001-honest-users-can-recover-from-temporary-key-control-over-a-quorum-of-keys) + - [Severity: High](#severity-high) + - [iSS-002: The `safe` Chooses The Fallback](#iss-002-the-safe-chooses-the-fallback) + - [Severity: High](#severity-high-1) + - [iSS-003: A Quorum Of Honest Users Retains Ownership](#iss-003-a-quorum-of-honest-users-retains-ownership) + - [Severity: Medium](#severity-medium) + - [iSS-004: The Liveness Challenge Period Is Greater Than The Timelock Delay](#iss-004-the-liveness-challenge-period-is-greater-than-the-timelock-delay) + - [Severity: Medium](#severity-medium-1) + - [iSS-005: Allowing Challenges Is Elective](#iss-005-allowing-challenges-is-elective) + - [Severity: Medium](#severity-medium-2) + - [iSS-006: No Challenge Spam](#iss-006-no-challenge-spam) + - [Severity: Medium](#severity-medium-3) + - [iSS-007: Honest Users Can Recover From Temporary Key Control Over a Quorum of Keys](#iss-007-honest-users-can-recover-from-temporary-key-control-over-a-quorum-of-keys) + - [Severity: Critical](#severity-critical-1) + - [iSS-008: Owners Of Child Safes Can Signal Rejection Of Transactions](#iss-008-owners-of-child-safes-can-signal-rejection-of-transactions) + - [Severity: Medium](#severity-medium-4) + - [iSS-009: The Signatures For A Cancelled Transaction Can Not Be Reused](#iss-009-the-signatures-for-a-cancelled-transaction-can-not-be-reused) + - [Severity: Medium](#severity-medium-5) + - [iSS-010: Only Owners Can Signal Rejection Of Transactions](#iss-010-only-owners-can-signal-rejection-of-transactions) + - [Severity: Low](#severity-low) +- [Function Specification](#function-specification) + - [`configureLivenessModule`](#configurelivenessmodule) + - [`clearLivenessModule`](#clearlivenessmodule) + - [`configureTimelockGuard`](#configuretimelockguard) + - [`clearTimelockGuard`](#cleartimelockguard) + - [`viewLivenessModuleConfiguration`](#viewlivenessmoduleconfiguration) + - [`viewTimelockGuardConfiguration`](#viewtimelockguardconfiguration) + - [`getLivenessChallengePeriodEnd`](#getlivenesschallengeperiodend) + - [`challenge`](#challenge) + - [`respond`](#respond) + - [`changeOwnershipToFallback`](#changeownershiptofallback) + - [`cancellationThreshold`](#cancellationthreshold) + - [`scheduleTransaction`](#scheduletransaction) + - [`checkTransaction`](#checktransaction) + - [`checkPendingTransactions`](#checkpendingtransactions) + - [`rejectTransaction`](#rejecttransaction) + - [`rejectTransactionWithSignature`](#rejecttransactionwithsignature) + - [`cancelTransaction`](#canceltransaction) This document describes extensions to the Security Council and Guardian Safe contracts, which provide additional functionality and security guarantees on top of those provided by the Safe contract. -These extensions are developed using a singleton contract that can be enabled simultanesouly as a -([module](https://docs.safe.global/advanced/smart-account-modules) and a -[guard](https://docs.safe.global/advanced/smart-account-guards)) which the Safe contract has -built-in support for: +These extensions are developed using a singleton contract that can be enabled simultaneously as a +([module](https://docs.safe.global/advanced/smart-account-modules) and a +[guard](https://docs.safe.global/advanced/smart-account-guards)) which the Safe contract has built-in support for: 1. **Guard contracts:** can execute pre- and post- transaction checks. 2. **Module contracts:** a contract which is @@ -214,7 +266,8 @@ The `liveness_challenge_period(safe)` MUST be greater than the `timelock_delay(s #### Severity: Medium -If this invariant is broken, it would not be possible to respond to challenges, and ownership could be transferred to the fallback owner even if the multisig is not in a liveness failure state. +If this invariant is broken, it would not be possible to respond to challenges, and ownership could be transferred to +the fallback owner even if the multisig is not in a liveness failure state. ### iSS-005: Allowing Challenges Is Elective @@ -331,7 +384,8 @@ Returns the `timelock_delay` for a given `safe`. ### `getLivenessChallengePeriodEnd` -Returns `challenge_start_time + liveness_challenge_period + timelock_delay` if there is a challenge for the given `safe`, or 0 if not. +Returns `challenge_start_time + liveness_challenge_period + timelock_delay` if there is a challenge for the given +`safe`, or 0 if not. - MUST never revert. From b224496a48cff7b12ce48fb5980b5eaafba54265 Mon Sep 17 00:00:00 2001 From: alcueca Date: Sun, 7 Sep 2025 08:35:39 +0100 Subject: [PATCH 03/20] spellcheck --- specs/protocol/safe-extensions-v2.md | 2 +- words.txt | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/specs/protocol/safe-extensions-v2.md b/specs/protocol/safe-extensions-v2.md index 5169cddd0..c21416541 100644 --- a/specs/protocol/safe-extensions-v2.md +++ b/specs/protocol/safe-extensions-v2.md @@ -202,7 +202,7 @@ that rejected the transaction. The `cancellation_threshold(safe)` is the number of owners that must reject a given transaction for it not to be executed after the `timelock_delay(safe)`. It starts at 1 for each safe, increasing by 1 with each consecutive -cancelled transacton by the given safe up to `blocking_threshold(safe)`, resetting to 1 with each successfully +cancelled transaction by the given safe up to `blocking_threshold(safe)`, resetting to 1 with each successfully executed transaction by the given safe. ## Assumptions diff --git a/words.txt b/words.txt index 669d688cc..c47d450f4 100644 --- a/words.txt +++ b/words.txt @@ -202,6 +202,8 @@ remotechainid replayability replayable reposted +reputational +restablished returndata Rollups rollups From a3991ed53d26803d2b7f68887f999325148364a2 Mon Sep 17 00:00:00 2001 From: alcueca Date: Sun, 7 Sep 2025 08:50:53 +0100 Subject: [PATCH 04/20] execution time must not change --- specs/protocol/safe-extensions-v2.md | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/specs/protocol/safe-extensions-v2.md b/specs/protocol/safe-extensions-v2.md index c21416541..84bd2629e 100644 --- a/specs/protocol/safe-extensions-v2.md +++ b/specs/protocol/safe-extensions-v2.md @@ -188,6 +188,10 @@ from a given safe. Given a scheduled transaction, the `timelock_delay(safe)` is the minimum time in seconds after `scheduling_time(safe, tx)` that execution is allowed. +### Execution Time + +The `execution_time(safe, tx)` is the sum of `scheduling_time(safe, tx)` and `timelock_delay(safe)`. + ### Rejected Transaction Given a transaction scheduled to execute from given safe, owners for the safe reject the transaction to signal they @@ -297,7 +301,15 @@ the account. If this invariant is broken, honest control of the multisig is lost. -### iSS-008: Owners Of Child Safes Can Signal Rejection Of Transactions +### iSS-008: The Execution Time Doesn't Change + +For a given transaction, the `execution_time(safe, tx)` must never change. + +#### Severity: Medium + +If this invariant is broken, a temporary compromise of the safe could be used to skip the timelock delay. + +### iSS-009: Owners Of Child Safes Can Signal Rejection Of Transactions A nested safe setup is a safe (parent safe) in which one or more owners are a Gnosis Safe (child safes). There can be multiple levels of nesting. The one parent safe that is not a child of any other safe is called a root safe. @@ -311,7 +323,7 @@ considered before registering that the child safe is rejecting the transaction. If this invariant is broken, the cancellation flow would be not operational for nested setups, making the timelock less useful. -### iSS-009: The Signatures For A Cancelled Transaction Can Not Be Reused +### iSS-010: The Signatures For A Cancelled Transaction Can Not Be Reused The signatures for a cancelled transaction can not be reused. @@ -320,7 +332,7 @@ The signatures for a cancelled transaction can not be reused. If this invariant is broken, a malicious user could spam the multisig with transactions that must be cancelled, causing an impact close to a liveness failure through operational overhead. -### iSS-010: Only Owners Can Signal Rejection Of Transactions +### iSS-011: Only Owners Can Signal Rejection Of Transactions To avoid rejection spamming, it must be verified upon rejection that the rejecting owner is an owner in the safe scheduled to execute the transaction, or in one of its child safes. @@ -438,6 +450,7 @@ the `timelock_delay`. - MUST take the same parameters as `execTransaction`. - MUST revert if an identical transaction has already been scheduled. - MUST revert if an identical transaction was cancelled. +- MUST set `execution_time(safe, tx)` to `block.timestamp + timelock_delay(safe)`. - MUST emit a `TransactionScheduled` event, with at least `safe` and relevant transaction data. To allow for identical transactions to be scheduled more than once, but requiring different signatures for each one, a @@ -450,7 +463,7 @@ delay period has passed. - MUST revert if the contract is not enabled as a guard for the safe. - MUST take the exact parameters from the `ITransactionGuard.checkTransaction` interface. -- MUST revert if `scheduling_time(safe, tx) + timelock_delay(safe) < block.timestamp`. +- MUST revert if `execution_time(safe, tx) < block.timestamp`. - MUST revert if the scheduled transaction was cancelled. ### `checkPendingTransactions` @@ -481,9 +494,8 @@ the `rejectTransaction` function name. ### `cancelTransaction` Called by anyone, verify that the `cancellation_threshold` has been met for the Safe to cancel a given scheduled -transaction. +transaction. It can be called at any time. - MUST revert if the contract is not enabled as a guard for the safe. -- MUST revert if `scheduling_time(safe, tx) + timelock_delay(safe) >= block.timestamp`. - MUST revert if `sum(rejecting_owners(safe, tx)) < cancellation_threshold(safe)`. - MUST emit a `TransactionCancelled` event, with at least `safe` and a transaction identifier. From 4311ca53cd9f12cbc5c59d931328ed9c5902d447 Mon Sep 17 00:00:00 2001 From: alcueca Date: Sun, 7 Sep 2025 08:57:15 +0100 Subject: [PATCH 05/20] toc --- specs/protocol/safe-extensions-v2.md | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/specs/protocol/safe-extensions-v2.md b/specs/protocol/safe-extensions-v2.md index 84bd2629e..cd8370aa8 100644 --- a/specs/protocol/safe-extensions-v2.md +++ b/specs/protocol/safe-extensions-v2.md @@ -24,6 +24,7 @@ - [Scheduled Transaction](#scheduled-transaction) - [Scheduling Time](#scheduling-time) - [Timelock Delay Period](#timelock-delay-period) + - [Execution Time](#execution-time) - [Rejected Transaction](#rejected-transaction) - [Rejecting Owners](#rejecting-owners) - [Cancellation Threshold](#cancellation-threshold) @@ -48,11 +49,13 @@ - [Severity: Medium](#severity-medium-3) - [iSS-007: Honest Users Can Recover From Temporary Key Control Over a Quorum of Keys](#iss-007-honest-users-can-recover-from-temporary-key-control-over-a-quorum-of-keys) - [Severity: Critical](#severity-critical-1) - - [iSS-008: Owners Of Child Safes Can Signal Rejection Of Transactions](#iss-008-owners-of-child-safes-can-signal-rejection-of-transactions) + - [iSS-008: The Execution Time Doesn't Change](#iss-008-the-execution-time-doesnt-change) - [Severity: Medium](#severity-medium-4) - - [iSS-009: The Signatures For A Cancelled Transaction Can Not Be Reused](#iss-009-the-signatures-for-a-cancelled-transaction-can-not-be-reused) + - [iSS-009: Owners Of Child Safes Can Signal Rejection Of Transactions](#iss-009-owners-of-child-safes-can-signal-rejection-of-transactions) - [Severity: Medium](#severity-medium-5) - - [iSS-010: Only Owners Can Signal Rejection Of Transactions](#iss-010-only-owners-can-signal-rejection-of-transactions) + - [iSS-010: The Signatures For A Cancelled Transaction Can Not Be Reused](#iss-010-the-signatures-for-a-cancelled-transaction-can-not-be-reused) + - [Severity: Medium](#severity-medium-6) + - [iSS-011: Only Owners Can Signal Rejection Of Transactions](#iss-011-only-owners-can-signal-rejection-of-transactions) - [Severity: Low](#severity-low) - [Function Specification](#function-specification) - [`configureLivenessModule`](#configurelivenessmodule) From 2676887d27a357ecf3b6549b81d5090a3da9210c Mon Sep 17 00:00:00 2001 From: alcueca Date: Mon, 8 Sep 2025 06:54:49 +0100 Subject: [PATCH 06/20] Renamed to Safer Safes, I like the name. --- specs/protocol/{safe-extensions-v2.md => safer-safes.md} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename specs/protocol/{safe-extensions-v2.md => safer-safes.md} (100%) diff --git a/specs/protocol/safe-extensions-v2.md b/specs/protocol/safer-safes.md similarity index 100% rename from specs/protocol/safe-extensions-v2.md rename to specs/protocol/safer-safes.md From 8320204411874ccd4e98b50690bfc81e22d66837 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alberto=20Cuesta=20Ca=C3=B1ada?= Date: Mon, 8 Sep 2025 14:22:27 +0100 Subject: [PATCH 07/20] timelock_delay no longer than 1 year --- specs/protocol/safer-safes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/specs/protocol/safer-safes.md b/specs/protocol/safer-safes.md index cd8370aa8..18f233b42 100644 --- a/specs/protocol/safer-safes.md +++ b/specs/protocol/safer-safes.md @@ -372,6 +372,7 @@ Configure the contract as a timelock guard by setting the `timelock_delay`. - MUST allow an arbitrary number of `safe` contracts to use the contract as a guard. - The contract MUST be enabled as a guard for the `safe`. +- MUST revert if `timelock_delay` is longer than 1 year. - MUST set the caller as a `safe`. - MUST take `timelock_delay` as a parameter and store is as related to the `safe`. - MUST emit a `GuardConfigured` event with at least `timelock_delay` as a parameter. From 3e6d5bf3d415f59ec2fe796b0567a377fd2570bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alberto=20Cuesta=20Ca=C3=B1ada?= Date: Mon, 8 Sep 2025 14:34:13 +0100 Subject: [PATCH 08/20] Pep's feedback --- specs/interop/managed-node.md | 2 +- specs/protocol/safer-safes.md | 12 +++++++----- words.txt | 2 +- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/specs/interop/managed-node.md b/specs/interop/managed-node.md index e7c557fa6..4e8667ab6 100644 --- a/specs/interop/managed-node.md +++ b/specs/interop/managed-node.md @@ -57,7 +57,7 @@ the file containing the jwt secret should be provided to the supervisor instance ## Node `->` Supervisor Events that a supervisor should subscribe to, originating from the node, handled by the supervisor. For the used types, -refer [this](#Types) section. +refer [this](#types) section. Every event sent from the node is of type `ManagedEvent` whose fields are populated with the events that occurred. All non-null events are sent at once. The other fields are omitted. diff --git a/specs/protocol/safer-safes.md b/specs/protocol/safer-safes.md index 18f233b42..abec9c962 100644 --- a/specs/protocol/safer-safes.md +++ b/specs/protocol/safer-safes.md @@ -265,7 +265,7 @@ While a quorum of honest users exist, they should remain in control of the accou #### Severity: Medium If this invariant is broken, there would be an operational and possibly reputational impact while the ownership of the -account is restablished to the account owners. +account is reestablished to the account owners. ### iSS-004: The Liveness Challenge Period Is Greater Than The Timelock Delay @@ -284,7 +284,7 @@ challenges at any time. #### Severity: Medium If this invariant is broken, there would be an operational and possibly reputational impact while the ownership of the -account is restablished to the account owners. +account is reestablished to the account owners. ### iSS-006: No Challenge Spam @@ -443,6 +443,7 @@ and sets its quorum to 1. Returns the `cancellation_threshold` for a given safe. +- MUST NOT revert - MUST return 0 if the contract is not enabled as a guard for the safe. ### `scheduleTransaction` @@ -474,9 +475,10 @@ delay period has passed. Called by anyone, returns the list of all scheduled but not cancelled transactions for a given safe. -*Note:* If we want to exclude executed transactions from this list, the TimelockGuard would need to query into the -storage of the safe, and if a module that overrides replayability is implemented, the TimelockGuard would need to look -into its storage as well. +- MUST NOT revert + +*Note:* If we want to exclude executed transactions from this list, we would need to implement the +`checkAfterExecution` hook and store the executed state for transactions. ### `rejectTransaction` diff --git a/words.txt b/words.txt index c47d450f4..39b7774c6 100644 --- a/words.txt +++ b/words.txt @@ -203,7 +203,7 @@ replayability replayable reposted reputational -restablished +reestablished returndata Rollups rollups From f30b79720a16c406330036f247cf65ba544aadbb Mon Sep 17 00:00:00 2001 From: alcueca Date: Wed, 10 Sep 2025 06:23:31 +0100 Subject: [PATCH 09/20] Removed nonsensical requirement --- specs/protocol/safer-safes.md | 1 - 1 file changed, 1 deletion(-) diff --git a/specs/protocol/safer-safes.md b/specs/protocol/safer-safes.md index abec9c962..3c4fa57bd 100644 --- a/specs/protocol/safer-safes.md +++ b/specs/protocol/safer-safes.md @@ -486,7 +486,6 @@ Called by a Safe owner, signal the rejection of a scheduled transaction. - MUST revert if the contract is not enabled as a guard for the safe. - MUST revert if not called by an owner of the safe scheduled to execute the transaction, or in one of its child safes. -- MUST be able to reference a transaction scheduled in a different safe. The transaction might not exist. - MUST emit a `TransactionRejected` event, with at least `safe` and a transaction identifier. ### `rejectTransactionWithSignature` From eb4b8f58255d3ce17ccd6aa83c049054eac0aea6 Mon Sep 17 00:00:00 2001 From: alcueca Date: Wed, 10 Sep 2025 06:33:23 +0100 Subject: [PATCH 10/20] Added comment about target gnosis safe version --- specs/protocol/safer-safes.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/specs/protocol/safer-safes.md b/specs/protocol/safer-safes.md index 3c4fa57bd..afc332e46 100644 --- a/specs/protocol/safer-safes.md +++ b/specs/protocol/safer-safes.md @@ -90,6 +90,9 @@ These extensions are developed using a singleton contract that can be enabled si authorized to execute transactions via the Safe. This means the liveness module must properly implement auth conditions internally. +The extension contract specificationis compatible with the +[V1.5.0](https://github.com/safe-global/safe-contracts/releases/tag/v1.5.0) Safe contract version. + When enabled as a guard, the extension contract implements a mandatory delay on the execution of transactions. When enabled as a module, the extension contract ensures a multisig remains operable by allowing challenges when it becomes unresponsive. If the multisig fails to prove liveness within a set period, ownership transfers to a trusted fallback From a781432ad7b5d22fbe1bfd04e22800b17e625cd8 Mon Sep 17 00:00:00 2001 From: alcueca Date: Wed, 10 Sep 2025 06:52:07 +0100 Subject: [PATCH 11/20] `changeOwnershipToFallback` access control change --- specs/protocol/safer-safes.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/specs/protocol/safer-safes.md b/specs/protocol/safer-safes.md index afc332e46..879d6f8e5 100644 --- a/specs/protocol/safer-safes.md +++ b/specs/protocol/safer-safes.md @@ -435,7 +435,7 @@ Cancels a challenge for an enabled `safe`. With a successful challenge, removes all current owners from an enabled `safe`, appoints `fallback` as its sole owner, and sets its quorum to 1. -- MUST be executable by anyone. +- MUST only be executable by `fallback` owner of the challenged `safe`. - MUST revert if the `safe` hasn't enabled the contract as a module. - MUST revert if the `safe` hasn't configured the contract as a module. - MUST revert if there isn't a successful challenge for the given `safe`. From 4da95049b66f236f454c7bf1d21b6800a9bdf088 Mon Sep 17 00:00:00 2001 From: alcueca Date: Wed, 17 Sep 2025 10:28:52 +0100 Subject: [PATCH 12/20] Fixed the specs to match the lessons from implementation of the LivenessModule --- specs/protocol/safer-safes.md | 149 +++++++++++++++++++--------------- 1 file changed, 83 insertions(+), 66 deletions(-) diff --git a/specs/protocol/safer-safes.md b/specs/protocol/safer-safes.md index 879d6f8e5..052902743 100644 --- a/specs/protocol/safer-safes.md +++ b/specs/protocol/safer-safes.md @@ -98,6 +98,8 @@ enabled as a module, the extension contract ensures a multisig remains operable unresponsive. If the multisig fails to prove liveness within a set period, ownership transfers to a trusted fallback owner to prevent deadlock. + As a best practice, the fallback owner should have stronger security guarantees than the safe enabling the extensions. + The extensions in this document are intended to replace the extensions in the [Safe Contract Extensions](./safe-extensions.md) document. @@ -349,107 +351,138 @@ If this invariant is broken, the event history of the timelock would contain use ## Function Specification -### `configureLivenessModule` +### Liveness Module -Configure the contract as a liveness module by setting the `liveness_challenge_period` and `fallback_owner`. +The Liveness Module is the same contract as the Timelock Guard, but the function specification is in its own section +for clarity. -- MUST allow an arbitrary number of `safe` contracts to use the contract as a module. -- The contract MUST be enabled as a module on the `safe`. -- MUST set the caller as a `safe`. -- MUST take `liveness_challenge_period` and `fallback_owner` as parameters and store them as related to the `safe`. -- If a challenge exists, it MUST be canceled, including emitting the appropriate events. -- MUST emit a `ModuleConfigured` event with at least `liveness_challenge_period` and `fallback_owner` as parameters. - -### `clearLivenessModule` +#### `version` -Removes the liveness module configuration by a previously enabled `safe`. +Returns the semantic contract version. -- The contract MUST NOT be enabled as a module on the `safe`. -- MUST erase the existing `liveness_challenge_period` and `fallback_owner` data related to the calling `safe`. -- If a challenge exists, it MUST be cancelled, including emitting the appropriate events. -- MUST emit a `ModuleCleared` event. +- MUST never revert. -### `configureTimelockGuard` +#### `livenessSafeConfiguration` -Configure the contract as a timelock guard by setting the `timelock_delay`. +Returns the `liveness_challenge_period` and `fallback_owner` for a given `safe`. -- MUST allow an arbitrary number of `safe` contracts to use the contract as a guard. -- The contract MUST be enabled as a guard for the `safe`. -- MUST revert if `timelock_delay` is longer than 1 year. -- MUST set the caller as a `safe`. -- MUST take `timelock_delay` as a parameter and store is as related to the `safe`. -- MUST emit a `GuardConfigured` event with at least `timelock_delay` as a parameter. +- MUST never revert. -### `clearTimelockGuard` +#### `challengeStartTime` -Remove the timelock guard configuration by a previously enabled `safe`. +Returns `challenge_start_time` if there is a challenge for the given `safe`, or 0 if not. -- The contract MUST NOT be enabled as a guard for the `safe`. -- MUST erase the existing `timelock_delay` data related to the calling `safe`. -- If a challenge exists, it MUST be cancelled, including emitting the appropriate events. -- MUST emit a `GuardCleared` event. +- MUST never revert. -### `viewLivenessModuleConfiguration` +#### `getLivenessChallengePeriodEnd` -Returns the `liveness_challenge_period` and `fallback_owner` for a given `safe`. +Returns `challenge_start_time + liveness_challenge_period + timelock_delay` if there is a challenge for the given +`safe`, or 0 if not. - MUST never revert. -### `viewTimelockGuardConfiguration` +#### `configureLivenessModule` -Returns the `timelock_delay` for a given `safe`. +Configure the contract as a liveness module for a given `safe` by setting the `liveness_challenge_period` and +`fallback_owner`. -- MUST never revert. +- MUST allow an arbitrary number of `safe` contracts to use the contract as a module. +- The contract MUST be enabled as a module on the `safe`. +- MUST set the caller as a `safe`. +- MUST take `liveness_challenge_period` and `fallback_owner` as parameters and store them as related to the `safe`. +- If a challenge exists, it MUST be canceled, including emitting the appropriate events. +- MUST emit a `ModuleConfigured` event with at least `liveness_challenge_period` and `fallback_owner` as parameters. -### `getLivenessChallengePeriodEnd` +#### `clearLivenessModule` -Returns `challenge_start_time + liveness_challenge_period + timelock_delay` if there is a challenge for the given -`safe`, or 0 if not. +Removes the liveness module configuration by a previously enabled `safe`. -- MUST never revert. +- The contract MUST be configured for the `safe`. +- The contract MUST NOT be enabled as a module on the `safe`. +- MUST erase the existing `liveness_challenge_period` and `fallback_owner` data related to the calling `safe`. +- If a challenge exists, it MUST be cancelled, including emitting the appropriate events. +- MUST emit a `ModuleCleared` event. -### `challenge` +#### `challenge` Challenges an enabled `safe` to prove that it is not in a liveness failure state. - MUST only be executable by `fallback` owner of the challenged `safe`. - MUST revert if the `safe` hasn't enabled the contract as a module. -- MUST revert if the `safe` hasn't configured the contract as a module. +- MUST revert if the `safe` hasn't configured the module for the `safe`. - MUST revert if a challenge for the `safe` exists. - MUST set `challenge_start_time` to the current block time. - MUST emit the `ChallengeStarted` event. -### `respond` +#### `respond` Cancels a challenge for an enabled `safe`. -- MUST only be executable by an enabled `safe`. - MUST revert if the `safe` hasn't enabled the contract as a module. -- MUST revert if the `safe` hasn't configured the contract as a module. +- MUST revert if the `safe` hasn't configured the module for the `safe`. - MUST revert if there isn't a challenge for the calling `safe`. - MUST reset `challenge_start_time` to 0. - MUST emit the `ChallengeCancelled` event. -### `changeOwnershipToFallback` +#### `changeOwnershipToFallback` With a successful challenge, removes all current owners from an enabled `safe`, appoints `fallback` as its sole owner, and sets its quorum to 1. - MUST only be executable by `fallback` owner of the challenged `safe`. - MUST revert if the `safe` hasn't enabled the contract as a module. -- MUST revert if the `safe` hasn't configured the contract as a module. +- MUST revert if the `safe` hasn't configured the module for the `safe`. - MUST revert if there isn't a successful challenge for the given `safe`. - MUST reset `challenge_start_time` to 0 to enable the fallback to start a new challenge. +- MUST set the `fallback_owner` as the sole owner of the `safe`. +- MUST set the quorum of the `safe` to 1. - MUST emit the `ChallengeExecuted` event. -### `cancellationThreshold` +### Timelock Guard + +The Timelock Guard is the same contract as the Liveness Module, but the function specification is in its own section +for clarity. + +#### `version` + +Returns the semantic contract version. + +- MUST never revert. + +#### `timelockSafeConfiguration` + +Returns the `timelock_delay` for a given `safe`. + +- MUST never revert. + +#### `configureTimelockGuard` + +Configure the contract as a timelock guard by setting the `timelock_delay`. + +- MUST allow an arbitrary number of `safe` contracts to use the contract as a guard. +- The contract MUST be enabled as a guard for the `safe`. +- MUST revert if `timelock_delay` is longer than 1 year. +- MUST set the caller as a `safe`. +- MUST take `timelock_delay` as a parameter and store is as related to the `safe`. +- MUST emit a `GuardConfigured` event with at least `timelock_delay` as a parameter. + +#### `clearTimelockGuard` + +Remove the timelock guard configuration by a previously enabled `safe`. + +- The contract MUST NOT be enabled as a guard for the `safe`. +- MUST erase the existing `timelock_delay` data related to the calling `safe`. +- If a challenge exists, it MUST be cancelled, including emitting the appropriate events. +- MUST emit a `GuardCleared` event. + +#### `cancellationThreshold` Returns the `cancellation_threshold` for a given safe. - MUST NOT revert - MUST return 0 if the contract is not enabled as a guard for the safe. -### `scheduleTransaction` +#### `scheduleTransaction` Called by anyone using signatures from Safe owners, registers a transaction in the TimelockGuard for execution after the `timelock_delay`. @@ -464,7 +497,7 @@ the `timelock_delay`. To allow for identical transactions to be scheduled more than once, but requiring different signatures for each one, a `salt` parameter can be included in `data` with the sole purpose of differentiating otherwise identical transactions. -### `checkTransaction` +#### `checkTransaction` Called by anyone, and also by the Safe in `execTransaction`, verifies if a given transaction was scheduled and the delay period has passed. @@ -474,7 +507,7 @@ delay period has passed. - MUST revert if `execution_time(safe, tx) < block.timestamp`. - MUST revert if the scheduled transaction was cancelled. -### `checkPendingTransactions` +#### `checkPendingTransactions` Called by anyone, returns the list of all scheduled but not cancelled transactions for a given safe. @@ -483,23 +516,7 @@ Called by anyone, returns the list of all scheduled but not cancelled transactio *Note:* If we want to exclude executed transactions from this list, we would need to implement the `checkAfterExecution` hook and store the executed state for transactions. -### `rejectTransaction` - -Called by a Safe owner, signal the rejection of a scheduled transaction. - -- MUST revert if the contract is not enabled as a guard for the safe. -- MUST revert if not called by an owner of the safe scheduled to execute the transaction, or in one of its child safes. -- MUST emit a `TransactionRejected` event, with at least `safe` and a transaction identifier. - -### `rejectTransactionWithSignature` - -Called by anyone, using signatures form one or more owners, signal the rejection of a scheduled transaction. Can reuse -the `rejectTransaction` function name. - -- MUST revert if the contract is not enabled as a guard for the safe. -- MUST reuse the same internal logic as `rejectTransaction`. - -### `cancelTransaction` +#### `cancelTransaction` Called by anyone, verify that the `cancellation_threshold` has been met for the Safe to cancel a given scheduled transaction. It can be called at any time. From 5d3c83cd439586e50cf441e8a373074f7cbcfc56 Mon Sep 17 00:00:00 2001 From: alcueca Date: Wed, 17 Sep 2025 11:02:07 +0100 Subject: [PATCH 13/20] Updated with the lessons from the Timelock implementation so far --- specs/protocol/safer-safes.md | 101 ++++++++++++++++++++++------------ 1 file changed, 67 insertions(+), 34 deletions(-) diff --git a/specs/protocol/safer-safes.md b/specs/protocol/safer-safes.md index 052902743..6b3d7f4cc 100644 --- a/specs/protocol/safer-safes.md +++ b/specs/protocol/safer-safes.md @@ -182,6 +182,14 @@ prove that it is not in a liveness failure state. The following list defines variables that will be used in TimelockGuard specifically. +### Safe + +The `safe` is the Safe contract that is being extended. + +### Tx Hash + +The `txHash` is the hash of all the transaction data as calculated by the Safe contract. + ### Scheduled Transaction A specific transaction that has been stored in the Timelock, for execution from a given safe. @@ -214,8 +222,16 @@ that rejected the transaction. The `cancellation_threshold(safe)` is the number of owners that must reject a given transaction for it not to be executed after the `timelock_delay(safe)`. It starts at 1 for each safe, increasing by 1 with each consecutive -cancelled transaction by the given safe up to `blocking_threshold(safe)`, resetting to 1 with each successfully -executed transaction by the given safe. +cancelled transaction by the given safe up to the minimum of `blocking_threshold(safe)` and `quorum(safe)`, resetting +to 1 with each successfully executed transaction by the given safe. + +The upper bound of the `cancellation_threshold(safe)` is `blocking_threshold(safe)` because at that point the absent +or malicious owners force the safe into a liveness failure, and the LivenessModule should be used. + +The upper bound of the `cancellation_threshold(safe)` is also `quorum(safe)` because at that point the safe is in a +safety failure, and the LivenessModule should be used if there are enough honest owners left to cancel the challenge +responses. If a malicious party has control over a quorum of keys, and is capable of indefinitely respond to liveness +challenges, other security mechanisms should be used to protect the protocol or its users. ## Assumptions @@ -455,72 +471,89 @@ Returns the `timelock_delay` for a given `safe`. - MUST never revert. +#### `cancellationThreshold` + +Returns the `cancellation_threshold` for a given `safe`. + +- MUST NOT revert +- MUST return 0 if the contract is not enabled as a guard for the `safe`. + +#### `getScheduledTransaction` + +Returns the scheduled transaction for a given `safe` and `txHash`. + +- MUST NOT revert +- MUST return an empty transaction if the transaction is not scheduled. + +#### `getAllScheduledTransactions` + +Called by anyone, returns the list of all scheduled but not cancelled or executed transactions for a given safe. + +- MUST NOT revert +- MUST return the list of all scheduled but not cancelled or executed transactions for the given `safe`. + #### `configureTimelockGuard` Configure the contract as a timelock guard by setting the `timelock_delay`. - MUST allow an arbitrary number of `safe` contracts to use the contract as a guard. -- The contract MUST be enabled as a guard for the `safe`. +- MUST revert if the contract is not enabled as a guard for the `safe`. - MUST revert if `timelock_delay` is longer than 1 year. - MUST set the caller as a `safe`. - MUST take `timelock_delay` as a parameter and store is as related to the `safe`. +- MUST set the `cancellation_threshold` to 1. - MUST emit a `GuardConfigured` event with at least `timelock_delay` as a parameter. #### `clearTimelockGuard` Remove the timelock guard configuration by a previously enabled `safe`. -- The contract MUST NOT be enabled as a guard for the `safe`. +- MUST revert if the contract is enabled as a guard for the `safe`. +- MUST revert if the contract is not configured for the `safe`. - MUST erase the existing `timelock_delay` data related to the calling `safe`. -- If a challenge exists, it MUST be cancelled, including emitting the appropriate events. - MUST emit a `GuardCleared` event. -#### `cancellationThreshold` - -Returns the `cancellation_threshold` for a given safe. - -- MUST NOT revert -- MUST return 0 if the contract is not enabled as a guard for the safe. - #### `scheduleTransaction` Called by anyone using signatures from Safe owners, registers a transaction in the TimelockGuard for execution after the `timelock_delay`. -- MUST revert if the contract is not enabled as a guard for the safe. +- MUST revert if the contract is not enabled as a guard for the `safe`. +- MUST revert if the contract is not configured for the `safe`. - MUST take the same parameters as `execTransaction`. - MUST revert if an identical transaction has already been scheduled. - MUST revert if an identical transaction was cancelled. -- MUST set `execution_time(safe, tx)` to `block.timestamp + timelock_delay(safe)`. +- MUST set `execution_time(safe, txHash)` to `block.timestamp + timelock_delay(safe)`. - MUST emit a `TransactionScheduled` event, with at least `safe` and relevant transaction data. -To allow for identical transactions to be scheduled more than once, but requiring different signatures for each one, a -`salt` parameter can be included in `data` with the sole purpose of differentiating otherwise identical transactions. +#### `cancelTransaction` + +Makes a scheduled transaction not executable. To do so, it builds a no-op transaction at the same nonce as the +scheduled transaction, and verifies that the supplied signatures for such a no-op transaction are valid and amount to +`cancellation_threshold(safe)`. If successful, it increases the `cancellation_threshold(safe)` by 1. + +- MUST revert if the contract is not enabled as a guard for the `safe`. +- MUST revert if the contract is not configured for the `safe`. +- MUST revert if `sum(rejecting_owners(safe, tx)) < cancellation_threshold(safe)`. +- MUST emit a `TransactionCancelled` event, with at least `safe` and a transaction identifier. #### `checkTransaction` -Called by anyone, and also by the Safe in `execTransaction`, verifies if a given transaction was scheduled and the -delay period has passed. +Can be called by anyone. It will be called by the `safe` in `execTransaction` if the contract is enabled as a guard. +It verifies if a given transaction was scheduled and the delay period has passed, and reverts if not. -- MUST revert if the contract is not enabled as a guard for the safe. +- MUST revert if the contract is not enabled as a guard for the `safe`. +- MUST revert if the contract is not configured for the `safe`. - MUST take the exact parameters from the `ITransactionGuard.checkTransaction` interface. - MUST revert if `execution_time(safe, tx) < block.timestamp`. - MUST revert if the scheduled transaction was cancelled. -#### `checkPendingTransactions` +#### `checkAfterExecution` -Called by anyone, returns the list of all scheduled but not cancelled transactions for a given safe. +Can be called by anyone. It will be called by the `safe` in `execTransaction` if the contract is enabled as a guard. +It updates state after the transaction has been executed. -- MUST NOT revert - -*Note:* If we want to exclude executed transactions from this list, we would need to implement the -`checkAfterExecution` hook and store the executed state for transactions. - -#### `cancelTransaction` - -Called by anyone, verify that the `cancellation_threshold` has been met for the Safe to cancel a given scheduled -transaction. It can be called at any time. - -- MUST revert if the contract is not enabled as a guard for the safe. -- MUST revert if `sum(rejecting_owners(safe, tx)) < cancellation_threshold(safe)`. -- MUST emit a `TransactionCancelled` event, with at least `safe` and a transaction identifier. +- MUST take the exact parameters from the `ITransactionGuard.checkTransaction` interface. +- MUST revert if the contract is not enabled as a guard for the `safe`. // TODO: I'm not sure if we want to ever revert here +- MUST revert if the contract is not configured for the `safe`. // TODO: I'm not sure if we want to ever revert here +- MUST set `cancellation_threshold(safe)` to 1. From d29efcf4626de1c835d2f69f5144bd64ab9875b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alberto=20Cuesta=20Ca=C3=B1ada?= Date: Thu, 18 Sep 2025 14:41:29 +0100 Subject: [PATCH 14/20] Remove `checkAfterTransaction` --- specs/protocol/safer-safes.md | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/specs/protocol/safer-safes.md b/specs/protocol/safer-safes.md index 6b3d7f4cc..c73cd7156 100644 --- a/specs/protocol/safer-safes.md +++ b/specs/protocol/safer-safes.md @@ -543,17 +543,10 @@ Can be called by anyone. It will be called by the `safe` in `execTransaction` if It verifies if a given transaction was scheduled and the delay period has passed, and reverts if not. - MUST revert if the contract is not enabled as a guard for the `safe`. -- MUST revert if the contract is not configured for the `safe`. - MUST take the exact parameters from the `ITransactionGuard.checkTransaction` interface. -- MUST revert if `execution_time(safe, tx) < block.timestamp`. +- MUST revert if `timelock_delay(safe) > 0` and `execution_time(safe, tx) < block.timestamp`. - MUST revert if the scheduled transaction was cancelled. - -#### `checkAfterExecution` - -Can be called by anyone. It will be called by the `safe` in `execTransaction` if the contract is enabled as a guard. -It updates state after the transaction has been executed. - -- MUST take the exact parameters from the `ITransactionGuard.checkTransaction` interface. -- MUST revert if the contract is not enabled as a guard for the `safe`. // TODO: I'm not sure if we want to ever revert here -- MUST revert if the contract is not configured for the `safe`. // TODO: I'm not sure if we want to ever revert here - MUST set `cancellation_threshold(safe)` to 1. + +Note that if the contract is enabled but not configured for the safe, then `timelock_delay(safe) == 0` and the check +passes. This is intentional to avoid bricking the multisig if the guard is enabled but not configured. From 618aec24ff4bdb437a00b2d9f6944682c9dfeabc Mon Sep 17 00:00:00 2001 From: alcueca Date: Fri, 19 Sep 2025 06:43:19 +0100 Subject: [PATCH 15/20] Add DPM capabilities --- specs/protocol/safer-safes.md | 28 ++++++++++++++++------------ 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/specs/protocol/safer-safes.md b/specs/protocol/safer-safes.md index c73cd7156..5a54e0b78 100644 --- a/specs/protocol/safer-safes.md +++ b/specs/protocol/safer-safes.md @@ -494,24 +494,17 @@ Called by anyone, returns the list of all scheduled but not cancelled or execute #### `configureTimelockGuard` -Configure the contract as a timelock guard by setting the `timelock_delay`. +Configure the contract as a timelock guard by setting the `timelock_delay` and `safety_delay`. - MUST allow an arbitrary number of `safe` contracts to use the contract as a guard. - MUST revert if the contract is not enabled as a guard for the `safe`. - MUST revert if `timelock_delay` is longer than 1 year. +- MUST revert if `safety_delay` is longer than 1 year. - MUST set the caller as a `safe`. -- MUST take `timelock_delay` as a parameter and store is as related to the `safe`. +- MUST take `timelock_delay` as a parameter and store it as related to the `safe`. +- MUST take `safety_delay` as a parameter and store it as related to the `safe`. - MUST set the `cancellation_threshold` to 1. -- MUST emit a `GuardConfigured` event with at least `timelock_delay` as a parameter. - -#### `clearTimelockGuard` - -Remove the timelock guard configuration by a previously enabled `safe`. - -- MUST revert if the contract is enabled as a guard for the `safe`. -- MUST revert if the contract is not configured for the `safe`. -- MUST erase the existing `timelock_delay` data related to the calling `safe`. -- MUST emit a `GuardCleared` event. +- MUST emit a `GuardConfigured` event with at least `timelock_delay` and `safety_delay` as parameters. #### `scheduleTransaction` @@ -537,6 +530,16 @@ scheduled transaction, and verifies that the supplied signatures for such a no-o - MUST revert if `sum(rejecting_owners(safe, tx)) < cancellation_threshold(safe)`. - MUST emit a `TransactionCancelled` event, with at least `safe` and a transaction identifier. +#### `slowTimelockGuard` + +Pauses the timelock guard for a given `safe`. + +- MUST revert if the contract is not enabled as a guard for the `safe`. +- MUST revert if the contract is not configured for the `safe`. +- MUST revert if a quorum of valid signatures from Safe owners is not provided as a parameter. +- MUST revert if the quorum of signatures was already used. +- MUST toggle the `slow` state of the `safe`. + #### `checkTransaction` Can be called by anyone. It will be called by the `safe` in `execTransaction` if the contract is enabled as a guard. @@ -544,6 +547,7 @@ It verifies if a given transaction was scheduled and the delay period has passed - MUST revert if the contract is not enabled as a guard for the `safe`. - MUST take the exact parameters from the `ITransactionGuard.checkTransaction` interface. +- MUST add the `safety_delay(safe)` to the `execution_time(safe, tx)` if the `safe` is `slow`. - MUST revert if `timelock_delay(safe) > 0` and `execution_time(safe, tx) < block.timestamp`. - MUST revert if the scheduled transaction was cancelled. - MUST set `cancellation_threshold(safe)` to 1. From ae913c373d0ae091ac40e2bd85532174042595f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alberto=20Cuesta=20Ca=C3=B1ada?= Date: Mon, 22 Sep 2025 16:15:19 +0100 Subject: [PATCH 16/20] Fixed wrong definition of `blocking_threshold` --- specs/protocol/safer-safes.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/specs/protocol/safer-safes.md b/specs/protocol/safer-safes.md index 5a54e0b78..b3e8a8049 100644 --- a/specs/protocol/safer-safes.md +++ b/specs/protocol/safer-safes.md @@ -117,7 +117,7 @@ The number of owners required to execute a transaction. ### Blocking Threshold The minimum number of owners not willing to execute a transaction, that by not approving the transaction guarantee -that `quorum` is not met. It is defined as `min(quorum, total_owners - quorum + 1)`. +that `quorum` is not met. It is defined as `total_owners - quorum + 1`. ### Active Owner From ba577ff3ce2265c4462581fd33b727f7f72e5281 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alberto=20Cuesta=20Ca=C3=B1ada?= Date: Thu, 16 Oct 2025 14:27:54 +0100 Subject: [PATCH 17/20] Update specs for TimelockGuard --- specs/protocol/safer-safes.md | 107 ++++++++++++++++++---------------- 1 file changed, 56 insertions(+), 51 deletions(-) diff --git a/specs/protocol/safer-safes.md b/specs/protocol/safer-safes.md index b3e8a8049..f8e99a03d 100644 --- a/specs/protocol/safer-safes.md +++ b/specs/protocol/safer-safes.md @@ -21,6 +21,8 @@ - [Liveness Challenge Period](#liveness-challenge-period) - [Successful Challenge](#successful-challenge) - [Definitions in Timelock Guard](#definitions-in-timelock-guard) + - [Safe](#safe) + - [Tx Hash](#tx-hash) - [Scheduled Transaction](#scheduled-transaction) - [Scheduling Time](#scheduling-time) - [Timelock Delay Period](#timelock-delay-period) @@ -58,28 +60,33 @@ - [iSS-011: Only Owners Can Signal Rejection Of Transactions](#iss-011-only-owners-can-signal-rejection-of-transactions) - [Severity: Low](#severity-low) - [Function Specification](#function-specification) - - [`configureLivenessModule`](#configurelivenessmodule) - - [`clearLivenessModule`](#clearlivenessmodule) - - [`configureTimelockGuard`](#configuretimelockguard) - - [`clearTimelockGuard`](#cleartimelockguard) - - [`viewLivenessModuleConfiguration`](#viewlivenessmoduleconfiguration) - - [`viewTimelockGuardConfiguration`](#viewtimelockguardconfiguration) - - [`getLivenessChallengePeriodEnd`](#getlivenesschallengeperiodend) - - [`challenge`](#challenge) - - [`respond`](#respond) - - [`changeOwnershipToFallback`](#changeownershiptofallback) - - [`cancellationThreshold`](#cancellationthreshold) - - [`scheduleTransaction`](#scheduletransaction) - - [`checkTransaction`](#checktransaction) - - [`checkPendingTransactions`](#checkpendingtransactions) - - [`rejectTransaction`](#rejecttransaction) - - [`rejectTransactionWithSignature`](#rejecttransactionwithsignature) - - [`cancelTransaction`](#canceltransaction) + - [Liveness Module](#liveness-module) + - [`version`](#version) + - [`livenessSafeConfiguration`](#livenesssafeconfiguration) + - [`challengeStartTime`](#challengestarttime) + - [`getLivenessChallengePeriodEnd`](#getlivenesschallengeperiodend) + - [`configureLivenessModule`](#configurelivenessmodule) + - [`clearLivenessModule`](#clearlivenessmodule) + - [`challenge`](#challenge) + - [`respond`](#respond) + - [`changeOwnershipToFallback`](#changeownershiptofallback) + - [Timelock Guard](#timelock-guard) + - [`version`](#version-1) + - [`timelockSafeConfiguration`](#timelocksafeconfiguration) + - [`cancellationThreshold`](#cancellationthreshold) + - [`timelockDelay`](#timelockdelay) + - [`getScheduledTransaction`](#getscheduledtransaction) + - [`pendingTransactions`](#pendingtransactions) + - [`configureTimelockGuard`](#configuretimelockguard) + - [`scheduleTransaction`](#scheduletransaction) + - [`cancelTransaction`](#canceltransaction) + - [`signCancellation`](#signcancellation) + - [`checkTransaction`](#checktransaction) + - [`checkAfterExecution`](#checkafterexecution) -This document describes extensions to the Security Council and Guardian Safe contracts, which -provide additional functionality and security guarantees on top of those provided by the Safe -contract. +This document describes extensions to the Gnosis Safe contracts, which provide additional functionality and security +guarantees on top of those already provided by standard contract. These extensions are developed using a singleton contract that can be enabled simultaneously as a ([module](https://docs.safe.global/advanced/smart-account-modules) and a @@ -90,22 +97,18 @@ These extensions are developed using a singleton contract that can be enabled si authorized to execute transactions via the Safe. This means the liveness module must properly implement auth conditions internally. -The extension contract specificationis compatible with the +The extension contract specification is compatible with the [V1.5.0](https://github.com/safe-global/safe-contracts/releases/tag/v1.5.0) Safe contract version. When enabled as a guard, the extension contract implements a mandatory delay on the execution of transactions. When -enabled as a module, the extension contract ensures a multisig remains operable by allowing challenges when it becomes -unresponsive. If the multisig fails to prove liveness within a set period, ownership transfers to a trusted fallback - owner to prevent deadlock. +enabled as a module, the extension contract ensures a multisig remains operable by allowing liveness challenges by a +trusted fallback address. If the multisig fails to prove liveness within a set period, ownership transfers to the fallback to prevent deadlock. As a best practice, the fallback owner should have stronger security guarantees than the safe enabling the extensions. The extensions in this document are intended to replace the extensions in the [Safe Contract Extensions](./safe-extensions.md) document. -For more information about the Security Council and Guardian roles, refer to the -[Stage One Roles and Requirements](./stage-1.md) document. - ## Definitions The following list defines variables that will be used in the Safe Extensions V2. @@ -392,7 +395,7 @@ Returns `challenge_start_time` if there is a challenge for the given `safe`, or #### `getLivenessChallengePeriodEnd` -Returns `challenge_start_time + liveness_challenge_period + timelock_delay` if there is a challenge for the given +Returns `challenge_start_time + liveness_challenge_period` if there is a challenge for the given `safe`, or 0 if not. - MUST never revert. @@ -406,15 +409,15 @@ Configure the contract as a liveness module for a given `safe` by setting the `l - The contract MUST be enabled as a module on the `safe`. - MUST set the caller as a `safe`. - MUST take `liveness_challenge_period` and `fallback_owner` as parameters and store them as related to the `safe`. -- If a challenge exists, it MUST be canceled, including emitting the appropriate events. - MUST emit a `ModuleConfigured` event with at least `liveness_challenge_period` and `fallback_owner` as parameters. +- IF a challenge exists, it MUST be canceled, including emitting the appropriate events. +- IF LivenessModule is enabled, `liveness_challenge_period >= timelock_delay * 2` #### `clearLivenessModule` Removes the liveness module configuration by a previously enabled `safe`. - The contract MUST be configured for the `safe`. -- The contract MUST NOT be enabled as a module on the `safe`. - MUST erase the existing `liveness_challenge_period` and `fallback_owner` data related to the calling `safe`. - If a challenge exists, it MUST be cancelled, including emitting the appropriate events. - MUST emit a `ModuleCleared` event. @@ -476,7 +479,12 @@ Returns the `timelock_delay` for a given `safe`. Returns the `cancellation_threshold` for a given `safe`. - MUST NOT revert -- MUST return 0 if the contract is not enabled as a guard for the `safe`. + +#### `timelockDelay` + +Returns the `timelock_delay` for a given `safe`. + +- MUST NOT revert #### `getScheduledTransaction` @@ -485,7 +493,7 @@ Returns the scheduled transaction for a given `safe` and `txHash`. - MUST NOT revert - MUST return an empty transaction if the transaction is not scheduled. -#### `getAllScheduledTransactions` +#### `pendingTransactions` Called by anyone, returns the list of all scheduled but not cancelled or executed transactions for a given safe. @@ -494,17 +502,15 @@ Called by anyone, returns the list of all scheduled but not cancelled or execute #### `configureTimelockGuard` -Configure the contract as a timelock guard by setting the `timelock_delay` and `safety_delay`. +Configure the contract as a timelock guard by setting the `timelock_delay`. - MUST allow an arbitrary number of `safe` contracts to use the contract as a guard. -- MUST revert if the contract is not enabled as a guard for the `safe`. - MUST revert if `timelock_delay` is longer than 1 year. -- MUST revert if `safety_delay` is longer than 1 year. - MUST set the caller as a `safe`. - MUST take `timelock_delay` as a parameter and store it as related to the `safe`. -- MUST take `safety_delay` as a parameter and store it as related to the `safe`. - MUST set the `cancellation_threshold` to 1. -- MUST emit a `GuardConfigured` event with at least `timelock_delay` and `safety_delay` as parameters. +- MUST emit a `GuardConfigured` event with at least `timelock_delay` as a parameter. +- IF LivenessModule is enabled, `liveness_challenge_period >= timelock_delay * 2` #### `scheduleTransaction` @@ -521,24 +527,16 @@ the `timelock_delay`. #### `cancelTransaction` -Makes a scheduled transaction not executable. To do so, it builds a no-op transaction at the same nonce as the -scheduled transaction, and verifies that the supplied signatures for such a no-op transaction are valid and amount to -`cancellation_threshold(safe)`. If successful, it increases the `cancellation_threshold(safe)` by 1. +Makes a scheduled transaction not executable. To do so, it builds a transaction for a no-op `signCancellation` function, and verifies that the supplied signatures for such a no-op transaction are valid and amount to `cancellation_threshold(safe)`. If successful, it increases the `cancellation_threshold(safe)` by 1. - MUST revert if the contract is not enabled as a guard for the `safe`. - MUST revert if the contract is not configured for the `safe`. - MUST revert if `sum(rejecting_owners(safe, tx)) < cancellation_threshold(safe)`. - MUST emit a `TransactionCancelled` event, with at least `safe` and a transaction identifier. -#### `slowTimelockGuard` +#### `signCancellation` -Pauses the timelock guard for a given `safe`. - -- MUST revert if the contract is not enabled as a guard for the `safe`. -- MUST revert if the contract is not configured for the `safe`. -- MUST revert if a quorum of valid signatures from Safe owners is not provided as a parameter. -- MUST revert if the quorum of signatures was already used. -- MUST toggle the `slow` state of the `safe`. +No-op function to facilitate using the Gnosis Safe facilities to cancel scheduled functions. #### `checkTransaction` @@ -546,11 +544,18 @@ Can be called by anyone. It will be called by the `safe` in `execTransaction` if It verifies if a given transaction was scheduled and the delay period has passed, and reverts if not. - MUST revert if the contract is not enabled as a guard for the `safe`. +- MUST succeed if the contract is enabled but not configured. - MUST take the exact parameters from the `ITransactionGuard.checkTransaction` interface. - MUST add the `safety_delay(safe)` to the `execution_time(safe, tx)` if the `safe` is `slow`. -- MUST revert if `timelock_delay(safe) > 0` and `execution_time(safe, tx) < block.timestamp`. +- MUST revert if `timelock_delay(safe) > 0` and `execution_time(safe, tx) > block.timestamp`. - MUST revert if the scheduled transaction was cancelled. - MUST set `cancellation_threshold(safe)` to 1. -Note that if the contract is enabled but not configured for the safe, then `timelock_delay(safe) == 0` and the check -passes. This is intentional to avoid bricking the multisig if the guard is enabled but not configured. +#### `checkAfterExecution` + +Hook called by the Safe after transaction execution when the guard is enabled. + +- MUST never revert. +- MUST take the exact parameters from the `ITransactionGuard.checkAfterExecution` interface. +- MUST reset the `cancellation_threshold` to 1 if the transaction was successful. +- MUST emit a `TransactionExecuted` event, with at least `safe` and a transaction identifier. \ No newline at end of file From ef06cd3c6fffec093baf594f4c1f6632a5e0c4ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alberto=20Cuesta=20Ca=C3=B1ada?= Date: Thu, 16 Oct 2025 14:50:26 +0100 Subject: [PATCH 18/20] Updated specs for LivenessModule --- specs/protocol/safer-safes.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/specs/protocol/safer-safes.md b/specs/protocol/safer-safes.md index f8e99a03d..e9751ae50 100644 --- a/specs/protocol/safer-safes.md +++ b/specs/protocol/safer-safes.md @@ -440,7 +440,7 @@ Cancels a challenge for an enabled `safe`. - MUST revert if the `safe` hasn't enabled the contract as a module. - MUST revert if the `safe` hasn't configured the module for the `safe`. - MUST revert if there isn't a challenge for the calling `safe`. -- MUST reset `challenge_start_time` to 0. +- MUST reset `challenge_start_time` to 0 for the calling `safe`. - MUST emit the `ChallengeCancelled` event. #### `changeOwnershipToFallback` @@ -455,7 +455,7 @@ and sets its quorum to 1. - MUST reset `challenge_start_time` to 0 to enable the fallback to start a new challenge. - MUST set the `fallback_owner` as the sole owner of the `safe`. - MUST set the quorum of the `safe` to 1. -- MUST emit the `ChallengeExecuted` event. +- MUST emit the `ChallengeSucceeded` event. ### Timelock Guard @@ -544,6 +544,7 @@ Can be called by anyone. It will be called by the `safe` in `execTransaction` if It verifies if a given transaction was scheduled and the delay period has passed, and reverts if not. - MUST revert if the contract is not enabled as a guard for the `safe`. +- MUST revert if the executor of the transaction is not an owner for the `safe`. - MUST succeed if the contract is enabled but not configured. - MUST take the exact parameters from the `ITransactionGuard.checkTransaction` interface. - MUST add the `safety_delay(safe)` to the `execution_time(safe, tx)` if the `safe` is `slow`. @@ -551,6 +552,8 @@ It verifies if a given transaction was scheduled and the delay period has passed - MUST revert if the scheduled transaction was cancelled. - MUST set `cancellation_threshold(safe)` to 1. +Note that we are tightening the security properties of the Safe by requiring that only owners can execute. This way an attacker must obtain a private key in addition to phishing enough signatures to schedule and execute. + #### `checkAfterExecution` Hook called by the Safe after transaction execution when the guard is enabled. From 9186edf15d170b740bbfb2493aca4d9870b67bc9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alberto=20Cuesta=20Ca=C3=B1ada?= Date: Thu, 16 Oct 2025 15:10:53 +0100 Subject: [PATCH 19/20] Fixed line length --- specs/protocol/safer-safes.md | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/specs/protocol/safer-safes.md b/specs/protocol/safer-safes.md index e9751ae50..df4c61120 100644 --- a/specs/protocol/safer-safes.md +++ b/specs/protocol/safer-safes.md @@ -199,8 +199,8 @@ A specific transaction that has been stored in the Timelock, for execution from ### Scheduling Time -The `scheduling_time(safe, tx)` is the time in seconds of the block in which a transaction was scheduled for execution -from a given safe. +The `scheduling_time(safe, tx)` is the time in seconds of the block in which a transaction was scheduled +for execution from a given safe. ### Timelock Delay Period @@ -527,7 +527,9 @@ the `timelock_delay`. #### `cancelTransaction` -Makes a scheduled transaction not executable. To do so, it builds a transaction for a no-op `signCancellation` function, and verifies that the supplied signatures for such a no-op transaction are valid and amount to `cancellation_threshold(safe)`. If successful, it increases the `cancellation_threshold(safe)` by 1. +Makes a scheduled transaction not executable. To do so, it builds a transaction for a no-op `signCancellation` function, +and verifies that the supplied signatures for such a no-op transaction are valid and amount to +`cancellation_threshold(safe)`. If successful, it increases the `cancellation_threshold(safe)` by 1. - MUST revert if the contract is not enabled as a guard for the `safe`. - MUST revert if the contract is not configured for the `safe`. @@ -552,7 +554,8 @@ It verifies if a given transaction was scheduled and the delay period has passed - MUST revert if the scheduled transaction was cancelled. - MUST set `cancellation_threshold(safe)` to 1. -Note that we are tightening the security properties of the Safe by requiring that only owners can execute. This way an attacker must obtain a private key in addition to phishing enough signatures to schedule and execute. +Note that we are tightening the security properties of the Safe by requiring that only owners can execute. +This way an attacker must obtain a private key in addition to phishing enough signatures to schedule and execute. #### `checkAfterExecution` From 62f3097e9e0ff6e30a3df13ba07c5e4f4658ad17 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alberto=20Cuesta=20Ca=C3=B1ada?= Date: Fri, 17 Oct 2025 13:06:20 +0100 Subject: [PATCH 20/20] Remove guards on ownership change --- specs/protocol/safer-safes.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/specs/protocol/safer-safes.md b/specs/protocol/safer-safes.md index df4c61120..51524999c 100644 --- a/specs/protocol/safer-safes.md +++ b/specs/protocol/safer-safes.md @@ -455,8 +455,11 @@ and sets its quorum to 1. - MUST reset `challenge_start_time` to 0 to enable the fallback to start a new challenge. - MUST set the `fallback_owner` as the sole owner of the `safe`. - MUST set the quorum of the `safe` to 1. +- MUST remove any Guard installed on the Safe. - MUST emit the `ChallengeSucceeded` event. +Note that if the Safe has a Guard enabled, it is removed. This is in case that the Guard is bricking the Safe. + ### Timelock Guard The Timelock Guard is the same contract as the Liveness Module, but the function specification is in its own section