Skip to content
Merged
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
26 changes: 0 additions & 26 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -65,29 +65,3 @@ jobs:
- run: yarn install
- run: yarn depcheck
- run: yarn lint

coverage:
runs-on: ubuntu-latest
steps:
- uses: actions/setup-node@v4
with:
node-version: '22'
- uses: actions/checkout@v4
with:
show-progress: false
- uses: actions/cache@v4
with:
path: node_modules
key: ${{ runner.os }}-${{ hashFiles('yarn.lock') }}
- run: yarn install

- run: yarn compile

- run: FORCE_COLOR=1 yarn coverage
- uses: actions/upload-artifact@v4
with:
name: solidity-coverage
path: |
coverage/
coverage.json

17 changes: 12 additions & 5 deletions .solhint.json
Original file line number Diff line number Diff line change
@@ -1,12 +1,19 @@
{
"extends": "solhint:recommended",
"rules": {
"compiler-version": ["error",">=0.7.5"],
"func-visibility": ["off",{"ignoreConstructors":true}],
"custom-errors": ["off"],
"compiler-version": ["error", ">=0.8.28"],
"explicit-types": ["warn", "explicit"],
"no-global-import": ["off"],
"function-max-lines": "off",
"gas-increment-by-one": "off",
"gas-indexed-events": "off",
"gas-strict-inequalities": "off",
"immutable-vars-naming": ["off"],
"mark-callable-contracts": ["off"]
"no-global-import": "off",
"use-natspec": "off",
"func-visibility": [
"off", {
"ignoreConstructors": true
}
]
}
}
21 changes: 15 additions & 6 deletions contracts/accounts/Simple7702Account.sol
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,14 @@ import "../core/BaseAccount.sol";
*/
contract Simple7702Account is BaseAccount, IERC165, IERC1271, ERC1155Holder, ERC721Holder {

// address of entryPoint v0.8
function entryPoint() public pure override returns (IEntryPoint) {
return IEntryPoint(0x4337084D9E255Ff0702461CF8895CE9E3b5Ff108);
IEntryPoint private immutable _entryPoint;

constructor(IEntryPoint anEntryPoint) {
_entryPoint = anEntryPoint;
}

function entryPoint() public view override returns (IEntryPoint) {
return _entryPoint;
}

/**
Expand All @@ -32,7 +37,7 @@ contract Simple7702Account is BaseAccount, IERC165, IERC1271, ERC1155Holder, ERC
return _checkSignature(userOpHash, userOp.signature) ? SIG_VALIDATION_SUCCESS : SIG_VALIDATION_FAILED;
}

function isValidSignature(bytes32 hash, bytes memory signature) public view returns (bytes4 magicValue) {
function isValidSignature(bytes32 hash, bytes memory signature) public virtual view returns (bytes4 magicValue) {
return _checkSignature(hash, signature) ? this.isValidSignature.selector : bytes4(0xffffffff);
}

Expand All @@ -44,11 +49,15 @@ contract Simple7702Account is BaseAccount, IERC165, IERC1271, ERC1155Holder, ERC
require(
msg.sender == address(this) ||
msg.sender == address(entryPoint()),
"not from self or EntryPoint"
NotFromEntryPoint(
msg.sender,
address(this),
address(entryPoint())
)
);
}

function supportsInterface(bytes4 id) public override(ERC1155Holder, IERC165) pure returns (bool) {
function supportsInterface(bytes4 id) public virtual override(ERC1155Holder, IERC165) pure returns (bool) {
return
id == type(IERC165).interfaceId ||
id == type(IAccount).interfaceId ||
Expand Down
27 changes: 22 additions & 5 deletions contracts/accounts/SimpleAccount.sol
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ contract SimpleAccount is BaseAccount, TokenCallbackHandler, UUPSUpgradeable, In
_;
}

error NotOwner(address msgSender, address entity, address owner );
error NotOwnerOrEntryPoint(address msgSender, address entity, address entryPoint, address owner);

/// @inheritdoc BaseAccount
function entryPoint() public view virtual override returns (IEntryPoint) {
return _entryPoint;
Expand All @@ -46,7 +49,14 @@ contract SimpleAccount is BaseAccount, TokenCallbackHandler, UUPSUpgradeable, In

function _onlyOwner() internal view {
// Directly from EOA owner, or through the account itself (which gets redirected through execute())
require(msg.sender == owner || msg.sender == address(this), "only owner");
require(
msg.sender == owner || msg.sender == address(this),
NotOwner(
msg.sender,
address(this),
owner
)
);
}

/**
Expand All @@ -61,12 +71,19 @@ contract SimpleAccount is BaseAccount, TokenCallbackHandler, UUPSUpgradeable, In

function _initialize(address anOwner) internal virtual {
owner = anOwner;
emit SimpleAccountInitialized(_entryPoint, owner);
emit SimpleAccountInitialized(entryPoint(), owner);
}

// Require the function call went through EntryPoint or owner
function _requireForExecute() internal view override virtual {
require(msg.sender == address(entryPoint()) || msg.sender == owner, "account: not Owner or EntryPoint");
require(msg.sender == address(entryPoint()) || msg.sender == owner,
NotOwnerOrEntryPoint(
msg.sender,
address(this),
address(entryPoint()),
owner
)
);
}

/// implement template method of BaseAccount
Expand All @@ -82,7 +99,7 @@ contract SimpleAccount is BaseAccount, TokenCallbackHandler, UUPSUpgradeable, In
/**
* check current account deposit in the entryPoint
*/
function getDeposit() public view returns (uint256) {
function getDeposit() public virtual view returns (uint256) {
return entryPoint().balanceOf(address(this));
}

Expand All @@ -98,7 +115,7 @@ contract SimpleAccount is BaseAccount, TokenCallbackHandler, UUPSUpgradeable, In
* @param withdrawAddress target to send to
* @param amount to withdraw
*/
function withdrawDepositTo(address payable withdrawAddress, uint256 amount) public onlyOwner {
function withdrawDepositTo(address payable withdrawAddress, uint256 amount) public virtual onlyOwner {
entryPoint().withdrawTo(withdrawAddress, amount);
}

Expand Down
14 changes: 11 additions & 3 deletions contracts/accounts/SimpleAccountFactory.sol
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ contract SimpleAccountFactory {
SimpleAccount public immutable accountImplementation;
ISenderCreator public immutable senderCreator;

error NotSenderCreator(address msgSender, address entity, address senderCreator);

constructor(IEntryPoint _entryPoint) {
accountImplementation = new SimpleAccount(_entryPoint);
senderCreator = _entryPoint.senderCreator();
Expand All @@ -28,8 +30,14 @@ contract SimpleAccountFactory {
* 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 owner,uint256 salt) public returns (SimpleAccount ret) {
require(msg.sender == address(senderCreator), "only callable from SenderCreator");
function createAccount(address owner, uint256 salt) public returns (SimpleAccount ret) {
require(msg.sender == address(senderCreator),
NotSenderCreator(
msg.sender,
address(this),
address(senderCreator)
)
);
address addr = getAddress(owner, salt);
uint256 codeSize = addr.code.length;
if (codeSize > 0) {
Expand All @@ -44,7 +52,7 @@ contract SimpleAccountFactory {
/**
* calculate the counterfactual address of this account as it would be returned by createAccount()
*/
function getAddress(address owner,uint256 salt) public view returns (address) {
function getAddress(address owner,uint256 salt) public virtual view returns (address) {
return Create2.computeAddress(bytes32(salt), keccak256(abi.encodePacked(
type(ERC1967Proxy).creationCode,
abi.encode(
Expand Down
7 changes: 6 additions & 1 deletion contracts/core/BaseAccount.sol
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ abstract contract BaseAccount is IAccount {
}

error ExecuteError(uint256 index, bytes error);
error NotFromEntryPoint(address msgSender, address entity, address entryPoint);

/**
* Return the account nonce.
Expand Down Expand Up @@ -94,7 +95,11 @@ abstract contract BaseAccount is IAccount {
function _requireFromEntryPoint() internal view virtual {
require(
msg.sender == address(entryPoint()),
"account: not from EntryPoint"
NotFromEntryPoint(
msg.sender,
address(this),
address(entryPoint())
)
);
}

Expand Down
71 changes: 32 additions & 39 deletions contracts/core/BasePaymaster.sol
Original file line number Diff line number Diff line change
Expand Up @@ -7,28 +7,41 @@ import "@openzeppelin/contracts/access/Ownable2Step.sol";
import "@openzeppelin/contracts/utils/introspection/IERC165.sol";
import "../interfaces/IPaymaster.sol";
import "../interfaces/IEntryPoint.sol";
import "./Stakeable.sol";
import "./UserOperationLib.sol";
/**
* Helper class for creating a paymaster.
* provides helper methods for staking.
* Validates that the postOp is called only by the entryPoint.
*/
abstract contract BasePaymaster is IPaymaster, Ownable2Step {
IEntryPoint public immutable entryPoint;
abstract contract BasePaymaster is IPaymaster, Stakeable {
IEntryPoint internal immutable _entryPoint;

uint256 internal constant PAYMASTER_VALIDATION_GAS_OFFSET = UserOperationLib.PAYMASTER_VALIDATION_GAS_OFFSET;
uint256 internal constant PAYMASTER_POSTOP_GAS_OFFSET = UserOperationLib.PAYMASTER_POSTOP_GAS_OFFSET;
uint256 internal constant PAYMASTER_DATA_OFFSET = UserOperationLib.PAYMASTER_DATA_OFFSET;

constructor(IEntryPoint _entryPoint) Ownable(msg.sender) {
_validateEntryPointInterface(_entryPoint);
entryPoint = _entryPoint;
error NotFromEntryPoint(address msgSender, address entity,address entryPoint);
error ERC165Error(address entryPoint, bytes4 interfaceId);
error MustOverride();

constructor(IEntryPoint __entryPoint, address owner) Ownable(owner) {
_validateEntryPointInterface(__entryPoint);
_entryPoint = __entryPoint;
}

function entryPoint() public view override returns (IEntryPoint) {
return _entryPoint;
}

// Sanity check: make sure this EntryPoint was compiled against the same
// IEntryPoint of this paymaster
function _validateEntryPointInterface(IEntryPoint _entryPoint) internal virtual {
require(IERC165(address(_entryPoint)).supportsInterface(type(IEntryPoint).interfaceId), "IEntryPoint interface mismatch");
function _validateEntryPointInterface(IEntryPoint __entryPoint) internal virtual {
bytes4 epInterfaceId = type(IEntryPoint).interfaceId;
require(
IERC165(address(__entryPoint)).supportsInterface(epInterfaceId),
ERC165Error(address(__entryPoint), epInterfaceId)
);
}

/// @inheritdoc IPaymaster
Expand Down Expand Up @@ -87,14 +100,14 @@ abstract contract BasePaymaster is IPaymaster, Ownable2Step {
) internal virtual {
(mode, context, actualGasCost, actualUserOpFeePerGas); // unused params
// subclass must override this method if validatePaymasterUserOp returns a context
revert("must override");
revert MustOverride();
}

/**
* Add a deposit for this paymaster, used for paying for transaction fees.
*/
function deposit() public payable {
entryPoint.depositTo{value: msg.value}(address(this));
entryPoint().depositTo{value: msg.value}(address(this));
}

/**
Expand All @@ -106,46 +119,26 @@ abstract contract BasePaymaster is IPaymaster, Ownable2Step {
address payable withdrawAddress,
uint256 amount
) public onlyOwner {
entryPoint.withdrawTo(withdrawAddress, amount);
}

/**
* Add stake for this paymaster.
* This method can also carry eth value to add to the current stake.
* @param unstakeDelaySec - The unstake delay for this paymaster. Can only be increased.
*/
function addStake(uint32 unstakeDelaySec) external payable onlyOwner {
entryPoint.addStake{value: msg.value}(unstakeDelaySec);
entryPoint().withdrawTo(withdrawAddress, amount);
}

/**
* Return current paymaster's deposit on the entryPoint.
*/
function getDeposit() public view returns (uint256) {
return entryPoint.balanceOf(address(this));
}

/**
* Unlock the stake, in order to withdraw it.
* The paymaster can't serve requests once unlocked, until it calls addStake again
*/
function unlockStake() external onlyOwner {
entryPoint.unlockStake();
}

/**
* Withdraw the entire paymaster's stake.
* stake must be unlocked first (and then wait for the unstakeDelay to be over)
* @param withdrawAddress - The address to send withdrawn value.
*/
function withdrawStake(address payable withdrawAddress) external onlyOwner {
entryPoint.withdrawStake(withdrawAddress);
function getDeposit() public virtual view returns (uint256) {
return _entryPoint.balanceOf(address(this));
}

/**
* Validate the call is made from a valid entrypoint
*/
function _requireFromEntryPoint() internal virtual {
require(msg.sender == address(entryPoint), "Sender not EntryPoint");
require(msg.sender == address(entryPoint()),
NotFromEntryPoint(
msg.sender,
address(this),
address(entryPoint())
)
);
}
}
11 changes: 7 additions & 4 deletions contracts/core/Eip7702Support.sol
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
pragma solidity ^0.8.28;
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.28;

// solhint-disable no-inline-assembly

import "../interfaces/PackedUserOperation.sol";
import "../core/UserOperationLib.sol";

library Eip7702Support {

error Eip7702SenderWithoutCode(address sender);
error Eip7702SenderNotDelegate(address sender);

// EIP-7702 code prefix before delegate address.
bytes3 internal constant EIP7702_PREFIX = 0xef0100;

Expand Down Expand Up @@ -72,9 +76,8 @@ library Eip7702Support {
// To be a valid EIP-7702 delegate, the first 3 bytes are EIP7702_PREFIX
// followed by the delegate address
if (bytes3(senderCode) != EIP7702_PREFIX) {
// instead of just "not an EIP-7702 delegate", if some info.
require(sender.code.length > 0, "sender has no code");
revert("not an EIP-7702 delegate");
require(sender.code.length > 0, Eip7702SenderWithoutCode(sender));
revert Eip7702SenderNotDelegate(sender);
}
return address(bytes20(senderCode << 24));
}
Expand Down
Loading