Smart Contract
EVM
A.8c5303eaa26202d6.EVM
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