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 Recurrent Script #174

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
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
60 changes: 60 additions & 0 deletions src/quark-core-scripts/src/Recurrent.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
// SPDX-License-Identifier: BSD-3-Clause
pragma solidity 0.8.23;

import {QuarkScript} from "quark-core/src/QuarkScript.sol";

/**
* @title Recurrent Core Script
* @notice Core transaction script that can be used to call a function every so often
* @author Compound Labs, Inc.
*/
contract Recurrent is QuarkScript {
Copy link
Collaborator

Choose a reason for hiding this comment

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

You could consider adding a cancel() function that just cancels the nonce. See the RecurringPurchase example.

error InvalidCallContext();
error TooFrequent(uint256 lastExecution);
error Premature();
error Expired();

/// @notice This contract's address
address internal immutable scriptAddress;

/**
* @notice Constructor
*/
constructor() {
scriptAddress = address(this);
}

/**
* @notice Execute a single call
* @dev Note: Does not use a reentrancy guard, so make sure to only call into trusted contracts
* @param callContract Contract to call
* @param callData Encoded calldata for call
* @param interval Interval for the call in seconds (i.e. every X seconds)
* @param notBefore Do not run this script before this time.
* @param notAfter Do not run this script after this time.
* @return Return data from call
*/
function run(address callContract, bytes calldata callData, uint256 interval, uint256 notBefore, uint256 notAfter) external returns (bytes memory) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Kind of interesting thing is that you can have multiple replayable txns using the same nonce with different calldata because Quark allows you to do that now.

if (address(this) == scriptAddress) {
revert InvalidCallContext();
}

// Note: this starts out as zero, as in
uint256 lastExecution = readU256("lastExecution");
Copy link
Collaborator

Choose a reason for hiding this comment

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

nit: Minor optimization can be to use a 1 char key instead. You can store it as a constant to keep the same readability.

if (block.timestamp < lastExecution + interval) revert TooFrequent(lastExecution);
Comment on lines +43 to +44
Copy link
Contributor

Choose a reason for hiding this comment

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

If the value is zero, don't we need to set it first? As i understand, this will always revert the first time unless interval is bigger than block.timestamp which i don't think makes sense

Copy link
Collaborator

Choose a reason for hiding this comment

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

@banky I think you have that logic inversed. It shouldn't revert if lastExecution is 0 since block.timestamp will likely be greater than lastExecution + interval.

Copy link
Contributor

Choose a reason for hiding this comment

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

ohh yeah that's right haha. i was reading it as if it was require

if (block.timestamp < notBefore) revert Premature();
if (block.timestamp > notAfter) revert Expired();
writeU256("lastExecution", block.timestamp);

(bool success, bytes memory returnData) = callContract.delegatecall(callData);
if (!success) {
assembly {
revert(add(returnData, 32), mload(returnData))
}
}

allowReplay();

return returnData;
}
}
Loading