Smart Contract

EVM

A.e467b9dd11fa00df.EVM

Valid From

138,783,760

Deployed

1w ago
Feb 14, 2026, 03:27:31 PM UTC

Dependents

63090 imports
1import Crypto
2import NonFungibleToken from 0x1d7e57aa55817448
3import FungibleToken from 0xf233dcee88fe0abe
4import FlowToken from 0x1654653399040a61
5
6/*
7
8    The Flow EVM contract defines important types and functionality
9    to allow Cadence code and Flow SDKs to interface
10    with the Etherem Virtual Machine environment on Flow.
11
12    The EVM contract emits events when relevant actions happen in Flow EVM
13    such as creating new blocks, executing transactions, and bridging FLOW
14
15    This contract also defines Cadence-Owned Account functionality,
16    which is currently the only way for Cadence code to interact with Flow EVM.
17
18    Additionally, functionality is provided for common EVM types
19    such as addresses, balances, ABIs, transaction results, and more.
20
21    The EVM contract is deployed to the Flow Service Account on every network
22    and many of its functionality is directly connected to the protocol software
23    to allow interaction with the EVM.
24
25    See additional EVM documentation here: https://developers.flow.com/evm/about
26
27*/
28
29access(all) contract EVM {
30
31    /// Block executed event is emitted when a new block is created,
32    /// which always happens when a transaction is executed.
33    access(all) event BlockExecuted (
34        // height or number of the block
35        height: UInt64,
36        // hash of the block
37        hash: [UInt8; 32],
38        // timestamp of the block creation
39        timestamp: UInt64,
40        // total Flow supply
41        totalSupply: Int,
42        // all gas used in the block by transactions included
43        totalGasUsed: UInt64,
44        // parent block hash
45        parentHash: [UInt8; 32],
46        // root hash of all the transaction receipts
47        receiptRoot: [UInt8; 32],
48        // root hash of all the transaction hashes
49        transactionHashRoot: [UInt8; 32],
50        /// value returned for PREVRANDAO opcode
51        prevrandao: [UInt8; 32],
52    )
53
54    /// Transaction executed event is emitted every time a transaction
55    /// is executed by the EVM (even if failed).
56    access(all) event TransactionExecuted (
57        // hash of the transaction
58        hash: [UInt8; 32],
59        // index of the transaction in a block
60        index: UInt16,
61        // type of the transaction
62        type: UInt8,
63        // RLP encoded transaction payload
64        payload: [UInt8],
65        // code indicating a specific validation (201-300) or execution (301-400) error
66        errorCode: UInt16,
67        // a human-readable message about the error (if any)
68        errorMessage: String,
69        // the amount of gas transaction used
70        gasConsumed: UInt64,
71        // if transaction was a deployment contains a newly deployed contract address
72        contractAddress: String,
73        // RLP encoded logs
74        logs: [UInt8],
75        // block height in which transaction was included
76        blockHeight: UInt64,
77        /// captures the hex encoded data that is returned from
78        /// the evm. For contract deployments
79        /// it returns the code deployed to
80        /// the address provided in the contractAddress field.
81        /// in case of revert, the smart contract custom error message
82        /// is also returned here (see EIP-140 for more details).
83        returnedData: [UInt8],
84        /// captures the input and output of the calls (rlp encoded) to the extra
85        /// precompiled contracts (e.g. Cadence Arch) during the transaction execution.
86        /// This data helps to replay the transactions without the need to
87        /// have access to the full cadence state data.
88        precompiledCalls: [UInt8],
89        /// stateUpdateChecksum provides a mean to validate
90        /// the updates to the storage when re-executing a transaction off-chain.
91        stateUpdateChecksum: [UInt8; 4]
92    )
93
94    /// FLOWTokensDeposited is emitted when FLOW tokens is bridged
95    /// into the EVM environment. Note that this event is not emitted
96    /// for transfer of flow tokens between two EVM addresses.
97    /// Similar to the FungibleToken.Deposited event
98    /// this event includes a depositedUUID that captures the
99    /// uuid of the source vault.
100    access(all) event FLOWTokensDeposited (
101        address: String,
102        amount: UFix64,
103        depositedUUID: UInt64,
104        balanceAfterInAttoFlow: UInt
105    )
106
107    /// FLOWTokensWithdrawn is emitted when FLOW tokens are bridged
108    /// out of the EVM environment. Note that this event is not emitted
109    /// for transfer of flow tokens between two EVM addresses.
110    /// similar to the FungibleToken.Withdrawn events
111    /// this event includes a withdrawnUUID that captures the
112    /// uuid of the returning vault.
113    access(all) event FLOWTokensWithdrawn (
114        address: String,
115        amount: UFix64,
116        withdrawnUUID: UInt64,
117        balanceAfterInAttoFlow: UInt
118    )
119
120    /// BridgeAccessorUpdated is emitted when the BridgeAccessor Capability
121    /// is updated in the stored BridgeRouter along with identifying
122    /// information about both.
123    access(all) event BridgeAccessorUpdated (
124        routerType: Type,
125        routerUUID: UInt64,
126        routerAddress: Address,
127        accessorType: Type,
128        accessorUUID: UInt64,
129        accessorAddress: Address
130    )
131
132     /// Block returns information about the latest executed block.
133    access(all) struct EVMBlock {
134        access(all) let height: UInt64
135
136        access(all) let hash: String
137
138        access(all) let totalSupply: Int
139
140        access(all) let timestamp: UInt64
141
142        init(height: UInt64, hash: String, totalSupply: Int, timestamp: UInt64) {
143            self.height = height
144            self.hash = hash
145            self.totalSupply = totalSupply
146            self.timestamp = timestamp
147        }
148    }
149
150    /// Returns the latest executed block.
151    access(all)
152    fun getLatestBlock(): EVMBlock {
153        return InternalEVM.getLatestBlock() as! EVMBlock
154    }
155
156    /// EVMAddress is an EVM-compatible address
157    access(all) struct EVMAddress {
158
159        /// Bytes of the address
160        access(all) let bytes: [UInt8; 20]
161
162        /// Constructs a new EVM address from the given byte representation
163        view init(bytes: [UInt8; 20]) {
164            self.bytes = bytes
165        }
166
167        /// Balance of the address
168        access(all)
169        view fun balance(): Balance {
170            let balance = InternalEVM.balance(
171                address: self.bytes
172            )
173            return Balance(attoflow: balance)
174        }
175
176        /// Nonce of the address
177        access(all)
178        fun nonce(): UInt64 {
179            return InternalEVM.nonce(
180                address: self.bytes
181            )
182        }
183
184        /// Code of the address
185        access(all)
186        fun code(): [UInt8] {
187            return InternalEVM.code(
188                address: self.bytes
189            )
190        }
191
192        /// CodeHash of the address
193        access(all)
194        fun codeHash(): [UInt8] {
195            return InternalEVM.codeHash(
196                address: self.bytes
197            )
198        }
199
200        /// Deposits the given vault into the EVM account with the given address
201        access(all)
202        fun deposit(from: @FlowToken.Vault) {
203            let amount = from.balance
204            if amount == 0.0 {
205                destroy from
206                return
207            }
208            let depositedUUID = from.uuid
209            InternalEVM.deposit(
210                from: <-from,
211                to: self.bytes
212            )
213            emit FLOWTokensDeposited(
214                address: self.toString(),
215                amount: amount,
216                depositedUUID: depositedUUID,
217                balanceAfterInAttoFlow: self.balance().attoflow
218            )
219        }
220
221        /// Serializes the address to a hex string without the 0x prefix
222        /// Future implementations should pass data to InternalEVM for native serialization
223        access(all)
224        view fun toString(): String {
225            return String.encodeHex(self.bytes.toVariableSized())
226        }
227
228        /// Compares the address with another address
229        access(all)
230        view fun equals(_ other: EVMAddress): Bool {
231            return self.bytes == other.bytes
232        }
233    }
234
235    /// Converts a hex string to an EVM address if the string is a valid hex string
236    /// Future implementations should pass data to InternalEVM for native deserialization
237    access(all)
238    fun addressFromString(_ asHex: String): EVMAddress {
239        pre {
240            asHex.length == 40 || asHex.length == 42:
241                "EVM.addressFromString(): Invalid hex string length for an EVM address. The provided string is \(asHex.length), but the length must be 40 or 42."
242        }
243        // Strip the 0x prefix if it exists
244        var withoutPrefix = (asHex[1] == "x" ? asHex.slice(from: 2, upTo: asHex.length) : asHex).toLower()
245        let bytes = withoutPrefix.decodeHex().toConstantSized<[UInt8; 20]>()!
246        return EVMAddress(bytes: bytes)
247    }
248
249    /// EVMBytes is a type wrapper used for ABI encoding/decoding into
250    /// Solidity `bytes` type
251    access(all) struct EVMBytes {
252
253        /// Byte array representing the `bytes` value
254        access(all) let value: [UInt8]
255
256        view init(value: [UInt8]) {
257            self.value = value
258        }
259    }
260
261    /// EVMBytes4 is a type wrapper used for ABI encoding/decoding into
262    /// Solidity `bytes4` type
263    access(all) struct EVMBytes4 {
264
265        /// Byte array representing the `bytes4` value
266        access(all) let value: [UInt8; 4]
267
268        view init(value: [UInt8; 4]) {
269            self.value = value
270        }
271    }
272
273    /// EVMBytes32 is a type wrapper used for ABI encoding/decoding into
274    /// Solidity `bytes32` type
275    access(all) struct EVMBytes32 {
276
277        /// Byte array representing the `bytes32` value
278        access(all) let value: [UInt8; 32]
279
280        view init(value: [UInt8; 32]) {
281            self.value = value
282        }
283    }
284
285    access(all) struct Balance {
286
287        /// The balance in atto-FLOW
288        /// Atto-FLOW is the smallest denomination of FLOW (1e18 FLOW)
289        /// that is used to store account balances inside EVM
290        /// similar to the way WEI is used to store ETH divisible to 18 decimal places.
291        access(all) var attoflow: UInt
292
293        /// Constructs a new balance
294        access(all)
295        view init(attoflow: UInt) {
296            self.attoflow = attoflow
297        }
298
299        /// Sets the balance by a UFix64 (8 decimal points), the format
300        /// that is used in Cadence to store FLOW tokens.
301        access(all)
302        fun setFLOW(flow: UFix64){
303            self.attoflow = InternalEVM.castToAttoFLOW(balance: flow)
304        }
305
306        /// Casts the balance to a UFix64 (rounding down)
307        /// Warning! casting a balance to a UFix64 which supports a lower level of precision
308        /// (8 decimal points in compare to 18) might result in rounding down error.
309        /// Use the inAttoFLOW function if you need more accuracy.
310        access(all)
311        view fun inFLOW(): UFix64 {
312            return InternalEVM.castToFLOW(balance: self.attoflow)
313        }
314
315        /// Returns the balance in Atto-FLOW
316        access(all)
317        view fun inAttoFLOW(): UInt {
318            return self.attoflow
319        }
320
321        /// Returns true if the balance is zero
322        access(all)
323        fun isZero(): Bool {
324            return self.attoflow == 0
325        }
326    }
327
328    /// reports the status of evm execution.
329    access(all) enum Status: UInt8 {
330        /// Returned (rarely) when status is unknown
331        /// and something has gone very wrong.
332        access(all) case unknown
333
334        /// Returned when execution of an evm transaction/call
335        /// has failed at the validation step (e.g. nonce mismatch).
336        /// An invalid transaction/call is rejected to be executed
337        /// or be included in a block.
338        access(all) case invalid
339
340        /// Returned when execution of an evm transaction/call
341        /// has been successful but the vm has reported an error in
342        /// the outcome of execution (e.g. running out of gas).
343        /// A failed tx/call is included in a block.
344        /// Note that resubmission of a failed transaction would
345        /// result in invalid status in the second attempt, given
346        /// the nonce would become invalid.
347        access(all) case failed
348
349        /// Returned when execution of an evm transaction/call
350        /// has been successful and no error is reported by the vm.
351        access(all) case successful
352    }
353
354    /// Reports the outcome of an evm transaction/call execution attempt
355    access(all) struct Result {
356        /// status of the execution
357        access(all) let status: Status
358
359        /// error code (error code zero means no error)
360        access(all) let errorCode: UInt64
361
362        /// error message
363        access(all) let errorMessage: String
364
365        /// returns the amount of gas metered during
366        /// evm execution
367        access(all) let gasUsed: UInt64
368
369        /// returns the data that is returned from
370        /// the evm for the call. For coa.deploy
371        /// calls it returns the code deployed to
372        /// the address provided in the contractAddress field.
373        /// in case of revert, the smart contract custom error message
374        /// is also returned here (see EIP-140 for more details).
375        access(all) let data: [UInt8]
376
377        /// returns the newly deployed contract address
378        /// if the transaction caused such a deployment
379        /// otherwise the value is nil.
380        access(all) let deployedContract: EVMAddress?
381
382        init(
383            status: Status,
384            errorCode: UInt64,
385            errorMessage: String,
386            gasUsed: UInt64,
387            data: [UInt8],
388            contractAddress: [UInt8; 20]?
389        ) {
390            self.status = status
391            self.errorCode = errorCode
392            self.errorMessage = errorMessage
393            self.gasUsed = gasUsed
394            self.data = data
395
396            if let addressBytes = contractAddress {
397                self.deployedContract = EVMAddress(bytes: addressBytes)
398            } else {
399                self.deployedContract = nil
400            }
401        }
402    }
403
404    /* 
405        Cadence-Owned Accounts (COA) 
406        A COA is a natively supported EVM smart contract wallet type 
407        that allows a Cadence resource to own and control an EVM address.
408        This native wallet provides the primitives needed to bridge
409        or control assets across Flow EVM and Cadence.
410        From the EVM perspective, COAs are smart contract wallets
411        that accept native token transfers and support several ERCs
412        including ERC-165, ERC-721, ERC-777, ERC-1155, ERC-1271.
413
414        COAs are not controlled by a key.
415        Instead, every COA account has a unique resource accessible
416        on the Cadence side, and anyone who owns that resource submits transactions
417        on behalf of this address. These direct transactions have COA’s EVM address
418        as the tx.origin and a new EVM transaction type (TxType = 0xff)
419        is used to differentiate these transactions from other types
420        of EVM transactions (e.g, DynamicFeeTxType (0x02).
421
422        Because of this, users are never able to access a key for their account,
423        meaning that they cannot control their COA's address on other EVM blockchains.
424    */
425
426    /* Entitlements enabling finer-grained access control on a CadenceOwnedAccount */
427
428    /// Allows validating ownership of a COA
429    access(all) entitlement Validate
430
431    /// Allows withdrawing FLOW from the COA back to Cadence
432    access(all) entitlement Withdraw
433
434    /// Allows sending Call transactions from the COA
435    access(all) entitlement Call
436
437    /// Allows sending deploy contract transactions from the COA
438    access(all) entitlement Deploy
439
440    /// Allows access to all the privliged functionality on a COA
441    access(all) entitlement Owner
442
443    /// Allows access to all bridging functionality for COAs
444    access(all) entitlement Bridge
445
446    /// Event that indicates when a new COA is created
447    access(all) event CadenceOwnedAccountCreated(address: String, uuid: UInt64)
448
449    /// Interface for types that have an associated EVM address
450    access(all) resource interface Addressable {
451        /// Gets the EVM address
452        access(all)
453        view fun address(): EVMAddress
454    }
455
456    access(all) resource CadenceOwnedAccount: Addressable {
457
458        access(self) var addressBytes: [UInt8; 20]
459
460        init() {
461            // address is initially set to zero
462            // but updated through initAddress later
463            // we have to do this since we need resource id (uuid)
464            // to calculate the EVM address for this cadence owned account
465            self.addressBytes = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
466        }
467
468        /// Sets the EVM address for the COA. Only callable once on initial creation.
469        ///
470        /// @param addressBytes: The 20 byte EVM address
471        ///
472        /// @return the token decimals of the ERC20
473        access(contract)
474        fun initAddress(addressBytes: [UInt8; 20]) {
475            // only allow set address for the first time
476            // check address is empty
477            pre {
478                self.addressBytes == [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]:
479                    "EVM.CadenceOwnedAccount.initAddress(): Cannot initialize the address bytes if it has already been set!"
480            }
481           self.addressBytes = addressBytes
482        }
483
484        /// Gets The EVM address of the cadence owned account
485        ///
486        access(all)
487        view fun address(): EVMAddress {
488            // Always create a new EVMAddress instance
489            return EVMAddress(bytes: self.addressBytes)
490        }
491
492        /// Gets the balance of the cadence owned account
493        ///
494        access(all)
495        view fun balance(): Balance {
496            return self.address().balance()
497        }
498
499        /// Deposits the given vault into the cadence owned account's balance
500        ///
501        /// @param from: The FlowToken Vault to deposit to this cadence owned account
502        ///
503        /// @return the token decimals of the ERC20
504        access(all)
505        fun deposit(from: @FlowToken.Vault) {
506            self.address().deposit(from: <-from)
507        }
508
509        /// Gets the EVM address of the cadence owned account behind an entitlement,
510        /// acting as proof of access
511        access(Owner | Validate)
512        view fun protectedAddress(): EVMAddress {
513            return self.address()
514        }
515
516        /// Withdraws the balance from the cadence owned account's balance.
517        /// Note that amounts smaller than 1e10 attoFlow can't be withdrawn,
518        /// given that Flow Token Vaults use UFix64 to store balances.
519        /// In other words, the smallest withdrawable amount is 1e10 attoFlow.
520        /// Amounts smaller than 1e10 attoFlow, will cause the function to panic
521        /// with: "withdraw failed! smallest unit allowed to transfer is 1e10 attoFlow".
522        /// If the given balance conversion to UFix64 results in rounding loss,
523        /// the withdrawal amount will be truncated to the maximum precision for UFix64.
524        ///
525        /// @param balance: The EVM balance to withdraw
526        ///
527        /// @return A FlowToken Vault with the requested balance
528        access(Owner | Withdraw)
529        fun withdraw(balance: Balance): @FlowToken.Vault {
530            if balance.isZero() {
531                return <-FlowToken.createEmptyVault(vaultType: Type<@FlowToken.Vault>())
532            }
533            let vault <- InternalEVM.withdraw(
534                from: self.addressBytes,
535                amount: balance.attoflow
536            ) as! @FlowToken.Vault
537            emit FLOWTokensWithdrawn(
538                address: self.address().toString(),
539                amount: balance.inFLOW(),
540                withdrawnUUID: vault.uuid,
541                balanceAfterInAttoFlow: self.balance().attoflow
542            )
543            return <-vault
544        }
545
546        /// Deploys a contract to the EVM environment.
547        /// Returns the result which contains address of
548        /// the newly deployed contract
549        ///
550        /// @param code: The bytecode of the Solidity contract
551        /// @param gasLimit: The EVM Gas limit for the deployment transaction
552        /// @param value: The value, as an EVM.Balance object, to send with the deployment
553        ///
554        /// @return The EVM transaction result
555        access(Owner | Deploy)
556        fun deploy(
557            code: [UInt8],
558            gasLimit: UInt64,
559            value: Balance
560        ): Result {
561            return InternalEVM.deploy(
562                from: self.addressBytes,
563                code: code,
564                gasLimit: gasLimit,
565                value: value.attoflow
566            ) as! Result
567        }
568
569        /// Calls a function with the given data.
570        /// The execution is limited by the given amount of gas
571        access(Owner | Call)
572        fun call(
573            to: EVMAddress,
574            data: [UInt8],
575            gasLimit: UInt64,
576            value: Balance
577        ): Result {
578            return InternalEVM.call(
579                from: self.addressBytes,
580                to: to.bytes,
581                data: data,
582                gasLimit: gasLimit,
583                value: value.attoflow
584            ) as! Result
585        }
586
587        /// Calls a contract function with the given data.
588        /// The execution is limited by the given amount of gas.
589        /// The transaction state changes are not persisted.
590        access(all)
591        fun dryCall(
592            to: EVMAddress,
593            data: [UInt8],
594            gasLimit: UInt64,
595            value: Balance,
596        ): Result {
597            return InternalEVM.dryCall(
598                from: self.addressBytes,
599                to: to.bytes,
600                data: data,
601                gasLimit: gasLimit,
602                value: value.attoflow
603            ) as! Result
604        }
605
606        /// Bridges the given NFT to the EVM environment, requiring a Provider
607        /// from which to withdraw a fee to fulfill the bridge request
608        ///
609        /// @param nft: The NFT to bridge to the COA's address in Flow EVM
610        /// @param feeProvider: A Withdraw entitled Provider reference to a FlowToken Vault
611        ///                     that contains the fees to be taken to pay for bridging
612        access(all)
613        fun depositNFT(
614            nft: @{NonFungibleToken.NFT},
615            feeProvider: auth(FungibleToken.Withdraw) &{FungibleToken.Provider}
616        ) {
617            EVM.borrowBridgeAccessor().depositNFT(nft: <-nft, to: self.address(), feeProvider: feeProvider)
618        }
619
620        /// Bridges the given NFT from the EVM environment, requiring a Provider
621        /// from which to withdraw a fee to fulfill the bridge request.
622        /// Note: the caller has to own the requested NFT in EVM
623        ///
624        /// @param type: The Cadence type of the NFT to withdraw
625        /// @param id: The EVM ERC721 ID of the NFT to withdraw
626        /// @param feeProvider: A Withdraw entitled Provider reference to a FlowToken Vault
627        ///                     that contains the fees to be taken to pay for bridging
628        ///
629        /// @return The requested NFT
630        access(Owner | Bridge)
631        fun withdrawNFT(
632            type: Type,
633            id: UInt256,
634            feeProvider: auth(FungibleToken.Withdraw) &{FungibleToken.Provider}
635        ): @{NonFungibleToken.NFT} {
636            return <- EVM.borrowBridgeAccessor().withdrawNFT(
637                caller: &self as auth(Call) &CadenceOwnedAccount,
638                type: type,
639                id: id,
640                feeProvider: feeProvider
641            )
642        }
643
644        /// Bridges the given Vault to the EVM environment
645        access(all)
646        fun depositTokens(
647            vault: @{FungibleToken.Vault},
648            feeProvider: auth(FungibleToken.Withdraw) &{FungibleToken.Provider}
649        ) {
650            EVM.borrowBridgeAccessor().depositTokens(vault: <-vault, to: self.address(), feeProvider: feeProvider)
651        }
652
653        /// Bridges the given fungible tokens from the EVM environment, requiring a Provider from which to withdraw a
654        /// fee to fulfill the bridge request. Note: the caller should own the requested tokens & sufficient balance of
655        /// requested tokens in EVM
656        access(Owner | Bridge)
657        fun withdrawTokens(
658            type: Type,
659            amount: UInt256,
660            feeProvider: auth(FungibleToken.Withdraw) &{FungibleToken.Provider}
661        ): @{FungibleToken.Vault} {
662            return <- EVM.borrowBridgeAccessor().withdrawTokens(
663                caller: &self as auth(Call) &CadenceOwnedAccount,
664                type: type,
665                amount: amount,
666                feeProvider: feeProvider
667            )
668        }
669    }
670
671    /// Creates a new cadence owned account
672    access(all)
673    fun createCadenceOwnedAccount(): @CadenceOwnedAccount {
674        let acc <-create CadenceOwnedAccount()
675        let addr = InternalEVM.createCadenceOwnedAccount(uuid: acc.uuid)
676        acc.initAddress(addressBytes: addr)
677
678        emit CadenceOwnedAccountCreated(address: acc.address().toString(), uuid: acc.uuid)
679        return <-acc
680    }
681
682    /// Runs an a RLP-encoded EVM transaction, deducts the gas fees,
683    /// and deposits the gas fees into the provided coinbase address.
684    ///
685    /// @param tx: The rlp-encoded transaction to run
686    /// @param coinbase: The address of entity to receive the transaction fees
687    /// for relaying the transaction
688    ///
689    /// @return: The transaction result
690    access(all)
691    fun run(tx: [UInt8], coinbase: EVMAddress): Result {
692        return InternalEVM.run(
693            tx: tx,
694            coinbase: coinbase.bytes
695        ) as! Result
696    }
697
698    /// mustRun runs the transaction using EVM.run
699    /// It will rollback if the tx execution status is unknown or invalid.
700    /// Note that this method does not rollback if transaction
701    /// is executed but an vm error is reported as the outcome
702    /// of the execution (status: failed).
703    access(all)
704    fun mustRun(tx: [UInt8], coinbase: EVMAddress): Result {
705        let runResult = self.run(tx: tx, coinbase: coinbase)
706        assert(
707            runResult.status == Status.failed || runResult.status == Status.successful,
708            message: "EVM.mustRun(): The provided transaction is not valid for execution"
709        )
710        return runResult
711    }
712
713    /// Simulates running unsigned RLP-encoded transaction using
714    /// the from address as the signer.
715    /// The transaction state changes are not persisted.
716    /// This is useful for gas estimation or calling view contract functions.
717    access(all)
718    fun dryRun(tx: [UInt8], from: EVMAddress): Result {
719        return InternalEVM.dryRun(
720            tx: tx,
721            from: from.bytes,
722        ) as! Result
723    }
724
725    /// Calls a contract function with the given data.
726    /// The execution is limited by the given amount of gas.
727    /// The transaction state changes are not persisted.
728    access(all)
729    fun dryCall(
730        from: EVMAddress,
731        to: EVMAddress,
732        data: [UInt8],
733        gasLimit: UInt64,
734        value: Balance,
735    ): Result {
736        return InternalEVM.dryCall(
737            from: from.bytes,
738            to: to.bytes,
739            data: data,
740            gasLimit: gasLimit,
741            value: value.attoflow
742        ) as! Result
743    }
744
745    /// Runs a batch of RLP-encoded EVM transactions, deducts the gas fees,
746    /// and deposits the gas fees into the provided coinbase address.
747    /// An invalid transaction is not executed and not included in the block.
748    access(all)
749    fun batchRun(txs: [[UInt8]], coinbase: EVMAddress): [Result] {
750        return InternalEVM.batchRun(
751            txs: txs,
752            coinbase: coinbase.bytes,
753        ) as! [Result]
754    }
755
756    access(all)
757    fun encodeABI(_ values: [AnyStruct]): [UInt8] {
758        return InternalEVM.encodeABI(values)
759    }
760
761    access(all)
762    fun decodeABI(types: [Type], data: [UInt8]): [AnyStruct] {
763        return InternalEVM.decodeABI(types: types, data: data)
764    }
765
766    access(all)
767    fun encodeABIWithSignature(
768        _ signature: String,
769        _ values: [AnyStruct]
770    ): [UInt8] {
771        let methodID = HashAlgorithm.KECCAK_256.hash(
772            signature.utf8
773        ).slice(from: 0, upTo: 4)
774        let arguments = InternalEVM.encodeABI(values)
775
776        return methodID.concat(arguments)
777    }
778
779    access(all)
780    fun decodeABIWithSignature(
781        _ signature: String,
782        types: [Type],
783        data: [UInt8]
784    ): [AnyStruct] {
785        let methodID = HashAlgorithm.KECCAK_256.hash(
786            signature.utf8
787        ).slice(from: 0, upTo: 4)
788
789        for byte in methodID {
790            if byte != data.removeFirst() {
791                panic("EVM.decodeABIWithSignature(): Cannot decode! The signature does not match the provided data.")
792            }
793        }
794
795        return InternalEVM.decodeABI(types: types, data: data)
796    }
797
798    /// ValidationResult returns the result of COA ownership proof validation
799    access(all) struct ValidationResult {
800
801        access(all) let isValid: Bool
802
803        /// If there was a problem with validation, this describes
804        /// what the problem was
805        access(all) let problem: String?
806
807        init(isValid: Bool, problem: String?) {
808            self.isValid = isValid
809            self.problem = problem
810        }
811    }
812
813    /// validateCOAOwnershipProof validates a COA ownership proof
814    access(all)
815    fun validateCOAOwnershipProof(
816        address: Address,
817        path: PublicPath,
818        signedData: [UInt8],
819        keyIndices: [UInt64],
820        signatures: [[UInt8]],
821        evmAddress: [UInt8; 20]
822    ): ValidationResult {
823        // make signature set first
824        // check number of signatures matches number of key indices
825        if keyIndices.length != signatures.length {
826            return ValidationResult(
827                isValid: false,
828                problem: "EVM.validateCOAOwnershipProof(): Key indices array length"
829                         .concat(" doesn't match the signatures array length!")
830            )
831        }
832
833        // fetch account
834        let acc = getAccount(address)
835
836        var signatureSet: [Crypto.KeyListSignature] = []
837        let keyList = Crypto.KeyList()
838        var keyListLength = 0
839        let seenAccountKeyIndices: {Int: Int} = {}
840        for signatureIndex, signature in signatures {
841            // index of the key on the account
842            let accountKeyIndex = Int(keyIndices[signatureIndex]!)
843            // index of the key in the key list
844            var keyListIndex = 0
845
846            if !seenAccountKeyIndices.containsKey(accountKeyIndex) {
847                // fetch account key with accountKeyIndex
848                if let key = acc.keys.get(keyIndex: accountKeyIndex) {
849                    if key.isRevoked {
850                        return ValidationResult(
851                            isValid: false,
852                            problem: "EVM.validateCOAOwnershipProof(): Cannot validate COA ownership"
853                                     .concat(" for Cadence account \(address). The account key at index \(accountKeyIndex) is revoked.")
854                        )
855                    }
856
857                    keyList.add(
858                      key.publicKey,
859                      hashAlgorithm: key.hashAlgorithm,
860                      // normalization factor. We need to divide by 1000 because the
861                      // `Crypto.KeyList.verify()` function expects the weight to be
862                      // in the range [0, 1]. 1000 is the key weight threshold.
863                      weight: key.weight / 1000.0,
864                   )
865
866                   keyListIndex = keyListLength
867                   keyListLength = keyListLength + 1
868                   seenAccountKeyIndices[accountKeyIndex] = keyListIndex
869                } else {
870                    return ValidationResult(
871                        isValid: false,
872                        problem: "EVM.validateCOAOwnershipProof(): Cannot validate COA ownership"
873                                     .concat(" for Cadence account \(address). The key index \(accountKeyIndex) is invalid.")
874                    )
875                }
876            } else {
877               // if we have already seen this accountKeyIndex, use the keyListIndex
878               // that was previously assigned to it
879               // `Crypto.KeyList.verify()` knows how to handle duplicate keys
880               keyListIndex = seenAccountKeyIndices[accountKeyIndex]!
881            }
882
883            signatureSet.append(Crypto.KeyListSignature(
884               keyIndex: keyListIndex,
885               signature: signature
886            ))
887        }
888
889        let isValid = keyList.verify(
890            signatureSet: signatureSet,
891            signedData: signedData,
892            domainSeparationTag: "FLOW-V0.0-user"
893        )
894
895        if !isValid{
896            return ValidationResult(
897                isValid: false,
898                problem: "EVM.validateCOAOwnershipProof(): Cannot validate COA ownership"
899                         .concat(" for Cadence account \(address). The given signatures are not valid or provide enough weight.")
900            )
901        }
902
903        let coaRef = acc.capabilities.borrow<&EVM.CadenceOwnedAccount>(path)
904        if coaRef == nil {
905             return ValidationResult(
906                 isValid: false,
907                 problem: "EVM.validateCOAOwnershipProof(): Cannot validate COA ownership. "
908                          .concat("Could not borrow the COA resource for account \(address).")
909             )
910        }
911
912        // verify evm address matching
913        var addr = coaRef!.address()
914        for index, item in coaRef!.address().bytes {
915            if item != evmAddress[index] {
916                return ValidationResult(
917                    isValid: false,
918                    problem: "EVM.validateCOAOwnershipProof(): Cannot validate COA ownership."
919                             .concat("The provided evm address does not match the account's COA address.")
920                )
921            }
922        }
923
924        return ValidationResult(
925            isValid: true,
926            problem: nil
927        )
928    }
929
930    /// Interface for a resource which acts as an entrypoint to the VM bridge
931    access(all) resource interface BridgeAccessor {
932
933        /// Endpoint enabling the bridging of an NFT to EVM
934        access(Bridge)
935        fun depositNFT(
936            nft: @{NonFungibleToken.NFT},
937            to: EVMAddress,
938            feeProvider: auth(FungibleToken.Withdraw) &{FungibleToken.Provider}
939        )
940
941        /// Endpoint enabling the bridging of an NFT from EVM
942        access(Bridge)
943        fun withdrawNFT(
944            caller: auth(Call) &CadenceOwnedAccount,
945            type: Type,
946            id: UInt256,
947            feeProvider: auth(FungibleToken.Withdraw) &{FungibleToken.Provider}
948        ): @{NonFungibleToken.NFT}
949
950        /// Endpoint enabling the bridging of a fungible token vault to EVM
951        access(Bridge)
952        fun depositTokens(
953            vault: @{FungibleToken.Vault},
954            to: EVMAddress,
955            feeProvider: auth(FungibleToken.Withdraw) &{FungibleToken.Provider}
956        )
957
958        /// Endpoint enabling the bridging of fungible tokens from EVM
959        access(Bridge)
960        fun withdrawTokens(
961            caller: auth(Call) &CadenceOwnedAccount,
962            type: Type,
963            amount: UInt256,
964            feeProvider: auth(FungibleToken.Withdraw) &{FungibleToken.Provider}
965        ): @{FungibleToken.Vault}
966    }
967
968    /// Interface which captures a Capability to the bridge Accessor,
969    /// saving it within the BridgeRouter resource
970    access(all) resource interface BridgeRouter {
971
972        /// Returns a reference to the BridgeAccessor designated
973        /// for internal bridge requests
974        access(Bridge) view fun borrowBridgeAccessor(): auth(Bridge) &{BridgeAccessor}
975
976        /// Sets the BridgeAccessor Capability in the BridgeRouter
977        access(Bridge) fun setBridgeAccessor(_ accessor: Capability<auth(Bridge) &{BridgeAccessor}>) {
978            pre {
979                accessor.check(): 
980                    "EVM.setBridgeAccessor(): Invalid BridgeAccessor Capability provided"
981                emit BridgeAccessorUpdated(
982                    routerType: self.getType(),
983                    routerUUID: self.uuid,
984                    routerAddress: self.owner?.address ?? panic("EVM.setBridgeAccessor(): Router must be stored in an account's storage"),
985                    accessorType: accessor.borrow()!.getType(),
986                    accessorUUID: accessor.borrow()!.uuid,
987                    accessorAddress: accessor.address
988                )
989            }
990        }
991    }
992
993    /// Returns a reference to the BridgeAccessor designated for internal bridge requests
994    access(self)
995    view fun borrowBridgeAccessor(): auth(Bridge) &{BridgeAccessor} {
996        return self.account.storage.borrow<auth(Bridge) &{BridgeRouter}>(from: /storage/evmBridgeRouter)
997            ?.borrowBridgeAccessor()
998            ?? panic("EVM.borrowBridgeAccessor(): Could not borrow a reference to the EVM bridge.")
999    }
1000
1001    /// The Heartbeat resource controls the block production.
1002    /// It is stored in the storage and used in the Flow protocol
1003    /// to call the heartbeat function once per block.
1004    access(all) resource Heartbeat {
1005        /// heartbeat calls commit block proposals and forms new blocks
1006        /// including all the recently executed transactions.
1007        /// The Flow protocol makes sure to call this function
1008        /// once per block as a system call.
1009        access(all)
1010        fun heartbeat() {
1011            InternalEVM.commitBlockProposal()
1012        }
1013    }
1014
1015    /// setupHeartbeat creates a heartbeat resource and saves it to storage.
1016    /// The function is called once during the contract initialization.
1017    ///
1018    /// The heartbeat resource is used to control the block production,
1019    /// and used in the Flow protocol to call the heartbeat function once per block.
1020    ///
1021    /// The function can be called by anyone, but only once:
1022    /// the function will fail if the resource already exists.
1023    ///
1024    /// The resulting resource is stored in the account storage,
1025    /// and is only accessible by the account, not the caller of the function.
1026    access(all)
1027    fun setupHeartbeat() {
1028        self.account.storage.save(<-create Heartbeat(), to: /storage/EVMHeartbeat)
1029    }
1030
1031    init() {
1032        self.setupHeartbeat()
1033    }
1034}