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 Paycall flag for reverting and add PayForGas event #195

Merged
merged 5 commits into from
Apr 29, 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
57 changes: 10 additions & 47 deletions .gas-snapshot
Original file line number Diff line number Diff line change
@@ -1,6 +1,3 @@
ApproveAndSwapTest:testSwap() (gas: 286242)
ApproveAndSwapTest:testSwapFailsIfWeExpectedTooMuch() (gas: 365553)
ApproveAndSwapTest:testSwapFailsWithNoApproval() (gas: 123118)
BatchExecutorTest:testBatchExecuteWithPartialFailures() (gas: 129689)
BatchExecutorTest:testBatchExecuteWithPartialFailuresDoesNotRevertIfAnyCallsRevert() (gas: 123262)
BatchExecutorTest:testBatchExecuteWithoutPartialFailures() (gas: 129953)
Expand Down Expand Up @@ -30,11 +27,6 @@ CodeJarTest:testCodeJarStoresSelfReference() (gas: 107362)
CodeJarTest:testCodeJarTickCounter() (gas: 176903)
CodeJarTest:testRevertsOnConstructorRevert() (gas: 85881)
CodeJarTest:testRevertsOnSelfDestructingConstructor() (gas: 54396)
CometClaimRewardsTest:testClaimComp() (gas: 132187)
CometRepayAndWithdrawMultipleAssetsTest:testInvalidInput() (gas: 68856)
CometRepayAndWithdrawMultipleAssetsTest:testRepayAndWithdrawMultipleAssets() (gas: 154648)
CometSupplyMultipleAssetsAndBorrowTest:testInvalidInput() (gas: 68807)
CometSupplyMultipleAssetsAndBorrowTest:testSupplyMultipleAssetsAndBorrow() (gas: 296281)
ConditionalMulticallTest:testConditionalRunEmptyInputIsValid() (gas: 52025)
ConditionalMulticallTest:testConditionalRunInvalidInput() (gas: 69668)
ConditionalMulticallTest:testConditionalRunMulticallError() (gas: 317487)
Expand All @@ -61,7 +53,6 @@ EthcallTest:testEthcallSupplyUSDCToComet() (gas: 173400)
EthcallTest:testEthcallWithdrawUSDCFromComet() (gas: 298210)
ExecutorTest:testExecutorCanDirectCall() (gas: 132195)
ExecutorTest:testExecutorCanDirectCallBySig() (gas: 118698)
GetDripTest:testDrip() (gas: 121447)
MulticallTest:testCallcodeToMulticallSucceedsWhenUninitialized() (gas: 84259)
MulticallTest:testCreateSubWalletAndExecute() (gas: 602979)
MulticallTest:testEmptyInputIsValid() (gas: 51541)
Expand All @@ -75,15 +66,16 @@ MulticallTest:testSupplyWETHWithdrawUSDCOnComet() (gas: 260802)
NonceTest:testIsSet() (gas: 30425)
NonceTest:testNextUnusedNonce() (gas: 40315)
NonceTest:testNonLinearNonce() (gas: 30757)
PaycallTest:testInitializeProperlyFromConstructor() (gas: 6581)
PaycallTest:testPaycallForPayWithUSDT() (gas: 122194)
PaycallTest:testPaycallForPayWithWBTC() (gas: 116673)
PaycallTest:testReturnCallResult() (gas: 91007)
PaycallTest:testRevertsForInvalidCallContext() (gas: 16770)
PaycallTest:testRevertsWhenCostIsMoreThanMaxPaymentCost() (gas: 120332)
PaycallTest:testSimpleCounterAndPayWithUSDC() (gas: 145103)
PaycallTest:testSimpleTransferTokenAndPayWithUSDC() (gas: 140647)
PaycallTest:testSupplyWETHWithdrawUSDCOnCometAndPayWithUSDC() (gas: 301424)
PaycallTest:testInitializeProperlyFromConstructor() (gas: 6606)
PaycallTest:testPaycallForPayWithUSDT() (gas: 126144)
PaycallTest:testPaycallForPayWithWBTC() (gas: 120640)
PaycallTest:testPaycallNonRevertyDoesNotRevertWhenCallReverts() (gas: 112591)
PaycallTest:testReturnCallResult() (gas: 93192)
PaycallTest:testRevertsForInvalidCallContext() (gas: 16767)
PaycallTest:testRevertsWhenCostIsMoreThanMaxPaymentCost() (gas: 120368)
PaycallTest:testSimpleCounterAndPayWithUSDC() (gas: 150073)
PaycallTest:testSimpleTransferTokenAndPayWithUSDC() (gas: 145617)
PaycallTest:testSupplyWETHWithdrawUSDCOnCometAndPayWithUSDC() (gas: 306416)
QuarkFactoryTest:testInvariantAddressesBetweenNonces() (gas: 3359064)
QuarkFactoryTest:testQuarkFactoryDeployToDeterministicAddresses() (gas: 3358797)
QuarkFactoryTest:testQuarkFactoryDeployTwice() (gas: 3452021)
Expand Down Expand Up @@ -157,24 +149,6 @@ RevertsTest:testRevertsInteger() (gas: 66969)
RevertsTest:testRevertsInvalidOpcode() (gas: 8391762413094949948)
RevertsTest:testRevertsOutOfGas() (gas: 295979)
RevertsTest:testRevertsWhenDividingByZero() (gas: 66815)
SupplyActionsTest:testInvalidInput() (gas: 68436)
SupplyActionsTest:testRepayBorrow() (gas: 90532)
SupplyActionsTest:testSupply() (gas: 132714)
SupplyActionsTest:testSupplyFrom() (gas: 112718)
SupplyActionsTest:testSupplyMultipleCollateral() (gas: 271636)
SupplyActionsTest:testSupplyTo() (gas: 132409)
TransferActionsTest:testRevertsForTransferERC777ReentrancyAttackWithReentrancyGuard() (gas: 150272)
TransferActionsTest:testRevertsForTransferReentrancyAttackWithReentrancyGuard() (gas: 131552)
TransferActionsTest:testRevertsForTransferReentrancyAttackWithoutCallbackEnabled() (gas: 100266)
TransferActionsTest:testRevertsForTransferReentrantAttackWithStolenSignature() (gas: 111641)
TransferActionsTest:testTransferERC20TokenToEOA() (gas: 70970)
TransferActionsTest:testTransferERC20TokenToQuarkWallet() (gas: 72347)
TransferActionsTest:testTransferERC777SuccessWithEvilReceiverWithoutAttackAttempt() (gas: 107807)
TransferActionsTest:testTransferERC777TokenReentrancyAttackSuccessWithCallbackEnabled() (gas: 145954)
TransferActionsTest:testTransferNativeTokenToEOA() (gas: 80223)
TransferActionsTest:testTransferNativeTokenToQuarkWallet() (gas: 55619)
TransferActionsTest:testTransferReentrancyAttackSuccessWithCallbackEnabled() (gas: 129665)
TransferActionsTest:testTransferSuccessWithEvilReceiverWithoutAttackAttempt() (gas: 88210)
UniswapFlashLoanTest:testFlashLoanForCollateralSwapOnCompound() (gas: 428563)
UniswapFlashLoanTest:testRevertsForInsufficientFundsToRepayFlashLoan() (gas: 194222)
UniswapFlashLoanTest:testRevertsForInvalidCaller() (gas: 71191)
Expand All @@ -184,17 +158,6 @@ UniswapFlashSwapExactOutTest:testInvalidCallerFlashSwap() (gas: 71169)
UniswapFlashSwapExactOutTest:testNotEnoughToPayFlashSwap() (gas: 294961)
UniswapFlashSwapExactOutTest:testRevertsIfCalledDirectly() (gas: 10871)
UniswapFlashSwapExactOutTest:testUniswapFlashSwapExactOutLeverageComet() (gas: 355100)
UniswapSwapActionsTest:testApprovalRefund() (gas: 164763)
UniswapSwapActionsTest:testBuyAssetOneStop() (gas: 252895)
UniswapSwapActionsTest:testBuyAssetTwoStops() (gas: 359452)
UniswapSwapActionsTest:testSellAssetOneStop() (gas: 250230)
UniswapSwapActionsTest:testSellAssetTwoStops() (gas: 363377)
WithdrawActionsTest:testBorrow() (gas: 154760)
WithdrawActionsTest:testInvalidInput() (gas: 68308)
WithdrawActionsTest:testWithdraw() (gas: 84474)
WithdrawActionsTest:testWithdrawFrom() (gas: 84000)
WithdrawActionsTest:testWithdrawMultipleAssets() (gas: 160197)
WithdrawActionsTest:testWithdrawTo() (gas: 84468)
isValidSignatureTest:testIsValidSignatureForEOAOwner() (gas: 13445)
isValidSignatureTest:testReturnsMagicValueForValidSignature() (gas: 9376)
isValidSignatureTest:testRevertsForEmptyContract() (gas: 11305)
Expand Down
22 changes: 15 additions & 7 deletions src/quark-core-scripts/src/Paycall.sol
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ import "openzeppelin/token/ERC20/extensions/IERC20Metadata.sol";
contract Paycall {
using SafeERC20 for IERC20;

event PayForGas(address indexed payer, address indexed payee, address indexed paymentToken, uint256 amount);

error InvalidCallContext();
error TransactionTooExpensive();

Expand All @@ -25,8 +27,11 @@ contract Paycall {
/// @notice Payment token address
address public immutable paymentTokenAddress;

/// @notice Flag for indicating if reverts from the call should be propagated or swallowed
bool public immutable propagateReverts;

/// @notice Constant buffer for gas overhead
/// This is a constant to accounted for the gas used by the Paycall contract itself that's not tracked by gasleft()
/// This is a constant to account for the gas used by the Paycall contract itself that's not tracked by gasleft()
uint256 internal constant GAS_OVERHEAD = 75000;
kevincheng96 marked this conversation as resolved.
Show resolved Hide resolved

/// @notice Difference in scale between the payment token and ETH, used to scale the payment token.
Expand All @@ -35,12 +40,14 @@ contract Paycall {

/**
* @notice Constructor
* @param ethPriceFeed Eth based price feed address that follows Chainlink's AggregatorV3Interface correlated to the payment token
* @param paymentToken Payment token address
* @param ethBasedPriceFeedAddress_ Eth based price feed address that follows Chainlink's AggregatorV3Interface correlated to the payment token
* @param paymentTokenAddress_ Payment token address
* @param propagateReverts_ Flag for indicating if reverts from the call should be propagated or swallowed
*/
constructor(address ethPriceFeed, address paymentToken) {
ethBasedPriceFeedAddress = ethPriceFeed;
paymentTokenAddress = paymentToken;
constructor(address ethBasedPriceFeedAddress_, address paymentTokenAddress_, bool propagateReverts_) {
ethBasedPriceFeedAddress = ethBasedPriceFeedAddress_;
paymentTokenAddress = paymentTokenAddress_;
propagateReverts = propagateReverts_;
scriptAddress = address(this);

divisorScale = 10
Expand Down Expand Up @@ -68,7 +75,7 @@ contract Paycall {
}

(bool success, bytes memory returnData) = callContract.delegatecall(callData);
if (!success) {
if (!success && propagateReverts) {
assembly {
revert(add(returnData, 32), mload(returnData))
kevincheng96 marked this conversation as resolved.
Show resolved Hide resolved
}
Expand All @@ -81,6 +88,7 @@ contract Paycall {
revert TransactionTooExpensive();
}
IERC20(paymentTokenAddress).safeTransfer(tx.origin, paymentAmount);
emit PayForGas(address(this), tx.origin, paymentTokenAddress, paymentAmount);
kevincheng96 marked this conversation as resolved.
Show resolved Hide resolved

return returnData;
}
Expand Down
59 changes: 55 additions & 4 deletions test/quark-core-scripts/Paycall.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import {Multicall} from "quark-core-scripts/src/Multicall.sol";
import {Paycall} from "quark-core-scripts/src/Paycall.sol";

import {Counter} from "test/lib/Counter.sol";
import {Reverts} from "test/lib/Reverts.sol";

import {YulHelper} from "test/lib/YulHelper.sol";
import {SignatureHelper} from "test/lib/SignatureHelper.sol";
Expand All @@ -26,6 +27,8 @@ import "quark-core-scripts/src/vendor/chainlink/AggregatorV3Interface.sol";
import {IComet, IERC20} from "test/lib/DeFiScripts.sol";

contract PaycallTest is Test {
event PayForGas(address indexed payer, address indexed payee, address indexed paymentToken, uint256 amount);

QuarkWalletProxyFactory public factory;
Counter public counter;
uint256 alicePrivateKey = 0xa11ce;
Expand All @@ -48,10 +51,12 @@ contract PaycallTest is Test {
address constant uniswapRouter = 0xE592427A0AEce92De3Edee1F18E0157C05861564;
bytes multicall = new YulHelper().getCode("Multicall.sol/Multicall.json");
bytes ethcall = new YulHelper().getCode("Ethcall.sol/Ethcall.json");
bytes reverts = new YulHelper().getCode("Reverts.sol/Reverts.json");
// Paycall has its contructor with 2 parameters
bytes paycall;
bytes paycallUSDT;
bytes paycallWBTC;
bytes paycallNonReverty;

bytes legendCometSupplyScript = new YulHelper().getCode("DeFiScripts.sol/CometSupplyActions.json");

Expand All @@ -61,9 +66,11 @@ contract PaycallTest is Test {

address ethcallAddress;
address multicallAddress;
address revertsAddress;
address paycallAddress;
address paycallUSDTAddress;
address paycallWBTCAddress;
address paycallNonRevertyAddress;
address legendCometSupplyScriptAddress;
address legendCometWithdrawScriptAddress;
address legendUniswapSwapScriptAddress;
Expand All @@ -82,13 +89,16 @@ contract PaycallTest is Test {
codeJar = QuarkWallet(payable(factory.walletImplementation())).codeJar();
ethcallAddress = codeJar.saveCode(ethcall);
multicallAddress = codeJar.saveCode(multicall);
paycall = abi.encodePacked(type(Paycall).creationCode, abi.encode(ETH_USD_PRICE_FEED, USDC));
paycallUSDT = abi.encodePacked(type(Paycall).creationCode, abi.encode(ETH_USD_PRICE_FEED, USDT));
paycallWBTC = abi.encodePacked(type(Paycall).creationCode, abi.encode(ETH_BTC_PRICE_FEED, WBTC));
revertsAddress = codeJar.saveCode(reverts);
paycall = abi.encodePacked(type(Paycall).creationCode, abi.encode(ETH_USD_PRICE_FEED, USDC, true));
paycallUSDT = abi.encodePacked(type(Paycall).creationCode, abi.encode(ETH_USD_PRICE_FEED, USDT, true));
paycallWBTC = abi.encodePacked(type(Paycall).creationCode, abi.encode(ETH_BTC_PRICE_FEED, WBTC, true));
paycallNonReverty = abi.encodePacked(type(Paycall).creationCode, abi.encode(ETH_USD_PRICE_FEED, USDC, false));

paycallAddress = codeJar.saveCode(paycall);
paycallUSDTAddress = codeJar.saveCode(paycallUSDT);
paycallWBTCAddress = codeJar.saveCode(paycallWBTC);
paycallNonRevertyAddress = codeJar.saveCode(paycallNonReverty);

legendCometSupplyScriptAddress = codeJar.saveCode(legendCometSupplyScript);
legendCometWithdrawScriptAddress = codeJar.saveCode(legendCometWithdrawScript);
Expand Down Expand Up @@ -165,7 +175,10 @@ contract PaycallTest is Test {

// gas: meter execute
vm.resumeGasMetering();
vm.expectEmit(true, true, true, false); // We ignore the amount because it will differ based on via-IR
emit PayForGas(address(wallet), tx.origin, USDC, 7_264_471);
wallet.executeQuarkOperation(op, v, r, s);

assertEq(counter.number(), 15);
assertApproxEqAbs(IERC20(USDC).balanceOf(address(wallet)), 992e6, 1e6);
}
Expand Down Expand Up @@ -199,7 +212,10 @@ contract PaycallTest is Test {

// gas: meter execute
vm.resumeGasMetering();
vm.expectEmit(true, true, true, false); // We ignore the amount because it will differ based on via-IR
emit PayForGas(address(wallet), tx.origin, USDC, 7_489_695);
wallet.executeQuarkOperation(op, v, r, s);

assertApproxEqAbs(IERC20(USDC).balanceOf(address(wallet)), 982e6, 1e6);
assertEq(IERC20(USDC).balanceOf(address(this)), 10e6);
}
Expand Down Expand Up @@ -257,7 +273,10 @@ contract PaycallTest is Test {

// gas: meter execute
vm.resumeGasMetering();
vm.expectEmit(true, true, true, false); // We ignore the amount because it will differ based on via-IR
emit PayForGas(address(wallet), tx.origin, USDC, 18_045_902);
wallet.executeQuarkOperation(op, v, r, s);

assertApproxEqAbs(IERC20(USDC).balanceOf(address(wallet)), 981e6, 1e6);
assertEq(IComet(cUSDCv3).collateralBalanceOf(address(wallet), WETH), 100 ether);
assertApproxEqAbs(IComet(cUSDCv3).borrowBalanceOf(address(wallet)), 1000e6, 2);
Expand Down Expand Up @@ -326,7 +345,10 @@ contract PaycallTest is Test {
(uint8 v, bytes32 r, bytes32 s) = new SignatureHelper().signOp(alicePrivateKey, wallet, op);

vm.resumeGasMetering();
vm.expectEmit(true, true, true, false); // We ignore the amount because it will differ based on via-IR
emit PayForGas(address(wallet), tx.origin, USDT, 7_056_836);
wallet.executeQuarkOperation(op, v, r, s);

assertEq(IERC20(WETH).balanceOf(address(this)), 1 ether);
// About $8 in USD fees
assertApproxEqAbs(IERC20(USDT).balanceOf(address(wallet)), 992e6, 1e6);
Expand Down Expand Up @@ -361,10 +383,13 @@ contract PaycallTest is Test {
(uint8 v, bytes32 r, bytes32 s) = new SignatureHelper().signOp(alicePrivateKey, wallet, op);

vm.resumeGasMetering();
vm.expectEmit(true, true, true, false); // We ignore the amount because it will differ based on via-IR
emit PayForGas(address(wallet), tx.origin, WBTC, 20_332);
wallet.executeQuarkOperation(op, v, r, s);

assertEq(IERC20(WETH).balanceOf(address(this)), 1 ether);
// Fees in WBTC will be around ~ 0.00021 WBTC
assertApproxEqAbs(IERC20(WBTC).balanceOf(address(wallet)), 99979e3, 1e3);
assertApproxEqAbs(IERC20(WBTC).balanceOf(address(wallet)), 99_979_000, 1e3);
}

function testRevertsWhenCostIsMoreThanMaxPaymentCost() public {
Expand Down Expand Up @@ -401,4 +426,30 @@ contract PaycallTest is Test {

assertEq(IERC20(USDC).balanceOf(address(wallet)), 1000e6);
}

function testPaycallNonRevertyDoesNotRevertWhenCallReverts() public {
// gas: do not meter set-up
vm.pauseGasMetering();
vm.txGasPrice(32 gwei);
QuarkWallet wallet = QuarkWallet(factory.create(alice, address(0)));
// Give wallet some USDC for payment
deal(USDC, address(wallet), 1000e6);

// Execute through paycall
QuarkWallet.QuarkOperation memory op = new QuarkOperationHelper().newBasicOpWithCalldata(
wallet,
paycallNonReverty,
abi.encodeWithSelector(Paycall.run.selector, revertsAddress, "", 20e6),
ScriptType.ScriptSource
);
(uint8 v, bytes32 r, bytes32 s) = new SignatureHelper().signOp(alicePrivateKey, wallet, op);

// gas: meter execute
vm.resumeGasMetering();
vm.expectEmit(true, true, true, false); // We ignore the amount because it will differ based on via-IR
emit PayForGas(address(wallet), tx.origin, USDC, 5_553_259);
wallet.executeQuarkOperation(op, v, r, s);

assertApproxEqAbs(IERC20(USDC).balanceOf(address(wallet)), 995e6, 1e6);
}
}
Loading