Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

EIP-6551 compat #27

Merged
merged 43 commits into from
Oct 13, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
23c3a86
upgrade to latest account-abstraction contracts
jfschwarz Apr 26, 2023
f76da22
fix package json
jfschwarz May 10, 2023
4170293
adjust tests
jfschwarz May 10, 2023
4e7cf06
trial to fix vercel routes
jfschwarz May 10, 2023
217223d
fix deploy script for gnosis chain
jfschwarz May 11, 2023
6e53bb0
implement IERC6551Account
jfschwarz May 30, 2023
3d6d79d
restructure contract inheritance hierarchy
jfschwarz Jun 7, 2023
203d1d2
add mech factory
jfschwarz Jun 7, 2023
1f67b0c
refactor contracts
jfschwarz Jun 12, 2023
de98dec
add erc20 threshold mech
jfschwarz Jun 12, 2023
c749149
cleanup
jfschwarz Jun 12, 2023
e264243
update docs
jfschwarz Jun 12, 2023
06d0971
adjust all deploy functions and migrate them to viem
jfschwarz Jun 14, 2023
b608faa
add hierarchy graph
jfschwarz Jun 20, 2023
139d356
adding exports
jfschwarz Jul 13, 2023
f8436f9
Merge branch 'main' into erc-6551
jfschwarz Aug 18, 2023
52484d9
fixes after merge
jfschwarz Aug 18, 2023
cdab142
upgrade deps
jfschwarz Aug 18, 2023
98c79c1
6551 updates and prevent ownership cycles
jfschwarz Aug 21, 2023
eb19c39
fine-tune execution interface
jfschwarz Aug 21, 2023
81dbd52
upgrade tooling
jfschwarz Aug 21, 2023
00304e4
ethers v5 -> v6 migration
jfschwarz Aug 21, 2023
8d249e6
erc165
jfschwarz Aug 21, 2023
a5a32fe
tokenbound deterministic deployment test green
jfschwarz Aug 25, 2023
cd7e95d
update 4337 entrypoint address
jfschwarz Aug 25, 2023
4167db5
fix 4337 tests
jfschwarz Aug 25, 2023
f399e1d
fix missing onlyOperator annotation
jfschwarz Aug 25, 2023
ee82d3f
improve test coverage
jfschwarz Aug 25, 2023
c2f8e3c
test fixes
jfschwarz Aug 28, 2023
a39dc16
front-end updates
jfschwarz Aug 28, 2023
5186a9c
fix deploy scripts
jfschwarz Aug 28, 2023
9fe7e05
migrate from n.xyz to sequencer
jfschwarz Sep 1, 2023
9f371eb
Merge pull request #28 from gnosis/sequencer
jfschwarz Sep 1, 2023
c57769b
deploy 6551 factory
jfschwarz Sep 4, 2023
3198123
more solid deploy script
jfschwarz Sep 4, 2023
1381bd4
fix wallet connect
jfschwarz Sep 4, 2023
63edc04
fix a JS error when switching chains
jfschwarz Oct 12, 2023
7354c08
work around issue of tokenID not being set by sequence api
jfschwarz Oct 12, 2023
0c7cc8c
fix walletconnect v2 issues
jfschwarz Oct 12, 2023
3ca7ec0
fix a ux issue
jfschwarz Oct 12, 2023
bf94f2e
upgrade to latest 6551 contract and adjust accordingly
jfschwarz Oct 13, 2023
11e9b4d
fix tests
jfschwarz Oct 13, 2023
39ef6c7
update eip6551 registry address
jfschwarz Oct 13, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 41 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,44 @@

Smart account with programmable ownership

#### Transferrable ownership
#### Token-bound ownership

- [ERC721Mech.sol](contracts/ERC721Mech.sol): allow the holder of a designated ERC-721 NFT to sign transactions on behalf of the Mech
- [ERC721TokenboundMech.sol](contracts/ERC721TokenboundMech.sol): allow the holder of a designated ERC-721 NFT to operate the Mech
- [ERC1155TokenboundMech.sol](contracts/ERC721TokenboundMech.sol): allow the holder of a designated ERC-1155 NFT to operate the Mech

#### Threshold ownership

- [ERC1155Mech.sol](contracts/ERC1155Mech.sol): allow holders of a minimum balance of ERC-1155 tokens to sign transactions on behalf of the Mech
- [ERC20ThresholdMech.sol](contracts/ERC20ThresholdMech.sol): allow holders of a minimum balance of an ERC-20 token to operate the Mech
- [ERC1155ThresholdMech.sol](contracts/ERC1155ThresholdMech.sol): allow holders of a minimum balances of designated ERC-1155 tokens to operate the Mech

#### Programmable ownership

- [ZodiacMech.sol](contracts/ZodiacMech.sol): allow enabled [zodiac](https://github.com/gnosis/zodiac) modules to sign transactions on behalf of the Mech
- [Mech.sol](contracts/base/Mech.sol): implement custom ownership terms by extending this abstract contract

![mech hierarchy](docs/mech-hierarchy.png)

## Mech interface

Mech implements the [EIP-4337](https://eips.ethereum.org/EIPS/eip-4337) account interface, [EIP-1271](https://eips.ethereum.org/EIPS/eip-1271), and the following functions:

### `isOperator(address signer)`

Returns true if `signer` is allowed to operate the Mech.
Sub classes implement this function for defining the specific operator criteria.

### `execute(address to, uint256 value, bytes data, uint8 operation)`

Allows the operator to make the Mech execute a transaction.

- `operation: 0` for a regular call
- `operation: 1` for a delegate call

### `execute(address to, uint256 value, bytes data, uint8 operation, uint256 txGas)`

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.

## Contribute

The repo is structured as a monorepo with `mech-contracts` as the container package exporting the contract sources and artifacts.
Expand Down Expand Up @@ -67,11 +92,11 @@ Integration tests are run on a mainnet fork and cover the interaction of mech co

## How it works

### EIP-4337 account
### EIP-4337 account abstraction

Mechs implement the EIP-4337 [Account](contracts/base/Account.sol) interface meaning they allow bundlers to execute account-abstracted user operations from the Mech's address.
Mech implements the EIP-4337 [Account](contracts/base/Account.sol) 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 `exec` function, or any other function using the `onlyOperator` modifier, to trigger execution.
The entry point then calls the `execute` function, or any other function using the `onlyOperator` modifier, to trigger execution.

### EIP-1271 signatures

Expand All @@ -95,18 +120,22 @@ An EIP-1271 signature will be considered valid if it meets the following conditi

### Deterministic deployment

The idea for the ERC721 and ERC1155 mechs is that the mech instance for the designated tokens is deployed to a deterministic address.
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 deterministic deployment is implemented via Zodiac's [ModuleProxyFactory](https://github.com/gnosis/zodiac/blob/master/contracts/factory/ModuleProxyFactory.sol), through which each mech instance is deployed as an ERC-1167 minimal proxy.

### Immutable storage
### EIP-6551 token-bound account

The token-bound versions of Mech adopts the [EIP-6551](https://eips.ethereum.org/EIPS/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.

### EIP-1167 minimal proxies with context

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 the mech avoid using storage but hard-code their configuration in bytecode.
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, Mech sub contracts can extend [ImmutableStorage](contracts/base/ImmutableStorage.sol) which allows writing data to the bytecode at a deterministic address once.
Note that using Solidity's `immutable` keyword is not an option for proxy contracts, since immutable fields can only be written to from the constructor which won't be invoked for proxy instances.
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.

### Migrate a Safe to a ZodiacMech

Expand Down
5 changes: 5 additions & 0 deletions TODO.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,8 @@
- check what the Safe indexer requires to pick up ZodiacMechs
- more requirements listed here: https://www.notion.so/Simpler-safe-mastercopy-dd8cf22626794b4aade600e1aa16da0e
- ZodiacMech should allow updating the ERC4337 entrypoint

## SDK

- get rid of zodiac dep
- migrate to viem
91 changes: 0 additions & 91 deletions contracts/ERC1155Mech.sol

This file was deleted.

48 changes: 48 additions & 0 deletions contracts/ERC1155ThresholdMech.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
//SPDX-License-Identifier: LGPL-3.0
pragma solidity ^0.8.12;

import "@openzeppelin/contracts/token/ERC1155/IERC1155.sol";
import "./base/ThresholdMech.sol";
import "./libraries/MinimalProxyStore.sol";

/**
* @dev A Mech that is operated by any holder of a defined set of minimum ERC1155 token balances
*/
contract ERC1155ThresholdMech is ThresholdMech {
function threshold()
public
view
returns (
address token,
uint256[] memory tokenIds,
uint256[] memory minBalances,
uint256 minTotalBalance
)
{
return
abi.decode(
MinimalProxyStore.getContext(address(this)),
(address, uint256[], uint256[], uint256)
);
}

function isOperator(address signer) public view override returns (bool) {
(
address token,
uint256[] memory tokenIds,
uint256[] memory minBalances,
uint256 minTotalBalance
) = this.threshold();

uint256 balanceSum = 0;
for (uint256 i = 0; i < tokenIds.length; i++) {
uint256 balance = IERC1155(token).balanceOf(signer, tokenIds[i]);
if (balance < minBalances[i]) {
return false;
}
balanceSum += balance;
}

return balanceSum >= minTotalBalance;
}
}
80 changes: 80 additions & 0 deletions contracts/ERC1155TokenboundMech.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
//SPDX-License-Identifier: LGPL-3.0
pragma solidity ^0.8.12;

import "@openzeppelin/contracts/token/ERC1155/IERC1155.sol";
import "./base/TokenboundMech.sol";

/**
* @dev A Mech that is operated by the holder of a designated ERC1155 token
*/
contract ERC1155TokenboundMech is TokenboundMech {
function isOperator(address signer) public view override returns (bool) {
(uint256 chainId, address tokenContract, uint256 tokenId) = this
.token();
if (chainId != block.chainid) return false;
return IERC1155(tokenContract).balanceOf(signer, tokenId) > 0;
}

function onERC1155Received(
address,
address from,
uint256 receivedTokenId,
uint256,
bytes calldata
) external view override returns (bytes4) {
(
uint256 chainId,
address boundTokenContract,
uint256 boundTokenId
) = this.token();

if (
chainId == block.chainid &&
msg.sender == boundTokenContract &&
receivedTokenId == boundTokenId
) {
// We block the transfer only if the sender has no balance left after the transfer.
// Note: ERC-1155 prescribes that balances are updated BEFORE the call to onERC1155Received.
if (
IERC1155(boundTokenContract).balanceOf(from, boundTokenId) == 0
) {
revert OwnershipCycle();
}
}

return 0xf23a6e61;
}

function onERC1155BatchReceived(
address,
address from,
uint256[] calldata ids,
uint256[] calldata,
bytes calldata
) external view override returns (bytes4) {
(
uint256 chainId,
address boundTokenContract,
uint256 boundTokenId
) = this.token();

if (chainId == block.chainid && msg.sender == boundTokenContract) {
// We block the transfer only if the sender has no balance left after the transfer.
// Note: ERC-1155 prescribes that balances are updated BEFORE the call to onERC1155BatchReceived.
for (uint256 i = 0; i < ids.length; i++) {
if (ids[i] == boundTokenId) {
if (
IERC1155(boundTokenContract).balanceOf(
from,
boundTokenId
) == 0
) {
revert OwnershipCycle();
}
}
}
}

return 0xbc197c81;
}
}
29 changes: 29 additions & 0 deletions contracts/ERC20ThresholdMech.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
//SPDX-License-Identifier: LGPL-3.0
pragma solidity ^0.8.12;

import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "./base/ThresholdMech.sol";
import "./libraries/MinimalProxyStore.sol";

/**
* @dev A Mech that is operated by any holder of a minimum ERC20 token balance
*/
contract ERC20ThresholdMech is ThresholdMech {
function threshold()
public
view
returns (address token, uint256 minBalance)
{
return
abi.decode(
MinimalProxyStore.getContext(address(this)),
(address, uint256)
);
}

function isOperator(address signer) public view override returns (bool) {
(address token, uint256 minBalance) = this.threshold();

return IERC20(token).balanceOf(signer) >= minBalance;
}
}
41 changes: 0 additions & 41 deletions contracts/ERC721Mech.sol

This file was deleted.

Loading
Loading