Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add MultiQuarkOperation functions to QuarkWalletProxyFactory #187

Merged
merged 3 commits into from
Mar 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 18 additions & 15 deletions .gas-snapshot
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ ExecutorTest:testExecutorCanDirectCall() (gas: 131488)
ExecutorTest:testExecutorCanDirectCallBySig() (gas: 118285)
GetDripTest:testDrip() (gas: 121425)
MulticallTest:testCallcodeToMulticallSucceedsWhenUninitialized() (gas: 84237)
MulticallTest:testCreateSubWalletAndExecute() (gas: 602061)
MulticallTest:testCreateSubWalletAndExecute() (gas: 602104)
MulticallTest:testEmptyInputIsValid() (gas: 51524)
MulticallTest:testExecutorCanMulticallAcrossSubwallets() (gas: 304280)
MulticallTest:testInvalidInput() (gas: 69134)
Expand All @@ -83,9 +83,9 @@ PaycallTest:testRevertsForInvalidCallContext() (gas: 16800)
PaycallTest:testSimpleCounterAndPayWithUSDC() (gas: 144951)
PaycallTest:testSimpleTransferTokenAndPayWithUSDC() (gas: 140495)
PaycallTest:testSupplyWETHWithdrawUSDCOnCometAndPayWithUSDC() (gas: 301251)
QuarkFactoryTest:testInvariantAddressesBetweenNonces() (gas: 3060265)
QuarkFactoryTest:testQuarkFactoryDeployToDeterministicAddresses() (gas: 3059998)
QuarkFactoryTest:testQuarkFactoryDeployTwice() (gas: 3145570)
QuarkFactoryTest:testInvariantAddressesBetweenNonces() (gas: 3309560)
QuarkFactoryTest:testQuarkFactoryDeployToDeterministicAddresses() (gas: 3309293)
QuarkFactoryTest:testQuarkFactoryDeployTwice() (gas: 3401228)
QuarkMinimalProxyTest:testSignerExecutor() (gas: 75496)
QuarkStateManagerTest:testNonceZeroIsValid() (gas: 91471)
QuarkStateManagerTest:testReadStorageForWallet() (gas: 148688)
Expand All @@ -94,17 +94,20 @@ QuarkStateManagerTest:testRevertsIfScriptAddressIsEOA() (gas: 61786)
QuarkStateManagerTest:testRevertsIfScriptAddressIsNull() (gas: 42213)
QuarkStateManagerTest:testSetActiveNonceAndCallbackNotImplemented() (gas: 92067)
QuarkStateManagerTest:testSetsAndGetsNextNonces() (gas: 810598)
QuarkWalletProxyFactoryTest:testCreateAdditionalWalletWithSalt() (gas: 212394)
QuarkWalletProxyFactoryTest:testCreateAndExecuteCreatesWallet() (gas: 411390)
QuarkWalletProxyFactoryTest:testCreateAndExecuteSetsMsgSender() (gas: 223594)
QuarkWalletProxyFactoryTest:testCreateAndExecuteWithSalt() (gas: 415922)
QuarkWalletProxyFactoryTest:testCreateAndExecuteWithSaltSetsMsgSender() (gas: 395520)
QuarkWalletProxyFactoryTest:testCreateRevertsOnRepeat() (gas: 8937393460516733229)
QuarkWalletProxyFactoryTest:testCreatesWalletAtDeterministicAddress() (gas: 222151)
QuarkWalletProxyFactoryTest:testExecuteOnExistingWallet() (gas: 410062)
QuarkWalletProxyFactoryTest:testExecutorIsOtherWallet() (gas: 282077)
QuarkWalletProxyFactoryTest:testExecutorSetInCreate() (gas: 103562)
QuarkWalletProxyFactoryTest:testVersion() (gas: 6014)
QuarkWalletProxyFactoryTest:testCreateAdditionalWalletWithSalt() (gas: 212726)
QuarkWalletProxyFactoryTest:testCreateAndExecuteCreatesWallet() (gas: 411984)
QuarkWalletProxyFactoryTest:testCreateAndExecuteMultiCreatesWallet() (gas: 446469)
QuarkWalletProxyFactoryTest:testCreateAndExecuteMultiWithSalt() (gas: 450642)
QuarkWalletProxyFactoryTest:testCreateAndExecuteSetsMsgSender() (gas: 223945)
QuarkWalletProxyFactoryTest:testCreateAndExecuteWithSalt() (gas: 416559)
QuarkWalletProxyFactoryTest:testCreateAndExecuteWithSaltSetsMsgSender() (gas: 396152)
QuarkWalletProxyFactoryTest:testCreateRevertsOnRepeat() (gas: 8937393460516733241)
QuarkWalletProxyFactoryTest:testCreatesWalletAtDeterministicAddress() (gas: 222644)
QuarkWalletProxyFactoryTest:testExecuteMultiOnExistingWallet() (gas: 444791)
QuarkWalletProxyFactoryTest:testExecuteOnExistingWallet() (gas: 410694)
QuarkWalletProxyFactoryTest:testExecutorIsOtherWallet() (gas: 282099)
QuarkWalletProxyFactoryTest:testExecutorSetInCreate() (gas: 103646)
QuarkWalletProxyFactoryTest:testVersion() (gas: 6130)
QuarkWalletTest:testAtomicIncrementer() (gas: 71908)
QuarkWalletTest:testAtomicMaxCounterScript() (gas: 270934)
QuarkWalletTest:testAtomicPing() (gas: 51700)
Expand Down
62 changes: 62 additions & 0 deletions src/quark-proxy/src/QuarkWalletProxyFactory.sol
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,15 @@ contract QuarkWalletProxyFactory {
/// @notice Default initial salt value
bytes32 public constant DEFAULT_SALT = bytes32(0);

/// @notice The EIP-712 domain separator for a MultiQuarkOperation
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't know why this is here?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We decided some time ago to put the domain separator in the factory as well. IIRC, it was a gas optimization to move the public getters for domain separators from the wallet to the factory. Now that we are using proxy wallets, it doesn't really matter. But having these be on the factory is still useful in the case where the wallet does not yet exist.

bytes32 public constant MULTI_QUARK_OPERATION_DOMAIN_SEPARATOR = keccak256(
abi.encode(
QuarkWalletMetadata.MULTI_QUARK_OPERATION_DOMAIN_TYPEHASH,
keccak256(bytes(QuarkWalletMetadata.NAME)),
keccak256(bytes(QuarkWalletMetadata.VERSION))
)
);

/// @notice Address of QuarkWallet implementation contract
address public immutable walletImplementation;

Expand Down Expand Up @@ -122,6 +131,59 @@ contract QuarkWalletProxyFactory {
return QuarkWallet(walletAddress).executeQuarkOperation(op, v, r, s);
}

/**
* @notice Create a wallet for (signer, executor) pair (and default salt) if it does not exist, then execute operation that is part of a MultiQuarkOperation
* @param signer Address to set as the signer of the QuarkWallet
* @param executor Address to set as the executor of the QuarkWallet
* @param op The QuarkOperation to execute on the wallet
* @param opDigests A list of EIP-712 digests for the operations in a MultiQuarkOperation
* @param v EIP-712 Signature `v` value
* @param r EIP-712 Signature `r` value
* @param s EIP-712 Signature `s` value
* @return bytes Return value of executing the operation
*/
function createAndExecuteMulti(
address signer,
address executor,
QuarkWallet.QuarkOperation calldata op,
bytes32[] calldata opDigests,
uint8 v,
bytes32 r,
bytes32 s
) external returns (bytes memory) {
return createAndExecuteMulti(signer, executor, DEFAULT_SALT, op, opDigests, v, r, s);
}

/**
* @notice Create a wallet for (signer, executor, salt) triple if it does not exist, then execute operation that is part of a MultiQuarkOperation
* @param signer Address to set as the signer of the QuarkWallet
* @param executor Address to set as the executor of the QuarkWallet
* @param salt Salt value of QuarkWallet to create and execute operation with
* @param op The QuarkOperation to execute on the wallet
* @param opDigests A list of EIP-712 digests for the operations in a MultiQuarkOperation
* @param v EIP-712 Signature `v` value
* @param r EIP-712 Signature `r` value
* @param s EIP-712 Signature `s` value
* @return bytes Return value of executing the operation
*/
function createAndExecuteMulti(
address signer,
address executor,
bytes32 salt,
QuarkWallet.QuarkOperation calldata op,
bytes32[] calldata opDigests,
uint8 v,
bytes32 r,
bytes32 s
) public returns (bytes memory) {
address payable walletAddress = walletAddressForSalt(signer, executor, salt);
if (walletAddress.code.length == 0) {
create(signer, executor, salt);
}

return QuarkWallet(walletAddress).executeMultiQuarkOperation(op, opDigests, v, r, s);
}

/**
* @notice Derive QuarkWallet address for (signer, executor) pair (and default salt value)
* @dev QuarkWallet at returned address may not yet have been created
Expand Down
174 changes: 174 additions & 0 deletions test/quark-proxy/QuarkWalletProxyFactory.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,180 @@ contract QuarkWalletProxyFactoryTest is Test {
assertEq(stateManager.isNonceSet(factory.walletAddressFor(alice, address(0)), nonce), true);
}

/* ===== create and execute MultiQuarkOperation tests ===== */

function testCreateAndExecuteMultiCreatesWallet() public {
// gas: disable metering except while executing operations
vm.pauseGasMetering();

bytes memory incrementer = new YulHelper().getCode("Incrementer.sol/Incrementer.json");
Counter counter = new Counter();
assertEq(counter.number(), 0);

vm.startPrank(address(alice));

bytes[] memory scriptSources = new bytes[](1);
scriptSources[0] = incrementer;

address incrementerAddress = codeJar.getCodeAddress(incrementer);
address aliceWalletAddress = factory.walletAddressFor(alice, address(0));
uint96 nonce = stateManager.nextNonce(aliceWalletAddress);
QuarkWallet.QuarkOperation memory op1 = QuarkWallet.QuarkOperation({
scriptAddress: incrementerAddress,
scriptSources: scriptSources,
scriptCalldata: abi.encodeWithSignature("incrementCounter(address)", counter),
nonce: nonce,
expiry: block.timestamp + 1000
});
bytes32 op1Digest = new SignatureHelper().opDigest(aliceWalletAddress, op1);

QuarkWallet.QuarkOperation memory op2 = QuarkWallet.QuarkOperation({
scriptAddress: incrementerAddress,
scriptSources: scriptSources,
scriptCalldata: abi.encodeWithSignature("incrementCounter(address)", counter),
nonce: nonce + 1,
expiry: block.timestamp + 1000
});
op2.nonce = op1.nonce + 1;
bytes32 op2Digest = new SignatureHelper().opDigest(aliceWalletAddress, op2);

bytes32[] memory opDigests = new bytes32[](2);
opDigests[0] = op1Digest;
opDigests[1] = op2Digest;
(uint8 v, bytes32 r, bytes32 s) = new SignatureHelper().signMultiOp(alicePrivateKey, opDigests);

vm.resumeGasMetering();
// call once
vm.expectEmit(true, true, true, true);
// it creates a wallet
emit WalletDeploy(alice, address(0), aliceWalletAddress, bytes32(0));
factory.createAndExecuteMulti(alice, address(0), op1, opDigests, v, r, s);

assertEq(counter.number(), 3);
assertEq(stateManager.isNonceSet(aliceWalletAddress, op1.nonce), true);

// call a second time
factory.createAndExecuteMulti(alice, address(0), op2, opDigests, v, r, s);

assertEq(counter.number(), 6);
assertEq(stateManager.isNonceSet(aliceWalletAddress, op2.nonce), true);
}

function testCreateAndExecuteMultiWithSalt() public {
// gas: do not meter set-up
vm.pauseGasMetering();
bytes memory incrementer = new YulHelper().getCode("Incrementer.sol/Incrementer.json");
Counter counter = new Counter();

bytes[] memory scriptSources = new bytes[](1);
scriptSources[0] = incrementer;

address incrementerAddress = codeJar.getCodeAddress(incrementer);
bytes32 salt = bytes32("salty salt salt");
address aliceWalletAddress = factory.walletAddressForSalt(alice, address(0), salt);
uint96 nonce = stateManager.nextNonce(aliceWalletAddress);
QuarkWallet.QuarkOperation memory op1 = QuarkWallet.QuarkOperation({
scriptAddress: incrementerAddress,
scriptSources: scriptSources,
scriptCalldata: abi.encodeWithSignature("incrementCounter(address)", counter),
nonce: nonce,
expiry: block.timestamp + 1000
});
bytes32 op1Digest = new SignatureHelper().opDigest(aliceWalletAddress, op1);

QuarkWallet.QuarkOperation memory op2 = QuarkWallet.QuarkOperation({
scriptAddress: incrementerAddress,
scriptSources: scriptSources,
scriptCalldata: abi.encodeWithSignature("incrementCounter(address)", counter),
nonce: nonce + 1,
expiry: block.timestamp + 1000
});
op2.nonce = op1.nonce + 1;
bytes32 op2Digest = new SignatureHelper().opDigest(aliceWalletAddress, op2);

bytes32[] memory opDigests = new bytes32[](2);
opDigests[0] = op1Digest;
opDigests[1] = op2Digest;
(uint8 v, bytes32 r, bytes32 s) = new SignatureHelper().signMultiOp(alicePrivateKey, opDigests);

vm.resumeGasMetering();
// call once
vm.expectEmit(true, true, true, true);
// it creates a wallet (with salt)
emit WalletDeploy(alice, address(0), aliceWalletAddress, salt);
factory.createAndExecuteMulti(alice, address(0), salt, op1, opDigests, v, r, s);

assertEq(counter.number(), 3);
assertEq(stateManager.isNonceSet(aliceWalletAddress, op1.nonce), true);

// call a second time
factory.createAndExecuteMulti(alice, address(0), salt, op2, opDigests, v, r, s);

assertEq(counter.number(), 6);
assertEq(stateManager.isNonceSet(aliceWalletAddress, op2.nonce), true);
}

function testExecuteMultiOnExistingWallet() public {
// gas: disable metering except while executing operations
vm.pauseGasMetering();

bytes memory incrementer = new YulHelper().getCode("Incrementer.sol/Incrementer.json");
Counter counter = new Counter();
assertEq(counter.number(), 0);

vm.startPrank(address(alice));

bytes[] memory scriptSources = new bytes[](1);
scriptSources[0] = incrementer;

address incrementerAddress = codeJar.getCodeAddress(incrementer);
address aliceWalletAddress = factory.walletAddressFor(alice, address(0));
uint96 nonce = stateManager.nextNonce(aliceWalletAddress);
QuarkWallet.QuarkOperation memory op1 = QuarkWallet.QuarkOperation({
scriptAddress: incrementerAddress,
scriptSources: scriptSources,
scriptCalldata: abi.encodeWithSignature("incrementCounter(address)", counter),
nonce: nonce,
expiry: block.timestamp + 1000
});
bytes32 op1Digest = new SignatureHelper().opDigest(aliceWalletAddress, op1);

QuarkWallet.QuarkOperation memory op2 = QuarkWallet.QuarkOperation({
scriptAddress: incrementerAddress,
scriptSources: scriptSources,
scriptCalldata: abi.encodeWithSignature("incrementCounter(address)", counter),
nonce: nonce + 1,
expiry: block.timestamp + 1000
});
op2.nonce = op1.nonce + 1;
bytes32 op2Digest = new SignatureHelper().opDigest(aliceWalletAddress, op2);

bytes32[] memory opDigests = new bytes32[](2);
opDigests[0] = op1Digest;
opDigests[1] = op2Digest;
(uint8 v, bytes32 r, bytes32 s) = new SignatureHelper().signMultiOp(alicePrivateKey, opDigests);

// gas: meter create, createAndExecute
vm.resumeGasMetering();

// the wallet is deployed
vm.expectEmit(true, true, true, true);
emit WalletDeploy(alice, address(0), aliceWalletAddress, bytes32(0));
factory.create(alice, address(0));

// call once
factory.createAndExecuteMulti(alice, address(0), op1, opDigests, v, r, s);

assertEq(counter.number(), 3);
assertEq(stateManager.isNonceSet(aliceWalletAddress, op1.nonce), true);

// call a second time
factory.createAndExecuteMulti(alice, address(0), op2, opDigests, v, r, s);

assertEq(counter.number(), 6);
assertEq(stateManager.isNonceSet(aliceWalletAddress, op2.nonce), true);
}

/* ===== msg.value and msg.sender tests ===== */

function testCreateAndExecuteSetsMsgSender() public {
Expand Down
Loading