-
Notifications
You must be signed in to change notification settings - Fork 24
FLIP 348: EVM Scheduled Transactions #349
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
base: main
Are you sure you want to change the base?
FLIP 348: EVM Scheduled Transactions #349
Conversation
joshuahannan
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well thought out and well written. I just have a few questions and comments
|
|
||
| ```go | ||
| type EVMScheduler interface { | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is getStatus included here or is that just querying the status on the EVM side?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can just use the EVM side to query. This way we could even introduce failed status.
| Scheduling involves coordination between all the above components and should be initiated from EOA using the EVM Scheduler proxy Solidity contract. | ||
|
|
||
| 1. **EOA** | ||
| 1. Submits an EVM transaction that calls the EVM Scheduler Proxy with all required arguments as well as the amount needed to pay for execution. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe add a first step here for EOA having to deploy a contract that contains the logic to execute for the scheduled transaction
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good idea
| 1. Submits an EVM transaction that calls the EVM Scheduler Proxy with all required arguments as well as the amount needed to pay for execution. | ||
| 2. **Solidity EVM Scheduler Proxy** | ||
| 1. The EVM Scheduler Proxy receives a scheduling request in Solidity with all arguments (handler address, data, priority, timestamp, gas limit). It stores the arguments internally and it also records the sender as the owner of the transaction data. | ||
| 2. If provided funds are sufficient, it forwards them to the EVM Scheduler COA. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How are we determining if the provided funds are sufficient? Is there a standard transaction fee calculation for a provided execution effort for Solidity? Are we taking the data storage into account?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It checks in the contract, so not standard fee, but additional check for the msg.value
| 1. The schedule function is invoked, which internally stores the gas limit. | ||
| 2. It withdraws the funds from EVM to a vault it will use to schedule with Flow transaction scheduler. | ||
| 3. If the funds were successfully withdrawn, it calls the Transaction Scheduler schedule function and provides the funds, priority, effort limit (converted from gas limit), and timestamp. For the handler, it provides its own function. It doesn’t forward any other EVM data. | ||
| 4. It records the scheduled transaction ID it received from the Transaction scheduler contract. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What does it do with the ScheduledTransaction resource? does it store it or destroy it? It might be good to keep it until the transaction is executed. The storage would need to be paid for though
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hm, I was thinking to destroy it since there's no use of keeping it, unless you think of something.
|
|
||
| # Alternatives | ||
|
|
||
| There were multiple alternatives considered, some of which proved to be unviable, most elegant ones due to limitation of Cadence Arch authorization mechanism. The Cadence Arch is a precompile that must follow the [EVM precompile interface](https://github.com/ethereum/go-ethereum/blob/1487a8577d1566497e161a04f8cee3204d4b3d36/core/vm/contracts.go#L52) and unless we fork the EVM we can not introduce more arguments to it. Forking of EVM is not acceptable at this point. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ah this makes sense. I think it may be possible without forking EVM (as Call will precede with transfer from msg.sender only issue maybe delegatecall there but I don't think it is too important in this context )
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What do you mean? can you elaborate
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Now every call in EVM side is basically Transfer + Call ( Transfer is the part sending the msg.value ) [0]
This Transfer part is something we can hook into [1] with TransferFunc func(StateDB, common.Address, common.Address, *uint256.Int)
Here we can find out caller ( which is msg.sender )
Now imagine let's say we saved the last caller in somewhere ( blockview, config etc ) then when we are injecting precompiles [2] we can inject that into precompile function. [3]
something like:
return MultiFunctionPrecompiledContract(
address,
[]Function{
&flowBlockHeight{heightProvider},
&proofVerifier{proofVer},
&randomnessSource{randomSourceProvider},
&revertibleRandom{revertibleRandomGenerator},
&scheduleThingy{lastSenderGetter}.
},
)
So technically precompiles can access the caller without forking EVM probably.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see what you mean. This approach can work. The only concern I have with it is I guess we would have to copy and modify the TransferFunc which is very critical function that should always be kept exactly in sync with the latest implementation.
I have to think a bit about this approach, but it could potentially unlock more context on the cadence arch. But I need to think about security of it more, since it's quite hacky.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
you can still call from gethCall, basically it will be something like this:
func(sb StateDB, caller common.Address, to common.Address, amount *uint256.Int){
// save caller
xxx.lastCaller = caller
// call original impl.
gethCore.Transfer(sb, caller, to, amount)
}yeah it is kinda hacky, I was using this to hook into transfers when I was developing tinyAN, just recalled from there.
|
very nice work, I think this will be very useful. |
Co-authored-by: Joshua Hannan <joshua.hannan@flowfoundation.org>
Co-authored-by: Dieter Shirley <dete@dapperlabs.com>
Thank you, great feedback. |
Issue: #348