Smart account with programmable ownership
- ERC721TokenboundMech.sol: allow the holder of a designated ERC-721 NFT to operate the Mech
- ERC1155TokenboundMech.sol: allow the holder of a designated ERC-1155 NFT to operate the Mech
- ERC20ThresholdMech.sol: allow holders of a minimum balance of an ERC-20 token to operate the Mech
- ERC1155ThresholdMech.sol: allow holders of a minimum balances of designated ERC-1155 tokens to operate the Mech
- ZodiacMech.sol: allow enabled zodiac modules to sign transactions on behalf of the Mech
- Mech.sol: implement custom ownership terms by extending this abstract contract
Mech implements the EIP-4337 account interface, EIP-1271, and the following functions:
Returns true if signer
is allowed to operate the Mech.
Sub classes implement this function for defining the specific operator criteria.
Allows the operator to make the Mech execute a transaction.
operation: 0
for a regular calloperation: 1
for a delegate call
Allows the operator to make the Mech execute a transaction restricting the gas amount made available to the direct execution of the internal meta transaction. Any remaining transaction gas must only be spent for surrounding checks of the operator criteria.
All mech contracts have been audited by G0 Group. Audit reports can be found in the audits directory.
The repo is structured as a monorepo with mech-contracts
as the container package exporting the contract sources and artifacts.
mech-sdk
is a set of TypeScript functions for interaction with the mech contracts.
frontend
is a private package containing the sources for the mech wallet app running at https://mech-omega.vercel.app.
yarn install
yarn build
yarn build:sdk
This step is necessary to make changes in SDK functions available to a locally running front-end.
yarn start
yarn test
Tests covers both, the contract logic as well as the SDK functions.
yarn integrationTest
Integration tests are run on a mainnet fork and cover the interaction of mech contracts with external contracts (Safe and EIP-4337 entry point).
Mech implements the EIP-4337 Account interface meaning they allow bundlers to execute account-abstracted user operations from the Mech's address.
For this purpose the EIP-4337 entry point contract first calls the Mech's validateUserOp()
function for checking if a user operation has a valid signature by the mech operator.
The entry point then calls the execute
function, or any other function using the onlyOperator
modifier, to trigger execution.
Mech implements the EIP-1271 interface.
It validates that a given ECDSA signature is from the expected account where the expected account is derived using the isOperator
function implemented by the sub contract.
Additionally, it supports validation of EIP-1271 contract signatures, which are expected to be given in the following format based on ECDSA {r, s, v} components with v = 0
as the recovery identifier:
0x000000000000000000000000<20 bytes smart contract address>>>>>>>> // r component: the address of signing EIP-1271 contract
0000000000000000000000000000000000000000000000000000000000000041 // s component: constant 65 bytes offset to signature data
00 // v component: constant unpadded `0` as recovery identifier
00000000000000000000000000000000<length of signature data bytes>
<bytes of signature data>
An EIP-1271 signature will be considered valid if it meets the following conditions:
- the signing contract is either the operator of the mech or the mech itself, and
- the signing contract's
isValidSignature()
function returns0x1626ba7e
(EIP-1271 magic value) for the given<bytes of signature data>
.
The idea for the token-bound versions of mech is that the mech instance for a designated token is deployed to an address that can be deterministically derived from the token contract address and token ID. This enables counterfactually funding the mech account (own token to unlock treasure) or granting access for it (use token as key card).
The token-bound versions of Mech adopts the EIP-6551 standard. This means that these kinds of mechs are deployed through the official 6551 account registry, so they are deployed to their canonical addresses and can be detected by compatible tools.
The holder of the token gains full control over the mech account and can write to its storage without any restrictions via delegate calls. Since tokens are transferrable this is problematic, as a past owner could mess with storage to change the mech's behavior in ways that future owners wouldn't expect. That's why the ERC721 and ERC1155 versions of mech avoid using storage but instead solely rely on the immutable data in their own bytecode.
To achieve this, mechs are deployed through a version of a EIP-1167 proxy factory that allows appending arbitrary bytes to the minimal proxy bytecode. The same mechanism is implemented by the 6551 account registry.
The ZodiacMech uses the same storage layout at the Safe contracts, meaning that an existing Safe instance can be migrated to the ZodiacMech implementation.
For migrating a Safe it needs to delegate call the SafeMigration.sol contract's migrate()
function.
This will revoke access for the Safe owners so that the account will only be controlled by enabled modules going forwards.
Attention: You must never trust the result of getModulesPaginated()
without extra validation.
Modules can add other modules without these appearing in the list returned by getModulesPaginated
by writing directly to storage slots via delegate calls.
This caveat is known for Safe and also extends to Zodiac Modifiers and Avatars, like ZodiacMech.
For validating that the return value of getModulesPaginated
indeed includes the full set of enabled modules it is required to calculate the ZodiacMech contract's storage hash and compare it with the value retrieved via eth_getProof.