Skip to content

Commit

Permalink
Merge pull request #6 from portto/v1.3.0
Browse files Browse the repository at this point in the history
v1.3.0: init
  • Loading branch information
popodidi authored Jun 12, 2023
2 parents 372728f + 99a3f5b commit 257ec83
Show file tree
Hide file tree
Showing 36 changed files with 4,820 additions and 1,072 deletions.
35 changes: 31 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,23 +1,50 @@
# BloctoAccount & BloctoAccountFactory

## Test & Deploy
## Test

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

deploy BloctoAccountFactory

## Deploy

deploy BloctoAccountCloneableWallet, BloctoAccountFactory, and addStake to BloctoAccountFactory

```
yarn deploy-accountfactory --network mumbai
yarn deploy --network mumbai
```


deploy VerifyingPaymaster
```
yarn deploy-verifyingpaymaster --network mumbai
```


verify BloctoAccountCloneableWallet
```
yarn verify-bloctoaccountcloneable --network mumbai
```


verify BloctoAccountFactory
```
yarn verify-accountfactory --network mumbai
```

verify VerifyingPaymaster
```
yarn verify-verifyingpaymaster --network mumbai
```

## Tool

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

## Acknowledgement

Expand Down
130 changes: 130 additions & 0 deletions contracts/BloctoAccount.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
// 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.3.0";

IEntryPoint private immutable _entryPoint;

/**
* 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);
}
}
16 changes: 16 additions & 0 deletions contracts/BloctoAccountCloneableWallet.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// 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 {
/**
* constructor that deploys a NON-FUNCTIONAL version of `BloctoAccount`
* @param anEntryPoint entrypoint address
*/
constructor(IEntryPoint anEntryPoint) BloctoAccount(anEntryPoint) {
initialized = true;
}
}
121 changes: 121 additions & 0 deletions contracts/BloctoAccountFactory.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.12;

import "@openzeppelin/contracts/utils/Create2.sol";
// import "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@account-abstraction/contracts/interfaces/IEntryPoint.sol";
import "./BloctoAccountProxy.sol";
import "./BloctoAccount.sol";

// BloctoAccountFactory for creating BloctoAccountProxy
contract BloctoAccountFactory is Ownable {
/// @notice This is the version of this contract.
string public constant VERSION = "1.3.0";
address public bloctoAccountImplementation;
IEntryPoint public entryPoint;

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

constructor(address _bloctoAccountImplementation, IEntryPoint _entryPoint) {
bloctoAccountImplementation = _bloctoAccountImplementation;
entryPoint = _entryPoint;
}

/**
* 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
*/
function createAccount(address _authorizedAddress, address _cosigner, address _recoveryAddress, uint256 _salt)
public
returns (BloctoAccount ret)
{
address addr = getAddress(_cosigner, _recoveryAddress, _salt);
uint256 codeSize = addr.code.length;
if (codeSize > 0) {
return BloctoAccount(payable(addr));
}
bytes32 salt = keccak256(abi.encodePacked(_salt, _cosigner, _recoveryAddress));
// for consistent address
BloctoAccountProxy newProxy = new BloctoAccountProxy{salt: salt}(address(this));
newProxy.initImplementation(bloctoAccountImplementation);
ret = BloctoAccount(payable(address(newProxy)));
ret.init(_authorizedAddress, uint256(uint160(_cosigner)), _recoveryAddress);
emit WalletCreated(address(ret), _authorizedAddress, false);
}

function createAccount2(
bytes memory _authorizedAddresses,
address _cosigner,
address _recoveryAddress,
uint256 _salt
) public returns (BloctoAccount ret) {
require(
_authorizedAddresses.length / 20 > 0 && _authorizedAddresses.length % 20 == 0, "invalid address byte array"
);

address addr = getAddress(_cosigner, _recoveryAddress, _salt);
uint256 codeSize = addr.code.length;
if (codeSize > 0) {
return BloctoAccount(payable(addr));
}
bytes32 salt = keccak256(abi.encodePacked(_salt, _cosigner, _recoveryAddress));
// for consistent address
BloctoAccountProxy newProxy = new BloctoAccountProxy{salt: salt}(address(this));
newProxy.initImplementation(bloctoAccountImplementation);
ret = BloctoAccount(payable(address(newProxy)));
ret.init2(_authorizedAddresses, uint256(uint160(_cosigner)), _recoveryAddress);

address firstAuthorizedAddress;
assembly {
firstAuthorizedAddress := mload(add(_authorizedAddresses, 20))
}
emit WalletCreated(address(ret), firstAuthorizedAddress, false);
}

/**
* calculate the counterfactual address of this account as it would be returned by createAccount()
*/
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(this))))
);
}

/**
* set the implementation of the BloctoAccountProxy
* @param _bloctoAccountImplementation target to send to
*/
function setImplementation(address _bloctoAccountImplementation) public onlyOwner {
bloctoAccountImplementation = _bloctoAccountImplementation;
}

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

/**
* 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);
}

/**
* add stake for this factory.
* This method can also carry eth value to add to the current stake.
* @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);
}
}
18 changes: 18 additions & 0 deletions contracts/BloctoAccountProxy.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.12;

import "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";

contract BloctoAccountProxy is ERC1967Proxy, Initializable {
constructor(address _logic) ERC1967Proxy(_logic, new bytes(0)) {}

/**
* initialize BloctoAccountProxy for adding the implementation address
* @param implementation implementation address
*/
function initImplementation(address implementation) public initializer {
require(Address.isContract(implementation), "ERC1967: new implementation is not a contract");
StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value = implementation;
}
}
27 changes: 27 additions & 0 deletions contracts/CoreWallet/BytesExtractSignature.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.12;

/// @title ECDSA is a library that contains useful methods for working with ECDSA signatures
library BytesExtractSignature {
/// @notice Extracts the r, s, and v components from the `sigData` field starting from the `offset`
/// @dev Note: does not do any bounds checking on the arguments!
/// @param sigData the signature data; could be 1 or more packed signatures.
/// @param offset the offset in sigData from which to start unpacking the signature components.
function extractSignature(
bytes memory sigData,
uint256 offset
) internal pure returns (bytes32 r, bytes32 s, uint8 v) {
// Divide the signature in r, s and v variables
// ecrecover takes the signature parameters, and the only way to get them
// currently is to use assembly.
// solium-disable-next-line security/no-inline-assembly
assembly {
let dataPointer := add(sigData, offset)
r := mload(add(dataPointer, 0x20))
s := mload(add(dataPointer, 0x40))
v := byte(0, mload(add(dataPointer, 0x60)))
}

return (r, s, v);
}
}
Loading

0 comments on commit 257ec83

Please sign in to comment.