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

Adding support for ERC6551 accounts #6

Merged
merged 6 commits into from
Jul 19, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
469 changes: 469 additions & 0 deletions contracts/core/eip6551/EIP6551OpenfortAccount.sol

Large diffs are not rendered by default.

62 changes: 62 additions & 0 deletions contracts/core/eip6551/ERC6551Registry.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;

/* solhint-disable reason-string */

import "@openzeppelin/contracts/utils/Create2.sol";
import "./interfaces/IERC6551Registry.sol";

contract ERC6551Registry is IERC6551Registry {
error InitializationFailed();

function createAccount(
address implementation,
uint256 chainId,
address tokenContract,
uint256 tokenId,
uint256 salt,
bytes calldata initData
) external returns (address) {
bytes memory code = _creationCode(implementation, chainId, tokenContract, tokenId, salt);

address _account = Create2.computeAddress(bytes32(salt), keccak256(code));

if (_account.code.length != 0) return _account;

_account = Create2.deploy(0, bytes32(salt), code);

if (initData.length != 0) {
(bool success,) = _account.call(initData);
if (!success) revert InitializationFailed();
}

emit AccountCreated(_account, implementation, chainId, tokenContract, tokenId, salt);

return _account;
}

function account(address implementation, uint256 chainId, address tokenContract, uint256 tokenId, uint256 salt)
external
view
returns (address)
{
bytes32 bytecodeHash = keccak256(_creationCode(implementation, chainId, tokenContract, tokenId, salt));

return Create2.computeAddress(bytes32(salt), bytecodeHash);
}

function _creationCode(
address implementation_,
uint256 chainId_,
address tokenContract_,
uint256 tokenId_,
uint256 salt_
) internal pure returns (bytes memory) {
return abi.encodePacked(
hex"3d60ad80600a3d3981f3363d3d373d3d3d363d73",
implementation_,
hex"5af43d82803e903d91602b57fd5bf3",
abi.encode(salt_, chainId_, tokenContract_, tokenId_)
);
}
}
52 changes: 52 additions & 0 deletions contracts/core/eip6551/interfaces/IERC6551Account.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;

/// @dev the ERC-165 identifier for this interface is `0x400a0398`
interface IERC6551Account {
/// @dev Token bound accounts MUST implement a `receive` function.
///
/// Token bound accounts MAY perform arbitrary logic to restrict conditions
/// under which Ether can be received.
receive() external payable;

/// @dev Executes `call` on address `to`, with value `value` and calldata
/// `data`.
///
/// MUST revert and bubble up errors if call fails.
///
/// By default, token bound accounts MUST allow the owner of the ERC-721 token
/// which owns the account to execute arbitrary calls using `executeCall`.
///
/// Token bound accounts MAY implement additional authorization mechanisms
/// which limit the ability of the ERC-721 token holder to execute calls.
///
/// Token bound accounts MAY implement additional execution functions which
/// grant execution permissions to other non-owner accounts.
///
/// @return The result of the call
function executeCall(address to, uint256 value, bytes calldata data) external payable returns (bytes memory);

/// @dev Returns identifier of the ERC-721 token which owns the
/// account
///
/// The return value of this function MUST be constant - it MUST NOT change
/// over time.
///
/// @return chainId The EIP-155 ID of the chain the ERC-721 token exists on
/// @return tokenContract The contract address of the ERC-721 token
/// @return tokenId The ID of the ERC-721 token
function token() external view returns (uint256 chainId, address tokenContract, uint256 tokenId);

/// @dev Returns the owner of the ERC-721 token which controls the account
/// if the token exists.
///
/// This is value is obtained by calling `ownerOf` on the ERC-721 contract.
///
/// @return Address of the owner of the ERC-721 token which owns the account
function owner() external view returns (address);

/// @dev Returns a nonce value that is updated on every successful transaction
///
/// @return The current account nonce
function nonce() external view returns (uint256);
}
36 changes: 36 additions & 0 deletions contracts/core/eip6551/interfaces/IERC6551Registry.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;

interface IERC6551Registry {
/// @dev The registry SHALL emit the AccountCreated event upon successful account creation
event AccountCreated(
address account, address implementation, uint256 chainId, address tokenContract, uint256 tokenId, uint256 salt
);

/// @dev Creates a token bound account for an ERC-721 token.
///
/// If account has already been created, returns the account address without calling create2.
///
/// If initData is not empty and account has not yet been created, calls account with
/// provided initData after creation.
///
/// Emits AccountCreated event.
///
/// @return the address of the account
function createAccount(
address implementation,
uint256 chainId,
address tokenContract,
uint256 tokenId,
uint256 salt,
bytes calldata initData
) external returns (address);

/// @dev Returns the computed address of a token bound account
///
/// @return The computed address of the account
function account(address implementation, uint256 chainId, address tokenContract, uint256 tokenId, uint256 salt)
external
view
returns (address);
}
74 changes: 74 additions & 0 deletions contracts/core/eip6551/utils/Bytecode.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;

library Bytecode {
error InvalidCodeAtRange(uint256 _size, uint256 _start, uint256 _end);

/**
* @notice Generate a creation code that results on a contract with `_code` as bytecode
* @param _code The returning value of the resulting `creationCode`
* @return creationCode (constructor) for new contract
*/
function creationCodeFor(bytes memory _code) internal pure returns (bytes memory) {
/*
0x00 0x63 0x63XXXXXX PUSH4 _code.length size
0x01 0x80 0x80 DUP1 size size
0x02 0x60 0x600e PUSH1 14 14 size size
0x03 0x60 0x6000 PUSH1 00 0 14 size size
0x04 0x39 0x39 CODECOPY size
0x05 0x60 0x6000 PUSH1 00 0 size
0x06 0xf3 0xf3 RETURN
<CODE>
*/

return abi.encodePacked(hex"63", uint32(_code.length), hex"80600E6000396000F3", _code);
}

/**
* @notice Returns the size of the code on a given address
* @param _addr Address that may or may not contain code
* @return size of the code on the given `_addr`
*/
function codeSize(address _addr) internal view returns (uint256 size) {
assembly {
size := extcodesize(_addr)
}
}

/**
* @notice Returns the code of a given address
* @dev It will fail if `_end < _start`
* @param _addr Address that may or may not contain code
* @param _start number of bytes of code to skip on read
* @param _end index before which to end extraction
* @return oCode read from `_addr` deployed bytecode
*
* Forked from: https://gist.github.com/KardanovIR/fe98661df9338c842b4a30306d507fbd
*/
function codeAt(address _addr, uint256 _start, uint256 _end) internal view returns (bytes memory oCode) {
uint256 csize = codeSize(_addr);
if (csize == 0) return bytes("");

if (_start > csize) return bytes("");
if (_end < _start) revert InvalidCodeAtRange(csize, _start, _end);

unchecked {
uint256 reqSize = _end - _start;
uint256 maxSize = csize - _start;

uint256 size = maxSize < reqSize ? maxSize : reqSize;

assembly {
// allocate output byte array - this could also be done without assembly
// by using o_code = new bytes(size)
oCode := mload(0x40)
// new "memory end" including padding
mstore(0x40, add(oCode, and(add(add(size, 0x20), 0x1f), not(0x1f))))
// store length in memory
mstore(oCode, size)
// actually retrieve the code, this needs assembly
extcodecopy(_addr, add(oCode, 0x20), _start, size)
}
}
}
}
4 changes: 4 additions & 0 deletions contracts/core/managed/OpenfortBeaconProxy.sol
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,8 @@ import {BeaconProxy} from "@openzeppelin/contracts/proxy/beacon/BeaconProxy.sol"
*/
contract OpenfortBeaconProxy is BeaconProxy {
constructor(address beacon, bytes memory data) BeaconProxy(beacon, data) {}

function implementation() external view returns (address) {
return _implementation();
}
}
4 changes: 4 additions & 0 deletions contracts/core/upgradeable/OpenfortUpgradeableProxy.sol
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,8 @@ import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.s
*/
contract OpenfortUpgradeableProxy is ERC1967Proxy {
constructor(address _logic, bytes memory _data) ERC1967Proxy(_logic, _data) {}

function implementation() external view returns (address) {
return _getImplementation();
}
}
15 changes: 15 additions & 0 deletions contracts/mock/VipNFT.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.19;

import {ERC721} from "@openzeppelin/contracts/token/ERC721/ERC721.sol";

contract VIPNFT is ERC721 {
constructor()
// solhint-disable-next-line no-empty-blocks
ERC721("VIP", "VIP")
{}

function mint(address to, uint256 amount) external {
_safeMint(to, amount);
}
}
24 changes: 12 additions & 12 deletions script/deployAllChains.sh
Original file line number Diff line number Diff line change
Expand Up @@ -19,21 +19,21 @@ LOG_FILE=script/deployments/$(date +%Y-%m-%d_%H:%M)"-deploymentAllChains.log"
# forge script StaticOpenfortDeploy --rpc-url $ARBITRUM_GOERLI_RPC -vvvv --verify --broadcast --slow --etherscan-api-key $ARBISCAN_API_KEY >> $LOG_FILE
# sleep 3

echo "------ UpgradeableOpenfortDeploy ------ (Goerli)"
forge script UpgradeableOpenfortDeploy --rpc-url $GOERLI_RPC -vvvv --verify --broadcast --slow --etherscan-api-key $GOERLI_API_KEY >> $LOG_FILE
sleep 3
echo "------ UpgradeableOpenfortDeploy ------ (Mumbai)"
forge script UpgradeableOpenfortDeploy --rpc-url $POLYGON_MUMBAI_RPC -vvvv --verify --broadcast --slow --legacy --etherscan-api-key $POLYGON_MUMBAI_KEY >> $LOG_FILE
sleep 3
# echo "------ UpgradeableOpenfortDeploy ------ (Goerli)"
# forge script UpgradeableOpenfortDeploy --rpc-url $GOERLI_RPC -vvvv --verify --broadcast --slow -g 150 --etherscan-api-key $GOERLI_API_KEY >> $LOG_FILE
# sleep 3
# echo "------ UpgradeableOpenfortDeploy ------ (Mumbai)"
# forge script UpgradeableOpenfortDeploy --rpc-url $POLYGON_MUMBAI_RPC -vvvv --verify --broadcast --slow --legacy --etherscan-api-key $POLYGON_MUMBAI_KEY >> $LOG_FILE
# sleep 3
echo "------ UpgradeableOpenfortDeploy ------ (Fuji)"
forge script UpgradeableOpenfortDeploy --rpc-url $AVALANCHE_FUJI_RPC -vvvv --verify --broadcast --slow --etherscan-api-key $FUJI_API_KEY >> $LOG_FILE
sleep 3
echo "------ UpgradeableOpenfortDeploy ------ (BSC testnet)"
forge script UpgradeableOpenfortDeploy --rpc-url $BSC_TESTNET_RPC -vvvv --verify --broadcast --slow --etherscan-api-key $BSCSCAN_TESTNET_API_KEY >> $LOG_FILE
sleep 3
echo "------ UpgradeableOpenfortDeploy ------ (Arbitrum Goerli testnet)"
forge script UpgradeableOpenfortDeploy --rpc-url $ARBITRUM_GOERLI_RPC -vvvv --verify --broadcast --slow -g 200 --etherscan-api-key $ARBISCAN_API_KEY >> $LOG_FILE
sleep 3
# echo "------ UpgradeableOpenfortDeploy ------ (BSC testnet)"
# forge script UpgradeableOpenfortDeploy --rpc-url $BSC_TESTNET_RPC -vvvv --verify --broadcast --slow --etherscan-api-key $BSCSCAN_TESTNET_API_KEY >> $LOG_FILE
# sleep 3
# echo "------ UpgradeableOpenfortDeploy ------ (Arbitrum Goerli testnet)"
# forge script UpgradeableOpenfortDeploy --rpc-url $ARBITRUM_GOERLI_RPC -vvvv --verify --broadcast --slow -g 200 --etherscan-api-key $ARBISCAN_API_KEY >> $LOG_FILE
# sleep 3

# echo "------ ManagedOpenfortDeploy ------ (Goerli)"
# forge script ManagedOpenfortDeploy --rpc-url $GOERLI_RPC -vvvv --verify --broadcast --slow --etherscan-api-key $GOERLI_API_KEY >> $LOG_FILE
Expand Down
47 changes: 47 additions & 0 deletions script/deployEIP6551.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;

import {Script, console} from "forge-std/Script.sol";
import {IEntryPoint} from "lib/account-abstraction/contracts/interfaces/IEntryPoint.sol";
import {VIPNFT} from "contracts/mock/VipNFT.sol";
import {EIP6551OpenfortAccount} from "contracts/core/eip6551/EIP6551OpenfortAccount.sol";
import {ERC6551Registry} from "../contracts/core/eip6551/ERC6551Registry.sol";

contract EIP6551OpenfortDeploy is Script {
uint256 internal deployPrivKey = vm.deriveKey(vm.envString("MNEMONIC"), 0);
address internal deployAddress = vm.addr(deployPrivKey);
IEntryPoint internal entryPoint = IEntryPoint((payable(vm.envAddress("ENTRY_POINT_ADDRESS"))));
VIPNFT testToken;

function run() public {
bytes32 versionSalt = vm.envBytes32("VERSION_SALT");
vm.startBroadcast(deployPrivKey);

// Create an acccount to serve as implementation
EIP6551OpenfortAccount eip6551OpenfortAccount = new EIP6551OpenfortAccount{salt: versionSalt}();

// Create a factory to deploy cloned accounts
ERC6551Registry erc6551Registry = new ERC6551Registry{salt: versionSalt}();

uint256 chainId;
assembly {
chainId := chainid()
}

// deploy a new VIPNFT collection
testToken = new VIPNFT();

// The first call should create a new account, while the second will just return the corresponding account address
address account2 = erc6551Registry.createAccount(
address(eip6551OpenfortAccount),
chainId,
address(testToken),
1,
1,
abi.encodeWithSignature("initialize(address)", address(entryPoint))
);
console.log("Registry at address %s has created an account at address %s", address(erc6551Registry), account2);

vm.stopBroadcast();
}
}
Loading