diff --git a/contracts/samples/SimpleAccount.sol b/contracts/samples/SimpleAccount.sol index 6c1a1982..10f5d12b 100644 --- a/contracts/samples/SimpleAccount.sol +++ b/contracts/samples/SimpleAccount.sol @@ -61,12 +61,19 @@ contract SimpleAccount is BaseAccount, TokenCallbackHandler, UUPSUpgradeable, In /** * execute a sequence of transactions + * @dev to reduce gas consumption for trivial case (no value), use a zero-length array to mean zero value */ - function executeBatch(address[] calldata dest, bytes[] calldata func) external { + function executeBatch(address[] calldata dest, uint256[] calldata value, bytes[] calldata func) external { _requireFromEntryPointOrOwner(); - require(dest.length == func.length, "wrong array lengths"); - for (uint256 i = 0; i < dest.length; i++) { - _call(dest[i], 0, func[i]); + require(dest.length == func.length && (value.length == 0 || value.length == func.length), "wrong array lengths"); + if (value.length == 0) { + for (uint256 i = 0; i < dest.length; i++) { + _call(dest[i], 0, func[i]); + } + } else { + for (uint256 i = 0; i < dest.length; i++) { + _call(dest[i], value[i], func[i]); + } } } diff --git a/reports/gas-checker.txt b/reports/gas-checker.txt index 17dea2e6..a9cb9e9f 100644 --- a/reports/gas-checker.txt +++ b/reports/gas-checker.txt @@ -12,28 +12,28 @@ ║ │ │ │ (delta for │ (compared to ║ ║ │ │ │ one UserOp) │ account.exec()) ║ ╟────────────────────────────────┼───────┼───────────────┼────────────────┼─────────────────────╢ -║ simple │ 1 │ 81901 │ │ ║ +║ simple │ 1 │ 81867 │ │ ║ ╟────────────────────────────────┼───────┼───────────────┼────────────────┼─────────────────────╢ -║ simple - diff from previous │ 2 │ │ 44212 │ 15198 ║ +║ simple - diff from previous │ 2 │ │ 44166 │ 15152 ║ ╟────────────────────────────────┼───────┼───────────────┼────────────────┼─────────────────────╢ -║ simple │ 10 │ 479854 │ │ ║ +║ simple │ 10 │ 479598 │ │ ║ ╟────────────────────────────────┼───────┼───────────────┼────────────────┼─────────────────────╢ -║ simple - diff from previous │ 11 │ │ 44236 │ 15222 ║ +║ simple - diff from previous │ 11 │ │ 44226 │ 15212 ║ ╟────────────────────────────────┼───────┼───────────────┼────────────────┼─────────────────────╢ -║ simple paymaster │ 1 │ 88172 │ │ ║ +║ simple paymaster │ 1 │ 88150 │ │ ║ ╟────────────────────────────────┼───────┼───────────────┼────────────────┼─────────────────────╢ -║ simple paymaster with diff │ 2 │ │ 43165 │ 14151 ║ +║ simple paymaster with diff │ 2 │ │ 43167 │ 14153 ║ ╟────────────────────────────────┼───────┼───────────────┼────────────────┼─────────────────────╢ -║ simple paymaster │ 10 │ 476994 │ │ ║ +║ simple paymaster │ 10 │ 476858 │ │ ║ ╟────────────────────────────────┼───────┼───────────────┼────────────────┼─────────────────────╢ -║ simple paymaster with diff │ 11 │ │ 43260 │ 14246 ║ +║ simple paymaster with diff │ 11 │ │ 43166 │ 14152 ║ ╟────────────────────────────────┼───────┼───────────────┼────────────────┼─────────────────────╢ -║ big tx 5k │ 1 │ 182958 │ │ ║ +║ big tx 5k │ 1 │ 182936 │ │ ║ ╟────────────────────────────────┼───────┼───────────────┼────────────────┼─────────────────────╢ -║ big tx - diff from previous │ 2 │ │ 144723 │ 19463 ║ +║ big tx - diff from previous │ 2 │ │ 144665 │ 19405 ║ ╟────────────────────────────────┼───────┼───────────────┼────────────────┼─────────────────────╢ -║ big tx 5k │ 10 │ 1485438 │ │ ║ +║ big tx 5k │ 10 │ 1485218 │ │ ║ ╟────────────────────────────────┼───────┼───────────────┼────────────────┼─────────────────────╢ -║ big tx - diff from previous │ 11 │ │ 144712 │ 19452 ║ +║ big tx - diff from previous │ 11 │ │ 144750 │ 19490 ║ ╚════════════════════════════════╧═══════╧═══════════════╧════════════════╧═════════════════════╝ diff --git a/test/deposit-paymaster.test.ts b/test/deposit-paymaster.test.ts index 6f7f3788..9b745aad 100644 --- a/test/deposit-paymaster.test.ts +++ b/test/deposit-paymaster.test.ts @@ -63,7 +63,7 @@ describe.skip('DepositPaymaster', () => { const paymasterWithdraw = await paymaster.populateTransaction.withdrawTokensTo(token.address, AddressZero, 1).then(tx => tx.data!) await expect( - account.executeBatch([paymaster.address, paymaster.address], [paymasterUnlock, paymasterWithdraw]) + account.executeBatch([paymaster.address, paymaster.address], [], [paymasterUnlock, paymasterWithdraw]) ).to.be.revertedWith('DepositPaymaster: must unlockTokenDeposit') }) it('should succeed to withdraw after unlock', async () => { diff --git a/test/simple-wallet.test.ts b/test/simple-wallet.test.ts index 1a57054d..3ce2a42b 100644 --- a/test/simple-wallet.test.ts +++ b/test/simple-wallet.test.ts @@ -6,6 +6,8 @@ import { SimpleAccount, SimpleAccountFactory__factory, SimpleAccount__factory, + TestCounter, + TestCounter__factory, TestUtil, TestUtil__factory } from '../typechain' @@ -56,6 +58,46 @@ describe('SimpleAccount', function () { expect(await testUtil.packUserOp(op)).to.equal(packed) }) + describe('#executeBatch', () => { + let account: SimpleAccount + let counter: TestCounter + before(async () => { + ({ proxy: account } = await createAccount(ethersSigner, await ethersSigner.getAddress(), entryPoint)) + counter = await new TestCounter__factory(ethersSigner).deploy() + }) + + it('should allow zero value array', async () => { + const counterJustEmit = await counter.populateTransaction.justemit().then(tx => tx.data!) + const rcpt = await account.executeBatch( + [counter.address, counter.address], + [], + [counterJustEmit, counterJustEmit] + ).then(async t => await t.wait()) + const targetLogs = await counter.queryFilter(counter.filters.CalledFrom(), rcpt.blockHash) + expect(targetLogs.length).to.eq(2) + }) + + it('should allow transfer value', async () => { + const counterJustEmit = await counter.populateTransaction.justemit().then(tx => tx.data!) + const target = createAddress() + await ethersSigner.sendTransaction({ from: accounts[0], to: account.address, value: parseEther('2') }) + const rcpt = await account.executeBatch( + [target, counter.address], + [ONE_ETH, 0], + ['0x', counterJustEmit] + ).then(async t => await t.wait()) + expect(await ethers.provider.getBalance(target)).to.equal(ONE_ETH) + const targetLogs = await counter.queryFilter(counter.filters.CalledFrom(), rcpt.blockHash) + expect(targetLogs.length).to.eq(1) + }) + + it('should fail with wrong array length', async () => { + const counterJustEmit = await counter.populateTransaction.justemit().then(tx => tx.data!) + await expect(account.executeBatch([counter.address, counter.address], [0], [counterJustEmit, counterJustEmit])) + .to.be.revertedWith('wrong array lengths') + }) + }) + describe('#validateUserOp', () => { let account: SimpleAccount let userOp: UserOperation