Skip to content

Commit

Permalink
Add scriptSources to executeScript (#189)
Browse files Browse the repository at this point in the history
This makes the direct execution path consistent with the by signature path.
  • Loading branch information
kevincheng96 authored Mar 22, 2024
1 parent 5f13394 commit 4f9560b
Show file tree
Hide file tree
Showing 7 changed files with 223 additions and 178 deletions.
306 changes: 154 additions & 152 deletions .gas-snapshot

Large diffs are not rendered by default.

16 changes: 12 additions & 4 deletions src/quark-core/src/QuarkWallet.sol
Original file line number Diff line number Diff line change
Expand Up @@ -228,17 +228,25 @@ contract QuarkWallet is IERC1271 {
* @param nonce Nonce for the operation; must be unused
* @param scriptAddress Address for the script to execute
* @param scriptCalldata Encoded call to invoke on the script
* @param scriptSources Creation codes Quark must ensure are deployed before executing the script
* @return Return value from the executed operation
*/
function executeScript(uint96 nonce, address scriptAddress, bytes calldata scriptCalldata)
external
returns (bytes memory)
{
function executeScript(
uint96 nonce,
address scriptAddress,
bytes calldata scriptCalldata,
bytes[] calldata scriptSources
) external returns (bytes memory) {
// only allow the executor for the wallet to use unsigned execution
if (msg.sender != IHasSignerExecutor(address(this)).executor()) {
revert Unauthorized();
}

// guarantee every script in scriptSources is deployed
for (uint256 i = 0; i < scriptSources.length; ++i) {
codeJar.saveCode(scriptSources[i]);
}

emit ExecuteQuarkScript(msg.sender, scriptAddress, nonce, ExecutionType.Direct);

return stateManager.setActiveNonceAndCallback(nonce, scriptAddress, scriptCalldata);
Expand Down
9 changes: 6 additions & 3 deletions src/quark-core/src/interfaces/IQuarkWallet.sol
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,12 @@ interface IQuarkWallet {
bytes32 r,
bytes32 s
) external returns (bytes memory);
function executeScript(uint96 nonce, address scriptAddress, bytes calldata scriptCalldata)
external
returns (bytes memory);
function executeScript(
uint96 nonce,
address scriptAddress,
bytes calldata scriptCalldata,
bytes[] calldata scriptSources
) external returns (bytes memory);
function getDigestForQuarkOperation(QuarkOperation calldata op) external view returns (bytes32);
function getDigestForMultiQuarkOperation(bytes32[] memory opDigests) external pure returns (bytes32);
function getDigestForQuarkMessage(bytes memory message) external view returns (bytes32);
Expand Down
2 changes: 1 addition & 1 deletion test/lib/ExecuteOnBehalf.sol
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,6 @@ contract ExecuteOnBehalf {
public
returns (bytes memory)
{
return targetWallet.executeScript(nonce, scriptAddress, scriptCalldata);
return targetWallet.executeScript(nonce, scriptAddress, scriptCalldata, new bytes[](0));
}
}
21 changes: 13 additions & 8 deletions test/quark-core-scripts/Multicall.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -391,45 +391,48 @@ contract MulticallTest is Test {
// 1. transfer 0.5 WETH from wallet A to wallet B
wallets[0] = address(walletA);
walletCalls[0] = abi.encodeWithSignature(
"executeScript(uint96,address,bytes)",
"executeScript(uint96,address,bytes,bytes[])",
QuarkWallet(payable(factory.walletImplementation())).stateManager().nextNonce(address(walletA)),
ethcallAddress,
abi.encodeWithSelector(
Ethcall.run.selector,
WETH,
abi.encodeCall(IERC20.transfer, (address(walletB), 0.5 ether)),
0 // value
)
),
new bytes[](0)
);

// 2. approve Comet cUSDCv3 to receive 0.5 WETH from wallet B
uint96 walletBNextNonce =
QuarkWallet(payable(factory.walletImplementation())).stateManager().nextNonce(address(walletB));
wallets[1] = address(walletB);
walletCalls[1] = abi.encodeWithSignature(
"executeScript(uint96,address,bytes)",
"executeScript(uint96,address,bytes,bytes[])",
walletBNextNonce,
ethcallAddress,
abi.encodeWithSelector(
Ethcall.run.selector,
WETH,
abi.encodeCall(IERC20.approve, (cUSDCv3, 0.5 ether)),
0 // value
)
),
new bytes[](0)
);

// 3. supply 0.5 WETH from wallet B to Comet cUSDCv3
wallets[2] = address(walletB);
walletCalls[2] = abi.encodeWithSignature(
"executeScript(uint96,address,bytes)",
"executeScript(uint96,address,bytes,bytes[])",
walletBNextNonce + 1,
ethcallAddress,
abi.encodeWithSelector(
Ethcall.run.selector,
cUSDCv3,
abi.encodeCall(IComet.supply, (WETH, 0.5 ether)),
0 // value
)
),
new bytes[](0)
);

// okay, woof, now wrap all that in ethcalls...
Expand Down Expand Up @@ -531,7 +534,8 @@ contract MulticallTest is Test {
path: abi.encodePacked(USDC, uint24(500), WETH) // Path: USDC - 0.05% -> WETH
})
)
)
),
new bytes[](0)
)
),
0 // value
Expand All @@ -546,7 +550,8 @@ contract MulticallTest is Test {
(
nonce + 1,
legendCometSupplyScriptAddress,
abi.encodeCall(CometSupplyActions.supply, (cWETHv3, WETH, 2 ether))
abi.encodeCall(CometSupplyActions.supply, (cWETHv3, WETH, 2 ether)),
new bytes[](0)
)
),
0 // value
Expand Down
3 changes: 2 additions & 1 deletion test/quark-core/Executor.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,8 @@ contract ExecutorTest is Test {
abi.encodeWithSignature(
"run(address,bytes,uint256)", address(counter), abi.encodeWithSignature("increment(uint256)", 5), 0
)
)
),
new bytes[](0)
);

assertEq(counter.number(), 5);
Expand Down
44 changes: 35 additions & 9 deletions test/quark-core/QuarkWallet.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ contract QuarkWalletTest is Test {

// gas: meter execute
vm.resumeGasMetering();
bytes memory result = aliceWalletExecutable.executeScript(nonce, scriptAddress, call);
bytes memory result = aliceWalletExecutable.executeScript(nonce, scriptAddress, call, new bytes[](0));

vm.stopPrank();

Expand Down Expand Up @@ -178,7 +178,7 @@ contract QuarkWalletTest is Test {
vm.resumeGasMetering();
vm.expectEmit(true, true, true, true);
emit ExecuteQuarkScript(address(aliceAccount), scriptAddress, nonce, ExecutionType.Direct);
aliceWalletExecutable.executeScript(nonce, scriptAddress, call);
aliceWalletExecutable.executeScript(nonce, scriptAddress, call, new bytes[](0));
}

/* ===== general invariant tests ===== */
Expand Down Expand Up @@ -207,7 +207,7 @@ contract QuarkWalletTest is Test {
uint96 nonce = stateManager.nextNonce(address(aliceWallet));
vm.prank(IHasSignerExecutor(address(aliceWallet)).executor());
vm.expectRevert(abi.encodeWithSelector(QuarkWallet.EmptyCode.selector));
aliceWallet.executeScript(nonce, address(0), bytes(""));
aliceWallet.executeScript(nonce, address(0), bytes(""), new bytes[](0));

// gas: do not meter set-up
vm.pauseGasMetering();
Expand Down Expand Up @@ -236,7 +236,7 @@ contract QuarkWalletTest is Test {
uint96 nonce2 = stateManager.nextNonce(address(aliceWallet));
vm.prank(IHasSignerExecutor(address(aliceWallet)).executor());
vm.expectRevert(abi.encodeWithSelector(QuarkWallet.EmptyCode.selector));
aliceWallet.executeScript(nonce2, emptyCodeAddress, bytes(""));
aliceWallet.executeScript(nonce2, emptyCodeAddress, bytes(""), new bytes[](0));
}

function testRevertsForRandomEmptyScriptAddress() public {
Expand Down Expand Up @@ -398,7 +398,7 @@ contract QuarkWalletTest is Test {

// gas: meter execute
vm.resumeGasMetering();
aliceWalletExecutable.executeScript(nonce, incrementerAddress, call);
aliceWalletExecutable.executeScript(nonce, incrementerAddress, call, new bytes[](0));

assertEq(counter.number(), 3);
assertEq(stateManager.nextNonce(address(aliceWalletExecutable)), 1);
Expand All @@ -415,10 +415,11 @@ contract QuarkWalletTest is Test {
Ethcall.run.selector,
address(aliceWalletExecutable),
abi.encodeWithSignature(
"executeScript(uint96,address,bytes)",
"executeScript(uint96,address,bytes,bytes[])",
stateManager.nextNonce(address(aliceWalletExecutable)),
incrementerAddress,
abi.encodeWithSignature("incrementCounter(address)", counter)
abi.encodeWithSignature("incrementCounter(address)", counter),
new bytes[](0)
),
0 // value
);
Expand All @@ -439,6 +440,31 @@ contract QuarkWalletTest is Test {
assertEq(stateManager.nextNonce(address(aliceWalletExecutable)), 1);
}

function testDirectExecuteWithScriptSources() public {
// gas: disable metering except while executing operations
vm.pauseGasMetering();
QuarkWallet aliceWalletExecutable = newWallet(aliceAccount, aliceAccount);
bytes memory incrementer = new YulHelper().getCode("Incrementer.sol/Incrementer.json");
address incrementerAddress = codeJar.getCodeAddress(incrementer);
uint96 nonce = stateManager.nextNonce(address(aliceWalletExecutable));
bytes memory call = abi.encodeWithSignature("incrementCounter(address)", counter);
bytes[] memory scriptSources = new bytes[](1);
scriptSources[0] = incrementer;

assertEq(counter.number(), 0);
assertEq(stateManager.nextNonce(address(aliceWalletExecutable)), 0);

// act as the executor for the wallet
vm.startPrank(aliceAccount);

// gas: meter execute
vm.resumeGasMetering();
aliceWalletExecutable.executeScript(nonce, incrementerAddress, call, scriptSources);

assertEq(counter.number(), 3);
assertEq(stateManager.nextNonce(address(aliceWalletExecutable)), 1);
}

function testRevertsForDirectExecuteByNonExecutorSigner() public {
// gas: disable metering except while executing operations
vm.pauseGasMetering();
Expand All @@ -457,7 +483,7 @@ contract QuarkWalletTest is Test {
vm.resumeGasMetering();

vm.expectRevert(abi.encodeWithSelector(QuarkWallet.Unauthorized.selector));
aliceWallet.executeScript(nonce, target, call);
aliceWallet.executeScript(nonce, target, call, new bytes[](0));

vm.stopPrank();

Expand All @@ -482,7 +508,7 @@ contract QuarkWalletTest is Test {
vm.resumeGasMetering();

vm.expectRevert(abi.encodeWithSelector(QuarkWallet.Unauthorized.selector));
aliceWallet.executeScript(nonce, target, call);
aliceWallet.executeScript(nonce, target, call, new bytes[](0));

vm.stopPrank();

Expand Down

0 comments on commit 4f9560b

Please sign in to comment.