124 lines
5.5 KiB
Solidity
124 lines
5.5 KiB
Solidity
// SPDX-License-Identifier: MIT
|
|
pragma solidity ^0.8.20;
|
|
|
|
import "@openzeppelin/contracts/access/AccessControl.sol";
|
|
import "../emoney/interfaces/IAccountWalletRegistry.sol";
|
|
|
|
/**
|
|
* @title AccountWalletRegistryExtended
|
|
* @notice Extends account-wallet registry with Smart Account (contract) support
|
|
* @dev Links accountRefId to contract addresses (smart accounts); walletRefId = keccak256(abi.encodePacked(smartAccount))
|
|
*/
|
|
contract AccountWalletRegistryExtended is IAccountWalletRegistry, AccessControl {
|
|
bytes32 public constant ACCOUNT_MANAGER_ROLE = keccak256("ACCOUNT_MANAGER_ROLE");
|
|
|
|
address public smartAccountFactory;
|
|
address public entryPoint;
|
|
|
|
mapping(bytes32 => WalletLink[]) private _accountToWallets;
|
|
mapping(bytes32 => bytes32[]) private _walletToAccounts;
|
|
mapping(bytes32 => mapping(bytes32 => uint256)) private _walletIndex;
|
|
mapping(bytes32 => mapping(bytes32 => bool)) private _walletAccountExists;
|
|
mapping(bytes32 => address) private _smartAccountAddress; // walletRefId => smart account address
|
|
|
|
event SmartAccountLinked(bytes32 indexed accountRefId, address indexed smartAccount, bytes32 provider);
|
|
|
|
constructor(address admin, address smartAccountFactory_, address entryPoint_) {
|
|
_grantRole(DEFAULT_ADMIN_ROLE, admin);
|
|
_grantRole(ACCOUNT_MANAGER_ROLE, admin);
|
|
smartAccountFactory = smartAccountFactory_;
|
|
entryPoint = entryPoint_;
|
|
}
|
|
|
|
function setSmartAccountFactory(address factory) external onlyRole(DEFAULT_ADMIN_ROLE) {
|
|
smartAccountFactory = factory;
|
|
}
|
|
|
|
function setEntryPoint(address entryPoint_) external onlyRole(DEFAULT_ADMIN_ROLE) {
|
|
entryPoint = entryPoint_;
|
|
}
|
|
|
|
function linkAccountToWallet(bytes32 accountRefId, bytes32 walletRefId, bytes32 provider)
|
|
external
|
|
override
|
|
onlyRole(ACCOUNT_MANAGER_ROLE)
|
|
{
|
|
require(accountRefId != bytes32(0), "zero accountRefId");
|
|
require(walletRefId != bytes32(0), "zero walletRefId");
|
|
require(provider != bytes32(0), "zero provider");
|
|
|
|
if (_walletAccountExists[walletRefId][accountRefId]) {
|
|
uint256 index = _walletIndex[accountRefId][walletRefId];
|
|
require(index < _accountToWallets[accountRefId].length, "index out of bounds");
|
|
WalletLink storage link = _accountToWallets[accountRefId][index];
|
|
require(link.walletRefId == walletRefId, "link mismatch");
|
|
link.active = true;
|
|
link.linkedAt = uint64(block.timestamp);
|
|
} else {
|
|
WalletLink memory newLink = WalletLink({
|
|
walletRefId: walletRefId,
|
|
linkedAt: uint64(block.timestamp),
|
|
active: true,
|
|
provider: provider
|
|
});
|
|
_accountToWallets[accountRefId].push(newLink);
|
|
_walletIndex[accountRefId][walletRefId] = _accountToWallets[accountRefId].length - 1;
|
|
_walletAccountExists[walletRefId][accountRefId] = true;
|
|
_walletToAccounts[walletRefId].push(accountRefId);
|
|
}
|
|
emit AccountWalletLinked(accountRefId, walletRefId, provider, uint64(block.timestamp));
|
|
}
|
|
|
|
function linkSmartAccount(bytes32 accountRefId, address smartAccount, bytes32 provider)
|
|
external
|
|
onlyRole(ACCOUNT_MANAGER_ROLE)
|
|
{
|
|
require(smartAccount != address(0), "AccountWalletRegistryExtended: zero smartAccount");
|
|
require(_isContract(smartAccount), "AccountWalletRegistryExtended: not a contract");
|
|
|
|
bytes32 walletRefId = keccak256(abi.encodePacked(smartAccount));
|
|
this.linkAccountToWallet(accountRefId, walletRefId, provider);
|
|
_smartAccountAddress[walletRefId] = smartAccount;
|
|
emit SmartAccountLinked(accountRefId, smartAccount, provider);
|
|
}
|
|
|
|
function unlinkAccountFromWallet(bytes32 accountRefId, bytes32 walletRefId) external override onlyRole(ACCOUNT_MANAGER_ROLE) {
|
|
require(_walletAccountExists[walletRefId][accountRefId], "not linked");
|
|
uint256 index = _walletIndex[accountRefId][walletRefId];
|
|
require(index < _accountToWallets[accountRefId].length, "index out of bounds");
|
|
_accountToWallets[accountRefId][index].active = false;
|
|
_walletAccountExists[walletRefId][accountRefId] = false;
|
|
emit AccountWalletUnlinked(accountRefId, walletRefId);
|
|
}
|
|
|
|
function getWallets(bytes32 accountRefId) external view override returns (WalletLink[] memory) {
|
|
return _accountToWallets[accountRefId];
|
|
}
|
|
|
|
function getAccounts(bytes32 walletRefId) external view override returns (bytes32[] memory) {
|
|
return _walletToAccounts[walletRefId];
|
|
}
|
|
|
|
function isLinked(bytes32 accountRefId, bytes32 walletRefId) external view override returns (bool) {
|
|
return _walletAccountExists[walletRefId][accountRefId];
|
|
}
|
|
|
|
function isActive(bytes32 accountRefId, bytes32 walletRefId) external view override returns (bool) {
|
|
if (!_walletAccountExists[walletRefId][accountRefId]) return false;
|
|
uint256 index = _walletIndex[accountRefId][walletRefId];
|
|
return index < _accountToWallets[accountRefId].length && _accountToWallets[accountRefId][index].active;
|
|
}
|
|
|
|
function isSmartAccount(bytes32 walletRefId) public view returns (bool) {
|
|
return _smartAccountAddress[walletRefId] != address(0);
|
|
}
|
|
|
|
function isSmartAccountAddress(address addr) public view returns (bool) {
|
|
return _smartAccountAddress[keccak256(abi.encodePacked(addr))] == addr;
|
|
}
|
|
|
|
function _isContract(address account) internal view returns (bool) {
|
|
return account.code.length > 0;
|
|
}
|
|
}
|