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

v1.5.4: init #41

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
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
2 changes: 1 addition & 1 deletion contracts/test/TestBloctoAccountCloneableWalletV200.sol
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import "./TestBloctoAccountV200.sol";
/// @notice This contract represents a complete but non working wallet.
contract TestBloctoAccountCloneableWalletV200 is TestBloctoAccountV200 {
/// @dev Cconstructor that deploys a NON-FUNCTIONAL version of `TestBloctoAccountV140`
constructor(IEntryPoint anEntryPoint) TestBloctoAccountV200(anEntryPoint) {
constructor(IEntryPoint anEntryPoint, address moduleManager) TestBloctoAccountV200(anEntryPoint, moduleManager) {
initialized = true;
}
}
2 changes: 1 addition & 1 deletion contracts/test/TestBloctoAccountV200.sol
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ contract TestBloctoAccountV200 is UUPSUpgradeable, TokenCallbackHandler, CoreWal
* constructor for BloctoAccount
* @param anEntryPoint entrypoint address
*/
constructor(IEntryPoint anEntryPoint) {
constructor(IEntryPoint anEntryPoint, address moduleManager) CoreWallet(moduleManager) {
_entryPoint = anEntryPoint;
}

Expand Down
5 changes: 3 additions & 2 deletions contracts/v1.5.x/BloctoAccount.sol
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ contract BloctoAccount is UUPSUpgradeable, TokenCallbackHandler, CoreWallet, Bas
/**
* This is the version of this contract.
*/
string public constant VERSION = "1.5.3";
string public constant VERSION = "1.5.4";

/// @notice entrypoint from 4337 official
IEntryPoint private immutable _entryPoint;
Expand All @@ -30,8 +30,9 @@ contract BloctoAccount is UUPSUpgradeable, TokenCallbackHandler, CoreWallet, Bas
/**
* constructor for BloctoAccount
* @param anEntryPoint entrypoint address
* @param moduleManager module manager address
*/
constructor(IEntryPoint anEntryPoint) {
constructor(IEntryPoint anEntryPoint, address moduleManager) CoreWallet(moduleManager) {
_entryPoint = anEntryPoint;
}

Expand Down
3 changes: 2 additions & 1 deletion contracts/v1.5.x/BloctoAccountCloneableWallet.sol
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ import "./BloctoAccount.sol";
contract BloctoAccountCloneableWallet is BloctoAccount {
/// @notice constructor that deploys a NON-FUNCTIONAL version of `BloctoAccount`
/// @param anEntryPoint entrypoint address
constructor(IEntryPoint anEntryPoint) BloctoAccount(anEntryPoint) {
/// @param moduleManager module manager address
constructor(IEntryPoint anEntryPoint, address moduleManager) BloctoAccount(anEntryPoint, moduleManager) {
initialized = true;
initializedImplementation = true;
}
Expand Down
10 changes: 7 additions & 3 deletions contracts/v1.5.x/BloctoAccountFactory.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,15 @@ pragma solidity 0.8.17;

import "./BloctoAccountFactoryV1_5_2.sol";
import "./BloctoAccountFactoryV1_5_3.sol";
import "./BloctoAccountFactoryV1_5_4.sol";

// BloctoAccountFactory for creating BloctoAccountProxy
contract BloctoAccountFactory is BloctoAccountFactoryV1_5_2, BloctoAccountFactoryV1_5_3 {
contract BloctoAccountFactory is BloctoAccountFactoryV1_5_2, BloctoAccountFactoryV1_5_3, BloctoAccountFactoryV1_5_4 {
/// @notice this is the version of this contract.
string public constant VERSION = "1.5.3";
string public constant VERSION = "1.5.4";

constructor(address _account_1_5_3) BloctoAccountFactoryV1_5_3(_account_1_5_3) {}
constructor(address _account_1_5_3, address _account_1_5_4)
BloctoAccountFactoryV1_5_3(_account_1_5_3)
BloctoAccountFactoryV1_5_4(_account_1_5_4)
{}
}
195 changes: 195 additions & 0 deletions contracts/v1.5.x/BloctoAccountFactoryV1_5_4.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,195 @@
// SPDX-License-Identifier: GPL-3.0
pragma solidity 0.8.17;

import "./BloctoAccountFactoryBase.sol";

// BloctoAccountFactory for creating BloctoAccountProxy
contract BloctoAccountFactoryV1_5_4 is BloctoAccountFactoryBase {
//---------------------------V1.5.4---------------------------//
address public immutable bloctoAccountImplementation_1_5_4;

constructor(address _account_1_5_4) {
bloctoAccountImplementation_1_5_4 = _account_1_5_4;
}

/// @notice create an account, and return its BloctoAccount. note: diretly use _salt to create account
/// @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_1_5_4(
address _authorizedAddress,
address _cosigner,
address _recoveryAddress,
bytes32 _salt,
uint8 _mergedKeyIndexWithParity,
bytes32 _mergedKey
) public onlyCreateAccountRole returns (BloctoAccount ret) {
// to be consistent address
address newProxy =
Create2.deploy(0, _salt, abi.encodePacked(BLOCTO_ACCOUNT_PROXY, abi.encode(address(initImplementation))));
ret = BloctoAccount(payable(newProxy));
ret.initImplementation(bloctoAccountImplementation_1_5_4);
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. note: diretly use _salt to create account
/// @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_1_5_4(
address[] calldata _authorizedAddresses,
address _cosigner,
address _recoveryAddress,
bytes32 _salt,
uint8[] calldata _mergedKeyIndexWithParitys,
bytes32[] calldata _mergedKeys
) public onlyCreateAccountRole returns (BloctoAccount ret) {
// to be consistent address
address newProxy =
Create2.deploy(0, _salt, abi.encodePacked(BLOCTO_ACCOUNT_PROXY, abi.encode(address(initImplementation))));
ret = BloctoAccount(payable(newProxy));
ret.initImplementation(bloctoAccountImplementation_1_5_4);
ret.init2(
_authorizedAddresses, uint256(uint160(_cosigner)), _recoveryAddress, _mergedKeyIndexWithParitys, _mergedKeys
);
// emit event only with _authorizedAddresses[0]
emit WalletCreated(address(ret), _authorizedAddresses[0], true);
}

/// @notice create an account and run first transaction, it combine from createAccount_1_5_4() of this and invoke2() from CoreWallet
/// @dev why Invoke2Data struct? Because 'CompilerError: Stack too deep.' problem, we cannot directly input (_nonce, _data, _signature)
/// @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)
/// @param _invoke2Data the invoke2 data {nonce, data, signature}
function createAccountWithInvoke2_1_5_4(
// same input as createAccount_1_5_4() of this contract
address _authorizedAddress,
address _cosigner,
address _recoveryAddress,
bytes32 _salt,
uint8 _mergedKeyIndexWithParity,
bytes32 _mergedKey,
// same input as invoke2() of CoreWallet.sol
Invoke2Data calldata _invoke2Data
) external onlyCreateAccountRole returns (BloctoAccount ret) {
ret = createAccount_1_5_4(
_authorizedAddress, _cosigner, _recoveryAddress, _salt, _mergedKeyIndexWithParity, _mergedKey
);
ret.invoke2(_invoke2Data.nonce, _invoke2Data.data, _invoke2Data.signature);
}

/// @notice create an account with multiple devices and run first transaction, it combine from createAccount2_1_5_4() of this and invoke2() from CoreWallet
/// @dev why Invoke2Data struct? Because 'CompilerError: Stack too deep.' problem, we cannot directly input (_nonce, _data, _signature)
/// @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
/// @param _invoke2Data the invoke2 data {nonce, data, signature}
function createAccount2WithInvoke2_1_5_4(
// same input as createAccount2_1_5_4() of this contract
address[] calldata _authorizedAddresses,
address _cosigner,
address _recoveryAddress,
bytes32 _salt,
uint8[] calldata _mergedKeyIndexWithParitys,
bytes32[] calldata _mergedKeys,
// same input as invoke2() of CoreWallet.sol
Invoke2Data calldata _invoke2Data
) external onlyCreateAccountRole returns (BloctoAccount ret) {
ret = createAccount2_1_5_4(
_authorizedAddresses, _cosigner, _recoveryAddress, _salt, _mergedKeyIndexWithParitys, _mergedKeys
);
ret.invoke2(_invoke2Data.nonce, _invoke2Data.data, _invoke2Data.signature);
}

/// @notice simulate for creating an account and run first transaction, it combine from createAccount_1_5_1() of this and invoke2() from CoreWallet
/// @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)
/// @param _invoke2Data the invoke2 data {nonce, data, signature}
function simulateCreateAccountWithInvoke2_1_5_4(
// same input as createAccount_1_5_1() of this contract
address _authorizedAddress,
address _cosigner,
address _recoveryAddress,
bytes32 _salt,
uint8 _mergedKeyIndexWithParity,
bytes32 _mergedKey,
// same input as invoke2() of CoreWallet.sol
Invoke2Data calldata _invoke2Data
) external onlyCreateAccountRole returns (BloctoAccount ret) {
ret = createAccount_1_5_4(
_authorizedAddress, _cosigner, _recoveryAddress, _salt, _mergedKeyIndexWithParity, _mergedKey
);
// always revert
try ret.simulateInvoke2(_invoke2Data.nonce, _invoke2Data.data, _invoke2Data.signature) {}
catch (bytes memory reason) {
// NOTE: this ExecutionResult from CoreWallet.sol
// success bytes(100), bytes4 selector from keccak256("ExecutionResult(bool)") 0x2a6b3136 + btyes32 (bool, 0x01) + bytes32 (uint256) + bytes32 (uint256)
if (
reason.length == 100 && uint8(reason[35]) == 1
&& bytes4(reason) == bytes4(keccak256("ExecutionResult(bool,uint256,uint256)"))
) {
revert CreateAccountWithInvokeResult(true, gasleft());
}
}

revert CreateAccountWithInvokeResult(false, gasleft());
}

/// @notice simulate for creating an account with multiple devices and run first transaction, it combine from createAccount2_1_5_1() of this and invoke2() from CoreWallet
/// @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
/// @param _invoke2Data the invoke2 data {nonce, data, signature}
function simulateCreateAccount2WithInvoke2_1_5_4(
// same input as createAccount2_1_5_1() of this contract
address[] calldata _authorizedAddresses,
address _cosigner,
address _recoveryAddress,
bytes32 _salt,
uint8[] calldata _mergedKeyIndexWithParitys,
bytes32[] calldata _mergedKeys,
// same input as invoke2() of CoreWallet.sol
Invoke2Data calldata _invoke2Data
) external onlyCreateAccountRole returns (BloctoAccount ret) {
ret = createAccount2_1_5_4(
_authorizedAddresses, _cosigner, _recoveryAddress, _salt, _mergedKeyIndexWithParitys, _mergedKeys
);
// always revert
try ret.simulateInvoke2(_invoke2Data.nonce, _invoke2Data.data, _invoke2Data.signature) {}
catch (bytes memory reason) {
// NOTE: this ExecutionResult from CoreWallet.sol
// success bytes(100), bytes4 selector from keccak256("ExecutionResult(bool)") 0x2a6b3136 + btyes32 (bool, 0x01) + bytes32 (uint256) + bytes32 (uint256)
if (
reason.length == 100 && uint8(reason[35]) == 1
&& bytes4(reason) == bytes4(keccak256("ExecutionResult(bool,uint256,uint256)"))
) {
revert CreateAccountWithInvokeResult(true, gasleft());
}
}

revert CreateAccountWithInvokeResult(false, gasleft());
}
}
54 changes: 45 additions & 9 deletions contracts/v1.5.x/CoreWallet.sol
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,9 @@ contract CoreWallet is IERC1271 {
/// @notice This is for point and revert flag b01 when any meta tx fail because of atomicity
uint256 internal constant FEE_SUCCESS_META_TXS_FAIL = type(uint256).max - 1;

/// @notice module manager address for verifying module address
address public immutable moduleManager;

/// @notice The pre-shifted authVersion (to get the current authVersion as an integer,
/// shift this value right by 160 bits). Starts as `1 << 160` (`AUTH_VERSION_INCREMENTOR`)
/// See the comment on the `authorizations` variable for how this is used.
Expand Down Expand Up @@ -109,7 +112,13 @@ contract CoreWallet is IERC1271 {
/// for more information about clone contracts.
bool public initialized;

error ExecutionResult(bool targetSuccess, uint256 gasLeft);
error ExecutionResult(bool targetSuccess, uint256 startGas, uint256 gasLeft);

/// @notice Construct of CoreWallet
/// @param imoduleManager immutable module manager address
constructor(address imoduleManager) {
moduleManager = imoduleManager;
}

/// @notice Used to decorate methods that can only be called directly by the recovery address.
modifier onlyRecoveryAddress() {
Expand Down Expand Up @@ -613,6 +622,7 @@ contract CoreWallet is IERC1271 {
/// @param _data The data containing the transactions to be invoked; see internalInvoke for details.
/// @param _signature Signature byte array associated with `_nonce, _data`
function simulateInvoke2(uint256 _nonce, bytes calldata _data, bytes calldata _signature) external {
uint256 startGas = gasleft();
// calculate hash
bytes32 operationHash =
keccak256(abi.encodePacked(EIP191_PREFIX, EIP191_VERSION_DATA, this, block.chainid, _nonce, _data));
Expand All @@ -632,7 +642,7 @@ contract CoreWallet is IERC1271 {
internalInvoke(operationHash, _data);

// always revert
revert ExecutionResult(true, gasleft());
revert ExecutionResult(true, startGas, gasleft());
}

/// @dev Internal invoke
Expand Down Expand Up @@ -722,7 +732,11 @@ contract CoreWallet is IERC1271 {
// the revert call from assembly.
string memory invalidLengthMessage = "data field too short";
string memory callFailed = "call failed";

string memory unverifiedModule = "unverified module";
// assembly cannot access immutable variables directly, so we need to load the moduleManager address into memory
address moduleManagerAddr = moduleManager;
// each meta tx success variable
bool success;
// At an absolute minimum, the data field must be at least 85 bytes
// <revert(1), to_address(20), value(32), data_length(32)>

Expand All @@ -743,6 +757,12 @@ contract CoreWallet is IERC1271 {
// 52 = to(20) + value(32)
let len := mload(add(memPtr, 52))

// delete call to blocto modules
// shift 12 byters and check if moduleAddr is zero
let moduleAddr := shr(96, len)
// only keep 12 bytes
len := and(len, 0xFFFFFFFFFFFFFFFFFFFFFFFF)

// Compute a pointer to the end of the current operation
// 84 = to(20) + value(32) + size(32)
let opEnd := add(len, add(memPtr, 84))
Expand All @@ -755,11 +775,27 @@ contract CoreWallet is IERC1271 {
// The computed end of this operation goes past the end of the data buffer. Not good!
revert(add(invalidLengthMessage, 32), mload(invalidLengthMessage))
}
// NOTE: Code that is compatible with solidity-coverage
// switch gt(opEnd, endPtr)
// case 1 {
// revert(add(invalidLengthMessage, 32), mload(invalidLengthMessage))
// }

switch moduleAddr
case 0 {
success := call(gas(), shr(96, mload(memPtr)), mload(add(memPtr, 20)), add(memPtr, 84), len, 0, 0)
}
default {
// step 1. is this module verified ?
let isVerifiedAddr := mload(0x40)
// signature of isVerified(address)
let selector := shl(224, 0xb9209e33)
mstore(isVerifiedAddr, selector)
mstore(add(isVerifiedAddr, 0x04), moduleAddr)
// call moduleManager to check if module is verified
let callSuccess := staticcall(gas(), moduleManagerAddr, isVerifiedAddr, 0x24, 0x00, 0x20)
let isVerified := mload(0x00)
if eq(0, and(callSuccess, isVerified)) {
revert(add(unverifiedModule, 32), mload(unverifiedModule))
}
// step 2. call module
success := delegatecall(gas(), moduleAddr, memPtr, add(len, 84), 0, 0)
}

// This line of code packs in a lot of functionality!
// - load the target address from memPtr, the address is only 20-bytes but mload always grabs 32-bytes,
Expand All @@ -769,7 +805,7 @@ contract CoreWallet is IERC1271 {
// - use the previously loaded len field as the size of the call data
// - make the call (passing all remaining gas to the child call)
// - check the result (0 == reverted)
if eq(0, call(gas(), shr(96, mload(memPtr)), mload(add(memPtr, 20)), add(memPtr, 84), len, 0, 0)) {
if eq(0, success) {
switch revertFlag
case 1 { revert(add(callFailed, 32), mload(callFailed)) }
default {
Expand Down
Loading