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

build(deps): bump @openzeppelin/contracts-upgradeable from 4.9.1 to 4.9.2 #10

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
621dadf
v1.3.0: init
kbehouse May 11, 2023
3263cf6
4337 contract update & add deploy script
kbehouse May 12, 2023
36a2665
4337: only for 4337 without temporarily versioin for upgrade
kbehouse May 16, 2023
99082a3
misc: update for clean code
kbehouse May 17, 2023
99bb0b6
CoreWallet compatibility with 1.2.0 wallet
kbehouse May 22, 2023
40fe3b1
wallet: add emit Received(msg.sender, msg.value); in receive()
kbehouse May 23, 2023
07cf787
test: add test/entrypoint/ and #simulateValidation
kbehouse May 23, 2023
5e2dd44
BloctoAccountFactory.sol: add addStake,withdrawTo(stake) functions
kbehouse May 24, 2023
316641f
comment: add comment for contracts
kbehouse May 25, 2023
315f136
createAccount2: add create account with init2 for multiple auhtorizes…
kbehouse May 25, 2023
3c22247
v1.3.0: executeBatch add value
kbehouse Jun 5, 2023
1ccddd0
schnorr: schnorr init and test multiple sign success
kbehouse Jun 7, 2023
574647a
v1.4.0: add test 'check none zero mergedKeyIndex'
kbehouse Jun 8, 2023
5a5db5c
v1.4.0: update _mergedKeyIndexWithParity to uint8
kbehouse Jun 8, 2023
3f6922a
v1.4.0: add bit for know isSchnorr or not
kbehouse Jun 8, 2023
7346f9b
v1.4.0: add createAccount2, comment, update test
kbehouse Jun 13, 2023
a8c592a
chore: delete unused bytesToAddresses()
kbehouse Jun 14, 2023
e532874
v1.4.0: merged key maybe zero
kbehouse Jun 15, 2023
6f653a3
v1.4.0: let BloctoAccountFactory be upgradeable
kbehouse Jun 15, 2023
22ce6ea
v1.4.0: deploy update for factory upgradeable
kbehouse Jun 15, 2023
7bf7111
v1.4.0: cosigner == 0 need mergedkey==0
kbehouse Jun 19, 2023
07fb23c
v1.4.0: use minimal proxy with storage
kbehouse Jun 19, 2023
4f54526
v1.4.0: update deploy script
kbehouse Jun 19, 2023
db94589
v1.4.0: save a little bit gas, authVersion -> AUTH_VERSION_INCREMENTO…
kbehouse Jun 20, 2023
5c11442
v1.4.0: update deploy scripts & a little bit of contract update
kbehouse Jun 21, 2023
cfee161
Merge pull request #9 from portto/v1.4.0
kbehouse Jun 21, 2023
ec8e7d4
build(deps): bump @openzeppelin/contracts-upgradeable
dependabot[bot] Jun 21, 2023
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
@@ -1,6 +1,7 @@
node_modules
log/
.vscode/
.openzeppelin/
#Hardhat files
cache
artifacts
Expand Down
40 changes: 34 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,21 +1,49 @@
# BloctoAccount & BloctoAccountFactory

## Test & Deploy
## Test

test
```
npx hardhat test test/entrypoint.test.ts
yarn test
```

deploy BloctoAccountFactory
Schnorr Multi Sign Test

```
yarn deploy-accountfactory --network mumbai
npx hardhat test test/schnorrMultiSign.test.ts
```

verify BloctoAccountFactory
## Deploy & Verify

deploy BloctoAccountCloneableWallet, BloctoAccountFactory, and addStake to BloctoAccountFactory

```
yarn deploy_verify --network goerli
```

create a test account and verify
```
npx hardhat run deploy/2_createSchnorrAccount_verify.ts --network goerli
```


## Tool

check storage layout
```
npx hardhat check
```

## Testnet chain info

goerli, arbitrum goerli, op goerli, mumbai, bsc testnet, avax testnet
```
yarn verify-accountfactory --network mumbai
BloctoAccountCloneableWallet
0x490B5ED8A17224a553c34fAA642161c8472118dd
BloctoAccountFactory
0x285cc5232236D227FCb23E6640f87934C948a028
VerifyingPaymaster
0x9C58dF1BB61a3f68C66Ef5fC7D8Ab4bd1DaEC9Ac
```


Expand Down
153 changes: 153 additions & 0 deletions contracts/BloctoAccount.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.12;

/* solhint-disable avoid-low-level-calls */
/* solhint-disable no-inline-assembly */
/* solhint-disable reason-string */

import "@openzeppelin/contracts/proxy/utils/UUPSUpgradeable.sol";
import "@account-abstraction/contracts/core/BaseAccount.sol";

import "./TokenCallbackHandler.sol";
import "./CoreWallet/CoreWallet.sol";

/**
* Blocto account.
* compatibility for EIP-4337 and smart contract wallet with cosigner functionality (CoreWallet)
*/
contract BloctoAccount is UUPSUpgradeable, TokenCallbackHandler, CoreWallet, BaseAccount {
/**
* This is the version of this contract.
*/
string public constant VERSION = "1.4.0";

/// @notice etnrypoint from 4337 official
IEntryPoint private immutable _entryPoint;

/// @notice initialized _IMPLEMENTATION_SLOT
bool public initializedImplementation = false;

/**
* constructor for BloctoAccount
* @param anEntryPoint entrypoint address
*/
constructor(IEntryPoint anEntryPoint) {
_entryPoint = anEntryPoint;
}

/**
* override from UUPSUpgradeable
* @param newImplementation implementation address
*/
function _authorizeUpgrade(address newImplementation) internal view override onlyInvoked {
(newImplementation);
}

/**
* return entrypoint
*/
function entryPoint() public view virtual override returns (IEntryPoint) {
return _entryPoint;
}

/**
* execute a transaction (called directly by entryPoint)
* @param dest dest call address
* @param value value to send
* @param func the func containing the transaction to be called
*/
function execute(address dest, uint256 value, bytes calldata func) external {
_requireFromEntryPoint();
_call(dest, value, func);
}

/**
* execute a sequence of transactions (called directly by entryPoint)
* @param dest sequence of dest call address
* @param value sequence of value to send
* @param func sequence of the func containing transactions to be called
*/
function executeBatch(address[] calldata dest, uint256[] calldata value, bytes[] calldata func) external {
_requireFromEntryPoint();
require(dest.length == func.length, "wrong array lengths");
for (uint256 i = 0; i < dest.length; i++) {
_call(dest[i], value[i], func[i]);
}
}

/**
* internal call for execute and executeBatch
* @param target target call address
* @param value value to send
* @param data the data containing the transaction to be called
*/
function _call(address target, uint256 value, bytes memory data) internal {
(bool success, bytes memory result) = target.call{value: value}(data);
if (!success) {
assembly {
revert(add(result, 32), mload(result))
}
}
}

/**
* implement validate signature method of BaseAccount from etnrypoint
* @param userOp user operation including signature for validating
* @param userOpHash user operation hash
*/
function _validateSignature(UserOperation calldata userOp, bytes32 userOpHash)
internal
virtual
override
returns (uint256 validationData)
{
bytes4 result = this.isValidSignature(userOpHash, userOp.signature);
if (result != IERC1271.isValidSignature.selector) {
return SIG_VALIDATION_FAILED;
}

return 0;
}

/**
* check current account deposit in the entryPoint StakeManager
*/
function getDeposit() public view returns (uint256) {
return entryPoint().balanceOf(address(this));
}

/**
* deposit more funds for this account in the entryPoint StakeManager
*/
function addDeposit() public payable {
entryPoint().depositTo{value: msg.value}(address(this));
}

/**
* withdraw deposit to withdrawAddress from entryPoint StakeManager
* @param withdrawAddress target to send to
* @param amount to withdraw
*/
function withdrawDepositTo(address payable withdrawAddress, uint256 amount) external onlyInvoked {
entryPoint().withdrawTo(withdrawAddress, amount);
}

/// @notice Used to decorate the `init` function so this can only be called one time. Necessary
/// since this contract will often be used as a "clone". (See above.)
modifier onlyOnceInitImplementation() {
require(!initializedImplementation, "must not already be initialized");
initializedImplementation = true;
_;
}

/// @notice initialize BloctoAccountProxy for adding the implementation address
/// @param implementation implementation address
function initImplementation(address implementation) public onlyOnceInitImplementation {
require(Address.isContract(implementation), "ERC1967: new implementation is not a contract");
StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value = implementation;
}

function disableInitImplementation() public {
initializedImplementation = true;
}
}
15 changes: 15 additions & 0 deletions contracts/BloctoAccountCloneableWallet.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 "./BloctoAccount.sol";

/// @title BloctoAccountCloneableWallet Wallet
/// @notice This contract represents a complete but non working wallet.
contract BloctoAccountCloneableWallet is BloctoAccount {
/// @notice constructor that deploys a NON-FUNCTIONAL version of `BloctoAccount`
/// @param anEntryPoint entrypoint address
constructor(IEntryPoint anEntryPoint) BloctoAccount(anEntryPoint) {
initialized = true;
initializedImplementation = true;
}
}
133 changes: 133 additions & 0 deletions contracts/BloctoAccountFactory.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.12;

import "@openzeppelin/contracts/utils/Create2.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
import "@account-abstraction/contracts/interfaces/IEntryPoint.sol";
import "./BloctoAccountProxy.sol";
import "./BloctoAccount.sol";

// BloctoAccountFactory for creating BloctoAccountProxy
contract BloctoAccountFactory is Initializable, OwnableUpgradeable {
/// @notice this is the version of this contract.
string public constant VERSION = "1.4.0";
/// @notice the init implementation address of BloctoAccountCloneableWallet, never change for cosistent address
address public initImplementation;
/// @notice the implementation address of BloctoAccountCloneableWallet
address public bloctoAccountImplementation;
/// @notice the address from EIP-4337 official implementation
IEntryPoint public entryPoint;

event WalletCreated(address wallet, address authorizedAddress, bool full);

/// @notice initialize
/// @param _bloctoAccountImplementation the implementation address for BloctoAccountCloneableWallet
/// @param _entryPoint the entrypoint address from EIP-4337 official implementation
function initialize(address _bloctoAccountImplementation, IEntryPoint _entryPoint) public initializer {
__Ownable_init_unchained();
initImplementation = _bloctoAccountImplementation;
bloctoAccountImplementation = _bloctoAccountImplementation;
entryPoint = _entryPoint;
}

/// @notice create an account, and return its BloctoAccount.
/// returns the address even if the account is already deployed.
/// Note that during UserOperation execution, this method is called only if the account is not deployed.
/// This method returns an existing account address so that entryPoint.getSenderAddress() would work even after account creation
/// @param _authorizedAddress the initial authorized address, must not be zero!
/// @param _cosigner the initial cosigning address for `_authorizedAddress`, can be equal to `_authorizedAddress`
/// @param _recoveryAddress the initial recovery address for the wallet, can be address(0)
/// @param _salt salt for create account (used for address calculation in create2)
/// @param _mergedKeyIndexWithParity the corresponding index of mergedKeys = authVersion + _mergedIndex
/// @param _mergedKey the corresponding mergedKey (using Schnorr merged key)
function createAccount(
address _authorizedAddress,
address _cosigner,
address _recoveryAddress,
uint256 _salt,
uint8 _mergedKeyIndexWithParity,
bytes32 _mergedKey
) public onlyOwner returns (BloctoAccount ret) {
bytes32 salt = keccak256(abi.encodePacked(_salt, _cosigner, _recoveryAddress));
// to be consistent address
BloctoAccountProxy newProxy = new BloctoAccountProxy{salt: salt}(initImplementation);
ret = BloctoAccount(payable(address(newProxy)));
// to save gas, first deploy using disableInitImplementation()
// to be consistent address, (after) first upgrade need to call initImplementation
ret.disableInitImplementation();
ret.init(
_authorizedAddress, uint256(uint160(_cosigner)), _recoveryAddress, _mergedKeyIndexWithParity, _mergedKey
);
emit WalletCreated(address(ret), _authorizedAddress, false);
}

/// @notice create an account with multiple authorized addresses, and return its BloctoAccount.
/// returns the address even if the account is already deployed.
/// @param _authorizedAddresses the initial authorized addresses, must not be zero!
/// @param _cosigner the initial cosigning address for `_authorizedAddress`, can be equal to `_authorizedAddress`
/// @param _recoveryAddress the initial recovery address for the wallet, can be address(0)
/// @param _salt salt for create account (used for address calculation in create2)
/// @param _mergedKeyIndexWithParitys the corresponding index of mergedKeys = authVersion + _mergedIndex
/// @param _mergedKeys the corresponding mergedKey
function createAccount2(
address[] calldata _authorizedAddresses,
address _cosigner,
address _recoveryAddress,
uint256 _salt,
uint8[] calldata _mergedKeyIndexWithParitys,
bytes32[] calldata _mergedKeys
) public onlyOwner returns (BloctoAccount ret) {
bytes32 salt = keccak256(abi.encodePacked(_salt, _cosigner, _recoveryAddress));
// to be consistent address
BloctoAccountProxy newProxy = new BloctoAccountProxy{salt: salt}(initImplementation);

ret = BloctoAccount(payable(address(newProxy)));
// to save gas, first deploy use disableInitImplementation()
// to be consistent address, (after) first upgrade need to call initImplementation()
// ret.initImplementation(bloctoAccountImplementation);
ret.disableInitImplementation();
ret.init2(
_authorizedAddresses, uint256(uint160(_cosigner)), _recoveryAddress, _mergedKeyIndexWithParitys, _mergedKeys
);
// emit event only with _authorizedAddresses[0]
emit WalletCreated(address(ret), _authorizedAddresses[0], true);
}

/// @notice calculate the counterfactual address of this account as it would be returned by createAccount()
/// @param _cosigner the initial cosigning address
/// @param _recoveryAddress the initial recovery address for the wallet
/// @param _salt salt for create account (used for address calculation in create2)
function getAddress(address _cosigner, address _recoveryAddress, uint256 _salt) public view returns (address) {
bytes32 salt = keccak256(abi.encodePacked(_salt, _cosigner, _recoveryAddress));
return Create2.computeAddress(
bytes32(salt),
keccak256(abi.encodePacked(type(BloctoAccountProxy).creationCode, abi.encode(address(initImplementation))))
);
}

/// @notice set the implementation
/// @param _bloctoAccountImplementation update the implementation address of BloctoAccountCloneableWallet for createAccount and createAccount2
function setImplementation(address _bloctoAccountImplementation) public onlyOwner {
bloctoAccountImplementation = _bloctoAccountImplementation;
}

/// @notice set the entrypoint
/// @param _entrypoint target entrypoint
function setEntrypoint(IEntryPoint _entrypoint) public onlyOwner {
entryPoint = _entrypoint;
}

/// @notice withdraw value from the deposit
/// @param withdrawAddress target to send to
/// @param amount to withdraw
function withdrawTo(address payable withdrawAddress, uint256 amount) public onlyOwner {
entryPoint.withdrawTo(withdrawAddress, amount);
}

/// @notice add stake in etnrypoint for this factory to avoid bundler reject
/// @param unstakeDelaySec - the unstake delay for this factory. Can only be increased.
function addStake(uint32 unstakeDelaySec) external payable onlyOwner {
entryPoint.addStake{value: msg.value}(unstakeDelaySec);
}
}
Loading