Skip to content
15 changes: 14 additions & 1 deletion contracts/smart-wallet/TWAccount.sol
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,19 @@ import "@openzeppelin/contracts/token/ERC1155/utils/ERC1155Holder.sol";
// Utils
import "../openzeppelin-presets/utils/cryptography/ECDSA.sol";

// $$\ $$\ $$\ $$\ $$\
// $$ | $$ | \__| $$ | $$ |
// $$$$$$\ $$$$$$$\ $$\ $$$$$$\ $$$$$$$ |$$\ $$\ $$\ $$$$$$\ $$$$$$$\
// \_$$ _| $$ __$$\ $$ |$$ __$$\ $$ __$$ |$$ | $$ | $$ |$$ __$$\ $$ __$$\
// $$ | $$ | $$ |$$ |$$ | \__|$$ / $$ |$$ | $$ | $$ |$$$$$$$$ |$$ | $$ |
// $$ |$$\ $$ | $$ |$$ |$$ | $$ | $$ |$$ | $$ | $$ |$$ ____|$$ | $$ |
// \$$$$ |$$ | $$ |$$ |$$ | \$$$$$$$ |\$$$$$\$$$$ |\$$$$$$$\ $$$$$$$ |
// \____/ \__| \__|\__|\__| \_______| \_____\____/ \_______|\_______/

/*///////////////////////////////////////////////////////////////
Storage layout
//////////////////////////////////////////////////////////////*/

library TWAccountStorage {
bytes32 internal constant TWACCOUNT_STORAGE_POSITION = keccak256("twaccount.storage");

Expand Down Expand Up @@ -46,7 +59,7 @@ contract TWAccount is
using ECDSA for bytes32;

/*///////////////////////////////////////////////////////////////
State (constant, immutable)
State
//////////////////////////////////////////////////////////////*/

bytes32 public constant SIGNER_ROLE = keccak256("SIGNER_ROLE");
Expand Down
110 changes: 98 additions & 12 deletions contracts/smart-wallet/TWAccountFactory.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,45 +4,131 @@ pragma solidity ^0.8.12;
// Utils
import "../extension/Multicall.sol";
import "@openzeppelin/contracts/proxy/Clones.sol";
import "../lib/TWStringSet.sol";

// Interface
import "./interfaces/ITWAccountFactory.sol";

// Smart wallet implementation
import "./TWAccount.sol";

// $$\ $$\ $$\ $$\ $$\
// $$ | $$ | \__| $$ | $$ |
// $$$$$$\ $$$$$$$\ $$\ $$$$$$\ $$$$$$$ |$$\ $$\ $$\ $$$$$$\ $$$$$$$\
// \_$$ _| $$ __$$\ $$ |$$ __$$\ $$ __$$ |$$ | $$ | $$ |$$ __$$\ $$ __$$\
// $$ | $$ | $$ |$$ |$$ | \__|$$ / $$ |$$ | $$ | $$ |$$$$$$$$ |$$ | $$ |
// $$ |$$\ $$ | $$ |$$ |$$ | $$ | $$ |$$ | $$ | $$ |$$ ____|$$ | $$ |
// \$$$$ |$$ | $$ |$$ |$$ | \$$$$$$$ |\$$$$$\$$$$ |\$$$$$$$\ $$$$$$$ |
// \____/ \__| \__|\__|\__| \_______| \_____\____/ \_______|\_______/

/*///////////////////////////////////////////////////////////////
Storage layout
//////////////////////////////////////////////////////////////*/

library TWAccountFactoryStorage {
bytes32 internal constant TWACCOUNT_FACTORY_STORAGE_POSITION = keccak256("twaccount.factory.storage");

struct Data {
TWStringSet.Set allAccounts;
mapping(address => TWStringSet.Set) accountsOfSigner;
}

function factoryStorage() internal pure returns (Data storage twaccountFactoryData) {
bytes32 position = TWACCOUNT_FACTORY_STORAGE_POSITION;
assembly {
twaccountFactoryData.slot := position
}
}
}

contract TWAccountFactory is ITWAccountFactory, Multicall {
using TWStringSet for TWStringSet.Set;

/*///////////////////////////////////////////////////////////////
State
//////////////////////////////////////////////////////////////*/

TWAccount private immutable _accountImplementation;

/*///////////////////////////////////////////////////////////////
Constructor
//////////////////////////////////////////////////////////////*/

constructor(IEntryPoint _entrypoint) {
_accountImplementation = new TWAccount(_entrypoint);
}

/// @notice Returns the implementation of the Account.
function accountImplementation() external view override returns (address) {
return address(_accountImplementation);
}
/*///////////////////////////////////////////////////////////////
External functions
//////////////////////////////////////////////////////////////*/

/// @notice Deploys a new Account with the given admin and salt.
function createAccount(address _admin, bytes32 _salt) external returns (address) {
/// @notice Deploys a new Account with the given admin and accountId used as salt.
function createAccount(address _admin, string memory _accountId) external returns (address) {
address impl = address(_accountImplementation);
address account = Clones.predictDeterministicAddress(impl, _salt);
bytes32 salt = keccak256(abi.encode(_accountId));
address account = Clones.predictDeterministicAddress(impl, salt);

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

account = Clones.cloneDeterministic(impl, _salt);
account = Clones.cloneDeterministic(impl, salt);

TWAccount(payable(account)).initialize(_admin);

emit AccountCreated(account, _admin, _salt);
_setupAccount(_admin, _accountId);

emit AccountCreated(account, _admin, _accountId);

return account;
}

/// @notice Returns the address of an Account that would be deployed with the given salt.
function getAddress(bytes32 _salt) external view returns (address) {
return Clones.predictDeterministicAddress(address(_accountImplementation), _salt);
/*///////////////////////////////////////////////////////////////
View functions
//////////////////////////////////////////////////////////////*/

/// @notice Returns the implementation of the Account.
function accountImplementation() external view override returns (address) {
return address(_accountImplementation);
}

/// @notice Returns the address of an Account that would be deployed with the given accountId as salt.
function getAddress(string memory _accountId) public view returns (address) {
bytes32 salt = keccak256(abi.encode(_accountId));
return Clones.predictDeterministicAddress(address(_accountImplementation), salt);
}

/// @notice Returns the list of accounts created by a signer.
function getAccountsOfSigner(address _signer) external view returns (AccountInfo[] memory) {
TWAccountFactoryStorage.Data storage data = TWAccountFactoryStorage.factoryStorage();
return _formatAccounts(data.accountsOfSigner[_signer].values());
}

/// @notice Returns the list of all accounts.
function getAllAccounts() external view returns (AccountInfo[] memory accounts) {
TWAccountFactoryStorage.Data storage data = TWAccountFactoryStorage.factoryStorage();
return _formatAccounts(data.allAccounts.values());
}

/*///////////////////////////////////////////////////////////////
Internal functions
//////////////////////////////////////////////////////////////*/

/// @dev Formats a list of accountIds to a list of `AccountInfo` (account id + account address).
function _formatAccounts(string[] memory _accountIds) internal view returns (AccountInfo[] memory accounts) {
uint256 len = _accountIds.length;
accounts = new AccountInfo[](len);
for (uint256 i = 0; i < len; i += 1) {
string memory accountId = _accountIds[i];
address account = getAddress(accountId);
accounts[i] = AccountInfo(accountId, account);
}
}

/// @dev Adds an account to the list of accounts created by a signer.
function _setupAccount(address _signer, string memory _accountId) internal {
TWAccountFactoryStorage.Data storage data = TWAccountFactoryStorage.factoryStorage();
data.allAccounts.add(_accountId);
data.accountsOfSigner[_signer].add(_accountId);
}
}
9 changes: 9 additions & 0 deletions contracts/smart-wallet/TWDynamicAccount.sol
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,15 @@ pragma solidity ^0.8.11;
import "./TWAccount.sol";
import "./BaseRouter.sol";

// $$\ $$\ $$\ $$\ $$\
// $$ | $$ | \__| $$ | $$ |
// $$$$$$\ $$$$$$$\ $$\ $$$$$$\ $$$$$$$ |$$\ $$\ $$\ $$$$$$\ $$$$$$$\
// \_$$ _| $$ __$$\ $$ |$$ __$$\ $$ __$$ |$$ | $$ | $$ |$$ __$$\ $$ __$$\
// $$ | $$ | $$ |$$ |$$ | \__|$$ / $$ |$$ | $$ | $$ |$$$$$$$$ |$$ | $$ |
// $$ |$$\ $$ | $$ |$$ |$$ | $$ | $$ |$$ | $$ | $$ |$$ ____|$$ | $$ |
// \$$$$ |$$ | $$ |$$ |$$ | \$$$$$$$ |\$$$$$\$$$$ |\$$$$$$$\ $$$$$$$ |
// \____/ \__| \__|\__|\__| \_______| \_____\____/ \_______|\_______/

contract TWDynamicAccount is TWAccount, BaseRouter {
/*///////////////////////////////////////////////////////////////
Constants
Expand Down
111 changes: 99 additions & 12 deletions contracts/smart-wallet/TWDynamicAccountFactory.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,45 +4,132 @@ pragma solidity ^0.8.12;
// Utils
import "../extension/Multicall.sol";
import "@openzeppelin/contracts/proxy/Clones.sol";
import "../lib/TWStringSet.sol";

// Interface
import "./interfaces/ITWAccountFactory.sol";

// Smart wallet implementation
import "./TWDynamicAccount.sol";

// $$\ $$\ $$\ $$\ $$\
// $$ | $$ | \__| $$ | $$ |
// $$$$$$\ $$$$$$$\ $$\ $$$$$$\ $$$$$$$ |$$\ $$\ $$\ $$$$$$\ $$$$$$$\
// \_$$ _| $$ __$$\ $$ |$$ __$$\ $$ __$$ |$$ | $$ | $$ |$$ __$$\ $$ __$$\
// $$ | $$ | $$ |$$ |$$ | \__|$$ / $$ |$$ | $$ | $$ |$$$$$$$$ |$$ | $$ |
// $$ |$$\ $$ | $$ |$$ |$$ | $$ | $$ |$$ | $$ | $$ |$$ ____|$$ | $$ |
// \$$$$ |$$ | $$ |$$ |$$ | \$$$$$$$ |\$$$$$\$$$$ |\$$$$$$$\ $$$$$$$ |
// \____/ \__| \__|\__|\__| \_______| \_____\____/ \_______|\_______/

/*///////////////////////////////////////////////////////////////
Storage layout
//////////////////////////////////////////////////////////////*/

library TWDynamicAccountFactoryStorage {
bytes32 internal constant DYNAMIC_ACCOUNT_FACTORY_STORAGE_POSITION =
keccak256("tw.dynamic.account.factory.storage");

struct Data {
TWStringSet.Set allAccounts;
mapping(address => TWStringSet.Set) accountsOfSigner;
}

function factoryStorage() internal pure returns (Data storage dynamicAccountFactoryData) {
bytes32 position = DYNAMIC_ACCOUNT_FACTORY_STORAGE_POSITION;
assembly {
dynamicAccountFactoryData.slot := position
}
}
}

contract TWDynamicAccountFactory is ITWAccountFactory, Multicall {
using TWStringSet for TWStringSet.Set;

/*///////////////////////////////////////////////////////////////
State
//////////////////////////////////////////////////////////////*/

TWDynamicAccount private immutable _accountImplementation;

/*///////////////////////////////////////////////////////////////
Constructor
//////////////////////////////////////////////////////////////*/

constructor(IEntryPoint _entrypoint) {
_accountImplementation = new TWDynamicAccount(_entrypoint);
}

/// @notice Returns the implementation of the Account.
function accountImplementation() external view override returns (address) {
return address(_accountImplementation);
}
/*///////////////////////////////////////////////////////////////
External functions
//////////////////////////////////////////////////////////////*/

/// @notice Deploys a new Account with the given admin and salt.
function createAccount(address _admin, bytes32 _salt) external returns (address) {
/// @notice Deploys a new Account with the given admin and accountId used as salt.
function createAccount(address _admin, string memory _accountId) external returns (address) {
address impl = address(_accountImplementation);
address account = Clones.predictDeterministicAddress(impl, _salt);
bytes32 salt = keccak256(abi.encode(_accountId));
address account = Clones.predictDeterministicAddress(impl, salt);

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

account = Clones.cloneDeterministic(impl, _salt);
account = Clones.cloneDeterministic(impl, salt);

TWAccount(payable(account)).initialize(_admin);

emit AccountCreated(account, _admin, _salt);
_setupAccount(_admin, _accountId);

emit AccountCreated(account, _admin, _accountId);

return account;
}

/// @notice Returns the address of an Account that would be deployed with the given salt.
function getAddress(bytes32 _salt) external view returns (address) {
return Clones.predictDeterministicAddress(address(_accountImplementation), _salt);
/*///////////////////////////////////////////////////////////////
View functions
//////////////////////////////////////////////////////////////*/

/// @notice Returns the implementation of the Account.
function accountImplementation() external view override returns (address) {
return address(_accountImplementation);
}

/// @notice Returns the address of an Account that would be deployed with the given accountId as salt.
function getAddress(string memory _accountId) public view returns (address) {
bytes32 salt = keccak256(abi.encode(_accountId));
return Clones.predictDeterministicAddress(address(_accountImplementation), salt);
}

/// @notice Returns the list of accounts created by a signer.
function getAccountsOfSigner(address _signer) external view returns (AccountInfo[] memory) {
TWDynamicAccountFactoryStorage.Data storage data = TWDynamicAccountFactoryStorage.factoryStorage();
return _formatAccounts(data.accountsOfSigner[_signer].values());
}

/// @notice Returns the list of all accounts.
function getAllAccounts() external view returns (AccountInfo[] memory accounts) {
TWDynamicAccountFactoryStorage.Data storage data = TWDynamicAccountFactoryStorage.factoryStorage();
return _formatAccounts(data.allAccounts.values());
}

/*///////////////////////////////////////////////////////////////
Internal functions
//////////////////////////////////////////////////////////////*/

/// @dev Formats a list of accountIds to a list of `AccountInfo` (account id + account address).
function _formatAccounts(string[] memory _accountIds) internal view returns (AccountInfo[] memory accounts) {
uint256 len = _accountIds.length;
accounts = new AccountInfo[](len);
for (uint256 i = 0; i < len; i += 1) {
string memory accountId = _accountIds[i];
address account = getAddress(accountId);
accounts[i] = AccountInfo(accountId, account);
}
}

/// @dev Adds an account to the list of accounts created by a signer.
function _setupAccount(address _signer, string memory _accountId) internal {
TWDynamicAccountFactoryStorage.Data storage data = TWDynamicAccountFactoryStorage.factoryStorage();
data.allAccounts.add(_accountId);
data.accountsOfSigner[_signer].add(_accountId);
}
}
39 changes: 34 additions & 5 deletions contracts/smart-wallet/interfaces/ITWAccountFactory.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,43 @@
pragma solidity ^0.8.12;

interface ITWAccountFactory {
event AccountCreated(address indexed account, address indexed accountAdmin, bytes32 indexed salt);
/*///////////////////////////////////////////////////////////////
Structs
//////////////////////////////////////////////////////////////*/

/// @notice Smart account details: address and ID (used as salt).
struct AccountInfo {
string id;
address account;
}

/*///////////////////////////////////////////////////////////////
Events
//////////////////////////////////////////////////////////////*/

/// @notice Emitted when a new Account is created.
event AccountCreated(address indexed account, address indexed accountAdmin, string indexed accountId);

/*///////////////////////////////////////////////////////////////
Extension Functions
//////////////////////////////////////////////////////////////*/

/// @notice Deploys a new Account with the given admin and accountId used as salt.
function createAccount(address admin, string memory accountId) external returns (address account);

/*///////////////////////////////////////////////////////////////
View Functions
//////////////////////////////////////////////////////////////*/

/// @notice Returns the address of the Account implementation.
function accountImplementation() external view returns (address);

/// @notice Deploys a new Account with the given admin and salt.
function createAccount(address admin, bytes32 salt) external returns (address account);
/// @notice Returns the address of an Account that would be deployed with the given accountId as salt.
function getAddress(string memory accountId) external view returns (address);

/// @notice Returns the list of accounts created by a signer.
function getAccountsOfSigner(address _signer) external view returns (AccountInfo[] memory);

/// @notice Returns the address of an Account that would be deployed with the given salt.
function getAddress(bytes32 salt) external view returns (address);
/// @notice Returns the list of all accounts.
function getAllAccounts() external view returns (AccountInfo[] memory);
}
Loading