@ensuro/account-abstraction 0.0.1 → 0.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
pragma solidity ^0.8.23;
|
|
3
3
|
|
|
4
4
|
import {AccessControl} from "@openzeppelin/contracts/access/AccessControl.sol";
|
|
5
|
+
import {Address} from "@openzeppelin/contracts/utils/Address.sol";
|
|
5
6
|
import {MessageHashUtils} from "@openzeppelin/contracts/utils/cryptography/MessageHashUtils.sol";
|
|
6
7
|
import {BaseAccount} from "@account-abstraction/contracts/core/BaseAccount.sol";
|
|
7
8
|
import {SIG_VALIDATION_SUCCESS, SIG_VALIDATION_FAILED} from "@account-abstraction/contracts/core/Helpers.sol";
|
|
@@ -48,7 +49,7 @@ contract AccessControlAccount is AccessControl, BaseAccount {
|
|
|
48
49
|
*/
|
|
49
50
|
function execute(address dest, uint256 value, bytes calldata func) external {
|
|
50
51
|
_requireFromEntryPointOrExecutor();
|
|
51
|
-
|
|
52
|
+
Address.functionCallWithValue(dest, func, value);
|
|
52
53
|
}
|
|
53
54
|
|
|
54
55
|
/**
|
|
@@ -63,11 +64,11 @@ contract AccessControlAccount is AccessControl, BaseAccount {
|
|
|
63
64
|
if (dest.length != func.length || (value.length != 0 && value.length != func.length)) revert WrongArrayLength();
|
|
64
65
|
if (value.length == 0) {
|
|
65
66
|
for (uint256 i = 0; i < dest.length; i++) {
|
|
66
|
-
|
|
67
|
+
Address.functionCallWithValue(dest[i], func[i], 0);
|
|
67
68
|
}
|
|
68
69
|
} else {
|
|
69
70
|
for (uint256 i = 0; i < dest.length; i++) {
|
|
70
|
-
|
|
71
|
+
Address.functionCallWithValue(dest[i], func[i], value[i]);
|
|
71
72
|
}
|
|
72
73
|
}
|
|
73
74
|
}
|
|
@@ -83,16 +84,6 @@ contract AccessControlAccount is AccessControl, BaseAccount {
|
|
|
83
84
|
return SIG_VALIDATION_SUCCESS;
|
|
84
85
|
}
|
|
85
86
|
|
|
86
|
-
function _call(address target, uint256 value, bytes memory data) internal {
|
|
87
|
-
(bool success, bytes memory result) = target.call{value: value}(data);
|
|
88
|
-
if (!success) {
|
|
89
|
-
// solhint-disable-next-line no-inline-assembly
|
|
90
|
-
assembly {
|
|
91
|
-
revert(add(result, 32), mload(result))
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
|
|
96
87
|
/**
|
|
97
88
|
* check current account deposit in the entryPoint
|
|
98
89
|
*/
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
pragma solidity ^0.8.23;
|
|
3
|
+
|
|
4
|
+
import {AccessManager} from "@openzeppelin/contracts/access/manager/AccessManager.sol";
|
|
5
|
+
import {Address} from "@openzeppelin/contracts/utils/Address.sol";
|
|
6
|
+
import {MessageHashUtils} from "@openzeppelin/contracts/utils/cryptography/MessageHashUtils.sol";
|
|
7
|
+
import {BaseAccount} from "@account-abstraction/contracts/core/BaseAccount.sol";
|
|
8
|
+
import {SIG_VALIDATION_SUCCESS, SIG_VALIDATION_FAILED} from "@account-abstraction/contracts/core/Helpers.sol";
|
|
9
|
+
import {IEntryPoint} from "@account-abstraction/contracts/interfaces/IEntryPoint.sol";
|
|
10
|
+
import {PackedUserOperation} from "@account-abstraction/contracts/interfaces/PackedUserOperation.sol";
|
|
11
|
+
import {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
|
|
12
|
+
import {BytesLib} from "solidity-bytes-utils/contracts/BytesLib.sol";
|
|
13
|
+
|
|
14
|
+
contract AccessManagerAccount is AccessManager, BaseAccount {
|
|
15
|
+
using BytesLib for bytes;
|
|
16
|
+
|
|
17
|
+
IEntryPoint private immutable _entryPoint;
|
|
18
|
+
|
|
19
|
+
bytes4 private constant EXECUTE_SELECTOR = bytes4(keccak256("execute(address,uint256,bytes)"));
|
|
20
|
+
|
|
21
|
+
error OnlyExecuteAllowedFromEntryPoint(bytes4 receivedSelector);
|
|
22
|
+
error OnlyExternalTargets();
|
|
23
|
+
error DelayNotAllowed();
|
|
24
|
+
|
|
25
|
+
/// @inheritdoc BaseAccount
|
|
26
|
+
function entryPoint() public view virtual override returns (IEntryPoint) {
|
|
27
|
+
return _entryPoint;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// solhint-disable-next-line no-empty-blocks
|
|
31
|
+
receive() external payable {}
|
|
32
|
+
|
|
33
|
+
constructor(IEntryPoint anEntryPoint, address initialAdmin) AccessManager(initialAdmin) {
|
|
34
|
+
_entryPoint = anEntryPoint;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* execute a transaction (called directly from owner, or by entryPoint)
|
|
39
|
+
* @param dest destination address to call
|
|
40
|
+
* @param value the value to pass in this call
|
|
41
|
+
* @param func the calldata to pass in this call
|
|
42
|
+
*/
|
|
43
|
+
function execute(address dest, uint256 value, bytes calldata func) external {
|
|
44
|
+
_requireFromEntryPoint();
|
|
45
|
+
Address.functionCallWithValue(dest, func, value);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// hashOperation variant that receives bytes memory
|
|
49
|
+
function _hashOperation(address caller, address target, bytes memory data) internal pure returns (bytes32) {
|
|
50
|
+
return keccak256(abi.encode(caller, target, data));
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function _checkAAExecuteCall(address signer, bytes calldata userOpCallData) internal returns (uint256) {
|
|
54
|
+
(address target, , bytes memory funcCall) = abi.decode(
|
|
55
|
+
userOpCallData[4:userOpCallData.length - 4],
|
|
56
|
+
(address, uint256, bytes)
|
|
57
|
+
);
|
|
58
|
+
(bool immediate, uint32 delay) = canCall(signer, target, bytes4(funcCall.toBytes32(0)));
|
|
59
|
+
if (immediate || delay == 0) return immediate ? SIG_VALIDATION_SUCCESS : SIG_VALIDATION_FAILED;
|
|
60
|
+
_consumeScheduledOp(_hashOperation(signer, target, funcCall));
|
|
61
|
+
return SIG_VALIDATION_SUCCESS;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/// implement template method of BaseAccount
|
|
65
|
+
function _validateSignature(
|
|
66
|
+
PackedUserOperation calldata userOp,
|
|
67
|
+
bytes32 userOpHash
|
|
68
|
+
) internal virtual override returns (uint256 validationData) {
|
|
69
|
+
// First check the initial selector, from EntryPoint only execute and executeBatch are allowed
|
|
70
|
+
bytes4 selector = bytes4(userOp.callData[0:4]);
|
|
71
|
+
if (selector != EXECUTE_SELECTOR) revert OnlyExecuteAllowedFromEntryPoint(selector);
|
|
72
|
+
address target = abi.decode(userOp.callData[4:36], (address));
|
|
73
|
+
// Calls to address(this) are not allowed through AA. It might be possible to implement, but this
|
|
74
|
+
// complicates the testing and it might introduce security issues
|
|
75
|
+
if (target == address(this)) revert OnlyExternalTargets();
|
|
76
|
+
bytes32 hash = MessageHashUtils.toEthSignedMessageHash(userOpHash);
|
|
77
|
+
address recovered = ECDSA.recover(hash, userOp.signature);
|
|
78
|
+
// Check first the signer can call execute
|
|
79
|
+
if (!_checkCanCall(recovered, userOp.callData, false)) return SIG_VALIDATION_FAILED;
|
|
80
|
+
// Then check it can call the specific target/selector
|
|
81
|
+
return _checkAAExecuteCall(recovered, userOp.callData);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* check current account deposit in the entryPoint
|
|
86
|
+
*/
|
|
87
|
+
function getDeposit() public view returns (uint256) {
|
|
88
|
+
return entryPoint().balanceOf(address(this));
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* deposit more funds for this account in the entryPoint
|
|
93
|
+
*/
|
|
94
|
+
function addDeposit() public payable {
|
|
95
|
+
entryPoint().depositTo{value: msg.value}(address(this));
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* @dev Adapted from AccessManaged._checkCanCall, checks a method can be called as if the AccessManagerAccount
|
|
100
|
+
* was an access managed contract (not validating against admin permissions)
|
|
101
|
+
*/
|
|
102
|
+
function _checkCanCall(address caller, bytes calldata data, bool fail) internal returns (bool) {
|
|
103
|
+
(bool immediate, uint32 delay) = canCall(caller, address(this), bytes4(data[0:4]));
|
|
104
|
+
if (!immediate) {
|
|
105
|
+
if (delay > 0) {
|
|
106
|
+
revert DelayNotAllowed();
|
|
107
|
+
// Is not possible to handle scheduled operations, because when target=address(this), schedule
|
|
108
|
+
// doesn't work the same way, otherwise here we should do just
|
|
109
|
+
// _consumeScheduledOp(hashOperation(caller, address(this), data));
|
|
110
|
+
} else {
|
|
111
|
+
if (fail)
|
|
112
|
+
revert AccessManagerUnauthorizedAccount(caller, getTargetFunctionRole(address(this), bytes4(data[0:4])));
|
|
113
|
+
else return false;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
return true;
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* withdraw value from the account's deposit
|
|
120
|
+
* @param withdrawAddress target to send to
|
|
121
|
+
* @param amount to withdraw
|
|
122
|
+
*/
|
|
123
|
+
function withdrawDepositTo(address payable withdrawAddress, uint256 amount) public {
|
|
124
|
+
_checkCanCall(_msgSender(), _msgData(), true);
|
|
125
|
+
entryPoint().withdrawTo(withdrawAddress, amount);
|
|
126
|
+
}
|
|
127
|
+
}
|