Skip to content

Commit

Permalink
AA-176: Add ERC-165 "supportsInterface" to the EntryPoint (#331)
Browse files Browse the repository at this point in the history
* AA-176: Add ERC-165 "supportsInterface" to the EntryPoint
  • Loading branch information
forshtat authored Aug 21, 2023
1 parent e5658d7 commit da37cc2
Show file tree
Hide file tree
Showing 4 changed files with 85 additions and 13 deletions.
15 changes: 14 additions & 1 deletion contracts/core/EntryPoint.sol
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,16 @@ import "./StakeManager.sol";
import "./SenderCreator.sol";
import "./Helpers.sol";
import "./NonceManager.sol";

// we also require '@gnosis.pm/safe-contracts' and both libraries have 'IERC165.sol', leading to conflicts
import "@openzeppelin/contracts/utils/introspection/ERC165.sol" as OpenZeppelin;
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";

/*
* Account-Abstraction (EIP-4337) singleton EntryPoint implementation.
* Only one instance required on each chain.
*/
contract EntryPoint is IEntryPoint, StakeManager, NonceManager, ReentrancyGuard {
contract EntryPoint is IEntryPoint, StakeManager, NonceManager, ReentrancyGuard, OpenZeppelin.ERC165 {

using UserOperationLib for UserOperation;

Expand All @@ -39,6 +42,16 @@ contract EntryPoint is IEntryPoint, StakeManager, NonceManager, ReentrancyGuard
*/
uint256 public constant SIG_VALIDATION_FAILED = 1;

/// @inheritdoc OpenZeppelin.IERC165
function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
// note: solidity "type(IEntryPoint).interfaceId" is without inherited methods but we want to check everything
return interfaceId == (type(IEntryPoint).interfaceId ^ type(IStakeManager).interfaceId ^ type(INonceManager).interfaceId) ||
interfaceId == type(IEntryPoint).interfaceId ||
interfaceId == type(IStakeManager).interfaceId ||
interfaceId == type(INonceManager).interfaceId ||
super.supportsInterface(interfaceId);
}

/**
* Compensate the caller's beneficiary address with the collected fees of all UserOperations.
* @param beneficiary - The address to receive the fees.
Expand Down
24 changes: 12 additions & 12 deletions reports/gas-checker.txt
Original file line number Diff line number Diff line change
Expand Up @@ -12,28 +12,28 @@
║ │ │ │ (delta for │ (compared to ║
║ │ │ │ one UserOp) │ account.exec()) ║
╟────────────────────────────────┼───────┼───────────────┼────────────────┼─────────────────────╢
║ simple │ 1 │ 81899 │ │ ║
║ simple │ 1 │ 81943 │ │ ║
╟────────────────────────────────┼───────┼───────────────┼────────────────┼─────────────────────╢
║ simple - diff from previous │ 2 │ │ 4418615172
║ simple - diff from previous │ 2 │ │ 4420815194
╟────────────────────────────────┼───────┼───────────────┼────────────────┼─────────────────────╢
║ simple │ 10 │ 479702 │ │ ║
║ simple │ 10 │ 479920 │ │ ║
╟────────────────────────────────┼───────┼───────────────┼────────────────┼─────────────────────╢
║ simple - diff from previous │ 11 │ │ 4422215208
║ simple - diff from previous │ 11 │ │ 4428015266
╟────────────────────────────────┼───────┼───────────────┼────────────────┼─────────────────────╢
║ simple paymaster │ 1 │ 88182 │ │ ║
║ simple paymaster │ 1 │ 88202 │ │ ║
╟────────────────────────────────┼───────┼───────────────┼────────────────┼─────────────────────╢
║ simple paymaster with diff │ 2 │ │ 4317514161
║ simple paymaster with diff │ 2 │ │ 4319714183
╟────────────────────────────────┼───────┼───────────────┼────────────────┼─────────────────────╢
║ simple paymaster │ 10 │ 476938 │ │ ║
║ simple paymaster │ 10 │ 477156 │ │ ║
╟────────────────────────────────┼───────┼───────────────┼────────────────┼─────────────────────╢
║ simple paymaster with diff │ 11 │ │ 4323414220
║ simple paymaster with diff │ 11 │ │ 4318414170
╟────────────────────────────────┼───────┼───────────────┼────────────────┼─────────────────────╢
║ big tx 5k │ 1 │ 182932 │ │ ║
║ big tx 5k │ 1 │ 183000 │ │ ║
╟────────────────────────────────┼───────┼───────────────┼────────────────┼─────────────────────╢
║ big tx - diff from previous │ 2 │ │ 14472119461
║ big tx - diff from previous │ 2 │ │ 14471919459
╟────────────────────────────────┼───────┼───────────────┼────────────────┼─────────────────────╢
║ big tx 5k │ 10 │ 1485370 │ │ ║
║ big tx 5k │ 10 │ 1485612 │ │ ║
╟────────────────────────────────┼───────┼───────────────┼────────────────┼─────────────────────╢
║ big tx - diff from previous │ 11 │ │ 14471019450
║ big tx - diff from previous │ 11 │ │ 14472019460
╚════════════════════════════════╧═══════╧═══════════════╧════════════════╧═════════════════════╝

16 changes: 16 additions & 0 deletions src/Utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { Interface, JsonFragment } from '@ethersproject/abi'

export function getERC165InterfaceID (abi: JsonFragment[]): string {
let interfaceId =
abi
.filter(it => it.type === 'function' && it.name != null)
.map(it => {
const iface = new Interface([it])
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
return iface.getSighash(it.name!)
})
.map((x) => parseInt(x, 16))
.reduce((x, y) => x ^ y)
interfaceId = interfaceId > 0 ? interfaceId : 0xFFFFFFFF + interfaceId + 1
return '0x' + interfaceId.toString(16).padStart(8, '0')
}
43 changes: 43 additions & 0 deletions test/entrypoint.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,11 @@ import {
TestSignatureAggregator__factory,
MaliciousAccount__factory,
TestWarmColdAccount__factory,
IEntryPoint__factory,
SimpleAccountFactory__factory,
IStakeManager__factory,
INonceManager__factory,
EntryPoint__factory,
TestPaymasterRevertCustomError__factory
} from '../typechain'
import {
Expand Down Expand Up @@ -53,6 +58,7 @@ import { arrayify, defaultAbiCoder, hexConcat, hexZeroPad, parseEther } from 'et
import { debugTransaction } from './debugTx'
import { BytesLike } from '@ethersproject/bytes'
import { toChecksumAddress } from 'ethereumjs-util'
import { getERC165InterfaceID } from '../src/Utils'

describe('EntryPoint', function () {
let entryPoint: EntryPoint
Expand Down Expand Up @@ -1362,4 +1368,41 @@ describe('EntryPoint', function () {
})
})
})

describe('ERC-165', function () {
it('should return true for IEntryPoint interface ID', async function () {
const iepInterface = IEntryPoint__factory.createInterface()
const iepInterfaceID = getERC165InterfaceID([...iepInterface.fragments])
expect(await entryPoint.supportsInterface(iepInterfaceID)).to.equal(true)
})

it('should return true for pure EntryPoint, IStakeManager and INonceManager interface IDs', async function () {
const epInterface = EntryPoint__factory.createInterface()
const smInterface = IStakeManager__factory.createInterface()
const nmInterface = INonceManager__factory.createInterface()
// note: manually generating "pure", solidity-like "type(IEntryPoint).interfaceId" without inherited methods
const epPureInterfaceFunctions = [
...epInterface.fragments.filter(it => [
'handleOps',
'handleAggregatedOps',
'getUserOpHash',
'getSenderAddress',
'simulateValidation',
'simulateHandleOp'
].includes(it.name))
]
const epPureInterfaceID = getERC165InterfaceID(epPureInterfaceFunctions)
const smInterfaceID = getERC165InterfaceID([...smInterface.fragments])
const nmInterfaceID = getERC165InterfaceID([...nmInterface.fragments])
expect(await entryPoint.supportsInterface(smInterfaceID)).to.equal(true)
expect(await entryPoint.supportsInterface(nmInterfaceID)).to.equal(true)
expect(await entryPoint.supportsInterface(epPureInterfaceID)).to.equal(true)
})

it('should return false for a wrong interface', async function () {
const saInterface = SimpleAccountFactory__factory.createInterface()
const entryPointInterfaceID = getERC165InterfaceID([...saInterface.fragments])
expect(await entryPoint.supportsInterface(entryPointInterfaceID)).to.equal(false)
})
})
})

0 comments on commit da37cc2

Please sign in to comment.