Skip to content

Commit

Permalink
AA-72 bundling module (eth-infinitism#25)
Browse files Browse the repository at this point in the history
- modular bundler support multi-userOp bundles.
- support debug_ api for testing.
- support opcode banning, using geth debug_traceCall module.
- uses new v0.4 contracts.
  • Loading branch information
drortirosh authored Dec 26, 2022
1 parent c36bbc4 commit 270ff68
Show file tree
Hide file tree
Showing 50 changed files with 2,841 additions and 1,300 deletions.
19 changes: 0 additions & 19 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -49,22 +49,6 @@ jobs: # a collection of steps
command: yarn lerna-test | tee /tmp/test-dev-results.log
- store_test_results: # special step to upload test results for display in Test Summary
path: /tmp/test-dev-results.log
test-flow:
working_directory: ~/aa # directory where steps will run
docker: # run the steps with Docker
- image: cimg/node:16.6.2
steps: # a collection of executable commands
- attach_workspace:
at: .
- run: # run hardhat-node as standalone process fork
name: hardhat-node-process
command: yarn hardhat-node
background: true
- run: # run tests
name: test
command: yarn lerna-test-flows | tee /tmp/test-flows-results.log
- store_test_results: # special step to upload test results for display in Test Summary
path: /tmp/test-flow-results.log

lint:
working_directory: ~/aa # directory where steps will run
Expand Down Expand Up @@ -96,9 +80,6 @@ workflows:
- test:
requires:
- build
- test-flow:
requires:
- build
- lint:
requires:
- build
Expand Down
46 changes: 46 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
name: Build
on:
push:
branches:
- '*'
pull_request:
types: [opened, reopened, synchronize]

env:
TS_NODE_TRANSPILE_ONLY: 1
FORCE_COLORS: 1

# todo: extract shared seto/checkout/install/compile, instead of repeat in each job.
jobs:

test:
runs-on: ubuntu-latest

steps:
- uses: actions/setup-node@v1
with:
node-version: '14'
- uses: actions/checkout@v1
- uses: actions/cache@v2
with:
path: node_modules
key: ${{ runner.os }}-${{ hashFiles('yarn.lock') }}
- run: yarn install
- run: yarn run ci

lint:
runs-on: ubuntu-latest
steps:
- uses: actions/setup-node@v1
with:
node-version: '14'
- uses: actions/checkout@v1
- uses: actions/cache@v2
with:
path: node_modules
key: ${{ runner.os }}-${{ hashFiles('yarn.lock') }}
- run: yarn install
- run: yarn preprocess
- run: yarn lerna-lint


6 changes: 6 additions & 0 deletions .idea/jsLinters/eslint.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ In another Window:
so it will listen on port 3000


To run a simple test, do `yarn run runop --deployDeployer --network localhost`
To run a simple test, do `yarn run runop --deployFactory --network localhost`

The runop script:
- deploys a wallet deployer (if not already there)
Expand Down
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,14 @@
"lerna-clear": "lerna run clear",
"lerna-lint": "lerna run lint --stream --parallel",
"lerna-test": "lerna run hardhat-test --stream",
"lerna-test-flows": "lerna run hardhat-test-flows --stream --no-prefix",
"lerna-tsc": "lerna run tsc --scope @account-abstraction/*",
"lerna-watch-tsc": "lerna run --parallel watch-tsc",
"lint-fix": "lerna run lint-fix --parallel",
"clear": "lerna run clear",
"hardhat-compile": "lerna run hardhat-compile",
"preprocess": "yarn lerna-clear && yarn hardhat-compile && yarn lerna-tsc",
"runop-self": "ts-node ./packages/bundler/src/runner/runop.ts --deployDeployer --selfBundler"
"runop-self": "ts-node ./packages/bundler/src/runner/runop.ts --deployFactory --selfBundler",
"ci": "env ; yarn depcheck; yarn preprocess"
},
"dependencies": {
"@typescript-eslint/eslint-plugin": "^5.33.0",
Expand Down
2 changes: 1 addition & 1 deletion packages/bundler/contracts/Import.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,4 @@ pragma solidity ^0.8.0;
// import contracts to get their type info.
import "@account-abstraction/utils/contracts/test/SampleRecipient.sol";
import "@account-abstraction/utils/contracts/test/SingletonFactory.sol";
import "@account-abstraction/contracts/samples/SimpleAccountDeployer.sol";
import "@account-abstraction/contracts/samples/SimpleAccountFactory.sol";
43 changes: 43 additions & 0 deletions packages/bundler/contracts/tests/TestOpcodesAccount.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.15;

import "@account-abstraction/contracts/interfaces/IAccount.sol";
import "@account-abstraction/contracts/interfaces/IPaymaster.sol";
import "@account-abstraction/contracts/interfaces/IEntryPoint.sol";
import "./TestRuleAccount.sol";

contract Dummy {
}


/**
* an account with "rules" to trigger different opcode validation rules
*/
contract TestOpcodesAccount is TestRuleAccount {

event TestMessage(address eventSender);

function runRule(string memory rule) public virtual override returns (uint) {
if (eq(rule, "number")) return block.number;
else if (eq(rule, "coinbase")) return uint160(address(block.coinbase));
else if (eq(rule, "blockhash")) return uint(blockhash(0));
else if (eq(rule, "create2")) {
new Dummy{salt : bytes32(uint(0x1))}();
return 0;
}
else if (eq(rule, "emit-msg")) {
emit TestMessage(address(this));
return 0;
}
return super.runRule(rule);
}
}

contract TestOpcodesAccountFactory {
function create(string memory rule) public returns (TestOpcodesAccount) {
TestOpcodesAccount a = new TestOpcodesAccount{salt : bytes32(uint(0))}();
a.runRule(rule);
return a;
}

}
26 changes: 26 additions & 0 deletions packages/bundler/contracts/tests/TestRecursionAccount.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.15;

import "@account-abstraction/contracts/interfaces/IAccount.sol";
import "@account-abstraction/contracts/interfaces/IPaymaster.sol";
import "@account-abstraction/contracts/interfaces/IEntryPoint.sol";
import "./TestRuleAccount.sol";

contract TestRecursionAccount is TestRuleAccount {

IEntryPoint public immutable ep;
constructor(IEntryPoint _ep) {
ep = _ep;
}

function runRule(string memory rule) public virtual override returns (uint) {

if (eq(rule, "handleOps")) {
UserOperation[] memory ops = new UserOperation[](0);
ep.handleOps(ops, payable(address (1)));
return 0;
}

return super.runRule(rule);
}
}
72 changes: 72 additions & 0 deletions packages/bundler/contracts/tests/TestRuleAccount.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.15;

import "@account-abstraction/contracts/interfaces/IAccount.sol";
import "@account-abstraction/contracts/interfaces/IPaymaster.sol";
import "@account-abstraction/contracts/interfaces/IEntryPoint.sol";

/**
* contract for testing account interaction.
* doesn't really do validation: the signature is a "rule" to define the validation action to take.
* as a paymaster, the paymasterAndData is the "rule" to take (at offset 20, just after the paymaster address)
* the account also as a "state" variable and event, so we can use it to test state transitions
*/
contract TestRuleAccount is IAccount, IPaymaster {

uint state;

event State(uint oldState, uint newState);

function setState(uint _state) external {
emit State(state, _state);
state = _state;
}

function eq(string memory a, string memory b) internal pure returns (bool) {
return keccak256(bytes(a)) == keccak256(bytes(b));
}

/**
* "rules" to test. override to add more "rules"
*/
function runRule(string memory rule) public virtual returns (uint) {
if (eq(rule, "")) return 0;
else if (eq(rule, "ok")) return 0;
else if (eq(rule, "fail")) revert("fail rule");
else
revert(string.concat("unknown rule: ", rule));
}

//needed in order to make it a valid paymaster
function addStake(IEntryPoint entryPoint) public payable {
entryPoint.addStake{value : msg.value}(1);
}

function validateUserOp(UserOperation calldata userOp, bytes32, address, uint256 missingAccountFunds)
external virtual override returns (uint256) {
if (missingAccountFunds > 0) {
/* solhint-disable-next-line avoid-low-level-calls */
(bool success,) = msg.sender.call{value : missingAccountFunds}("");
success;
}
runRule(string(userOp.signature));
return 0;
}

function validatePaymasterUserOp(UserOperation calldata userOp, bytes32, uint256)
public virtual override returns (bytes memory context, uint256 deadline) {
string memory rule = string(userOp.paymasterAndData[20 :]);
runRule(rule);
return ("", 0);
}

function postOp(PostOpMode, bytes calldata, uint256) external {}
}

contract TestRuleAccountFactory {
function create(string memory rule) public returns (TestRuleAccount) {
TestRuleAccount a = new TestRuleAccount{salt : bytes32(uint(0))}();
a.runRule(rule);
return a;
}
}
90 changes: 90 additions & 0 deletions packages/bundler/contracts/tests/TestStorageAccount.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.15;

import "@account-abstraction/contracts/interfaces/IAccount.sol";
import "@account-abstraction/contracts/interfaces/IPaymaster.sol";
import "@account-abstraction/contracts/interfaces/IEntryPoint.sol";
import "./TestRuleAccount.sol";

contract TestCoin {
mapping(address => uint) balances;

function balanceOf(address addr) public returns (uint) {
return balances[addr];
}

function mint(address addr) public returns (uint) {
return balances[addr] += 100;
}

//unrelated to token: testing inner object revert
function reverting() public returns (uint) {
revert("inner-revert");
}

function wasteGas() public returns (uint) {
while (true) {
require(msg.sender != ecrecover("message", 27, bytes32(0), bytes32(0)));
}
return 0;
}
}

/**
* an account with "rules" to trigger different opcode validation rules
*/
contract TestStorageAccount is TestRuleAccount {

TestCoin coin;

function setCoin(TestCoin _coin) public returns (uint){
coin = _coin;
return 0;
}

event TestMessage(address eventSender);

function validatePaymasterUserOp(UserOperation calldata userOp, bytes32 userOpHash, uint256 maxCost)
public virtual override returns (bytes memory context, uint256 deadline) {
string memory rule = string(userOp.paymasterAndData[20 :]);
if (eq(rule, 'postOp-context')) {
return ("some-context",0);
}
// return ("",0);
return super.validatePaymasterUserOp(userOp, userOpHash, maxCost);
}

function runRule(string memory rule) public virtual override returns (uint) {
if (eq(rule, "number")) return block.number;
else if (eq(rule, "balance-self")) return coin.balanceOf(address(this));
else if (eq(rule, "mint-self")) return coin.mint(address(this));
else if (eq(rule, "balance-1")) return coin.balanceOf(address(1));
else if (eq(rule, "mint-1")) return coin.mint(address(1));
else if (eq(rule, "inner-revert")) {
(bool success,) = address(coin).call(abi.encode(coin.reverting));
success;
return 0;
}
else if (eq(rule, "oog")) {
try coin.wasteGas{gas : 50000}() {}
catch {}
return 0;
}
return super.runRule(rule);
}
}

contract TestStorageAccountFactory {
TestCoin immutable coin;
constructor() {
coin = new TestCoin();
}

function create(uint salt, string memory rule) public returns (TestStorageAccount) {
TestStorageAccount a = new TestStorageAccount{salt : bytes32(salt)}();
a.setCoin(coin);
a.runRule(rule);
return a;
}

}
Loading

0 comments on commit 270ff68

Please sign in to comment.