diff --git a/src/LightAccount.sol b/src/LightAccount.sol index 11fd863..719912c 100644 --- a/src/LightAccount.sol +++ b/src/LightAccount.sol @@ -29,7 +29,8 @@ import {CustomSlotInitializable} from "./CustomSlotInitializable.sol"; * * 3. Supports [ERC-1271](https://eips.ethereum.org/EIPS/eip-1271) signature * validation for both validating the signature on user operations and in - * exposing its own `isValidSignature` method. + * exposing its own `isValidSignature` method. This only works when the owner of + * LightAccount also support ERC-1271. * * 4. Event `SimpleAccountInitialized` renamed to `LightAccountInitialized`. */ @@ -104,6 +105,22 @@ contract LightAccount is BaseAccount, TokenCallbackHandler, UUPSUpgradeable, Cus } } + /** + * @notice Execute a sequence of transactions + * @param dest An array of the targets for each transaction in the sequence + * @param value An array of value for each transaction in the sequence + * @param func An array of calldata for each transaction in the sequence. + * Must be the same length as dest, with corresponding elements representing + * the parameters for each transaction. + */ + function executeBatch(address[] calldata dest, uint256[] calldata value, bytes[] calldata func) external { + _requireFromEntryPointOrOwner(); + require(dest.length == func.length && dest.length == value.length, "wrong array lengths"); + for (uint256 i = 0; i < dest.length; i++) { + _call(dest[i], value[i], func[i]); + } + } + /** * @notice Called once as part of initialization, either during initial deployment or when first upgrading to * this contract. diff --git a/test/LightAccount.t.sol b/test/LightAccount.t.sol index c21c137..b671991 100644 --- a/test/LightAccount.t.sol +++ b/test/LightAccount.t.sol @@ -47,6 +47,13 @@ contract LightAccountTest is Test { assertTrue(lightSwitch.on()); } + function testExecuteWithValueCanBeCalledByOwner() public { + vm.prank(eoaAddress); + account.execute(address(lightSwitch), 1 ether, abi.encodeCall(LightSwitch.turnOn, ())); + assertTrue(lightSwitch.on()); + assertEq(address(lightSwitch).balance, 1 ether); + } + function testExecuteCanBeCalledByEntryPointWithExternalOwner() public { UserOperation memory op = _getSignedOp(address(lightSwitch), abi.encodeCall(LightSwitch.turnOn, ()), EOA_PRIVATE_KEY); @@ -107,6 +114,32 @@ contract LightAccountTest is Test { account.executeBatch(dest, func); } + function testExecuteBatchWithValueCalledByOwner() public { + vm.prank(eoaAddress); + address[] memory dest = new address[](1); + dest[0] = address(lightSwitch); + uint256[] memory value = new uint256[](1); + value[0] = uint256(1); + bytes[] memory func = new bytes[](1); + func[0] = abi.encodeCall(LightSwitch.turnOn, ()); + account.executeBatch(dest, value, func); + assertTrue(lightSwitch.on()); + assertEq(address(lightSwitch).balance, 1); + } + + function testExecuteBatchWithValueFailsForUnevenInputArrays() public { + vm.prank(eoaAddress); + address[] memory dest = new address[](1); + dest[0] = address(lightSwitch); + uint256[] memory value = new uint256[](2); + value[0] = uint256(1); + value[1] = uint256(1 ether); + bytes[] memory func = new bytes[](1); + func[0] = abi.encodeCall(LightSwitch.turnOn, ()); + vm.expectRevert(bytes("wrong array lengths")); + account.executeBatch(dest, value, func); + } + function testInitialize() public { LightAccountFactory factory = new LightAccountFactory(entryPoint); vm.expectEmit(true, false, false, false); @@ -272,7 +305,7 @@ contract LightAccountTest is Test { contract LightSwitch { bool public on; - function turnOn() external { + function turnOn() external payable { on = true; } }