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

AA-466 // Prevent initcode front-running #233

Draft
wants to merge 1 commit into
base: dev
Choose a base branch
from
Draft
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
24 changes: 11 additions & 13 deletions contracts/common/Stakeable.sol
Original file line number Diff line number Diff line change
Expand Up @@ -28,33 +28,31 @@ contract Stakeable is Ownable, IStakeable {
/// @notice Error thrown when an invalid EntryPoint address is provided.
error InvalidEntryPointAddress();

constructor(address newOwner) {
address public immutable ENTRY_POINT;

constructor(address newOwner, address entryPoint) {
_setOwner(newOwner);
require(entryPoint != address(0), InvalidEntryPointAddress());
ENTRY_POINT = entryPoint;
}

/// @notice Stakes a certain amount of Ether on an EntryPoint.
/// @dev The contract should have enough Ether to cover the stake.
/// @param epAddress The address of the EntryPoint where the stake is added.
/// @param unstakeDelaySec The delay in seconds before the stake can be unlocked.
function addStake(address epAddress, uint32 unstakeDelaySec) external payable onlyOwner {
require(epAddress != address(0), InvalidEntryPointAddress());
IEntryPoint(epAddress).addStake{ value: msg.value }(unstakeDelaySec);
function addStake(uint32 unstakeDelaySec) external payable onlyOwner {
IEntryPoint(ENTRY_POINT).addStake{ value: msg.value }(unstakeDelaySec);
}

/// @notice Unlocks the stake on an EntryPoint.
/// @dev This starts the unstaking delay after which funds can be withdrawn.
/// @param epAddress The address of the EntryPoint from which the stake is to be unlocked.
function unlockStake(address epAddress) external onlyOwner {
require(epAddress != address(0), InvalidEntryPointAddress());
IEntryPoint(epAddress).unlockStake();
function unlockStake() external onlyOwner {
IEntryPoint(ENTRY_POINT).unlockStake();
}

/// @notice Withdraws the stake from an EntryPoint to a specified address.
/// @dev This can only be done after the unstaking delay has passed since the unlock.
/// @param epAddress The address of the EntryPoint where the stake is withdrawn from.
/// @param withdrawAddress The address to receive the withdrawn stake.
function withdrawStake(address epAddress, address payable withdrawAddress) external onlyOwner {
require(epAddress != address(0), InvalidEntryPointAddress());
IEntryPoint(epAddress).withdrawStake(withdrawAddress);
function withdrawStake(address payable withdrawAddress) external onlyOwner {
IEntryPoint(ENTRY_POINT).withdrawStake(withdrawAddress);
}
}
2 changes: 1 addition & 1 deletion contracts/factory/BiconomyMetaFactory.sol
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ contract BiconomyMetaFactory is Stakeable {

/// @notice Constructor to set the owner of the contract.
/// @param owner_ The address of the owner.
constructor(address owner_) Stakeable(owner_) {
constructor(address owner_, address entryPoint) Stakeable(owner_, entryPoint) {
require(owner_ != address(0), ZeroAddressNotAllowed());
}

Expand Down
26 changes: 24 additions & 2 deletions contracts/factory/K1ValidatorFactory.sol
Original file line number Diff line number Diff line change
Expand Up @@ -46,12 +46,24 @@ contract K1ValidatorFactory is Stakeable {
/// @notice Error thrown when a zero address is provided for the implementation, K1 validator, or bootstrapper.
error ZeroAddressNotAllowed();

/// @notice Error thrown when the createAccount function is called by a non-EntryPoint.
error EntryPointOnly();

/// @notice Constructor to set the immutable variables.
/// @param implementation The address of the Nexus implementation to be used for all deployments.
/// @param factoryOwner The address of the factory owner.
/// @param k1Validator The address of the K1 Validator module to be used for all deployments.
/// @param bootstrapper The address of the Bootstrapper module to be used for all deployments.
constructor(address implementation, address factoryOwner, address k1Validator, NexusBootstrap bootstrapper, IERC7484 registry) Stakeable(factoryOwner) {
constructor(
address implementation,
address factoryOwner,
address entryPoint,
address k1Validator,
NexusBootstrap bootstrapper,
IERC7484 registry
)
Stakeable(factoryOwner, entryPoint)
{
require(
!(implementation == address(0) || k1Validator == address(0) || address(bootstrapper) == address(0) || factoryOwner == address(0)),
ZeroAddressNotAllowed()
Expand All @@ -62,7 +74,7 @@ contract K1ValidatorFactory is Stakeable {
REGISTRY = registry;
}

/// @notice Creates a new Nexus with a specific validator and initialization data.
/// @notice Creates a new Nexus with K1 validator and initialization data.
/// @param eoaOwner The address of the EOA owner of the Nexus.
/// @param index The index of the Nexus.
/// @param attesters The list of attesters for the Nexus.
Expand All @@ -84,6 +96,16 @@ contract K1ValidatorFactory is Stakeable {
return account;
}

/// @notice Creates a new Nexus with K1 validator and initialization data by the EntryPoint.
/// @dev same as createAccount but should be called by the EntryPoint
/// Wallets should use this method to prevent front-running userOp.initcode execution which can lead the whole userOp to fail
/// See AA-466 for more details https://github.com/eth-infinitism/account-abstraction/pull/514
function createAccountByEP(address eoaOwner, uint256 index, address[] calldata attesters, uint8 threshold) external payable returns (address payable) {
require(msg.sender == IEntryPoint(ENTRY_POINT).senderCreator(), EntryPointOnly());
return createAccount(eoaOwner, index, attesters, threshold);
}

/// @notice Computes the expected address of a Nexus contract using the factory's deterministic deployment algorithm.
/// @notice Computes the expected address of a Nexus contract using the factory's deterministic deployment algorithm.
/// @param eoaOwner The address of the EOA owner of the Nexus.
/// @param index The index of the Nexus.
Expand Down
2 changes: 1 addition & 1 deletion contracts/factory/NexusAccountFactory.sol
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ contract NexusAccountFactory is Stakeable, INexusFactory {
/// @notice Constructor to set the smart account implementation address and the factory owner.
/// @param implementation_ The address of the Nexus implementation to be used for all deployments.
/// @param owner_ The address of the owner of the factory.
constructor(address implementation_, address owner_) Stakeable(owner_) {
constructor(address implementation_, address owner_, address entryPoint) Stakeable(owner_, entryPoint) {
require(implementation_ != address(0), ImplementationAddressCanNotBeZero());
require(owner_ != address(0), ZeroAddressNotAllowed());
ACCOUNT_IMPLEMENTATION = implementation_;
Expand Down
4 changes: 3 additions & 1 deletion contracts/factory/RegistryFactory.sol
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,9 @@ contract RegistryFactory is Stakeable, INexusFactory {
/// @notice Constructor to set the smart account implementation address and owner.
/// @param implementation_ The address of the Nexus implementation to be used for all deployments.
/// @param owner_ The address of the owner of the factory.
constructor(address implementation_, address owner_, IERC7484 registry_, address[] memory attesters_, uint8 threshold_) Stakeable(owner_) {
constructor(address implementation_, address owner_, address entryPoint, IERC7484 registry_, address[] memory attesters_, uint8 threshold_)
Stakeable(owner_, entryPoint)
{
require(implementation_ != address(0), ImplementationAddressCanNotBeZero());
require(owner_ != address(0), ZeroAddressNotAllowed());
require(threshold_ <= attesters_.length, InvalidThreshold(threshold_, attesters_.length));
Expand Down
9 changes: 3 additions & 6 deletions contracts/interfaces/common/IStakeable.sol
Original file line number Diff line number Diff line change
Expand Up @@ -23,18 +23,15 @@ pragma solidity ^0.8.27;
interface IStakeable {
/// @notice Stakes a certain amount of Ether on an EntryPoint.
/// @dev The contract should have enough Ether to cover the stake.
/// @param epAddress The address of the EntryPoint where the stake is added.
/// @param unstakeDelaySec The delay in seconds before the stake can be unlocked.
function addStake(address epAddress, uint32 unstakeDelaySec) external payable;
function addStake(uint32 unstakeDelaySec) external payable;

/// @notice Unlocks the stake on an EntryPoint.
/// @dev This starts the unstaking delay after which funds can be withdrawn.
/// @param epAddress The address of the EntryPoint from which the stake is to be unlocked.
function unlockStake(address epAddress) external;
function unlockStake() external;

/// @notice Withdraws the stake from an EntryPoint to a specified address.
/// @dev This can only be done after the unstaking delay has passed since the unlock.
/// @param epAddress The address of the EntryPoint where the stake is withdrawn from.
/// @param withdrawAddress The address to receive the withdrawn stake.
function withdrawStake(address epAddress, address payable withdrawAddress) external;
function withdrawStake(address payable withdrawAddress) external;
}
Loading