-
Notifications
You must be signed in to change notification settings - Fork 32
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add AccessPoint, AccessRegistry, WithdrawWorkflow contracts and Signa…
…turePaymaster, update interfaces and tests
- Loading branch information
Showing
9 changed files
with
988 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,159 @@ | ||
// SPDX-License-Identifier: MIT | ||
pragma solidity ^0.8.18; | ||
|
||
import "@openzeppelin/contracts/utils/Address.sol"; | ||
import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; | ||
import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; | ||
import "@openzeppelin/contracts/interfaces/IERC20.sol"; | ||
|
||
import "@aa/core/BaseAccount.sol"; | ||
import "@aa/core/UserOperationLib.sol"; | ||
import "@aa/samples/callback/TokenCallbackHandler.sol"; | ||
|
||
import "../libraries/ByteSignature.sol"; | ||
import "../interfaces/IAccessPoint.sol"; | ||
import "../interfaces/IAccessRegistry.sol"; | ||
|
||
/** | ||
* @title AccessPoint | ||
*/ | ||
contract AccessPoint is | ||
IAccessPoint, | ||
Initializable, | ||
BaseAccount, | ||
TokenCallbackHandler | ||
{ | ||
using UserOperationLib for UserOperation; | ||
using ECDSA for bytes32; | ||
|
||
/* ============ State Variables ============ */ | ||
|
||
address public owner; | ||
|
||
IAccessRegistry public immutable override registry; | ||
IEntryPoint private immutable _entryPoint; | ||
|
||
/* ============ Modifiers ============ */ | ||
|
||
modifier onlyFromEntryPointOrOwner(address target) { | ||
_onlyFromEntryPointOrOwner(target); | ||
_; | ||
} | ||
|
||
// Require the function call went through EntryPoint or owner | ||
function _onlyFromEntryPointOrOwner(address target) internal view { | ||
// Check that the caller is either the owner or an envoy with permission. | ||
if (!(msg.sender == address(entryPoint()) || msg.sender == owner)) { | ||
revert ExecutionUnauthorized(owner, msg.sender, target); | ||
} | ||
} | ||
|
||
/* ============ Constructor & Initializers ============ */ | ||
|
||
/// @notice Creates the proxy by fetching the constructor params from the registry, optionally delegate calling | ||
/// to a target contract if one is provided. | ||
/// @dev The rationale of this approach is to have the proxy's CREATE2 address not depend on any constructor params. | ||
constructor(IEntryPoint entryPoint_, IAccessRegistry registry_) { | ||
registry = registry_; | ||
_entryPoint = entryPoint_; | ||
_disableInitializers(); | ||
} | ||
|
||
/** | ||
* @dev The _entryPoint member is immutable, to reduce gas consumption. To upgrade EntryPoint, | ||
* a new implementation of AccessPoint must be deployed with the new EntryPoint address. | ||
*/ | ||
function initialize(address owner_) external virtual initializer { | ||
owner = owner_; | ||
} | ||
|
||
/* ============ View Functions ============ */ | ||
|
||
function getNonce() | ||
public | ||
view | ||
virtual | ||
override(BaseAccount, IAccessPoint) | ||
returns (uint256) | ||
{ | ||
return super.getNonce(); | ||
} | ||
|
||
// @inheritdoc BaseAccount | ||
function entryPoint() public view virtual override returns (IEntryPoint) { | ||
return _entryPoint; | ||
} | ||
|
||
/* ============ State Change ============ */ | ||
|
||
/* ============ Fallback Functions ============ */ | ||
|
||
fallback(bytes calldata data) external payable returns (bytes memory) { | ||
revert FallbackIsNotAllowed(data); | ||
} | ||
|
||
/// @dev Called when `msg.value` is not zero and the call data is empty. | ||
receive() external payable {} | ||
|
||
/* ============ External Functions ============ */ | ||
|
||
function execute(address target, bytes calldata data) | ||
external | ||
payable | ||
override | ||
onlyFromEntryPointOrOwner(target) | ||
returns (bytes memory response) | ||
{ | ||
// Delegate call to the target contract, and handle the response. | ||
response = _execute(target, data); | ||
} | ||
|
||
/* ============ Internal Functions ============ */ | ||
|
||
/// @notice Executes a DELEGATECALL to the provided target with the provided data. | ||
/// @dev Shared logic between the constructor and the `execute` function. | ||
function _execute(address target, bytes memory data) | ||
internal | ||
returns (bytes memory response) | ||
{ | ||
if (!registry.isWorkflowAllowed(target)) { | ||
revert WorkflowUnauthorized(target); | ||
} | ||
// Check that the target is a contract. | ||
if (target.code.length == 0) { | ||
revert TargetNotContract(target); | ||
} | ||
|
||
// Delegate call to the target contract. | ||
bool success; | ||
(success, response) = target.delegatecall(data); | ||
|
||
// Log the execution. | ||
emit Execute(target, data, response); | ||
|
||
// Check if the call was successful or not. | ||
if (!success) { | ||
// If there is return data, the delegate call reverted with a reason or a custom error, which we bubble up. | ||
if (response.length > 0) { | ||
assembly { | ||
// The length of the data is at `response`, while the actual data is at `response + 32`. | ||
let returndata_size := mload(response) | ||
revert(add(response, 32), returndata_size) | ||
} | ||
} else { | ||
revert ExecutionReverted(); | ||
} | ||
} | ||
} | ||
|
||
/// @notice Valides that owner signed the user operation | ||
function _validateSignature( | ||
UserOperation calldata userOp, | ||
bytes32 userOpHash | ||
) internal virtual override returns (uint256 validationData) { | ||
bytes32 hash = userOpHash.toEthSignedMessageHash(); | ||
if (owner != ECDSA.recover(hash, userOp.signature)) | ||
return SIG_VALIDATION_FAILED; | ||
return 0; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,197 @@ | ||
// SPDX-License-Identifier: MIT | ||
pragma solidity ^0.8.18; | ||
|
||
import "@openzeppelin/contracts/utils/Address.sol"; | ||
import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; | ||
import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; | ||
import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; | ||
import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; | ||
import "@openzeppelin/contracts/interfaces/IERC20.sol"; | ||
import "@openzeppelin/contracts/utils/Create2.sol"; | ||
import "@openzeppelin/contracts/proxy/beacon/UpgradeableBeacon.sol"; | ||
|
||
import "@aa/core/BaseAccount.sol"; | ||
import "@aa/samples/callback/TokenCallbackHandler.sol"; | ||
|
||
import "../libraries/ByteSignature.sol"; | ||
import "./AccessPoint.sol"; | ||
import "../proxy/SafeBeaconProxy.sol"; | ||
|
||
import "../interfaces/IAccessRegistry.sol"; | ||
|
||
contract AccessRegistry is | ||
Initializable, | ||
UUPSUpgradeable, | ||
OwnableUpgradeable, | ||
IAccessRegistry | ||
{ | ||
/* ============ Constants ============ */ | ||
|
||
/* ============ State Variables ============ */ | ||
uint256 public override factoryVersion; | ||
UpgradeableBeacon public beacon; | ||
|
||
/* ============ Internal storage ============ */ | ||
|
||
mapping(address => IAccessPoint) internal _accessPoints; | ||
mapping(address => bool) internal _workflows; | ||
|
||
/* ============ Modifiers ============ */ | ||
|
||
/// @notice Checks that the caller has a accessPoint. | ||
modifier onlyCallerWithAccessPoint() { | ||
if (address(_accessPoints[msg.sender]) == address(0)) { | ||
revert UserDoesNotHaveAccessPoint(msg.sender); | ||
} | ||
_; | ||
} | ||
|
||
/// @notice Check that the user does not have a accessPoint. | ||
modifier onlyNonAccessPointOwner(address user) { | ||
IAccessPoint accessPoint = _accessPoints[user]; | ||
if (address(accessPoint) != address(0)) { | ||
revert UserHasAccessPoint(user, accessPoint); | ||
} | ||
_; | ||
} | ||
|
||
/* ============ Constructor & Upgrades ============ */ | ||
constructor() { | ||
_disableInitializers(); | ||
} | ||
|
||
/** | ||
* @dev Upgrade calling `upgradeTo()` | ||
*/ | ||
function initialize(IAccessPoint impl) external virtual initializer { | ||
__Ownable_init(); | ||
__UUPSUpgradeable_init(); | ||
beacon = new UpgradeableBeacon(address(impl)); | ||
factoryVersion = 1; | ||
} | ||
|
||
/** | ||
* @dev Upgrade the wallet implementations using the beacon | ||
* @param newImpl The new implementation | ||
*/ | ||
function upgradeAll(IAccessPoint newImpl) external override onlyOwner { | ||
require( | ||
address(newImpl) != address(0) && | ||
address(newImpl) != beacon.implementation(), | ||
"invalid address" | ||
); | ||
factoryVersion++; | ||
emit AccessPointFactoryUpgraded( | ||
beacon.implementation(), | ||
address(newImpl) | ||
); | ||
beacon.upgradeTo(address(newImpl)); | ||
} | ||
|
||
/** | ||
* @dev Authorize the upgrade. Only by an owner. | ||
* @param newImplementation address of the new implementation | ||
*/ | ||
// This function is called by the proxy contract when the factory is upgraded | ||
function _authorizeUpgrade(address newImplementation) | ||
internal | ||
view | ||
override | ||
onlyOwner | ||
{ | ||
(newImplementation); | ||
} | ||
|
||
/* ============ View Functions ============ */ | ||
|
||
/// @inheritdoc IAccessRegistry | ||
function isWorkflowAllowed(address workflow) external view returns (bool) { | ||
return _workflows[workflow]; | ||
} | ||
|
||
/// @inheritdoc IAccessRegistry | ||
function getAccessPoint(address user) | ||
external | ||
view | ||
returns (IAccessPoint accessPoint) | ||
{ | ||
accessPoint = _accessPoints[user]; | ||
} | ||
|
||
/// @inheritdoc IAccessRegistry | ||
function getAddress(address owner) public view override returns (address) { | ||
return | ||
Create2.computeAddress( | ||
bytes32(abi.encodePacked(owner)), | ||
keccak256( | ||
abi.encodePacked( | ||
type(SafeBeaconProxy).creationCode, | ||
abi.encode( | ||
address(beacon), | ||
abi.encodeCall(IAccessPoint.initialize, (owner)) | ||
) | ||
) | ||
) | ||
); | ||
} | ||
|
||
/// @dev TODO:Should be removed. Added because Kinto EntryPoint needs this function. | ||
function getWalletTimestamp(address) external pure returns (uint256) { | ||
return 1; | ||
} | ||
|
||
/* ============ State Change ============ */ | ||
|
||
/// @inheritdoc IAccessRegistry | ||
function disallowWorkflow(address workflow) external { | ||
_workflows[workflow] = false; | ||
emit WorkflowStatusChanged(workflow, false); | ||
} | ||
|
||
/// @inheritdoc IAccessRegistry | ||
function allowWorkflow(address workflow) external { | ||
_workflows[workflow] = true; | ||
emit WorkflowStatusChanged(workflow, true); | ||
} | ||
|
||
/// @inheritdoc IAccessRegistry | ||
function deployFor(address user) | ||
external | ||
override | ||
onlyNonAccessPointOwner(user) | ||
returns (IAccessPoint accessPoint) | ||
{ | ||
return _deploy(user); | ||
} | ||
|
||
/* ============ Internal ============ */ | ||
|
||
/// @dev See `deploy`. | ||
function _deploy(address owner) | ||
internal | ||
returns (IAccessPoint accessPoint) | ||
{ | ||
// Use the address of the owner as the CREATE2 salt. | ||
bytes32 salt = bytes32(abi.encodePacked(owner)); | ||
|
||
// Deploy the accessPoint with CREATE2. | ||
accessPoint = IAccessPoint( | ||
payable( | ||
new SafeBeaconProxy{salt: salt}( | ||
address(beacon), | ||
abi.encodeCall(IAccessPoint.initialize, (owner)) | ||
) | ||
) | ||
); | ||
|
||
// Associate the owner and the accessPoint. | ||
_accessPoints[owner] = accessPoint; | ||
|
||
// Log the creation of the accessPoint. | ||
emit DeployAccessPoint({ | ||
operator: msg.sender, | ||
owner: owner, | ||
accessPoint: accessPoint | ||
}); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
// SPDX-License-Identifier: MIT | ||
pragma solidity ^0.8.18; | ||
|
||
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; | ||
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; | ||
|
||
import {IAccessPoint} from "../../interfaces/IAccessPoint.sol"; | ||
|
||
interface IWrappedNativeAsset is IERC20 { | ||
function deposit() external payable; | ||
|
||
function withdraw(uint256 amount) external; | ||
} | ||
|
||
contract WithdrawWorkflow { | ||
using SafeERC20 for IERC20; | ||
|
||
error NativeWithdrawalFailed(); | ||
|
||
function withdrawERC20(IERC20 asset, uint256 amount) external { | ||
address owner = _getOwner(); | ||
asset.safeTransfer({to: owner, value: amount}); | ||
} | ||
|
||
function withdrawNative(uint256 amount) external { | ||
address owner = _getOwner(); | ||
(bool sent, ) = owner.call{value: amount}(""); | ||
if (!sent) { | ||
revert NativeWithdrawalFailed(); | ||
} | ||
} | ||
|
||
function _getOwner() internal view returns (address) { | ||
return IAccessPoint(address(this)).owner(); | ||
} | ||
} |
Oops, something went wrong.