diff --git a/cadence/contracts/standards/EVM.cdc b/cadence/contracts/standards/EVM.cdc index c0af43c8..87e92adc 100644 --- a/cadence/contracts/standards/EVM.cdc +++ b/cadence/contracts/standards/EVM.cdc @@ -149,6 +149,32 @@ contract EVM { ) emit FLOWTokensDeposited(addressBytes: self.bytes, amount: amount) } + + /// Serializes the address to a hex string without the 0x prefix + /// Future implementations should pass data to InternalEVM for native serialization + access(all) + view fun toString(): String { + return String.encodeHex(self.bytes.toVariableSized()) + } + + /// Compares the address with another address + access(all) + view fun equals(_ other: EVMAddress): Bool { + return self.bytes == other.bytes + } + } + + /// Converts a hex string to an EVM address if the string is a valid hex string + /// Future implementations should pass data to InternalEVM for native deserialization + access(all) + fun addressFromString(_ asHex: String): EVMAddress { + pre { + asHex.length == 40 || asHex.length == 42: "Invalid hex string length for an EVM address" + } + // Strip the 0x prefix if it exists + var withoutPrefix = (asHex[1] == "x" ? asHex.slice(from: 2, upTo: asHex.length) : asHex).toLower() + let bytes = withoutPrefix.decodeHex().toConstantSized<[UInt8;20]>()! + return EVMAddress(bytes: bytes) } access(all) diff --git a/cadence/transactions/flow-token/transfer_flow_to_cadence_or_evm.cdc b/cadence/transactions/flow-token/transfer_flow_to_cadence_or_evm.cdc new file mode 100644 index 00000000..da4b92e8 --- /dev/null +++ b/cadence/transactions/flow-token/transfer_flow_to_cadence_or_evm.cdc @@ -0,0 +1,62 @@ +import "FungibleToken" +import "FlowToken" + +import "EVM" + +// Transfers $FLOW from the signer's account to the recipient's address, determining the target VM based on the format +// of the recipient's hex address. +/// +/// @param addressString: The recipient's address in hex format - this should be either an EVM address or a Flow address +/// @param amount: The amount of $FLOW to transfer as a UFix64 value +/// +transaction(addressString: String, amount: UFix64) { + + let sentVault: @FlowToken.Vault + let evmRecipient: EVM.EVMAddress? + var receiver: &{FungibleToken.Receiver}? + + prepare(signer: auth(BorrowValue, SaveValue) &Account) { + // Reference signer's FlowToken Vault + let sourceVault = signer.storage.borrow(from: /storage/flowTokenVault) + ?? panic("Could not borrow signer's FlowToken.Vault") + + // Init receiver as nil + self.receiver = nil + // Ensure address is prefixed with '0x' + let withPrefix = addressString.slice(from: 0, upTo: 2) == "0x" ? addressString : "0x".concat(addressString) + // Attempt to parse address as Cadence or EVM address + let cadenceRecipient = withPrefix.length < 40 ? Address.fromString(withPrefix) : nil + self.evmRecipient = cadenceRecipient == nil ? EVM.addressFromString(withPrefix) : nil + + // Validate exactly one target address is assigned + if cadenceRecipient != nil && self.evmRecipient != nil { + panic("Malformed recipient address - assignable as both Cadence and EVM addresses") + } else if cadenceRecipient == nil && self.evmRecipient == nil { + panic("Malformed recipient address - not assignable as either Cadence or EVM address") + } + + if cadenceRecipient != nil { + // Assign FungibleToken Receiver if recipient is a Cadence address + self.receiver = getAccount(cadenceRecipient!).capabilities.borrow<&{FungibleToken.Receiver}>(/public/flowTokenReceiver) + ?? panic("Could not borrow FungibleToken Receiver from recipient") + } + + // Create empty FLOW vault to capture funds + self.sentVault <- sourceVault.withdraw(amount: amount) as! @FlowToken.Vault + } + + pre { + self.receiver != nil || self.evmRecipient != nil: "Could not assign a recipient for the transfer" + self.sentVault.balance == amount: "Attempting to send an incorrect amount of $FLOW" + } + + execute { + // Complete Cadence transfer if the FungibleToken Receiver is assigned + if self.receiver != nil { + self.receiver!.deposit(from: <-self.sentVault) + } else { + // Otherwise, complete EVM transfer + self.evmRecipient!.deposit(from: <-self.sentVault) + } + } +}