github.com/onflow/flow-go@v0.35.7-crescendo-preview.23-atree-inlining/fvm/evm/stdlib/contract.cdc (about) 1 import Crypto 2 import "NonFungibleToken" 3 import "FungibleToken" 4 import "FlowToken" 5 6 access(all) 7 contract EVM { 8 9 // Entitlements enabling finer-graned 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: String, 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: String, 33 // hash of all the transaction receipts 34 receiptRoot: String, 35 // all the transactions included in the block 36 transactionHashes: [String] 37 ) 38 39 /// Transaction executed event is emitted everytime a transaction 40 /// is executed by the EVM (even if failed). 41 access(all) 42 event TransactionExecuted( 43 // hash of the transaction 44 hash: String, 45 // index of the transaction in a block 46 index: UInt16, 47 // type of the transaction 48 type: UInt8, 49 // RLP and hex encoded transaction payload 50 payload: String, 51 // code indicating a specific validation (201-300) or execution (301-400) error 52 errorCode: UInt16, 53 // the amount of gas transaction used 54 gasConsumed: UInt64, 55 // if transaction was a deployment contains a newly deployed contract address 56 contractAddress: String, 57 // RLP and hex encoded logs 58 logs: String, 59 // block height in which transaction was inclued 60 blockHeight: UInt64, 61 // block hash in which transaction was included 62 blockHash: String 63 ) 64 65 access(all) 66 event CadenceOwnedAccountCreated(address: String) 67 68 /// FLOWTokensDeposited is emitted when FLOW tokens is bridged 69 /// into the EVM environment. Note that this event is not emitted 70 /// for transfer of flow tokens between two EVM addresses. 71 access(all) 72 event FLOWTokensDeposited(address: String, amount: UFix64) 73 74 /// FLOWTokensWithdrawn is emitted when FLOW tokens are bridged 75 /// out of the EVM environment. Note that this event is not emitted 76 /// for transfer of flow tokens between two EVM addresses. 77 access(all) 78 event FLOWTokensWithdrawn(address: String, amount: UFix64) 79 80 /// BridgeAccessorUpdated is emitted when the BridgeAccessor Capability 81 /// is updated in the stored BridgeRouter along with identifying 82 /// information about both. 83 access(all) 84 event BridgeAccessorUpdated( 85 routerType: Type, 86 routerUUID: UInt64, 87 routerAddress: Address, 88 accessorType: Type, 89 accessorUUID: UInt64, 90 accessorAddress: Address 91 ) 92 93 /// EVMAddress is an EVM-compatible address 94 access(all) 95 struct EVMAddress { 96 97 /// Bytes of the address 98 access(all) 99 let bytes: [UInt8; 20] 100 101 /// Constructs a new EVM address from the given byte representation 102 view init(bytes: [UInt8; 20]) { 103 self.bytes = bytes 104 } 105 106 /// Balance of the address 107 access(all) 108 view fun balance(): Balance { 109 let balance = InternalEVM.balance( 110 address: self.bytes 111 ) 112 return Balance(attoflow: balance) 113 } 114 115 /// Nonce of the address 116 access(all) 117 fun nonce(): UInt64 { 118 return InternalEVM.nonce( 119 address: self.bytes 120 ) 121 } 122 123 /// Code of the address 124 access(all) 125 fun code(): [UInt8] { 126 return InternalEVM.code( 127 address: self.bytes 128 ) 129 } 130 131 /// CodeHash of the address 132 access(all) 133 fun codeHash(): [UInt8] { 134 return InternalEVM.codeHash( 135 address: self.bytes 136 ) 137 } 138 139 /// Deposits the given vault into the EVM account with the given address 140 access(all) 141 fun deposit(from: @FlowToken.Vault) { 142 let amount = from.balance 143 if amount == 0.0 { 144 panic("calling deposit function with an empty vault is not allowed") 145 } 146 InternalEVM.deposit( 147 from: <-from, 148 to: self.bytes 149 ) 150 emit FLOWTokensDeposited(address: self.toString(), amount: amount) 151 } 152 153 /// Serializes the address to a hex string without the 0x prefix 154 /// Future implementations should pass data to InternalEVM for native serialization 155 access(all) 156 view fun toString(): String { 157 return String.encodeHex(self.bytes.toVariableSized()) 158 } 159 160 /// Compares the address with another address 161 access(all) 162 view fun equals(_ other: EVMAddress): Bool { 163 return self.bytes == other.bytes 164 } 165 } 166 167 /// Converts a hex string to an EVM address if the string is a valid hex string 168 /// Future implementations should pass data to InternalEVM for native deserialization 169 access(all) 170 fun addressFromString(_ asHex: String): EVMAddress { 171 pre { 172 asHex.length == 40 || asHex.length == 42: "Invalid hex string length for an EVM address" 173 } 174 // Strip the 0x prefix if it exists 175 var withoutPrefix = (asHex[1] == "x" ? asHex.slice(from: 2, upTo: asHex.length) : asHex).toLower() 176 let bytes = withoutPrefix.decodeHex().toConstantSized<[UInt8;20]>()! 177 return EVMAddress(bytes: bytes) 178 } 179 180 access(all) 181 struct Balance { 182 183 /// The balance in atto-FLOW 184 /// Atto-FLOW is the smallest denomination of FLOW (1e18 FLOW) 185 /// that is used to store account balances inside EVM 186 /// similar to the way WEI is used to store ETH divisible to 18 decimal places. 187 access(all) 188 var attoflow: UInt 189 190 /// Constructs a new balance 191 access(all) 192 view init(attoflow: UInt) { 193 self.attoflow = attoflow 194 } 195 196 /// Sets the balance by a UFix64 (8 decimal points), the format 197 /// that is used in Cadence to store FLOW tokens. 198 access(all) 199 fun setFLOW(flow: UFix64){ 200 self.attoflow = InternalEVM.castToAttoFLOW(balance: flow) 201 } 202 203 /// Casts the balance to a UFix64 (rounding down) 204 /// Warning! casting a balance to a UFix64 which supports a lower level of precision 205 /// (8 decimal points in compare to 18) might result in rounding down error. 206 /// Use the toAttoFlow function if you care need more accuracy. 207 access(all) 208 view fun inFLOW(): UFix64 { 209 return InternalEVM.castToFLOW(balance: self.attoflow) 210 } 211 212 /// Returns the balance in Atto-FLOW 213 access(all) 214 view fun inAttoFLOW(): UInt { 215 return self.attoflow 216 } 217 218 /// Returns true if the balance is zero 219 access(all) 220 fun isZero(): Bool { 221 return self.attoflow == 0 222 } 223 } 224 225 /// reports the status of evm execution. 226 access(all) enum Status: UInt8 { 227 /// is (rarely) returned when status is unknown 228 /// and something has gone very wrong. 229 access(all) case unknown 230 231 /// is returned when execution of an evm transaction/call 232 /// has failed at the validation step (e.g. nonce mismatch). 233 /// An invalid transaction/call is rejected to be executed 234 /// or be included in a block. 235 access(all) case invalid 236 237 /// is returned when execution of an evm transaction/call 238 /// has been successful but the vm has reported an error as 239 /// the outcome of execution (e.g. running out of gas). 240 /// A failed tx/call is included in a block. 241 /// Note that resubmission of a failed transaction would 242 /// result in invalid status in the second attempt, given 243 /// the nonce would be come invalid. 244 access(all) case failed 245 246 /// is returned when execution of an evm transaction/call 247 /// has been successful and no error is reported by the vm. 248 access(all) case successful 249 } 250 251 /// reports the outcome of evm transaction/call execution attempt 252 access(all) struct Result { 253 /// status of the execution 254 access(all) 255 let status: Status 256 257 /// error code (error code zero means no error) 258 access(all) 259 let errorCode: UInt64 260 261 /// returns the amount of gas metered during 262 /// evm execution 263 access(all) 264 let gasUsed: UInt64 265 266 /// returns the data that is returned from 267 /// the evm for the call. For coa.deploy 268 /// calls it returns the code deployed to 269 /// the address provided in the contractAddress field. 270 access(all) 271 let data: [UInt8] 272 273 /// returns the newly deployed contract address 274 /// if the transaction caused such a deployment 275 /// otherwise the value is nil. 276 access(all) 277 let deployedContract: EVMAddress? 278 279 init( 280 status: Status, 281 errorCode: UInt64, 282 gasUsed: UInt64, 283 data: [UInt8], 284 contractAddress: [UInt8; 20]? 285 ) { 286 self.status = status 287 self.errorCode = errorCode 288 self.gasUsed = gasUsed 289 self.data = data 290 291 if let addressBytes = contractAddress { 292 self.deployedContract = EVMAddress(bytes: addressBytes) 293 } else { 294 self.deployedContract = nil 295 } 296 } 297 } 298 299 access(all) 300 resource interface Addressable { 301 /// The EVM address 302 access(all) 303 view fun address(): EVMAddress 304 } 305 306 access(all) 307 resource CadenceOwnedAccount: Addressable { 308 309 access(self) 310 var addressBytes: [UInt8; 20] 311 312 init() { 313 // address is initially set to zero 314 // but updated through initAddress later 315 // we have to do this since we need resource id (uuid) 316 // to calculate the EVM address for this cadence owned account 317 self.addressBytes = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] 318 } 319 320 access(contract) 321 fun initAddress(addressBytes: [UInt8; 20]) { 322 // only allow set address for the first time 323 // check address is empty 324 for item in self.addressBytes { 325 assert(item == 0, message: "address byte is not empty") 326 } 327 self.addressBytes = addressBytes 328 } 329 330 /// The EVM address of the cadence owned account 331 access(all) 332 view fun address(): EVMAddress { 333 // Always create a new EVMAddress instance 334 return EVMAddress(bytes: self.addressBytes) 335 } 336 337 /// Get balance of the cadence owned account 338 access(all) 339 view fun balance(): Balance { 340 return self.address().balance() 341 } 342 343 /// Deposits the given vault into the cadence owned account's balance 344 access(all) 345 fun deposit(from: @FlowToken.Vault) { 346 self.address().deposit(from: <-from) 347 } 348 349 /// The EVM address of the cadence owned account behind an entitlement, acting as proof of access 350 access(Owner | Validate) 351 view fun protectedAddress(): EVMAddress { 352 return self.address() 353 } 354 355 /// Withdraws the balance from the cadence owned account's balance 356 /// Note that amounts smaller than 10nF (10e-8) can't be withdrawn 357 /// given that Flow Token Vaults use UFix64s to store balances. 358 /// If the given balance conversion to UFix64 results in 359 /// rounding error, this function would fail. 360 access(Owner | Withdraw) 361 fun withdraw(balance: Balance): @FlowToken.Vault { 362 if balance.isZero() { 363 panic("calling withdraw function with zero balance is not allowed") 364 } 365 let vault <- InternalEVM.withdraw( 366 from: self.addressBytes, 367 amount: balance.attoflow 368 ) as! @FlowToken.Vault 369 emit FLOWTokensWithdrawn(address: self.address().toString(), amount: balance.inFLOW()) 370 return <-vault 371 } 372 373 /// Deploys a contract to the EVM environment. 374 /// Returns the result which contains address of 375 /// the newly deployed contract 376 access(Owner | Deploy) 377 fun deploy( 378 code: [UInt8], 379 gasLimit: UInt64, 380 value: Balance 381 ): Result { 382 return InternalEVM.deploy( 383 from: self.addressBytes, 384 code: code, 385 gasLimit: gasLimit, 386 value: value.attoflow 387 ) as! Result 388 } 389 390 /// Calls a function with the given data. 391 /// The execution is limited by the given amount of gas 392 access(Owner | Call) 393 fun call( 394 to: EVMAddress, 395 data: [UInt8], 396 gasLimit: UInt64, 397 value: Balance 398 ): Result { 399 return InternalEVM.call( 400 from: self.addressBytes, 401 to: to.bytes, 402 data: data, 403 gasLimit: gasLimit, 404 value: value.attoflow 405 ) as! Result 406 } 407 408 /// Bridges the given NFT to the EVM environment, requiring a Provider from which to withdraw a fee to fulfill 409 /// the bridge request 410 access(all) 411 fun depositNFT( 412 nft: @{NonFungibleToken.NFT}, 413 feeProvider: auth(FungibleToken.Withdraw) &{FungibleToken.Provider} 414 ) { 415 EVM.borrowBridgeAccessor().depositNFT(nft: <-nft, to: self.address(), feeProvider: feeProvider) 416 } 417 418 /// Bridges the given NFT from the EVM environment, requiring a Provider from which to withdraw a fee to fulfill 419 /// the bridge request. Note: the caller should own the requested NFT in EVM 420 access(Owner | Bridge) 421 fun withdrawNFT( 422 type: Type, 423 id: UInt256, 424 feeProvider: auth(FungibleToken.Withdraw) &{FungibleToken.Provider} 425 ): @{NonFungibleToken.NFT} { 426 return <- EVM.borrowBridgeAccessor().withdrawNFT( 427 caller: &self as auth(Call) &CadenceOwnedAccount, 428 type: type, 429 id: id, 430 feeProvider: feeProvider 431 ) 432 } 433 434 /// Bridges the given Vault to the EVM environment, requiring a Provider from which to withdraw a fee to fulfill 435 /// the bridge request 436 access(all) 437 fun depositTokens( 438 vault: @{FungibleToken.Vault}, 439 feeProvider: auth(FungibleToken.Withdraw) &{FungibleToken.Provider} 440 ) { 441 EVM.borrowBridgeAccessor().depositTokens(vault: <-vault, to: self.address(), feeProvider: feeProvider) 442 } 443 444 /// Bridges the given fungible tokens from the EVM environment, requiring a Provider from which to withdraw a 445 /// fee to fulfill the bridge request. Note: the caller should own the requested tokens & sufficient balance of 446 /// requested tokens in EVM 447 access(Owner | Bridge) 448 fun withdrawTokens( 449 type: Type, 450 amount: UInt256, 451 feeProvider: auth(FungibleToken.Withdraw) &{FungibleToken.Provider} 452 ): @{FungibleToken.Vault} { 453 return <- EVM.borrowBridgeAccessor().withdrawTokens( 454 caller: &self as auth(Call) &CadenceOwnedAccount, 455 type: type, 456 amount: amount, 457 feeProvider: feeProvider 458 ) 459 } 460 } 461 462 /// Creates a new cadence owned account 463 access(all) 464 fun createCadenceOwnedAccount(): @CadenceOwnedAccount { 465 let acc <-create CadenceOwnedAccount() 466 let addr = InternalEVM.createCadenceOwnedAccount(uuid: acc.uuid) 467 acc.initAddress(addressBytes: addr) 468 469 emit CadenceOwnedAccountCreated(address: acc.address().toString()) 470 return <-acc 471 } 472 473 /// Runs an a RLP-encoded EVM transaction, deducts the gas fees, 474 /// and deposits the gas fees into the provided coinbase address. 475 access(all) 476 fun run(tx: [UInt8], coinbase: EVMAddress): Result { 477 return InternalEVM.run( 478 tx: tx, 479 coinbase: coinbase.bytes 480 ) as! Result 481 } 482 483 /// mustRun runs the transaction using EVM.run yet it 484 /// rollback if the tx execution status is unknown or invalid. 485 /// Note that this method does not rollback if transaction 486 /// is executed but an vm error is reported as the outcome 487 /// of the execution (status: failed). 488 access(all) 489 fun mustRun(tx: [UInt8], coinbase: EVMAddress): Result { 490 let runResult = self.run(tx: tx, coinbase: coinbase) 491 assert( 492 runResult.status == Status.failed || runResult.status == Status.successful, 493 message: "tx is not valid for execution" 494 ) 495 return runResult 496 } 497 498 /// Simulates running unsigned RLP-encoded transaction using 499 /// the from address as the signer. 500 /// The transaction state changes are not persisted. 501 /// This is useful for gas estimation or calling view contract functions. 502 access(all) 503 fun dryRun(tx: [UInt8], from: EVMAddress): Result { 504 return InternalEVM.dryRun( 505 tx: tx, 506 from: from.bytes, 507 ) as! Result 508 } 509 510 /// Runs a batch of RLP-encoded EVM transactions, deducts the gas fees, 511 /// and deposits the gas fees into the provided coinbase address. 512 /// An invalid transaction is not executed and not included in the block. 513 access(all) 514 fun batchRun(txs: [[UInt8]], coinbase: EVMAddress): [Result] { 515 return InternalEVM.batchRun( 516 txs: txs, 517 coinbase: coinbase.bytes, 518 ) as! [Result] 519 } 520 521 access(all) 522 fun encodeABI(_ values: [AnyStruct]): [UInt8] { 523 return InternalEVM.encodeABI(values) 524 } 525 526 access(all) 527 fun decodeABI(types: [Type], data: [UInt8]): [AnyStruct] { 528 return InternalEVM.decodeABI(types: types, data: data) 529 } 530 531 access(all) 532 fun encodeABIWithSignature( 533 _ signature: String, 534 _ values: [AnyStruct] 535 ): [UInt8] { 536 let methodID = HashAlgorithm.KECCAK_256.hash( 537 signature.utf8 538 ).slice(from: 0, upTo: 4) 539 let arguments = InternalEVM.encodeABI(values) 540 541 return methodID.concat(arguments) 542 } 543 544 access(all) 545 fun decodeABIWithSignature( 546 _ signature: String, 547 types: [Type], 548 data: [UInt8] 549 ): [AnyStruct] { 550 let methodID = HashAlgorithm.KECCAK_256.hash( 551 signature.utf8 552 ).slice(from: 0, upTo: 4) 553 554 for byte in methodID { 555 if byte != data.removeFirst() { 556 panic("signature mismatch") 557 } 558 } 559 560 return InternalEVM.decodeABI(types: types, data: data) 561 } 562 563 /// ValidationResult returns the result of COA ownership proof validation 564 access(all) 565 struct ValidationResult { 566 access(all) 567 let isValid: Bool 568 569 access(all) 570 let problem: String? 571 572 init(isValid: Bool, problem: String?) { 573 self.isValid = isValid 574 self.problem = problem 575 } 576 } 577 578 /// validateCOAOwnershipProof validates a COA ownership proof 579 access(all) 580 fun validateCOAOwnershipProof( 581 address: Address, 582 path: PublicPath, 583 signedData: [UInt8], 584 keyIndices: [UInt64], 585 signatures: [[UInt8]], 586 evmAddress: [UInt8; 20] 587 ): ValidationResult { 588 589 // make signature set first 590 // check number of signatures matches number of key indices 591 if keyIndices.length != signatures.length { 592 return ValidationResult( 593 isValid: false, 594 problem: "key indices size doesn't match the signatures" 595 ) 596 } 597 598 var signatureSet: [Crypto.KeyListSignature] = [] 599 for signatureIndex, signature in signatures{ 600 signatureSet.append(Crypto.KeyListSignature( 601 keyIndex: Int(keyIndices[signatureIndex]), 602 signature: signature 603 )) 604 } 605 606 // fetch account 607 let acc = getAccount(address) 608 609 // constructing key list 610 let keyList = Crypto.KeyList() 611 for signature in signatureSet { 612 let key = acc.keys.get(keyIndex: signature.keyIndex)! 613 assert(!key.isRevoked, message: "revoked key is used") 614 keyList.add( 615 key.publicKey, 616 hashAlgorithm: key.hashAlgorithm, 617 weight: key.weight, 618 ) 619 } 620 621 let isValid = keyList.verify( 622 signatureSet: signatureSet, 623 signedData: signedData, 624 domainSeparationTag: "FLOW-V0.0-user" 625 ) 626 627 if !isValid{ 628 return ValidationResult( 629 isValid: false, 630 problem: "the given signatures are not valid or provide enough weight" 631 ) 632 } 633 634 let coaRef = acc.capabilities.borrow<&EVM.CadenceOwnedAccount>(path) 635 if coaRef == nil { 636 return ValidationResult( 637 isValid: false, 638 problem: "could not borrow bridge account's resource" 639 ) 640 } 641 642 // verify evm address matching 643 var addr = coaRef!.address() 644 for index, item in coaRef!.address().bytes { 645 if item != evmAddress[index] { 646 return ValidationResult( 647 isValid: false, 648 problem: "evm address mismatch" 649 ) 650 } 651 } 652 653 return ValidationResult( 654 isValid: true, 655 problem: nil 656 ) 657 } 658 659 /// Block returns information about the latest executed block. 660 access(all) 661 struct EVMBlock { 662 access(all) 663 let height: UInt64 664 665 access(all) 666 let hash: String 667 668 access(all) 669 let totalSupply: Int 670 671 access(all) 672 let timestamp: UInt64 673 674 init(height: UInt64, hash: String, totalSupply: Int, timestamp: UInt64) { 675 self.height = height 676 self.hash = hash 677 self.totalSupply = totalSupply 678 self.timestamp = timestamp 679 } 680 } 681 682 /// Returns the latest executed block. 683 access(all) 684 fun getLatestBlock(): EVMBlock { 685 return InternalEVM.getLatestBlock() as! EVMBlock 686 } 687 688 /// Interface for a resource which acts as an entrypoint to the VM bridge 689 access(all) 690 resource interface BridgeAccessor { 691 692 /// Endpoint enabling the bridging of an NFT to EVM 693 access(Bridge) 694 fun depositNFT( 695 nft: @{NonFungibleToken.NFT}, 696 to: EVMAddress, 697 feeProvider: auth(FungibleToken.Withdraw) &{FungibleToken.Provider} 698 ) 699 700 /// Endpoint enabling the bridging of an NFT from EVM 701 access(Bridge) 702 fun withdrawNFT( 703 caller: auth(Call) &CadenceOwnedAccount, 704 type: Type, 705 id: UInt256, 706 feeProvider: auth(FungibleToken.Withdraw) &{FungibleToken.Provider} 707 ): @{NonFungibleToken.NFT} 708 709 /// Endpoint enabling the bridging of a fungible token vault to EVM 710 access(Bridge) 711 fun depositTokens( 712 vault: @{FungibleToken.Vault}, 713 to: EVMAddress, 714 feeProvider: auth(FungibleToken.Withdraw) &{FungibleToken.Provider} 715 ) 716 717 /// Endpoint enabling the bridging of fungible tokens from EVM 718 access(Bridge) 719 fun withdrawTokens( 720 caller: auth(Call) &CadenceOwnedAccount, 721 type: Type, 722 amount: UInt256, 723 feeProvider: auth(FungibleToken.Withdraw) &{FungibleToken.Provider} 724 ): @{FungibleToken.Vault} 725 } 726 727 /// Interface which captures a Capability to the bridge Accessor, saving it within the BridgeRouter resource 728 access(all) 729 resource interface BridgeRouter { 730 731 /// Returns a reference to the BridgeAccessor designated for internal bridge requests 732 access(Bridge) view fun borrowBridgeAccessor(): auth(Bridge) &{BridgeAccessor} 733 734 /// Sets the BridgeAccessor Capability in the BridgeRouter 735 access(Bridge) fun setBridgeAccessor(_ accessor: Capability<auth(Bridge) &{BridgeAccessor}>) { 736 pre { 737 accessor.check(): "Invalid BridgeAccessor Capability provided" 738 emit BridgeAccessorUpdated( 739 routerType: self.getType(), 740 routerUUID: self.uuid, 741 routerAddress: self.owner?.address ?? panic("Router must have an owner to be identified"), 742 accessorType: accessor.borrow()!.getType(), 743 accessorUUID: accessor.borrow()!.uuid, 744 accessorAddress: accessor.address 745 ) 746 } 747 } 748 } 749 750 /// Returns a reference to the BridgeAccessor designated for internal bridge requests 751 access(self) 752 view fun borrowBridgeAccessor(): auth(Bridge) &{BridgeAccessor} { 753 return self.account.storage.borrow<auth(Bridge) &{BridgeRouter}>(from: /storage/evmBridgeRouter) 754 ?.borrowBridgeAccessor() 755 ?? panic("Could not borrow reference to the EVM bridge") 756 } 757 }