Overview
MOVR Balance
MOVR Value
$0.00| Transaction Hash |
|
Block
|
From
|
To
|
|||||
|---|---|---|---|---|---|---|---|---|---|
Latest 1 internal transaction
| Parent Transaction Hash | Block | From | To | |||
|---|---|---|---|---|---|---|
| 6251888 | 694 days ago | Contract Creation | 0 MOVR |
Cross-Chain Transactions
Loading...
Loading
Minimal Proxy Contract for 0x8f4e4d51b8050b0ff713eff1f88f3dd8b5e8a530
Contract Name:
SphinxModule
Compiler Version
v0.8.4+commit.c7e474f2
Optimization Enabled:
No with 200 runs
Other Settings:
default evmVersion
Contract Source Code (Solidity Standard Json-Input format)
// SPDX-License-Identifier: MIT
pragma solidity 0.8.4;
import { Enum } from "@gnosis.pm/safe-contracts-1.3.0/common/Enum.sol";
// We import `GnosisSafe` v1.3.0 here, but this contract also supports `GnosisSafeL2.sol` (v1.3.0)
// as well as `Safe.sol` and `SafeL2.sol` from Safe v1.4.1. All of these contracts share the same
// interface for the functions used in this contract.
import { GnosisSafe } from "@gnosis.pm/safe-contracts-1.3.0/GnosisSafe.sol";
// Likewise, we deploy `IProxy` v1.3.0 here, but this contract also supports `IProxy` v1.4.1.
import { IProxy } from "@gnosis.pm/safe-contracts-1.3.0/proxies/GnosisSafeProxy.sol";
import { Initializable } from "@openzeppelin/contracts/proxy/utils/Initializable.sol";
import { ReentrancyGuard } from "@openzeppelin/contracts/security/ReentrancyGuard.sol";
import { MerkleProof } from "@openzeppelin/contracts/utils/cryptography/MerkleProof.sol";
import {
SphinxLeafType,
SphinxLeaf,
SphinxLeafWithProof,
MerkleRootState,
MerkleRootStatus
} from "./SphinxDataTypes.sol";
import { ISphinxModule } from "./interfaces/ISphinxModule.sol";
/**
* @title SphinxModule
* @notice The `SphinxModule` contains the logic that executes deployments in a Gnosis Safe and
* verifies that the Gnosis Safe owners have signed the Merkle root that contains
* the deployment. It also contains logic for cancelling active Merkle roots.
*
* The `SphinxModule` exists as an implementation contract, which is delegatecalled
* by minimal, non-upgradeable EIP-1167 proxy contracts. We use this architecture
* because it's considerably cheaper to deploy an EIP-1167 proxy than a `SphinxModule`.
*/
contract SphinxModule is ReentrancyGuard, Enum, ISphinxModule, Initializable {
/**
* @inheritdoc ISphinxModule
*/
string public constant override VERSION = "1.0.0";
/**
* @dev The hash of the version string for the Gnosis Safe proxy v1.3.0.
*/
bytes32 internal constant SAFE_VERSION_HASH_1_3_0 = keccak256("1.3.0");
/**
* @dev The hash of the version string for the Gnosis Safe proxy v1.4.1.
*/
bytes32 internal constant SAFE_VERSION_HASH_1_4_1 = keccak256("1.4.1");
/**
* @dev The EIP-712 domain separator, which displays a bit of context to the user
* when they sign the Merkle root off-chain.
*/
bytes32 internal constant DOMAIN_SEPARATOR =
keccak256(
abi.encode(
keccak256("EIP712Domain(string name,string version)"),
keccak256(bytes("Sphinx")),
keccak256(bytes(VERSION))
)
);
/**
* @dev The EIP-712 type hash, which just contains the Merkle root.
*/
bytes32 internal constant TYPE_HASH = keccak256("MerkleRoot(bytes32 root)");
/**
* @inheritdoc ISphinxModule
*/
mapping(bytes32 => MerkleRootState) public override merkleRootStates;
/**
* @inheritdoc ISphinxModule
*/
uint256 public override merkleRootNonce;
/**
* @inheritdoc ISphinxModule
*/
bytes32 public override activeMerkleRoot;
/**
* @inheritdoc ISphinxModule
*/
address payable public override safeProxy;
/**
* @notice Locks the `SphinxModule` implementation contract so it can't be
* initialized directly.
*/
constructor() {
_disableInitializers();
}
/**
* @inheritdoc ISphinxModule
*/
function initialize(address _safeProxy) external override initializer {
require(_safeProxy != address(0), "SphinxModule: invalid Safe address");
// Check that the Gnosis Safe proxy has a singleton with a valid version. This check
// prevents users from accidentally adding the module to a Gnosis Safe with an invalid
// version.
address safeSingleton = IProxy(_safeProxy).masterCopy();
string memory safeVersion = GnosisSafe(payable(safeSingleton)).VERSION();
bytes32 safeVersionHash = keccak256(abi.encodePacked(safeVersion));
require(
safeVersionHash == SAFE_VERSION_HASH_1_3_0 ||
safeVersionHash == SAFE_VERSION_HASH_1_4_1,
"SphinxModule: invalid Safe version"
);
safeProxy = payable(_safeProxy);
}
/**
* @inheritdoc ISphinxModule
*/
function approve(
bytes32 _root,
SphinxLeafWithProof memory _leafWithProof,
bytes memory _signatures
)
public
override
// We add a re-entrancy guard out of an abundance of caution. It's possible for the call to
// the Gnosis Safe's `checkSignatures` function to call into another contract when
// validating an EIP-1271 contract signature.
nonReentrant
{
require(_root != bytes32(0), "SphinxModule: invalid root");
require(activeMerkleRoot == bytes32(0), "SphinxModule: active merkle root");
// Check that the Merkle root hasn't been used before.
MerkleRootState storage state = merkleRootStates[_root];
require(state.status == MerkleRootStatus.EMPTY, "SphinxModule: root already used");
SphinxLeaf memory leaf = _leafWithProof.leaf;
// Revert if the Merkle leaf does not yield the Merkle root, given the Merkle proof.
require(
MerkleProof.verify(_leafWithProof.proof, _root, _getLeafHash(leaf)),
"SphinxModule: failed to verify leaf"
);
require(leaf.leafType == SphinxLeafType.APPROVE, "SphinxModule: invalid leaf type");
// The `APPROVE` leaf must always have an index of 0.
require(leaf.index == 0, "SphinxModule: invalid leaf index");
// Decode the `APPROVE` leaf data.
(
address leafSafeProxy,
address moduleProxy,
uint256 leafMerkleRootNonce,
uint256 numLeaves,
address executor,
string memory uri,
bool arbitraryChain
) = abi.decode(leaf.data, (address, address, uint256, uint256, address, string, bool));
require(leafSafeProxy == address(safeProxy), "SphinxModule: invalid SafeProxy");
require(moduleProxy == address(this), "SphinxModule: invalid SphinxModuleProxy");
require(leafMerkleRootNonce == merkleRootNonce, "SphinxModule: invalid nonce");
// The `numLeaves` must be at least `1` because there must always be an `APPROVE` leaf.
require(numLeaves > 0, "SphinxModule: numLeaves cannot be 0");
require(executor == msg.sender, "SphinxModule: caller isn't executor");
// The current chain ID must match the leaf's chain ID, or the Merkle root must
// be executable on an arbitrary chain.
require(leaf.chainId == block.chainid || arbitraryChain, "SphinxModule: invalid chain id");
// If the Merkle root can be executable on an arbitrary chain, the leaf must have a chain ID
// of 0. This isn't strictly necessary; it just enforces a convention.
require(!arbitraryChain || leaf.chainId == 0, "SphinxModule: leaf chain id must be 0");
// We don't validate the `uri` because we allow it to be empty.
emit SphinxMerkleRootApproved(_root, leafMerkleRootNonce, msg.sender, numLeaves, uri);
// Assign values to all fields of the new Merkle root's `MerkleRootState` except for the
// `status` field, which will be assigned below.
state.numLeaves = numLeaves;
state.leavesExecuted = 1;
state.uri = uri;
state.executor = msg.sender;
state.arbitraryChain = arbitraryChain;
unchecked {
merkleRootNonce = leafMerkleRootNonce + 1;
}
// If there is only an `APPROVE` leaf, mark the Merkle root as completed. The purpose of
// this is to allow the Gnosis Safe owners to cancel a different Merkle root that has been
// signed off-chain, but has not been approved in this contract. The owners can do this by
// by signing a new Merkle root that has the same Merkle root nonce and approving it
// on-chain. This prevents the old Merkle root from ever being approved. In the event that
// the Gnosis Safe owners want to cancel a Merkle root without approving a new deployment,
// they can simply approve a Merkle root that contains a single `APPROVE` leaf.
if (numLeaves == 1) {
emit SphinxMerkleRootCompleted(_root);
state.status = MerkleRootStatus.COMPLETED;
// We don't need to set the `activeMerkleRoot` to equal `bytes32(0)` because it already
// equals `bytes32(0)`. At the beginning of this function, we checked that the
// `activeMerkleRoot` equals `bytes32(0)`, and we never set it to a non-zero value. This
// is because the Merkle root is approved and completed in this call.
} else {
// We set the status to `APPROVED` because there are `EXECUTE` leaves in this Merkle tree.
state.status = MerkleRootStatus.APPROVED;
activeMerkleRoot = _root;
}
// Check that a sufficient number of Gnosis Safe owners have signed the Merkle root (or,
// more specifically, EIP-712 data that includes the Merkle root). We do this last to
// follow the checks-effects-interactions pattern, since it's possible for `checkSignatures`
// to call into another contract if it's validating an EIP-1271 contract signature.
bytes memory typedData = abi.encodePacked(
"\x19\x01",
DOMAIN_SEPARATOR,
keccak256(abi.encode(TYPE_HASH, _root))
);
GnosisSafe(payable(leafSafeProxy)).checkSignatures(
keccak256(typedData),
typedData,
_signatures
);
}
/**
* @inheritdoc ISphinxModule
*/
function cancel(
bytes32 _root,
SphinxLeafWithProof memory _leafWithProof,
bytes memory _signatures
)
public
override
// We add a re-entrancy guard out of an abundance of caution. It's possible for the call to
// the Gnosis Safe's `checkSignatures` function to call into another contract when
// validating an EIP-1271 contract signature.
nonReentrant
{
require(_root != bytes32(0), "SphinxModule: invalid root");
require(activeMerkleRoot != bytes32(0), "SphinxModule: no root to cancel");
// Check that the Merkle root hasn't been used before.
MerkleRootState storage state = merkleRootStates[_root];
require(state.status == MerkleRootStatus.EMPTY, "SphinxModule: root already used");
SphinxLeaf memory leaf = _leafWithProof.leaf;
// Revert if the Merkle leaf does not yield the Merkle root, given the Merkle proof.
require(
MerkleProof.verify(_leafWithProof.proof, _root, _getLeafHash(leaf)),
"SphinxModule: failed to verify leaf"
);
require(leaf.leafType == SphinxLeafType.CANCEL, "SphinxModule: invalid leaf type");
// The `CANCEL` leaf must always have an index of 0.
require(leaf.index == 0, "SphinxModule: invalid leaf index");
// Decode the `CANCEL` leaf data.
(
address leafSafeProxy,
address moduleProxy,
uint256 leafMerkleRootNonce,
bytes32 merkleRootToCancel,
address executor,
string memory uri
) = abi.decode(leaf.data, (address, address, uint256, bytes32, address, string));
require(leafSafeProxy == address(safeProxy), "SphinxModule: invalid SafeProxy");
require(moduleProxy == address(this), "SphinxModule: invalid SphinxModuleProxy");
require(leafMerkleRootNonce == merkleRootNonce, "SphinxModule: invalid nonce");
require(merkleRootToCancel == activeMerkleRoot, "SphinxModule: invalid root to cancel");
require(executor == msg.sender, "SphinxModule: caller isn't executor");
// The current chain ID must match the leaf's chain ID. We don't allow `arbitraryChain` to
// be `true` here because we don't think there's a use case for cancelling Merkle roots
// across arbitrary networks.
require(leaf.chainId == block.chainid, "SphinxModule: invalid chain id");
// We don't validate the `uri` because we allow it to be empty.
// Cancel the active Merkle root.
emit SphinxMerkleRootCanceled(
_root,
merkleRootToCancel,
leafMerkleRootNonce,
msg.sender,
uri
);
merkleRootStates[merkleRootToCancel].status = MerkleRootStatus.CANCELED;
activeMerkleRoot = bytes32(0);
// Mark the input Merkle root as `COMPLETED`.
emit SphinxMerkleRootCompleted(_root);
// Assign values to all fields of the new Merkle root's `MerkleRootState` except for the
// `arbitraryChain` field, which is `false` for this Merkle root.
state.numLeaves = 1;
state.leavesExecuted = 1;
state.uri = uri;
state.executor = msg.sender;
state.status = MerkleRootStatus.COMPLETED;
unchecked {
merkleRootNonce = leafMerkleRootNonce + 1;
}
// Check that a sufficient number of Gnosis Safe owners have signed the Merkle root (or,
// more specifically, EIP-712 data that includes the Merkle root). We do this last to
// follow the checks-effects-interactions pattern, since it's possible for `checkSignatures`
// to call into another contract if it's validating an EIP-1271 contract signature.
bytes memory typedData = abi.encodePacked(
"\x19\x01",
DOMAIN_SEPARATOR,
keccak256(abi.encode(TYPE_HASH, _root))
);
GnosisSafe(payable(leafSafeProxy)).checkSignatures(
keccak256(typedData),
typedData,
_signatures
);
}
/**
* @inheritdoc ISphinxModule
*/
function execute(SphinxLeafWithProof[] memory _leavesWithProofs) public override nonReentrant {
uint256 numActions = _leavesWithProofs.length;
require(numActions > 0, "SphinxModule: no leaves to execute");
// We cache the active Merkle root in memory because it reduces the amount of gas used in
// this call.
bytes32 cachedActiveMerkleRoot = activeMerkleRoot;
require(cachedActiveMerkleRoot != bytes32(0), "SphinxModule: no active root");
MerkleRootState storage state = merkleRootStates[cachedActiveMerkleRoot];
require(state.executor == msg.sender, "SphinxModule: caller isn't executor");
// Cache the `leavesExecuted` state variable to reduce the number of SLOADs in this call.
uint256 leavesExecuted = state.leavesExecuted;
// Revert if the number of previously executed leaves plus the number of leaves in the current
// array is greater than the `numLeaves` specified in the `approve` function.
require(
state.numLeaves >= leavesExecuted + numActions,
"SphinxModule: extra leaves not allowed"
);
// Cache the `arbitraryChain` boolean. This reduces the amount of SLOADs in this function.
bool arbitraryChain = state.arbitraryChain;
SphinxLeaf memory leaf;
bytes32[] memory proof;
// Iterate through each of the Merkle leaves in the array.
for (uint256 i = 0; i < numActions; i++) {
leaf = _leavesWithProofs[i].leaf;
proof = _leavesWithProofs[i].proof;
require(
MerkleProof.verify(proof, cachedActiveMerkleRoot, _getLeafHash(leaf)),
"SphinxModule: failed to verify leaf"
);
require(leaf.leafType == SphinxLeafType.EXECUTE, "SphinxModule: invalid leaf type");
// Revert if the current leaf is being executed in the incorrect order.
require(leaf.index == leavesExecuted, "SphinxModule: invalid leaf index");
// The current chain ID must match the leaf's chain ID, or the Merkle root must
// be executable on an arbitrary chain.
require(
leaf.chainId == block.chainid || arbitraryChain,
"SphinxModule: invalid chain id"
);
// If the Merkle root can be executable on an arbitrary chain, the leaf must have a chain ID
// of 0. This isn't strictly necessary; it just enforces a convention.
require(!arbitraryChain || leaf.chainId == 0, "SphinxModule: leaf chain id must be 0");
// Decode the Merkle leaf's data.
(
address to,
uint256 value,
uint256 gas,
bytes memory txData,
Enum.Operation operation,
bool requireSuccess
) = abi.decode(leaf.data, (address, uint256, uint256, bytes, Enum.Operation, bool));
leavesExecuted += 1;
// Declare a `success` boolean, which we'll assign to the outcome of the call to the
// Gnosis Safe. Slither thinks that it's possible for this variable to remain
// unassigned, which is not true. It's always either assigned in the body of the `try`
// statement or the `catch` statement below.
// slither-disable-next-line uninitialized-local
bool success;
// Check that the amount of gas forwarded to the Gnosis Safe will be *equal* to the
// `gas` specified by the user. If you'd like to understand the specifics of this
// `require` statement, you'll need some background about the EVM first:
// - When hard-coding a gas amount to an external call, the EVM will forward *at most*
// the specified gas amount. It's possible to forward less gas if there isn't enough
// gas available in the current scope.
// - We can only forward 63/64 of the available gas to the external call (as of
// EIP-150). In other words, if we want to forward 100k gas, there must be at least
// 100k * (64 / 63) gas available in the current scope.
// So, without this `require` statement, it'd be possible for the executor to send an
// insufficient amount of gas to the Gnosis Safe, which could cause the user's
// transaction to fail. We multiply the `gas` by (64 / 63) to account for the fact that
// we can only forward 63/64 of the available gas to the external call. Lastly, we add
// 10k as a buffer to account for:
// 1. The cold `SLOAD` that occurs for the `safeProxy` variable shortly after this
// `require` statement. This costs 2100 gas.
// 2. Several thousand gas to account for any future changes in the EVM.
require(gasleft() >= ((gas * 64) / 63) + 10000, "SphinxModule: insufficient gas");
// Slither warns that a call inside of a loop can lead to a denial-of-service
// attack if the call reverts. However, this isn't a concern because the call to the
// Gnosis Safe is wrapped in a try/catch, and because we restrict the amount of gas sent
// along with the call. Slither also warns of a re-entrancy vulnerability here, which
// isn't a concern because we've included a `nonReentrant` modifier in this function.
// slither-disable-start calls-loop
// slither-disable-start reentrancy-no-eth
// Call the Gnosis Safe. We wrap it in a try/catch in case there's an EVM error that
// occurs when making the call, which would otherwise cause the current context to
// revert. This could happen if the user supplies an extremely low `gas` value (e.g.
// 1000).
try
GnosisSafe(safeProxy).execTransactionFromModule{ gas: gas }(
to,
value,
txData,
operation
)
returns (bool execSuccess) {
// The `execSuccess` returns whether or not the user's transaction reverted. We
// don't use a low-level call to make it easy to retrieve this value.
success = execSuccess;
} catch {
// An EVM error occurred when making the call. This can happen if the user supplies
// an extremely low `gas` value (e.g. 1000). In this situation, we set the `success`
// boolean to `false`. We don't need to explicitly set it because its default value
// is `false`.
}
// slither-disable-end calls-loop
// slither-disable-end reentrancy-no-eth
if (success) emit SphinxActionSucceeded(cachedActiveMerkleRoot, leaf.index);
else emit SphinxActionFailed(cachedActiveMerkleRoot, leaf.index);
// Mark the active Merkle root as failed if the Gnosis Safe transaction failed and the
// current leaf requires that it must succeed.
if (!success && requireSuccess) {
emit SphinxMerkleRootFailed(cachedActiveMerkleRoot, leaf.index);
state.status = MerkleRootStatus.FAILED;
state.leavesExecuted = leavesExecuted;
activeMerkleRoot = bytes32(0);
return;
}
}
state.leavesExecuted = leavesExecuted;
// Mark the Merkle root as completed if all of the Merkle leaves have been executed.
if (leavesExecuted == state.numLeaves) {
emit SphinxMerkleRootCompleted(cachedActiveMerkleRoot);
state.status = MerkleRootStatus.COMPLETED;
activeMerkleRoot = bytes32(0);
}
}
/**
* @notice Hash a Merkle leaf. We do this before attempting to prove that the leaf
* belongs to a Merkle root. We double-hash the leaf to prevent second preimage attacks,
* as recommended by OpenZeppelin's Merkle Tree library.
*
* @param _leaf The Merkle leaf to hash.
*/
function _getLeafHash(SphinxLeaf memory _leaf) internal pure returns (bytes32) {
return keccak256(abi.encodePacked(keccak256(abi.encode(_leaf))));
}
}// SPDX-License-Identifier: MIT
pragma solidity >=0.7.0 <0.9.0;
/**
* @custom:value APPROVE Approve a new deployment on a chain. This leaf must be submitted in the
* `approve` function on the `SphinxModuleProxy`.
* @custom:value EXECUTE Execute a transaction in the deployment. These leaves must be submitted in
* the `execute` function on the `SphinxModuleProxy`.
* @custom:value CANCEL Cancel an active Merkle root. This leaf must be submitted in the `cancel`
* function on the `SphinxModuleProxy`.
*/
enum SphinxLeafType {
APPROVE,
EXECUTE,
CANCEL
}
/**
* @notice A Merkle leaf.
*
* @custom:field chainId The current chain ID.
* @custom:field index The index of the leaf within the Merkle tree on this chain.
* @custom:field leafType The type of the leaf.
* @custom:field data Arbitrary data that is ABI encoded based on the leaf type.
*/
struct SphinxLeaf {
uint256 chainId;
uint256 index;
SphinxLeafType leafType;
bytes data;
}
/**
* @custom:field leaf A Merkle leaf.
* @custom:field proof The Merkle leaf's proof.
*/
struct SphinxLeafWithProof {
SphinxLeaf leaf;
bytes32[] proof;
}
/**
* @notice The state of a Merkle root in a `SphinxModuleProxy`.
*
* @custom:field numLeaves The total number of leaves in the Merkle tree on the current chain.
* There must be at least one leaf: either an `APPROVE` leaf or a
* `CANCEL` leaf.
* @custom:field leavesExecuted The number of Merkle leaves that have been executed on the current
* chain for the current Merkle root.
* @custom:field uri An optional field that contains the URI of the Merkle root. Its
* purpose is to provide a public record that allows anyone to
* re-assemble the deployment from scratch. This may include the
* Solidity compiler inputs, which are required for Etherscan
* verification. The format, location, and contents of the URI are
* determined by off-chain tooling.
* @custom:field executor The address of the caller, which is the only account that is allowed
* to execute calls on the `SphinxModuleProxy` for the Merkle root.
* @custom:field status The status of the Merkle root.
* @custom:field arbitraryChain If this is `true`, the Merkle root can be executed on any chain
* without the explicit permission of the Gnosis Safe owners. This is
* useful if the owners want their system to be permissionlessly
* deployed on new chains. By default, this is disabled, which means
* that the Gnosis Safe owners must explicitly approve the Merkle root
* on individual chains.
*/
struct MerkleRootState {
uint256 numLeaves;
uint256 leavesExecuted;
string uri;
address executor;
MerkleRootStatus status;
bool arbitraryChain;
}
/**
* @notice Enum that represents the status of a Merkle root in a `SphinxModuleProxy`.
*
* @custom:value EMPTY The Merkle root has never been used.
* @custom:value APPROVED The Merkle root has been signed by the Gnosis Safe owners, and the
* `approve` function has been called on the `SphinxModuleProxy`. The
* Merkle root is considered "active" after this happens.
* @custom:value COMPLETED The Merkle root has been completed on this network.
* @custom:value CANCELED The Merkle root was previously active, but has been canceled by the
* Gnosis Safe owner(s).
* @custom:value FAILED The Merkle root has failed due to a transaction reverting in the Gnosis
* Safe.
*/
enum MerkleRootStatus {
EMPTY,
APPROVED,
COMPLETED,
CANCELED,
FAILED
}// SPDX-License-Identifier: MIT
pragma solidity >=0.7.0 <0.9.0;
import { SphinxLeafWithProof, MerkleRootStatus } from "../SphinxDataTypes.sol";
/**
* @notice The interface of the `SphinxModule` contract.
*/
interface ISphinxModule {
/**
* @notice Emitted when an `EXECUTE` leaf fails in the Gnosis Safe.
*
* @param merkleRoot The Merkle root that contains the failing action.
* @param leafIndex The index of the leaf in the Merkle tree.
*/
event SphinxActionFailed(bytes32 indexed merkleRoot, uint256 leafIndex);
/**
* @notice Emitted when an `EXECUTE` Merkle leaf succeeds in the Gnosis Safe.
*
* @param merkleRoot The Merkle root that contains the action that succeeded.
* @param leafIndex The index of the leaf in the Merkle tree.
*/
event SphinxActionSucceeded(bytes32 indexed merkleRoot, uint256 leafIndex);
/**
* @notice Emitted when a Merkle root is approved.
*
* @param merkleRoot The Merkle root that was approved.
* @param nonce The `nonce` field in the `APPROVE` leaf. This matches the nonce
* in the `SphinxModuleProxy` before the approval occurred.
* @param executor The address of the caller.
* @param numLeaves The total number of leaves in the Merkle tree on the current chain.
* @param uri The URI of the Merkle root. This may be an empty string.
*/
event SphinxMerkleRootApproved(
bytes32 indexed merkleRoot,
uint256 indexed nonce,
address executor,
uint256 numLeaves,
string uri
);
/**
* @notice Emitted when an active Merkle root is canceled.
*
* @param completedMerkleRoot The Merkle root that contains the `CANCEL` leaf which canceled the
* active Merkle root.
* @param canceledMerkleRoot The Merkle root that was canceled.
* @param nonce The `nonce` field in the `CANCEL` leaf. This matches the nonce
* in the `SphinxModuleProxy` before the cancellation occurred.
* @param executor The address of the caller.
* @param uri The URI of the Merkle root that contains the `CANCEL` leaf (not
* the Merkle root that was cancelled). This may be an empty string.
*/
event SphinxMerkleRootCanceled(
bytes32 indexed completedMerkleRoot,
bytes32 indexed canceledMerkleRoot,
uint256 indexed nonce,
address executor,
string uri
);
/**
* @notice Emitted when a Merkle root is completed.
*
* @param merkleRoot The Merkle root that was completed.
*/
event SphinxMerkleRootCompleted(bytes32 indexed merkleRoot);
/**
* @notice Emitted when an action fails due to a transaction reverting in the Gnosis Safe.
*
* @param merkleRoot The Merkle root that contains the failed action.
* @param leafIndex The index of the leaf in the Merkle tree that caused the failure.
*/
event SphinxMerkleRootFailed(bytes32 indexed merkleRoot, uint256 leafIndex);
/**
* @notice The version of the `SphinxModule`.
*/
function VERSION() external view returns (string memory);
/**
* @notice The Merkle root that is currently active. This means that it has been signed
* off-chain by the Gnosis Safe owner(s) and approved on-chain. This is `bytes32(0)` if
* there is no active Merkle root.
*/
function activeMerkleRoot() external view returns (bytes32);
/**
* @notice Approve a new Merkle root, which must be signed by a sufficient number of Gnosis Safe
* owners.
*
* @param _root The Merkle root to approve.
* @param _leafWithProof The `APPROVE` Merkle leaf and its Merkle proof, which must yield the
* Merkle root.
* @param _signatures The signatures of the Gnosis Safe owners.
*/
function approve(
bytes32 _root,
SphinxLeafWithProof memory _leafWithProof,
bytes memory _signatures
) external;
/**
* @notice Cancel an active Merkle root. The Gnosis Safe owners(s) can cancel an active Merkle
* root by signing a different Merkle root that contains a `CANCEL` Merkle leaf. This
* new Merkle root is submitted to this function.
*
* @param _root The Merkle root that contains the `CANCEL` leaf. This is _not_ the
* active Merkle root.
* @param _leafWithProof The `CANCEL` Merkle leaf and its Merkle proof, which must yield the
* `_root` supplied to this function (not the active Merkle root).
* @param _signatures The signatures of the Gnosis Safe owners that signed the Merkle root
* that contains the `CANCEL` leaf.
*/
function cancel(
bytes32 _root,
SphinxLeafWithProof memory _leafWithProof,
bytes memory _signatures
) external;
/**
* @notice The current nonce in this contract. This is incremented each time a Merkle root is
* used for the first time in the current contract. This can occur by using the Merkle
* root to approve a deployment, or cancel an active one. The nonce removes the
* possibility that a Merkle root can be signed by the owners, then submitted on-chain
* far into the future, even after other Merkle roots have been submitted. The nonce
* also allows the Gnosis Safe owners to cancel a Merkle root that has been signed
* off-chain, but has not been approved on-chain. In this situation, the owners can
* approve a new Merkle root that has the same nonce, then approve it on-chain,
* preventing the old Merkle root from ever being approved.
*/
function merkleRootNonce() external view returns (uint256);
/**
* @notice Mapping from a Merkle root to its `MerkleRootState` struct.
*/
function merkleRootStates(
bytes32
)
external
view
returns (
uint256 numLeaves,
uint256 leavesExecuted,
string memory uri,
address executor,
MerkleRootStatus status,
bool arbitraryChain
);
/**
* @notice Execute a set of Merkle leaves. These leaves must belong to the active Merkle root,
* which must have been approved by the Gnosis Safe owners in the `approve` function.
*
* @param _leavesWithProofs An array of `EXECUTE` Merkle leaves, along with their Merkle proofs.
*/
function execute(SphinxLeafWithProof[] memory _leavesWithProofs) external;
/**
* @notice Initializes this contract. It's necessary to use an initializer function instead of a
* constructor because this contract is meant to exist behind an EIP-1167 proxy, which
* isn't able to use constructor arguments.
*
* This call will revert if the input Gnosis Safe proxy's singleton has a `VERSION()`
* function that does not equal "1.3.0" or "1.4.1". This prevents users from
* accidentally adding the module to an incompatible Safe. This does _not_ ensure that
* the Gnosis Safe singleton isn't malicious. If a singleton has a valid `VERSION()`
* function and arbitrary malicious logic, this call would still consider the singleton
* to be valid.
*
* @param _safeProxy The address of the Gnosis Safe proxy that this contract belongs to.
*/
function initialize(address _safeProxy) external;
/**
* @notice The address of the Gnosis Safe proxy that this contract belongs to.
*/
function safeProxy() external view returns (address payable);
}// SPDX-License-Identifier: LGPL-3.0-only pragma solidity >=0.7.0 <0.9.0; import "./base/ModuleManager.sol"; import "./base/OwnerManager.sol"; import "./base/FallbackManager.sol"; import "./base/GuardManager.sol"; import "./common/EtherPaymentFallback.sol"; import "./common/Singleton.sol"; import "./common/SignatureDecoder.sol"; import "./common/SecuredTokenTransfer.sol"; import "./common/StorageAccessible.sol"; import "./interfaces/ISignatureValidator.sol"; import "./external/GnosisSafeMath.sol"; /// @title Gnosis Safe - A multisignature wallet with support for confirmations using signed messages based on ERC191. /// @author Stefan George - <[email protected]> /// @author Richard Meissner - <[email protected]> contract GnosisSafe is EtherPaymentFallback, Singleton, ModuleManager, OwnerManager, SignatureDecoder, SecuredTokenTransfer, ISignatureValidatorConstants, FallbackManager, StorageAccessible, GuardManager { using GnosisSafeMath for uint256; string public constant VERSION = "1.3.0"; // keccak256( // "EIP712Domain(uint256 chainId,address verifyingContract)" // ); bytes32 private constant DOMAIN_SEPARATOR_TYPEHASH = 0x47e79534a245952e8b16893a336b85a3d9ea9fa8c573f3d803afb92a79469218; // keccak256( // "SafeTx(address to,uint256 value,bytes data,uint8 operation,uint256 safeTxGas,uint256 baseGas,uint256 gasPrice,address gasToken,address refundReceiver,uint256 nonce)" // ); bytes32 private constant SAFE_TX_TYPEHASH = 0xbb8310d486368db6bd6f849402fdd73ad53d316b5a4b2644ad6efe0f941286d8; event SafeSetup(address indexed initiator, address[] owners, uint256 threshold, address initializer, address fallbackHandler); event ApproveHash(bytes32 indexed approvedHash, address indexed owner); event SignMsg(bytes32 indexed msgHash); event ExecutionFailure(bytes32 txHash, uint256 payment); event ExecutionSuccess(bytes32 txHash, uint256 payment); uint256 public nonce; bytes32 private _deprecatedDomainSeparator; // Mapping to keep track of all message hashes that have been approve by ALL REQUIRED owners mapping(bytes32 => uint256) public signedMessages; // Mapping to keep track of all hashes (message or transaction) that have been approve by ANY owners mapping(address => mapping(bytes32 => uint256)) public approvedHashes; // This constructor ensures that this contract can only be used as a master copy for Proxy contracts constructor() { // By setting the threshold it is not possible to call setup anymore, // so we create a Safe with 0 owners and threshold 1. // This is an unusable Safe, perfect for the singleton threshold = 1; } /// @dev Setup function sets initial storage of contract. /// @param _owners List of Safe owners. /// @param _threshold Number of required confirmations for a Safe transaction. /// @param to Contract address for optional delegate call. /// @param data Data payload for optional delegate call. /// @param fallbackHandler Handler for fallback calls to this contract /// @param paymentToken Token that should be used for the payment (0 is ETH) /// @param payment Value that should be paid /// @param paymentReceiver Adddress that should receive the payment (or 0 if tx.origin) function setup( address[] calldata _owners, uint256 _threshold, address to, bytes calldata data, address fallbackHandler, address paymentToken, uint256 payment, address payable paymentReceiver ) external { // setupOwners checks if the Threshold is already set, therefore preventing that this method is called twice setupOwners(_owners, _threshold); if (fallbackHandler != address(0)) internalSetFallbackHandler(fallbackHandler); // As setupOwners can only be called if the contract has not been initialized we don't need a check for setupModules setupModules(to, data); if (payment > 0) { // To avoid running into issues with EIP-170 we reuse the handlePayment function (to avoid adjusting code of that has been verified we do not adjust the method itself) // baseGas = 0, gasPrice = 1 and gas = payment => amount = (payment + 0) * 1 = payment handlePayment(payment, 0, 1, paymentToken, paymentReceiver); } emit SafeSetup(msg.sender, _owners, _threshold, to, fallbackHandler); } /// @dev Allows to execute a Safe transaction confirmed by required number of owners and then pays the account that submitted the transaction. /// Note: The fees are always transferred, even if the user transaction fails. /// @param to Destination address of Safe transaction. /// @param value Ether value of Safe transaction. /// @param data Data payload of Safe transaction. /// @param operation Operation type of Safe transaction. /// @param safeTxGas Gas that should be used for the Safe transaction. /// @param baseGas Gas costs that are independent of the transaction execution(e.g. base transaction fee, signature check, payment of the refund) /// @param gasPrice Gas price that should be used for the payment calculation. /// @param gasToken Token address (or 0 if ETH) that is used for the payment. /// @param refundReceiver Address of receiver of gas payment (or 0 if tx.origin). /// @param signatures Packed signature data ({bytes32 r}{bytes32 s}{uint8 v}) function execTransaction( address to, uint256 value, bytes calldata data, Enum.Operation operation, uint256 safeTxGas, uint256 baseGas, uint256 gasPrice, address gasToken, address payable refundReceiver, bytes memory signatures ) public payable virtual returns (bool success) { bytes32 txHash; // Use scope here to limit variable lifetime and prevent `stack too deep` errors { bytes memory txHashData = encodeTransactionData( // Transaction info to, value, data, operation, safeTxGas, // Payment info baseGas, gasPrice, gasToken, refundReceiver, // Signature info nonce ); // Increase nonce and execute transaction. nonce++; txHash = keccak256(txHashData); checkSignatures(txHash, txHashData, signatures); } address guard = getGuard(); { if (guard != address(0)) { Guard(guard).checkTransaction( // Transaction info to, value, data, operation, safeTxGas, // Payment info baseGas, gasPrice, gasToken, refundReceiver, // Signature info signatures, msg.sender ); } } // We require some gas to emit the events (at least 2500) after the execution and some to perform code until the execution (500) // We also include the 1/64 in the check that is not send along with a call to counteract potential shortings because of EIP-150 require(gasleft() >= ((safeTxGas * 64) / 63).max(safeTxGas + 2500) + 500, "GS010"); // Use scope here to limit variable lifetime and prevent `stack too deep` errors { uint256 gasUsed = gasleft(); // If the gasPrice is 0 we assume that nearly all available gas can be used (it is always more than safeTxGas) // We only substract 2500 (compared to the 3000 before) to ensure that the amount passed is still higher than safeTxGas success = execute(to, value, data, operation, gasPrice == 0 ? (gasleft() - 2500) : safeTxGas); gasUsed = gasUsed.sub(gasleft()); // If no safeTxGas and no gasPrice was set (e.g. both are 0), then the internal tx is required to be successful // This makes it possible to use `estimateGas` without issues, as it searches for the minimum gas where the tx doesn't revert require(success || safeTxGas != 0 || gasPrice != 0, "GS013"); // We transfer the calculated tx costs to the tx.origin to avoid sending it to intermediate contracts that have made calls uint256 payment = 0; if (gasPrice > 0) { payment = handlePayment(gasUsed, baseGas, gasPrice, gasToken, refundReceiver); } if (success) emit ExecutionSuccess(txHash, payment); else emit ExecutionFailure(txHash, payment); } { if (guard != address(0)) { Guard(guard).checkAfterExecution(txHash, success); } } } function handlePayment( uint256 gasUsed, uint256 baseGas, uint256 gasPrice, address gasToken, address payable refundReceiver ) private returns (uint256 payment) { // solhint-disable-next-line avoid-tx-origin address payable receiver = refundReceiver == address(0) ? payable(tx.origin) : refundReceiver; if (gasToken == address(0)) { // For ETH we will only adjust the gas price to not be higher than the actual used gas price payment = gasUsed.add(baseGas).mul(gasPrice < tx.gasprice ? gasPrice : tx.gasprice); require(receiver.send(payment), "GS011"); } else { payment = gasUsed.add(baseGas).mul(gasPrice); require(transferToken(gasToken, receiver, payment), "GS012"); } } /** * @dev Checks whether the signature provided is valid for the provided data, hash. Will revert otherwise. * @param dataHash Hash of the data (could be either a message hash or transaction hash) * @param data That should be signed (this is passed to an external validator contract) * @param signatures Signature data that should be verified. Can be ECDSA signature, contract signature (EIP-1271) or approved hash. */ function checkSignatures( bytes32 dataHash, bytes memory data, bytes memory signatures ) public view { // Load threshold to avoid multiple storage loads uint256 _threshold = threshold; // Check that a threshold is set require(_threshold > 0, "GS001"); checkNSignatures(dataHash, data, signatures, _threshold); } /** * @dev Checks whether the signature provided is valid for the provided data, hash. Will revert otherwise. * @param dataHash Hash of the data (could be either a message hash or transaction hash) * @param data That should be signed (this is passed to an external validator contract) * @param signatures Signature data that should be verified. Can be ECDSA signature, contract signature (EIP-1271) or approved hash. * @param requiredSignatures Amount of required valid signatures. */ function checkNSignatures( bytes32 dataHash, bytes memory data, bytes memory signatures, uint256 requiredSignatures ) public view { // Check that the provided signature data is not too short require(signatures.length >= requiredSignatures.mul(65), "GS020"); // There cannot be an owner with address 0. address lastOwner = address(0); address currentOwner; uint8 v; bytes32 r; bytes32 s; uint256 i; for (i = 0; i < requiredSignatures; 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))); // Check that signature data pointer (s) is not pointing inside the static part of the signatures bytes // This check is not completely accurate, since it is possible that more signatures than the threshold are send. // Here we only check that the pointer is not pointing inside the part that is being processed require(uint256(s) >= requiredSignatures.mul(65), "GS021"); // Check that signature data pointer (s) is in bounds (points to the length of data -> 32 bytes) require(uint256(s).add(32) <= signatures.length, "GS022"); // Check if the contract signature is in bounds: start of data is s + 32 and end is start + signature length uint256 contractSignatureLen; // solhint-disable-next-line no-inline-assembly assembly { contractSignatureLen := mload(add(add(signatures, s), 0x20)) } require(uint256(s).add(32).add(contractSignatureLen) <= signatures.length, "GS023"); // Check signature bytes memory contractSignature; // solhint-disable-next-line no-inline-assembly assembly { // The signature data for contract signatures is appended to the concatenated signatures and the offset is stored in s contractSignature := add(add(signatures, s), 0x20) } require(ISignatureValidator(currentOwner).isValidSignature(data, contractSignature) == EIP1271_MAGIC_VALUE, "GS024"); } 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))); // Hashes are automatically approved by the sender of the message or when they have been pre-approved via a separate transaction require(msg.sender == currentOwner || approvedHashes[currentOwner][dataHash] != 0, "GS025"); } 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); } require(currentOwner > lastOwner && owners[currentOwner] != address(0) && currentOwner != SENTINEL_OWNERS, "GS026"); lastOwner = currentOwner; } } /// @dev Allows to estimate a Safe transaction. /// This method is only meant for estimation purpose, therefore the call will always revert and encode the result in the revert data. /// Since the `estimateGas` function includes refunds, call this method to get an estimated of the costs that are deducted from the safe with `execTransaction` /// @param to Destination address of Safe transaction. /// @param value Ether value of Safe transaction. /// @param data Data payload of Safe transaction. /// @param operation Operation type of Safe transaction. /// @return Estimate without refunds and overhead fees (base transaction and payload data gas costs). /// @notice Deprecated in favor of common/StorageAccessible.sol and will be removed in next version. function requiredTxGas( address to, uint256 value, bytes calldata data, Enum.Operation operation ) external returns (uint256) { uint256 startGas = gasleft(); // We don't provide an error message here, as we use it to return the estimate require(execute(to, value, data, operation, gasleft())); uint256 requiredGas = startGas - gasleft(); // Convert response to string and return via error message revert(string(abi.encodePacked(requiredGas))); } /** * @dev Marks a hash as approved. This can be used to validate a hash that is used by a signature. * @param hashToApprove The hash that should be marked as approved for signatures that are verified by this contract. */ function approveHash(bytes32 hashToApprove) external { require(owners[msg.sender] != address(0), "GS030"); approvedHashes[msg.sender][hashToApprove] = 1; emit ApproveHash(hashToApprove, msg.sender); } /// @dev Returns the chain id used by this contract. function getChainId() public view returns (uint256) { uint256 id; // solhint-disable-next-line no-inline-assembly assembly { id := chainid() } return id; } function domainSeparator() public view returns (bytes32) { return keccak256(abi.encode(DOMAIN_SEPARATOR_TYPEHASH, getChainId(), this)); } /// @dev Returns the bytes that are hashed to be signed by owners. /// @param to Destination address. /// @param value Ether value. /// @param data Data payload. /// @param operation Operation type. /// @param safeTxGas Gas that should be used for the safe transaction. /// @param baseGas Gas costs for that are independent of the transaction execution(e.g. base transaction fee, signature check, payment of the refund) /// @param gasPrice Maximum gas price that should be used for this transaction. /// @param gasToken Token address (or 0 if ETH) that is used for the payment. /// @param refundReceiver Address of receiver of gas payment (or 0 if tx.origin). /// @param _nonce Transaction nonce. /// @return Transaction hash bytes. function encodeTransactionData( address to, uint256 value, bytes calldata data, Enum.Operation operation, uint256 safeTxGas, uint256 baseGas, uint256 gasPrice, address gasToken, address refundReceiver, uint256 _nonce ) public view returns (bytes memory) { bytes32 safeTxHash = keccak256( abi.encode( SAFE_TX_TYPEHASH, to, value, keccak256(data), operation, safeTxGas, baseGas, gasPrice, gasToken, refundReceiver, _nonce ) ); return abi.encodePacked(bytes1(0x19), bytes1(0x01), domainSeparator(), safeTxHash); } /// @dev Returns hash to be signed by owners. /// @param to Destination address. /// @param value Ether value. /// @param data Data payload. /// @param operation Operation type. /// @param safeTxGas Fas that should be used for the safe transaction. /// @param baseGas Gas costs for data used to trigger the safe transaction. /// @param gasPrice Maximum gas price that should be used for this transaction. /// @param gasToken Token address (or 0 if ETH) that is used for the payment. /// @param refundReceiver Address of receiver of gas payment (or 0 if tx.origin). /// @param _nonce Transaction nonce. /// @return Transaction hash. function getTransactionHash( address to, uint256 value, bytes calldata data, Enum.Operation operation, uint256 safeTxGas, uint256 baseGas, uint256 gasPrice, address gasToken, address refundReceiver, uint256 _nonce ) public view returns (bytes32) { return keccak256(encodeTransactionData(to, value, data, operation, safeTxGas, baseGas, gasPrice, gasToken, refundReceiver, _nonce)); } }
// SPDX-License-Identifier: LGPL-3.0-only pragma solidity >=0.7.0 <0.9.0; import "../common/Enum.sol"; /// @title Executor - A contract that can execute transactions /// @author Richard Meissner - <[email protected]> contract Executor { function execute( address to, uint256 value, bytes memory data, Enum.Operation operation, uint256 txGas ) internal returns (bool success) { if (operation == Enum.Operation.DelegateCall) { // solhint-disable-next-line no-inline-assembly assembly { success := delegatecall(txGas, to, add(data, 0x20), mload(data), 0, 0) } } else { // solhint-disable-next-line no-inline-assembly assembly { success := call(txGas, to, value, add(data, 0x20), mload(data), 0, 0) } } } }
// SPDX-License-Identifier: LGPL-3.0-only pragma solidity >=0.7.0 <0.9.0; import "../common/SelfAuthorized.sol"; /// @title Fallback Manager - A contract that manages fallback calls made to this contract /// @author Richard Meissner - <[email protected]> contract FallbackManager is SelfAuthorized { event ChangedFallbackHandler(address handler); // keccak256("fallback_manager.handler.address") bytes32 internal constant FALLBACK_HANDLER_STORAGE_SLOT = 0x6c9a6c4a39284e37ed1cf53d337577d14212a4870fb976a4366c693b939918d5; function internalSetFallbackHandler(address handler) internal { bytes32 slot = FALLBACK_HANDLER_STORAGE_SLOT; // solhint-disable-next-line no-inline-assembly assembly { sstore(slot, handler) } } /// @dev Allows to add a contract to handle fallback calls. /// Only fallback calls without value and with data will be forwarded. /// This can only be done via a Safe transaction. /// @param handler contract to handle fallbacks calls. function setFallbackHandler(address handler) public authorized { internalSetFallbackHandler(handler); emit ChangedFallbackHandler(handler); } // solhint-disable-next-line payable-fallback,no-complex-fallback fallback() external { bytes32 slot = FALLBACK_HANDLER_STORAGE_SLOT; // solhint-disable-next-line no-inline-assembly assembly { let handler := sload(slot) if iszero(handler) { return(0, 0) } calldatacopy(0, 0, calldatasize()) // The msg.sender address is shifted to the left by 12 bytes to remove the padding // Then the address without padding is stored right after the calldata mstore(calldatasize(), shl(96, caller())) // Add 20 bytes for the address appended add the end let success := call(gas(), handler, 0, 0, add(calldatasize(), 20), 0, 0) returndatacopy(0, 0, returndatasize()) if iszero(success) { revert(0, returndatasize()) } return(0, returndatasize()) } } }
// SPDX-License-Identifier: LGPL-3.0-only
pragma solidity >=0.7.0 <0.9.0;
import "../common/Enum.sol";
import "../common/SelfAuthorized.sol";
interface Guard {
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;
function checkAfterExecution(bytes32 txHash, bool success) external;
}
/// @title Fallback Manager - A contract that manages fallback calls made to this contract
/// @author Richard Meissner - <[email protected]>
contract GuardManager is SelfAuthorized {
event ChangedGuard(address guard);
// keccak256("guard_manager.guard.address")
bytes32 internal constant GUARD_STORAGE_SLOT = 0x4a204f620c8c5ccdca3fd54d003badd85ba500436a431f0cbda4f558c93c34c8;
/// @dev Set a guard that checks transactions before execution
/// @param guard The address of the guard to be used or the 0 address to disable the guard
function setGuard(address guard) external authorized {
bytes32 slot = GUARD_STORAGE_SLOT;
// solhint-disable-next-line no-inline-assembly
assembly {
sstore(slot, guard)
}
emit ChangedGuard(guard);
}
function getGuard() internal view returns (address guard) {
bytes32 slot = GUARD_STORAGE_SLOT;
// solhint-disable-next-line no-inline-assembly
assembly {
guard := sload(slot)
}
}
}// SPDX-License-Identifier: LGPL-3.0-only pragma solidity >=0.7.0 <0.9.0; import "../common/Enum.sol"; import "../common/SelfAuthorized.sol"; import "./Executor.sol"; /// @title Module Manager - A contract that manages modules that can execute transactions via this contract /// @author Stefan George - <[email protected]> /// @author Richard Meissner - <[email protected]> contract ModuleManager is SelfAuthorized, Executor { event EnabledModule(address module); event DisabledModule(address module); event ExecutionFromModuleSuccess(address indexed module); event ExecutionFromModuleFailure(address indexed module); address internal constant SENTINEL_MODULES = address(0x1); mapping(address => address) internal modules; function setupModules(address to, bytes memory data) internal { require(modules[SENTINEL_MODULES] == address(0), "GS100"); modules[SENTINEL_MODULES] = SENTINEL_MODULES; if (to != address(0)) // Setup has to complete successfully or transaction fails. require(execute(to, 0, data, Enum.Operation.DelegateCall, gasleft()), "GS000"); } /// @dev Allows to add a module to the whitelist. /// This can only be done via a Safe transaction. /// @notice Enables the module `module` for the Safe. /// @param module Module to be whitelisted. function enableModule(address module) public authorized { // Module address cannot be null or sentinel. require(module != address(0) && module != SENTINEL_MODULES, "GS101"); // Module cannot be added twice. require(modules[module] == address(0), "GS102"); modules[module] = modules[SENTINEL_MODULES]; modules[SENTINEL_MODULES] = module; emit EnabledModule(module); } /// @dev Allows to remove a module from the whitelist. /// This can only be done via a Safe transaction. /// @notice Disables the module `module` for the Safe. /// @param prevModule Module that pointed to the module to be removed in the linked list /// @param module Module to be removed. function disableModule(address prevModule, address module) public authorized { // Validate module address and check that it corresponds to module index. require(module != address(0) && module != SENTINEL_MODULES, "GS101"); require(modules[prevModule] == module, "GS103"); modules[prevModule] = modules[module]; modules[module] = address(0); emit DisabledModule(module); } /// @dev Allows a Module to execute a Safe transaction without any further confirmations. /// @param to Destination address of module transaction. /// @param value Ether value of module transaction. /// @param data Data payload of module transaction. /// @param operation Operation type of module transaction. function execTransactionFromModule( address to, uint256 value, bytes memory data, Enum.Operation operation ) public virtual returns (bool success) { // Only whitelisted modules are allowed. require(msg.sender != SENTINEL_MODULES && modules[msg.sender] != address(0), "GS104"); // Execute transaction without further confirmations. success = execute(to, value, data, operation, gasleft()); if (success) emit ExecutionFromModuleSuccess(msg.sender); else emit ExecutionFromModuleFailure(msg.sender); } /// @dev Allows a Module to execute a Safe transaction without any further confirmations and return data /// @param to Destination address of module transaction. /// @param value Ether value of module transaction. /// @param data Data payload of module transaction. /// @param operation Operation type of module transaction. function execTransactionFromModuleReturnData( address to, uint256 value, bytes memory data, Enum.Operation operation ) public returns (bool success, bytes memory returnData) { success = execTransactionFromModule(to, value, data, operation); // solhint-disable-next-line no-inline-assembly assembly { // Load free memory location let ptr := mload(0x40) // We allocate memory for the return data by setting the free memory location to // current free memory location + data size + 32 bytes for data size value mstore(0x40, add(ptr, add(returndatasize(), 0x20))) // Store the size mstore(ptr, returndatasize()) // Store the data returndatacopy(add(ptr, 0x20), 0, returndatasize()) // Point the return data to the correct memory location returnData := ptr } } /// @dev Returns if an module is enabled /// @return True if the module is enabled function isModuleEnabled(address module) public view returns (bool) { return SENTINEL_MODULES != module && modules[module] != address(0); } /// @dev Returns array of modules. /// @param start Start of the page. /// @param pageSize Maximum number of modules that should be returned. /// @return array Array of modules. /// @return next Start of the next page. function getModulesPaginated(address start, uint256 pageSize) external view returns (address[] memory array, address next) { // Init array with max page size array = new address[](pageSize); // Populate return array uint256 moduleCount = 0; address currentModule = modules[start]; while (currentModule != address(0x0) && currentModule != SENTINEL_MODULES && moduleCount < pageSize) { array[moduleCount] = currentModule; currentModule = modules[currentModule]; moduleCount++; } next = currentModule; // Set correct size of returned array // solhint-disable-next-line no-inline-assembly assembly { mstore(array, moduleCount) } } }
// SPDX-License-Identifier: LGPL-3.0-only pragma solidity >=0.7.0 <0.9.0; import "../common/SelfAuthorized.sol"; /// @title OwnerManager - Manages a set of owners and a threshold to perform actions. /// @author Stefan George - <[email protected]> /// @author Richard Meissner - <[email protected]> contract OwnerManager is SelfAuthorized { event AddedOwner(address owner); event RemovedOwner(address owner); event ChangedThreshold(uint256 threshold); address internal constant SENTINEL_OWNERS = address(0x1); mapping(address => address) internal owners; uint256 internal ownerCount; uint256 internal threshold; /// @dev Setup function sets initial storage of contract. /// @param _owners List of Safe owners. /// @param _threshold Number of required confirmations for a Safe transaction. function setupOwners(address[] memory _owners, uint256 _threshold) internal { // Threshold can only be 0 at initialization. // Check ensures that setup function can only be called once. require(threshold == 0, "GS200"); // Validate that threshold is smaller than number of added owners. require(_threshold <= _owners.length, "GS201"); // There has to be at least one Safe owner. require(_threshold >= 1, "GS202"); // Initializing Safe owners. address currentOwner = SENTINEL_OWNERS; for (uint256 i = 0; i < _owners.length; i++) { // Owner address cannot be null. address owner = _owners[i]; require(owner != address(0) && owner != SENTINEL_OWNERS && owner != address(this) && currentOwner != owner, "GS203"); // No duplicate owners allowed. require(owners[owner] == address(0), "GS204"); owners[currentOwner] = owner; currentOwner = owner; } owners[currentOwner] = SENTINEL_OWNERS; ownerCount = _owners.length; threshold = _threshold; } /// @dev Allows to add a new owner to the Safe and update the threshold at the same time. /// This can only be done via a Safe transaction. /// @notice Adds the owner `owner` to the Safe and updates the threshold to `_threshold`. /// @param owner New owner address. /// @param _threshold New threshold. function addOwnerWithThreshold(address owner, uint256 _threshold) public authorized { // Owner address cannot be null, the sentinel or the Safe itself. require(owner != address(0) && owner != SENTINEL_OWNERS && owner != address(this), "GS203"); // No duplicate owners allowed. require(owners[owner] == address(0), "GS204"); owners[owner] = owners[SENTINEL_OWNERS]; owners[SENTINEL_OWNERS] = owner; ownerCount++; emit AddedOwner(owner); // Change threshold if threshold was changed. if (threshold != _threshold) changeThreshold(_threshold); } /// @dev Allows to remove an owner from the Safe and update the threshold at the same time. /// This can only be done via a Safe transaction. /// @notice Removes the owner `owner` from the Safe and updates the threshold to `_threshold`. /// @param prevOwner Owner that pointed to the owner to be removed in the linked list /// @param owner Owner address to be removed. /// @param _threshold New threshold. function removeOwner( address prevOwner, address owner, uint256 _threshold ) public authorized { // Only allow to remove an owner, if threshold can still be reached. require(ownerCount - 1 >= _threshold, "GS201"); // Validate owner address and check that it corresponds to owner index. require(owner != address(0) && owner != SENTINEL_OWNERS, "GS203"); require(owners[prevOwner] == owner, "GS205"); owners[prevOwner] = owners[owner]; owners[owner] = address(0); ownerCount--; emit RemovedOwner(owner); // Change threshold if threshold was changed. if (threshold != _threshold) changeThreshold(_threshold); } /// @dev Allows to swap/replace an owner from the Safe with another address. /// This can only be done via a Safe transaction. /// @notice Replaces the owner `oldOwner` in the Safe with `newOwner`. /// @param prevOwner Owner that pointed to the owner to be replaced in the linked list /// @param oldOwner Owner address to be replaced. /// @param newOwner New owner address. function swapOwner( address prevOwner, address oldOwner, address newOwner ) public authorized { // Owner address cannot be null, the sentinel or the Safe itself. require(newOwner != address(0) && newOwner != SENTINEL_OWNERS && newOwner != address(this), "GS203"); // No duplicate owners allowed. require(owners[newOwner] == address(0), "GS204"); // Validate oldOwner address and check that it corresponds to owner index. require(oldOwner != address(0) && oldOwner != SENTINEL_OWNERS, "GS203"); require(owners[prevOwner] == oldOwner, "GS205"); owners[newOwner] = owners[oldOwner]; owners[prevOwner] = newOwner; owners[oldOwner] = address(0); emit RemovedOwner(oldOwner); emit AddedOwner(newOwner); } /// @dev Allows to update the number of required confirmations by Safe owners. /// This can only be done via a Safe transaction. /// @notice Changes the threshold of the Safe to `_threshold`. /// @param _threshold New threshold. function changeThreshold(uint256 _threshold) public authorized { // Validate that threshold is smaller than number of owners. require(_threshold <= ownerCount, "GS201"); // There has to be at least one Safe owner. require(_threshold >= 1, "GS202"); threshold = _threshold; emit ChangedThreshold(threshold); } function getThreshold() public view returns (uint256) { return threshold; } function isOwner(address owner) public view returns (bool) { return owner != SENTINEL_OWNERS && owners[owner] != address(0); } /// @dev Returns array of owners. /// @return Array of Safe owners. function getOwners() public view returns (address[] memory) { address[] memory array = new address[](ownerCount); // populate return array uint256 index = 0; address currentOwner = owners[SENTINEL_OWNERS]; while (currentOwner != SENTINEL_OWNERS) { array[index] = currentOwner; currentOwner = owners[currentOwner]; index++; } return array; } }
// SPDX-License-Identifier: LGPL-3.0-only pragma solidity >=0.7.0 <0.9.0; /// @title Enum - Collection of enums /// @author Richard Meissner - <[email protected]> contract Enum { enum Operation {Call, DelegateCall} }
// SPDX-License-Identifier: LGPL-3.0-only pragma solidity >=0.7.0 <0.9.0; /// @title EtherPaymentFallback - A contract that has a fallback to accept ether payments /// @author Richard Meissner - <[email protected]> contract EtherPaymentFallback { event SafeReceived(address indexed sender, uint256 value); /// @dev Fallback function accepts Ether transactions. receive() external payable { emit SafeReceived(msg.sender, msg.value); } }
// SPDX-License-Identifier: LGPL-3.0-only pragma solidity >=0.7.0 <0.9.0; /// @title SecuredTokenTransfer - Secure token transfer /// @author Richard Meissner - <[email protected]> contract SecuredTokenTransfer { /// @dev Transfers a token and returns if it was a success /// @param token Token that should be transferred /// @param receiver Receiver to whom the token should be transferred /// @param amount The amount of tokens that should be transferred function transferToken( address token, address receiver, uint256 amount ) internal returns (bool transferred) { // 0xa9059cbb - keccack("transfer(address,uint256)") bytes memory data = abi.encodeWithSelector(0xa9059cbb, receiver, amount); // solhint-disable-next-line no-inline-assembly assembly { // We write the return value to scratch space. // See https://docs.soliditylang.org/en/v0.7.6/internals/layout_in_memory.html#layout-in-memory let success := call(sub(gas(), 10000), token, 0, add(data, 0x20), mload(data), 0, 0x20) switch returndatasize() case 0 { transferred := success } case 0x20 { transferred := iszero(or(iszero(success), iszero(mload(0)))) } default { transferred := 0 } } } }
// SPDX-License-Identifier: LGPL-3.0-only pragma solidity >=0.7.0 <0.9.0; /// @title SelfAuthorized - authorizes current contract to perform actions /// @author Richard Meissner - <[email protected]> contract SelfAuthorized { function requireSelfCall() private view { require(msg.sender == address(this), "GS031"); } modifier authorized() { // This is a function call as it minimized the bytecode size requireSelfCall(); _; } }
// SPDX-License-Identifier: LGPL-3.0-only pragma solidity >=0.7.0 <0.9.0; /// @title SignatureDecoder - Decodes signatures that a encoded as bytes /// @author Richard Meissner - <[email protected]> contract SignatureDecoder { /// @dev divides bytes signature into `uint8 v, bytes32 r, bytes32 s`. /// @notice Make sure to peform a bounds check for @param pos, to avoid out of bounds access on @param signatures /// @param pos which signature to read. A prior bounds check of this parameter should be performed, to avoid out of bounds access /// @param signatures concatenated rsv signatures function signatureSplit(bytes memory signatures, uint256 pos) internal pure returns ( uint8 v, bytes32 r, bytes32 s ) { // The signature format is a compact form of: // {bytes32 r}{bytes32 s}{uint8 v} // Compact means, uint8 is not padded to 32 bytes. // solhint-disable-next-line no-inline-assembly assembly { let signaturePos := mul(0x41, pos) r := mload(add(signatures, add(signaturePos, 0x20))) s := mload(add(signatures, add(signaturePos, 0x40))) // Here we are loading the last 32 bytes, including 31 bytes // of 's'. There is no 'mload8' to do this. // // 'byte' is not working due to the Solidity parser, so lets // use the second best option, 'and' v := and(mload(add(signatures, add(signaturePos, 0x41))), 0xff) } } }
// SPDX-License-Identifier: LGPL-3.0-only pragma solidity >=0.7.0 <0.9.0; /// @title Singleton - Base for singleton contracts (should always be first super contract) /// This contract is tightly coupled to our proxy contract (see `proxies/GnosisSafeProxy.sol`) /// @author Richard Meissner - <[email protected]> contract Singleton { // singleton always needs to be first declared variable, to ensure that it is at the same location as in the Proxy contract. // It should also always be ensured that the address is stored alone (uses a full word) address private singleton; }
// SPDX-License-Identifier: LGPL-3.0-only
pragma solidity >=0.7.0 <0.9.0;
/// @title StorageAccessible - generic base contract that allows callers to access all internal storage.
/// @notice See https://github.com/gnosis/util-contracts/blob/bb5fe5fb5df6d8400998094fb1b32a178a47c3a1/contracts/StorageAccessible.sol
contract StorageAccessible {
/**
* @dev Reads `length` bytes of storage in the currents contract
* @param offset - the offset in the current contract's storage in words to start reading from
* @param length - the number of words (32 bytes) of data to read
* @return the bytes that were read.
*/
function getStorageAt(uint256 offset, uint256 length) public view returns (bytes memory) {
bytes memory result = new bytes(length * 32);
for (uint256 index = 0; index < length; index++) {
// solhint-disable-next-line no-inline-assembly
assembly {
let word := sload(add(offset, index))
mstore(add(add(result, 0x20), mul(index, 0x20)), word)
}
}
return result;
}
/**
* @dev Performs a delegetecall on a targetContract in the context of self.
* Internally reverts execution to avoid side effects (making it static).
*
* This method reverts with data equal to `abi.encode(bool(success), bytes(response))`.
* Specifically, the `returndata` after a call to this method will be:
* `success:bool || response.length:uint256 || response:bytes`.
*
* @param targetContract Address of the contract containing the code to execute.
* @param calldataPayload Calldata that should be sent to the target contract (encoded method name and arguments).
*/
function simulateAndRevert(address targetContract, bytes memory calldataPayload) external {
// solhint-disable-next-line no-inline-assembly
assembly {
let success := delegatecall(gas(), targetContract, add(calldataPayload, 0x20), mload(calldataPayload), 0, 0)
mstore(0x00, success)
mstore(0x20, returndatasize())
returndatacopy(0x40, 0, returndatasize())
revert(0, add(returndatasize(), 0x40))
}
}
}// SPDX-License-Identifier: LGPL-3.0-only
pragma solidity >=0.7.0 <0.9.0;
/**
* @title GnosisSafeMath
* @dev Math operations with safety checks that revert on error
* Renamed from SafeMath to GnosisSafeMath to avoid conflicts
* TODO: remove once open zeppelin update to solc 0.5.0
*/
library GnosisSafeMath {
/**
* @dev Multiplies two numbers, reverts on overflow.
*/
function mul(uint256 a, uint256 b) internal pure returns (uint256) {
// Gas optimization: this is cheaper than requiring 'a' not being zero, but the
// benefit is lost if 'b' is also tested.
// See: https://github.com/OpenZeppelin/openzeppelin-solidity/pull/522
if (a == 0) {
return 0;
}
uint256 c = a * b;
require(c / a == b);
return c;
}
/**
* @dev Subtracts two numbers, reverts on overflow (i.e. if subtrahend is greater than minuend).
*/
function sub(uint256 a, uint256 b) internal pure returns (uint256) {
require(b <= a);
uint256 c = a - b;
return c;
}
/**
* @dev Adds two numbers, reverts on overflow.
*/
function add(uint256 a, uint256 b) internal pure returns (uint256) {
uint256 c = a + b;
require(c >= a);
return c;
}
/**
* @dev Returns the largest of two numbers.
*/
function max(uint256 a, uint256 b) internal pure returns (uint256) {
return a >= b ? a : b;
}
}// SPDX-License-Identifier: LGPL-3.0-only
pragma solidity >=0.7.0 <0.9.0;
contract ISignatureValidatorConstants {
// bytes4(keccak256("isValidSignature(bytes,bytes)")
bytes4 internal constant EIP1271_MAGIC_VALUE = 0x20c13b0b;
}
abstract contract ISignatureValidator is ISignatureValidatorConstants {
/**
* @dev Should return whether the signature provided is valid for the provided data
* @param _data Arbitrary length data signed on the behalf of address(this)
* @param _signature Signature byte array associated with _data
*
* MUST return the bytes4 magic value 0x20c13b0b when function passes.
* MUST NOT modify state (using STATICCALL for solc < 0.5, view modifier for solc > 0.5)
* MUST allow external calls
*/
function isValidSignature(bytes memory _data, bytes memory _signature) public view virtual returns (bytes4);
}// SPDX-License-Identifier: LGPL-3.0-only pragma solidity >=0.7.0 <0.9.0; /// @title IProxy - Helper interface to access masterCopy of the Proxy on-chain /// @author Richard Meissner - <[email protected]> interface IProxy { function masterCopy() external view returns (address); } /// @title GnosisSafeProxy - Generic proxy contract allows to execute all transactions applying the code of a master contract. /// @author Stefan George - <[email protected]> /// @author Richard Meissner - <[email protected]> contract GnosisSafeProxy { // singleton always needs to be first declared variable, to ensure that it is at the same location in the contracts to which calls are delegated. // To reduce deployment costs this variable is internal and needs to be retrieved via `getStorageAt` address internal singleton; /// @dev Constructor function sets address of singleton contract. /// @param _singleton Singleton address. constructor(address _singleton) { require(_singleton != address(0), "Invalid singleton address provided"); singleton = _singleton; } /// @dev Fallback function forwards all transactions and returns all received return data. fallback() external payable { // solhint-disable-next-line no-inline-assembly assembly { let _singleton := and(sload(0), 0xffffffffffffffffffffffffffffffffffffffff) // 0xa619486e == keccak("masterCopy()"). The value is right padded to 32-bytes with 0s if eq(calldataload(0), 0xa619486e00000000000000000000000000000000000000000000000000000000) { mstore(0, _singleton) return(0, 0x20) } calldatacopy(0, 0, calldatasize()) let success := delegatecall(gas(), _singleton, 0, calldatasize(), 0, 0) returndatacopy(0, 0, returndatasize()) if eq(success, 0) { revert(0, returndatasize()) } return(0, returndatasize()) } } }
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (proxy/utils/Initializable.sol)
pragma solidity ^0.8.2;
import "../../utils/Address.sol";
/**
* @dev This is a base contract to aid in writing upgradeable contracts, or any kind of contract that will be deployed
* behind a proxy. Since proxied contracts do not make use of a constructor, it's common to move constructor logic to an
* external initializer function, usually called `initialize`. It then becomes necessary to protect this initializer
* function so it can only be called once. The {initializer} modifier provided by this contract will have this effect.
*
* The initialization functions use a version number. Once a version number is used, it is consumed and cannot be
* reused. This mechanism prevents re-execution of each "step" but allows the creation of new initialization steps in
* case an upgrade adds a module that needs to be initialized.
*
* For example:
*
* [.hljs-theme-light.nopadding]
* ```solidity
* contract MyToken is ERC20Upgradeable {
* function initialize() initializer public {
* __ERC20_init("MyToken", "MTK");
* }
* }
*
* contract MyTokenV2 is MyToken, ERC20PermitUpgradeable {
* function initializeV2() reinitializer(2) public {
* __ERC20Permit_init("MyToken");
* }
* }
* ```
*
* TIP: To avoid leaving the proxy in an uninitialized state, the initializer function should be called as early as
* possible by providing the encoded function call as the `_data` argument to {ERC1967Proxy-constructor}.
*
* CAUTION: When used with inheritance, manual care must be taken to not invoke a parent initializer twice, or to ensure
* that all initializers are idempotent. This is not verified automatically as constructors are by Solidity.
*
* [CAUTION]
* ====
* Avoid leaving a contract uninitialized.
*
* An uninitialized contract can be taken over by an attacker. This applies to both a proxy and its implementation
* contract, which may impact the proxy. To prevent the implementation contract from being used, you should invoke
* the {_disableInitializers} function in the constructor to automatically lock it when it is deployed:
*
* [.hljs-theme-light.nopadding]
* ```
* /// @custom:oz-upgrades-unsafe-allow constructor
* constructor() {
* _disableInitializers();
* }
* ```
* ====
*/
abstract contract Initializable {
/**
* @dev Indicates that the contract has been initialized.
* @custom:oz-retyped-from bool
*/
uint8 private _initialized;
/**
* @dev Indicates that the contract is in the process of being initialized.
*/
bool private _initializing;
/**
* @dev Triggered when the contract has been initialized or reinitialized.
*/
event Initialized(uint8 version);
/**
* @dev A modifier that defines a protected initializer function that can be invoked at most once. In its scope,
* `onlyInitializing` functions can be used to initialize parent contracts.
*
* Similar to `reinitializer(1)`, except that functions marked with `initializer` can be nested in the context of a
* constructor.
*
* Emits an {Initialized} event.
*/
modifier initializer() {
bool isTopLevelCall = !_initializing;
require(
(isTopLevelCall && _initialized < 1) || (!Address.isContract(address(this)) && _initialized == 1),
"Initializable: contract is already initialized"
);
_initialized = 1;
if (isTopLevelCall) {
_initializing = true;
}
_;
if (isTopLevelCall) {
_initializing = false;
emit Initialized(1);
}
}
/**
* @dev A modifier that defines a protected reinitializer function that can be invoked at most once, and only if the
* contract hasn't been initialized to a greater version before. In its scope, `onlyInitializing` functions can be
* used to initialize parent contracts.
*
* A reinitializer may be used after the original initialization step. This is essential to configure modules that
* are added through upgrades and that require initialization.
*
* When `version` is 1, this modifier is similar to `initializer`, except that functions marked with `reinitializer`
* cannot be nested. If one is invoked in the context of another, execution will revert.
*
* Note that versions can jump in increments greater than 1; this implies that if multiple reinitializers coexist in
* a contract, executing them in the right order is up to the developer or operator.
*
* WARNING: setting the version to 255 will prevent any future reinitialization.
*
* Emits an {Initialized} event.
*/
modifier reinitializer(uint8 version) {
require(!_initializing && _initialized < version, "Initializable: contract is already initialized");
_initialized = version;
_initializing = true;
_;
_initializing = false;
emit Initialized(version);
}
/**
* @dev Modifier to protect an initialization function so that it can only be invoked by functions with the
* {initializer} and {reinitializer} modifiers, directly or indirectly.
*/
modifier onlyInitializing() {
require(_initializing, "Initializable: contract is not initializing");
_;
}
/**
* @dev Locks the contract, preventing any future reinitialization. This cannot be part of an initializer call.
* Calling this in the constructor of a contract will prevent that contract from being initialized or reinitialized
* to any version. It is recommended to use this to lock implementation contracts that are designed to be called
* through proxies.
*
* Emits an {Initialized} event the first time it is successfully executed.
*/
function _disableInitializers() internal virtual {
require(!_initializing, "Initializable: contract is initializing");
if (_initialized != type(uint8).max) {
_initialized = type(uint8).max;
emit Initialized(type(uint8).max);
}
}
/**
* @dev Returns the highest version that has been initialized. See {reinitializer}.
*/
function _getInitializedVersion() internal view returns (uint8) {
return _initialized;
}
/**
* @dev Returns `true` if the contract is currently initializing. See {onlyInitializing}.
*/
function _isInitializing() internal view returns (bool) {
return _initializing;
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (security/ReentrancyGuard.sol)
pragma solidity ^0.8.0;
/**
* @dev Contract module that helps prevent reentrant calls to a function.
*
* Inheriting from `ReentrancyGuard` will make the {nonReentrant} modifier
* available, which can be applied to functions to make sure there are no nested
* (reentrant) calls to them.
*
* Note that because there is a single `nonReentrant` guard, functions marked as
* `nonReentrant` may not call one another. This can be worked around by making
* those functions `private`, and then adding `external` `nonReentrant` entry
* points to them.
*
* TIP: If you would like to learn more about reentrancy and alternative ways
* to protect against it, check out our blog post
* https://blog.openzeppelin.com/reentrancy-after-istanbul/[Reentrancy After Istanbul].
*/
abstract contract ReentrancyGuard {
// Booleans are more expensive than uint256 or any type that takes up a full
// word because each write operation emits an extra SLOAD to first read the
// slot's contents, replace the bits taken up by the boolean, and then write
// back. This is the compiler's defense against contract upgrades and
// pointer aliasing, and it cannot be disabled.
// The values being non-zero value makes deployment a bit more expensive,
// but in exchange the refund on every call to nonReentrant will be lower in
// amount. Since refunds are capped to a percentage of the total
// transaction's gas, it is best to keep them low in cases like this one, to
// increase the likelihood of the full refund coming into effect.
uint256 private constant _NOT_ENTERED = 1;
uint256 private constant _ENTERED = 2;
uint256 private _status;
constructor() {
_status = _NOT_ENTERED;
}
/**
* @dev Prevents a contract from calling itself, directly or indirectly.
* Calling a `nonReentrant` function from another `nonReentrant`
* function is not supported. It is possible to prevent this from happening
* by making the `nonReentrant` function external, and making it call a
* `private` function that does the actual work.
*/
modifier nonReentrant() {
_nonReentrantBefore();
_;
_nonReentrantAfter();
}
function _nonReentrantBefore() private {
// On the first call to nonReentrant, _status will be _NOT_ENTERED
require(_status != _ENTERED, "ReentrancyGuard: reentrant call");
// Any calls to nonReentrant after this point will fail
_status = _ENTERED;
}
function _nonReentrantAfter() private {
// By storing the original value once again, a refund is triggered (see
// https://eips.ethereum.org/EIPS/eip-2200)
_status = _NOT_ENTERED;
}
/**
* @dev Returns true if the reentrancy guard is currently set to "entered", which indicates there is a
* `nonReentrant` function in the call stack.
*/
function _reentrancyGuardEntered() internal view returns (bool) {
return _status == _ENTERED;
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (utils/Address.sol)
pragma solidity ^0.8.1;
/**
* @dev Collection of functions related to the address type
*/
library Address {
/**
* @dev Returns true if `account` is a contract.
*
* [IMPORTANT]
* ====
* It is unsafe to assume that an address for which this function returns
* false is an externally-owned account (EOA) and not a contract.
*
* Among others, `isContract` will return false for the following
* types of addresses:
*
* - an externally-owned account
* - a contract in construction
* - an address where a contract will be created
* - an address where a contract lived, but was destroyed
*
* Furthermore, `isContract` will also return true if the target contract within
* the same transaction is already scheduled for destruction by `SELFDESTRUCT`,
* which only has an effect at the end of a transaction.
* ====
*
* [IMPORTANT]
* ====
* You shouldn't rely on `isContract` to protect against flash loan attacks!
*
* Preventing calls from contracts is highly discouraged. It breaks composability, breaks support for smart wallets
* like Gnosis Safe, and does not provide security since it can be circumvented by calling from a contract
* constructor.
* ====
*/
function isContract(address account) internal view returns (bool) {
// This method relies on extcodesize/address.code.length, which returns 0
// for contracts in construction, since the code is only stored at the end
// of the constructor execution.
return account.code.length > 0;
}
/**
* @dev Replacement for Solidity's `transfer`: sends `amount` wei to
* `recipient`, forwarding all available gas and reverting on errors.
*
* https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost
* of certain opcodes, possibly making contracts go over the 2300 gas limit
* imposed by `transfer`, making them unable to receive funds via
* `transfer`. {sendValue} removes this limitation.
*
* https://consensys.net/diligence/blog/2019/09/stop-using-soliditys-transfer-now/[Learn more].
*
* IMPORTANT: because control is transferred to `recipient`, care must be
* taken to not create reentrancy vulnerabilities. Consider using
* {ReentrancyGuard} or the
* https://solidity.readthedocs.io/en/v0.8.0/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
*/
function sendValue(address payable recipient, uint256 amount) internal {
require(address(this).balance >= amount, "Address: insufficient balance");
(bool success, ) = recipient.call{value: amount}("");
require(success, "Address: unable to send value, recipient may have reverted");
}
/**
* @dev Performs a Solidity function call using a low level `call`. A
* plain `call` is an unsafe replacement for a function call: use this
* function instead.
*
* If `target` reverts with a revert reason, it is bubbled up by this
* function (like regular Solidity function calls).
*
* Returns the raw returned data. To convert to the expected return value,
* use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].
*
* Requirements:
*
* - `target` must be a contract.
* - calling `target` with `data` must not revert.
*
* _Available since v3.1._
*/
function functionCall(address target, bytes memory data) internal returns (bytes memory) {
return functionCallWithValue(target, data, 0, "Address: low-level call failed");
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with
* `errorMessage` as a fallback revert reason when `target` reverts.
*
* _Available since v3.1._
*/
function functionCall(
address target,
bytes memory data,
string memory errorMessage
) internal returns (bytes memory) {
return functionCallWithValue(target, data, 0, errorMessage);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but also transferring `value` wei to `target`.
*
* Requirements:
*
* - the calling contract must have an ETH balance of at least `value`.
* - the called Solidity function must be `payable`.
*
* _Available since v3.1._
*/
function functionCallWithValue(address target, bytes memory data, uint256 value) internal returns (bytes memory) {
return functionCallWithValue(target, data, value, "Address: low-level call with value failed");
}
/**
* @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but
* with `errorMessage` as a fallback revert reason when `target` reverts.
*
* _Available since v3.1._
*/
function functionCallWithValue(
address target,
bytes memory data,
uint256 value,
string memory errorMessage
) internal returns (bytes memory) {
require(address(this).balance >= value, "Address: insufficient balance for call");
(bool success, bytes memory returndata) = target.call{value: value}(data);
return verifyCallResultFromTarget(target, success, returndata, errorMessage);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but performing a static call.
*
* _Available since v3.3._
*/
function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {
return functionStaticCall(target, data, "Address: low-level static call failed");
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
* but performing a static call.
*
* _Available since v3.3._
*/
function functionStaticCall(
address target,
bytes memory data,
string memory errorMessage
) internal view returns (bytes memory) {
(bool success, bytes memory returndata) = target.staticcall(data);
return verifyCallResultFromTarget(target, success, returndata, errorMessage);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but performing a delegate call.
*
* _Available since v3.4._
*/
function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {
return functionDelegateCall(target, data, "Address: low-level delegate call failed");
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
* but performing a delegate call.
*
* _Available since v3.4._
*/
function functionDelegateCall(
address target,
bytes memory data,
string memory errorMessage
) internal returns (bytes memory) {
(bool success, bytes memory returndata) = target.delegatecall(data);
return verifyCallResultFromTarget(target, success, returndata, errorMessage);
}
/**
* @dev Tool to verify that a low level call to smart-contract was successful, and revert (either by bubbling
* the revert reason or using the provided one) in case of unsuccessful call or if target was not a contract.
*
* _Available since v4.8._
*/
function verifyCallResultFromTarget(
address target,
bool success,
bytes memory returndata,
string memory errorMessage
) internal view returns (bytes memory) {
if (success) {
if (returndata.length == 0) {
// only check isContract if the call was successful and the return data is empty
// otherwise we already know that it was a contract
require(isContract(target), "Address: call to non-contract");
}
return returndata;
} else {
_revert(returndata, errorMessage);
}
}
/**
* @dev Tool to verify that a low level call was successful, and revert if it wasn't, either by bubbling the
* revert reason or using the provided one.
*
* _Available since v4.3._
*/
function verifyCallResult(
bool success,
bytes memory returndata,
string memory errorMessage
) internal pure returns (bytes memory) {
if (success) {
return returndata;
} else {
_revert(returndata, errorMessage);
}
}
function _revert(bytes memory returndata, string memory errorMessage) private pure {
// Look for revert reason and bubble it up if present
if (returndata.length > 0) {
// The easiest way to bubble the revert reason is using memory via assembly
/// @solidity memory-safe-assembly
assembly {
let returndata_size := mload(returndata)
revert(add(32, returndata), returndata_size)
}
} else {
revert(errorMessage);
}
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.2) (utils/cryptography/MerkleProof.sol)
pragma solidity ^0.8.0;
/**
* @dev These functions deal with verification of Merkle Tree proofs.
*
* The tree and the proofs can be generated using our
* https://github.com/OpenZeppelin/merkle-tree[JavaScript library].
* You will find a quickstart guide in the readme.
*
* WARNING: You should avoid using leaf values that are 64 bytes long prior to
* hashing, or use a hash function other than keccak256 for hashing leaves.
* This is because the concatenation of a sorted pair of internal nodes in
* the merkle tree could be reinterpreted as a leaf value.
* OpenZeppelin's JavaScript library generates merkle trees that are safe
* against this attack out of the box.
*/
library MerkleProof {
/**
* @dev Returns true if a `leaf` can be proved to be a part of a Merkle tree
* defined by `root`. For this, a `proof` must be provided, containing
* sibling hashes on the branch from the leaf to the root of the tree. Each
* pair of leaves and each pair of pre-images are assumed to be sorted.
*/
function verify(bytes32[] memory proof, bytes32 root, bytes32 leaf) internal pure returns (bool) {
return processProof(proof, leaf) == root;
}
/**
* @dev Calldata version of {verify}
*
* _Available since v4.7._
*/
function verifyCalldata(bytes32[] calldata proof, bytes32 root, bytes32 leaf) internal pure returns (bool) {
return processProofCalldata(proof, leaf) == root;
}
/**
* @dev Returns the rebuilt hash obtained by traversing a Merkle tree up
* from `leaf` using `proof`. A `proof` is valid if and only if the rebuilt
* hash matches the root of the tree. When processing the proof, the pairs
* of leafs & pre-images are assumed to be sorted.
*
* _Available since v4.4._
*/
function processProof(bytes32[] memory proof, bytes32 leaf) internal pure returns (bytes32) {
bytes32 computedHash = leaf;
for (uint256 i = 0; i < proof.length; i++) {
computedHash = _hashPair(computedHash, proof[i]);
}
return computedHash;
}
/**
* @dev Calldata version of {processProof}
*
* _Available since v4.7._
*/
function processProofCalldata(bytes32[] calldata proof, bytes32 leaf) internal pure returns (bytes32) {
bytes32 computedHash = leaf;
for (uint256 i = 0; i < proof.length; i++) {
computedHash = _hashPair(computedHash, proof[i]);
}
return computedHash;
}
/**
* @dev Returns true if the `leaves` can be simultaneously proven to be a part of a merkle tree defined by
* `root`, according to `proof` and `proofFlags` as described in {processMultiProof}.
*
* CAUTION: Not all merkle trees admit multiproofs. See {processMultiProof} for details.
*
* _Available since v4.7._
*/
function multiProofVerify(
bytes32[] memory proof,
bool[] memory proofFlags,
bytes32 root,
bytes32[] memory leaves
) internal pure returns (bool) {
return processMultiProof(proof, proofFlags, leaves) == root;
}
/**
* @dev Calldata version of {multiProofVerify}
*
* CAUTION: Not all merkle trees admit multiproofs. See {processMultiProof} for details.
*
* _Available since v4.7._
*/
function multiProofVerifyCalldata(
bytes32[] calldata proof,
bool[] calldata proofFlags,
bytes32 root,
bytes32[] memory leaves
) internal pure returns (bool) {
return processMultiProofCalldata(proof, proofFlags, leaves) == root;
}
/**
* @dev Returns the root of a tree reconstructed from `leaves` and sibling nodes in `proof`. The reconstruction
* proceeds by incrementally reconstructing all inner nodes by combining a leaf/inner node with either another
* leaf/inner node or a proof sibling node, depending on whether each `proofFlags` item is true or false
* respectively.
*
* CAUTION: Not all merkle trees admit multiproofs. To use multiproofs, it is sufficient to ensure that: 1) the tree
* is complete (but not necessarily perfect), 2) the leaves to be proven are in the opposite order they are in the
* tree (i.e., as seen from right to left starting at the deepest layer and continuing at the next layer).
*
* _Available since v4.7._
*/
function processMultiProof(
bytes32[] memory proof,
bool[] memory proofFlags,
bytes32[] memory leaves
) internal pure returns (bytes32 merkleRoot) {
// This function rebuilds the root hash by traversing the tree up from the leaves. The root is rebuilt by
// consuming and producing values on a queue. The queue starts with the `leaves` array, then goes onto the
// `hashes` array. At the end of the process, the last hash in the `hashes` array should contain the root of
// the merkle tree.
uint256 leavesLen = leaves.length;
uint256 proofLen = proof.length;
uint256 totalHashes = proofFlags.length;
// Check proof validity.
require(leavesLen + proofLen - 1 == totalHashes, "MerkleProof: invalid multiproof");
// The xxxPos values are "pointers" to the next value to consume in each array. All accesses are done using
// `xxx[xxxPos++]`, which return the current value and increment the pointer, thus mimicking a queue's "pop".
bytes32[] memory hashes = new bytes32[](totalHashes);
uint256 leafPos = 0;
uint256 hashPos = 0;
uint256 proofPos = 0;
// At each step, we compute the next hash using two values:
// - a value from the "main queue". If not all leaves have been consumed, we get the next leaf, otherwise we
// get the next hash.
// - depending on the flag, either another value from the "main queue" (merging branches) or an element from the
// `proof` array.
for (uint256 i = 0; i < totalHashes; i++) {
bytes32 a = leafPos < leavesLen ? leaves[leafPos++] : hashes[hashPos++];
bytes32 b = proofFlags[i]
? (leafPos < leavesLen ? leaves[leafPos++] : hashes[hashPos++])
: proof[proofPos++];
hashes[i] = _hashPair(a, b);
}
if (totalHashes > 0) {
require(proofPos == proofLen, "MerkleProof: invalid multiproof");
unchecked {
return hashes[totalHashes - 1];
}
} else if (leavesLen > 0) {
return leaves[0];
} else {
return proof[0];
}
}
/**
* @dev Calldata version of {processMultiProof}.
*
* CAUTION: Not all merkle trees admit multiproofs. See {processMultiProof} for details.
*
* _Available since v4.7._
*/
function processMultiProofCalldata(
bytes32[] calldata proof,
bool[] calldata proofFlags,
bytes32[] memory leaves
) internal pure returns (bytes32 merkleRoot) {
// This function rebuilds the root hash by traversing the tree up from the leaves. The root is rebuilt by
// consuming and producing values on a queue. The queue starts with the `leaves` array, then goes onto the
// `hashes` array. At the end of the process, the last hash in the `hashes` array should contain the root of
// the merkle tree.
uint256 leavesLen = leaves.length;
uint256 proofLen = proof.length;
uint256 totalHashes = proofFlags.length;
// Check proof validity.
require(leavesLen + proofLen - 1 == totalHashes, "MerkleProof: invalid multiproof");
// The xxxPos values are "pointers" to the next value to consume in each array. All accesses are done using
// `xxx[xxxPos++]`, which return the current value and increment the pointer, thus mimicking a queue's "pop".
bytes32[] memory hashes = new bytes32[](totalHashes);
uint256 leafPos = 0;
uint256 hashPos = 0;
uint256 proofPos = 0;
// At each step, we compute the next hash using two values:
// - a value from the "main queue". If not all leaves have been consumed, we get the next leaf, otherwise we
// get the next hash.
// - depending on the flag, either another value from the "main queue" (merging branches) or an element from the
// `proof` array.
for (uint256 i = 0; i < totalHashes; i++) {
bytes32 a = leafPos < leavesLen ? leaves[leafPos++] : hashes[hashPos++];
bytes32 b = proofFlags[i]
? (leafPos < leavesLen ? leaves[leafPos++] : hashes[hashPos++])
: proof[proofPos++];
hashes[i] = _hashPair(a, b);
}
if (totalHashes > 0) {
require(proofPos == proofLen, "MerkleProof: invalid multiproof");
unchecked {
return hashes[totalHashes - 1];
}
} else if (leavesLen > 0) {
return leaves[0];
} else {
return proof[0];
}
}
function _hashPair(bytes32 a, bytes32 b) private pure returns (bytes32) {
return a < b ? _efficientHash(a, b) : _efficientHash(b, a);
}
function _efficientHash(bytes32 a, bytes32 b) private pure returns (bytes32 value) {
/// @solidity memory-safe-assembly
assembly {
mstore(0x00, a)
mstore(0x20, b)
value := keccak256(0x00, 0x40)
}
}
}{
"remappings": [
"@openzeppelin/contracts/=node_modules/@openzeppelin/contracts/",
"@eth-optimism/contracts/=node_modules/@eth-optimism/contracts/",
"sphinx-forge-std/=node_modules/sphinx-forge-std/src/",
"ds-test/=node_modules/ds-test/src/",
"@gnosis.pm/safe-contracts-1.3.0/=node_modules/@gnosis.pm/safe-contracts/contracts/",
"@gnosis.pm/safe-contracts-1.4.1/=node_modules/@gnosis.pm/safe-contracts-1.4.1/contracts/",
"solmate/=node_modules/solmate/"
],
"optimizer": {
"enabled": false,
"runs": 200
},
"metadata": {
"useLiteralContent": false,
"bytecodeHash": "ipfs"
},
"outputSelection": {
"*": {
"*": [
"evm.bytecode",
"evm.deployedBytecode",
"devdoc",
"userdoc",
"metadata",
"abi"
]
}
},
"evmVersion": "istanbul",
"libraries": {}
}Contract ABI
API[{"inputs":[],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint8","name":"version","type":"uint8"}],"name":"Initialized","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"merkleRoot","type":"bytes32"},{"indexed":false,"internalType":"uint256","name":"leafIndex","type":"uint256"}],"name":"SphinxActionFailed","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"merkleRoot","type":"bytes32"},{"indexed":false,"internalType":"uint256","name":"leafIndex","type":"uint256"}],"name":"SphinxActionSucceeded","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"merkleRoot","type":"bytes32"},{"indexed":true,"internalType":"uint256","name":"nonce","type":"uint256"},{"indexed":false,"internalType":"address","name":"executor","type":"address"},{"indexed":false,"internalType":"uint256","name":"numLeaves","type":"uint256"},{"indexed":false,"internalType":"string","name":"uri","type":"string"}],"name":"SphinxMerkleRootApproved","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"completedMerkleRoot","type":"bytes32"},{"indexed":true,"internalType":"bytes32","name":"canceledMerkleRoot","type":"bytes32"},{"indexed":true,"internalType":"uint256","name":"nonce","type":"uint256"},{"indexed":false,"internalType":"address","name":"executor","type":"address"},{"indexed":false,"internalType":"string","name":"uri","type":"string"}],"name":"SphinxMerkleRootCanceled","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"merkleRoot","type":"bytes32"}],"name":"SphinxMerkleRootCompleted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"merkleRoot","type":"bytes32"},{"indexed":false,"internalType":"uint256","name":"leafIndex","type":"uint256"}],"name":"SphinxMerkleRootFailed","type":"event"},{"inputs":[],"name":"VERSION","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"activeMerkleRoot","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"_root","type":"bytes32"},{"components":[{"components":[{"internalType":"uint256","name":"chainId","type":"uint256"},{"internalType":"uint256","name":"index","type":"uint256"},{"internalType":"enum SphinxLeafType","name":"leafType","type":"uint8"},{"internalType":"bytes","name":"data","type":"bytes"}],"internalType":"struct SphinxLeaf","name":"leaf","type":"tuple"},{"internalType":"bytes32[]","name":"proof","type":"bytes32[]"}],"internalType":"struct SphinxLeafWithProof","name":"_leafWithProof","type":"tuple"},{"internalType":"bytes","name":"_signatures","type":"bytes"}],"name":"approve","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"_root","type":"bytes32"},{"components":[{"components":[{"internalType":"uint256","name":"chainId","type":"uint256"},{"internalType":"uint256","name":"index","type":"uint256"},{"internalType":"enum SphinxLeafType","name":"leafType","type":"uint8"},{"internalType":"bytes","name":"data","type":"bytes"}],"internalType":"struct SphinxLeaf","name":"leaf","type":"tuple"},{"internalType":"bytes32[]","name":"proof","type":"bytes32[]"}],"internalType":"struct SphinxLeafWithProof","name":"_leafWithProof","type":"tuple"},{"internalType":"bytes","name":"_signatures","type":"bytes"}],"name":"cancel","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"components":[{"components":[{"internalType":"uint256","name":"chainId","type":"uint256"},{"internalType":"uint256","name":"index","type":"uint256"},{"internalType":"enum SphinxLeafType","name":"leafType","type":"uint8"},{"internalType":"bytes","name":"data","type":"bytes"}],"internalType":"struct SphinxLeaf","name":"leaf","type":"tuple"},{"internalType":"bytes32[]","name":"proof","type":"bytes32[]"}],"internalType":"struct SphinxLeafWithProof[]","name":"_leavesWithProofs","type":"tuple[]"}],"name":"execute","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_safeProxy","type":"address"}],"name":"initialize","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"merkleRootNonce","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"name":"merkleRootStates","outputs":[{"internalType":"uint256","name":"numLeaves","type":"uint256"},{"internalType":"uint256","name":"leavesExecuted","type":"uint256"},{"internalType":"string","name":"uri","type":"string"},{"internalType":"address","name":"executor","type":"address"},{"internalType":"enum MerkleRootStatus","name":"status","type":"uint8"},{"internalType":"bool","name":"arbitraryChain","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"safeProxy","outputs":[{"internalType":"address payable","name":"","type":"address"}],"stateMutability":"view","type":"function"}]Loading...
Loading
Loading...
Loading
Loading...
Loading
Net Worth in USD
$0.00
Net Worth in MOVR
Multichain Portfolio | 35 Chains
| Chain | Token | Portfolio % | Price | Amount | Value |
|---|
Loading...
Loading
Loading...
Loading
Loading...
Loading
[ Download: CSV Export ]
A contract address hosts a smart contract, which is a set of code stored on the blockchain that runs when predetermined conditions are met. Learn more about addresses in our Knowledge Base.