From 91e2348a4ec53f64fb2598a7e0be9b5e0cd5b3b7 Mon Sep 17 00:00:00 2001 From: Giovanni Sanchez <108043524+sisyphusSmiling@users.noreply.github.com> Date: Fri, 23 Feb 2024 17:45:59 -0600 Subject: [PATCH 1/7] update EVM against flow-go feature/stable-cadence --- cadence/contracts/EVM.cdc | 347 ++++++++++++++++++++++++++++++++------ 1 file changed, 298 insertions(+), 49 deletions(-) diff --git a/cadence/contracts/EVM.cdc b/cadence/contracts/EVM.cdc index 32bb949..4ac9554 100644 --- a/cadence/contracts/EVM.cdc +++ b/cadence/contracts/EVM.cdc @@ -1,8 +1,12 @@ +import Crypto import "FlowToken" access(all) contract EVM { + access(all) + event CadenceOwnedAccountCreated(addressBytes: [UInt8; 20]) + /// EVMAddress is an EVM-compatible address access(all) struct EVMAddress { @@ -16,80 +20,179 @@ contract EVM { self.bytes = bytes } - /// Deposits the given vault into the EVM account with the given address - access(all) - fun deposit(from: @FlowToken.Vault) { - InternalEVM.deposit( - from: <-from, - to: self.bytes - ) - } - /// Balance of the address access(all) fun balance(): Balance { let balance = InternalEVM.balance( address: self.bytes ) - - return Balance(flow: balance) + return Balance(attoflow: balance) } } access(all) struct Balance { - /// The balance in FLOW + /// The balance in atto-FLOW + /// Atto-FLOW is the smallest denomination of FLOW (1e18 FLOW) + /// that is used to store account balances inside EVM + /// similar to the way WEI is used to store ETH divisible to 18 decimal places. + access(all) + var attoflow: UInt + + /// Constructs a new balance + access(all) + init(attoflow: UInt) { + self.attoflow = attoflow + } + + /// Sets the balance by a UFix64 (8 decimal points), the format + /// that is used in Cadence to store FLOW tokens. + access(all) + fun setFLOW(flow: UFix64){ + self.attoflow = InternalEVM.castToAttoFLOW(balance: flow) + } + + /// Casts the balance to a UFix64 (rounding down) + /// Warning! casting a balance to a UFix64 which supports a lower level of precision + /// (8 decimal points in compare to 18) might result in rounding down error. + /// Use the toAttoFlow function if you care need more accuracy. + access(all) + fun inFLOW(): UFix64 { + return InternalEVM.castToFLOW(balance: self.attoflow) + } + + /// Returns the balance in Atto-FLOW + access(all) + fun inAttoFLOW(): UInt { + return self.attoflow + } + } + + /// reports the status of evm execution. + access(all) enum Status: UInt8 { + /// is (rarely) returned when status is unknown + /// and something has gone very wrong. + access(all) case unknown + + /// is returned when execution of an evm transaction/call + /// has failed at the validation step (e.g. nonce mismatch). + /// An invalid transaction/call is rejected to be executed + /// or be included in a block. + access(all) case invalid + + /// is returned when execution of an evm transaction/call + /// has been successful but the vm has reported an error as + /// the outcome of execution (e.g. running out of gas). + /// A failed tx/call is included in a block. + /// Note that resubmission of a failed transaction would + /// result in invalid status in the second attempt, given + /// the nonce would be come invalid. + access(all) case failed + + /// is returned when execution of an evm transaction/call + /// has been successful and no error is reported by the vm. + access(all) case successful + } + + /// reports the outcome of evm transaction/call execution attempt + access(all) struct Result { + /// status of the execution + access(all) + let status: Status + + /// error code (error code zero means no error) access(all) - let flow: UFix64 + let errorCode: UInt64 - /// Constructs a new balance, given the balance in FLOW - init(flow: UFix64) { - self.flow = flow + /// returns the amount of gas metered during + /// evm execution + access(all) + let gasUsed: UInt64 + + /// returns the data that is returned from + /// the evm for the call. For coa.deploy + /// calls it returns the address bytes of the + /// newly deployed contract. + access(all) + let data: [UInt8] + + init( + status: Status, + errorCode: UInt64, + gasUsed: UInt64, + data: [UInt8] + ) { + self.status = status + self.errorCode = errorCode + self.gasUsed = gasUsed + self.data = data } + } - // TODO: - // /// Returns the balance in terms of atto-FLOW. - // /// Atto-FLOW is the smallest denomination of FLOW inside EVM - // access(all) - // fun toAttoFlow(): UInt64 + access(all) + resource interface Addressable { + /// The EVM address + access(all) + fun address(): EVMAddress } access(all) - resource BridgedAccount { + resource CadenceOwnedAccount: Addressable { access(self) - let addressBytes: [UInt8; 20] + var addressBytes: [UInt8; 20] + + init() { + // address is initially set to zero + // but updated through initAddress later + // we have to do this since we need resource id (uuid) + // to calculate the EVM address for this cadence owned account + self.addressBytes = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + } - init(addressBytes: [UInt8; 20]) { + access(contract) + fun initAddress(addressBytes: [UInt8; 20]) { + // only allow set address for the first time + // check address is empty + for item in self.addressBytes { + assert(item == 0, message: "address byte is not empty") + } self.addressBytes = addressBytes } - /// The EVM address of the bridged account + /// The EVM address of the cadence owned account access(all) fun address(): EVMAddress { // Always create a new EVMAddress instance return EVMAddress(bytes: self.addressBytes) } - /// Get balance of the bridged account + /// Get balance of the cadence owned account access(all) fun balance(): Balance { return self.address().balance() } - /// Deposits the given vault into the bridged account's balance + /// Deposits the given vault into the cadence owned account's balance access(all) fun deposit(from: @FlowToken.Vault) { - self.address().deposit(from: <-from) + InternalEVM.deposit( + from: <-from, + to: self.addressBytes + ) } - /// Withdraws the balance from the bridged account's balance + /// Withdraws the balance from the cadence owned account's balance + /// Note that amounts smaller than 10nF (10e-8) can't be withdrawn + /// given that Flow Token Vaults use UFix64s to store balances. + /// If the given balance conversion to UFix64 results in + /// rounding error, this function would fail. access(all) fun withdraw(balance: Balance): @FlowToken.Vault { let vault <- InternalEVM.withdraw( from: self.addressBytes, - amount: balance.flow + amount: balance.attoflow ) as! @FlowToken.Vault return <-vault } @@ -106,7 +209,7 @@ contract EVM { from: self.addressBytes, code: code, gasLimit: gasLimit, - value: value.flow + value: value.attoflow ) return EVMAddress(bytes: addressBytes) } @@ -119,33 +222,50 @@ contract EVM { data: [UInt8], gasLimit: UInt64, value: Balance - ): [UInt8] { - return InternalEVM.call( - from: self.addressBytes, - to: to.bytes, - data: data, - gasLimit: gasLimit, - value: value.flow - ) + ): Result { + return InternalEVM.call( + from: self.addressBytes, + to: to.bytes, + data: data, + gasLimit: gasLimit, + value: value.attoflow + ) as! Result } } - /// Creates a new bridged account + /// Creates a new cadence owned account access(all) - fun createBridgedAccount(): @BridgedAccount { - return <-create BridgedAccount( - addressBytes: InternalEVM.createBridgedAccount() - ) + fun createCadenceOwnedAccount(): @CadenceOwnedAccount { + let acc <-create CadenceOwnedAccount() + let addr = InternalEVM.createCadenceOwnedAccount(uuid: acc.uuid) + acc.initAddress(addressBytes: addr) + emit CadenceOwnedAccountCreated(addressBytes: addr) + return <-acc } /// Runs an a RLP-encoded EVM transaction, deducts the gas fees, /// and deposits the gas fees into the provided coinbase address. - /// - /// Returns true if the transaction was successful, - /// and returns false otherwise access(all) - fun run(tx: [UInt8], coinbase: EVMAddress) { - InternalEVM.run(tx: tx, coinbase: coinbase.bytes) + fun run(tx: [UInt8], coinbase: EVMAddress): Result { + return InternalEVM.run( + tx: tx, + coinbase: coinbase.bytes + ) as! Result + } + + /// mustRun runs the transaction using EVM.run yet it + /// rollback if the tx execution status is unknown or invalid. + /// Note that this method does not rollback if transaction + /// is executed but an vm error is reported as the outcome + /// of the execution (status: failed). + access(all) + fun mustRun(tx: [UInt8], coinbase: EVMAddress): Result { + let runResult = self.run(tx: tx, coinbase: coinbase) + assert( + runResult.status == Status.failed || runResult.status == Status.successful, + message: "tx is not valid for execution" + ) + return runResult } access(all) @@ -157,4 +277,133 @@ contract EVM { fun decodeABI(types: [Type], data: [UInt8]): [AnyStruct] { return InternalEVM.decodeABI(types: types, data: data) } + + access(all) + fun encodeABIWithSignature( + _ signature: String, + _ values: [AnyStruct] + ): [UInt8] { + let methodID = HashAlgorithm.KECCAK_256.hash( + signature.utf8 + ).slice(from: 0, upTo: 4) + let arguments = InternalEVM.encodeABI(values) + + return methodID.concat(arguments) + } + + access(all) + fun decodeABIWithSignature( + _ signature: String, + types: [Type], + data: [UInt8] + ): [AnyStruct] { + let methodID = HashAlgorithm.KECCAK_256.hash( + signature.utf8 + ).slice(from: 0, upTo: 4) + + for byte in methodID { + if byte != data.removeFirst() { + panic("signature mismatch") + } + } + + return InternalEVM.decodeABI(types: types, data: data) + } + + /// ValidationResult returns the result of COA ownership proof validation + access(all) + struct ValidationResult { + access(all) + let isValid: Bool + + access(all) + let problem: String? + + init(isValid: Bool, problem: String?) { + self.isValid = isValid + self.problem = problem + } + } + + /// validateCOAOwnershipProof validates a COA ownership proof + access(all) + fun validateCOAOwnershipProof( + address: Address, + path: PublicPath, + signedData: [UInt8], + keyIndices: [UInt64], + signatures: [[UInt8]], + evmAddress: [UInt8; 20] + ): ValidationResult { + + // make signature set first + // check number of signatures matches number of key indices + if keyIndices.length != signatures.length { + return ValidationResult( + isValid: false, + problem: "key indices size doesn't match the signatures" + ) + } + + var signatureSet: [Crypto.KeyListSignature] = [] + for signatureIndex, signature in signatures{ + signatureSet.append(Crypto.KeyListSignature( + keyIndex: Int(keyIndices[signatureIndex]), + signature: signature + )) + } + + // fetch account + let acc = getAccount(address) + + // constructing key list + let keyList = Crypto.KeyList() + for signature in signatureSet { + let key = acc.keys.get(keyIndex: signature.keyIndex)! + assert(!key.isRevoked, message: "revoked key is used") + keyList.add( + key.publicKey, + hashAlgorithm: key.hashAlgorithm, + weight: key.weight, + ) + } + + let isValid = keyList.verify( + signatureSet: signatureSet, + signedData: signedData + ) + + if !isValid{ + return ValidationResult( + isValid: false, + problem: "the given signatures are not valid or provide enough weight" + ) + } + + let coaRef = acc.getCapability(path) + .borrow<&EVM.CadenceOwnedAccount{EVM.Addressable}>() + + if coaRef == nil { + return ValidationResult( + isValid: false, + problem: "could not borrow bridge account's resource" + ) + } + + // verify evm address matching + var addr = coaRef!.address() + for index, item in coaRef!.address().bytes { + if item != evmAddress[index] { + return ValidationResult( + isValid: false, + problem: "evm address mismatch" + ) + } + } + + return ValidationResult( + isValid: true, + problem: nil + ) + } } \ No newline at end of file From 55fdeb9b4e982eb5ee2ee004f34e4bedeb130a7f Mon Sep 17 00:00:00 2001 From: Giovanni Sanchez <108043524+sisyphusSmiling@users.noreply.github.com> Date: Fri, 23 Feb 2024 17:46:43 -0600 Subject: [PATCH 2/7] update evm scripts --- cadence/scripts/evm/get_balance.cdc | 2 +- cadence/scripts/evm/get_evm_address_string.cdc | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/cadence/scripts/evm/get_balance.cdc b/cadence/scripts/evm/get_balance.cdc index ea03aca..4b5c15d 100644 --- a/cadence/scripts/evm/get_balance.cdc +++ b/cadence/scripts/evm/get_balance.cdc @@ -10,5 +10,5 @@ access(all) fun main(address: String): UFix64 { bytes[10], bytes[11], bytes[12], bytes[13], bytes[14], bytes[15], bytes[16], bytes[17], bytes[18], bytes[19] ] - return EVM.EVMAddress(bytes: addressBytes).balance().flow + return EVM.EVMAddress(bytes: addressBytes).balance().inFlow } diff --git a/cadence/scripts/evm/get_evm_address_string.cdc b/cadence/scripts/evm/get_evm_address_string.cdc index a481d16..23319cf 100644 --- a/cadence/scripts/evm/get_evm_address_string.cdc +++ b/cadence/scripts/evm/get_evm_address_string.cdc @@ -4,7 +4,7 @@ import "EVM" /// access(all) fun main(flowAddress: Address): String? { let account = getAuthAccount(flowAddress) - if let address = account.storage.borrow<&EVM.BridgedAccount>(from: /storage/evm)?.address() { + if let address = account.storage.borrow<&EVM.CadenceOwnedAccount>(from: /storage/evm)?.address() { let bytes: [UInt8] = [] for byte in address.bytes { bytes.append(byte) From fe2cdd3d6476c2f349b9ae49b06dc0779e8e6244 Mon Sep 17 00:00:00 2001 From: Giovanni Sanchez <108043524+sisyphusSmiling@users.noreply.github.com> Date: Fri, 23 Feb 2024 17:47:48 -0600 Subject: [PATCH 3/7] update EVM Cadence transactions --- cadence/transactions/evm/create_coa_and_fund.cdc | 10 +++++----- cadence/transactions/evm/mint_and_fund_evm_address.cdc | 8 ++++---- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/cadence/transactions/evm/create_coa_and_fund.cdc b/cadence/transactions/evm/create_coa_and_fund.cdc index 316073d..78ea9d9 100644 --- a/cadence/transactions/evm/create_coa_and_fund.cdc +++ b/cadence/transactions/evm/create_coa_and_fund.cdc @@ -8,18 +8,18 @@ import "EVM" /// transaction(amount: UFix64) { let sentVault: @FlowToken.Vault - let coa: &EVM.BridgedAccount + let coa: &EVM.CadenceOwnedAccount prepare(signer: auth(Storage) &Account) { - let vaultRef = signer.storage.borrow( + let vaultRef = signer.storage.borrow( from: /storage/flowTokenVault ) ?? panic("Could not borrow reference to the owner's Vault!") self.sentVault <- vaultRef.withdraw(amount: amount) as! @FlowToken.Vault - if signer.storage.borrow<&EVM.BridgedAccount>(from: /storage/evm) == nil { - signer.storage.save(<-EVM.createBridgedAccount(), to: /storage/evm) + if signer.storage.borrow<&EVM.CadenceOwnedAccount>(from: /storage/evm) == nil { + signer.storage.save(<-EVM.createCadenceOwnedAccount(), to: /storage/evm) } - self.coa = signer.storage.borrow<&EVM.BridgedAccount>(from: /storage/evm) + self.coa = signer.storage.borrow<&EVM.CadenceOwnedAccount>(from: /storage/evm) ?? panic("Could not borrow reference to the signer's COA!") } diff --git a/cadence/transactions/evm/mint_and_fund_evm_address.cdc b/cadence/transactions/evm/mint_and_fund_evm_address.cdc index f4a2df8..1d1d76e 100644 --- a/cadence/transactions/evm/mint_and_fund_evm_address.cdc +++ b/cadence/transactions/evm/mint_and_fund_evm_address.cdc @@ -8,16 +8,16 @@ import "EVM" transaction(to: EVM.EVMAddress, amount: UFix64, gasLimit: UInt64) { let tokenAdmin: &FlowToken.Administrator - let coa: &EVM.BridgedAccount + let coa: auth(EVM.Call) &EVM.CadenceOwnedAccount prepare(signer: auth(Storage) &Account) { self.tokenAdmin = signer.storage.borrow<&FlowToken.Administrator>(from: /storage/flowTokenAdmin) ?? panic("Signer is not the token admin") - if signer.storage.borrow<&EVM.BridgedAccount>(from: /storage/evm) == nil { - signer.storage.save(<-EVM.createBridgedAccount(), to: /storage/evm) + if signer.storage.borrow<&EVM.CadenceOwnedAccount>(from: /storage/evm) == nil { + signer.storage.save(<-EVM.createCadenceOwnedAccount(), to: /storage/evm) } - self.coa = signer.storage.borrow<&EVM.BridgedAccount>(from: /storage/evm) + self.coa = signer.storage.borrow(from: /storage/evm) ?? panic("Could not borrow reference to the signer's COA!") } From 91305b95d599128365de11e33bb2bee7d7f814cb Mon Sep 17 00:00:00 2001 From: Giovanni Sanchez <108043524+sisyphusSmiling@users.noreply.github.com> Date: Fri, 23 Feb 2024 17:48:17 -0600 Subject: [PATCH 4/7] update transfer_flow Cadence transaction --- cadence/transactions/transfer_flow.cdc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cadence/transactions/transfer_flow.cdc b/cadence/transactions/transfer_flow.cdc index 9d186a5..3e853d5 100644 --- a/cadence/transactions/transfer_flow.cdc +++ b/cadence/transactions/transfer_flow.cdc @@ -9,7 +9,7 @@ transaction (amount: UFix64, to: Address) { prepare(signer: auth(BorrowValue) &Account) { // Get a reference to the signer's stored vault - let vaultRef = signer.storage.borrow(from: /storage/flowTokenVault) + let vaultRef = signer.storage.borrow(from: /storage/flowTokenVault) ?? panic("Could not borrow reference to the owner's Vault!") // Withdraw tokens from the signer's stored vault From a32df44d68c199388c5f0477b4ed00d21a0c038a Mon Sep 17 00:00:00 2001 From: Giovanni Sanchez <108043524+sisyphusSmiling@users.noreply.github.com> Date: Fri, 23 Feb 2024 17:48:39 -0600 Subject: [PATCH 5/7] update fund.ts --- lib/flow/fund.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/flow/fund.ts b/lib/flow/fund.ts index dda10bd..436d0ed 100644 --- a/lib/flow/fund.ts +++ b/lib/flow/fund.ts @@ -43,16 +43,16 @@ import EVM from ${publicConfig.contractEVM} transaction(to: EVM.EVMAddress, amount: UFix64, gasLimit: UInt64) { let tokenAdmin: &FlowToken.Administrator - let coa: &EVM.BridgedAccount + let coa: auth(EVM.Call) &EVM.CadenceOwnedAccount prepare(signer: auth(Storage) &Account) { self.tokenAdmin = signer.storage.borrow<&FlowToken.Administrator>(from: /storage/flowTokenAdmin) ?? panic("Signer is not the token admin") - if signer.storage.borrow<&EVM.BridgedAccount>(from: /storage/evm) == nil { - signer.storage.save(<-EVM.createBridgedAccount(), to: /storage/evm) + if signer.storage.borrow<&EVM.CadenceOwnedAccount>(from: /storage/evm) == nil { + signer.storage.save(<-EVM.createCadenceOwnedAccount(), to: /storage/evm) } - self.coa = signer.storage.borrow<&EVM.BridgedAccount>(from: /storage/evm) + self.coa = signer.storage.borrow(from: /storage/evm) ?? panic("Could not borrow reference to the signer's COA!") } From 5620a5ba3b46b608a692ddcb2f735841b515062e Mon Sep 17 00:00:00 2001 From: Giovanni Sanchez <108043524+sisyphusSmiling@users.noreply.github.com> Date: Fri, 23 Feb 2024 17:49:36 -0600 Subject: [PATCH 6/7] fix EVM transactions --- .../transactions/evm/create_flow_account_with_coa_and_fund.cdc | 2 +- cadence/transactions/evm/mint_and_fund_evm_address.cdc | 2 +- lib/flow/fund.ts | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/cadence/transactions/evm/create_flow_account_with_coa_and_fund.cdc b/cadence/transactions/evm/create_flow_account_with_coa_and_fund.cdc index 8de4f8b..6a26a07 100644 --- a/cadence/transactions/evm/create_flow_account_with_coa_and_fund.cdc +++ b/cadence/transactions/evm/create_flow_account_with_coa_and_fund.cdc @@ -57,7 +57,7 @@ transaction( self.tokenReceiver.deposit(from: <-flowVault) - let coa <- EVM.createBridgedAccount() + let coa <- EVM.createCadenceOwnedAccount() coa.address().deposit(from: <-evmVault) self.newAccount.storage.save(<-coa, to: StoragePath(identifier: "evm")!) diff --git a/cadence/transactions/evm/mint_and_fund_evm_address.cdc b/cadence/transactions/evm/mint_and_fund_evm_address.cdc index 1d1d76e..262f2cc 100644 --- a/cadence/transactions/evm/mint_and_fund_evm_address.cdc +++ b/cadence/transactions/evm/mint_and_fund_evm_address.cdc @@ -17,7 +17,7 @@ transaction(to: EVM.EVMAddress, amount: UFix64, gasLimit: UInt64) { if signer.storage.borrow<&EVM.CadenceOwnedAccount>(from: /storage/evm) == nil { signer.storage.save(<-EVM.createCadenceOwnedAccount(), to: /storage/evm) } - self.coa = signer.storage.borrow(from: /storage/evm) + self.coa = signer.storage.borrow(from: /storage/evm) ?? panic("Could not borrow reference to the signer's COA!") } diff --git a/lib/flow/fund.ts b/lib/flow/fund.ts index 436d0ed..7bc43cd 100644 --- a/lib/flow/fund.ts +++ b/lib/flow/fund.ts @@ -52,7 +52,7 @@ transaction(to: EVM.EVMAddress, amount: UFix64, gasLimit: UInt64) { if signer.storage.borrow<&EVM.CadenceOwnedAccount>(from: /storage/evm) == nil { signer.storage.save(<-EVM.createCadenceOwnedAccount(), to: /storage/evm) } - self.coa = signer.storage.borrow(from: /storage/evm) + self.coa = signer.storage.borrow(from: /storage/evm) ?? panic("Could not borrow reference to the signer's COA!") } From 3564b5fb611f54608ba4dc6aee03b38576d58b67 Mon Sep 17 00:00:00 2001 From: Giovanni Sanchez <108043524+sisyphusSmiling@users.noreply.github.com> Date: Fri, 23 Feb 2024 17:52:38 -0600 Subject: [PATCH 7/7] fix EVM balance query --- cadence/scripts/evm/get_balance.cdc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cadence/scripts/evm/get_balance.cdc b/cadence/scripts/evm/get_balance.cdc index 4b5c15d..39e5cc3 100644 --- a/cadence/scripts/evm/get_balance.cdc +++ b/cadence/scripts/evm/get_balance.cdc @@ -10,5 +10,5 @@ access(all) fun main(address: String): UFix64 { bytes[10], bytes[11], bytes[12], bytes[13], bytes[14], bytes[15], bytes[16], bytes[17], bytes[18], bytes[19] ] - return EVM.EVMAddress(bytes: addressBytes).balance().inFlow + return EVM.EVMAddress(bytes: addressBytes).balance().inFLOW() }