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

Added core, base tests and deployment scripts #1

Merged
merged 3 commits into from
Mar 24, 2024
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ cache
artifacts
coverage.json
coverage
abi

# Typechain generated files
generated-types
Expand Down
2 changes: 1 addition & 1 deletion .solcover.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
module.exports = {
skipFiles: [],
skipFiles: ["interfaces/", "mock/"],
configureYulOptimizer: true,
};
Empty file removed contracts/.gitkeep
Empty file.
119 changes: 119 additions & 0 deletions contracts/core/Account.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.16;

import {Address} from "@openzeppelin/contracts/utils/Address.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import {Initializable} from "@openzeppelin/contracts/proxy/utils/Initializable.sol";
import {UUPSUpgradeable} from "@openzeppelin/contracts/proxy/utils/UUPSUpgradeable.sol";

import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol";

import {IAccount} from "../interfaces/IAccount.sol";
import {IBioRegistry} from "../interfaces/IBioRegistry.sol";

/**
* @title Account
* @notice This contract represents a smart account, the ownership of which is determined by a UUID and a threshold of trusted issuers.
*/
contract Account is IAccount, Initializable, UUPSUpgradeable {
using SafeERC20 for IERC20;
using EnumerableSet for EnumerableSet.UintSet;

IBioRegistry public bioRegistry;

string public UUID;
uint256 public threshold;

EnumerableSet.UintSet private _issuers;

modifier onlyOwner() {
validateExecution();
_;
}

constructor() {
_disableInitializers();
}

/**
* @inheritdoc IAccount
*/
function __Account_init(
IBioRegistry bioRegistry_,
string memory uuid_,
uint256[] memory issuers_,
uint256 threshold_
) external initializer {
bioRegistry = bioRegistry_;

UUID = uuid_;
threshold = threshold_;

uint256 arrayLength_ = issuers_.length;
for (uint256 i = 0; i < arrayLength_; ++i) {
_issuers.add(issuers_[i]);
}
}

/**
* @inheritdoc IAccount
*/
function setThreshold(uint256 threshold_) external onlyOwner {
threshold = threshold_;
}

/**
* @inheritdoc IAccount
*/
function transfer(uint256 amount_, address to_) external payable onlyOwner {
(bool success_, bytes memory data_) = to_.call{value: amount_}("");
Address.verifyCallResult(success_, data_, "Account: transfer failed");
}

/**
* @inheritdoc IAccount
*/
function transferERC20(address token_, uint256 amount_, address to_) external onlyOwner {
IERC20(token_).safeTransfer(to_, amount_);
}

/**
* @inheritdoc IAccount
*/
function arbitraryCall(address to_, bytes calldata data_) external onlyOwner {
(bool success_, bytes memory data__) = to_.call(data_);
Address.verifyCallResult(success_, data__, "Account: arbitrary call failed");
}

/**
* @inheritdoc IAccount
*/
function getTrustedIssuers() external view returns (uint256[] memory) {
return _issuers.values();
}

/**
* @inheritdoc IAccount
*/
function validateExecution() public view {
uint256 counter_ = 0;

uint256 arrayLength_ = _issuers.length();
for (uint256 i = 0; i < arrayLength_; i++) {
if (msg.sender == bioRegistry.getUserByUUID(_issuers.at(i), UUID).userAddress) {
++counter_;
}
}

require(counter_ >= threshold, "Account: unauthorized");
}

// A functionality to upgrade the contract

function _authorizeUpgrade(address) internal virtual override onlyOwner {}

function implementation() external view returns (address) {
return _getImplementation();
}
}
152 changes: 152 additions & 0 deletions contracts/core/AccountFactory.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.16;

import {Create2} from "@openzeppelin/contracts/utils/Create2.sol";
import {Address} from "@openzeppelin/contracts/utils/Address.sol";
import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol";

import {UUPSUpgradeable} from "@openzeppelin/contracts/proxy/utils/UUPSUpgradeable.sol";
import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";

import {Account} from "./Account.sol";

import {IBioRegistry} from "../interfaces/IBioRegistry.sol";
import {IAccountFactory} from "../interfaces/IAccountFactory.sol";

/**
* @title AccountFactory
* @notice Manages the creation and deployment of Account contracts using deterministic method.
*/
contract AccountFactory is IAccountFactory, OwnableUpgradeable, UUPSUpgradeable {
IBioRegistry public bioRegistry;

address public accountImplementation;

event AccountCreated(string uuid, address account);

constructor() {
_disableInitializers();
}

function __AccountFactory_init(
IBioRegistry bioRegistry_,
address accountImplementation_
) external initializer {
__Ownable_init();

bioRegistry = bioRegistry_;
accountImplementation = accountImplementation_;
}

/**
* @inheritdoc IAccountFactory
*/
function setAccountImplementation(address accountImplementation_) external onlyOwner {
accountImplementation = accountImplementation_;
}

/**
* @inheritdoc IAccountFactory
*/
function createAccountWithSalt(
string memory uuid_,
uint256[] memory trustedIssuers_,
uint256 threshold,
bytes32 salt_
) external returns (address) {
validateAccountCreation(uuid_, trustedIssuers_);

bytes memory data_ = abi.encodeWithSelector(
Account.__Account_init.selector,
bioRegistry,
uuid_,
trustedIssuers_,
threshold
);

address account_ = _deploy2(data_, msg.sender, salt_);

_register(uuid_, account_);

emit AccountCreated(uuid_, account_);

return account_;
}

/**
* @inheritdoc IAccountFactory
*/
function predictAccountAddress(
string memory uuid_,
uint256[] memory trustedIssuers_,
uint256 threshold,
address deployer_,
bytes32 salt_
) external view returns (address) {
bytes memory data_ = abi.encodeWithSelector(
Account.__Account_init.selector,
bioRegistry,
uuid_,
trustedIssuers_,
threshold
);

bytes32 combinedSalt_ = _getCombinedSalt(deployer_, salt_);

bytes32 bytecodeHash = keccak256(
abi.encodePacked(
type(ERC1967Proxy).creationCode,
abi.encode(accountImplementation, data_)
)
);

return Create2.computeAddress(combinedSalt_, bytecodeHash);
}

/**
* @notice Function to validate account creation.
* Account can only be created by user that is set in trusted issuers.
*/
function validateAccountCreation(
string memory uuid_,
uint256[] memory trustedIssuers_
) public view {
uint256 arrayLength_ = trustedIssuers_.length;
for (uint256 i = 0; i < arrayLength_; i++) {
require(
msg.sender == bioRegistry.getUserByUUID(trustedIssuers_[i], uuid_).userAddress,
"AccountFactory: Account can be created only by user that is set in trusted issuers"
);
}
}

function _register(string memory uuid_, address userAccount_) private {
bioRegistry.registerAccount(uuid_, userAccount_);
}

function _deploy2(
bytes memory data_,
address deployer_,
bytes32 salt_
) private returns (address) {
return
address(
new ERC1967Proxy{salt: _getCombinedSalt(deployer_, salt_)}(
accountImplementation,
data_
)
);
}

function _getCombinedSalt(address deployer_, bytes32 salt_) private pure returns (bytes32) {
return keccak256(abi.encodePacked(deployer_, salt_));
}

// A functionality to upgrade the contract

function _authorizeUpgrade(address) internal virtual override onlyOwner {}

function implementation() external view returns (address) {
return _getImplementation();
}
}
Loading
Loading