Contract Name:
AvocadoMultisig
Contract Source Code:
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (interfaces/IERC1271.sol)
pragma solidity ^0.8.0;
/**
* @dev Interface of the ERC1271 standard signature validation method for
* contracts as defined in https://eips.ethereum.org/EIPS/eip-1271[ERC-1271].
*
* _Available since v4.1._
*/
interface IERC1271 {
/**
* @dev Should return whether the signature provided is valid for the provided data
* @param hash Hash of the data to be signed
* @param signature Signature byte array associated with _data
*/
function isValidSignature(bytes32 hash, bytes memory signature) external view returns (bytes4 magicValue);
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.5.0) (token/ERC1155/IERC1155Receiver.sol)
pragma solidity ^0.8.0;
import "../../utils/introspection/IERC165.sol";
/**
* @dev _Available since v3.1._
*/
interface IERC1155Receiver is IERC165 {
/**
* @dev Handles the receipt of a single ERC1155 token type. This function is
* called at the end of a `safeTransferFrom` after the balance has been updated.
*
* NOTE: To accept the transfer, this must return
* `bytes4(keccak256("onERC1155Received(address,address,uint256,uint256,bytes)"))`
* (i.e. 0xf23a6e61, or its own function selector).
*
* @param operator The address which initiated the transfer (i.e. msg.sender)
* @param from The address which previously owned the token
* @param id The ID of the token being transferred
* @param value The amount of tokens being transferred
* @param data Additional data with no specified format
* @return `bytes4(keccak256("onERC1155Received(address,address,uint256,uint256,bytes)"))` if transfer is allowed
*/
function onERC1155Received(
address operator,
address from,
uint256 id,
uint256 value,
bytes calldata data
) external returns (bytes4);
/**
* @dev Handles the receipt of a multiple ERC1155 token types. This function
* is called at the end of a `safeBatchTransferFrom` after the balances have
* been updated.
*
* NOTE: To accept the transfer(s), this must return
* `bytes4(keccak256("onERC1155BatchReceived(address,address,uint256[],uint256[],bytes)"))`
* (i.e. 0xbc197c81, or its own function selector).
*
* @param operator The address which initiated the batch transfer (i.e. msg.sender)
* @param from The address which previously owned the token
* @param ids An array containing ids of each token being transferred (order and length must match values array)
* @param values An array containing amounts of each token being transferred (order and length must match ids array)
* @param data Additional data with no specified format
* @return `bytes4(keccak256("onERC1155BatchReceived(address,address,uint256[],uint256[],bytes)"))` if transfer is allowed
*/
function onERC1155BatchReceived(
address operator,
address from,
uint256[] calldata ids,
uint256[] calldata values,
bytes calldata data
) external returns (bytes4);
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.5.0) (token/ERC1155/utils/ERC1155Holder.sol)
pragma solidity ^0.8.0;
import "./ERC1155Receiver.sol";
/**
* Simple implementation of `ERC1155Receiver` that will allow a contract to hold ERC1155 tokens.
*
* IMPORTANT: When inheriting this contract, you must include a way to use the received tokens, otherwise they will be
* stuck.
*
* @dev _Available since v3.1._
*/
contract ERC1155Holder is ERC1155Receiver {
function onERC1155Received(
address,
address,
uint256,
uint256,
bytes memory
) public virtual override returns (bytes4) {
return this.onERC1155Received.selector;
}
function onERC1155BatchReceived(
address,
address,
uint256[] memory,
uint256[] memory,
bytes memory
) public virtual override returns (bytes4) {
return this.onERC1155BatchReceived.selector;
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (token/ERC1155/utils/ERC1155Receiver.sol)
pragma solidity ^0.8.0;
import "../IERC1155Receiver.sol";
import "../../../utils/introspection/ERC165.sol";
/**
* @dev _Available since v3.1._
*/
abstract contract ERC1155Receiver is ERC165, IERC1155Receiver {
/**
* @dev See {IERC165-supportsInterface}.
*/
function supportsInterface(bytes4 interfaceId) public view virtual override(ERC165, IERC165) returns (bool) {
return interfaceId == type(IERC1155Receiver).interfaceId || super.supportsInterface(interfaceId);
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.6.0) (token/ERC721/IERC721Receiver.sol)
pragma solidity ^0.8.0;
/**
* @title ERC721 token receiver interface
* @dev Interface for any contract that wants to support safeTransfers
* from ERC721 asset contracts.
*/
interface IERC721Receiver {
/**
* @dev Whenever an {IERC721} `tokenId` token is transferred to this contract via {IERC721-safeTransferFrom}
* by `operator` from `from`, this function is called.
*
* It must return its Solidity selector to confirm the token transfer.
* If any other value is returned or the interface is not implemented by the recipient, the transfer will be reverted.
*
* The selector can be obtained in Solidity with `IERC721Receiver.onERC721Received.selector`.
*/
function onERC721Received(
address operator,
address from,
uint256 tokenId,
bytes calldata data
) external returns (bytes4);
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (token/ERC721/utils/ERC721Holder.sol)
pragma solidity ^0.8.0;
import "../IERC721Receiver.sol";
/**
* @dev Implementation of the {IERC721Receiver} interface.
*
* Accepts all token transfers.
* Make sure the contract is able to use its token with {IERC721-safeTransferFrom}, {IERC721-approve} or {IERC721-setApprovalForAll}.
*/
contract ERC721Holder is IERC721Receiver {
/**
* @dev See {IERC721Receiver-onERC721Received}.
*
* Always returns `IERC721Receiver.onERC721Received.selector`.
*/
function onERC721Received(
address,
address,
uint256,
bytes memory
) public virtual override returns (bytes4) {
return this.onERC721Received.selector;
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.8.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
* ====
*
* [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://diligence.consensys.net/posts/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.5.11/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.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: MIT
// OpenZeppelin Contracts v4.4.1 (utils/introspection/ERC165.sol)
pragma solidity ^0.8.0;
import "./IERC165.sol";
/**
* @dev Implementation of the {IERC165} interface.
*
* Contracts that want to implement ERC165 should inherit from this contract and override {supportsInterface} to check
* for the additional interface id that will be supported. For example:
*
* ```solidity
* function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
* return interfaceId == type(MyInterface).interfaceId || super.supportsInterface(interfaceId);
* }
* ```
*
* Alternatively, {ERC165Storage} provides an easier to use but more expensive implementation.
*/
abstract contract ERC165 is IERC165 {
/**
* @dev See {IERC165-supportsInterface}.
*/
function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
return interfaceId == type(IERC165).interfaceId;
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (utils/introspection/IERC165.sol)
pragma solidity ^0.8.0;
/**
* @dev Interface of the ERC165 standard, as defined in the
* https://eips.ethereum.org/EIPS/eip-165[EIP].
*
* Implementers can declare support of contract interfaces, which can then be
* queried by others ({ERC165Checker}).
*
* For an implementation, see {ERC165}.
*/
interface IERC165 {
/**
* @dev Returns true if this contract implements the interface defined by
* `interfaceId`. See the corresponding
* https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[EIP section]
* to learn more about how these ids are created.
*
* This function call must use less than 30 000 gas.
*/
function supportsInterface(bytes4 interfaceId) external view returns (bool);
}
// 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);
///////////////////////////////////////////////
// 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 * 8) < value ? 1 : 0);
}
}
}
// 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;
/// @title IAvocado
/// @notice interface to access internal vars on-chain
interface IAvocado {
function _avoImpl() external view returns (address);
function _data() external view returns (uint256);
function _owner() external view returns (address);
}
/// @title Avocado
/// @notice Proxy for Avocados as deployed by the AvoFactory.
/// Basic Proxy with fallback to delegate and address for implementation contract at storage 0x0
//
// @dev If this contract changes then the deployment addresses for new Avocados through factory change too!!
// Relayers might want to pass in version as new param then to forward to the correct factory
contract Avocado {
/// @notice flexible immutable data slot.
/// first 20 bytes: address owner
/// next 4 bytes: uint32 index
/// next 1 byte: uint8 type
/// next 9 bytes: used flexible for use-cases found in the future
uint256 internal immutable _data;
/// @notice address of the Avocado logic / implementation contract. IMPORTANT: SAME STORAGE SLOT AS FOR PROXY
//
// @dev _avoImpl MUST ALWAYS be the first declared variable here in the proxy and in the logic contract
// when upgrading, the storage at memory address 0x0 is upgraded (first slot).
// To reduce deployment costs this variable is internal but can still be retrieved with
// _avoImpl(), see code and comments in fallback below
address internal _avoImpl;
/// @notice sets _avoImpl & immutable _data, fetching it from msg.sender.
//
// @dev those values are not input params to not influence the deterministic Create2 address!
constructor() {
// "\x8c\x65\x73\x89" is hardcoded bytes of function selector for transientDeployData()
(, bytes memory deployData_) = msg.sender.staticcall(bytes("\x8c\x65\x73\x89"));
address impl_;
uint256 data_;
assembly {
// cast first 20 bytes to version address (_avoImpl)
impl_ := mload(add(deployData_, 0x20))
// cast bytes in position 0x40 to uint256 data; deployData_ plus 0x40 due to padding
data_ := mload(add(deployData_, 0x40))
}
_data = data_;
_avoImpl = impl_;
}
/// @notice Delegates the current call to `_avoImpl` unless one of the view methods is called:
/// `_avoImpl()` returns the address for `_avoImpl`, `_owner()` returns the first
/// 20 bytes of `_data`, `_data()` returns `_data`.
//
// @dev Mostly based on OpenZeppelin Proxy.sol
// logic contract must not implement a function `_avoImpl()`, `_owner()` or `_data()`
// as they will not be callable due to collision
fallback() external payable {
uint256 data_ = _data;
assembly {
let functionSelector_ := calldataload(0)
// 0xb2bdfa7b = function selector for _owner()
if eq(functionSelector_, 0xb2bdfa7b00000000000000000000000000000000000000000000000000000000) {
// store address owner at memory address 0x0, loading only last 20 bytes through the & mask
mstore(0, and(data_, 0x000000000000000000000000ffffffffffffffffffffffffffffffffffffffff))
return(0, 0x20) // send 32 bytes of memory slot 0 as return value
}
// 0x68beab3f = function selector for _data()
if eq(functionSelector_, 0x68beab3f00000000000000000000000000000000000000000000000000000000) {
mstore(0, data_) // store uint256 _data at memory address 0x0
return(0, 0x20) // send 32 bytes of memory slot 0 as return value
}
// load address avoImpl_ from storage
let avoImpl_ := and(sload(0), 0xffffffffffffffffffffffffffffffffffffffff)
// first 4 bytes of calldata specify which function to call.
// if those first 4 bytes == 874095c6 (function selector for _avoImpl()) then we return the _avoImpl address
// The value is right padded to 32-bytes with 0s
if eq(functionSelector_, 0x874095c600000000000000000000000000000000000000000000000000000000) {
mstore(0, avoImpl_) // store address avoImpl_ at memory address 0x0
return(0, 0x20) // send 32 bytes of memory slot 0 as return value
}
// @dev code below is taken from OpenZeppelin Proxy.sol _delegate function
// Copy msg.data. We take full control of memory in this inline assembly
// block because it will not return to Solidity code. We overwrite the
// Solidity scratch pad at memory position 0.
calldatacopy(0, 0, calldatasize())
// Call the implementation.
// out and outsize are 0 because we don't know the size yet.
let result := delegatecall(gas(), avoImpl_, 0, calldatasize(), 0, 0)
// Copy the returned data.
returndatacopy(0, 0, returndatasize())
switch result
// delegatecall returns 0 on error.
case 0 {
revert(0, returndatasize())
}
default {
return(0, returndatasize())
}
}
}
}
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.18;
import { ECDSA } from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
import { Address } from "@openzeppelin/contracts/utils/Address.sol";
import { IERC1271 } from "@openzeppelin/contracts/interfaces/IERC1271.sol";
import { IAvoRegistry } from "../interfaces/IAvoRegistry.sol";
import { IAvoSignersList } from "../interfaces/IAvoSignersList.sol";
import { IAvocadoMultisigV1Base } from "../interfaces/IAvocadoMultisigV1.sol";
import { IAvocadoMultisigV1Secondary, IAvocadoMultisigV1SecondaryConstants } from "../interfaces/IAvocadoMultisigV1Secondary.sol";
import { IAvocado } from "../Avocado.sol";
import { IAvoConfigV1 } from "../interfaces/IAvoConfigV1.sol";
import { AvocadoMultisigBase, AvocadoMultisigCore } from "./AvocadoMultisigCore.sol";
// --------------------------- DEVELOPER NOTES -----------------------------------------
// @dev IMPORTANT: all storage variables go into AvocadoMultisigVariables.sol
// -------------------------------------------------------------------------------------
// empty interface used for Natspec docs for nice layout in automatically generated docs:
//
/// @title AvocadoMultisig v1.1.0
/// @notice Smart wallet enabling meta transactions through multiple EIP712 signatures (Multisig n out of m).
///
/// Supports:
/// - Executing arbitrary actions
/// - Receiving NFTs (ERC721)
/// - Receiving ERC1155 tokens
/// - ERC1271 smart contract signatures
/// - Instadapp Flashloan callbacks
/// - chain-agnostic signatures, user can sign once for execution of actions on different chains.
///
/// The `cast` method allows the AvoForwarder (relayer) to execute multiple arbitrary actions authorized by signature.
///
/// Broadcasters are expected to call the AvoForwarder contract `execute()` method, which also automatically
/// deploys an AvocadoMultisig if necessary first.
///
/// Upgradeable by calling `upgradeTo` through a `cast` / `castAuthorized` call.
///
/// The `castAuthorized` method allows the signers of the wallet to execute multiple arbitrary actions with signatures
/// without the AvoForwarder in between, to guarantee the smart wallet is truly non-custodial.
///
/// _@dev Notes:_
/// - This contract implements parts of EIP-2770 in a minimized form. E.g. domainSeparator is immutable etc.
/// - This contract does not implement ERC2771, because trusting an upgradeable "forwarder" bears a security
/// risk for this non-custodial wallet.
/// - Signature related logic is based off of OpenZeppelin EIP712Upgradeable.
/// - All signatures are validated for defaultChainId of `634` instead of `block.chainid` from opcode (EIP-1344).
/// - For replay protection, the current `block.chainid` instead is used in the EIP-712 salt.
interface AvocadoMultisig_V1 {}
/// @dev Simple contract to upgrade the implementation address stored at storage slot 0x0.
/// Mostly based on OpenZeppelin ERC1967Upgrade contract, adapted with onlySelf etc.
/// IMPORTANT: For any new implementation, the upgrade method MUST be in the implementation itself,
/// otherwise it can not be upgraded anymore!
abstract contract AvocadoMultisigSelfUpgradeable is AvocadoMultisigCore {
/// @notice upgrade the contract to a new implementation address.
/// - Must be a valid version at the AvoRegistry.
/// - Can only be self-called (authorization same as for `cast` methods).
/// @param avoImplementation_ New contract address
/// @param afterUpgradeHookData_ flexible bytes for custom usage in after upgrade hook logic
//
// Implementation must call `_afterUpgradeHook()`
function upgradeTo(address avoImplementation_, bytes calldata afterUpgradeHookData_) public onlySelf {
_spell(address(avoSecondary), msg.data);
}
/// @notice hook called after executing an upgrade from previous `fromImplementation_`, with flexible bytes `data_`
function _afterUpgradeHook(address fromImplementation_, bytes calldata data_) public virtual onlySelf {}
}
abstract contract AvocadoMultisigProtected is AvocadoMultisigCore {
/***********************************|
| ONLY SELF |
|__________________________________*/
/// @notice occupies the sequential `avoNonces_` in storage. This can be used to cancel / invalidate
/// a previously signed request(s) because the nonce will be "used" up.
/// - Can only be self-called (authorization same as for `cast` methods).
/// @param avoNonces_ sequential ascending ordered nonces to be occupied in storage.
/// E.g. if current AvoNonce is 77 and txs are queued with avoNonces 77, 78 and 79,
/// then you would submit [78, 79] here because 77 will be occupied by the tx executing
/// `occupyAvoNonces()` as an action itself. If executing via non-sequential nonces, you would
/// submit [77, 78, 79].
/// - Maximum array length is 5.
/// - gap from the current avoNonce will revert (e.g. [79, 80] if current one is 77)
function occupyAvoNonces(uint88[] calldata avoNonces_) external onlySelf {
_spell(address(avoSecondary), msg.data);
}
/// @notice occupies the `nonSequentialNonces_` in storage. This can be used to cancel / invalidate
/// previously signed request(s) because the nonce will be "used" up.
/// - Can only be self-called (authorization same as for `cast` methods).
/// @param nonSequentialNonces_ the non-sequential nonces to occupy
function occupyNonSequentialNonces(bytes32[] calldata nonSequentialNonces_) external onlySelf {
_spell(address(avoSecondary), msg.data);
}
/***********************************|
| FLASHLOAN CALLBACK |
|__________________________________*/
/// @dev callback used by Instadapp Flashloan Aggregator, executes operations while owning
/// the flashloaned amounts. `data_` must contain actions, one of them must pay back flashloan
// /// @param assets_ assets_ received a flashloan for
// /// @param amounts_ flashloaned amounts for each asset
// /// @param premiums_ fees to pay for the flashloan
/// @param initiator_ flashloan initiator -> must be this contract
/// @param data_ data bytes containing the `abi.encoded()` actions that are executed like in `CastParams.actions`
function executeOperation(
address[] calldata /* assets_ */,
uint256[] calldata /* amounts_ */,
uint256[] calldata /* premiums_ */,
address initiator_,
bytes calldata data_
) external returns (bool) {
// @dev using the valid case inverted via one ! to optimize gas usage
// data_ includes id and actions
if (
!(_transientAllowHash ==
bytes31(keccak256(abi.encode(data_, block.timestamp, EXECUTE_OPERATION_SELECTOR))) &&
initiator_ == address(this))
) {
revert AvocadoMultisig__Unauthorized();
}
// get and reset transient id
uint256 id_ = uint256(_transientId);
_transientId = 0;
if (tx.origin == 0x000000000000000000000000000000000000dEaD) {
// tx origin 0x000000000000000000000000000000000000dEaD used for backend gas estimations -> forward to simulate
_spell(
address(avoSecondary),
abi.encodeCall(avoSecondary._simulateExecuteActions, (abi.decode(data_, (Action[])), id_, true))
);
} else {
// decode actions to be executed after getting the flashloan and id_ packed into the data_
_executeActions(abi.decode(data_, (Action[])), id_, true);
}
return true;
}
/***********************************|
| INDIRECT INTERNAL |
|__________________________________*/
/// @dev executes a low-level .call or .delegateCall on all `actions_`.
/// Can only be self-called by this contract under certain conditions, essentially internal method.
/// This is called like an external call to create a separate execution frame.
/// This way we can revert all the `actions_` if one fails without reverting the whole transaction.
/// @param actions_ the actions to execute (target, data, value, operation)
/// @param id_ id for `actions_`, see `CastParams.id`
function _callTargets(Action[] calldata actions_, uint256 id_) external payable {
if (tx.origin == 0x000000000000000000000000000000000000dEaD) {
// tx origin 0x000000000000000000000000000000000000dEaD used for backend gas estimations -> forward to simulate
_spell(address(avoSecondary), abi.encodeCall(avoSecondary._simulateExecuteActions, (actions_, id_, false)));
} else {
// _transientAllowHash must be set
if (
(_transientAllowHash !=
bytes31(keccak256(abi.encode(actions_, id_, block.timestamp, _CALL_TARGETS_SELECTOR))))
) {
revert AvocadoMultisig__Unauthorized();
}
_executeActions(actions_, id_, false);
}
}
}
abstract contract AvocadoMultisigEIP1271 is AvocadoMultisigCore {
/// @inheritdoc IERC1271
/// @param signature This can be one of the following:
/// - empty: `hash` must be a previously signed message in storage then.
/// - 65 bytes: owner signature for a Multisig with only owner as signer (requiredSigners = 1, signers=[owner]).
/// - a multiple of 85 bytes, through grouping of 65 bytes signature + 20 bytes signer address each.
/// To signal decoding this way, the signature bytes must be prefixed with `0xDEC0DE6520`.
/// - the `abi.encode` result for `SignatureParams` struct array.
/// @dev reverts with `AvocadoMultisig__InvalidEIP1271Signature` or `AvocadoMultisig__InvalidParams` if signature is invalid.
/// @dev input `message_` is hashed with `domainSeparatorV4()` according to EIP712 typed data (`EIP1271_TYPE_HASH`)
function isValidSignature(
bytes32 hash,
bytes calldata signature
) external view override returns (bytes4 magicValue) {
// hashing with domain separator mitigates any potential replaying on other networks or other Avocados of the same owner
hash = ECDSA.toTypedDataHash(
_domainSeparatorV4(
DOMAIN_SEPARATOR_SALT_HASHED // includes block.chainid
),
keccak256(abi.encode(EIP1271_TYPE_HASH, hash))
);
// @dev function params without _ for inheritdoc
if (signature.length == 0) {
// must be pre-allow-listed via `signMessage` method
if (_signedMessages[hash] != 1) {
revert AvocadoMultisig__InvalidEIP1271Signature();
}
} else {
(bool validSignature_, ) = _verifySig(
hash,
// decode signaturesParams_ from bytes signature
avoSecondary.decodeEIP1271Signature(signature, IAvocado(address(this))._owner()),
// we have no way to know nonce type, so make sure validity test covers everything.
// setting this flag true will check that the digest is not a used non-sequential nonce.
// unfortunately, for sequential nonces it adds unneeded verification and gas cost,
// because the check will always pass, but there is no way around it.
true
);
if (!validSignature_) {
revert AvocadoMultisig__InvalidEIP1271Signature();
}
}
return EIP1271_MAGIC_VALUE;
}
/// @notice Marks a bytes32 `message_` (signature digest) as signed, making it verifiable by EIP-1271 `isValidSignature()`.
/// - Can only be self-called (authorization same as for `cast` methods).
/// @param message_ data hash to be allow-listed as signed
/// @dev input `message_` is hashed with `domainSeparatorV4()` according to EIP712 typed data (`EIP1271_TYPE_HASH`)
function signMessage(bytes32 message_) external onlySelf {
_spell(address(avoSecondary), msg.data);
}
/// @notice Removes a previously `signMessage()` signed bytes32 `message_` (signature digest).
/// - Can only be self-called (authorization same as for `cast` methods).
/// @param message_ data hash to be removed from allow-listed signatures
function removeSignedMessage(bytes32 message_) external onlySelf {
_spell(address(avoSecondary), msg.data);
}
}
abstract contract AvocadoMultisigSigners is AvocadoMultisigCore {
/// @notice adds `addSigners_` to allowed signers and sets required signers count to `requiredSigners_`
/// Note the `addSigners_` to be added must:
/// - NOT be duplicates (already present in current allowed signers)
/// - NOT be the zero address
/// - be sorted ascending
function addSigners(address[] calldata addSigners_, uint8 requiredSigners_) external onlySelf {
_spell(address(avoSecondary), msg.data);
}
/// @notice removes `removeSigners_` from allowed signers and sets required signers count to `requiredSigners_`
/// Note the `removeSigners_` to be removed must:
/// - NOT be the owner
/// - be sorted ascending
/// - be present in current allowed signers
function removeSigners(address[] calldata removeSigners_, uint8 requiredSigners_) external onlySelf {
_spell(address(avoSecondary), msg.data);
}
/// @notice sets number of required signers for a valid request to `requiredSigners_`
function setRequiredSigners(uint8 requiredSigners_) external onlySelf {
_spell(address(avoSecondary), msg.data);
}
}
abstract contract AvocadoMultisigCast is AvocadoMultisigCore {
/// @inheritdoc IAvocadoMultisigV1Base
function getSigDigest(
CastParams memory params_,
CastForwardParams memory forwardParams_
) public view returns (bytes32) {
return _getSigDigest(params_, forwardParams_);
}
/// @inheritdoc IAvocadoMultisigV1Base
function verify(
CastParams calldata params_,
CastForwardParams calldata forwardParams_,
SignatureParams[] calldata signaturesParams_
) external view returns (bool) {
_validateParams(
params_.actions.length,
params_.avoNonce,
forwardParams_.validAfter,
forwardParams_.validUntil,
forwardParams_.value
);
_verifySigWithRevert(_getSigDigest(params_, forwardParams_), signaturesParams_, params_.avoNonce == -1);
return true;
}
/// @inheritdoc IAvocadoMultisigV1Base
function cast(
CastParams calldata params_,
CastForwardParams calldata forwardParams_,
SignatureParams[] memory signaturesParams_
) external payable returns (bool success_, string memory revertReason_) {
return _cast(params_, forwardParams_, signaturesParams_, new ChainAgnosticHash[](0));
}
/// @inheritdoc IAvocadoMultisigV1Base
function simulateCast(
CastParams calldata params_,
CastForwardParams calldata forwardParams_,
SignatureParams[] memory signaturesParams_
) external payable returns (bool success_, string memory revertReason_) {
return _simulateCast(params_, forwardParams_, signaturesParams_, new ChainAgnosticHash[](0), false);
}
/// @inheritdoc IAvocadoMultisigV1Base
function estimateCast(
CastParams calldata params_,
CastForwardParams calldata forwardParams_,
SignatureParams[] memory signaturesParams_
) external payable returns (bool success_, string memory revertReason_) {
return _simulateCast(params_, forwardParams_, signaturesParams_, new ChainAgnosticHash[](0), true);
}
}
abstract contract AvocadoMultisigCastChainAgnostic is AvocadoMultisigCore {
/// @inheritdoc IAvocadoMultisigV1Base
function castChainAgnostic(
CastChainAgnosticParams calldata params_,
SignatureParams[] memory signaturesParams_,
ChainAgnosticHash[] calldata chainAgnosticHashes_
) external payable returns (bool success_, string memory revertReason_) {
if (params_.chainId != block.chainid) {
revert AvocadoMultisig__InvalidParams();
}
return _cast(params_.params, params_.forwardParams, signaturesParams_, chainAgnosticHashes_);
}
/// @inheritdoc IAvocadoMultisigV1Base
function getChainAgnosticHashes(
CastChainAgnosticParams[] calldata params_
) public pure returns (ChainAgnosticHash[] memory chainAgnosticHashes_) {
uint256 length_ = params_.length;
if (length_ < 2) {
revert AvocadoMultisig__InvalidParams();
}
chainAgnosticHashes_ = new ChainAgnosticHash[](length_);
for (uint256 i; i < length_; ) {
chainAgnosticHashes_[i] = ChainAgnosticHash(
_castChainAgnosticParamsHash(params_[i].params, params_[i].forwardParams, params_[i].chainId),
params_[i].chainId
);
unchecked {
++i;
}
}
}
/// @inheritdoc IAvocadoMultisigV1Base
function getSigDigestChainAgnostic(CastChainAgnosticParams[] calldata params_) public view returns (bytes32) {
return _getSigDigestChainAgnostic(getChainAgnosticHashes(params_));
}
/// @inheritdoc IAvocadoMultisigV1Base
function getSigDigestChainAgnosticFromHashes(
ChainAgnosticHash[] calldata chainAgnosticHashes_
) public view returns (bytes32) {
return _getSigDigestChainAgnostic(chainAgnosticHashes_);
}
/// @inheritdoc IAvocadoMultisigV1Base
function verifyChainAgnostic(
CastChainAgnosticParams calldata params_,
SignatureParams[] calldata signaturesParams_,
ChainAgnosticHash[] calldata chainAgnosticHashes_
) public view returns (bool) {
if (params_.chainId != block.chainid) {
revert AvocadoMultisig__InvalidParams();
}
_validateParams(
params_.params.actions.length,
params_.params.avoNonce,
params_.forwardParams.validAfter,
params_.forwardParams.validUntil,
params_.forwardParams.value
);
_validateChainAgnostic(
_castChainAgnosticParamsHash(params_.params, params_.forwardParams, block.chainid),
chainAgnosticHashes_
);
_verifySigWithRevert(
_getSigDigestChainAgnostic(chainAgnosticHashes_),
signaturesParams_,
params_.params.avoNonce == -1
);
return true;
}
/// @inheritdoc IAvocadoMultisigV1Base
function simulateCastChainAgnostic(
CastChainAgnosticParams calldata params_,
SignatureParams[] memory signaturesParams_,
ChainAgnosticHash[] calldata chainAgnosticHashes_
) external payable returns (bool success_, string memory revertReason_) {
if (params_.chainId != block.chainid) {
revert AvocadoMultisig__InvalidParams();
}
return _simulateCast(params_.params, params_.forwardParams, signaturesParams_, chainAgnosticHashes_, false);
}
/// @inheritdoc IAvocadoMultisigV1Base
function estimateCastChainAgnostic(
CastChainAgnosticParams calldata params_,
SignatureParams[] memory signaturesParams_,
ChainAgnosticHash[] calldata chainAgnosticHashes_
) external payable returns (bool success_, string memory revertReason_) {
if (params_.chainId != block.chainid) {
revert AvocadoMultisig__InvalidParams();
}
return _simulateCast(params_.params, params_.forwardParams, signaturesParams_, chainAgnosticHashes_, true);
}
}
abstract contract AvocadoMultisigCastAuthorized is AvocadoMultisigCore {
/// @inheritdoc IAvocadoMultisigV1Base
function getSigDigestAuthorized(
CastParams memory params_,
CastAuthorizedParams memory authorizedParams_
) public view returns (bytes32) {
return _getSigDigestAuthorized(params_, authorizedParams_);
}
/// @inheritdoc IAvocadoMultisigV1Base
function verifyAuthorized(
CastParams calldata params_,
CastAuthorizedParams calldata authorizedParams_,
SignatureParams[] calldata signaturesParams_
) external view returns (bool) {
// make sure actions are defined and nonce is valid
_validateParams(
params_.actions.length,
params_.avoNonce,
authorizedParams_.validAfter,
authorizedParams_.validUntil,
0 // no value param in authorized interaction
);
_verifySigWithRevert(
_getSigDigestAuthorized(params_, authorizedParams_),
signaturesParams_,
params_.avoNonce == -1
);
return true;
}
/// @inheritdoc IAvocadoMultisigV1Base
function castAuthorized(
CastParams calldata params_,
CastAuthorizedParams calldata authorizedParams_,
SignatureParams[] memory signaturesParams_
) external payable returns (bool success_, string memory revertReason_) {
uint256 gasSnapshot_ = gasleft();
// make sure actions are defined and nonce is valid
_validateParams(
params_.actions.length,
params_.avoNonce,
authorizedParams_.validAfter,
authorizedParams_.validUntil,
0 // no value param in authorized interaction
);
bytes32 digest_ = _getSigDigestAuthorized(params_, authorizedParams_);
address[] memory signers_ = _verifySigWithRevert(digest_, signaturesParams_, params_.avoNonce == -1);
(success_, revertReason_) = _executeCast(
params_,
_dynamicReserveGas(CAST_AUTHORIZED_RESERVE_GAS, signers_.length, params_.metadata.length),
params_.avoNonce == -1 ? digest_ : bytes32(0)
);
// @dev on changes in the code below this point, measure the needed reserve gas via `gasleft()` anew
// and update reserve gas constant amounts
if (success_) {
emit CastExecuted(params_.source, msg.sender, signers_, params_.metadata);
} else {
emit CastFailed(params_.source, msg.sender, signers_, revertReason_, params_.metadata);
}
// @dev `_payAuthorizedFee()` costs ~24.5k gas for if a fee is configured and maxFee is set
_spell(
address(avoSecondary),
abi.encodeCall(avoSecondary.payAuthorizedFee, (gasSnapshot_, authorizedParams_.maxFee))
);
// @dev ending point for measuring reserve gas should be here. Also see comment in `AvocadoMultisigCore._executeCast()`
}
}
contract AvocadoMultisig is
AvocadoMultisigBase,
AvocadoMultisigCore,
AvocadoMultisigSelfUpgradeable,
AvocadoMultisigProtected,
AvocadoMultisigEIP1271,
AvocadoMultisigSigners,
AvocadoMultisigCast,
AvocadoMultisigCastAuthorized,
AvocadoMultisigCastChainAgnostic
{
/***********************************|
| CONSTRUCTOR / INITIALIZERS |
|__________________________________*/
/// @notice constructor sets multiple immutable values for contracts and payFee fallback logic.
/// @param avoRegistry_ address of the avoRegistry (proxy) contract
/// @param avoForwarder_ address of the avoForwarder (proxy) contract
/// to forward tx with valid signatures. must be valid version in AvoRegistry.
/// @param avoSignersList_ address of the AvoSignersList (proxy) contract
/// @param avoConfigV1_ AvoConfigV1 contract holding values for authorizedFee values
/// @param secondary_ AvocadoMultisigSecondary contract for extended logic
constructor(
IAvoRegistry avoRegistry_,
address avoForwarder_,
IAvoSignersList avoSignersList_,
IAvoConfigV1 avoConfigV1_,
IAvocadoMultisigV1Secondary secondary_
) AvocadoMultisigBase(avoRegistry_, avoForwarder_, avoSignersList_, avoConfigV1_) AvocadoMultisigCore(secondary_) {
// sanity checks to ensure all immutables are configured with same values on AvoSecondary
if (
address(IAvocadoMultisigV1SecondaryConstants(address(secondary_)).avoRegistry()) != address(avoRegistry_) ||
IAvocadoMultisigV1SecondaryConstants(address(secondary_)).avoForwarder() != avoForwarder_ ||
address(IAvocadoMultisigV1SecondaryConstants(address(secondary_)).avoSignersList()) !=
address(avoSignersList_) ||
IAvocadoMultisigV1SecondaryConstants(address(secondary_)).AUTHORIZED_MIN_FEE() != AUTHORIZED_MIN_FEE ||
IAvocadoMultisigV1SecondaryConstants(address(secondary_)).AUTHORIZED_MAX_FEE() != AUTHORIZED_MAX_FEE ||
IAvocadoMultisigV1SecondaryConstants(address(secondary_)).AUTHORIZED_FEE_COLLECTOR() !=
AUTHORIZED_FEE_COLLECTOR
) {
revert AvocadoMultisig__InvalidParams();
}
}
/// @inheritdoc IAvocadoMultisigV1Base
function initialize() public initializer {
_spell(address(avoSecondary), msg.data);
}
/***********************************|
| PUBLIC API |
|__________________________________*/
receive() external payable {}
/// @inheritdoc IAvocadoMultisigV1Base
function domainSeparatorV4() public view returns (bytes32) {
return
_domainSeparatorV4(
DOMAIN_SEPARATOR_SALT_HASHED // includes block.chainid
);
}
/// @inheritdoc IAvocadoMultisigV1Base
function domainSeparatorV4ChainAgnostic() public view returns (bytes32) {
return
_domainSeparatorV4(
DOMAIN_SEPARATOR_CHAIN_AGNOSTIC_SALT_HASHED // includes default chain id (634)
);
}
/// @inheritdoc IAvocadoMultisigV1Base
function isSigner(address signer_) public view returns (bool) {
address[] memory allowedSigners_ = _getSigners(); // includes owner
uint256 allowedSignersLength_ = allowedSigners_.length;
for (uint256 i; i < allowedSignersLength_; ) {
if (allowedSigners_[i] == signer_) {
return true;
}
unchecked {
++i;
}
}
return false;
}
/// @inheritdoc IAvocadoMultisigV1Base
function signers() public view returns (address[] memory signers_) {
return _getSigners();
}
/// @inheritdoc IAvocadoMultisigV1Base
function requiredSigners() public view returns (uint8) {
return _getRequiredSigners();
}
/// @inheritdoc IAvocadoMultisigV1Base
function signersCount() public view returns (uint8) {
return _getSignersCount();
}
/// @inheritdoc IAvocadoMultisigV1Base
function owner() public view returns (address) {
return IAvocado(address(this))._owner();
}
/// @inheritdoc IAvocadoMultisigV1Base
function index() public view returns (uint32) {
return uint32(IAvocado(address(this))._data() >> 160);
}
/// @notice incrementing nonce for each valid tx executed (to ensure uniqueness)
function avoNonce() public view returns (uint256) {
return uint256(_avoNonce);
}
}
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.18;
import { ECDSA } from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
import { Address } from "@openzeppelin/contracts/utils/Address.sol";
import { Strings } from "@openzeppelin/contracts/utils/Strings.sol";
import { ERC721Holder } from "@openzeppelin/contracts/token/ERC721/utils/ERC721Holder.sol";
import { IERC1271 } from "@openzeppelin/contracts/interfaces/IERC1271.sol";
import { ERC1155Holder } from "@openzeppelin/contracts/token/ERC1155/utils/ERC1155Holder.sol";
import { InstaFlashReceiverInterface } from "../external/InstaFlashReceiverInterface.sol";
import { IAvoRegistry } from "../interfaces/IAvoRegistry.sol";
import { IAvoSignersList } from "../interfaces/IAvoSignersList.sol";
import { IAvocadoMultisigV1Base } from "../interfaces/IAvocadoMultisigV1.sol";
import { IAvocadoMultisigV1Secondary } from "../interfaces/IAvocadoMultisigV1Secondary.sol";
import { IAvoConfigV1 } from "../interfaces/IAvoConfigV1.sol";
import { IAvocado } from "../Avocado.sol";
import { AvocadoMultisigErrors } from "./AvocadoMultisigErrors.sol";
import { AvocadoMultisigEvents } from "./AvocadoMultisigEvents.sol";
import { AvocadoMultisigVariables } from "./AvocadoMultisigVariables.sol";
import { AvocadoMultisigInitializable } from "./lib/AvocadoMultisigInitializable.sol";
import { AvocadoMultisigStructs } from "./AvocadoMultisigStructs.sol";
import { AvocadoMultisigProtected } from "./AvocadoMultisig.sol";
/// @dev AvocadoMultisigBase contains all internal helper and base state needed for AvocadoMultisig main AND
/// secondary contract logic.
abstract contract AvocadoMultisigBase is
AvocadoMultisigErrors,
AvocadoMultisigEvents,
AvocadoMultisigVariables,
AvocadoMultisigStructs,
AvocadoMultisigInitializable
{
/***********************************|
| CONSTRUCTOR / INITIALIZERS |
|__________________________________*/
constructor(
IAvoRegistry avoRegistry_,
address avoForwarder_,
IAvoSignersList avoSignersList_,
IAvoConfigV1 avoConfigV1_
) AvocadoMultisigVariables(avoRegistry_, avoForwarder_, avoSignersList_, avoConfigV1_) {
// Ensure logic contract initializer is not abused by disabling initializing
// see https://forum.openzeppelin.com/t/security-advisory-initialize-uups-implementation-contracts/15301
// and https://docs.openzeppelin.com/upgrades-plugins/1.x/writing-upgradeable#initializing_the_implementation_contract
_disableInitializers();
}
/***********************************|
| INTERNAL |
|__________________________________*/
/// @dev returns the dynamic reserve gas to be kept back for emitting the CastExecuted or CastFailed event
function _dynamicReserveGas(
uint256 fixedReserveGas_,
uint256 signersCount_,
uint256 metadataLength_
) internal pure returns (uint256 reserveGas_) {
unchecked {
// the gas usage for the emitting the CastExecuted/CastFailed events depends on the signers count
// the cost per signer is PER_SIGNER_RESERVE_GAS. We calculate this dynamically to ensure
// enough reserve gas is reserved in Multisigs with a higher signersCount.
// same for metadata bytes length, dynamically calculated with cost per byte for emit event
reserveGas_ =
fixedReserveGas_ +
(PER_SIGNER_RESERVE_GAS * signersCount_) +
(EMIT_EVENT_COST_PER_BYTE * metadataLength_);
}
}
/// @dev Returns the domain separator for the chain with id `DEFAULT_CHAIN_ID` and `salt_`
function _domainSeparatorV4(bytes32 salt_) internal view returns (bytes32) {
return
keccak256(
abi.encode(
TYPE_HASH,
DOMAIN_SEPARATOR_NAME_HASHED,
DOMAIN_SEPARATOR_VERSION_HASHED,
DEFAULT_CHAIN_ID,
address(this),
salt_
)
);
}
/// @dev returns the EIP712 `CAST_PARAMS_TYPE_HASH` hash for `params_`.
function _castParamsHash(CastParams memory params_) internal pure returns (bytes32) {
// get keccak256s for actions
uint256 actionsLength_ = params_.actions.length;
bytes memory actionsAbiEncodePacked_;
for (uint256 i; i < actionsLength_; ) {
actionsAbiEncodePacked_ = abi.encodePacked(
actionsAbiEncodePacked_,
keccak256(
abi.encode(
ACTION_TYPE_HASH,
params_.actions[i].target,
keccak256(params_.actions[i].data),
params_.actions[i].value,
params_.actions[i].operation
)
)
);
unchecked {
++i;
}
}
return
keccak256(
abi.encode(
CAST_PARAMS_TYPE_HASH,
// actions[]
keccak256(actionsAbiEncodePacked_),
params_.id,
params_.avoNonce,
params_.salt,
params_.source,
keccak256(params_.metadata)
)
);
}
/// @dev returns the EIP712 `CAST_FORWARD_PARAMS_TYPE_HASH` hash for `forwardParams_`.
function _castForwardParamsHash(CastForwardParams memory forwardParams_) internal pure returns (bytes32) {
return
keccak256(
abi.encode(
CAST_FORWARD_PARAMS_TYPE_HASH,
forwardParams_.gas,
forwardParams_.gasPrice,
forwardParams_.validAfter,
forwardParams_.validUntil,
forwardParams_.value
)
);
}
/// @dev returns the EIP712 `CAST_CHAIN_AGNOSTIC_PARAMS_TYPE_HASH` hash for `params_`, `forwardParams_`, `chainId_`.
function _castChainAgnosticParamsHash(
CastParams memory params_,
CastForwardParams memory forwardParams_,
uint256 chainId_
) internal pure returns (bytes32) {
return
keccak256(
abi.encode(
CAST_CHAIN_AGNOSTIC_PARAMS_TYPE_HASH,
_castParamsHash(params_),
_castForwardParamsHash(forwardParams_),
chainId_
)
);
}
/// @dev gets the digest (hash) used to verify an EIP712 signature for `chainAgnosticHashes_`.
/// @param chainAgnosticHashes_ EIP712 type hashes of `CAST_CHAIN_AGNOSTIC_PARAMS_TYPE_HASH` struct for all `CastChainAgnosticParams`
/// struct array elements as used when creating the signature. Result of `getChainAgnosticHashes()`.
/// must be set in the same order as when creating the signature.
/// @return bytes32 digest e.g. for signature or non-sequential nonce
function _getSigDigestChainAgnostic(
ChainAgnosticHash[] memory chainAgnosticHashes_
) internal view returns (bytes32) {
uint256 length_ = chainAgnosticHashes_.length;
bytes memory hashesAbiEncodePacked_;
bytes memory chainIdsAbiEncodePacked_;
for (uint256 i; i < length_; ) {
hashesAbiEncodePacked_ = abi.encodePacked(hashesAbiEncodePacked_, chainAgnosticHashes_[i].hash);
chainIdsAbiEncodePacked_ = abi.encodePacked(chainIdsAbiEncodePacked_, chainAgnosticHashes_[i].chainId);
unchecked {
++i;
}
}
return
ECDSA.toTypedDataHash(
// domain separator without chain id as salt for chain agnofstic actions (chain id is in signed params instead)
_domainSeparatorV4(
DOMAIN_SEPARATOR_CHAIN_AGNOSTIC_SALT_HASHED // includes default chain id (634)
),
// structHash according to CAST_CHAIN_AGNOSTIC_TYPE_HASH
keccak256(
abi.encode(
CAST_CHAIN_AGNOSTIC_TYPE_HASH,
// hash for castChainAgnostic() params[]
keccak256(hashesAbiEncodePacked_),
// Note: chain ids must be included in this hash here to guarantee input params for the ChainAgnosticHashes
// struct array at e.g. castChainAgnostic() are valid. Otherwise, chainIds for the non-current chain could
// be passed in wrongly there.
keccak256(chainIdsAbiEncodePacked_)
)
)
);
}
/// @dev gets the digest (hash) used to verify an EIP712 signature for `forwardParams_`
/// @param params_ Cast params such as id, avoNonce and actions to execute
/// @param forwardParams_ Cast params related to validity of forwarding as instructed and signed
/// @return bytes32 digest e.g. for signature or non-sequential nonce
function _getSigDigest(
CastParams memory params_,
CastForwardParams memory forwardParams_
) internal view returns (bytes32) {
return
ECDSA.toTypedDataHash(
// domain separator
_domainSeparatorV4(
DOMAIN_SEPARATOR_SALT_HASHED // includes block.chainid
),
// structHash according to CAST_TYPE_HASH
keccak256(abi.encode(CAST_TYPE_HASH, _castParamsHash(params_), _castForwardParamsHash(forwardParams_)))
);
}
/// @dev Verifies a EIP712 signature, returning valid status in `isValid_` or reverting
/// in case the params for the signatures / digest are wrong
/// @param digest_ the EIP712 digest for the signature
/// @param signaturesParams_ SignatureParams structs array for signature and signer:
/// - signature: the EIP712 signature, 65 bytes ECDSA signature for a default EOA.
/// For smart contract signatures it must fulfill the requirements for the relevant
/// smart contract `.isValidSignature()` EIP1271 logic
/// - signer: address of the signature signer.
/// Must match the actual signature signer or refer to the smart contract
/// that must be an allowed signer and validates signature via EIP1271
/// @param isNonSequentialNonce_ flag to signal verify with non sequential nonce or not
/// @return isValid_ true if the signature is valid, false otherwise
/// @return recoveredSigners_ recovered valid signer addresses of the signatures. In case that `isValid_` is
/// false, the last element in the array with a value is the invalid signer
function _verifySig(
bytes32 digest_,
SignatureParams[] memory signaturesParams_,
bool isNonSequentialNonce_
) internal view returns (bool isValid_, address[] memory recoveredSigners_) {
// gas measurements:
// cost until the for loop in verify signature is:
// 1 signer 3374 (_getSigners() with only owner is cheaper)
// 2 signers 6473
// every additional allowedSigner (!) + 160 gas (additional SSTORE2 load cost)
// For non-sequential nonce additional cold SLOAD + check cost is ~2200
// dynamic cost for verifying any additional signer ~6900
// So formula:
// Avoado signersCount == 1 ? -> 11_000 gas
// Avoado signersCount > 1 ? -> 6400 + allowedSignersCount * 160 + signersLength * 6900
// is non Sequential nonce? + 2200
// is smart contract signer? + buffer amount. A very basic ECDSA verify call like with e.g. MockSigner costs ~9k.
uint256 signaturesLength_ = signaturesParams_.length;
if (
// enough signatures must be submitted to reach quorom of `requiredSigners`
signaturesLength_ < _getRequiredSigners() ||
// for non sequential nonce, if nonce is already used, the signature has already been used and is invalid
(isNonSequentialNonce_ && nonSequentialNonces[digest_] == 1)
) {
revert AvocadoMultisig__InvalidParams();
}
// fill recovered signers array for use in event emit
recoveredSigners_ = new address[](signaturesLength_);
// get current signers from storage
address[] memory allowedSigners_ = _getSigners(); // includes owner
uint256 allowedSignersLength_ = allowedSigners_.length;
// track last allowed signer index for loop performance improvements
uint256 lastAllowedSignerIndex_ = 0;
bool isContract_ = false; // keeping this variable outside the loop so it is not re-initialized in each loop -> cheaper
bool isAllowedSigner_ = false;
for (uint256 i; i < signaturesLength_; ) {
if (Address.isContract(signaturesParams_[i].signer)) {
recoveredSigners_[i] = signaturesParams_[i].signer;
// set flag that the signer is a contract so we don't have to check again in code below
isContract_ = true;
} else {
// recover signer from signature
recoveredSigners_[i] = ECDSA.recover(digest_, signaturesParams_[i].signature);
if (signaturesParams_[i].signer != recoveredSigners_[i]) {
// signer does not match recovered signer. Either signer param is wrong or params used to
// build digest are not the same as for the signature
revert AvocadoMultisig__InvalidParams();
}
}
// because signers in storage and signers from signatures input params must be ordered ascending,
// the for loop can be optimized each new cycle to start from the position where the last signer
// has been found.
// this also ensures that input params signers must be ordered ascending off-chain
// (which again is used to improve performance and simplifies ensuring unique signers)
for (uint256 j = lastAllowedSignerIndex_; j < allowedSignersLength_; ) {
if (allowedSigners_[j] == recoveredSigners_[i]) {
isAllowedSigner_ = true;
unchecked {
lastAllowedSignerIndex_ = j + 1; // set to j+1 so that next cycle starts at next array position
}
break;
}
// could be optimized by checking if allowedSigners_[j] > recoveredSigners_[i]
// and immediately skipping with a `break;` if so. Because that implies that the recoveredSigners_[i]
// can not be present in allowedSigners_ due to ascending sort.
// But that would optimize the failing invalid case and increase cost for the default case where
// the input data is valid -> skip.
unchecked {
++j;
}
}
// validate if signer is allowed
if (!isAllowedSigner_) {
return (false, recoveredSigners_);
} else {
// reset `isAllowedSigner_` for next loop
isAllowedSigner_ = false;
}
if (isContract_) {
// validate as smart contract signature
if (
IERC1271(signaturesParams_[i].signer).isValidSignature(digest_, signaturesParams_[i].signature) !=
EIP1271_MAGIC_VALUE
) {
// return value is not EIP1271_MAGIC_VALUE -> smart contract returned signature is invalid
return (false, recoveredSigners_);
}
// reset isContract for next loop (because defined outside of the loop to save gas)
isContract_ = false;
}
// else already everything validated through recovered signer must be an allowed signer etc. in logic above
unchecked {
++i;
}
}
return (true, recoveredSigners_);
}
/// @dev Verifies a EIP712 signature, reverting if it is not valid.
/// @param digest_ the EIP712 digest for the signature
/// @param signaturesParams_ SignatureParams structs array for signature and signer:
/// - signature: the EIP712 signature, 65 bytes ECDSA signature for a default EOA.
/// For smart contract signatures it must fulfill the requirements for the relevant
/// smart contract `.isValidSignature()` EIP1271 logic
/// - signer: address of the signature signer.
/// Must match the actual signature signer or refer to the smart contract
/// that must be an allowed signer and validates signature via EIP1271
/// @param isNonSequentialNonce_ flag to signal verify with non sequential nonce or not
/// @return recoveredSigners_ recovered valid signer addresses of the signatures. In case that `isValid_` is
/// false, the last element in the array with a value is the invalid signer
function _verifySigWithRevert(
bytes32 digest_,
SignatureParams[] memory signaturesParams_,
bool isNonSequentialNonce_
) internal view returns (address[] memory recoveredSigners_) {
bool validSignature_;
(validSignature_, recoveredSigners_) = _verifySig(digest_, signaturesParams_, isNonSequentialNonce_);
// signature must be valid
if (!validSignature_) {
revert AvocadoMultisig__InvalidSignature();
}
}
}
/// @dev AvocadoMultisigCore contains all internal helper and base state needed for AvocadoMultisig.sol main logic
abstract contract AvocadoMultisigCore is
AvocadoMultisigBase,
ERC721Holder,
ERC1155Holder,
InstaFlashReceiverInterface,
IAvocadoMultisigV1Base,
IERC1271
{
IAvocadoMultisigV1Secondary public immutable avoSecondary;
constructor(IAvocadoMultisigV1Secondary secondary_) {
if (address(secondary_) == address(0)) {
revert AvocadoMultisig__InvalidParams();
}
avoSecondary = secondary_;
}
/// @dev ensures the method can only be called by the same contract itself.
modifier onlySelf() {
_requireSelfCalled();
_;
}
/// @dev internal method for modifier logic to reduce bytecode size of contract.
function _requireSelfCalled() internal view {
if (msg.sender != address(this)) {
revert AvocadoMultisig__Unauthorized();
}
}
/// @dev method used to trigger a delegatecall with `data_` to `target_`.
function _spell(address target_, bytes memory data_) internal returns (bytes memory response_) {
assembly {
let succeeded := delegatecall(gas(), target_, add(data_, 0x20), mload(data_), 0, 0)
let size := returndatasize()
response_ := mload(0x40)
mstore(0x40, add(response_, and(add(add(size, 0x20), 0x1f), not(0x1f))))
mstore(response_, size)
returndatacopy(add(response_, 0x20), 0, size)
switch iszero(succeeded)
case 1 {
// throw if delegatecall failed
returndatacopy(0x00, 0x00, size)
revert(0x00, size)
}
}
}
/// @dev executes multiple cast actions according to CastParams `params_`, reserving `reserveGas_` in this contract.
/// Uses a sequential nonce unless `nonSequentialNonce_` is set.
/// @return success_ boolean flag indicating whether all actions have been executed successfully.
/// @return revertReason_ if `success_` is false, then revert reason is returned as string here.
function _executeCast(
CastParams calldata params_,
uint256 reserveGas_,
bytes32 nonSequentialNonce_
) internal returns (bool success_, string memory revertReason_) {
// set allowHash to signal allowed entry into _callTargets with actions in current block only
_transientAllowHash = bytes31(
keccak256(abi.encode(params_.actions, params_.id, block.timestamp, _CALL_TARGETS_SELECTOR))
);
// nonce must be used *always* if signature is valid
if (nonSequentialNonce_ == bytes32(0)) {
// use sequential nonce, already validated in `_validateParams()`
_avoNonce++;
} else {
// use non-sequential nonce, already validated in `_verifySig()`
nonSequentialNonces[nonSequentialNonce_] = 1;
}
// execute _callTargets via a low-level call to create a separate execution frame
// this is used to revert all the actions if one action fails without reverting the whole transaction
bytes memory calldata_ = abi.encodeCall(AvocadoMultisigProtected._callTargets, (params_.actions, params_.id));
bytes memory result_;
unchecked {
if (gasleft() < reserveGas_ + 150) {
// catch out of gas issues when available gas does not even cover reserveGas
// -> immediately return with out of gas. + 150 to cover sload, sub etc.
_resetTransientStorage();
return (false, "AVO__OUT_OF_GAS");
}
}
// using inline assembly for delegatecall to define custom gas amount that should stay here in caller
assembly {
success_ := delegatecall(
// reserve some gas to make sure we can emit CastFailed event even for out of gas cases
// and execute fee paying logic for `castAuthorized()`.
// if gasleft() is less than the amount wanted to be sent along, sub would overflow and send all gas
// that's why there is the explicit check a few lines up.
sub(gas(), reserveGas_),
// load _avoImpl from slot 0 and explicitly convert to address with bit mask
and(sload(0), 0xffffffffffffffffffffffffffffffffffffffff),
add(calldata_, 0x20),
mload(calldata_),
0,
0
)
let size := returndatasize()
result_ := mload(0x40)
mstore(0x40, add(result_, and(add(add(size, 0x20), 0x1f), not(0x1f))))
mstore(result_, size)
returndatacopy(add(result_, 0x20), 0, size)
}
// @dev starting point for measuring reserve gas should be here right after actions execution.
// on changes in code after execution (below here or below `_executeCast()` call in calling method),
// measure the needed reserve gas via `gasleft()` anew and update `CAST_AUTHORIZED_RESERVE_GAS`
// and `CAST_EVENTS_RESERVE_GAS` accordingly. use a method that forces maximum logic execution,
// e.g. `castAuthorized()` with failing action.
// gas measurement currently: ~1400 gas for logic in this method below
if (!success_) {
if (result_.length == 0) {
if (gasleft() < reserveGas_ - 150) {
// catch out of gas errors where not the action ran out of gas but the logic around execution
// of the action itself. -150 to cover gas cost until here
revertReason_ = "AVO__OUT_OF_GAS";
} else {
// @dev this case might be caused by edge-case out of gas errors that we were unable to catch,
// but could potentially also have other reasons
revertReason_ = "AVO__REASON_NOT_DEFINED";
}
} else {
assembly {
result_ := add(result_, 0x04)
}
revertReason_ = abi.decode(result_, (string));
}
}
// reset all transient variables to get the gas refund (4800)
_resetTransientStorage();
}
/// @dev handles failure of an action execution depending on error cause,
/// decoding and reverting with `result_` as reason string.
function _handleActionFailure(uint256 actionMinGasLeft_, uint256 i_, bytes memory result_) internal view {
if (gasleft() < actionMinGasLeft_) {
// action ran out of gas. can not add action index as that again might run out of gas. keep revert minimal
revert("AVO__OUT_OF_GAS");
}
revert(string.concat(Strings.toString(i_), avoSecondary.getRevertReasonFromReturnedData(result_)));
}
function _simulateCast(
CastParams calldata params_,
CastForwardParams calldata forwardParams_,
SignatureParams[] memory signaturesParams_,
ChainAgnosticHash[] memory chainAgnosticHashes_,
bool estimate_
) internal returns (bool success_, string memory revertReason_) {
if (
!(msg.sender == 0x000000000000000000000000000000000000dEaD ||
(msg.sender == avoForwarder && tx.origin == 0x000000000000000000000000000000000000dEaD))
) {
// sender must be the allowed AvoForwarder and tx origin must be dead address,
// or msg.sender must be 0x000000000000000000000000000000000000dEaD directly.
revert AvocadoMultisig__Unauthorized();
}
(success_, revertReason_) = abi.decode(
_spell(
address(avoSecondary),
abi.encodeCall(
avoSecondary.simulateCast,
(params_, forwardParams_, signaturesParams_, chainAgnosticHashes_)
)
),
(bool, string)
);
if (estimate_ && !success_) {
// on estimate, revert to get a more accurate gas estimation result.
revert(revertReason_);
}
}
/// @dev executes a cast process for `cast()` or `castChainAgnostic()`
/// @param params_ Cast params such as id, avoNonce and actions to execute
/// @param forwardParams_ Cast params related to validity of forwarding as instructed and signed
/// @param signaturesParams_ SignatureParams structs array for signature and signer:
/// - signature: the EIP712 signature, 65 bytes ECDSA signature for a default EOA.
/// For smart contract signatures it must fulfill the requirements for the relevant
/// smart contract `.isValidSignature()` EIP1271 logic
/// - signer: address of the signature signer.
/// Must match the actual signature signer or refer to the smart contract
/// that must be an allowed signer and validates signature via EIP1271
/// @param chainAgnosticHashes_ EIP712 type hashes of `CAST_CHAIN_AGNOSTIC_PARAMS_TYPE_HASH` struct for all `CastChainAgnosticParams`
/// struct array elements as used when creating the signature. Result of `getChainAgnosticHashes()`.
/// must be set in the same order as when creating the signature.
/// @return success_ true if all actions were executed succesfully, false otherwise.
/// @return revertReason_ revert reason if one of the actions fails in the following format:
/// The revert reason will be prefixed with the index of the action.
/// e.g. if action 1 fails, then the reason will be "1_reason".
/// if an action in the flashloan callback fails (or an otherwise nested action),
/// it will be prefixed with with two numbers: "1_2_reason".
/// e.g. if action 1 is the flashloan, and action 2 of flashloan actions fails,
/// the reason will be 1_2_reason.
function _cast(
CastParams calldata params_,
CastForwardParams calldata forwardParams_,
SignatureParams[] memory signaturesParams_,
ChainAgnosticHash[] memory chainAgnosticHashes_
) internal returns (bool success_, string memory revertReason_) {
if (msg.sender != avoForwarder) {
// sender must be the allowed AvoForwarder
revert AvocadoMultisig__Unauthorized();
}
unchecked {
// compare actual sent gas to user instructed gas, adding 500 to `gasleft()` for approx. already used gas
if ((gasleft() + 500) < forwardParams_.gas) {
// relayer has not sent enough gas to cover gas limit as user instructed.
// this error should not be blamed on the user but rather on the relayer
revert AvocadoMultisig__InsufficientGasSent();
}
}
// @dev gas measurement: uses maximum 685 gas when all params must be validated
_validateParams(
params_.actions.length,
params_.avoNonce,
forwardParams_.validAfter,
forwardParams_.validUntil,
forwardParams_.value
);
bytes32 digest_;
if (chainAgnosticHashes_.length > 0) {
// validate that input `CastParams` and `CastForwardParams` are present in `chainAgnosticHashes_`
_validateChainAgnostic(
_castChainAgnosticParamsHash(params_, forwardParams_, block.chainid),
chainAgnosticHashes_
);
digest_ = _getSigDigestChainAgnostic(chainAgnosticHashes_);
} else {
digest_ = _getSigDigest(params_, forwardParams_);
}
address[] memory signers_ = _verifySigWithRevert(digest_, signaturesParams_, params_.avoNonce == -1);
(success_, revertReason_) = _executeCast(
params_,
_dynamicReserveGas(CAST_EVENTS_RESERVE_GAS, signers_.length, params_.metadata.length),
params_.avoNonce == -1 ? digest_ : bytes32(0)
);
// @dev on changes in the code below this point, measure the needed reserve gas via `gasleft()` anew
// and update the reserve gas constant amounts.
// gas measurement currently: ~7500 gas for emit event with max revertReason length
if (success_) {
emit CastExecuted(params_.source, msg.sender, signers_, params_.metadata);
} else {
emit CastFailed(params_.source, msg.sender, signers_, revertReason_, params_.metadata);
}
// @dev ending point for measuring reserve gas should be here. Also see comment in `AvocadoMultisigCore._executeCast()`
}
/// @dev executes `actions_` with respective target, calldata, operation etc.
/// IMPORTANT: Validation of `id_` and `_transientAllowHash` is expected to happen in `executeOperation()` and `_callTargets()`.
/// catches out of gas errors (as well as possible), reverting with `AVO__OUT_OF_GAS`.
/// reverts with action index + error code in case of failure (e.g. "1_SOME_ERROR").
function _executeActions(Action[] memory actions_, uint256 id_, bool isFlashloanCallback_) internal {
// reset _transientAllowHash immediately to avert reentrancy etc. & get the gas refund (4800)
_resetTransientStorage();
uint256 storageSlot0Snapshot_; // avoImpl, nonce, initialized vars
uint256 storageSlot1Snapshot_; // signers related variables
// delegate call = ids 1 and 21
bool isDelegateCallId_ = id_ == 1 || id_ == 21;
if (isDelegateCallId_) {
// store values before execution to make sure core storage vars are not modified by a delegatecall.
// this ensures the smart wallet does not end up in a corrupted state.
// for mappings etc. it is hard to protect against storage changes, so we must rely on the owner / signer
// to know what is being triggered and the effects of a tx
assembly {
storageSlot0Snapshot_ := sload(0x0) // avoImpl, nonce & initialized vars
storageSlot1Snapshot_ := sload(0x1) // signers related variables
}
}
uint256 actionsLength_ = actions_.length;
for (uint256 i; i < actionsLength_; ) {
Action memory action_ = actions_[i];
// execute action
bool success_;
bytes memory result_;
uint256 actionMinGasLeft_;
if (action_.operation == 0 && (id_ < 2 || id_ == 20 || id_ == 21)) {
// call (operation = 0 & id = call(0 / 20) or mixed(1 / 21))
unchecked {
// store amount of gas that stays with caller, according to EIP150 to detect out of gas errors
// -> as close as possible to actual call
actionMinGasLeft_ = gasleft() / 64;
}
// low-level call will return success true also if action target is not even a contract.
// we do not explicitly check for this, default interaction is via UI which can check and handle this.
// Also applies to delegatecall etc.
(success_, result_) = action_.target.call{ value: action_.value }(action_.data);
// handle action failure right after external call to better detect out of gas errors
if (!success_) {
_handleActionFailure(actionMinGasLeft_, i, result_);
}
} else if (action_.operation == 1 && isDelegateCallId_) {
// delegatecall (operation = 1 & id = mixed(1 / 21))
unchecked {
// store amount of gas that stays with caller, according to EIP150 to detect out of gas errors
// -> as close as possible to actual call
actionMinGasLeft_ = gasleft() / 64;
}
(success_, result_) = action_.target.delegatecall(action_.data);
// handle action failure right after external call to better detect out of gas errors
if (!success_) {
_handleActionFailure(actionMinGasLeft_, i, result_);
}
// reset _transientAllowHash to make sure it can not be set up in any way for reentrancy
_resetTransientStorage();
// for delegatecall, make sure storage was not modified. After every action, to also defend reentrancy
uint256 storageSlot0_;
uint256 storageSlot1_;
assembly {
storageSlot0_ := sload(0x0) // avoImpl, nonce & initialized vars
storageSlot1_ := sload(0x1) // signers related variables
}
if (!(storageSlot0_ == storageSlot0Snapshot_ && storageSlot1_ == storageSlot1Snapshot_)) {
revert(string.concat(Strings.toString(i), "_AVO__MODIFIED_STORAGE"));
}
} else if (action_.operation == 2 && (id_ == 20 || id_ == 21)) {
// flashloan (operation = 2 & id = flashloan(20 / 21))
if (isFlashloanCallback_) {
revert(string.concat(Strings.toString(i), "_AVO__NO_FLASHLOAN_IN_FLASHLOAN"));
}
// flashloan is always executed via .call, flashloan aggregator uses `msg.sender`, so .delegatecall
// wouldn't send funds to this contract but rather to the original sender.
bytes memory data_ = action_.data;
assembly {
data_ := add(data_, 4) // Skip function selector (4 bytes)
}
// get actions data from calldata action_.data. Only supports InstaFlashAggregatorInterface
(, , , data_, ) = abi.decode(data_, (address[], uint256[], uint256, bytes, bytes));
// set allowHash to signal allowed entry into executeOperation()
_transientAllowHash = bytes31(
keccak256(abi.encode(data_, block.timestamp, EXECUTE_OPERATION_SELECTOR))
);
// store id_ in transient storage slot
_transientId = uint8(id_);
unchecked {
// store amount of gas that stays with caller, according to EIP150 to detect out of gas errors
// -> as close as possible to actual call
actionMinGasLeft_ = gasleft() / 64;
}
// handle action failure right after external call to better detect out of gas errors
(success_, result_) = action_.target.call{ value: action_.value }(action_.data);
if (!success_) {
_handleActionFailure(actionMinGasLeft_, i, result_);
}
// reset _transientAllowHash to prevent reentrancy during actions execution
_resetTransientStorage();
} else {
// either operation does not exist or the id was not set according to what the action wants to execute
revert(string.concat(Strings.toString(i), "_AVO__INVALID_ID_OR_OPERATION"));
}
unchecked {
++i;
}
}
}
/// @dev Validates input params, reverts on invalid values.
/// @param actionsLength_ the length of the actions array to execute
/// @param avoNonce_ the avoNonce from input CastParams
/// @param validAfter_ timestamp after which the request is valid
/// @param validUntil_ timestamp before which the request is valid
/// @param value_ the msg.value expected to be sent along
function _validateParams(
uint256 actionsLength_,
int256 avoNonce_,
uint256 validAfter_,
uint256 validUntil_,
uint256 value_
) internal view {
// make sure actions are defined and nonce is valid:
// must be -1 to use a non-sequential nonce or otherwise it must match the avoNonce
if (!(actionsLength_ > 0 && (avoNonce_ == -1 || uint256(avoNonce_) == _avoNonce))) {
revert AvocadoMultisig__InvalidParams();
}
// make sure request is within valid timeframe
if ((validAfter_ > block.timestamp) || (validUntil_ > 0 && validUntil_ < block.timestamp)) {
revert AvocadoMultisig__InvalidTiming();
}
// make sure msg.value matches `value_` (if set)
if (value_ > 0 && msg.value != value_) {
revert AvocadoMultisig__InvalidParams();
}
}
/// @dev Validates input params for `castChainAgnostic`: verifies that the `curCastChainAgnosticHash_` is present in
/// the `castChainAgnosticHashes_` array of hashes. Reverts with `AvocadoMultisig__InvalidParams` if not.
/// Reverts with `AvocadoMultisig__ChainAgnosticChainMismatch` if the hash is present but valid for another chain.
function _validateChainAgnostic(
bytes32 curCastChainAgnosticHash_,
ChainAgnosticHash[] memory castChainAgnosticHashes_
) internal view {
uint256 length_ = castChainAgnosticHashes_.length;
// chain agnostic must be at least 2 hashes
if (length_ > 1) {
for (uint256 i; i < length_; ) {
if (
curCastChainAgnosticHash_ == castChainAgnosticHashes_[i].hash &&
block.chainid == castChainAgnosticHashes_[i].chainId
) {
// hash must be found, and must be for the current chain to be valid
return;
}
unchecked {
++i;
}
}
}
// `_castChainAgnosticParamsHash()` of current input params is not present in castChainAgnosticHashes_ -> revert
revert AvocadoMultisig__InvalidParams();
}
/// @dev gets the digest (hash) used to verify an EIP712 signature for `authorizedParams_`
/// @param params_ Cast params such as id, avoNonce and actions to execute
/// @param authorizedParams_ Cast params related to execution through owner such as maxFee
/// @return bytes32 digest e.g. for signature or non-sequential nonce
function _getSigDigestAuthorized(
CastParams memory params_,
CastAuthorizedParams memory authorizedParams_
) internal view returns (bytes32) {
return
ECDSA.toTypedDataHash(
// domain separator
_domainSeparatorV4(
DOMAIN_SEPARATOR_SALT_HASHED // includes block.chainid
),
// structHash according to CAST_AUTHORIZED_TYPE_HASH
keccak256(
abi.encode(
CAST_AUTHORIZED_TYPE_HASH,
_castParamsHash(params_),
// CastAuthorizedParams hash
keccak256(
abi.encode(
CAST_AUTHORIZED_PARAMS_TYPE_HASH,
authorizedParams_.maxFee,
authorizedParams_.gasPrice,
authorizedParams_.validAfter,
authorizedParams_.validUntil
)
)
)
)
);
}
}
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.18;
abstract contract AvocadoMultisigErrors {
/// @notice thrown when a method is called with invalid params (e.g. a zero address as input param)
error AvocadoMultisig__InvalidParams();
/// @notice thrown when a signature is not valid (e.g. not signed by enough allowed signers)
error AvocadoMultisig__InvalidSignature();
/// @notice thrown when someone is trying to execute a in some way auth protected logic
error AvocadoMultisig__Unauthorized();
/// @notice thrown when forwarder/relayer does not send enough gas as the user has defined.
/// this error should not be blamed on the user but rather on the relayer
error AvocadoMultisig__InsufficientGasSent();
/// @notice thrown when a signature has expired or when a request isn't valid yet
error AvocadoMultisig__InvalidTiming();
/// @notice thrown when _toHexDigit() fails
error AvocadoMultisig__ToHexDigit();
/// @notice thrown when an EIP1271 signature is invalid
error AvocadoMultisig__InvalidEIP1271Signature();
/// @notice thrown when a `castAuthorized()` `fee` is bigger than the `maxFee` given through the input param
error AvocadoMultisig__MaxFee(uint256 fee, uint256 maxFee);
/// @notice thrown when `castAuthorized()` fee can not be covered by available contract funds
error AvocadoMultisig__InsufficientBalance(uint256 fee);
}
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.18;
abstract contract AvocadoMultisigEvents {
/// @notice Emitted when the implementation is upgraded to a new logic contract
event Upgraded(address indexed newImplementation);
/// @notice Emitted when a message is marked as allowed smart contract signature
event SignedMessage(bytes32 indexed messageHash);
/// @notice Emitted when a previously allowed signed message is removed
event RemoveSignedMessage(bytes32 indexed messageHash);
/// @notice emitted when the avoNonce in storage is increased through an authorized call to
/// `occupyAvoNonces()`, which can be used to cancel a previously signed request
event AvoNonceOccupied(uint256 indexed occupiedAvoNonce);
/// @notice emitted when a non-sequential nonce is occupied in storage through an authorized call to
/// `useNonSequentialNonces()`, which can be used to cancel a previously signed request
event NonSequentialNonceOccupied(bytes32 indexed occupiedNonSequentialNonce);
/// @notice Emitted when a fee is paid through use of the `castAuthorized()` method
event FeePaid(uint256 indexed fee);
/// @notice Emitted when paying a fee reverts at the recipient
event FeePayFailed(uint256 indexed fee);
/// @notice emitted when syncing to the AvoSignersList fails
event ListSyncFailed();
/// @notice emitted when all actions are executed successfully.
/// caller = owner / AvoForwarder address. signers = addresses that triggered this execution
event CastExecuted(address indexed source, address indexed caller, address[] signers, bytes metadata);
/// @notice emitted if one of the executed actions fails. The reason will be prefixed with the index of the action.
/// e.g. if action 1 fails, then the reason will be 1_reason
/// if an action in the flashloan callback fails, it will be prefixed with with two numbers:
/// e.g. if action 1 is the flashloan, and action 2 of flashloan actions fails, the reason will be 1_2_reason.
/// caller = owner / AvoForwarder address. signers = addresses that triggered this execution
/// Note If the signature was invalid, the `signers` array last set element is the signer that caused the revert
event CastFailed(address indexed source, address indexed caller, address[] signers, string reason, bytes metadata);
/// @notice emitted when a signer is added as Multisig signer
event SignerAdded(address indexed signer);
/// @notice emitted when a signer is removed as Multisig signer
event SignerRemoved(address indexed signer);
/// @notice emitted when the required signers count is updated
event RequiredSignersSet(uint8 indexed requiredSigners);
}
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.18;
interface AvocadoMultisigStructs {
/// @notice a combination of a bytes signature and its signer.
struct SignatureParams {
///
/// @param signature ECDSA signature of `getSigDigest()` for default flow or EIP1271 smart contract signature
bytes signature;
///
/// @param signer signer of the signature. Can be set to smart contract address that supports EIP1271
address signer;
}
/// @notice an arbitrary executable action
struct Action {
///
/// @param target the target address to execute the action on
address target;
///
/// @param data the calldata to be passed to the call for each target
bytes data;
///
/// @param value the msg.value to be passed to the call for each target. set to 0 if none
uint256 value;
///
/// @param operation type of operation to execute:
/// 0 -> .call; 1 -> .delegateCall, 2 -> flashloan (via .call)
uint256 operation;
}
/// @notice common params for both `cast()` and `castAuthorized()`
struct CastParams {
Action[] actions;
///
/// @param id Required:
/// id for actions, e.g. 0 = CALL, 1 = MIXED (call and delegatecall),
/// 20 = FLASHLOAN_CALL, 21 = FLASHLOAN_MIXED
uint256 id;
///
/// @param avoNonce Required:
/// avoNonce to be used for this tx. Must equal the avoNonce value on smart
/// wallet or alternatively it must be set to -1 to use a non-sequential nonce instead
int256 avoNonce;
///
/// @param salt Optional:
/// Salt to customize non-sequential nonce (if `avoNonce` is set to -1)
bytes32 salt;
///
/// @param source Optional:
/// Source / referral for this tx
address source;
///
/// @param metadata Optional:
/// metadata for any potential additional data to be tracked in the tx
bytes metadata;
}
/// @notice `cast()` input params related to forwarding validity
struct CastForwardParams {
///
/// @param gas Optional:
/// As EIP-2770: user instructed minimum amount of gas that the relayer (AvoForwarder)
/// must send for the execution. Sending less gas will fail the tx at the cost of the relayer.
/// Also protects against potential gas griefing attacks
/// See https://ronan.eth.limo/blog/ethereum-gas-dangers/
uint256 gas;
///
/// @param gasPrice Optional:
/// Not implemented / used yet
uint256 gasPrice;
///
/// @param validAfter Optional:
/// the earliest block timestamp that the request can be forwarded in,
/// or 0 if the request is not time-limited to occur after a certain time.
/// Protects against relayers executing a certain transaction at an earlier moment
/// not intended by the user, where it might have a completely different effect.
uint256 validAfter;
///
/// @param validUntil Optional:
/// Similar to EIP-2770: the latest block timestamp (instead of block number) the request
/// can be forwarded, or 0 if request should be valid forever.
/// Protects against relayers executing a certain transaction at a later moment
/// not intended by the user, where it might have a completely different effect.
uint256 validUntil;
///
/// @param value Optional:
/// Not implemented / used yet (`msg.value` amount the broadcaster should send along)
uint256 value;
}
/// @notice `castAuthorized()` input params
struct CastAuthorizedParams {
///
/// @param maxFee Optional:
/// the maximum Avocado charge-up allowed to be paid for tx execution
uint256 maxFee;
///
/// @param gasPrice Optional:
/// Not implemented / used yet
uint256 gasPrice;
///
/// @param validAfter Optional:
/// the earliest block timestamp that the request can be forwarded in,
/// or 0 if the request is not time-limited to occur after a certain time.
/// Protects against relayers executing a certain transaction at an earlier moment
/// not intended by the user, where it might have a completely different effect.
uint256 validAfter;
///
/// @param validUntil Optional:
/// Similar to EIP-2770: the latest block timestamp (instead of block number) the request
/// can be forwarded, or 0 if request should be valid forever.
/// Protects against relayers executing a certain transaction at a later moment
/// not intended by the user, where it might have a completely different effect.
uint256 validUntil;
}
/// @notice params for `castChainAgnostic()` to be used when casting txs on multiple chains with one signature
struct CastChainAgnosticParams {
///
/// @param params cast params containing actions to be executed etc.
CastParams params;
///
/// @param forwardParams params related to forwarding validity
CastForwardParams forwardParams;
///
/// @param chainId chainId where these actions are valid
uint256 chainId;
}
/// @notice unique chain agnostic hash with chain id to be used for chain agnostic interactions
struct ChainAgnosticHash {
///
/// @param hash EIP712 type `CAST_CHAIN_AGNOSTIC_PARAMS_TYPE_HASH` hash for one specific `CastChainAgnosticParams` struct
bytes32 hash;
///
/// @param chainId chainId where this `hash` is for
uint256 chainId;
}
}
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.18;
import { SSTORE2 } from "solmate/src/utils/SSTORE2.sol";
import { IAvoRegistry } from "../interfaces/IAvoRegistry.sol";
import { IAvoSignersList } from "../interfaces/IAvoSignersList.sol";
import { IAvoConfigV1 } from "../interfaces/IAvoConfigV1.sol";
import { IAvocado } from "../Avocado.sol";
import { AvocadoMultisigErrors } from "./AvocadoMultisigErrors.sol";
import { AvocadoMultisigEvents } from "./AvocadoMultisigEvents.sol";
abstract contract AvocadoMultisigConstants is AvocadoMultisigErrors {
/************************************|
| CONSTANTS |
|___________________________________*/
/// @notice overwrite chain id for EIP712 is always set to 634 for the Avocado RPC / network
uint256 public constant DEFAULT_CHAIN_ID = 634;
// constants for EIP712 values
string public constant DOMAIN_SEPARATOR_NAME = "Avocado-Multisig";
string public constant DOMAIN_SEPARATOR_VERSION = "1.1.0";
// hashed EIP712 values
bytes32 internal constant DOMAIN_SEPARATOR_NAME_HASHED = keccak256(bytes(DOMAIN_SEPARATOR_NAME));
bytes32 internal constant DOMAIN_SEPARATOR_VERSION_HASHED = keccak256(bytes(DOMAIN_SEPARATOR_VERSION));
/// @notice _TYPE_HASH is copied from OpenZeppelin EIP712 but with added salt as last param (we use it for `block.chainid`)
bytes32 public constant TYPE_HASH =
keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract,bytes32 salt)");
/// @notice EIP712 typehash for `cast()` calls, including structs
bytes32 public constant CAST_TYPE_HASH =
keccak256(
"Cast(CastParams params,CastForwardParams forwardParams)Action(address target,bytes data,uint256 value,uint256 operation)CastForwardParams(uint256 gas,uint256 gasPrice,uint256 validAfter,uint256 validUntil,uint256 value)CastParams(Action[] actions,uint256 id,int256 avoNonce,bytes32 salt,address source,bytes metadata)"
);
/// @notice EIP712 typehash for Action struct
bytes32 public constant ACTION_TYPE_HASH =
keccak256("Action(address target,bytes data,uint256 value,uint256 operation)");
/// @notice EIP712 typehash for CastParams struct
bytes32 public constant CAST_PARAMS_TYPE_HASH =
keccak256(
"CastParams(Action[] actions,uint256 id,int256 avoNonce,bytes32 salt,address source,bytes metadata)Action(address target,bytes data,uint256 value,uint256 operation)"
);
/// @notice EIP712 typehash for CastForwardParams struct
bytes32 public constant CAST_FORWARD_PARAMS_TYPE_HASH =
keccak256(
"CastForwardParams(uint256 gas,uint256 gasPrice,uint256 validAfter,uint256 validUntil,uint256 value)"
);
/// @notice EIP712 typehash for `castAuthorized()` calls, including structs
bytes32 public constant CAST_AUTHORIZED_TYPE_HASH =
keccak256(
"CastAuthorized(CastParams params,CastAuthorizedParams authorizedParams)Action(address target,bytes data,uint256 value,uint256 operation)CastAuthorizedParams(uint256 maxFee,uint256 gasPrice,uint256 validAfter,uint256 validUntil)CastParams(Action[] actions,uint256 id,int256 avoNonce,bytes32 salt,address source,bytes metadata)"
);
/// @notice EIP712 typehash for CastAuthorizedParams struct
bytes32 public constant CAST_AUTHORIZED_PARAMS_TYPE_HASH =
keccak256("CastAuthorizedParams(uint256 maxFee,uint256 gasPrice,uint256 validAfter,uint256 validUntil)");
/// @notice EIP712 typehash for `castChainAgnostic()` calls, bytes32 hashes array + chainId, made up of `getSigDigest()` for
/// action on the respective chain.
bytes32 public constant CAST_CHAIN_AGNOSTIC_TYPE_HASH =
keccak256(
"CastChainAgnostic(CastChainAgnosticParams[] params,uint256[] chainIds)Action(address target,bytes data,uint256 value,uint256 operation)CastChainAgnosticParams(CastParams params,CastForwardParams forwardParams,uint256 chainId)CastForwardParams(uint256 gas,uint256 gasPrice,uint256 validAfter,uint256 validUntil,uint256 value)CastParams(Action[] actions,uint256 id,int256 avoNonce,bytes32 salt,address source,bytes metadata)"
);
/// @notice EIP712 typehash for CastChainAgnosticParams struct
bytes32 public constant CAST_CHAIN_AGNOSTIC_PARAMS_TYPE_HASH =
keccak256(
"CastChainAgnosticParams(CastParams params,CastForwardParams forwardParams,uint256 chainId)Action(address target,bytes data,uint256 value,uint256 operation)CastForwardParams(uint256 gas,uint256 gasPrice,uint256 validAfter,uint256 validUntil,uint256 value)CastParams(Action[] actions,uint256 id,int256 avoNonce,bytes32 salt,address source,bytes metadata)"
);
/// @notice EIP712 typehash for signed hashes used for EIP1271 (`isValidSignature()`)
bytes32 public constant EIP1271_TYPE_HASH = keccak256("AvocadoHash(bytes32 hash)");
/// @notice defines the max signers count for the Multisig. This is chosen deliberately very high, as there shouldn't
/// really be a limit on signers count in practice. It is extremely unlikely that anyone runs into this very high
/// limit but it helps to implement test coverage within this given limit
uint256 public constant MAX_SIGNERS_COUNT = 90;
/// @dev "magic value" according to EIP1271 https://eips.ethereum.org/EIPS/eip-1271#specification
bytes4 internal constant EIP1271_MAGIC_VALUE = 0x1626ba7e;
/// @dev constants for _transientAllowHash functionality: function selectors
bytes4 internal constant _CALL_TARGETS_SELECTOR = bytes4(keccak256(bytes("_callTargets()")));
bytes4 internal constant EXECUTE_OPERATION_SELECTOR = bytes4(keccak256(bytes("executeOpeartion()")));
/// @dev amount of gas to keep in castAuthorized caller method as reserve for emitting event + paying fee.
/// the dynamic part is covered with PER_SIGNER_RESERVE_GAS.
/// use 48_500 as reserve gas for `castAuthorized()`. Usually it will cost less but 48_500 is the maximum amount
/// with buffer (~32_000 + 1_400 base) pay fee logic etc. could cost on maximum logic execution.
uint256 internal constant CAST_AUTHORIZED_RESERVE_GAS = 48_500;
/// @dev amount of gas to keep in cast caller method as reserve for emitting CastFailed / CastExecuted event.
/// ~7500 gas + ~1400 gas + buffer. the dynamic part is covered with PER_SIGNER_RESERVE_GAS and EMIT_EVENT_COST_PER_BYTE.
uint256 internal constant CAST_EVENTS_RESERVE_GAS = 10_000;
/// @dev emitting one byte in an event costs 8 byte see https://github.com/wolflo/evm-opcodes/blob/main/gas.md#a8-log-operations
uint256 internal constant EMIT_EVENT_COST_PER_BYTE = 8;
/// @dev maximum length of revert reason, longer will be truncated. necessary to reserve enugh gas for event emit
uint256 internal constant REVERT_REASON_MAX_LENGTH = 250;
/// @dev each additional signer costs ~358 gas to emit in the CastFailed / CastExecuted event. this amount must be
/// factored in dynamically depending on the number of signers (PER_SIGNER_RESERVE_GAS * number of signers)
uint256 internal constant PER_SIGNER_RESERVE_GAS = 370;
/************************************|
| IMMUTABLES |
|___________________________________*/
// hashed EIP712 value to reduce gas usage
bytes32 internal immutable DOMAIN_SEPARATOR_SALT_HASHED = keccak256(abi.encodePacked(block.chainid));
// hashed EIP712 value to reduce gas usage
bytes32 internal immutable DOMAIN_SEPARATOR_CHAIN_AGNOSTIC_SALT_HASHED =
keccak256(abi.encodePacked(DEFAULT_CHAIN_ID));
/// @notice registry holding the valid versions (addresses) for Avocado smart wallet implementation contracts
/// The registry is used to verify a valid version before upgrading & to pay fees for `castAuthorized()`
IAvoRegistry public immutable avoRegistry;
/// @notice address of the AvoForwarder (proxy) that is allowed to forward tx with valid signatures
address public immutable avoForwarder;
/// @notice Signers <> Avocados mapping list contract for easy on-chain tracking
IAvoSignersList public immutable avoSignersList;
// @dev Note that AvocadoMultisig.sol (main) also has an immutable for avoSecondary in AvocadoMultisigCore.sol
// backup fee logic
/// @dev minimum fee for fee charged via `castAuthorized()` to charge if `AvoRegistry.calcFee()` would fail
uint256 public immutable AUTHORIZED_MIN_FEE;
/// @dev global maximum for fee charged via `castAuthorized()`. If AvoRegistry returns a fee higher than this,
/// then MAX_AUTHORIZED_FEE is charged as fee instead (capping)
uint256 public immutable AUTHORIZED_MAX_FEE;
/// @dev address that the fee charged via `castAuthorized()` is sent to in the fallback case
address payable public immutable AUTHORIZED_FEE_COLLECTOR;
/***********************************|
| CONSTRUCTOR |
|__________________________________*/
constructor(
IAvoRegistry avoRegistry_,
address avoForwarder_,
IAvoSignersList avoSignersList_,
IAvoConfigV1 avoConfigV1_
) {
if (
address(avoRegistry_) == address(0) ||
avoForwarder_ == address(0) ||
address(avoSignersList_) == address(0) ||
address(avoConfigV1_) == address(0)
) {
revert AvocadoMultisig__InvalidParams();
}
avoRegistry = avoRegistry_;
avoForwarder = avoForwarder_;
avoSignersList = avoSignersList_;
// get values from AvoConfigV1 contract
IAvoConfigV1.AvocadoMultisigConfig memory avoConfig_ = avoConfigV1_.avocadoMultisigConfig();
// min & max fee settings, fee collector address are required
if (
avoConfig_.authorizedMinFee == 0 ||
avoConfig_.authorizedMaxFee == 0 ||
avoConfig_.authorizedFeeCollector == address(0) ||
avoConfig_.authorizedMinFee > avoConfig_.authorizedMaxFee
) {
revert AvocadoMultisig__InvalidParams();
}
AUTHORIZED_MIN_FEE = avoConfig_.authorizedMinFee;
AUTHORIZED_MAX_FEE = avoConfig_.authorizedMaxFee;
AUTHORIZED_FEE_COLLECTOR = payable(avoConfig_.authorizedFeeCollector);
}
}
abstract contract AvocadoMultisigVariablesSlot0 {
/// @notice address of the smart wallet logic / implementation contract.
// @dev IMPORTANT: SAME STORAGE SLOT AS FOR PROXY (`Avocado`). DO NOT MOVE THIS VARIABLE.
// _avoImpl MUST ALWAYS be the first declared variable here in the logic contract and in the proxy!
// When upgrading, the storage at memory address 0x0 is upgraded (first slot).
// Note immutable and constants do not take up storage slots so they can come before.
address internal _avoImpl;
/// @dev nonce that is incremented for every `cast` / `castAuthorized` transaction (unless it uses a non-sequential nonce)
uint80 internal _avoNonce;
/// @dev AvocadoMultisigInitializable.sol variables (modified from OpenZeppelin), see ./lib folder
/// @dev Indicates that the contract has been initialized.
uint8 internal _initialized;
/// @dev Indicates that the contract is in the process of being initialized.
bool internal _initializing;
}
abstract contract AvocadoMultisigVariablesSlot1 is AvocadoMultisigConstants, AvocadoMultisigEvents {
/// @dev signers are stored with SSTORE2 to save gas, especially for storage checks at delegateCalls.
/// getter and setter are implemented below
address internal _signersPointer;
/// @notice signers count required to reach quorom and be able to execute actions
uint8 internal _requiredSigners;
/// @notice number of signers currently listed as allowed signers
//
// @dev should be updated directly via `_setSigners()` in AvocadoMultisigSecondary
uint8 internal _signersCount;
// 10 bytes empty
/***********************************|
| SIGNERS GETTERS |
|__________________________________*/
/// @dev reads signers from storage with SSTORE2
function _getSigners() internal view returns (address[] memory signers_) {
address pointer_ = _signersPointer;
if (pointer_ == address(0)) {
// signers not set yet -> only owner is signer currently.
signers_ = new address[](1);
signers_[0] = IAvocado(address(this))._owner();
return signers_;
}
return abi.decode(SSTORE2.read(pointer_), (address[]));
}
/// @dev reads required signers (and returns 1 if it is not set)
function _getRequiredSigners() internal view returns (uint8 requiredSigners_) {
requiredSigners_ = _requiredSigners;
if (requiredSigners_ == 0) {
requiredSigners_ = 1;
}
}
/// @dev reads signers count (and returns 1 if it is not set)
function _getSignersCount() internal view returns (uint8 signersCount_) {
signersCount_ = _signersCount;
if (signersCount_ == 0) {
signersCount_ = 1;
}
}
}
abstract contract AvocadoMultisigVariablesSlot2 {
/// @dev allow-listed signed messages, e.g. for Permit2 Uniswap interaction.
/// mappings are not in sequential storage slot, thus not influenced by previous storage variables
/// (but consider the slot number in calculating the hash of the key to store).
mapping(bytes32 => uint256) internal _signedMessages;
}
abstract contract AvocadoMultisigVariablesSlot3 {
/// @notice occupied non-sequential nonces (which can not be used again)
mapping(bytes32 => uint256) public nonSequentialNonces;
}
abstract contract AvocadoMultisigSlotGaps {
// slots 4 to 53
// create some storage slot gaps for future expansion before the transient storage slot
uint256[50] private __gaps;
}
abstract contract AvocadoMultisigTransient {
// slot 54
/// @dev transient allow hash used to signal allowing certain entry into methods such as _callTargets etc.
bytes31 internal _transientAllowHash;
/// @dev transient id used for passing id to flashloan callback
uint8 internal _transientId;
}
/// @notice Defines storage variables for AvocadoMultisig
abstract contract AvocadoMultisigVariables is
AvocadoMultisigConstants,
AvocadoMultisigVariablesSlot0,
AvocadoMultisigVariablesSlot1,
AvocadoMultisigVariablesSlot2,
AvocadoMultisigVariablesSlot3,
AvocadoMultisigSlotGaps,
AvocadoMultisigTransient
{
constructor(
IAvoRegistry avoRegistry_,
address avoForwarder_,
IAvoSignersList avoSignersList_,
IAvoConfigV1 avoConfigV1_
) AvocadoMultisigConstants(avoRegistry_, avoForwarder_, avoSignersList_, avoConfigV1_) {}
/// @dev resets transient storage to default value (1). 1 is better than 0 for optimizing gas refunds
function _resetTransientStorage() internal {
assembly {
sstore(54, 1) // Store 1 in the transient storage 54
}
}
}
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.18;
import { Address } from "@openzeppelin/contracts/utils/Address.sol";
import { AvocadoMultisigVariables } from "../AvocadoMultisigVariables.sol";
/// @dev contract copied from OpenZeppelin Initializable but with storage vars moved to AvocadoMultisigVariables.sol
/// from OpenZeppelin Contracts (last updated v4.8.1) (proxy/utils/Initializable.sol)
/// see https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v4.8.1/contracts/proxy/utils/Initializable.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]
* ```
* 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 AvocadoMultisigInitializable is AvocadoMultisigVariables {
/**
* @dev Indicates that the contract has been initialized.
* @custom:oz-retyped-from bool
*/
// uint8 private _initialized; // -> in AvocadoMultisigVariables
/**
* @dev Indicates that the contract is in the process of being initialized.
*/
// bool private _initializing; // -> in AvocadoMultisigVariables
/**
* @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
pragma solidity >=0.8.18;
interface InstaFlashReceiverInterface {
function executeOperation(
address[] calldata assets,
uint256[] calldata amounts,
uint256[] calldata premiums,
address initiator,
bytes calldata _data
) external returns (bool);
}
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.18;
import { AvocadoMultisigStructs } from "../AvocadoMultisig/AvocadoMultisigStructs.sol";
// @dev base interface without getters for storage variables (to avoid overloads issues)
interface IAvocadoMultisigV1Base is AvocadoMultisigStructs {
/// @notice initializer called by AvoFactory after deployment, sets the `owner_` as the only signer
function initialize() external;
/// @notice returns the domainSeparator for EIP712 signature
function domainSeparatorV4() external view returns (bytes32);
/// @notice returns the domainSeparator for EIP712 signature for `castChainAgnostic`
function domainSeparatorV4ChainAgnostic() external view returns (bytes32);
/// @notice gets the digest (hash) used to verify an EIP712 signature for `cast()`.
///
/// This is also used as the non-sequential nonce that will be marked as used when the
/// request with the matching `params_` and `forwardParams_` is executed via `cast()`.
/// @param params_ Cast params such as id, avoNonce and actions to execute
/// @param forwardParams_ Cast params related to validity of forwarding as instructed and signed
/// @return bytes32 digest to verify signature (or used as non-sequential nonce)
function getSigDigest(
CastParams calldata params_,
CastForwardParams calldata forwardParams_
) external view returns (bytes32);
/// @notice gets the digest (hash) used to verify an EIP712 signature for `castAuthorized()`.
///
/// This is also the non-sequential nonce that will be marked as used when the request
/// with the matching `params_` and `authorizedParams_` is executed via `castAuthorized()`.
/// @param params_ Cast params such as id, avoNonce and actions to execute
/// @param authorizedParams_ Cast params related to authorized execution such as maxFee, as signed
/// @return bytes32 digest to verify signature (or used as non-sequential nonce)
function getSigDigestAuthorized(
CastParams calldata params_,
CastAuthorizedParams calldata authorizedParams_
) external view returns (bytes32);
/// @notice Verify the signatures for a `cast()' call are valid and can be executed.
/// This does not guarantuee that the tx will not revert, simply that the params are valid.
/// Does not revert and returns successfully if the input is valid.
/// Reverts if input params, signature or avoNonce etc. are invalid.
/// @param params_ Cast params such as id, avoNonce and actions to execute
/// @param forwardParams_ Cast params related to validity of forwarding as instructed and signed
/// @param signaturesParams_ SignatureParams structs array for signature and signer:
/// - signature: the EIP712 signature, 65 bytes ECDSA signature for a default EOA.
/// For smart contract signatures it must fulfill the requirements for the relevant
/// smart contract `.isValidSignature()` EIP1271 logic
/// - signer: address of the signature signer.
/// Must match the actual signature signer or refer to the smart contract
/// that must be an allowed signer and validates signature via EIP1271
/// @return returns true if everything is valid, otherwise reverts
function verify(
CastParams calldata params_,
CastForwardParams calldata forwardParams_,
SignatureParams[] calldata signaturesParams_
) external view returns (bool);
/// @notice Verify the signatures for a `castAuthorized()' call are valid and can be executed.
/// This does not guarantuee that the tx will not revert, simply that the params are valid.
/// Does not revert and returns successfully if the input is valid.
/// Reverts if input params, signature or avoNonce etc. are invalid.
/// @param params_ Cast params such as id, avoNonce and actions to execute
/// @param authorizedParams_ Cast params related to authorized execution such as maxFee, as signed
/// @param signaturesParams_ SignatureParams structs array for signature and signer:
/// - signature: the EIP712 signature, 65 bytes ECDSA signature for a default EOA.
/// For smart contract signatures it must fulfill the requirements for the relevant
/// smart contract `.isValidSignature()` EIP1271 logic
/// - signer: address of the signature signer.
/// Must match the actual signature signer or refer to the smart contract
/// that must be an allowed signer and validates signature via EIP1271
/// @return returns true if everything is valid, otherwise reverts
function verifyAuthorized(
CastParams calldata params_,
CastAuthorizedParams calldata authorizedParams_,
SignatureParams[] calldata signaturesParams_
) external view returns (bool);
/// @notice Executes arbitrary actions with valid signatures. Only executable by AvoForwarder.
/// If one action fails, the transaction doesn't revert, instead emits the `CastFailed` event.
/// In that case, all previous actions are reverted.
/// On success, emits CastExecuted event.
/// @dev validates EIP712 signature then executes each action via .call or .delegatecall
/// @param params_ Cast params such as id, avoNonce and actions to execute
/// @param forwardParams_ Cast params related to validity of forwarding as instructed and signed
/// @param signaturesParams_ SignatureParams structs array for signature and signer:
/// - signature: the EIP712 signature, 65 bytes ECDSA signature for a default EOA.
/// For smart contract signatures it must fulfill the requirements for the relevant
/// smart contract `.isValidSignature()` EIP1271 logic
/// - signer: address of the signature signer.
/// Must match the actual signature signer or refer to the smart contract
/// that must be an allowed signer and validates signature via EIP1271
/// @return success true if all actions were executed succesfully, false otherwise.
/// @return revertReason revert reason if one of the actions fails in the following format:
/// The revert reason will be prefixed with the index of the action.
/// e.g. if action 1 fails, then the reason will be "1_reason".
/// if an action in the flashloan callback fails (or an otherwise nested action),
/// it will be prefixed with with two numbers: "1_2_reason".
/// e.g. if action 1 is the flashloan, and action 2 of flashloan actions fails,
/// the reason will be 1_2_reason.
function cast(
CastParams calldata params_,
CastForwardParams calldata forwardParams_,
SignatureParams[] calldata signaturesParams_
) external payable returns (bool success, string memory revertReason);
/// @notice Simulates a `cast()` call with exact same params and execution logic except for:
/// - any `gasleft()` use removed to remove potential problems when estimating gas.
/// - reverts on param validations removed (verify validity with `verify` instead).
/// - signature validation is skipped (must be manually added to gas estimations).
/// @dev tx.origin must be dead address, msg.sender must be AvoForwarder.
/// @dev - set `signaturesParams_` to empty to automatically simulate with required signers length.
/// - if `signaturesParams_` first element signature is not set, or if first signer is set to
/// 0xFFfFfFffFFfffFFfFFfFFFFFffFFFffffFfFFFfF, then gas usage burn is simulated
/// for verify signature functionality. DO NOT set signature to non-empty for subsequent
/// elements then; set all signatures to empty!
/// - if `signaturesParams_` is set normally, signatures are verified as in actual execute
/// - buffer amounts for mock smart contract signers signature verification must be added
/// off-chain as this varies on a case per case basis.
function simulateCast(
CastParams calldata params_,
CastForwardParams calldata forwardParams_,
SignatureParams[] memory signaturesParams_
) external payable returns (bool success_, string memory revertReason_);
/// @notice Exact same as `simulateCast`, just reverts in case of `success_` = false to optimize
/// for use with .estimateGas().
function estimateCast(
CastParams calldata params_,
CastForwardParams calldata forwardParams_,
SignatureParams[] memory signaturesParams_
) external payable returns (bool success_, string memory revertReason_);
/// @notice Executes arbitrary actions through authorized transaction sent with valid signatures.
/// Includes a fee in native network gas token, amount depends on registry `calcFee()`.
/// If one action fails, the transaction doesn't revert, instead emits the `CastFailed` event.
/// In that case, all previous actions are reverted.
/// On success, emits CastExecuted event.
/// @dev executes a .call or .delegateCall for every action (depending on params)
/// @param params_ Cast params such as id, avoNonce and actions to execute
/// @param authorizedParams_ Cast params related to authorized execution such as maxFee, as signed
/// @param signaturesParams_ SignatureParams structs array for signature and signer:
/// - signature: the EIP712 signature, 65 bytes ECDSA signature for a default EOA.
/// For smart contract signatures it must fulfill the requirements for the relevant
/// smart contract `.isValidSignature()` EIP1271 logic
/// - signer: address of the signature signer.
/// Must match the actual signature signer or refer to the smart contract
/// that must be an allowed signer and validates signature via EIP1271
/// @return success true if all actions were executed succesfully, false otherwise.
/// @return revertReason revert reason if one of the actions fails in the following format:
/// The revert reason will be prefixed with the index of the action.
/// e.g. if action 1 fails, then the reason will be "1_reason".
/// if an action in the flashloan callback fails (or an otherwise nested action),
/// it will be prefixed with with two numbers: "1_2_reason".
/// e.g. if action 1 is the flashloan, and action 2 of flashloan actions fails,
/// the reason will be 1_2_reason.
function castAuthorized(
CastParams calldata params_,
CastAuthorizedParams calldata authorizedParams_,
SignatureParams[] calldata signaturesParams_
) external payable returns (bool success, string memory revertReason);
/// @notice returns the hashes struct for each `CastChainAgnosticParams` element of `params_`. The returned array must be
/// passed into `castChainAgnostic()` as the param `chainAgnosticHashes_` there (order must be the same).
/// The returned hash for each element is the EIP712 type hash for `CAST_CHAIN_AGNOSTIC_PARAMS_TYPE_HASH`,
/// as used when the signature digest is built.
function getChainAgnosticHashes(
CastChainAgnosticParams[] calldata params_
) external pure returns (ChainAgnosticHash[] memory chainAgnosticHashes_);
/// @notice gets the digest (hash) used to verify an EIP712 signature for `castChainAgnostic()`,
/// built from the `CastChainAgnosticParams`.
///
/// This is also the non-sequential nonce that will be marked as used when the request
/// with the matching `params_` is executed via `castChainAgnostic()`.
/// @param params_ Cast params such as id, avoNonce and actions to execute
/// @return bytes32 digest to verify signature (or used as non-sequential nonce)
function getSigDigestChainAgnostic(CastChainAgnosticParams[] calldata params_) external view returns (bytes32);
/// @notice gets the digest (hash) used to verify an EIP712 signature for `castChainAgnostic()`,
/// built from the chain agnostic hashes (result of `getChainAgnosticHashes()`).
///
/// This is also the non-sequential nonce that will be marked as used when the request
/// with the matching `params_` is executed via `castChainAgnostic()`.
/// @param chainAgnosticHashes_ EIP712 type hashes of `CAST_CHAIN_AGNOSTIC_PARAMS_TYPE_HASH` for all `CastChainAgnosticParams`
/// struct array elements as used when creating the signature. Result of `getChainAgnosticHashes()`.
/// must be set in the same order as when creating the signature.
/// @return bytes32 digest to verify signature (or used as non-sequential nonce)
function getSigDigestChainAgnosticFromHashes(
ChainAgnosticHash[] calldata chainAgnosticHashes_
) external view returns (bytes32);
/// @notice Executes arbitrary actions with valid signatures. Only executable by AvoForwarder.
/// If one action fails, the transaction doesn't revert, instead emits the `CastFailed` event.
/// In that case, all previous actions are reverted.
/// On success, emits CastExecuted event.
/// @dev validates EIP712 signature then executes each action via .call or .delegatecall
/// @param params_ params containing info and intents regarding actions to be executed. Made up of
/// same params as for `cast()` plus chain id.
/// @param signaturesParams_ SignatureParams structs array for signature and signer:
/// - signature: the EIP712 signature, 65 bytes ECDSA signature for a default EOA.
/// For smart contract signatures it must fulfill the requirements for the relevant
/// smart contract `.isValidSignature()` EIP1271 logic
/// - signer: address of the signature signer.
/// Must match the actual signature signer or refer to the smart contract
/// that must be an allowed signer and validates signature via EIP1271
/// @param chainAgnosticHashes_ EIP712 type hashes of `CAST_CHAIN_AGNOSTIC_PARAMS_TYPE_HASH` for all `CastChainAgnosticParams`
/// struct array elements as used when creating the signature. Result of `getChainAgnosticHashes()`.
/// must be set in the same order as when creating the signature.
/// @return success true if all actions were executed succesfully, false otherwise.
/// @return revertReason revert reason if one of the actions fails in the following format:
/// The revert reason will be prefixed with the index of the action.
/// e.g. if action 1 fails, then the reason will be "1_reason".
/// if an action in the flashloan callback fails (or an otherwise nested action),
/// it will be prefixed with with two numbers: "1_2_reason".
/// e.g. if action 1 is the flashloan, and action 2 of flashloan actions fails,
/// the reason will be 1_2_reason.
function castChainAgnostic(
CastChainAgnosticParams calldata params_,
SignatureParams[] memory signaturesParams_,
ChainAgnosticHash[] calldata chainAgnosticHashes_
) external payable returns (bool success, string memory revertReason);
/// @notice Simulates a `castChainAgnostic()` call with exact same params and execution logic except for:
/// - any `gasleft()` use removed to remove potential problems when estimating gas.
/// - reverts on param validations removed (verify validity with `verify` instead).
/// - signature validation is skipped (must be manually added to gas estimations).
/// @dev tx.origin must be dead address, msg.sender must be AvoForwarder.
/// @dev - set `signaturesParams_` to empty to automatically simulate with required signers length.
/// - if `signaturesParams_` first element signature is not set, or if first signer is set to
/// 0xFFfFfFffFFfffFFfFFfFFFFFffFFFffffFfFFFfF, then gas usage burn is simulated
/// for verify signature functionality. DO NOT set signature to non-empty for subsequent
/// elements then; set all signatures to empty!
/// - if `signaturesParams_` is set normally, signatures are verified as in actual execute
/// - buffer amounts for mock smart contract signers signature verification must be added
/// off-chain as this varies on a case per case basis.
function simulateCastChainAgnostic(
CastChainAgnosticParams calldata params_,
SignatureParams[] memory signaturesParams_,
ChainAgnosticHash[] calldata chainAgnosticHashes_
) external payable returns (bool success_, string memory revertReason_);
/// @notice Exact same as `simulateCastChainAgnostic`, just reverts in case of `success_` = false to
/// optimize for use with .estimateGas().
function estimateCastChainAgnostic(
CastChainAgnosticParams calldata params_,
SignatureParams[] memory signaturesParams_,
ChainAgnosticHash[] calldata chainAgnosticHashes_
) external payable returns (bool success_, string memory revertReason_);
/// @notice Verify the signatures for a `castChainAgnostic()' call are valid and can be executed.
/// This does not guarantuee that the tx will not revert, simply that the params are valid.
/// Does not revert and returns successfully if the input is valid.
/// Reverts if input params, signature or avoNonce etc. are invalid.
/// @param params_ params containing info and intents regarding actions to be executed. Made up of
/// same params as for `cast()` plus chain id.
/// @param signaturesParams_ SignatureParams structs array for signature and signer:
/// - signature: the EIP712 signature, 65 bytes ECDSA signature for a default EOA.
/// For smart contract signatures it must fulfill the requirements for the relevant
/// smart contract `.isValidSignature()` EIP1271 logic
/// - signer: address of the signature signer.
/// Must match the actual signature signer or refer to the smart contract
/// that must be an allowed signer and validates signature via EIP1271
/// @param chainAgnosticHashes_ EIP712 type hashes of `CAST_CHAIN_AGNOSTIC_PARAMS_TYPE_HASH` for all `CastChainAgnosticParams`
/// struct array elements as used when creating the signature. Result of `getChainAgnosticHashes()`.
/// must be set in the same order as when creating the signature.
/// @return returns true if everything is valid, otherwise reverts
function verifyChainAgnostic(
CastChainAgnosticParams calldata params_,
SignatureParams[] calldata signaturesParams_,
ChainAgnosticHash[] calldata chainAgnosticHashes_
) external view returns (bool);
/// @notice checks if an address `signer_` is an allowed signer (returns true if allowed)
function isSigner(address signer_) external view returns (bool);
/// @notice returns allowed signers on Avocado wich can trigger actions if reaching quorum `requiredSigners`.
/// signers automatically include owner.
function signers() external view returns (address[] memory signers_);
/// @notice returns the number of required signers
function requiredSigners() external view returns (uint8);
/// @notice returns the number of allowed signers
function signersCount() external view returns (uint8);
/// @notice Avocado owner
function owner() external view returns (address);
/// @notice Avocado index (number of Avocado for EOA owner)
function index() external view returns (uint32);
}
// @dev full interface with some getters for storage variables
interface IAvocadoMultisigV1 is IAvocadoMultisigV1Base {
/// @notice Domain separator name for signatures
function DOMAIN_SEPARATOR_NAME() external view returns (string memory);
/// @notice Domain separator version for signatures
function DOMAIN_SEPARATOR_VERSION() external view returns (string memory);
/// @notice incrementing nonce for each valid tx executed (to ensure uniqueness)
function avoNonce() external view returns (uint256);
}
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.18;
import { IAvoRegistry } from "./IAvoRegistry.sol";
import { IAvoSignersList } from "./IAvoSignersList.sol";
import { AvocadoMultisigStructs } from "../AvocadoMultisig/AvocadoMultisigStructs.sol";
interface IAvocadoMultisigV1Secondary {
/// @notice pays the fee for `castAuthorized()` calls via the AvoRegistry (or fallback)
/// @param gasUsedFrom_ `gasleft()` snapshot at gas measurement starting point
/// @param maxFee_ maximum acceptable fee to be paid, revert if fee is bigger than this value
function payAuthorizedFee(uint256 gasUsedFrom_, uint256 maxFee_) external payable;
/// @notice decodes `signature` for EIP1271 into `signaturesParams_`
function decodeEIP1271Signature(
bytes calldata signature,
address owner_
) external pure returns (AvocadoMultisigStructs.SignatureParams[] memory signaturesParams_);
/// @notice Get the revert reason from the returnedData (supports Panic, Error & Custom Errors).
/// @param returnedData_ revert data of the call
/// @return reason_ revert reason
function getRevertReasonFromReturnedData(bytes memory returnedData_) external pure returns (string memory reason_);
/// @notice upgrade the contract to a new implementation address.
/// - Must be a valid version at the AvoRegistry.
/// - Can only be self-called (authorization same as for `cast` methods).
/// @param avoImplementation_ New contract address
/// @param afterUpgradeHookData_ flexible bytes for custom usage in after upgrade hook logic
//
// Implementation must call `_afterUpgradeHook()`
function upgradeTo(address avoImplementation_, bytes calldata afterUpgradeHookData_) external;
/// @notice executes a SIMULATE cast process for `cast()` or `castChainAgnostic()` for gas estimations.
/// @param params_ Cast params such as id, avoNonce and actions to execute
/// @param forwardParams_ Cast params related to validity of forwarding as instructed and signed
/// @param signaturesParams_ SignatureParams structs array for signature and signer:
/// - signature: the EIP712 signature, 65 bytes ECDSA signature for a default EOA.
/// For smart contract signatures it must fulfill the requirements for the relevant
/// smart contract `.isValidSignature()` EIP1271 logic
/// - signer: address of the signature signer.
/// Must match the actual signature signer or refer to the smart contract
/// that must be an allowed signer and validates signature via EIP1271
/// @param chainAgnosticHashes_ EIP712 type hashes of `CAST_CHAIN_AGNOSTIC_PARAMS_TYPE_HASH` struct for all `CastChainAgnosticParams`
/// struct array elements as used when creating the signature. Result of `getChainAgnosticHashes()`.
/// must be set in the same order as when creating the signature.
/// @return success_ true if all actions were executed succesfully, false otherwise.
/// @return revertReason_ revert reason if one of the actions fails in the following format:
/// The revert reason will be prefixed with the index of the action.
/// e.g. if action 1 fails, then the reason will be "1_reason".
/// if an action in the flashloan callback fails (or an otherwise nested action),
/// it will be prefixed with with two numbers: "1_2_reason".
/// e.g. if action 1 is the flashloan, and action 2 of flashloan actions fails,
/// the reason will be 1_2_reason.
function simulateCast(
AvocadoMultisigStructs.CastParams calldata params_,
AvocadoMultisigStructs.CastForwardParams calldata forwardParams_,
AvocadoMultisigStructs.SignatureParams[] memory signaturesParams_,
AvocadoMultisigStructs.ChainAgnosticHash[] memory chainAgnosticHashes_
) external returns (bool success_, string memory revertReason_);
/// @notice SIMULATES: executes `actions_` with respective target, calldata, operation etc.
function _simulateExecuteActions(
AvocadoMultisigStructs.Action[] memory actions_,
uint256 id_,
bool isFlashloanCallback_
) external;
}
interface IAvocadoMultisigV1SecondaryConstants {
function avoRegistry() external view returns (IAvoRegistry);
function avoForwarder() external view returns (address);
function avoSignersList() external view returns (IAvoSignersList);
function AUTHORIZED_MIN_FEE() external view returns (uint256);
function AUTHORIZED_MAX_FEE() external view returns (uint256);
function AUTHORIZED_FEE_COLLECTOR() external view returns (address);
}
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.18;
interface IAvoConfigV1 {
struct AvocadoMultisigConfig {
///
/// @param authorizedMinFee_ minimum for fee charged via `castAuthorized()` to charge if
/// `AvoRegistry.calcFee()` would fail.
uint256 authorizedMinFee;
///
/// @param authorizedMaxFee_ maximum for fee charged via `castAuthorized()`. If AvoRegistry
/// returns a fee higher than this, then `authorizedMaxFee_` is charged as fee instead.
uint256 authorizedMaxFee;
///
/// @param authorizedFeeCollector_ address that the fee charged via `castAuthorized()` is sent to in the fallback case.
address authorizedFeeCollector;
}
struct AvoDepositManagerConfig {
address depositToken;
}
struct AvoSignersListConfig {
bool trackInStorage;
}
/// @notice config for AvocadoMultisig
function avocadoMultisigConfig() external view returns (AvocadoMultisigConfig memory);
/// @notice config for AvoDepositManager
function avoDepositManagerConfig() external view returns (AvoDepositManagerConfig memory);
/// @notice config for AvoSignersList
function avoSignersListConfig() external view returns (AvoSignersListConfig memory);
}
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.18;
interface IAvoFeeCollector {
/// @notice fee config params used to determine the fee for Avocado smart wallet `castAuthorized()` calls
struct FeeConfig {
///
/// @param feeCollector address that the fee should be paid to
address payable feeCollector;
///
/// @param mode current fee mode: 0 = percentage fee (gas cost markup); 1 = static fee (better for L2)
uint8 mode;
///
/// @param fee current fee amount:
/// - for mode percentage: fee in 1e6 percentage (1e8 = 100%, 1e6 = 1%)
/// - for static mode: absolute amount in native gas token to charge
/// (max value 30_9485_009,821345068724781055 in 1e18)
uint88 fee;
}
/// @notice calculates the `feeAmount_` for an Avocado (`msg.sender`) transaction `gasUsed_` based on
/// fee configuration present on the contract
/// @param gasUsed_ amount of gas used, required if mode is percentage. not used if mode is static fee.
/// @return feeAmount_ calculated fee amount to be paid
/// @return feeCollector_ address to send the fee to
function calcFee(uint256 gasUsed_) external view returns (uint256 feeAmount_, address payable feeCollector_);
}
interface IAvoRegistry is IAvoFeeCollector {
/// @notice checks if an address is listed as allowed AvoForwarder version, reverts if not.
/// @param avoForwarderVersion_ address of the AvoForwarder logic contract to check
function requireValidAvoForwarderVersion(address avoForwarderVersion_) external view;
/// @notice checks if an address is listed as allowed Avocado version, reverts if not.
/// @param avoVersion_ address of the Avocado logic contract to check
function requireValidAvoVersion(address avoVersion_) external view;
}
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.18;
interface IAvoSignersList {
/// @notice adds mappings of `addSigners_` to an Avocado `avocado_`.
/// checks the data present at the Avocado to validate input data.
///
/// If `trackInStorage` flag is set to false, then only an event will be emitted for off-chain tracking.
/// The contract itself will not track avocados per signer on-chain!
///
/// Silently ignores `addSigners_` that are already added
///
/// There is expectedly no need for this method to be called by anyone other than the Avocado itself.
function syncAddAvoSignerMappings(address avocado_, address[] calldata addSigners_) external;
/// @notice removes mappings of `removeSigners_` from an Avocado `avocado_`.
/// checks the data present at the Avocado to validate input data.
///
/// If `trackInStorage` flag is set to false, then only an event will be emitted for off-chain tracking.
/// The contract itself will not track avocados per signer on-chain!
///
/// Silently ignores `removeSigners_` that are already removed
///
/// There is expectedly no need for this method to be called by anyone other than the Avocado itself.
function syncRemoveAvoSignerMappings(address avocado_, address[] calldata removeSigners_) external;
}
// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity >=0.8.0;
/// @notice Read and write to persistent storage at a fraction of the cost.
/// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/SSTORE2.sol)
/// @author Modified from 0xSequence (https://github.com/0xSequence/sstore2/blob/master/contracts/SSTORE2.sol)
library SSTORE2 {
uint256 internal constant DATA_OFFSET = 1; // We skip the first byte as it's a STOP opcode to ensure the contract can't be called.
/*//////////////////////////////////////////////////////////////
WRITE LOGIC
//////////////////////////////////////////////////////////////*/
function write(bytes memory data) internal returns (address pointer) {
// Prefix the bytecode with a STOP opcode to ensure it cannot be called.
bytes memory runtimeCode = abi.encodePacked(hex"00", data);
bytes memory creationCode = abi.encodePacked(
//---------------------------------------------------------------------------------------------------------------//
// Opcode | Opcode + Arguments | Description | Stack View //
//---------------------------------------------------------------------------------------------------------------//
// 0x60 | 0x600B | PUSH1 11 | codeOffset //
// 0x59 | 0x59 | MSIZE | 0 codeOffset //
// 0x81 | 0x81 | DUP2 | codeOffset 0 codeOffset //
// 0x38 | 0x38 | CODESIZE | codeSize codeOffset 0 codeOffset //
// 0x03 | 0x03 | SUB | (codeSize - codeOffset) 0 codeOffset //
// 0x80 | 0x80 | DUP | (codeSize - codeOffset) (codeSize - codeOffset) 0 codeOffset //
// 0x92 | 0x92 | SWAP3 | codeOffset (codeSize - codeOffset) 0 (codeSize - codeOffset) //
// 0x59 | 0x59 | MSIZE | 0 codeOffset (codeSize - codeOffset) 0 (codeSize - codeOffset) //
// 0x39 | 0x39 | CODECOPY | 0 (codeSize - codeOffset) //
// 0xf3 | 0xf3 | RETURN | //
//---------------------------------------------------------------------------------------------------------------//
hex"60_0B_59_81_38_03_80_92_59_39_F3", // Returns all code in the contract except for the first 11 (0B in hex) bytes.
runtimeCode // The bytecode we want the contract to have after deployment. Capped at 1 byte less than the code size limit.
);
/// @solidity memory-safe-assembly
assembly {
// Deploy a new contract with the generated creation code.
// We start 32 bytes into the code to avoid copying the byte length.
pointer := create(0, add(creationCode, 32), mload(creationCode))
}
require(pointer != address(0), "DEPLOYMENT_FAILED");
}
/*//////////////////////////////////////////////////////////////
READ LOGIC
//////////////////////////////////////////////////////////////*/
function read(address pointer) internal view returns (bytes memory) {
return readBytecode(pointer, DATA_OFFSET, pointer.code.length - DATA_OFFSET);
}
function read(address pointer, uint256 start) internal view returns (bytes memory) {
start += DATA_OFFSET;
return readBytecode(pointer, start, pointer.code.length - start);
}
function read(
address pointer,
uint256 start,
uint256 end
) internal view returns (bytes memory) {
start += DATA_OFFSET;
end += DATA_OFFSET;
require(pointer.code.length >= end, "OUT_OF_BOUNDS");
return readBytecode(pointer, start, end - start);
}
/*//////////////////////////////////////////////////////////////
INTERNAL HELPER LOGIC
//////////////////////////////////////////////////////////////*/
function readBytecode(
address pointer,
uint256 start,
uint256 size
) private view returns (bytes memory data) {
/// @solidity memory-safe-assembly
assembly {
// Get a pointer to some free memory.
data := mload(0x40)
// Update the free memory pointer to prevent overriding our data.
// We use and(x, not(31)) as a cheaper equivalent to sub(x, mod(x, 32)).
// Adding 31 to size and running the result through the logic above ensures
// the memory pointer remains word-aligned, following the Solidity convention.
mstore(0x40, add(data, and(add(add(size, 32), 31), not(31))))
// Store the size of the data in the first 32 byte chunk of free memory.
mstore(data, size)
// Copy the code into memory right after the 32 bytes we used to store the size.
extcodecopy(pointer, add(data, 32), start, size)
}
}
}