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

EVM support #225

Merged
merged 58 commits into from
May 22, 2024
Merged
Show file tree
Hide file tree
Changes from 22 commits
Commits
Show all changes
58 commits
Select commit Hold shift + click to select a range
7f73d24
evm support flip
ramtinms Nov 27, 2023
1a8a6ec
fix typos
ramtinms Nov 28, 2023
0c99501
Apply suggestions from code review
ramtinms Dec 1, 2023
3a3a805
Apply suggestions from code review
ramtinms Dec 1, 2023
fbb565c
Apply suggestions from code review
ramtinms Dec 1, 2023
4db769c
apply pr feedbacks
ramtinms Dec 4, 2023
b8b0378
set issue number
ramtinms Dec 4, 2023
d82c91d
Apply @turbolent's suggestions from his review
franklywatson Dec 11, 2023
c0e3a0f
apply PR feedback
ramtinms Dec 13, 2023
5dad2cb
Update protocol/20231116-evm-support.md
franklywatson Dec 13, 2023
8323c2b
Reference uniqueness of BridgedAccount
franklywatson Dec 13, 2023
5c3004e
Link to more tech information about Flow consensus
franklywatson Dec 13, 2023
f646491
Apply suggestions from code review
turbolent Dec 20, 2023
dac4fe4
Added image directory and EVM diagrams
franklywatson Dec 22, 2023
7cd8dc6
Updated FLIP to reflect new naming
franklywatson Dec 22, 2023
900af71
Added Flow EVM account model diagram
franklywatson Dec 22, 2023
bcd7a14
Further tidyup
franklywatson Dec 22, 2023
27c4675
Restore diagram from merge error
franklywatson Dec 22, 2023
d2176fc
add language to code fences
turbolent Jan 5, 2024
6dbd64d
document EVM.run
turbolent Jan 5, 2024
eb3543d
improve API
turbolent Jan 5, 2024
aa6ca62
convert example script to transaction
turbolent Jan 5, 2024
586aa0f
update flip with recent changes
ramtinms Jan 15, 2024
3411fbe
typos
ramtinms Jan 15, 2024
bf0dcf2
typos
ramtinms Jan 15, 2024
5e65fa1
Apply suggestions from code review
turbolent Jan 17, 2024
550a270
link to ERC-721
turbolent Jan 17, 2024
69f77cd
Add line break
franklywatson Jan 17, 2024
631055f
small improvements
ramtinms Jan 18, 2024
cc76ab1
change state toproposed
ramtinms Jan 18, 2024
683be9f
update isValid draft
ramtinms Jan 18, 2024
318b15b
link to ERC-1271
turbolent Jan 18, 2024
93931f4
fix deposite
ramtinms Jan 24, 2024
5c78cb6
Minor naming change
franklywatson Feb 5, 2024
ca025f7
Update protocol/20231116-evm-support.md
ramtinms Feb 21, 2024
3dc0125
Update EVM support FLIP with new interface & Cadence 1.0 refactor (#249)
sisyphusSmiling Feb 23, 2024
3e6c3b8
update evm contract
ramtinms Mar 4, 2024
f9ae903
add status codes
ramtinms Mar 4, 2024
2023b5a
update doc about run
ramtinms Mar 4, 2024
77068ea
add any evm address deposit to the contract
ramtinms Mar 5, 2024
d010243
add details about the token bridge
ramtinms Mar 11, 2024
e0122e0
Update deploy return type
devbugging Apr 2, 2024
3f8c071
update Cadence 1.0 syntax
devbugging Apr 2, 2024
eb6f104
update deploy return result type
devbugging Apr 11, 2024
45ae318
update email
turbolent Apr 11, 2024
def9a09
deploy update comment
devbugging Apr 11, 2024
21c817e
update deploy result optional
devbugging Apr 23, 2024
13cc05f
Update protocol/20231116-evm-support.md status
franklywatson Apr 24, 2024
a0e9aac
Add batch run to EVM FLIP (#257)
devbugging Apr 29, 2024
eff8cb2
updates to all the changes
devbugging Apr 29, 2024
29c4b4a
add dry run
devbugging Apr 29, 2024
6c185ab
update with events
devbugging Apr 29, 2024
d717807
add missing comma
devbugging Apr 29, 2024
bacc4cc
revert status change, FLIP is not approved yet
turbolent May 7, 2024
380fb8f
update with hex-encoded events and revertible random
devbugging May 20, 2024
39a4bb9
put back appendix c
devbugging May 20, 2024
a1793b2
make functions view
devbugging May 20, 2024
70e1ae2
update view funcs
devbugging May 20, 2024
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
238 changes: 238 additions & 0 deletions protocol/20231116-evm-support.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,238 @@
---
status: draft
flip: 223
authors: Ramtin Seraj ([email protected]), Bastian Müller ([email protected])
turbolent marked this conversation as resolved.
Show resolved Hide resolved
sponsor: Dieter Shirley ([email protected])
updated: 2023-12-04
---

# FLIP 223: EVM integration interface

## Objective

- Defining a Cadence interface for the EVM integrated into the FVM.
- Facilitates seamless interaction between Cadence and EVM environments.

## Motivation

Following the discussion [here](https://forum.flow.com/t/evm-on-flow-beyond-solidity/5260), this proposal outlines a path to achieve full EVM equivalence on Flow, enabling developers to deploy any Ethereum decentralized application (dApp) on Flow without making any code changes. This allows developers to fully leverage the native functionality of Flow. Trusted tools and protocols such as Uniswap, Opensea, Metamask, Chainlink Oracle, Layerzero, AAVE, Curve, Remix, and Hardhat will be readily compatible with Flow. Additionally, developers will still have the ability to write Cadence smart contracts to extend and enhance the functionality of Solidity smart contracts, ensuring full composability.

Support for EVM on Flow enables developers to leverage the network effects and tooling available in Solidity and EVM, while also benefiting from Flow's user-friendly features and mainstream focus for onboarding and user experience. Additionally, developers can take advantage of Cadence's unique capabilities.

## Design Proposal

#### EVM as a standard smart contract

To better understand the approach proposed in this Flip, consider EVM on Flow as a virtual blockchain deployed to the Flow blockchain at a specific address (e.g., a service account). EVM on Flow functions as a smart contract that emulates the EVM with its own dedicated chain-ID. Signed transactions are inputted, and a chain of blocks is produced as output. Similar to other built-in standard contracts (e.g., RLP encoding), this EVM environment can be imported into any Flow transaction or script.

This is made possible using selective integration of the core EVM runtime without the supporting software stack in which it currently exists on Ethereum. For equivalence we also provide an EVM compatible JSON-RPC API implementation to facilitate EVM on Flow interactions from existing EVM clients.

```cadence
import EVM from <ServiceAddress>
```

Within the Flow transaction, if EVM interaction is successful

- it makes changes to the on-chain data
- forms a new block if successful,
- emits several Flow event types (see [here](https://github.com/onflow/flow-go/blob/master/fvm/evm/types/events.go)) that can be consumed to track the chain progress.
And if unsuccessful, it reverts the transaction.

As EVM interactions are encapsulated within Flow transactions, they leverage the security measures provided by Flow. These transactions undergo the same process of collection, execution, and verification as other Flow transactions, without any EVM intervention. Consequently, there is no requirement for intricate block formation logic (such as handling forks and reorganizations), mempools, or additional safeguards against malicious MEV (Miner Extractable Value) behaviours. More information about Flow's consensus model is available [here](https://flow.com/core-protocol).

In the EVM environment, resource consumption is metered as "gas usage". When interacting with the EVM environment, the total gas usage is translated back into Flow computation usage and is paid as part of FLOW transaction fees (weigh-adjusted conversion).

#### EVM Addresses

In the EVM world, there is no concept of accounts or a minimum balance requirement. Any sequence of bytes with a length of 20 is considered a valid address.

Every EVM address has a balance of native tokens (e.g. ETH on Ethereum), a nonce (for deduplication) and a root hash of the state (if smart contract).
In this design, we use the FLOW token for this native token. The balance of an EVM address is stored as a smaller denomination of FLOW called `Atto-FLOW`, it works similarly to the way Wei is used to store ETH values on Ethereum.

```cadence
access(all)
contract EVM {

/// EVMAddress is an EVM-compatible address
access(all)
struct EVMAddress {

/// Bytes of the address
access(all)
let bytes: [UInt8; 20]

/// Constructs a new EVM address from the given byte representation
init(bytes: [UInt8; 20])
Copy link
Contributor

@sisyphusSmiling sisyphusSmiling Jan 4, 2024

Choose a reason for hiding this comment

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

I'm finding it frustrating to convert between [UInt8] and [UInt8; 20] when constructing EVMAddress and I think others will as well. For example, given some hex encoded address, I would want to construct an EVMAddress in a script to call a solidity contract. I would expect I could simply:

let evmAddress = EVMAddress(bytes: hexAddress.decodeHex())

Instead, I have to do:

let evmAddressBytes: [UInt8] = evmContractAddressHex.decodeHex()
let evmAddress = EVM.EVMAddress(
        bytes: [
            evmAddressBytes[0], evmAddressBytes[1], evmAddressBytes[2], evmAddressBytes[3], evmAddressBytes[4],
            evmAddressBytes[5], evmAddressBytes[6], evmAddressBytes[7], evmAddressBytes[8], evmAddressBytes[9],
            evmAddressBytes[10], evmAddressBytes[11], evmAddressBytes[12], evmAddressBytes[13], evmAddressBytes[14],
            evmAddressBytes[15], evmAddressBytes[16], evmAddressBytes[17], evmAddressBytes[18], evmAddressBytes[19]
        ]
    )

I think it would be preferable to abstract away the complexities of dealing with the restricted byte array in the struct's initialization.

Copy link
Member

Choose a reason for hiding this comment

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

It would be great to keep the type-safety, though I see and agree with the UX problems.

If the input type would be a variable-sized array, what would be the behaviour for input that is too short or too long? Developers might make assumptions, which could potentially be different from actual behaviour, and thus lead to costly mistakes.

For converting between constant-size and variable-size arrays, there is actually a Cadence feature request issue open: onflow/cadence#2530

Copy link
Contributor

Choose a reason for hiding this comment

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

I think a pre-condition like bytes.length == 20 in EVMAddress.init is behaviorally similar to the current implementation. Is that workable?

Copy link
Member

Choose a reason for hiding this comment

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

A pre-condition is a dynamic (run-time) check, and not a static one (type-checking time). It would be nice not to have to express the requirement of the length statically so errors can be prevented as early as possible


/// Returns the balance of this address
access(all)
turbolent marked this conversation as resolved.
Show resolved Hide resolved
fun balance(): Balance

/// Deposits the given vault into the EVM account with the given address
access(all)
fun deposit(from: @FlowToken.Vault)
}
}
```

A FLOW is equivalent of 10^18 atto-FLOW. Because EVM environments uses different way of storage of value for the native token than FLOW protocol (FLOW protocol uses a fixed point representation), to remove any room for mistakes, EVM environment has structure called `Balance` that could be used.

Note that no new FLOW token is minted and every balance on EVM addresses has to be deposited by bridging FLOW tokens into addresses using `deposit` method.

```cadence
access(all)
contract EVM {

access(all)
struct Balance {
/// The balance in FLOW
access(all)
let flow: UFix64

/// Constructs a new balance, given the balance in FLOW
init(flow: UFix64)

/// Returns the balance in terms of atto-FLOW.
/// Atto-FLOW is the smallest denomination of FLOW inside EVM
access(all)
fun toAttoFlow(): UInt64
}
}
```

Every account on Flow EVM could be queried by constructing an EVM structure. Here is an example:

```cadence
// Example of balance query
import EVM from <ServiceAddress>

access(all)
fun main(bytes: [UInt8; 20]) {
let addr = EVM.EVMAddress(bytes: bytes)
let bal = addr.balance()
}
```

#### EVM-style transaction wrapping

One of the design goals of this work is to ensure that existing EVM ecosystem tooling and products which builders use can integrate effortlessly. To achieve this, the EVM smart contract accepts RLP-encoded transactions for execution. Any transaction can be wrapped and submitted by any user through Flow transactions. As mentioned earlier, the resource usage during EVM interaction is translated into Flow transaction fees, which must be paid by the account that wrapped the original transaction.

To facilitate the wrapping operation and refunding, the run interface also allows a `coinbase` address to be passed. The use of the `coinbase` address in this context indicates the EVM address which will receive the gas usage * gas price (set in transaction). Essentially, the transaction wrapper behaves similarly to a miner, receives the gas usage fees on an EVM address and pays for the transaction fees.

Any failure during the execution would revert the whole Flow transaction.

```cadence
access(all)
contract EVM {

/// Runs an a RLP-encoded EVM transaction, deducts the gas fees,
/// and deposits the gas fees into the provided coinbase address.
///
/// Returns true if the transaction was successful,
/// and returns false otherwise
access(all)
fun run(tx: [UInt8], coinbase: EVMAddress): Bool
}
```

```cadence
// Example of tx wrapping
import EVM from <ServiceAddress>

transaction(rlpEncodedTransaction: [UInt8], coinbaseBytes: [UInt8; 20]) {

prepare(signer: AuthAccount) {
let coinbase = EVM.EVMAddress(bytes: coinbaseBytes)
EVM.run(tx: rlpEncodedTransaction, coinbase: coinbase)
}
}
```

For example, a user might use MetaMask to sign a transaction for Flow EVM and broadcast it to services that check the gas fee on the transaction and wrap the transaction to be executed.

Note that account nonce would protect against double execution of a transaction, similar to how other non-virtual blockchains prevent the minor from including a transaction multiple times.

#### Cadence Owned Accounts

Another major goal for this work is seamless composability across environments. For this goal, we have introduced a new type of address a Cadence Owned Account (COA), to the EVM environment (besides EOA and Smart Contract accounts). This new address type is similar to EOAs except instead of being tied to a public/private key it would be controlled by Cadence resources. Unlike EOAs which are created using the key presented by the wallet there is no corresponding EVM key present in a CadenceOwnedAccount. Any COA is interacted with through CadenceOwnedAccount resource and any Flow account or Cadence smart contract that holds this resource could interact with the EVM environment on behalf of the address that is stored in the resource.

```cadence
access(all)
contract EVM {

access(all)
resource CadenceOwnedAccount {

access(self)
let addressBytes: [UInt8; 20]

/// Constructs a new Cadence Owned Account for the address
init(addressBytes: [UInt8; 20])

/// The EVM address of the Cadence Owned Account
access(all)
fun address(): EVMAddress

turbolent marked this conversation as resolved.
Show resolved Hide resolved
/// Withdraws the balance from the Cadence Owned Account's balance
access(all)
franklywatson marked this conversation as resolved.
Show resolved Hide resolved
fun withdraw(balance: Balance): @FlowToken.Vault

/// Get balance of the Cadence Owned Account
access(all)
fun balance(): Balance

/// Deploys a contract to the EVM environment.
/// Returns the address of the newly deployed contract.
/// The value (balance) is taken from the EVM account.
access(all)
fun deploy(
code: [UInt8],
gasLimit: UInt64,
value: Balance
): EVMAddress

/// Calls a function with the given data.
/// The execution is limited by the given amount of gas.
/// The value (balance) is taken from the EVM account.
access(all)
fun call(
to: EVMAddress,
data: [UInt8],
gasLimit: UInt64,
value: Balance
): [UInt8]
}

/// Creates a new Cadence Owned Account
access(all)
fun createCadenceOwnedAccount(): @CadenceOwnedAccount
}
```

COA addresses are unique and allocated by the FVM and stored inside the resource. Calls through COAs form a new type of transaction for the EVM that doesn't require signatures and doesn't need nonce checking. COAs could deploy smart contracts or make calls to the ones that are already deployed on Flow EVM.

![Illustration of EVM account model](20231116-evm-support/flow-evm-account-model.png)

COAs also facilitate the withdrawal of Flow tokens back from the EVM balance environment into the Cadence environment through `withdraw`.

**What about other fungible and non-tokens?**

The term "Cadence Owned accounts" is used because their design facilitates the building of bridges. A Cadence smart contract can control a COA, enabling transactional operations between two environments. For instance, this smart contract can receive a Fungible on the Cadence side and send the equivalent on the EVM side to a specified address, all in a single Flow transaction. More details on this will be provided in subsequent updates.

#### Safety and Reproducibility of the EVM state

EVM state is not accessible by Cadence outside of the defined interfaces above, and Cadence state is also protected against raw access from the EVM.

At the start, the EVM state is empty (empty root hash), and all the state is stored under a Flow account that is not controlled by anyone (network-owned). Any interaction with this environment emits a transactionExecuted event (direct calls for COAs use `255` as transaction type).

So anyone following these events could reconstruct the whole EVM state by re-executing these transactions.

### Dependencies

The project introduces no new dependencies from the Ethereum codebase since those required are already included in flow-go

### Tutorials and Examples

A proof of concept implementation of this proposal is available [here](https://github.com/onflow/flow-emulator/releases/tag/v0.57.4-evm-poc).

### What parts of the design still need to be defined?
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.