Skip to content

Commit

Permalink
Merge pull request #2 from openfort-xyz/development
Browse files Browse the repository at this point in the history
First working version of Managed Accounts
  • Loading branch information
eloi010 authored May 31, 2023
2 parents 29d18b6 + da77686 commit cb0f56e
Show file tree
Hide file tree
Showing 20 changed files with 1,583 additions and 116 deletions.
3 changes: 0 additions & 3 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,3 @@ broadcast/

/.DS_Store
.DS_Store

contracts/mock/*
script/deployMock.sol
3 changes: 3 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"solidity.compileUsingRemoteVersion": "v0.8.19+commit.7dd6d404"
}
4 changes: 4 additions & 0 deletions contracts/core/BaseOpenfortAccount.sol
Original file line number Diff line number Diff line change
Expand Up @@ -372,4 +372,8 @@ abstract contract BaseOpenfortAccount is
emit SessionKeyRevoked(_key);
}
}

function version() external pure virtual returns (uint256) {
return 1;
}
}
25 changes: 25 additions & 0 deletions contracts/core/managed/ManagedOpenfortAccount.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.12;

// Base account contract to inherit from
import {BaseOpenfortAccount} from "../BaseOpenfortAccount.sol";

/**
* @title ManagedOpenfortAccount (Upgradeable via Beacon)
* @author Eloi<[email protected]>
* @notice Smart contract wallet managed via Beacon with session keys following the ERC-4337 standard.
* It inherits from:
* - BaseOpenfortAccount
*/
contract ManagedOpenfortAccount is BaseOpenfortAccount {
/*
* @notice Initialize the smart contract wallet.
*/
function initialize(address _defaultAdmin, address _entrypoint, bytes calldata) public override initializer {
if (_defaultAdmin == address(0) || _entrypoint == address(0)) {
revert ZeroAddressNotAllowed();
}
_transferOwnership(_defaultAdmin);
entrypointContract = _entrypoint;
}
}
116 changes: 116 additions & 0 deletions contracts/core/managed/ManagedOpenfortFactory.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.12;

import {BeaconProxy} from "@openzeppelin/contracts/proxy/beacon/BeaconProxy.sol";
import {Create2} from "@openzeppelin/contracts/utils/Create2.sol";
// Smart wallet implementation to use
import {ManagedOpenfortAccount} from "./ManagedOpenfortAccount.sol";
import {OpenfortBeacon} from "./OpenfortBeacon.sol";
// Interfaces
import {IBaseOpenfortFactory} from "../../interfaces/IBaseOpenfortFactory.sol";

/**
* @title ManagedOpenfortFactory (Non-upgradeable)
* @author Eloi<[email protected]>
* @notice Contract to create an on-chain factory to deploy new ManagedOpenfortAccounts.
* It uses OpenZeppelin's Create2 and BeaconProxy libraries.
* It inherits from:
* - IBaseOpenfortFactory
*/
contract ManagedOpenfortFactory is IBaseOpenfortFactory {
address public immutable entrypointContract;
address public immutable openfortBeacon;

constructor(address _entrypoint, address _openfortBeacon) {
if (_entrypoint == address(0) || _openfortBeacon == address(0)) {
revert ZeroAddressNotAllowed();
}
entrypointContract = _entrypoint;
openfortBeacon = _openfortBeacon;
}

/*
* @notice Deploy a new Account for _admin.
*/
function createAccount(address _admin, bytes calldata _data) external returns (address account) {
bytes32 salt = keccak256(abi.encode(_admin));
account = getAddress(_admin);

if (account.code.length > 0) {
return account;
}

emit AccountCreated(account, _admin);
account = address(
new BeaconProxy{salt: salt}(
openfortBeacon,
abi.encodeCall(ManagedOpenfortAccount.initialize, (_admin, entrypointContract, _data))
)
);
}

/*
* @notice Deploy a new account for _admin with a nonce.
*/
function createAccountWithNonce(address _admin, bytes calldata _data, uint256 nonce)
external
returns (address account)
{
bytes32 salt = keccak256(abi.encode(_admin, nonce));
account = getAddressWithNonce(_admin, nonce);

if (account.code.length > 0) {
return account;
}

emit AccountCreated(account, _admin);
account = address(
new BeaconProxy{salt: salt}(
openfortBeacon,
abi.encodeCall(ManagedOpenfortAccount.initialize, (_admin, entrypointContract, _data))
)
);
}

/*
* @notice Return the address of an account that would be deployed with the given admin signer.
*/
function getAddress(address _admin) public view returns (address) {
bytes32 salt = keccak256(abi.encode(_admin));
return Create2.computeAddress(
bytes32(salt),
keccak256(
abi.encodePacked(
type(BeaconProxy).creationCode,
abi.encode(
openfortBeacon,
abi.encodeCall(ManagedOpenfortAccount.initialize, (_admin, entrypointContract, ""))
)
)
)
);
}

/*
* @notice Return the address of an account that would be deployed with the given admin signer and nonce.
*/
function getAddressWithNonce(address _admin, uint256 nonce) public view returns (address) {
bytes32 salt = keccak256(abi.encode(_admin, nonce));
return Create2.computeAddress(
bytes32(salt),
keccak256(
abi.encodePacked(
type(BeaconProxy).creationCode,
abi.encode(
openfortBeacon,
abi.encodeCall(ManagedOpenfortAccount.initialize, (_admin, entrypointContract, ""))
)
)
)
);
}

function accountImplementation() external view override returns (address) {
return OpenfortBeacon(openfortBeacon).implementation();
}
}
15 changes: 15 additions & 0 deletions contracts/core/managed/OpenfortBeacon.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.12;

import {UpgradeableBeacon} from "@openzeppelin/contracts/proxy/beacon/UpgradeableBeacon.sol";

/**
* @title OpenfortBeacon (Non-upgradeable)
* @author Eloi<[email protected]>
* @notice Contract to create the Beacon to determine implementation contract, which is where they will delegate all function calls.
* It inherits from:
* - UpgradeableBeacon
*/
contract OpenfortBeacon is UpgradeableBeacon {
constructor(address implementation_) UpgradeableBeacon(implementation_) {}
}
6 changes: 3 additions & 3 deletions contracts/core/static/StaticOpenfortFactory.sol
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,12 @@ contract StaticOpenfortFactory is IBaseOpenfortFactory {
address public immutable entrypointContract;
address public immutable accountImplementation;

constructor(address _entrypoint) {
if (_entrypoint == address(0)) {
constructor(address _entrypoint, address _accountImplementation) {
if (_entrypoint == address(0) || _accountImplementation == address(0)) {
revert ZeroAddressNotAllowed();
}
entrypointContract = _entrypoint;
accountImplementation = address(new StaticOpenfortAccount());
accountImplementation = _accountImplementation;
}

/*
Expand Down
26 changes: 9 additions & 17 deletions contracts/core/upgradeable/UpgradeableOpenfortFactory.sol
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,12 @@ contract UpgradeableOpenfortFactory is IBaseOpenfortFactory {
address public immutable entrypointContract;
address public immutable accountImplementation;

constructor(address _entrypoint) {
if (_entrypoint == address(0)) {
constructor(address _entrypoint, address _accountImplementation) {
if (_entrypoint == address(0) || _accountImplementation == address(0)) {
revert ZeroAddressNotAllowed();
}
entrypointContract = _entrypoint;
accountImplementation = address(new UpgradeableOpenfortAccount());
accountImplementation = _accountImplementation;
}

/*
Expand All @@ -41,13 +41,9 @@ contract UpgradeableOpenfortFactory is IBaseOpenfortFactory {

emit AccountCreated(account, _admin);
account = address(
UpgradeableOpenfortAccount(
payable(
new ERC1967Proxy{salt : bytes32(salt)}(
address(accountImplementation),
abi.encodeCall(UpgradeableOpenfortAccount.initialize, (_admin, entrypointContract, _data))
)
)
new ERC1967Proxy{salt: salt}(
accountImplementation,
abi.encodeCall(UpgradeableOpenfortAccount.initialize, (_admin, entrypointContract, _data))
)
);
}
Expand All @@ -68,13 +64,9 @@ contract UpgradeableOpenfortFactory is IBaseOpenfortFactory {

emit AccountCreated(account, _admin);
account = address(
UpgradeableOpenfortAccount(
payable(
new ERC1967Proxy{salt : bytes32(salt)}(
address(accountImplementation),
abi.encodeCall(UpgradeableOpenfortAccount.initialize, (_admin, entrypointContract, _data))
)
)
new ERC1967Proxy{salt: salt}(
accountImplementation,
abi.encodeCall(UpgradeableOpenfortAccount.initialize, (_admin, entrypointContract, _data))
)
);
}
Expand Down
29 changes: 29 additions & 0 deletions contracts/mock/MockedV2ManagedOpenfortAccount.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.12;

// Base account contract to inherit from
import {BaseOpenfortAccount} from "../core/BaseOpenfortAccount.sol";

/**
* @title ManagedOpenfortAccount (Upgradeable via Beacon)
* @author Eloi<[email protected]>
* @notice Smart contract wallet managed via Beacon with session keys following the ERC-4337 standard.
* It inherits from:
* - BaseOpenfortAccount
*/
contract MockedV2ManagedOpenfortAccount is BaseOpenfortAccount {
/*
* @notice Initialize the smart contract wallet.
*/
function initialize(address _defaultAdmin, address _entrypoint, bytes calldata) public override initializer {
if (_defaultAdmin == address(0) || _entrypoint == address(0)) {
revert ZeroAddressNotAllowed();
}
_transferOwnership(_defaultAdmin);
entrypointContract = _entrypoint;
}

function version() external pure override returns (uint256) {
return 2;
}
}
15 changes: 15 additions & 0 deletions contracts/mock/Rewards.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.12;

import "@openzeppelin/contracts/token/ERC20/ERC20.sol";

contract Rewards is ERC20 {
constructor()
// solhint-disable-next-line no-empty-blocks
ERC20("GEMS", "GEMS")
{}

function claim() external {
_mint(msg.sender, 10);
}
}
15 changes: 15 additions & 0 deletions contracts/mock/USDC.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.12;

import "@openzeppelin/contracts/token/ERC20/ERC20.sol";

contract USDC is ERC20 {
constructor()
// solhint-disable-next-line no-empty-blocks
ERC20("USDC", "USDC")
{}

function mint(address sender, uint256 amount) external {
_mint(sender, amount);
}
}
51 changes: 51 additions & 0 deletions script/deployManagedAccounts.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.12;

import {Script, console} from "forge-std/Script.sol";
import {IEntryPoint} from "lib/account-abstraction/contracts/interfaces/IEntryPoint.sol";
import {ManagedOpenfortAccount} from "../contracts/core/managed/ManagedOpenfortAccount.sol";
import {OpenfortBeacon} from "contracts/core/managed/OpenfortBeacon.sol";
import {ManagedOpenfortFactory} from "../contracts/core/managed/ManagedOpenfortFactory.sol";
import {MockedV2ManagedOpenfortAccount} from "../contracts/mock/MockedV2ManagedOpenfortAccount.sol";

contract ManagedOpenfortDeploy 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"))));

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

// Create an acccount to server as implementation
ManagedOpenfortAccount managedOpenfortAccount = new ManagedOpenfortAccount{salt: versionSalt}();

OpenfortBeacon openfortBeacon = new OpenfortBeacon(address(managedOpenfortAccount));

// Create a factory to deploy cloned accounts
ManagedOpenfortFactory managedOpenfortFactory = new ManagedOpenfortFactory{salt: versionSalt}(address(entryPoint), address(openfortBeacon));
// address account1 = managedOpenfortFactory.accountImplementation();

// The first call should create a new account, while the second will just return the corresponding account address
address account2 = managedOpenfortFactory.createAccount(deployAddress, bytes(""));
console.log(
"Factory at address %s has created an account at address %s", address(managedOpenfortFactory), account2
);

MockedV2ManagedOpenfortAccount mockedOpenfortAccount = new MockedV2ManagedOpenfortAccount{salt: versionSalt}();

// assert(account1 != account2);
// address account3 = managedOpenfortFactory.createAccountWithNonce(deployAddress, "", 3);
// console.log(
// "Factory at address %s has created an account at address %s", address(managedOpenfortFactory), account3
// );
// assert(account2 != account3);
// address account4 = managedOpenfortFactory.createAccountWithNonce(deployAddress, "", 4);
// console.log(
// "Factory at address %s has created an account at address %s", address(managedOpenfortFactory), account4
// );
// assert(account3 != account4);

vm.stopBroadcast();
}
}
23 changes: 23 additions & 0 deletions script/deployMock.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.12;

import {Script, console} from "forge-std/Script.sol";
import {IEntryPoint} from "lib/account-abstraction/contracts/interfaces/IEntryPoint.sol";
import {StaticOpenfortFactory} from "../contracts/core/static/StaticOpenfortFactory.sol";
// import {USDC} from "../contracts/mock/USDC.sol";
import {Rewards} from "../contracts/mock/Rewards.sol";

contract DeployMock 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"))));

function run() public {
vm.startBroadcast(deployPrivKey);

// USDC u = new USDC();
Rewards r = new Rewards();

vm.stopBroadcast();
}
}
Loading

0 comments on commit cb0f56e

Please sign in to comment.