-
Notifications
You must be signed in to change notification settings - Fork 670
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
f72fa45
commit ff5ffd7
Showing
5 changed files
with
169 additions
and
20 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
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
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,75 @@ | ||
// SPDX-License-Identifier: GPL-3.0 | ||
|
||
/* solhint-disable one-contract-per-file */ | ||
/* solhint-disable avoid-low-level-calls */ | ||
pragma solidity ^0.8.15; | ||
|
||
import "@openzeppelin/contracts/utils/Create2.sol"; | ||
import "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; | ||
|
||
import "../samples/SimpleAccount.sol"; | ||
import "../interfaces/IAccountExecute.sol"; | ||
|
||
/** | ||
* a sample account with execUserOp. | ||
* Note that this account does nothing special with the userop, just extract | ||
* call to execute. In theory, such account can reference the signature, the hash, etc. | ||
*/ | ||
contract TestExecAccount is SimpleAccount, IAccountExecute { | ||
|
||
constructor(IEntryPoint anEntryPoint) SimpleAccount(anEntryPoint){ | ||
} | ||
|
||
event Executed(UserOperation userOp, bytes innerCallRet); | ||
|
||
function executeUserOp(UserOperation calldata userOp, bytes32 /*userOpHash*/) external { | ||
_requireFromEntryPointOrOwner(); | ||
|
||
// read from the userOp.callData, but skip the "magic" prefix (executeUserOp sig), | ||
// which caused it to call this method. | ||
bytes calldata innerCall = userOp.callData[4 :]; | ||
|
||
bytes memory innerCallRet; | ||
if (innerCall.length > 0) { | ||
(address target, bytes memory data) = abi.decode(innerCall, (address, bytes)); | ||
bool success; | ||
(success, innerCallRet) = target.call(data); | ||
require(success, "inner call failed"); | ||
} | ||
|
||
emit Executed(userOp, innerCallRet); | ||
} | ||
} | ||
|
||
contract TestExecAccountFactory { | ||
TestExecAccount public immutable accountImplementation; | ||
|
||
constructor(IEntryPoint _entryPoint) { | ||
accountImplementation = new TestExecAccount(_entryPoint); | ||
} | ||
|
||
function createAccount(address owner, uint256 salt) public returns (address ret) { | ||
address addr = getAddress(owner, salt); | ||
uint codeSize = addr.code.length; | ||
if (codeSize > 0) { | ||
return addr; | ||
} | ||
ret = address(new ERC1967Proxy{salt: bytes32(salt)}( | ||
address(accountImplementation), | ||
abi.encodeCall(SimpleAccount.initialize, (owner)) | ||
)); | ||
} | ||
|
||
/** | ||
* calculate the counterfactual address of this account as it would be returned by createAccount() | ||
*/ | ||
function getAddress(address owner, uint256 salt) public view returns (address) { | ||
return Create2.computeAddress(bytes32(salt), keccak256(abi.encodePacked( | ||
type(ERC1967Proxy).creationCode, | ||
abi.encode( | ||
address(accountImplementation), | ||
abi.encodeCall(SimpleAccount.initialize, (owner)) | ||
) | ||
))); | ||
} | ||
} |
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
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,56 @@ | ||
import { before } from 'mocha' | ||
import { | ||
EntryPoint, | ||
TestExecAccount, | ||
TestExecAccount__factory, | ||
TestExecAccountFactory__factory | ||
} from '../typechain' | ||
import { createAccountOwner, deployEntryPoint, fund, objdump } from './testutils' | ||
import { fillAndSign } from './UserOp' | ||
import { Signer, Wallet } from 'ethers' | ||
import { ethers } from 'hardhat' | ||
import { defaultAbiCoder, hexConcat, hexStripZeros } from 'ethers/lib/utils' | ||
import { expect } from 'chai' | ||
|
||
describe('IAccountExecute', () => { | ||
let ethersSigner: Signer | ||
let entryPoint: EntryPoint | ||
let account: TestExecAccount | ||
let owner: Wallet | ||
before(async () => { | ||
const provider = ethers.provider | ||
ethersSigner = provider.getSigner() | ||
entryPoint = await deployEntryPoint() | ||
const factory = await new TestExecAccountFactory__factory(ethersSigner).deploy(entryPoint.address) | ||
owner = createAccountOwner() | ||
await factory.createAccount(owner.getAddress(), 0) | ||
const accountAddress = await factory.callStatic.createAccount(owner.getAddress(), 0) | ||
account = TestExecAccount__factory.connect(accountAddress, provider) | ||
await fund(accountAddress) | ||
}) | ||
|
||
it('should execute ', async () => { | ||
const execSig = account.interface.getSighash('executeUserOp') | ||
// innerCall, as TestExecAccount.executeUserOp will try to decode it: | ||
const innerCall = defaultAbiCoder.encode(['address', 'bytes'], [ | ||
account.address, | ||
account.interface.encodeFunctionData('entryPoint') | ||
]) | ||
|
||
const userOp = await fillAndSign({ | ||
sender: account.address, | ||
callGasLimit: 100000, // normal estimate also chokes on this callData | ||
callData: hexConcat([execSig, innerCall]) | ||
}, owner, entryPoint) | ||
|
||
await entryPoint.handleOps([userOp], ethersSigner.getAddress()) | ||
|
||
const e = | ||
await account.queryFilter(account.filters.Executed()) | ||
|
||
expect(e.length).to.eq(1, "didn't call inner execUserOp (no Executed event)") | ||
console.log(e[0].event, objdump(e[0].args)) | ||
// validate we retrieved the return value of the called "entryPoint()" function: | ||
expect(hexStripZeros(e[0].args.innerCallRet)).to.eq(hexStripZeros(entryPoint.address)) | ||
}) | ||
}) |