Smart Contract

EVM

A.8c5303eaa26202d6.EVM

Deployed

2h ago
Feb 26, 2026, 07:30:11 PM UTC

Dependents

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