AvocadoMultisig

AvocadoMultisig_V1

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

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.

AvocadoMultisigErrors

AvocadoMultisig__InvalidParams

error AvocadoMultisig__InvalidParams()

thrown when a method is called with invalid params (e.g. a zero address as input param)

AvocadoMultisig__InvalidSignature

error AvocadoMultisig__InvalidSignature()

thrown when a signature is not valid (e.g. not signed by enough allowed signers)

AvocadoMultisig__Unauthorized

error AvocadoMultisig__Unauthorized()

thrown when someone is trying to execute a in some way auth protected logic

AvocadoMultisig__InsufficientGasSent

error AvocadoMultisig__InsufficientGasSent()

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

AvocadoMultisig__InvalidTiming

error AvocadoMultisig__InvalidTiming()

thrown when a signature has expired or when a request isn't valid yet

AvocadoMultisig__ToHexDigit

error AvocadoMultisig__ToHexDigit()

thrown when _toHexDigit() fails

AvocadoMultisig__InvalidEIP1271Signature

error AvocadoMultisig__InvalidEIP1271Signature()

thrown when an EIP1271 signature is invalid

AvocadoMultisig__MaxFee

error AvocadoMultisig__MaxFee(uint256 fee, uint256 maxFee)

thrown when a castAuthorized() fee is bigger than the maxFee given through the input param

AvocadoMultisig__InsufficientBalance

error AvocadoMultisig__InsufficientBalance(uint256 fee)

thrown when castAuthorized() fee can not be covered by available contract funds

AvocadoMultisigEvents

Upgraded

event Upgraded(address newImplementation)

Emitted when the implementation is upgraded to a new logic contract

SignedMessage

event SignedMessage(bytes32 messageHash)

Emitted when a message is marked as allowed smart contract signature

RemoveSignedMessage

event RemoveSignedMessage(bytes32 messageHash)

Emitted when a previously allowed signed message is removed

AvoNonceOccupied

event AvoNonceOccupied(uint256 occupiedAvoNonce)

emitted when the avoNonce in storage is increased through an authorized call to occupyAvoNonces(), which can be used to cancel a previously signed request

NonSequentialNonceOccupied

event NonSequentialNonceOccupied(bytes32 occupiedNonSequentialNonce)

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

FeePaid

event FeePaid(uint256 fee)

Emitted when a fee is paid through use of the castAuthorized() method

FeePayFailed

event FeePayFailed(uint256 fee)

Emitted when paying a fee reverts at the recipient

ListSyncFailed

event ListSyncFailed()

emitted when syncing to the AvoSignersList / AvoAuthoritiesList fails

CastExecuted

event CastExecuted(address source, address caller, address[] signers, bytes metadata)

emitted when all actions are executed successfully. caller = owner / AvoForwarder address. signers = addresses that triggered this execution

CastFailed

event CastFailed(address source, address caller, address[] signers, string reason, bytes metadata)

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

SignerAdded

event SignerAdded(address signer)

emitted when a signer is added as Multisig signer

SignerRemoved

event SignerRemoved(address signer)

emitted when a signer is removed as Multisig signer

RequiredSignersSet

event RequiredSignersSet(uint8 requiredSigners)

emitted when the required signers count is updated

AvocadoMultisigStructs

SignatureParams

struct SignatureParams {
  bytes signature;
  address signer;
}

Action

struct Action {
  address target;
  bytes data;
  uint256 value;
  uint256 operation;
}

CastParams

struct CastParams {
  struct AvocadoMultisigStructs.Action[] actions;
  uint256 id;
  int256 avoNonce;
  bytes32 salt;
  address source;
  bytes metadata;
}

CastForwardParams

struct CastForwardParams {
  uint256 gas;
  uint256 gasPrice;
  uint256 validAfter;
  uint256 validUntil;
  uint256 value;
}

CastAuthorizedParams

struct CastAuthorizedParams {
  uint256 maxFee;
  uint256 gasPrice;
  uint256 validAfter;
  uint256 validUntil;
}

AvocadoMultisigConstants

DEFAULT_CHAIN_ID

uint256 DEFAULT_CHAIN_ID

overwrite chain id for EIP712 is always set to 634 for the Avocado RPC / network

DOMAIN_SEPARATOR_NAME

string DOMAIN_SEPARATOR_NAME

DOMAIN_SEPARATOR_VERSION

string DOMAIN_SEPARATOR_VERSION

TYPE_HASH

bytes32 TYPE_HASH

_TYPE_HASH is copied from OpenZeppelin EIP712 but with added salt as last param (we use it for block.chainid)

CAST_TYPE_HASH

bytes32 CAST_TYPE_HASH

EIP712 typehash for cast() calls, including structs

ACTION_TYPE_HASH

bytes32 ACTION_TYPE_HASH

EIP712 typehash for Action struct

CAST_PARAMS_TYPE_HASH

bytes32 CAST_PARAMS_TYPE_HASH

EIP712 typehash for CastParams struct

CAST_FORWARD_PARAMS_TYPE_HASH

bytes32 CAST_FORWARD_PARAMS_TYPE_HASH

EIP712 typehash for CastForwardParams struct

CAST_AUTHORIZED_TYPE_HASH

bytes32 CAST_AUTHORIZED_TYPE_HASH

EIP712 typehash for castAuthorized() calls, including structs

CAST_AUTHORIZED_PARAMS_TYPE_HASH

bytes32 CAST_AUTHORIZED_PARAMS_TYPE_HASH

EIP712 typehash for CastAuthorizedParams struct

MAX_SIGNERS_COUNT

uint256 MAX_SIGNERS_COUNT

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

avoRegistry

contract IAvoRegistry avoRegistry

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()

avoForwarder

address avoForwarder

address of the AvoForwarder (proxy) that is allowed to forward tx with valid signatures

avoSignersList

contract IAvoSignersList avoSignersList

Signers <> Avocados mapping list contract for easy on-chain tracking

AUTHORIZED_MIN_FEE

uint256 AUTHORIZED_MIN_FEE

minimum fee for fee charged via castAuthorized() to charge if AvoRegistry.calcFee() would fail

AUTHORIZED_MAX_FEE

uint256 AUTHORIZED_MAX_FEE

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)

AUTHORIZED_FEE_COLLECTOR

address payable AUTHORIZED_FEE_COLLECTOR

address that the fee charged via castAuthorized() is sent to in the fallback case

AvocadoMultisigVariables

Defines storage variables for AvocadoMultisig

nonSequentialNonces

mapping(bytes32 => uint256) nonSequentialNonces

occupied non-sequential nonces (which can not be used again)

AvocadoMultisigSelfUpgradeable

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!

upgradeTo

function upgradeTo(address avoImplementation_, bytes afterUpgradeHookData_) public

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).

Parameters

NameTypeDescription
avoImplementation_addressNew contract address
afterUpgradeHookData_bytesflexible bytes for custom usage in after upgrade hook logic

_afterUpgradeHook

function _afterUpgradeHook(address fromImplementation_, bytes data_) public virtual

hook called after executing an upgrade from previous fromImplementation_, with flexible bytes data_

AvocadoMultisigProtected

occupyAvoNonces

function occupyAvoNonces(uint88[] avoNonces_) external

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).

Parameters

NameTypeDescription
avoNonces_uint88sequential 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)

occupyNonSequentialNonces

function occupyNonSequentialNonces(bytes32[] nonSequentialNonces_) external

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).

Parameters

NameTypeDescription
nonSequentialNonces_bytes32the non-sequential nonces to occupy

executeOperation

function executeOperation(address[], uint256[], uint256[], address initiator_, bytes data_) external returns (bool)

Parameters

NameTypeDescription
address
uint256
uint256
initiator_addressflashloan initiator -> must be this contract
data_bytesdata bytes containing the abi.encoded() actions that are executed like in CastParams.actions

_callTargets

function _callTargets(struct AvocadoMultisigStructs.Action[] actions_, uint256 id_) external payable

_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.*

Parameters

NameTypeDescription
actions_struct AvocadoMultisigStructs.Actionthe actions to execute (target, data, value, operation)
id_uint256id for actions_, see CastParams.id

AvocadoMultisigEIP1271

isValidSignature

function isValidSignature(bytes32 hash, bytes signature) external view returns (bytes4 magicValue)

reverts with AvocadoMultisig__InvalidEIP1271Signature or AvocadoMultisig__InvalidParams if signature is invalid.

Parameters

NameTypeDescription
hashbytes32
signaturebytesThis 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.

signMessage

function signMessage(bytes32 message_) external

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).

Parameters

NameTypeDescription
message_bytes32data hash to be allow-listed as signed

removeSignedMessage

function removeSignedMessage(bytes32 message_) external

Removes a previously signMessage() signed bytes32 message_ (signature digest). - Can only be self-called (authorization same as for cast methods).

Parameters

NameTypeDescription
message_bytes32data hash to be removed from allow-listed signatures

AvocadoMultisigSigners

isSigner

function isSigner(address signer_) public view returns (bool)

checks if an address signer_ is an allowed signer (returns true if allowed)

addSigners

function addSigners(address[] addSigners_, uint8 requiredSigners_) external

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

removeSigners

function removeSigners(address[] removeSigners_, uint8 requiredSigners_) external

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

setRequiredSigners

function setRequiredSigners(uint8 requiredSigners_) external

sets number of required signers for a valid request to requiredSigners_

AvocadoMultisigCast

getSigDigest

function getSigDigest(struct AvocadoMultisigStructs.CastParams params_, struct AvocadoMultisigStructs.CastForwardParams forwardParams_) public view returns (bytes32)

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()`.

Parameters

NameTypeDescription
params_struct AvocadoMultisigStructs.CastParamsCast params such as id, avoNonce and actions to execute
forwardParams_struct AvocadoMultisigStructs.CastForwardParamsCast params related to validity of forwarding as instructed and signed

Return Values

NameTypeDescription
0bytes32bytes32 digest to verify signature (or used as non-sequential nonce)

verify

function verify(struct AvocadoMultisigStructs.CastParams params_, struct AvocadoMultisigStructs.CastForwardParams forwardParams_, struct AvocadoMultisigStructs.SignatureParams[] signaturesParams_) external view returns (bool)

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.

Parameters

NameTypeDescription
params_struct AvocadoMultisigStructs.CastParamsCast params such as id, avoNonce and actions to execute
forwardParams_struct AvocadoMultisigStructs.CastForwardParamsCast params related to validity of forwarding as instructed and signed
signaturesParams_struct AvocadoMultisigStructs.SignatureParamsSignatureParams 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 Values

NameTypeDescription
0boolreturns true if everything is valid, otherwise reverts

cast

function cast(struct AvocadoMultisigStructs.CastParams params_, struct AvocadoMultisigStructs.CastForwardParams forwardParams_, struct AvocadoMultisigStructs.SignatureParams[] signaturesParams_) external payable returns (bool success_, string revertReason_)

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.

validates EIP712 signature then executes each action via .call or .delegatecall

Parameters

NameTypeDescription
params_struct AvocadoMultisigStructs.CastParamsCast params such as id, avoNonce and actions to execute
forwardParams_struct AvocadoMultisigStructs.CastForwardParamsCast params related to validity of forwarding as instructed and signed
signaturesParams_struct AvocadoMultisigStructs.SignatureParamsSignatureParams 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 Values

NameTypeDescription
success_bool
revertReason_string

AvocadoMultisigCastAuthorized

getSigDigestAuthorized

function getSigDigestAuthorized(struct AvocadoMultisigStructs.CastParams params_, struct AvocadoMultisigStructs.CastAuthorizedParams authorizedParams_) public view returns (bytes32)

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()`.

Parameters

NameTypeDescription
params_struct AvocadoMultisigStructs.CastParamsCast params such as id, avoNonce and actions to execute
authorizedParams_struct AvocadoMultisigStructs.CastAuthorizedParamsCast params related to authorized execution such as maxFee, as signed

Return Values

NameTypeDescription
0bytes32bytes32 digest to verify signature (or used as non-sequential nonce)

verifyAuthorized

function verifyAuthorized(struct AvocadoMultisigStructs.CastParams params_, struct AvocadoMultisigStructs.CastAuthorizedParams authorizedParams_, struct AvocadoMultisigStructs.SignatureParams[] signaturesParams_) external view returns (bool)

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.

Parameters

NameTypeDescription
params_struct AvocadoMultisigStructs.CastParamsCast params such as id, avoNonce and actions to execute
authorizedParams_struct AvocadoMultisigStructs.CastAuthorizedParamsCast params related to authorized execution such as maxFee, as signed
signaturesParams_struct AvocadoMultisigStructs.SignatureParamsSignatureParams 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 Values

NameTypeDescription
0boolreturns true if everything is valid, otherwise reverts

castAuthorized

function castAuthorized(struct AvocadoMultisigStructs.CastParams params_, struct AvocadoMultisigStructs.CastAuthorizedParams authorizedParams_, struct AvocadoMultisigStructs.SignatureParams[] signaturesParams_) external payable returns (bool success_, string revertReason_)

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.

executes a .call or .delegateCall for every action (depending on params)

Parameters

NameTypeDescription
params_struct AvocadoMultisigStructs.CastParamsCast params such as id, avoNonce and actions to execute
authorizedParams_struct AvocadoMultisigStructs.CastAuthorizedParamsCast params related to authorized execution such as maxFee, as signed
signaturesParams_struct AvocadoMultisigStructs.SignatureParamsSignatureParams 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 Values

NameTypeDescription
success_bool
revertReason_string

AvocadoMultisig

constructor

constructor(contract IAvoRegistry avoRegistry_, address avoForwarder_, contract IAvoSignersList avoSignersList_, contract IAvoConfigV1 avoConfigV1_) public

constructor sets multiple immutable values for contracts and payFee fallback logic.

Parameters

NameTypeDescription
avoRegistry_contract IAvoRegistryaddress of the avoRegistry (proxy) contract
avoForwarder_addressaddress of the avoForwarder (proxy) contract to forward tx with valid signatures. must be valid version in AvoRegistry.
avoSignersList_contract IAvoSignersListaddress of the AvoSignersList (proxy) contract
avoConfigV1_contract IAvoConfigV1AvoConfigV1 contract holding values for authorizedFee values

initialize

function initialize() public

initializer called by AvoFactory after deployment, sets the owner_ as the only signer

receive

receive() external payable

domainSeparatorV4

function domainSeparatorV4() public view returns (bytes32)

returns the domainSeparator for EIP712 signature

signers

function signers() public view returns (address[] signers_)

returns allowed signers on Avocado wich can trigger actions if reaching quorum requiredSigners. signers automatically include owner.

requiredSigners

function requiredSigners() public view returns (uint8)

returns the number of required signers

signersCount

function signersCount() public view returns (uint8)

returns the number of allowed signers

owner

function owner() public view returns (address)

Avocado owner

index

function index() public view returns (uint32)

Avocado index (number of Avocado for EOA owner)

avoNonce

function avoNonce() public view returns (uint256)

incrementing nonce for each valid tx executed (to ensure uniqueness)

Table of Contents