Smart Contract
EVM
A.e467b9dd11fa00df.EVM
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}