Skip to content

Commit

Permalink
Add max payment cost to Paycall (#185)
Browse files Browse the repository at this point in the history
The max payment cost is specified in wei and is used to revert the script if the payment token amount exceeds it.
  • Loading branch information
kevincheng96 authored Mar 22, 2024
1 parent bb166ca commit 5f13394
Show file tree
Hide file tree
Showing 2 changed files with 60 additions and 10 deletions.
10 changes: 9 additions & 1 deletion src/quark-core-scripts/src/Paycall.sol
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ contract Paycall {
using SafeERC20 for IERC20;

error InvalidCallContext();
error TransactionTooExpensive();

/// @notice This contract's address
address internal immutable scriptAddress;
Expand Down Expand Up @@ -53,9 +54,13 @@ contract Paycall {
* @notice Execute delegatecall on a contract and pay tx.origin for gas
* @param callContract Contract to call
* @param callData Encoded calldata for call
* @param maxPaymentCost The maximum amount of payment tokens allowed for this transaction
* @return Return data from call
*/
function run(address callContract, bytes calldata callData) external returns (bytes memory) {
function run(address callContract, bytes calldata callData, uint256 maxPaymentCost)
external
returns (bytes memory)
{
uint256 gasInitial = gasleft();
// Ensures that this script cannot be called directly and self-destructed
if (address(this) == scriptAddress) {
Expand All @@ -72,6 +77,9 @@ contract Paycall {
(, int256 price,,,) = AggregatorV3Interface(ethBasedPriceFeedAddress).latestRoundData();
uint256 gasUsed = gasInitial - gasleft() + GAS_OVERHEAD;
uint256 paymentAmount = gasUsed * tx.gasprice * uint256(price) / divisorScale;
if (paymentAmount > maxPaymentCost) {
revert TransactionTooExpensive();
}
IERC20(paymentTokenAddress).safeTransfer(tx.origin, paymentAmount);

return returnData;
Expand Down
60 changes: 51 additions & 9 deletions test/quark-core-scripts/Paycall.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,8 @@ contract PaycallTest is Test {
address(counter),
abi.encodeCall(Counter.setNumber, (1)),
0 // value
)
),
10e6
);
}

Expand Down Expand Up @@ -155,7 +156,8 @@ contract PaycallTest is Test {
abi.encodeWithSelector(
Paycall.run.selector,
multicallAddress,
abi.encodeWithSelector(Multicall.run.selector, callContracts, callDatas)
abi.encodeWithSelector(Multicall.run.selector, callContracts, callDatas),
10e6
),
ScriptType.ScriptSource
);
Expand Down Expand Up @@ -188,7 +190,8 @@ contract PaycallTest is Test {
USDC,
abi.encodeWithSignature("transfer(address,uint256)", address(this), 10e6),
0
)
),
20e6
),
ScriptType.ScriptSource
);
Expand Down Expand Up @@ -245,7 +248,8 @@ contract PaycallTest is Test {
abi.encodeWithSelector(
Paycall.run.selector,
multicallAddress,
abi.encodeWithSelector(Multicall.run.selector, callContracts, callDatas)
abi.encodeWithSelector(Multicall.run.selector, callContracts, callDatas),
20e6
),
ScriptType.ScriptSource
);
Expand Down Expand Up @@ -275,7 +279,8 @@ contract PaycallTest is Test {
ethcallAddress,
abi.encodeWithSelector(
Ethcall.run.selector, address(counter), abi.encodeWithSignature("decrement(uint256)", (1)), 0
)
),
20e6
),
ScriptType.ScriptSource
);
Expand All @@ -297,7 +302,7 @@ contract PaycallTest is Test {
vm.txGasPrice(32 gwei);
QuarkWallet wallet = QuarkWallet(factory.create(alice, address(0)));

// Deal some USDT and WBTC
// Deal some USDT and WETH
deal(USDT, address(wallet), 1000e6);
deal(WETH, address(wallet), 1 ether);

Expand All @@ -313,7 +318,8 @@ contract PaycallTest is Test {
WETH,
abi.encodeWithSignature("transfer(address,uint256)", address(this), 1 ether),
0
)
),
10e6
),
ScriptType.ScriptSource
);
Expand All @@ -331,7 +337,7 @@ contract PaycallTest is Test {
vm.txGasPrice(32 gwei);
QuarkWallet wallet = QuarkWallet(factory.create(alice, address(0)));

// Deal some USDT and WBTC
// Deal some WBTC and WETH
deal(WBTC, address(wallet), 1e8);
deal(WETH, address(wallet), 1 ether);

Expand All @@ -347,7 +353,8 @@ contract PaycallTest is Test {
WETH,
abi.encodeWithSignature("transfer(address,uint256)", address(this), 1 ether),
0
)
),
30e3
),
ScriptType.ScriptSource
);
Expand All @@ -359,4 +366,39 @@ contract PaycallTest is Test {
// Fees in WBTC will be around ~ 0.00021 WBTC
assertApproxEqAbs(IERC20(WBTC).balanceOf(address(wallet)), 99979e3, 1e3);
}

function testRevertsWhenCostIsMoreThanMaxPaymentCost() public {
vm.pauseGasMetering();
vm.txGasPrice(32 gwei);
QuarkWallet wallet = QuarkWallet(factory.create(alice, address(0)));

// Deal some USDC and WETH
deal(USDC, address(wallet), 1000e6);
deal(WETH, address(wallet), 1 ether);

// Pay with USDC
QuarkWallet.QuarkOperation memory op = new QuarkOperationHelper().newBasicOpWithCalldata(
wallet,
paycall,
abi.encodeWithSelector(
Paycall.run.selector,
ethcallAddress,
abi.encodeWithSelector(
Ethcall.run.selector,
WETH,
abi.encodeWithSignature("transfer(address,uint256)", address(this), 1 ether),
0
),
5e6
),
ScriptType.ScriptSource
);
(uint8 v, bytes32 r, bytes32 s) = new SignatureHelper().signOp(alicePrivateKey, wallet, op);

vm.resumeGasMetering();
vm.expectRevert(abi.encodeWithSelector(Paycall.TransactionTooExpensive.selector));
wallet.executeQuarkOperation(op, v, r, s);

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

0 comments on commit 5f13394

Please sign in to comment.