Skip to content

Commit

Permalink
Add AccessPoint, AccessRegistry, WithdrawWorkflow contracts and Signa…
Browse files Browse the repository at this point in the history
…turePaymaster, update interfaces and tests
  • Loading branch information
ylv-io committed Jan 29, 2024
1 parent a9bb547 commit 827f6f4
Show file tree
Hide file tree
Showing 9 changed files with 988 additions and 0 deletions.
159 changes: 159 additions & 0 deletions src/access/AccessPoint.sol
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);

Check warning on line 47 in src/access/AccessPoint.sol

View check run for this annotation

Codecov / codecov/patch

src/access/AccessPoint.sol#L47

Added line #L47 was not covered by tests
}
}

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

Check warning on line 92 in src/access/AccessPoint.sol

View check run for this annotation

Codecov / codecov/patch

src/access/AccessPoint.sol#L92

Added line #L92 was not covered by tests
}

/// @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 warning on line 120 in src/access/AccessPoint.sol

View check run for this annotation

Codecov / codecov/patch

src/access/AccessPoint.sol#L120

Added line #L120 was not covered by tests
}
// Check that the target is a contract.
if (target.code.length == 0) {
revert TargetNotContract(target);

Check warning on line 124 in src/access/AccessPoint.sol

View check run for this annotation

Codecov / codecov/patch

src/access/AccessPoint.sol#L124

Added line #L124 was not covered by tests
}

// 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();

Check warning on line 144 in src/access/AccessPoint.sol

View check run for this annotation

Codecov / codecov/patch

src/access/AccessPoint.sol#L144

Added line #L144 was not covered by tests
}
}
}

/// @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;

Check warning on line 156 in src/access/AccessPoint.sol

View check run for this annotation

Codecov / codecov/patch

src/access/AccessPoint.sol#L156

Added line #L156 was not covered by tests
return 0;
}
}
197 changes: 197 additions & 0 deletions src/access/AccessRegistry.sol
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(

Check warning on line 84 in src/access/AccessRegistry.sol

View check run for this annotation

Codecov / codecov/patch

src/access/AccessRegistry.sol#L83-L84

Added lines #L83 - L84 were not covered by tests
beacon.implementation(),
address(newImpl)
);
beacon.upgradeTo(address(newImpl));

Check warning on line 88 in src/access/AccessRegistry.sol

View check run for this annotation

Codecov / codecov/patch

src/access/AccessRegistry.sol#L88

Added line #L88 was not covered by tests
}

/**
* @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];

Check warning on line 118 in src/access/AccessRegistry.sol

View check run for this annotation

Codecov / codecov/patch

src/access/AccessRegistry.sol#L118

Added line #L118 was not covered by tests
}

/// @inheritdoc IAccessRegistry
function getAddress(address owner) public view override returns (address) {
return
Create2.computeAddress(

Check warning on line 124 in src/access/AccessRegistry.sol

View check run for this annotation

Codecov / codecov/patch

src/access/AccessRegistry.sol#L123-L124

Added lines #L123 - L124 were not covered by tests
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);

Check warning on line 148 in src/access/AccessRegistry.sol

View check run for this annotation

Codecov / codecov/patch

src/access/AccessRegistry.sol#L147-L148

Added lines #L147 - L148 were not covered by tests
}

/// @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
});
}
}
36 changes: 36 additions & 0 deletions src/access/workflows/WithdrawWorkflow.sol
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();

Check warning on line 29 in src/access/workflows/WithdrawWorkflow.sol

View check run for this annotation

Codecov / codecov/patch

src/access/workflows/WithdrawWorkflow.sol#L29

Added line #L29 was not covered by tests
}
}

function _getOwner() internal view returns (address) {
return IAccessPoint(address(this)).owner();
}
}
Loading

0 comments on commit 827f6f4

Please sign in to comment.