Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
N
nebula
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
exchain
nebula
Commits
d9163367
Unverified
Commit
d9163367
authored
Sep 27, 2023
by
Maurelian
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
feat(ctb): Add outline for Safe liveness checks
parent
986b2b0e
Changes
4
Hide whitespace changes
Inline
Side-by-side
Showing
4 changed files
with
235 additions
and
3 deletions
+235
-3
.gitmodules
.gitmodules
+0
-3
LivenessGuard.sol
packages/contracts-bedrock/src/Safe/LivenessGuard.sol
+105
-0
LivenessModule.sol
packages/contracts-bedrock/src/Safe/LivenessModule.sol
+1
-0
SafeLivenessChecking.t.sol
packages/contracts-bedrock/test/SafeLivenessChecking.t.sol
+129
-0
No files found.
.gitmodules
View file @
d9163367
...
@@ -17,6 +17,3 @@
...
@@ -17,6 +17,3 @@
path = packages/contracts-bedrock/lib/safe-contracts
path = packages/contracts-bedrock/lib/safe-contracts
url = https://github.com/safe-global/safe-contracts
url = https://github.com/safe-global/safe-contracts
branch = v1.4.0
branch = v1.4.0
packages/contracts-bedrock/src/Safe/LivenessGuard.sol
0 → 100644
View file @
d9163367
// SPDX-License-Identifier: MIT
pragma solidity 0.8.15;
import { Safe } from "safe-contracts/Safe.sol";
import { BaseGuard } from "safe-contracts/base/GuardManager.sol";
import { SignatureDecoder } from "safe-contracts/common/SignatureDecoder.sol";
import { Enum } from "safe-contracts/common/Enum.sol";
contract LivenessGuard is SignatureDecoder, BaseGuard {
Safe public safe;
mapping(address => uint256) public lastSigned;
constructor(Safe _safe) {
safe = _safe;
}
/// @notice We just need to satisfy the BaseGuard interfae, but we don't actually need to use this method.
function checkAfterExecution(bytes32 txHash, bool success) external {
return;
}
/// @notice This checkTransaction implementation records the most recent time which any owner has signed a
/// transaction.
function checkTransaction(
address to,
uint256 value,
bytes memory data,
Enum.Operation operation,
uint256 safeTxGas,
uint256 baseGas,
uint256 gasPrice,
address gasToken,
address payable refundReceiver,
bytes memory signatures,
address msgSender
)
external
{
require(msg.sender == address(safe), "LivenessGuard: only Safe can call this function");
if (to == address(safe) && data[0:4] == bytes4(keccak256("setGuard(address)"))) {
// We can't allow the guard to be disabled, or else the upgrade delay can be bypassed.
// TODO(Maurelian): Figure out how to best address this.
}
// This is a bit of a hack, maybe just replicate the functionality here rather than calling home
bytes memory txHashData = Safe(payable(msg.sender)).encodeTransactionData(
// Transaction info
to,
value,
data,
operation,
safeTxGas,
// Payment info
baseGas,
gasPrice,
gasToken,
refundReceiver,
// Signature info
Safe(payable(msg.sender)).nonce() // check that this works
);
address[] memory signers = _getNSigners(keccak256(txHashData), signatures);
for (uint256 i = 0; i < signers.length; i++) {
lastSigned[signers[i]] = block.timestamp;
}
}
function _getNSigners(bytes32 dataHash, bytes memory signatures) internal returns (address[] memory _owners) {
uint256 numSignatures = signatures.length / 65; // division OK?
_owners = new address[](numSignatures);
// The following code is extracted from the Safe.checkNSignatures() method. It removes the signature validation
// code,
// and keeps only the parsing code necessary to extract the owner addresses from the signatures.
// We do not double check if the owner derived from a signature is valid. As tHis is handled in
// the final require statement of Safe.checkNSignatures().
address currentOwner;
uint8 v;
bytes32 r;
bytes32 s;
uint256 i;
for (i = 0; i < numSignatures; i++) {
(v, r, s) = signatureSplit(signatures, i);
if (v == 0) {
// If v is 0 then it is a contract signature
// When handling contract signatures the address of the contract is encoded into r
currentOwner = address(uint160(uint256(r)));
} else if (v == 1) {
// If v is 1 then it is an approved hash
// When handling approved hashes the address of the approver is encoded into r
currentOwner = address(uint160(uint256(r)));
} else if (v > 30) {
// If v > 30 then default va (27,28) has been adjusted for eth_sign flow
// To support eth_sign and similar we adjust v and hash the messageHash with the Ethereum message prefix
// before applying ecrecover
currentOwner =
ecrecover(keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", dataHash)), v - 4, r, s);
} else {
// Default is the ecrecover flow with the provided data hash
// Use ecrecover with the messageHash for EOA signatures
currentOwner = ecrecover(dataHash, v, r, s);
}
_owners[i] = currentOwner;
}
}
}
packages/contracts-bedrock/src/Safe/LivenessModule.sol
0 → 100644
View file @
d9163367
packages/contracts-bedrock/test/SafeLivenessChecking.t.sol
0 → 100644
View file @
d9163367
// SPDX-License-Identifier: MIT
pragma solidity 0.8.15;
import { Test } from "forge-std/Test.sol";
import { Safe } from "safe-contracts/Safe.sol";
import { SafeProxyFactory } from "safe-contracts/proxies/SafeProxyFactory.sol";
import { ModuleManager } from "safe-contracts/base/ModuleManager.sol";
import { Enum } from "safe-contracts/common/Enum.sol";
import { LivenessGuard } from "src/Safe/LivenessGuard.sol";
contract LivnessGuard_TestInit is Test {
struct Signer {
address owner;
uint256 pk;
}
LivenessGuard livenessGuard;
Safe safe;
string mnemonic = "test test test test test test test test test test test junk";
Signer[] signers;
function newSigner(uint256 index) public returns (Signer memory signer_) {
signer_.pk = vm.deriveKey(mnemonic, uint32(index));
signer_.owner = vm.addr(signer_.pk);
}
function signTransaction(
uint256 _pk,
address _to,
uint256 _value,
bytes memory _data
)
public
view
returns (bytes memory sig_)
{
bytes32 txDataHash;
{
txDataHash = safe.getTransactionHash({
to: _to,
value: _value,
data: _data,
operation: Enum.Operation.Call,
safeTxGas: 0,
baseGas: 0,
gasPrice: 0,
gasToken: address(0),
refundReceiver: address(0),
_nonce: safe.nonce()
});
}
(uint8 v, bytes32 r, bytes32 s) = vm.sign(_pk, txDataHash);
sig_ = abi.encodePacked(v, r, s);
}
function exec(Signer[] memory _signers, address _to, bytes memory _data) internal {
bytes memory sig;
for (uint256 i; i < _signers.length; i++) {
bytes.concat(sig, signTransaction(_signers[i].pk, address(safe), 0, _data));
}
safe.execTransaction({
to: _to,
value: 0,
data: _data,
operation: Enum.Operation.Call,
safeTxGas: 0,
baseGas: 0,
gasPrice: 0,
gasToken: address(0),
refundReceiver: payable(0),
signatures: sig
});
}
// @dev Create a new Safe instance with a minimimal proxy and implementation.
function newSafe(Signer[] memory _signers) internal returns (Safe safe_) {
SafeProxyFactory safeProxyFactory = new SafeProxyFactory();
Safe safeSingleton = new Safe();
bytes memory initData = abi.encodeWithSelector(
Safe.setup.selector, _signers, 2, address(0), hex"", address(0), address(0), 0, address(0)
);
safe_ = Safe(payable(safeProxyFactory.createProxyWithNonce(address(safeSingleton), initData, block.timestamp)));
}
function setUp() public {
// Create 3 signers
for (uint256 i; i < 3; i++) {
signers.push(newSigner(i));
}
Signer[] memory signers_ = signers;
safe = newSafe(signers_);
livenessGuard = new LivenessGuard(safe);
// enable the module
bytes memory data = abi.encodeCall(ModuleManager.enableModule, (address(livenessGuard)));
bytes memory sig1 = signTransaction(signers[0].pk, address(safe), 0, data);
bytes memory sig2 = signTransaction(signers[1].pk, address(safe), 0, data);
bytes memory sigs = bytes.concat(sig1, sig2);
safe.execTransaction({
to: address(safe),
value: 0,
data: data,
operation: Enum.Operation.Call,
safeTxGas: 0,
baseGas: 0,
gasPrice: 0,
gasToken: address(0),
refundReceiver: payable(0),
signatures: sigs
});
}
}
contract LivnessGuard_TestCheckTx is LivnessGuard_TestInit {
function test_checkTransaction_succeeds() external {
Signer[] memory signers_ = signers;
exec(signers, address(1111), hex"abba");
for (uint256 i; i < signers.length; i++) {
assertEq(livenessGuard.lastSigned(signers[i].owner), block.timestamp);
}
}
}
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment