// SPDX-License-Identifier: MIT
pragma solidity ^0.8.18;
/// @title Base Authenticator abstract contract
abstract contract Authenticator {
bytes4 internal constant PROPOSE_SELECTOR = bytes4(keccak256("propose(address,string,(address,bytes),bytes)"));
bytes4 internal constant VOTE_SELECTOR = bytes4(keccak256("vote(address,uint256,uint8,(uint8,bytes)[],string)"));
bytes4 internal constant UPDATE_PROPOSAL_SELECTOR =
bytes4(keccak256("updateProposal(address,uint256,(address,bytes),string)"));
/// @dev Forwards a call to the target contract.
function _call(address target, bytes4 functionSelector, bytes memory data) internal {
// solhint-disable-next-line avoid-low-level-calls
(bool success, ) = target.call(abi.encodePacked(functionSelector, data));
if (!success) {
// If the call failed, we revert with the propagated error message.
// solhint-disable-next-line no-inline-assembly
assembly {
let returnDataSize := returndatasize()
returndatacopy(0, 0, returnDataSize)
revert(0, returnDataSize)
}
}
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.8.0) (utils/cryptography/ECDSA.sol)
pragma solidity ^0.8.0;
import "../Strings.sol";
/**
* @dev Elliptic Curve Digital Signature Algorithm (ECDSA) operations.
*
* These functions can be used to verify that a message was signed by the holder
* of the private keys of a given address.
*/
library ECDSA {
enum RecoverError {
NoError,
InvalidSignature,
InvalidSignatureLength,
InvalidSignatureS,
InvalidSignatureV // Deprecated in v4.8
}
function _throwError(RecoverError error) private pure {
if (error == RecoverError.NoError) {
return; // no error: do nothing
} else if (error == RecoverError.InvalidSignature) {
revert("ECDSA: invalid signature");
} else if (error == RecoverError.InvalidSignatureLength) {
revert("ECDSA: invalid signature length");
} else if (error == RecoverError.InvalidSignatureS) {
revert("ECDSA: invalid signature 's' value");
}
}
/**
* @dev Returns the address that signed a hashed message (`hash`) with
* `signature` or error string. This address can then be used for verification purposes.
*
* The `ecrecover` EVM opcode allows for malleable (non-unique) signatures:
* this function rejects them by requiring the `s` value to be in the lower
* half order, and the `v` value to be either 27 or 28.
*
* IMPORTANT: `hash` _must_ be the result of a hash operation for the
* verification to be secure: it is possible to craft signatures that
* recover to arbitrary addresses for non-hashed data. A safe way to ensure
* this is by receiving a hash of the original message (which may otherwise
* be too long), and then calling {toEthSignedMessageHash} on it.
*
* Documentation for signature generation:
* - with https://web3js.readthedocs.io/en/v1.3.4/web3-eth-accounts.html#sign[Web3.js]
* - with https://docs.ethers.io/v5/api/signer/#Signer-signMessage[ethers]
*
* _Available since v4.3._
*/
function tryRecover(bytes32 hash, bytes memory signature) internal pure returns (address, RecoverError) {
if (signature.length == 65) {
bytes32 r;
bytes32 s;
uint8 v;
// ecrecover takes the signature parameters, and the only way to get them
// currently is to use assembly.
/// @solidity memory-safe-assembly
assembly {
r := mload(add(signature, 0x20))
s := mload(add(signature, 0x40))
v := byte(0, mload(add(signature, 0x60)))
}
return tryRecover(hash, v, r, s);
} else {
return (address(0), RecoverError.InvalidSignatureLength);
}
}
/**
* @dev Returns the address that signed a hashed message (`hash`) with
* `signature`. This address can then be used for verification purposes.
*
* The `ecrecover` EVM opcode allows for malleable (non-unique) signatures:
* this function rejects them by requiring the `s` value to be in the lower
* half order, and the `v` value to be either 27 or 28.
*
* IMPORTANT: `hash` _must_ be the result of a hash operation for the
* verification to be secure: it is possible to craft signatures that
* recover to arbitrary addresses for non-hashed data. A safe way to ensure
* this is by receiving a hash of the original message (which may otherwise
* be too long), and then calling {toEthSignedMessageHash} on it.
*/
function recover(bytes32 hash, bytes memory signature) internal pure returns (address) {
(address recovered, RecoverError error) = tryRecover(hash, signature);
_throwError(error);
return recovered;
}
/**
* @dev Overload of {ECDSA-tryRecover} that receives the `r` and `vs` short-signature fields separately.
*
* See https://eips.ethereum.org/EIPS/eip-2098[EIP-2098 short signatures]
*
* _Available since v4.3._
*/
function tryRecover(
bytes32 hash,
bytes32 r,
bytes32 vs
) internal pure returns (address, RecoverError) {
bytes32 s = vs & bytes32(0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff);
uint8 v = uint8((uint256(vs) >> 255) + 27);
return tryRecover(hash, v, r, s);
}
/**
* @dev Overload of {ECDSA-recover} that receives the `r and `vs` short-signature fields separately.
*
* _Available since v4.2._
*/
function recover(
bytes32 hash,
bytes32 r,
bytes32 vs
) internal pure returns (address) {
(address recovered, RecoverError error) = tryRecover(hash, r, vs);
_throwError(error);
return recovered;
}
/**
* @dev Overload of {ECDSA-tryRecover} that receives the `v`,
* `r` and `s` signature fields separately.
*
* _Available since v4.3._
*/
function tryRecover(
bytes32 hash,
uint8 v,
bytes32 r,
bytes32 s
) internal pure returns (address, RecoverError) {
// EIP-2 still allows signature malleability for ecrecover(). Remove this possibility and make the signature
// unique. Appendix F in the Ethereum Yellow paper (https://ethereum.github.io/yellowpaper/paper.pdf), defines
// the valid range for s in (301): 0 < s < secp256k1n ÷ 2 + 1, and for v in (302): v ∈ {27, 28}. Most
// signatures from current libraries generate a unique signature with an s-value in the lower half order.
//
// If your library generates malleable signatures, such as s-values in the upper range, calculate a new s-value
// with 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141 - s1 and flip v from 27 to 28 or
// vice versa. If your library also generates signatures with 0/1 for v instead 27/28, add 27 to v to accept
// these malleable signatures as well.
if (uint256(s) > 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0) {
return (address(0), RecoverError.InvalidSignatureS);
}
// If the signature is valid (and not malleable), return the signer address
address signer = ecrecover(hash, v, r, s);
if (signer == address(0)) {
return (address(0), RecoverError.InvalidSignature);
}
return (signer, RecoverError.NoError);
}
/**
* @dev Overload of {ECDSA-recover} that receives the `v`,
* `r` and `s` signature fields separately.
*/
function recover(
bytes32 hash,
uint8 v,
bytes32 r,
bytes32 s
) internal pure returns (address) {
(address recovered, RecoverError error) = tryRecover(hash, v, r, s);
_throwError(error);
return recovered;
}
/**
* @dev Returns an Ethereum Signed Message, created from a `hash`. This
* produces hash corresponding to the one signed with the
* https://eth.wiki/json-rpc/API#eth_sign[`eth_sign`]
* JSON-RPC method as part of EIP-191.
*
* See {recover}.
*/
function toEthSignedMessageHash(bytes32 hash) internal pure returns (bytes32) {
// 32 is the length in bytes of hash,
// enforced by the type signature above
return keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", hash));
}
/**
* @dev Returns an Ethereum Signed Message, created from `s`. This
* produces hash corresponding to the one signed with the
* https://eth.wiki/json-rpc/API#eth_sign[`eth_sign`]
* JSON-RPC method as part of EIP-191.
*
* See {recover}.
*/
function toEthSignedMessageHash(bytes memory s) internal pure returns (bytes32) {
return keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n", Strings.toString(s.length), s));
}
/**
* @dev Returns an Ethereum Signed Typed Data, created from a
* `domainSeparator` and a `structHash`. This produces hash corresponding
* to the one signed with the
* https://eips.ethereum.org/EIPS/eip-712[`eth_signTypedData`]
* JSON-RPC method as part of EIP-712.
*
* See {recover}.
*/
function toTypedDataHash(bytes32 domainSeparator, bytes32 structHash) internal pure returns (bytes32) {
return keccak256(abi.encodePacked("\x19\x01", domainSeparator, structHash));
}
}
// SPDX-License-Identifier: LGPL-3.0-only
pragma solidity >=0.7.0 <0.9.0;
/// @title Enum - Collection of enums
/// @author Richard Meissner - <richard@gnosis.pm>
contract Enum {
enum Operation {Call, DelegateCall}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.18;
import { Authenticator } from "./Authenticator.sol";
import { SignatureVerifier } from "../utils/SignatureVerifier.sol";
/// @title Ethereum Signature Authenticator
contract EthSigAuthenticator is Authenticator, SignatureVerifier {
error InvalidFunctionSelector();
// solhint-disable-next-line no-empty-blocks
constructor(string memory name, string memory version) SignatureVerifier(name, version) {}
/// @notice Authenticates a user by verifying an EIP712 signature.
/// @param v The v component of the signature.
/// @param r The r component of the signature.
/// @param s The s component of the signature.
/// @param salt The salt used to generate the signature.
/// @param target The target Space contract address.
/// @param functionSelector The function selector of the function to be called.
function authenticate(
uint8 v,
bytes32 r,
bytes32 s,
uint256 salt,
address target,
bytes4 functionSelector,
bytes calldata data
) external {
if (functionSelector == PROPOSE_SELECTOR) {
_verifyProposeSig(v, r, s, salt, target, data);
} else if (functionSelector == VOTE_SELECTOR) {
_verifyVoteSig(v, r, s, target, data);
} else if (functionSelector == UPDATE_PROPOSAL_SELECTOR) {
_verifyUpdateProposalSig(v, r, s, salt, target, data);
} else {
revert InvalidFunctionSelector();
}
_call(target, functionSelector, data);
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.18;
import { ProposalStatus } from "../../types.sol";
/// @title Execution Strategy Errors
interface IExecutionStrategyErrors {
/// @notice Thrown when the current status of a proposal does not allow the desired action.
/// @param status The current status of the proposal.
error InvalidProposalStatus(ProposalStatus status);
/// @notice Thrown when the execution of a proposal fails.
error ExecutionFailed();
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.8.0) (utils/math/Math.sol)
pragma solidity ^0.8.0;
/**
* @dev Standard math utilities missing in the Solidity language.
*/
library Math {
enum Rounding {
Down, // Toward negative infinity
Up, // Toward infinity
Zero // Toward zero
}
/**
* @dev Returns the largest of two numbers.
*/
function max(uint256 a, uint256 b) internal pure returns (uint256) {
return a > b ? a : b;
}
/**
* @dev Returns the smallest of two numbers.
*/
function min(uint256 a, uint256 b) internal pure returns (uint256) {
return a < b ? a : b;
}
/**
* @dev Returns the average of two numbers. The result is rounded towards
* zero.
*/
function average(uint256 a, uint256 b) internal pure returns (uint256) {
// (a + b) / 2 can overflow.
return (a & b) + (a ^ b) / 2;
}
/**
* @dev Returns the ceiling of the division of two numbers.
*
* This differs from standard division with `/` in that it rounds up instead
* of rounding down.
*/
function ceilDiv(uint256 a, uint256 b) internal pure returns (uint256) {
// (a + b - 1) / b can overflow on addition, so we distribute.
return a == 0 ? 0 : (a - 1) / b + 1;
}
/**
* @notice Calculates floor(x * y / denominator) with full precision. Throws if result overflows a uint256 or denominator == 0
* @dev Original credit to Remco Bloemen under MIT license (https://xn--2-umb.com/21/muldiv)
* with further edits by Uniswap Labs also under MIT license.
*/
function mulDiv(
uint256 x,
uint256 y,
uint256 denominator
) internal pure returns (uint256 result) {
unchecked {
// 512-bit multiply [prod1 prod0] = x * y. Compute the product mod 2^256 and mod 2^256 - 1, then use
// use the Chinese Remainder Theorem to reconstruct the 512 bit result. The result is stored in two 256
// variables such that product = prod1 * 2^256 + prod0.
uint256 prod0; // Least significant 256 bits of the product
uint256 prod1; // Most significant 256 bits of the product
assembly {
let mm := mulmod(x, y, not(0))
prod0 := mul(x, y)
prod1 := sub(sub(mm, prod0), lt(mm, prod0))
}
// Handle non-overflow cases, 256 by 256 division.
if (prod1 == 0) {
return prod0 / denominator;
}
// Make sure the result is less than 2^256. Also prevents denominator == 0.
require(denominator > prod1, "Math: mulDiv overflow");
///////////////////////////////////////////////
// 512 by 256 division.
///////////////////////////////////////////////
// Make division exact by subtracting the remainder from [prod1 prod0].
uint256 remainder;
assembly {
// Compute remainder using mulmod.
remainder := mulmod(x, y, denominator)
// Subtract 256 bit number from 512 bit number.
prod1 := sub(prod1, gt(remainder, prod0))
prod0 := sub(prod0, remainder)
}
// Factor powers of two out of denominator and compute largest power of two divisor of denominator. Always >= 1.
// See https://cs.stackexchange.com/q/138556/92363.
// Does not overflow because the denominator cannot be zero at this stage in the function.
uint256 twos = denominator & (~denominator + 1);
assembly {
// Divide denominator by twos.
denominator := div(denominator, twos)
// Divide [prod1 prod0] by twos.
prod0 := div(prod0, twos)
// Flip twos such that it is 2^256 / twos. If twos is zero, then it becomes one.
twos := add(div(sub(0, twos), twos), 1)
}
// Shift in bits from prod1 into prod0.
prod0 |= prod1 * twos;
// Invert denominator mod 2^256. Now that denominator is an odd number, it has an inverse modulo 2^256 such
// that denominator * inv = 1 mod 2^256. Compute the inverse by starting with a seed that is correct for
// four bits. That is, denominator * inv = 1 mod 2^4.
uint256 inverse = (3 * denominator) ^ 2;
// Use the Newton-Raphson iteration to improve the precision. Thanks to Hensel's lifting lemma, this also works
// in modular arithmetic, doubling the correct bits in each step.
inverse *= 2 - denominator * inverse; // inverse mod 2^8
inverse *= 2 - denominator * inverse; // inverse mod 2^16
inverse *= 2 - denominator * inverse; // inverse mod 2^32
inverse *= 2 - denominator * inverse; // inverse mod 2^64
inverse *= 2 - denominator * inverse; // inverse mod 2^128
inverse *= 2 - denominator * inverse; // inverse mod 2^256
// Because the division is now exact we can divide by multiplying with the modular inverse of denominator.
// This will give us the correct result modulo 2^256. Since the preconditions guarantee that the outcome is
// less than 2^256, this is the final result. We don't need to compute the high bits of the result and prod1
// is no longer required.
result = prod0 * inverse;
return result;
}
}
/**
* @notice Calculates x * y / denominator with full precision, following the selected rounding direction.
*/
function mulDiv(
uint256 x,
uint256 y,
uint256 denominator,
Rounding rounding
) internal pure returns (uint256) {
uint256 result = mulDiv(x, y, denominator);
if (rounding == Rounding.Up && mulmod(x, y, denominator) > 0) {
result += 1;
}
return result;
}
/**
* @dev Returns the square root of a number. If the number is not a perfect square, the value is rounded down.
*
* Inspired by Henry S. Warren, Jr.'s "Hacker's Delight" (Chapter 11).
*/
function sqrt(uint256 a) internal pure returns (uint256) {
if (a == 0) {
return 0;
}
// For our first guess, we get the biggest power of 2 which is smaller than the square root of the target.
//
// We know that the "msb" (most significant bit) of our target number `a` is a power of 2 such that we have
// `msb(a) <= a < 2*msb(a)`. This value can be written `msb(a)=2**k` with `k=log2(a)`.
//
// This can be rewritten `2**log2(a) <= a < 2**(log2(a) + 1)`
// → `sqrt(2**k) <= sqrt(a) < sqrt(2**(k+1))`
// → `2**(k/2) <= sqrt(a) < 2**((k+1)/2) <= 2**(k/2 + 1)`
//
// Consequently, `2**(log2(a) / 2)` is a good first approximation of `sqrt(a)` with at least 1 correct bit.
uint256 result = 1 << (log2(a) >> 1);
// At this point `result` is an estimation with one bit of precision. We know the true value is a uint128,
// since it is the square root of a uint256. Newton's method converges quadratically (precision doubles at
// every iteration). We thus need at most 7 iteration to turn our partial result with one bit of precision
// into the expected uint128 result.
unchecked {
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
return min(result, a / result);
}
}
/**
* @notice Calculates sqrt(a), following the selected rounding direction.
*/
function sqrt(uint256 a, Rounding rounding) internal pure returns (uint256) {
unchecked {
uint256 result = sqrt(a);
return result + (rounding == Rounding.Up && result * result < a ? 1 : 0);
}
}
/**
* @dev Return the log in base 2, rounded down, of a positive value.
* Returns 0 if given 0.
*/
function log2(uint256 value) internal pure returns (uint256) {
uint256 result = 0;
unchecked {
if (value >> 128 > 0) {
value >>= 128;
result += 128;
}
if (value >> 64 > 0) {
value >>= 64;
result += 64;
}
if (value >> 32 > 0) {
value >>= 32;
result += 32;
}
if (value >> 16 > 0) {
value >>= 16;
result += 16;
}
if (value >> 8 > 0) {
value >>= 8;
result += 8;
}
if (value >> 4 > 0) {
value >>= 4;
result += 4;
}
if (value >> 2 > 0) {
value >>= 2;
result += 2;
}
if (value >> 1 > 0) {
result += 1;
}
}
return result;
}
/**
* @dev Return the log in base 2, following the selected rounding direction, of a positive value.
* Returns 0 if given 0.
*/
function log2(uint256 value, Rounding rounding) internal pure returns (uint256) {
unchecked {
uint256 result = log2(value);
return result + (rounding == Rounding.Up && 1 << result < value ? 1 : 0);
}
}
/**
* @dev Return the log in base 10, rounded down, of a positive value.
* Returns 0 if given 0.
*/
function log10(uint256 value) internal pure returns (uint256) {
uint256 result = 0;
unchecked {
if (value >= 10**64) {
value /= 10**64;
result += 64;
}
if (value >= 10**32) {
value /= 10**32;
result += 32;
}
if (value >= 10**16) {
value /= 10**16;
result += 16;
}
if (value >= 10**8) {
value /= 10**8;
result += 8;
}
if (value >= 10**4) {
value /= 10**4;
result += 4;
}
if (value >= 10**2) {
value /= 10**2;
result += 2;
}
if (value >= 10**1) {
result += 1;
}
}
return result;
}
/**
* @dev Return the log in base 10, following the selected rounding direction, of a positive value.
* Returns 0 if given 0.
*/
function log10(uint256 value, Rounding rounding) internal pure returns (uint256) {
unchecked {
uint256 result = log10(value);
return result + (rounding == Rounding.Up && 10**result < value ? 1 : 0);
}
}
/**
* @dev Return the log in base 256, rounded down, of a positive value.
* Returns 0 if given 0.
*
* Adding one to the result gives the number of pairs of hex symbols needed to represent `value` as a hex string.
*/
function log256(uint256 value) internal pure returns (uint256) {
uint256 result = 0;
unchecked {
if (value >> 128 > 0) {
value >>= 128;
result += 16;
}
if (value >> 64 > 0) {
value >>= 64;
result += 8;
}
if (value >> 32 > 0) {
value >>= 32;
result += 4;
}
if (value >> 16 > 0) {
value >>= 16;
result += 2;
}
if (value >> 8 > 0) {
result += 1;
}
}
return result;
}
/**
* @dev Return the log in base 10, following the selected rounding direction, of a positive value.
* Returns 0 if given 0.
*/
function log256(uint256 value, Rounding rounding) internal pure returns (uint256) {
unchecked {
uint256 result = log256(value);
return result + (rounding == Rounding.Up && 1 << (result << 3) < value ? 1 : 0);
}
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.18;
import { IndexedStrategy, Strategy } from "src/types.sol";
/// @title Snapshot X Types Hashing Library
/// @notice For use in EIP712 signatures.
library SXHash {
bytes32 private constant STRATEGY_TYPEHASH = keccak256("Strategy(address addr,bytes params)");
bytes32 private constant INDEXED_STRATEGY_TYPEHASH = keccak256("IndexedStrategy(uint8 index,bytes params)");
/// @dev Hashes a Strategy type.
function hash(Strategy memory strategy) internal pure returns (bytes32) {
return keccak256(abi.encode(STRATEGY_TYPEHASH, strategy.addr, keccak256(strategy.params)));
}
/// @dev Hashes an array of IndexedStrategy types.
function hash(IndexedStrategy[] memory indexedStrategies) internal pure returns (bytes32) {
bytes32[] memory indexedStrategyHashes = new bytes32[](indexedStrategies.length);
for (uint256 i = 0; i < indexedStrategies.length; i++) {
indexedStrategyHashes[i] = keccak256(
abi.encode(
INDEXED_STRATEGY_TYPEHASH,
indexedStrategies[i].index,
keccak256(indexedStrategies[i].params)
)
);
}
return keccak256(abi.encodePacked(indexedStrategyHashes));
}
/// @dev Hashes an IndexedStrategy type.
function hash(IndexedStrategy memory indexedStrategy) internal pure returns (bytes32) {
return
keccak256(abi.encode(INDEXED_STRATEGY_TYPEHASH, indexedStrategy.index, keccak256(indexedStrategy.params)));
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.18;
import { ECDSA } from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
import { EIP712 } from "@openzeppelin/contracts/utils/cryptography/EIP712.sol";
import { Choice, IndexedStrategy, Strategy } from "src/types.sol";
import { SXHash } from "src/utils/SXHash.sol";
import { TRUE, FALSE } from "../types.sol";
/// @title EIP712 Signature Verifier
/// @notice Verifies Signatures for Snapshot X actions.
abstract contract SignatureVerifier is EIP712 {
using SXHash for IndexedStrategy[];
using SXHash for IndexedStrategy;
using SXHash for Strategy;
/// @notice Thrown if a signature is invalid.
error InvalidSignature();
/// @notice Thrown if a user has already used a specific salt.
error SaltAlreadyUsed();
bytes32 private constant PROPOSE_TYPEHASH =
keccak256(
"Propose(address space,address author,string metadataURI,Strategy executionStrategy,"
"bytes userProposalValidationParams,uint256 salt)"
"Strategy(address addr,bytes params)"
);
bytes32 private constant VOTE_TYPEHASH =
keccak256(
"Vote(address space,address voter,uint256 proposalId,uint8 choice,"
"IndexedStrategy[] userVotingStrategies,string voteMetadataURI)"
"IndexedStrategy(uint8 index,bytes params)"
);
bytes32 private constant UPDATE_PROPOSAL_TYPEHASH =
keccak256(
"updateProposal(address space,address author,uint256 proposalId,"
"Strategy executionStrategy,string metadataURI,uint256 salt)"
"Strategy(address addr,bytes params)"
);
mapping(address author => mapping(uint256 salt => uint256 used)) private usedSalts;
// solhint-disable-next-line no-empty-blocks
constructor(string memory name, string memory version) EIP712(name, version) {}
/// @dev Verifies an EIP712 signature for a propose call.
/// We use memory instead of calldata here for the `data` argument because of stack constraints.
function _verifyProposeSig(uint8 v, bytes32 r, bytes32 s, uint256 salt, address space, bytes memory data) internal {
(
address author,
string memory metadataURI,
Strategy memory executionStrategy,
bytes memory userProposalValidationParams
) = abi.decode(data, (address, string, Strategy, bytes));
if (usedSalts[author][salt] != FALSE) revert SaltAlreadyUsed();
address recoveredAddress = ECDSA.recover(
_hashTypedDataV4(
keccak256(
abi.encode(
PROPOSE_TYPEHASH,
space,
author,
keccak256(bytes(metadataURI)),
executionStrategy.hash(),
keccak256(userProposalValidationParams),
salt
)
)
),
v,
r,
s
);
if (recoveredAddress != author) revert InvalidSignature();
// Mark salt as used to prevent replay attacks.
usedSalts[author][salt] = TRUE;
}
/// @dev Verifies an EIP712 signature for a vote call.
function _verifyVoteSig(uint8 v, bytes32 r, bytes32 s, address space, bytes calldata data) internal view {
(
address voter,
uint256 proposeId,
Choice choice,
IndexedStrategy[] memory userVotingStrategies,
string memory voteMetadataURI
) = abi.decode(data, (address, uint256, Choice, IndexedStrategy[], string));
address recoveredAddress = ECDSA.recover(
_hashTypedDataV4(
keccak256(
abi.encode(
VOTE_TYPEHASH,
space,
voter,
proposeId,
choice,
userVotingStrategies.hash(),
keccak256(bytes(voteMetadataURI))
)
)
),
v,
r,
s
);
if (recoveredAddress != voter) revert InvalidSignature();
}
/// @dev Verifies an EIP712 signature for an update proposal call.
/// We use memory instead of calldata here for the `data` argument because of stack constraints.
function _verifyUpdateProposalSig(
uint8 v,
bytes32 r,
bytes32 s,
uint256 salt,
address space,
bytes memory data
) internal {
(address author, uint256 proposalId, Strategy memory executionStrategy, string memory metadataURI) = abi.decode(
data,
(address, uint256, Strategy, string)
);
if (usedSalts[author][salt] != FALSE) revert SaltAlreadyUsed();
address recoveredAddress = ECDSA.recover(
_hashTypedDataV4(
keccak256(
abi.encode(
UPDATE_PROPOSAL_TYPEHASH,
space,
author,
proposalId,
executionStrategy.hash(),
keccak256(bytes(metadataURI)),
salt
)
)
),
v,
r,
s
);
if (recoveredAddress != author) revert InvalidSignature();
// Mark salt as used to prevent replay attacks.
usedSalts[author][salt] = TRUE;
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.8.0) (utils/Strings.sol)
pragma solidity ^0.8.0;
import "./math/Math.sol";
/**
* @dev String operations.
*/
library Strings {
bytes16 private constant _SYMBOLS = "0123456789abcdef";
uint8 private constant _ADDRESS_LENGTH = 20;
/**
* @dev Converts a `uint256` to its ASCII `string` decimal representation.
*/
function toString(uint256 value) internal pure returns (string memory) {
unchecked {
uint256 length = Math.log10(value) + 1;
string memory buffer = new string(length);
uint256 ptr;
/// @solidity memory-safe-assembly
assembly {
ptr := add(buffer, add(32, length))
}
while (true) {
ptr--;
/// @solidity memory-safe-assembly
assembly {
mstore8(ptr, byte(mod(value, 10), _SYMBOLS))
}
value /= 10;
if (value == 0) break;
}
return buffer;
}
}
/**
* @dev Converts a `uint256` to its ASCII `string` hexadecimal representation.
*/
function toHexString(uint256 value) internal pure returns (string memory) {
unchecked {
return toHexString(value, Math.log256(value) + 1);
}
}
/**
* @dev Converts a `uint256` to its ASCII `string` hexadecimal representation with fixed length.
*/
function toHexString(uint256 value, uint256 length) internal pure returns (string memory) {
bytes memory buffer = new bytes(2 * length + 2);
buffer[0] = "0";
buffer[1] = "x";
for (uint256 i = 2 * length + 1; i > 1; --i) {
buffer[i] = _SYMBOLS[value & 0xf];
value >>= 4;
}
require(value == 0, "Strings: hex length insufficient");
return string(buffer);
}
/**
* @dev Converts an `address` with fixed length of 20 bytes to its not checksummed ASCII `string` hexadecimal representation.
*/
function toHexString(address addr) internal pure returns (string memory) {
return toHexString(uint256(uint160(addr)), _ADDRESS_LENGTH);
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.18;
import { Enum } from "@gnosis.pm/safe-contracts/contracts/common/Enum.sol";
import { IExecutionStrategy } from "src/interfaces/IExecutionStrategy.sol";
/// @dev Constants used to replace the `bool` type in mappings for gas efficiency.
uint256 constant TRUE = 1;
uint256 constant FALSE = 0;
/// @notice The data stored for each proposal when it is created.
/// @dev Packed into 4 256-bit slots.
struct Proposal {
// SLOT 1:
// The address of the proposal creator.
address author;
// The block number at which the voting period starts.
// This is also the snapshot block number where voting power is calculated at.
uint32 startBlockNumber;
//
// SLOT 2:
// The address of execution strategy used for the proposal.
IExecutionStrategy executionStrategy;
// The minimum block number at which the proposal can be finalized.
uint32 minEndBlockNumber;
// The maximum block number at which the proposal can be finalized.
uint32 maxEndBlockNumber;
// An enum that stores whether a proposal is pending, executed, or cancelled.
FinalizationStatus finalizationStatus;
//
// SLOT 3:
// The hash of the execution payload. We do not store the payload itself to save gas.
bytes32 executionPayloadHash;
//
// SLOT 4:
// Bit array where the index of each each bit corresponds to whether the voting strategy.
// at that index is active at the time of proposal creation.
uint256 activeVotingStrategies;
}
/// @notice The data stored for each strategy.
struct Strategy {
// The address of the strategy contract.
address addr;
// The parameters of the strategy.
bytes params;
}
/// @notice The data stored for each indexed strategy.
struct IndexedStrategy {
uint8 index;
bytes params;
}
/// @notice The set of possible finalization statuses for a proposal.
/// This is stored inside each Proposal struct.
enum FinalizationStatus {
Pending,
Executed,
Cancelled
}
/// @notice The set of possible statuses for a proposal.
enum ProposalStatus {
VotingDelay,
VotingPeriod,
VotingPeriodAccepted,
Accepted,
Executed,
Rejected,
Cancelled
}
/// @notice The set of possible choices for a vote.
enum Choice {
Against,
For,
Abstain
}
/// @notice Transaction struct that can be used to represent transactions inside a proposal.
struct MetaTransaction {
address to;
uint256 value;
bytes data;
Enum.Operation operation;
// We require a salt so that the struct can always be unique and we can use its hash as a unique identifier.
uint256 salt;
}
/// @dev Structure used for the function `initialize` of the Space contract because of solidity's stack constraints.
/// For more information, see `ISpaceActions.sol`.
struct InitializeCalldata {
address owner;
uint32 votingDelay;
uint32 minVotingDuration;
uint32 maxVotingDuration;
Strategy proposalValidationStrategy;
string proposalValidationStrategyMetadataURI;
string daoURI;
string metadataURI;
Strategy[] votingStrategies;
string[] votingStrategyMetadataURIs;
address[] authenticators;
}
/// @dev Structure used for the function `updateSettings` of the Space contract because of solidity's stack constraints.
/// For more information, see `ISpaceOwnerActions.sol`.
struct UpdateSettingsCalldata {
uint32 minVotingDuration;
uint32 maxVotingDuration;
uint32 votingDelay;
string metadataURI;
string daoURI;
Strategy proposalValidationStrategy;
string proposalValidationStrategyMetadataURI;
address[] authenticatorsToAdd;
address[] authenticatorsToRemove;
Strategy[] votingStrategiesToAdd;
string[] votingStrategyMetadataURIsToAdd;
uint8[] votingStrategiesToRemove;
}
{
"compilationTarget": {
"src/authenticators/EthSigAuthenticator.sol": "EthSigAuthenticator"
},
"evmVersion": "paris",
"libraries": {},
"metadata": {
"bytecodeHash": "none"
},
"optimizer": {
"enabled": true,
"runs": 10000
},
"remappings": [
":@gnosis.pm/safe-contracts/=lib/safe-contracts/",
":@murky/=lib/murky/src/",
":@openzeppelin/contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/contracts/",
":@openzeppelin/contracts/=lib/openzeppelin-contracts/contracts/",
":@prb/test/=lib/prb-test/src/",
":@zodiac/=lib/zodiac/contracts/",
":ds-test/=lib/forge-std/lib/ds-test/src/",
":erc4626-tests/=lib/openzeppelin-contracts/lib/erc4626-tests/",
":forge-gas-snapshot/=lib/forge-gas-snapshot/src/",
":forge-std/=lib/forge-std/src/",
":murky/=lib/murky/",
":openzeppelin-contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/",
":openzeppelin-contracts/=lib/openzeppelin-contracts/",
":prb-test/=lib/prb-test/src/",
":safe-contracts/=lib/safe-contracts/contracts/",
":zodiac/=lib/zodiac/contracts/"
],
"viaIR": true
}
[{"inputs":[{"internalType":"string","name":"name","type":"string"},{"internalType":"string","name":"version","type":"string"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"InvalidFunctionSelector","type":"error"},{"inputs":[],"name":"InvalidSignature","type":"error"},{"inputs":[],"name":"SaltAlreadyUsed","type":"error"},{"inputs":[{"internalType":"uint8","name":"v","type":"uint8"},{"internalType":"bytes32","name":"r","type":"bytes32"},{"internalType":"bytes32","name":"s","type":"bytes32"},{"internalType":"uint256","name":"salt","type":"uint256"},{"internalType":"address","name":"target","type":"address"},{"internalType":"bytes4","name":"functionSelector","type":"bytes4"},{"internalType":"bytes","name":"data","type":"bytes"}],"name":"authenticate","outputs":[],"stateMutability":"nonpayable","type":"function"}]