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

[Design] L2 state derived automatic contract update transactions. #1736

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Changes from 1 commit
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
65 changes: 65 additions & 0 deletions design/scratchpad/Design_auto_updater.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
# Ten Auto Updater

## Introduction
Copy link
Collaborator

Choose a reason for hiding this comment

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

It is not clear what high-level problem this solves


In Ethereum it is well known that transactions cannot originate from contracts. There is no way to automatically run some update. Someone has to init the transaction. If you have a contract on a layer 2 that needs rebalancing for example someone has to initiate it. This results in having to publish the transaction on the L1 and pay for it.
Copy link
Collaborator

Choose a reason for hiding this comment

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

what does rebalancing mean?



This feature suggestion would allow to circumvent the L1 publishing by creating a definition of "L2 state derived transactions" which are akin to cross chain synthetic transactions.


### Cross Chain Synthetic Transactions Primer

The synthetic transactions we currently have are not published to the L1. They are not included in the batch albeit mutating its state. This is not an issue, because the transactions are deterministically derived from the L1 state or more specifically, the L1 block that the batch points to. This means that when a validator is recomputing the batch, the data for rebuilding the transactions, in the same order is available implicitly. If we were to publish them to the L1 it would only be redundant and increase costs.
Comment on lines +11 to +13
Copy link

Choose a reason for hiding this comment

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

The primer on "Cross Chain Synthetic Transactions" is informative. However, it assumes prior knowledge of the batch process and how it relates to L1 state. For clarity, consider adding a brief explanation or a reference link for readers who may not be familiar with these concepts.



### L2 State Derived transactions

L2 state derived transaction is a special synthetic transaction, also started by the sequencer that would call a specific contract. This contract would have to register itself as updateable and prepay L2 gas costs to the `block.coinbase`. Using this prepayed funding, the sequencer would create the implicit functions calling the entry point function with no calldata and no value. It is possible to extend to support implicit calldata sent through the prepayment, but its best to keep it simple for the initial design.

The updatable contracts would need to export a callable method that determines if an update transaction should happen. Let's say this function signature will be `shouldAutoUpdate() returns (bool)`.
Whenever a batch is produced, after all the transactions have been applied the sequencer would go through the registered contracts and `derive` the synthetic auto updates that need to happen. Then they would be applied as any other transaction would, where the gas limit and cost would be put as the prepaid amount. Anything unused will be refunded back to the contract.
Copy link

Choose a reason for hiding this comment

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

The section on "L2 State Derived transactions" introduces a new concept effectively. It's important to ensure that the term "sequencer" is well-defined elsewhere in the documentation, as it's a key actor in this process. The explanation of the contract registration and the prepayment mechanism is clear, but it might benefit from a simple diagram or flowchart to illustrate the process visually.


#### Computation implications

The callable `shouldAutoUpdate` function would need to be subsidized by the sequencer and thus would have hard cap gas limit. This is similar to how optimism grants free gas for some special auto calls on cross chain messages. Alternatively we can use a scheduling mechanic where the contract instructs the sequencer when a transaction should happen, similar to Arbitrum's retryable transactions. When something is scheduled, it would be prepaid and the executed scheduled call should reschedule a new one to keep the automation going.
Comment on lines +23 to +25
Copy link

Choose a reason for hiding this comment

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

The "Computation implications" subsection introduces the shouldAutoUpdate function and discusses its gas implications. The comparison to Optimism's gas grants is useful for context. However, the document should specify the exact gas limit for the shouldAutoUpdate calls or provide a method for determining it to avoid ambiguity.



#### Use cases
Copy link
Collaborator

Choose a reason for hiding this comment

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

this section should be in the beginning, in the high level overview


This feature can be good for a plethora of use cases that already rely on some sort of authorized caller to update the contract periodically. It would make those more decentralised, as the rules for when it should be called would be in the smart contract and early coming transactions would not be derived by the validator.
For example you can have option protocols that auto excercise and settle on expiry, instead of the current approach that requires people to manually execute the settlement.
It's also possible to have automatic liquidations for lending protocols.
Another obvious application would be automatic arbitrage.
Gas intensive dApps could also greatly benefit - imagine a uniswap proxy router contract that users just queue swaps in and they all get batch executed automatically at the end of the batch. If there is a heavily traded pair with a lot of concurrent traffic to it, this would reduce the expensive storage read/writes.

It's important to note that most dApps aren't structured to be automated in such a way - there is no way to iterate their debt for example, as its stored in maps and people search for liquidation opportunities off chain. Regardless of this, it's reasonable to expect the feature will be utilized. Contracts currently are mostly structured around rebalancing on user inputs directly, along with exposing some public functions that can do another rebalance based on calling other contracts and so on. This is highly inefficient as most of those contracts would be far better off using an automated update system that is paid for by the users who would split the cost instead of paying all of it. While its easy to argue that L2 execution costs are so low right now that this is irrelevant, lowering the barrier even more would enable more use cases and make current ones more efficient.


And lastly, there is a very exotic use case for this feature that isn't really possible anywhere - security monitoring. A contract can register an automatic callback that checks some constraints and if something is wrong pause everything automatically.
Copy link

Choose a reason for hiding this comment

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

The mention of "security monitoring" as an exotic use case is intriguing. It would be helpful to expand on this idea with more details or a hypothetical scenario to illustrate how it might work in practice.


### Tech Requirements

We'd need a predeployed system smart contract that allows for registering contracts:

```solidity
interface TenAutomationRegistry {
function registerCallback(bytes memory callInstructions) external payable
function uniqueCaller(address targetContract, bytes memory callInstructions) pure returns (address)
}
```

dApps will use this system contract to register callbacks. Whenever `registerCallback` is called a record will be put to call `msg.sender` with `callInstructions`.
It is payable in this design, because we might need to put some arbitrary cost to prevent DOS attacks.
The `uniqueCaller` getter would return the unique address who will be the `tx.origin` for a specific contract's callback. This will allow the dApps to limit who can call the special automatic functions if they wish to do so. Futhermore it will be a layer of security as the caller address will be isolated per contract's callback, in order to prevent potential security exploits, albeit I'm not able to come up with an example.
Copy link

Choose a reason for hiding this comment

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

The "Tech Requirements" section outlines the interface for the TenAutomationRegistry. The use of payable in the registerCallback function is appropriate given the need to prevent DOS attacks. The uniqueCaller function's purpose is well-explained, but it would be beneficial to include a note on how the unique address is generated or an example of its usage.



To implement the whole feature we'd need to extend the logic in the `BatchExecutor` component and simply add another layer of transactions at the end to be executed by the `evm_facade`. Those transactions will be executed with gas priced same as normal user transactions.
Copy link

Choose a reason for hiding this comment

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

The implementation details involving the BatchExecutor component are briefly mentioned. It would be advantageous to link to existing documentation or code for the BatchExecutor and evm_facade for readers to understand the changes in context.


### Performance considerations

If instead of scheduling, we use a callable `shouldAutoUpdate`, having too many registered contracts can start slowing down batch production. This is the only notable detail as we would have to subsidize those, but putting a cost on registering should still be somewhat of a deterrent, given that those calls will use limited gas cap.

As for potential slowdown of batch production due to the expensive computations when doing an automatic update - As this will have to be paid for by the contract/user/whatever to `block.coinbase` beforehand, I don't see any difference between `L2 derived transactions` and `L2 user transactions`. If a user transaction takes a while to finish on the EVM, it would pay for it. Same for the derived transactions.

There shouldn't be any noticeable performance degradation even if this feature is heavily utilised. When looking at the performance of the sequencer previously the bottleneck was never in the EVM processing. Even a ton of storage mutating transactions were taking sub milliseconds. If contracts are engineered smartly, with dirty flags for example, the automatic updates will be blazing fast as they would be hitting storage slots that are already in the in memory state tree. The fact that the auto updates would be derived from the L2 state means that most of its use cases would be triggered by mutations to warm storage slots.
Loading