From d940912261bb5c9cd72cd0c9d2183a79474552dd Mon Sep 17 00:00:00 2001 From: Giovanni Sanchez <108043524+sisyphusSmiling@users.noreply.github.com> Date: Tue, 13 Feb 2024 16:16:19 -0600 Subject: [PATCH 01/29] refactor contracts for Cadence 1.0 --- cadence/contracts/Burner.cdc | 44 ++ cadence/contracts/FUSD.cdc | 119 ++- cadence/contracts/FlowToken.cdc | 198 +++-- cadence/contracts/FungibleToken.cdc | 204 ++--- .../contracts/FungibleTokenMetadataViews.cdc | 180 +++++ cadence/contracts/MetadataViews.cdc | 706 ++++++++++++++++++ cadence/contracts/NonFungibleToken.cdc | 234 ++++++ cadence/contracts/ViewResolver.cdc | 59 ++ flow.json | 44 +- 9 files changed, 1601 insertions(+), 187 deletions(-) create mode 100644 cadence/contracts/Burner.cdc create mode 100644 cadence/contracts/FungibleTokenMetadataViews.cdc create mode 100644 cadence/contracts/MetadataViews.cdc create mode 100644 cadence/contracts/NonFungibleToken.cdc create mode 100644 cadence/contracts/ViewResolver.cdc diff --git a/cadence/contracts/Burner.cdc b/cadence/contracts/Burner.cdc new file mode 100644 index 0000000..0a3795e --- /dev/null +++ b/cadence/contracts/Burner.cdc @@ -0,0 +1,44 @@ +/// Burner is a contract that can facilitate the destruction of any resource on flow. +/// +/// Contributors +/// - Austin Kline - https://twitter.com/austin_flowty +/// - Deniz Edincik - https://twitter.com/bluesign +/// - Bastian Müller - https://twitter.com/turbolent +access(all) contract Burner { + /// When Crescendo (Cadence 1.0) is released, custom destructors will be removed from cadece. + /// Burnable is an interface meant to replace this lost feature, allowing anyone to add a callback + /// method to ensure they do not destroy something which is not meant to be, + /// or to add logic based on destruction such as tracking the supply of a FT Collection + /// + /// NOTE: The only way to see benefit from this interface + /// is to always use the burn method in this contract. Anyone who owns a resource can always elect **not** + /// to destroy a resource this way + access(all) resource interface Burnable { + access(contract) fun burnCallback() + } + + /// burn is a global method which will destroy any resource it is given. + /// If the provided resource implements the Burnable interface, + /// it will call the burnCallback method and then destroy afterwards. + access(all) fun burn(_ r: @AnyResource) { + if let s <- r as? @{Burnable} { + s.burnCallback() + destroy s + } else if let arr <- r as? @[AnyResource] { + while arr.length > 0 { + let item <- arr.removeFirst() + self.burn(<-item) + } + destroy arr + } else if let dict <- r as? @{HashableStruct: AnyResource} { + let keys = dict.keys + while keys.length > 0 { + let item <- dict.remove(key: keys.removeFirst())! + self.burn(<-item) + } + destroy dict + } else { + destroy r + } + } +} \ No newline at end of file diff --git a/cadence/contracts/FUSD.cdc b/cadence/contracts/FUSD.cdc index ef45ba3..3bd2e29 100644 --- a/cadence/contracts/FUSD.cdc +++ b/cadence/contracts/FUSD.cdc @@ -1,33 +1,57 @@ -import FungibleToken from "./FungibleToken.cdc" +import FungibleToken from "FungibleToken" +import MetadataViews from "MetadataViews" +import FungibleTokenMetadataViews from "FungibleTokenMetadataViews" +import ViewResolver from "ViewResolver" -pub contract FUSD: FungibleToken { +access(all) contract FUSD: FungibleToken { + + access(all) entitlement ProxyOwner // Event that is emitted when the contract is created - pub event TokensInitialized(initialSupply: UFix64) + access(all) event TokensInitialized(initialSupply: UFix64) // Event that is emitted when tokens are withdrawn from a Vault - pub event TokensWithdrawn(amount: UFix64, from: Address?) + access(all) event TokensWithdrawn(amount: UFix64, from: Address?) // Event that is emitted when tokens are deposited to a Vault - pub event TokensDeposited(amount: UFix64, to: Address?) + access(all) event TokensDeposited(amount: UFix64, to: Address?) // Event that is emitted when new tokens are minted - pub event TokensMinted(amount: UFix64) + access(all) event TokensMinted(amount: UFix64) // The storage path for the admin resource - pub let AdminStoragePath: StoragePath + access(all) let AdminStoragePath: StoragePath // The storage Path for minters' MinterProxy - pub let MinterProxyStoragePath: StoragePath + access(all) let MinterProxyStoragePath: StoragePath // The public path for minters' MinterProxy capability - pub let MinterProxyPublicPath: PublicPath + access(all) let MinterProxyPublicPath: PublicPath // Event that is emitted when a new minter resource is created - pub event MinterCreated() + access(all) event MinterCreated() // Total supply of fusd in existence - pub var totalSupply: UFix64 + access(all) var totalSupply: UFix64 + + /// Function that returns all the Metadata Views implemented by this contract. + /// @param resourceType: An optional resource type to return views for + /// @return An array of Types defining the implemented views. This value will be used by + /// developers to know which parameter to pass to the resolveView() method. + /// + access(all) view fun getContractViews(resourceType: Type?): [Type] { + return [] + } + + /// Function that resolves a metadata view for this token. + /// + /// @param resourceType: An optional resource type to return views for + /// @param view: The Type of the desired view. + /// @return A structure representing the requested view. + /// + access(all) fun resolveContractView(resourceType: Type?, viewType: Type): AnyStruct? { + return nil + } // Vault // @@ -41,16 +65,48 @@ pub contract FUSD: FungibleToken { // out of thin air. A special Minter resource needs to be defined to mint // new tokens. // - pub resource Vault: FungibleToken.Provider, FungibleToken.Receiver, FungibleToken.Balance { + access(all) resource Vault: FungibleToken.Vault { // holds the balance of a users tokens - pub var balance: UFix64 + access(all) var balance: UFix64 // initialize the balance at resource creation time init(balance: UFix64) { self.balance = balance } + /// Called when a fungible token is burned via the `Burner.burn()` method + access(contract) fun burnCallback() { + if self.balance > 0.0 { + FUSD.totalSupply = FUSD.totalSupply - self.balance + } + self.balance = 0.0 + } + + access(all) view fun getViews(): [Type] { + return FUSD.getContractViews(resourceType: nil) + } + + access(all) fun resolveView(_ view: Type): AnyStruct? { + return FUSD.resolveContractView(resourceType: nil, viewType: view) + } + + /// getSupportedVaultTypes optionally returns a list of vault types that this receiver accepts + access(all) view fun getSupportedVaultTypes(): {Type: Bool} { + let supportedTypes: {Type: Bool} = {} + supportedTypes[self.getType()] = true + return supportedTypes + } + + access(all) view fun isSupportedVaultType(type: Type): Bool { + return self.getSupportedVaultTypes()[type] ?? false + } + + /// Asks if the amount can be withdrawn from this vault + access(all) view fun isAvailableToWithdraw(amount: UFix64): Bool { + return amount <= self.balance + } + // withdraw // // Function that takes an integer amount as an argument @@ -60,7 +116,7 @@ pub contract FUSD: FungibleToken { // created Vault to the context that called so it can be deposited // elsewhere. // - pub fun withdraw(amount: UFix64): @FungibleToken.Vault { + access(FungibleToken.Withdraw) fun withdraw(amount: UFix64): @{FungibleToken.Vault} { self.balance = self.balance - amount emit TokensWithdrawn(amount: amount, from: self.owner?.address) return <-create Vault(balance: amount) @@ -73,7 +129,7 @@ pub contract FUSD: FungibleToken { // It is allowed to destroy the sent Vault because the Vault // was a temporary holder of the tokens. The Vault's balance has // been consumed and therefore can be destroyed. - pub fun deposit(from: @FungibleToken.Vault) { + access(all) fun deposit(from: @{FungibleToken.Vault}) { let vault <- from as! @FUSD.Vault self.balance = self.balance + vault.balance emit TokensDeposited(amount: vault.balance, to: self.owner?.address) @@ -81,8 +137,10 @@ pub contract FUSD: FungibleToken { destroy vault } - destroy() { - FUSD.totalSupply = FUSD.totalSupply - self.balance + /// createEmptyVault allows any user to create a new Vault that has a zero balance + /// + access(all) fun createEmptyVault(): @{FungibleToken.Vault} { + return <- FUSD.createEmptyVault(vaultType: Type<@FUSD.Vault>()) } } @@ -93,7 +151,10 @@ pub contract FUSD: FungibleToken { // and store the returned Vault in their storage in order to allow their // account to be able to receive deposits of this token type. // - pub fun createEmptyVault(): @FUSD.Vault { + access(all) fun createEmptyVault(vaultType: Type): @{FungibleToken.Vault} { + pre { + vaultType == Type<@FUSD.Vault>(): "Unsupported vault type requested" + } return <-create Vault(balance: 0.0) } @@ -102,14 +163,14 @@ pub contract FUSD: FungibleToken { // Resource object that can mint new tokens. // The admin stores this and passes it to the minter account as a capability wrapper resource. // - pub resource Minter { + access(all) resource Minter { // mintTokens // // Function that mints new tokens, adds them to the total supply, // and returns them to the calling context. // - pub fun mintTokens(amount: UFix64): @FUSD.Vault { + access(all) fun mintTokens(amount: UFix64): @FUSD.Vault { pre { amount > 0.0: "Amount minted must be greater than zero" } @@ -120,8 +181,8 @@ pub contract FUSD: FungibleToken { } - pub resource interface MinterProxyPublic { - pub fun setMinterCapability(cap: Capability<&Minter>) + access(all) resource interface MinterProxyPublic { + access(all) fun setMinterCapability(cap: Capability<&Minter>) } // MinterProxy @@ -130,18 +191,18 @@ pub contract FUSD: FungibleToken { // The resource that this capability represents can be deleted by the admin // in order to unilaterally revoke minting capability if needed. - pub resource MinterProxy: MinterProxyPublic { + access(all) resource MinterProxy: MinterProxyPublic { // access(self) so nobody else can copy the capability and use it. access(self) var minterCapability: Capability<&Minter>? // Anyone can call this, but only the admin can create Minter capabilities, // so the type system constrains this to being called by the admin. - pub fun setMinterCapability(cap: Capability<&Minter>) { + access(all) fun setMinterCapability(cap: Capability<&Minter>) { self.minterCapability = cap } - pub fun mintTokens(amount: UFix64): @FUSD.Vault { + access(ProxyOwner) fun mintTokens(amount: UFix64): @FUSD.Vault { return <- self.minterCapability! .borrow()! .mintTokens(amount:amount) @@ -159,7 +220,7 @@ pub contract FUSD: FungibleToken { // Anyone can call this, but the MinterProxy cannot mint without a Minter capability, // and only the admin can provide that. // - pub fun createMinterProxy(): @MinterProxy { + access(all) fun createMinterProxy(): @MinterProxy { return <- create MinterProxy() } @@ -172,7 +233,7 @@ pub contract FUSD: FungibleToken { // Ideally we would create this structure in a single function, generate the paths from the address // and cache all of this information to enable easy revocation but String/Path comversion isn't yet supported. // - pub resource Administrator { + access(all) resource Administrator { // createNewMinter // @@ -184,7 +245,7 @@ pub contract FUSD: FungibleToken { // then the admin account running: // transactions/fusd/admin/deposit_fusd_minter.cdc // - pub fun createNewMinter(): @Minter { + access(all) fun createNewMinter(): @Minter { emit MinterCreated() return <- create Minter() } @@ -199,7 +260,7 @@ pub contract FUSD: FungibleToken { self.totalSupply = 0.0 let admin <- create Administrator() - self.account.save(<-admin, to: self.AdminStoragePath) + self.account.storage.save(<-admin, to: self.AdminStoragePath) // Emit an event that shows that the contract was initialized emit TokensInitialized(initialSupply: 0.0) diff --git a/cadence/contracts/FlowToken.cdc b/cadence/contracts/FlowToken.cdc index a69a715..7892899 100644 --- a/cadence/contracts/FlowToken.cdc +++ b/cadence/contracts/FlowToken.cdc @@ -1,30 +1,27 @@ -import FungibleToken from "./FungibleToken.cdc" +import FungibleToken from "FungibleToken" +import MetadataViews from "MetadataViews" +import FungibleTokenMetadataViews from "FungibleTokenMetadataViews" +import Burner from "Burner" -pub contract FlowToken: FungibleToken { +access(all) contract FlowToken: FungibleToken { // Total supply of Flow tokens in existence - pub var totalSupply: UFix64 - - // Event that is emitted when the contract is created - pub event TokensInitialized(initialSupply: UFix64) + access(all) var totalSupply: UFix64 // Event that is emitted when tokens are withdrawn from a Vault - pub event TokensWithdrawn(amount: UFix64, from: Address?) + access(all) event TokensWithdrawn(amount: UFix64, from: Address?) // Event that is emitted when tokens are deposited to a Vault - pub event TokensDeposited(amount: UFix64, to: Address?) + access(all) event TokensDeposited(amount: UFix64, to: Address?) // Event that is emitted when new tokens are minted - pub event TokensMinted(amount: UFix64) - - // Event that is emitted when tokens are destroyed - pub event TokensBurned(amount: UFix64) + access(all) event TokensMinted(amount: UFix64) // Event that is emitted when a new minter resource is created - pub event MinterCreated(allowedAmount: UFix64) + access(all) event MinterCreated(allowedAmount: UFix64) // Event that is emitted when a new burner resource is created - pub event BurnerCreated() + access(all) event BurnerCreated() // Vault // @@ -38,16 +35,38 @@ pub contract FlowToken: FungibleToken { // out of thin air. A special Minter resource needs to be defined to mint // new tokens. // - pub resource Vault: FungibleToken.Provider, FungibleToken.Receiver, FungibleToken.Balance { + access(all) resource Vault: FungibleToken.Vault { // holds the balance of a users tokens - pub var balance: UFix64 + access(all) var balance: UFix64 // initialize the balance at resource creation time init(balance: UFix64) { self.balance = balance } + /// Called when a fungible token is burned via the `Burner.burn()` method + access(contract) fun burnCallback() { + if self.balance > 0.0 { + FlowToken.totalSupply = FlowToken.totalSupply - self.balance + } + self.balance = 0.0 + } + + /// getSupportedVaultTypes optionally returns a list of vault types that this receiver accepts + access(all) view fun getSupportedVaultTypes(): {Type: Bool} { + return {self.getType(): true} + } + + access(all) view fun isSupportedVaultType(type: Type): Bool { + if (type == self.getType()) { return true } else { return false } + } + + /// Asks if the amount can be withdrawn from this vault + access(all) view fun isAvailableToWithdraw(amount: UFix64): Bool { + return amount <= self.balance + } + // withdraw // // Function that takes an integer amount as an argument @@ -57,7 +76,7 @@ pub contract FlowToken: FungibleToken { // created Vault to the context that called so it can be deposited // elsewhere. // - pub fun withdraw(amount: UFix64): @FungibleToken.Vault { + access(FungibleToken.Withdraw) fun withdraw(amount: UFix64): @{FungibleToken.Vault} { self.balance = self.balance - amount emit TokensWithdrawn(amount: amount, from: self.owner?.address) return <-create Vault(balance: amount) @@ -70,7 +89,7 @@ pub contract FlowToken: FungibleToken { // It is allowed to destroy the sent Vault because the Vault // was a temporary holder of the tokens. The Vault's balance has // been consumed and therefore can be destroyed. - pub fun deposit(from: @FungibleToken.Vault) { + access(all) fun deposit(from: @{FungibleToken.Vault}) { let vault <- from as! @FlowToken.Vault self.balance = self.balance + vault.balance emit TokensDeposited(amount: vault.balance, to: self.owner?.address) @@ -78,8 +97,26 @@ pub contract FlowToken: FungibleToken { destroy vault } - destroy() { - FlowToken.totalSupply = FlowToken.totalSupply - self.balance + /// Get all the Metadata Views implemented by FlowToken + /// + /// @return An array of Types defining the implemented views. This value will be used by + /// developers to know which parameter to pass to the resolveView() method. + /// + access(all) view fun getViews(): [Type]{ + return FlowToken.getContractViews(resourceType: nil) + } + + /// Get a Metadata View from FlowToken + /// + /// @param view: The Type of the desired view. + /// @return A structure representing the requested view. + /// + access(all) fun resolveView(_ view: Type): AnyStruct? { + return FlowToken.resolveContractView(resourceType: nil, viewType: view) + } + + access(all) fun createEmptyVault(): @{FungibleToken.Vault} { + return <-create Vault(balance: 0.0) } } @@ -90,45 +127,93 @@ pub contract FlowToken: FungibleToken { // and store the returned Vault in their storage in order to allow their // account to be able to receive deposits of this token type. // - pub fun createEmptyVault(): @FungibleToken.Vault { + access(all) fun createEmptyVault(vaultType: Type): @FlowToken.Vault { return <-create Vault(balance: 0.0) } - pub resource Administrator { + /// Gets a list of the metadata views that this contract supports + access(all) view fun getContractViews(resourceType: Type?): [Type] { + return [Type(), + Type(), + Type(), + Type()] + } + + /// Get a Metadata View from FlowToken + /// + /// @param view: The Type of the desired view. + /// @return A structure representing the requested view. + /// + access(all) fun resolveContractView(resourceType: Type?, viewType: Type): AnyStruct? { + switch viewType { + case Type(): + return FungibleTokenMetadataViews.FTView( + ftDisplay: self.resolveContractView(resourceType: nil, viewType: Type()) as! FungibleTokenMetadataViews.FTDisplay?, + ftVaultData: self.resolveContractView(resourceType: nil, viewType: Type()) as! FungibleTokenMetadataViews.FTVaultData? + ) + case Type(): + let media = MetadataViews.Media( + file: MetadataViews.HTTPFile( + url: FlowToken.getLogoURI() + ), + mediaType: "image/svg+xml" + ) + let medias = MetadataViews.Medias([media]) + return FungibleTokenMetadataViews.FTDisplay( + name: "FLOW Network Token", + symbol: "FLOW", + description: "FLOW is the native token for the Flow blockchain. It is required for securing the network, transaction fees, storage fees, staking, FLIP voting and may be used by applications built on the Flow Blockchain", + externalURL: MetadataViews.ExternalURL("https://flow.com"), + logos: medias, + socials: { + "twitter": MetadataViews.ExternalURL("https://twitter.com/flow_blockchain") + } + ) + case Type(): + let vaultRef = FlowToken.account.storage.borrow(from: /storage/flowTokenVault) + ?? panic("Could not borrow reference to the contract's Vault!") + return FungibleTokenMetadataViews.FTVaultData( + storagePath: /storage/flowTokenVault, + receiverPath: /public/flowTokenReceiver, + metadataPath: /public/flowTokenBalance, + receiverLinkedType: Type<&{FungibleToken.Receiver, FungibleToken.Vault}>(), + metadataLinkedType: Type<&{FungibleToken.Balance, FungibleToken.Vault}>(), + createEmptyVaultFunction: (fun (): @{FungibleToken.Vault} { + return <-vaultRef.createEmptyVault() + }) + ) + case Type(): + return FungibleTokenMetadataViews.TotalSupply(totalSupply: FlowToken.totalSupply) + } + return nil + } + + access(all) resource Administrator { // createNewMinter // // Function that creates and returns a new minter resource // - pub fun createNewMinter(allowedAmount: UFix64): @Minter { + access(all) fun createNewMinter(allowedAmount: UFix64): @Minter { emit MinterCreated(allowedAmount: allowedAmount) return <-create Minter(allowedAmount: allowedAmount) } - - // createNewBurner - // - // Function that creates and returns a new burner resource - // - pub fun createNewBurner(): @Burner { - emit BurnerCreated() - return <-create Burner() - } } // Minter // // Resource object that token admin accounts can hold to mint new tokens. // - pub resource Minter { + access(all) resource Minter { // the amount of tokens that the minter is allowed to mint - pub var allowedAmount: UFix64 + access(all) var allowedAmount: UFix64 // mintTokens // // Function that mints new tokens, adds them to the total supply, // and returns them to the calling context. // - pub fun mintTokens(amount: UFix64): @FlowToken.Vault { + access(all) fun mintTokens(amount: UFix64): @FlowToken.Vault { pre { amount > UFix64(0): "Amount minted must be greater than zero" amount <= self.allowedAmount: "Amount minted must be less than the allowed amount" @@ -144,55 +229,34 @@ pub contract FlowToken: FungibleToken { } } - // Burner - // - // Resource object that token admin accounts can hold to burn tokens. - // - pub resource Burner { - - // burnTokens - // - // Function that destroys a Vault instance, effectively burning the tokens. - // - // Note: the burned tokens are automatically subtracted from the - // total supply in the Vault destructor. - // - pub fun burnTokens(from: @FungibleToken.Vault) { - let vault <- from as! @FlowToken.Vault - let amount = vault.balance - destroy vault - emit TokensBurned(amount: amount) - } + /// Gets the Flow Logo XML URI from storage + access(all) fun getLogoURI(): String { + return FlowToken.account.storage.copy(from: /storage/flowTokenLogoURI) ?? "" } - init(adminAccount: AuthAccount) { + init() { self.totalSupply = 0.0 // Create the Vault with the total supply of tokens and save it in storage // let vault <- create Vault(balance: self.totalSupply) - adminAccount.save(<-vault, to: /storage/flowTokenVault) + + self.account.storage.save(<-vault, to: /storage/flowTokenVault) // Create a public capability to the stored Vault that only exposes // the `deposit` method through the `Receiver` interface // - adminAccount.link<&FlowToken.Vault{FungibleToken.Receiver}>( - /public/flowTokenReceiver, - target: /storage/flowTokenVault - ) + let receiverCapability = self.account.capabilities.storage.issue<&FlowToken.Vault>(/storage/flowTokenVault) + self.account.capabilities.publish(receiverCapability, at: /public/flowTokenReceiver) // Create a public capability to the stored Vault that only exposes // the `balance` field through the `Balance` interface // - adminAccount.link<&FlowToken.Vault{FungibleToken.Balance}>( - /public/flowTokenBalance, - target: /storage/flowTokenVault - ) + let balanceCapability = self.account.capabilities.storage.issue<&FlowToken.Vault>(/storage/flowTokenVault) + self.account.capabilities.publish(balanceCapability, at: /public/flowTokenBalance) let admin <- create Administrator() - adminAccount.save(<-admin, to: /storage/flowTokenAdmin) + self.account.storage.save(<-admin, to: /storage/flowTokenAdmin) - // Emit an event that shows that the contract was initialized - emit TokensInitialized(initialSupply: self.totalSupply) } } \ No newline at end of file diff --git a/cadence/contracts/FungibleToken.cdc b/cadence/contracts/FungibleToken.cdc index eca3737..c731911 100644 --- a/cadence/contracts/FungibleToken.cdc +++ b/cadence/contracts/FungibleToken.cdc @@ -2,21 +2,18 @@ # The Flow Fungible Token standard -## `FungibleToken` contract interface +## `FungibleToken` contract -The interface that all fungible token contracts would have to conform to. -If a users wants to deploy a new token contract, their contract -would need to implement the FungibleToken interface. - -Their contract would have to follow all the rules and naming -that the interface specifies. +The Fungible Token standard is no longer an interface +that all fungible token contracts would have to conform to. -## `Vault` resource +If a users wants to deploy a new token contract, their contract +does not need to implement the FungibleToken interface, but their tokens +do need to implement the interfaces defined in this contract. -Each account that owns tokens would need to have an instance -of the Vault resource stored in their account storage. +## `Vault` resource interface -The Vault resource has methods that the owner and other users can call. +Each fungible token resource type needs to implement the `Vault` resource interface. ## `Provider`, `Receiver`, and `Balance` resource interfaces @@ -32,43 +29,40 @@ these interfaces to do various things with the tokens. For example, a faucet can be implemented by conforming to the Provider interface. -By using resources and interfaces, users of FungibleToken contracts -can send and receive tokens peer-to-peer, without having to interact -with a central ledger smart contract. To send tokens to another user, -a user would simply withdraw the tokens from their Vault, then call -the deposit function on another user's Vault to complete the transfer. - */ +import ViewResolver from "ViewResolver" +import Burner from "Burner" + /// FungibleToken /// -/// The interface that fungible token contracts implement. -/// -pub contract interface FungibleToken { +/// Fungible Token implementations are no longer required to implement the fungible token +/// interface. We still have it as an interface here because there are some useful +/// utility methods that many projects will still want to have on their contracts, +/// but they are by no means required. all that is required is that the token +/// implements the `Vault` interface +access(all) contract interface FungibleToken: ViewResolver { - /// The total number of tokens in existence. - /// It is up to the implementer to ensure that the total supply - /// stays accurate and up to date - /// - pub var totalSupply: UFix64 + // An entitlement for allowing the withdrawal of tokens from a Vault + access(all) entitlement Withdraw - /// TokensInitialized - /// - /// The event that is emitted when the contract is created - /// - pub event TokensInitialized(initialSupply: UFix64) - - /// TokensWithdrawn - /// /// The event that is emitted when tokens are withdrawn from a Vault - /// - pub event TokensWithdrawn(amount: UFix64, from: Address?) + access(all) event Withdrawn(type: String, amount: UFix64, from: Address?, fromUUID: UInt64, withdrawnUUID: UInt64) + + /// The event that is emitted when tokens are deposited to a Vault + access(all) event Deposited(type: String, amount: UFix64, to: Address?, toUUID: UInt64, depositedUUID: UInt64) + + /// Event that is emitted when the global burn method is called with a non-zero balance + access(all) event Burned(type: String, amount: UFix64, fromUUID: UInt64) - /// TokensDeposited + /// Balance /// - /// The event that is emitted when tokens are deposited into a Vault + /// The interface that provides standard functions\ + /// for getting balance information /// - pub event TokensDeposited(amount: UFix64, to: Address?) + access(all) resource interface Balance { + access(all) var balance: UFix64 + } /// Provider /// @@ -79,28 +73,31 @@ pub contract interface FungibleToken { /// because it leaves open the possibility of creating custom providers /// that do not necessarily need their own balance. /// - pub resource interface Provider { + access(all) resource interface Provider { + + /// Function to ask a provider if a specific amount of tokens + /// is available to be withdrawn + /// This could be useful to avoid panicing when calling withdraw + /// when the balance is unknown + /// Additionally, if the provider is pulling from multiple vaults + /// it only needs to check some of the vaults until the desired amount + /// is reached, potentially helping with performance. + /// + access(all) view fun isAvailableToWithdraw(amount: UFix64): Bool - /// withdraw subtracts tokens from the owner's Vault + /// withdraw subtracts tokens from the implementing resource /// and returns a Vault with the removed tokens. /// - /// The function's access level is public, but this is not a problem - /// because only the owner storing the resource in their account - /// can initially call this function. + /// The function's access level is `access(Withdraw)` + /// So in order to access it, one would either need the object itself + /// or an entitled reference with `Withdraw`. /// - /// The owner may grant other accounts access by creating a private - /// capability that allows specific other users to access - /// the provider resource through a reference. - /// - /// The owner may also grant all accounts access by creating a public - /// capability that allows all users to access the provider - /// resource through a reference. - /// - pub fun withdraw(amount: UFix64): @Vault { + access(Withdraw) fun withdraw(amount: UFix64): @{Vault} { post { // `result` refers to the return value result.balance == amount: "Withdrawal amount must be the same as the balance of the withdrawn Vault" + emit Withdrawn(type: self.getType().identifier, amount: amount, from: self.owner?.address, fromUUID: self.uuid, withdrawnUUID: result.uuid) } } } @@ -115,84 +112,115 @@ pub contract interface FungibleToken { /// can do custom things with the tokens, like split them up and /// send them to different places. /// - pub resource interface Receiver { + access(all) resource interface Receiver { /// deposit takes a Vault and deposits it into the implementing resource type /// - pub fun deposit(from: @Vault) + access(all) fun deposit(from: @{Vault}) + + /// getSupportedVaultTypes optionally returns a list of vault types that this receiver accepts + access(all) view fun getSupportedVaultTypes(): {Type: Bool} + + /// Returns whether or not the given type is accepted by the Receiver + /// A vault that can accept any type should just return true by default + access(all) view fun isSupportedVaultType(type: Type): Bool } - /// Balance + /// Vault /// - /// The interface that contains the `balance` field of the Vault - /// and enforces that when new Vaults are created, the balance - /// is initialized correctly. + /// Ideally, this interface would also conform to Receiver, Balance, Transferor, Provider, and Resolver + /// but that is not supported yet /// - pub resource interface Balance { + access(all) resource interface Vault: Receiver, Provider, Balance, ViewResolver.Resolver, Burner.Burnable { - /// The total balance of a vault - /// - pub var balance: UFix64 + /// Field that tracks the balance of a vault + access(all) var balance: UFix64 - init(balance: UFix64) { + /// Called when a fungible token is burned via the `Burner.burn()` method + /// Implementations can do any bookkeeping or emit any events + /// that should be emitted when a vault is destroyed. + /// Many implementations will want to update the token's total supply + /// to reflect that the tokens have been burned and removed from the supply. + /// Implementations also need to set the balance to zero before the end of the function + /// This is to prevent vault owners from spamming fake Burned events. + access(contract) fun burnCallback() { + pre { + emit Burned(type: self.getType().identifier, amount: self.balance, fromUUID: self.uuid) + } post { - self.balance == balance: - "Balance must be initialized to the initial balance" + self.balance == 0.0: "The balance must be set to zero during the burnCallback method so that it cannot be spammed" } } - } - /// Vault - /// - /// The resource that contains the functions to send and receive tokens. - /// - pub resource Vault: Provider, Receiver, Balance { - - // The declaration of a concrete type in a contract interface means that - // every Fungible Token contract that implements the FungibleToken interface - // must define a concrete `Vault` resource that conforms to the `Provider`, `Receiver`, - // and `Balance` interfaces, and declares their required fields and functions - - /// The total balance of the vault - /// - pub var balance: UFix64 + /// getSupportedVaultTypes optionally returns a list of vault types that this receiver accepts + /// The default implementation is included here because vaults are expected + /// to only accepted their own type, so they have no need to provide an implementation + /// for this function + access(all) view fun getSupportedVaultTypes(): {Type: Bool} { + // Below check is implemented to make sure that run-time type would + // only get returned when the parent resource conforms with `FungibleToken.Vault`. + if self.getType().isSubtype(of: Type<@{FungibleToken.Vault}>()) { + return {self.getType(): true} + } else { + // Return an empty dictionary as the default value for resource who don't + // implement `FungibleToken.Vault`, such as `FungibleTokenSwitchboard`, `TokenForwarder` etc. + return {} + } + } - // The conforming type must declare an initializer - // that allows prioviding the initial balance of the Vault - // - init(balance: UFix64) + /// Checks if the given type is supported by this Vault + access(all) view fun isSupportedVaultType(type: Type): Bool { + return self.getSupportedVaultTypes()[type] ?? false + } /// withdraw subtracts `amount` from the Vault's balance /// and returns a new Vault with the subtracted balance /// - pub fun withdraw(amount: UFix64): @Vault { + access(Withdraw) fun withdraw(amount: UFix64): @{Vault} { pre { self.balance >= amount: "Amount withdrawn must be less than or equal than the balance of the Vault" } post { + result.getType() == self.getType(): "Must return the same vault type as self" // use the special function `before` to get the value of the `balance` field // at the beginning of the function execution // self.balance == before(self.balance) - amount: - "New Vault balance must be the difference of the previous balance and the withdrawn Vault" + "New Vault balance must be the difference of the previous balance and the withdrawn Vault balance" } } /// deposit takes a Vault and adds its balance to the balance of this Vault /// - pub fun deposit(from: @Vault) { + access(all) fun deposit(from: @{FungibleToken.Vault}) { + // Assert that the concrete type of the deposited vault is the same + // as the vault that is accepting the deposit + pre { + from.isInstance(self.getType()): + "Cannot deposit an incompatible token type" + emit Deposited(type: from.getType().identifier, amount: from.balance, to: self.owner?.address, toUUID: self.uuid, depositedUUID: from.uuid) + } post { self.balance == before(self.balance) + before(from.balance): "New Vault balance must be the sum of the previous balance and the deposited Vault" } } + + /// createEmptyVault allows any user to create a new Vault that has a zero balance + /// + access(all) fun createEmptyVault(): @{Vault} { + post { + result.balance == 0.0: "The newly created Vault must have zero balance" + } + } } /// createEmptyVault allows any user to create a new Vault that has a zero balance /// - pub fun createEmptyVault(): @Vault { + access(all) fun createEmptyVault(vaultType: Type): @{FungibleToken.Vault} { post { + result.getType() == vaultType: "The returned vault does not match the desired type" result.balance == 0.0: "The newly created Vault must have zero balance" } } diff --git a/cadence/contracts/FungibleTokenMetadataViews.cdc b/cadence/contracts/FungibleTokenMetadataViews.cdc new file mode 100644 index 0000000..789d779 --- /dev/null +++ b/cadence/contracts/FungibleTokenMetadataViews.cdc @@ -0,0 +1,180 @@ +import FungibleToken from "FungibleToken" +import MetadataViews from "MetadataViews" +import ViewResolver from "ViewResolver" + +/// This contract implements the metadata standard proposed +/// in FLIP-1087. +/// +/// Ref: https://github.com/onflow/flips/blob/main/application/20220811-fungible-tokens-metadata.md +/// +/// Structs and resources can implement one or more +/// metadata types, called views. Each view type represents +/// a different kind of metadata. +/// +access(all) contract FungibleTokenMetadataViews { + + /// FTView wraps FTDisplay and FTVaultData, and is used to give a complete + /// picture of a Fungible Token. Most Fungible Token contracts should + /// implement this view. + /// + access(all) struct FTView { + access(all) let ftDisplay: FTDisplay? + access(all) let ftVaultData: FTVaultData? + view init( + ftDisplay: FTDisplay?, + ftVaultData: FTVaultData? + ) { + self.ftDisplay = ftDisplay + self.ftVaultData = ftVaultData + } + } + + /// Helper to get a FT view. + /// + /// @param viewResolver: A reference to the resolver resource + /// @return A FTView struct + /// + access(all) fun getFTView(viewResolver: &{ViewResolver.Resolver}): FTView { + let maybeFTView = viewResolver.resolveView(Type()) + if let ftView = maybeFTView { + return ftView as! FTView + } + return FTView( + ftDisplay: self.getFTDisplay(viewResolver), + ftVaultData: self.getFTVaultData(viewResolver) + ) + } + + /// View to expose the information needed to showcase this FT. + /// This can be used by applications to give an overview and + /// graphics of the FT. + /// + access(all) struct FTDisplay { + /// The display name for this token. + /// + /// Example: "Flow" + /// + access(all) let name: String + + /// The abbreviated symbol for this token. + /// + /// Example: "FLOW" + access(all) let symbol: String + + /// A description the provides an overview of this token. + /// + /// Example: "The FLOW token is the native currency of the Flow network." + access(all) let description: String + + /// External link to a URL to view more information about the fungible token. + access(all) let externalURL: MetadataViews.ExternalURL + + /// One or more versions of the fungible token logo. + access(all) let logos: MetadataViews.Medias + + /// Social links to reach the fungible token's social homepages. + /// Possible keys may be "instagram", "twitter", "discord", etc. + access(all) let socials: {String: MetadataViews.ExternalURL} + + view init( + name: String, + symbol: String, + description: String, + externalURL: MetadataViews.ExternalURL, + logos: MetadataViews.Medias, + socials: {String: MetadataViews.ExternalURL} + ) { + self.name = name + self.symbol = symbol + self.description = description + self.externalURL = externalURL + self.logos = logos + self.socials = socials + } + } + + /// Helper to get FTDisplay in a way that will return a typed optional. + /// + /// @param viewResolver: A reference to the resolver resource + /// @return An optional FTDisplay struct + /// + access(all) fun getFTDisplay(_ viewResolver: &{ViewResolver.Resolver}): FTDisplay? { + if let maybeDisplayView = viewResolver.resolveView(Type()) { + if let displayView = maybeDisplayView as? FTDisplay { + return displayView + } + } + return nil + } + + /// View to expose the information needed store and interact with a FT vault. + /// This can be used by applications to setup a FT vault with proper + /// storage and public capabilities. + /// + access(all) struct FTVaultData { + /// Path in storage where this FT vault is recommended to be stored. + access(all) let storagePath: StoragePath + + /// Public path which must be linked to expose the public receiver capability. + access(all) let receiverPath: PublicPath + + /// Public path which must be linked to expose the balance and resolver public capabilities. + access(all) let metadataPath: PublicPath + + /// Type that should be linked at the `receiverPath`. This is a restricted type requiring + /// the `FungibleToken.Receiver` interface. + access(all) let receiverLinkedType: Type + + /// Type that should be linked at the `receiverPath`. This is a restricted type requiring + /// the `ViewResolver.Resolver` interfaces. + access(all) let metadataLinkedType: Type + + /// Function that allows creation of an empty FT vault that is intended + /// to store the funds. + access(all) let createEmptyVault: fun(): @{FungibleToken.Vault} + + view init( + storagePath: StoragePath, + receiverPath: PublicPath, + metadataPath: PublicPath, + receiverLinkedType: Type, + metadataLinkedType: Type, + createEmptyVaultFunction: fun(): @{FungibleToken.Vault} + ) { + pre { + receiverLinkedType.isSubtype(of: Type<&{FungibleToken.Receiver}>()): "Receiver public type must include FungibleToken.Receiver." + metadataLinkedType.isSubtype(of: Type<&{FungibleToken.Vault}>()): "Metadata linked type must be a fungible token vault" + } + self.storagePath = storagePath + self.receiverPath = receiverPath + self.metadataPath = metadataPath + self.receiverLinkedType = receiverLinkedType + self.metadataLinkedType = metadataLinkedType + self.createEmptyVault = createEmptyVaultFunction + } + } + + /// Helper to get FTVaultData in a way that will return a typed Optional. + /// + /// @param viewResolver: A reference to the resolver resource + /// @return A optional FTVaultData struct + /// + access(all) fun getFTVaultData(_ viewResolver: &{ViewResolver.Resolver}): FTVaultData? { + if let view = viewResolver.resolveView(Type()) { + if let v = view as? FTVaultData { + return v + } + } + return nil + } + + /// View to expose the total supply of the Vault's token + access(all) struct TotalSupply { + access(all) let supply: UFix64 + + view init(totalSupply: UFix64) { + self.supply = totalSupply + } + } +} + \ No newline at end of file diff --git a/cadence/contracts/MetadataViews.cdc b/cadence/contracts/MetadataViews.cdc new file mode 100644 index 0000000..6b151d3 --- /dev/null +++ b/cadence/contracts/MetadataViews.cdc @@ -0,0 +1,706 @@ +import FungibleToken from "FungibleToken" +import NonFungibleToken from "NonFungibleToken" +import ViewResolver from "ViewResolver" + +/// This contract implements the metadata standard proposed +/// in FLIP-0636. +/// +/// Ref: https://github.com/onflow/flips/blob/main/application/20210916-nft-metadata.md +/// +/// Structs and resources can implement one or more +/// metadata types, called views. Each view type represents +/// a different kind of metadata, such as a creator biography +/// or a JPEG image file. +/// +access(all) contract MetadataViews { + + /// Display is a basic view that includes the name, description and + /// thumbnail for an object. Most objects should implement this view. + /// + access(all) struct Display { + + /// The name of the object. + /// + /// This field will be displayed in lists and therefore should + /// be short an concise. + /// + access(all) let name: String + + /// A written description of the object. + /// + /// This field will be displayed in a detailed view of the object, + /// so can be more verbose (e.g. a paragraph instead of a single line). + /// + access(all) let description: String + + /// A small thumbnail representation of the object. + /// + /// This field should be a web-friendly file (i.e JPEG, PNG) + /// that can be displayed in lists, link previews, etc. + /// + access(all) let thumbnail: {File} + + view init( + name: String, + description: String, + thumbnail: {File} + ) { + self.name = name + self.description = description + self.thumbnail = thumbnail + } + } + + /// Helper to get Display in a typesafe way + /// + /// @param viewResolver: A reference to the resolver resource + /// @return An optional Display struct + /// + access(all) fun getDisplay(_ viewResolver: &{ViewResolver.Resolver}) : Display? { + if let view = viewResolver.resolveView(Type()) { + if let v = view as? Display { + return v + } + } + return nil + } + + /// Generic interface that represents a file stored on or off chain. Files + /// can be used to references images, videos and other media. + /// + access(all) struct interface File { + access(all) view fun uri(): String + } + + /// View to expose a file that is accessible at an HTTP (or HTTPS) URL. + /// + access(all) struct HTTPFile: File { + access(all) let url: String + + view init(url: String) { + self.url = url + } + + access(all) view fun uri(): String { + return self.url + } + } + + /// View to expose a file stored on IPFS. + /// IPFS images are referenced by their content identifier (CID) + /// rather than a direct URI. A client application can use this CID + /// to find and load the image via an IPFS gateway. + /// + access(all) struct IPFSFile: File { + + /// CID is the content identifier for this IPFS file. + /// + /// Ref: https://docs.ipfs.io/concepts/content-addressing/ + /// + access(all) let cid: String + + /// Path is an optional path to the file resource in an IPFS directory. + /// + /// This field is only needed if the file is inside a directory. + /// + /// Ref: https://docs.ipfs.io/concepts/file-systems/ + /// + access(all) let path: String? + + view init(cid: String, path: String?) { + self.cid = cid + self.path = path + } + + /// This function returns the IPFS native URL for this file. + /// Ref: https://docs.ipfs.io/how-to/address-ipfs-on-web/#native-urls + /// + /// @return The string containing the file uri + /// + access(all) view fun uri(): String { + if let path = self.path { + return "ipfs://".concat(self.cid).concat("/").concat(path) + } + + return "ipfs://".concat(self.cid) + } + } + + /// View to represent a file with an correspoiding mediaType. + /// + access(all) struct Media { + + /// File for the media + /// + access(all) let file: {File} + + /// media-type comes on the form of type/subtype as described here + /// https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types + /// + access(all) let mediaType: String + + view init(file: {File}, mediaType: String) { + self.file=file + self.mediaType=mediaType + } + } + + /// Wrapper view for multiple media views + /// + access(all) struct Medias { + + /// An arbitrary-sized list for any number of Media items + access(all) let items: [Media] + + view init(_ items: [Media]) { + self.items = items + } + } + + /// Helper to get Medias in a typesafe way + /// + /// @param viewResolver: A reference to the resolver resource + /// @return A optional Medias struct + /// + access(all) fun getMedias(_ viewResolver: &{ViewResolver.Resolver}) : Medias? { + if let view = viewResolver.resolveView(Type()) { + if let v = view as? Medias { + return v + } + } + return nil + } + + /// View to represent a license according to https://spdx.org/licenses/ + /// This view can be used if the content of an NFT is licensed. + /// + access(all) struct License { + access(all) let spdxIdentifier: String + + view init(_ identifier: String) { + self.spdxIdentifier = identifier + } + } + + /// Helper to get License in a typesafe way + /// + /// @param viewResolver: A reference to the resolver resource + /// @return A optional License struct + /// + access(all) fun getLicense(_ viewResolver: &{ViewResolver.Resolver}) : License? { + if let view = viewResolver.resolveView(Type()) { + if let v = view as? License { + return v + } + } + return nil + } + + /// View to expose a URL to this item on an external site. + /// This can be used by applications like .find and Blocto to direct users + /// to the original link for an NFT or a project page that describes the NFT collection. + /// eg https://www.my-nft-project.com/overview-of-nft-collection + /// + access(all) struct ExternalURL { + access(all) let url: String + + view init(_ url: String) { + self.url=url + } + } + + /// Helper to get ExternalURL in a typesafe way + /// + /// @param viewResolver: A reference to the resolver resource + /// @return A optional ExternalURL struct + /// + access(all) fun getExternalURL(_ viewResolver: &{ViewResolver.Resolver}) : ExternalURL? { + if let view = viewResolver.resolveView(Type()) { + if let v = view as? ExternalURL { + return v + } + } + return nil + } + + /// View that defines the composable royalty standard that gives marketplaces a + /// unified interface to support NFT royalties. + /// + access(all) struct Royalty { + + /// Generic FungibleToken Receiver for the beneficiary of the royalty + /// Can get the concrete type of the receiver with receiver.getType() + /// Recommendation - Users should create a new link for a FlowToken + /// receiver for this using `getRoyaltyReceiverPublicPath()`, and not + /// use the default FlowToken receiver. This will allow users to update + /// the capability in the future to use a more generic capability + access(all) let receiver: Capability<&{FungibleToken.Receiver}> + + /// Multiplier used to calculate the amount of sale value transferred to + /// royalty receiver. Note - It should be between 0.0 and 1.0 + /// Ex - If the sale value is x and multiplier is 0.56 then the royalty + /// value would be 0.56 * x. + /// Generally percentage get represented in terms of basis points + /// in solidity based smart contracts while cadence offers `UFix64` + /// that already supports the basis points use case because its + /// operations are entirely deterministic integer operations and support + /// up to 8 points of precision. + access(all) let cut: UFix64 + + /// Optional description: This can be the cause of paying the royalty, + /// the relationship between the `wallet` and the NFT, or anything else + /// that the owner might want to specify. + access(all) let description: String + + view init(receiver: Capability<&{FungibleToken.Receiver}>, cut: UFix64, description: String) { + pre { + cut >= 0.0 && cut <= 1.0 : "Cut value should be in valid range i.e [0,1]" + } + self.receiver = receiver + self.cut = cut + self.description = description + } + } + + /// Wrapper view for multiple Royalty views. + /// Marketplaces can query this `Royalties` struct from NFTs + /// and are expected to pay royalties based on these specifications. + /// + access(all) struct Royalties { + + /// Array that tracks the individual royalties + access(self) let cutInfos: [Royalty] + + access(all) view init(_ cutInfos: [Royalty]) { + // Validate that sum of all cut multipliers should not be greater than 1.0 + var totalCut = 0.0 + for royalty in cutInfos { + totalCut = totalCut + royalty.cut + } + assert(totalCut <= 1.0, message: "Sum of cutInfos multipliers should not be greater than 1.0") + // Assign the cutInfos + self.cutInfos = cutInfos + } + + /// Return the cutInfos list + /// + /// @return An array containing all the royalties structs + /// + access(all) view fun getRoyalties(): [Royalty] { + return self.cutInfos + } + } + + /// Helper to get Royalties in a typesafe way + /// + /// @param viewResolver: A reference to the resolver resource + /// @return A optional Royalties struct + /// + access(all) fun getRoyalties(_ viewResolver: &{ViewResolver.Resolver}) : Royalties? { + if let view = viewResolver.resolveView(Type()) { + if let v = view as? Royalties { + return v + } + } + return nil + } + + /// Get the path that should be used for receiving royalties + /// This is a path that will eventually be used for a generic switchboard receiver, + /// hence the name but will only be used for royalties for now. + /// + /// @return The PublicPath for the generic FT receiver + /// + access(all) view fun getRoyaltyReceiverPublicPath(): PublicPath { + return /public/GenericFTReceiver + } + + /// View to represent a single field of metadata on an NFT. + /// This is used to get traits of individual key/value pairs along with some + /// contextualized data about the trait + /// + access(all) struct Trait { + // The name of the trait. Like Background, Eyes, Hair, etc. + access(all) let name: String + + // The underlying value of the trait, the rest of the fields of a trait provide context to the value. + access(all) let value: AnyStruct + + // displayType is used to show some context about what this name and value represent + // for instance, you could set value to a unix timestamp, and specify displayType as "Date" to tell + // platforms to consume this trait as a date and not a number + access(all) let displayType: String? + + // Rarity can also be used directly on an attribute. + // + // This is optional because not all attributes need to contribute to the NFT's rarity. + access(all) let rarity: Rarity? + + view init(name: String, value: AnyStruct, displayType: String?, rarity: Rarity?) { + self.name = name + self.value = value + self.displayType = displayType + self.rarity = rarity + } + } + + /// Wrapper view to return all the traits on an NFT. + /// This is used to return traits as individual key/value pairs along with + /// some contextualized data about each trait. + access(all) struct Traits { + access(all) let traits: [Trait] + + view init(_ traits: [Trait]) { + self.traits = traits + } + + /// Adds a single Trait to the Traits view + /// + /// @param Trait: The trait struct to be added + /// + access(all) fun addTrait(_ t: Trait) { + self.traits.append(t) + } + } + + /// Helper to get Traits view in a typesafe way + /// + /// @param viewResolver: A reference to the resolver resource + /// @return A optional Traits struct + /// + access(all) fun getTraits(_ viewResolver: &{ViewResolver.Resolver}) : Traits? { + if let view = viewResolver.resolveView(Type()) { + if let v = view as? Traits { + return v + } + } + return nil + } + + /// Helper function to easily convert a dictionary to traits. For NFT + /// collections that do not need either of the optional values of a Trait, + /// this method should suffice to give them an array of valid traits. + /// + /// @param dict: The dictionary to be converted to Traits + /// @param excludedNames: An optional String array specifying the `dict` + /// keys that are not wanted to become `Traits` + /// @return The generated Traits view + /// + access(all) fun dictToTraits(dict: {String: AnyStruct}, excludedNames: [String]?): Traits { + // Collection owners might not want all the fields in their metadata included. + // They might want to handle some specially, or they might just not want them included at all. + if excludedNames != nil { + for k in excludedNames! { + dict.remove(key: k) + } + } + + let traits: [Trait] = [] + for k in dict.keys { + let trait = Trait(name: k, value: dict[k]!, displayType: nil, rarity: nil) + traits.append(trait) + } + + return Traits(traits) + } + + /// Optional view for collections that issue multiple objects + /// with the same or similar metadata, for example an X of 100 set. This + /// information is useful for wallets and marketplaces. + /// An NFT might be part of multiple editions, which is why the edition + /// information is returned as an arbitrary sized array + /// + access(all) struct Edition { + + /// The name of the edition + /// For example, this could be Set, Play, Series, + /// or any other way a project could classify its editions + access(all) let name: String? + + /// The edition number of the object. + /// For an "24 of 100 (#24/100)" item, the number is 24. + access(all) let number: UInt64 + + /// The max edition number of this type of objects. + /// This field should only be provided for limited-editioned objects. + /// For an "24 of 100 (#24/100)" item, max is 100. + /// For an item with unlimited edition, max should be set to nil. + /// + access(all) let max: UInt64? + + view init(name: String?, number: UInt64, max: UInt64?) { + if max != nil { + assert(number <= max!, message: "The number cannot be greater than the max number!") + } + self.name = name + self.number = number + self.max = max + } + } + + /// Wrapper view for multiple Edition views + /// + access(all) struct Editions { + + /// An arbitrary-sized list for any number of editions + /// that the NFT might be a part of + access(all) let infoList: [Edition] + + view init(_ infoList: [Edition]) { + self.infoList = infoList + } + } + + /// Helper to get Editions in a typesafe way + /// + /// @param viewResolver: A reference to the resolver resource + /// @return An optional Editions struct + /// + access(all) fun getEditions(_ viewResolver: &{ViewResolver.Resolver}) : Editions? { + if let view = viewResolver.resolveView(Type()) { + if let v = view as? Editions { + return v + } + } + return nil + } + + /// View representing a project-defined serial number for a specific NFT + /// Projects have different definitions for what a serial number should be + /// Some may use the NFTs regular ID and some may use a different + /// classification system. The serial number is expected to be unique among + /// other NFTs within that project + /// + access(all) struct Serial { + access(all) let number: UInt64 + + view init(_ number: UInt64) { + self.number = number + } + } + + /// Helper to get Serial in a typesafe way + /// + /// @param viewResolver: A reference to the resolver resource + /// @return An optional Serial struct + /// + access(all) fun getSerial(_ viewResolver: &{ViewResolver.Resolver}) : Serial? { + if let view = viewResolver.resolveView(Type()) { + if let v = view as? Serial { + return v + } + } + return nil + } + + /// View to expose rarity information for a single rarity + /// Note that a rarity needs to have either score or description but it can + /// have both + /// + access(all) struct Rarity { + /// The score of the rarity as a number + access(all) let score: UFix64? + + /// The maximum value of score + access(all) let max: UFix64? + + /// The description of the rarity as a string. + /// + /// This could be Legendary, Epic, Rare, Uncommon, Common or any other string value + access(all) let description: String? + + view init(score: UFix64?, max: UFix64?, description: String?) { + if score == nil && description == nil { + panic("A Rarity needs to set score, description or both") + } + + self.score = score + self.max = max + self.description = description + } + } + + /// Helper to get Rarity view in a typesafe way + /// + /// @param viewResolver: A reference to the resolver resource + /// @return A optional Rarity struct + /// + access(all) fun getRarity(_ viewResolver: &{ViewResolver.Resolver}) : Rarity? { + if let view = viewResolver.resolveView(Type()) { + if let v = view as? Rarity { + return v + } + } + return nil + } + + /// NFTView wraps all Core views along `id` and `uuid` fields, and is used + /// to give a complete picture of an NFT. Most NFTs should implement this + /// view. + /// + access(all) struct NFTView { + access(all) let id: UInt64 + access(all) let uuid: UInt64 + access(all) let display: MetadataViews.Display? + access(all) let externalURL: MetadataViews.ExternalURL? + access(all) let collectionData: NFTCollectionData? + access(all) let collectionDisplay: NFTCollectionDisplay? + access(all) let royalties: Royalties? + access(all) let traits: Traits? + + view init( + id : UInt64, + uuid : UInt64, + display : MetadataViews.Display?, + externalURL : MetadataViews.ExternalURL?, + collectionData : NFTCollectionData?, + collectionDisplay : NFTCollectionDisplay?, + royalties : Royalties?, + traits: Traits? + ) { + self.id = id + self.uuid = uuid + self.display = display + self.externalURL = externalURL + self.collectionData = collectionData + self.collectionDisplay = collectionDisplay + self.royalties = royalties + self.traits = traits + } + } + + /// Helper to get an NFT view + /// + /// @param id: The NFT id + /// @param viewResolver: A reference to the resolver resource + /// @return A NFTView struct + /// + access(all) fun getNFTView(id: UInt64, viewResolver: &{ViewResolver.Resolver}) : NFTView { + let nftView = viewResolver.resolveView(Type()) + if nftView != nil { + return nftView! as! NFTView + } + + return NFTView( + id : id, + uuid: viewResolver.uuid, + display: MetadataViews.getDisplay(viewResolver), + externalURL : MetadataViews.getExternalURL(viewResolver), + collectionData : self.getNFTCollectionData(viewResolver), + collectionDisplay : self.getNFTCollectionDisplay(viewResolver), + royalties : self.getRoyalties(viewResolver), + traits : self.getTraits(viewResolver) + ) + } + + /// View to expose the information needed store and retrieve an NFT. + /// This can be used by applications to setup a NFT collection with proper + /// storage and public capabilities. + /// + access(all) struct NFTCollectionData { + /// Path in storage where this NFT is recommended to be stored. + access(all) let storagePath: StoragePath + + /// Public path which must be linked to expose public capabilities of this NFT + /// including standard NFT interfaces and metadataviews interfaces + access(all) let publicPath: PublicPath + + /// The concrete type of the collection that is exposed to the public + /// now that entitlements exist, it no longer needs to be restricted to a specific interface + access(all) let publicCollection: Type + + /// Type that should be linked at the aforementioned public path + access(all) let publicLinkedType: Type + + /// Function that allows creation of an empty NFT collection that is intended to store + /// this NFT. + access(all) let createEmptyCollection: fun(): @{NonFungibleToken.Collection} + + view init( + storagePath: StoragePath, + publicPath: PublicPath, + publicCollection: Type, + publicLinkedType: Type, + createEmptyCollectionFunction: fun(): @{NonFungibleToken.Collection} + ) { + pre { + publicLinkedType.isSubtype(of: Type<&{NonFungibleToken.Collection}>()): "Public type must be a subtype of NonFungibleToken.Collection interface." + } + self.storagePath=storagePath + self.publicPath=publicPath + self.publicCollection=publicCollection + self.publicLinkedType=publicLinkedType + self.createEmptyCollection=createEmptyCollectionFunction + } + } + + /// Helper to get NFTCollectionData in a way that will return an typed Optional + /// + /// @param viewResolver: A reference to the resolver resource + /// @return A optional NFTCollectionData struct + /// + access(all) fun getNFTCollectionData(_ viewResolver: &{ViewResolver.Resolver}) : NFTCollectionData? { + if let view = viewResolver.resolveView(Type()) { + if let v = view as? NFTCollectionData { + return v + } + } + return nil + } + + /// View to expose the information needed to showcase this NFT's + /// collection. This can be used by applications to give an overview and + /// graphics of the NFT collection this NFT belongs to. + /// + access(all) struct NFTCollectionDisplay { + // Name that should be used when displaying this NFT collection. + access(all) let name: String + + // Description that should be used to give an overview of this collection. + access(all) let description: String + + // External link to a URL to view more information about this collection. + access(all) let externalURL: MetadataViews.ExternalURL + + // Square-sized image to represent this collection. + access(all) let squareImage: MetadataViews.Media + + // Banner-sized image for this collection, recommended to have a size near 1200x630. + access(all) let bannerImage: MetadataViews.Media + + // Social links to reach this collection's social homepages. + // Possible keys may be "instagram", "twitter", "discord", etc. + access(all) let socials: {String: MetadataViews.ExternalURL} + + view init( + name: String, + description: String, + externalURL: MetadataViews.ExternalURL, + squareImage: MetadataViews.Media, + bannerImage: MetadataViews.Media, + socials: {String: MetadataViews.ExternalURL} + ) { + self.name = name + self.description = description + self.externalURL = externalURL + self.squareImage = squareImage + self.bannerImage = bannerImage + self.socials = socials + } + } + + /// Helper to get NFTCollectionDisplay in a way that will return a typed + /// Optional + /// + /// @param viewResolver: A reference to the resolver resource + /// @return A optional NFTCollection struct + /// + access(all) fun getNFTCollectionDisplay(_ viewResolver: &{ViewResolver.Resolver}) : NFTCollectionDisplay? { + if let view = viewResolver.resolveView(Type()) { + if let v = view as? NFTCollectionDisplay { + return v + } + } + return nil + } +} \ No newline at end of file diff --git a/cadence/contracts/NonFungibleToken.cdc b/cadence/contracts/NonFungibleToken.cdc new file mode 100644 index 0000000..ab12116 --- /dev/null +++ b/cadence/contracts/NonFungibleToken.cdc @@ -0,0 +1,234 @@ +/** + +## The Flow Non-Fungible Token standard + +## `NonFungibleToken` contract + +The interface that all Non-Fungible Token contracts should conform to. +If a user wants to deploy a new NFT contract, their contract should implement +The types defined here + +## `NFT` resource interface + +The core resource type that represents an NFT in the smart contract. + +## `Collection` Resource interface + +The resource that stores a user's NFT collection. +It includes a few functions to allow the owner to easily +move tokens in and out of the collection. + +## `Provider` and `Receiver` resource interfaces + +These interfaces declare functions with some pre and post conditions +that require the Collection to follow certain naming and behavior standards. + +They are separate because it gives developers the ability to define functions +that can use any type that implements these interfaces + +By using resources and interfaces, users of NFT smart contracts can send +and receive tokens peer-to-peer, without having to interact with a central ledger +smart contract. + +To send an NFT to another user, a user would simply withdraw the NFT +from their Collection, then call the deposit function on another user's +Collection to complete the transfer. + +*/ + +import ViewResolver from "ViewResolver" + +/// The main NFT contract. Other NFT contracts will +/// import and implement the interfaces defined in this contract +/// +access(all) contract interface NonFungibleToken: ViewResolver { + + /// An entitlement for allowing the withdrawal of tokens from a Vault + access(all) entitlement Withdraw + + /// An entitlement for allowing updates and update events for an NFT + access(all) entitlement Update + + /// entitlement for owner that grants Withdraw and Update + access(all) entitlement Owner + + /// Event that contracts should emit when the metadata of an NFT is updated + /// It can only be emitted by calling the `emitNFTUpdated` function + /// with an `Updatable` entitled reference to the NFT that was updated + /// The entitlement prevents spammers from calling this from other users' collections + /// because only code within a collection or that has special entitled access + /// to the collections methods will be able to get the entitled reference + /// + /// The event makes it so that third-party indexers can monitor the events + /// and query the updated metadata from the owners' collections. + /// + access(all) event Updated(type: String, id: UInt64, uuid: UInt64, owner: Address?) + access(contract) view fun emitNFTUpdated(_ nftRef: auth(Update | Owner) &{NonFungibleToken.NFT}) + { + emit Updated(type: nftRef.getType().identifier, id: nftRef.id, uuid: nftRef.uuid, owner: nftRef.owner?.address) + } + + + /// Event that is emitted when a token is withdrawn, + /// indicating the type, id, uuid, the owner of the collection that it was withdrawn from, + /// and the UUID of the resource it was withdrawn from, usually a collection. + /// + /// If the collection is not in an account's storage, `from` will be `nil`. + /// + access(all) event Withdrawn(type: String, id: UInt64, uuid: UInt64, from: Address?, providerUUID: UInt64) + + /// Event that emitted when a token is deposited to a collection. + /// Indicates the type, id, uuid, the owner of the collection that it was deposited to, + /// and the UUID of the collection it was deposited to + /// + /// If the collection is not in an account's storage, `from`, will be `nil`. + /// + access(all) event Deposited(type: String, id: UInt64, uuid: UInt64, to: Address?, collectionUUID: UInt64) + + /// Included for backwards-compatibility + access(all) resource interface INFT: NFT {} + + /// Interface that the NFTs must conform to + /// + access(all) resource interface NFT: ViewResolver.Resolver { + + /// unique ID for the NFT + access(all) let id: UInt64 + + /// Event that is emitted automatically every time a resource is destroyed + /// The type information is included in the metadata event so it is not needed as an argument + access(all) event ResourceDestroyed(id: UInt64 = self.id, uuid: UInt64 = self.uuid) + + /// createEmptyCollection creates an empty Collection that is able to store the NFT + /// and returns it to the caller so that they can own NFTs + /// @return A an empty collection that can store this NFT + access(all) fun createEmptyCollection(): @{Collection} { + post { + result.getLength() == 0: "The created collection must be empty!" + } + } + + /// Gets all the NFTs that this NFT directly owns + /// @return A dictionary of all subNFTS keyed by type + access(all) view fun getAvailableSubNFTS(): {Type: UInt64} { + return {} + } + + /// Get a reference to an NFT that this NFT owns + /// Both arguments are optional to allow the NFT to choose + /// how it returns sub NFTs depending on what arguments are provided + /// For example, if `type` has a value, but `id` doesn't, the NFT + /// can choose which NFT of that type to return if there is a "default" + /// If both are `nil`, then NFTs that only store a single NFT can just return + /// that. This helps callers who aren't sure what they are looking for + /// + /// @param type: The Type of the desired NFT + /// @param id: The id of the NFT to borrow + /// + /// @return A structure representing the requested view. + access(all) fun getSubNFT(type: Type, id: UInt64) : &{NonFungibleToken.NFT}? { + return nil + } + } + + /// Interface to mediate withdrawals from a resource, usually a Collection + /// + access(all) resource interface Provider { + + // We emit withdraw events from the provider interface because conficting withdraw + // events aren't as confusing to event listeners as conflicting deposit events + + /// withdraw removes an NFT from the collection and moves it to the caller + /// It does not specify whether the ID is UUID or not + /// @param withdrawID: The id of the NFT to withdraw from the collection + access(Withdraw | Owner) fun withdraw(withdrawID: UInt64): @{NFT} { + post { + result.id == withdrawID: "The ID of the withdrawn token must be the same as the requested ID" + emit Withdrawn(type: result.getType().identifier, id: result.id, uuid: result.uuid, from: self.owner?.address, providerUUID: self.uuid) + } + } + } + + /// Interface to mediate deposits to the Collection + /// + access(all) resource interface Receiver { + + /// deposit takes an NFT as an argument and adds it to the Collection + /// @param token: The NFT to deposit + access(all) fun deposit(token: @{NFT}) + + /// getSupportedNFTTypes returns a list of NFT types that this receiver accepts + /// @return A dictionary of types mapped to booleans indicating if this + /// reciever supports it + access(all) view fun getSupportedNFTTypes(): {Type: Bool} + + /// Returns whether or not the given type is accepted by the collection + /// A collection that can accept any type should just return true by default + /// @param type: An NFT type + /// @return A boolean indicating if this receiver can recieve the desired NFT type + access(all) view fun isSupportedNFTType(type: Type): Bool + } + + /// Kept for backwards-compatibility reasons + access(all) resource interface CollectionPublic { + access(all) fun deposit(token: @{NFT}) + access(all) view fun getLength(): Int + access(all) view fun getIDs(): [UInt64] + access(all) view fun borrowNFT(_ id: UInt64): &{NFT}? + } + + /// Requirement for the concrete resource type + /// to be declared in the implementing contract + /// + access(all) resource interface Collection: Provider, Receiver, CollectionPublic, ViewResolver.ResolverCollection { + + /// deposit takes a NFT as an argument and stores it in the collection + /// @param token: The NFT to deposit into the collection + access(all) fun deposit(token: @{NonFungibleToken.NFT}) { + pre { + // We emit the deposit event in the `Collection` interface + // because the `Collection` interface is almost always the final destination + // of tokens and deposit emissions from custom receivers could be confusing + // and hard to reconcile to event listeners + emit Deposited(type: token.getType().identifier, id: token.id, uuid: token.uuid, to: self.owner?.address, collectionUUID: self.uuid) + } + } + + /// Gets the amount of NFTs stored in the collection + /// @return An integer indicating the size of the collection + access(all) view fun getLength(): Int + + /// Borrows a reference to an NFT stored in the collection + /// If the NFT with the specified ID is not in the collection, + /// the function should return `nil` and not panic. + /// + /// @param id: The desired nft id in the collection to return a referece for. + /// @return An optional reference to the NFT + access(all) view fun borrowNFT(_ id: UInt64): &{NonFungibleToken.NFT}? { + post { + (result == nil) || (result?.id == id): + "Cannot borrow NFT reference: The ID of the returned reference does not match the ID that was specified" + } + } + + /// createEmptyCollection creates an empty Collection of the same type + /// and returns it to the caller + /// @return A an empty collection of the same type + access(all) fun createEmptyCollection(): @{Collection} { + post { + result.getType() == self.getType(): "The created collection does not have the same type as this collection" + result.getLength() == 0: "The created collection must be empty!" + } + } + } + + /// createEmptyCollection creates an empty Collection for the specified NFT type + /// and returns it to the caller so that they can own NFTs + /// @param nftType: The desired nft type to return a collection for. + /// @return An array of NFT Types that the implementing contract defines. + access(all) fun createEmptyCollection(nftType: Type): @{NonFungibleToken.Collection} { + post { + result.getIDs().length == 0: "The created collection must be empty!" + } + } +} \ No newline at end of file diff --git a/cadence/contracts/ViewResolver.cdc b/cadence/contracts/ViewResolver.cdc new file mode 100644 index 0000000..862a4e8 --- /dev/null +++ b/cadence/contracts/ViewResolver.cdc @@ -0,0 +1,59 @@ +// Taken from the NFT Metadata standard, this contract exposes an interface to let +// anyone borrow a contract and resolve views on it. +// +// This will allow you to obtain information about a contract without necessarily knowing anything about it. +// All you need is its address and name and you're good to go! +access(all) contract interface ViewResolver { + + /// Function that returns all the Metadata Views implemented by the resolving contract. + /// Some contracts may have multiple resource types that support metadata views + /// so there there is an optional parameter for specify which resource type the caller + /// is looking for views for. + /// Some contract-level views may be type-agnostic. In that case, the contract + /// should return the same views regardless of what type is passed in. + /// + /// @param resourceType: An optional resource type to return views for + /// @return An array of Types defining the implemented views. This value will be used by + /// developers to know which parameter to pass to the resolveView() method. + /// + access(all) view fun getContractViews(resourceType: Type?): [Type] + + /// Function that resolves a metadata view for this token. + /// Some contracts may have multiple resource types that support metadata views + /// so there there is an optional parameter for specify which resource type the caller + /// is looking for views for. + /// Some contract-level views may be type-agnostic. In that case, the contract + /// should return the same views regardless of what type is passed in. + /// + /// @param resourceType: An optional resource type to return views for + /// @param view: The Type of the desired view. + /// @return A structure representing the requested view. + /// + access(all) fun resolveContractView(resourceType: Type?, viewType: Type): AnyStruct? + + /// Provides access to a set of metadata views. A struct or + /// resource (e.g. an NFT) can implement this interface to provide access to + /// the views that it supports. + /// + access(all) resource interface Resolver { + + /// Same as getViews above, but on a specific NFT instead of a contract + access(all) view fun getViews(): [Type] + + /// Same as resolveView above, but on a specific NFT instead of a contract + access(all) fun resolveView(_ view: Type): AnyStruct? + } + + /// A group of view resolvers indexed by ID. + /// + access(all) resource interface ResolverCollection { + access(all) view fun borrowViewResolver(id: UInt64): &{Resolver}? { + return nil + } + + access(all) view fun getIDs(): [UInt64] { + return [] + } + } +} + \ No newline at end of file diff --git a/flow.json b/flow.json index d81dbf6..36ba579 100644 --- a/flow.json +++ b/flow.json @@ -6,6 +6,12 @@ } }, "contracts": { + "Burner": { + "source": "./cadence/contracts/Burner.cdc", + "aliases": { + "emulator": "0xee82856bf20e2aa6" + } + }, "FUSD": { "source": "./cadence/contracts/FUSD.cdc", "aliases": { @@ -18,12 +24,40 @@ "emulator": "0xee82856bf20e2aa6", "testnet": "0x9a0766d93b6608b7" } + }, + "MetadataViews": { + "source": "./cadence/contracts/MetadataViews.cdc", + "aliases": { + "emulator": "f8d6e0586b0a20c7", + "testnet": "631e88ae7f1d7c20" + } + }, + "NonFungibleToken": { + "source": "./cadence/contracts/NonFungibleToken.cdc", + "aliases": { + "emulator": "f8d6e0586b0a20c7", + "testnet": "631e88ae7f1d7c20" + } + }, + "ViewResolver": { + "source": "./cadence/contracts/ViewResolver.cdc", + "aliases": { + "emulator": "f8d6e0586b0a20c7", + "testnet": "631e88ae7f1d7c20" + } + }, + "FungibleTokenMetadataViews": { + "source": "./cadence/contracts/FungibleTokenMetadataViews.cdc", + "aliases": { + "emulator": "f8d6e0586b0a20c7", + "testnet": "0xf233dcee88fe0abe" + } } }, "networks": { "emulator": "127.0.0.1:3569", "testnet": "access.devnet.nodes.onflow.org:9000", - "crescendo": "access.crescendo.nodes.onflow.org:9000" + "crescendo": "access.crescendo.nodes.onflow.org:9000" }, "accounts": { "emulator-account": { @@ -37,7 +71,11 @@ }, "deployments": { "emulator": { - "emulator-account": ["FUSD"] + "emulator-account": [ + "FungibleTokenMetadataViews", + "Burner", + "FUSD" + ] } } -} +} \ No newline at end of file From ae508ed4eae085817e331ddf2c0f4e08201c5b26 Mon Sep 17 00:00:00 2001 From: Giovanni Sanchez <108043524+sisyphusSmiling@users.noreply.github.com> Date: Tue, 13 Feb 2024 16:17:46 -0600 Subject: [PATCH 02/29] add EVM contract --- cadence/contracts/EVM.cdc | 160 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 160 insertions(+) create mode 100644 cadence/contracts/EVM.cdc diff --git a/cadence/contracts/EVM.cdc b/cadence/contracts/EVM.cdc new file mode 100644 index 0000000..32bb949 --- /dev/null +++ b/cadence/contracts/EVM.cdc @@ -0,0 +1,160 @@ +import "FlowToken" + +access(all) +contract EVM { + + /// EVMAddress is an EVM-compatible address + access(all) + struct EVMAddress { + + /// Bytes of the address + access(all) + let bytes: [UInt8; 20] + + /// Constructs a new EVM address from the given byte representation + init(bytes: [UInt8; 20]) { + 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) + } + } + + access(all) + struct Balance { + + /// The balance in FLOW + access(all) + let flow: UFix64 + + /// Constructs a new balance, given the balance in FLOW + init(flow: UFix64) { + self.flow = flow + } + + // 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 BridgedAccount { + + access(self) + let addressBytes: [UInt8; 20] + + init(addressBytes: [UInt8; 20]) { + self.addressBytes = addressBytes + } + + /// The EVM address of the bridged account + access(all) + fun address(): EVMAddress { + // Always create a new EVMAddress instance + return EVMAddress(bytes: self.addressBytes) + } + + /// Get balance of the bridged account + access(all) + fun balance(): Balance { + return self.address().balance() + } + + /// Deposits the given vault into the bridged account's balance + access(all) + fun deposit(from: @FlowToken.Vault) { + self.address().deposit(from: <-from) + } + + /// Withdraws the balance from the bridged account's balance + access(all) + fun withdraw(balance: Balance): @FlowToken.Vault { + let vault <- InternalEVM.withdraw( + from: self.addressBytes, + amount: balance.flow + ) as! @FlowToken.Vault + return <-vault + } + + /// Deploys a contract to the EVM environment. + /// Returns the address of the newly deployed contract + access(all) + fun deploy( + code: [UInt8], + gasLimit: UInt64, + value: Balance + ): EVMAddress { + let addressBytes = InternalEVM.deploy( + from: self.addressBytes, + code: code, + gasLimit: gasLimit, + value: value.flow + ) + return EVMAddress(bytes: addressBytes) + } + + /// Calls a function with the given data. + /// The execution is limited by the given amount of gas + access(all) + fun call( + to: EVMAddress, + data: [UInt8], + gasLimit: UInt64, + value: Balance + ): [UInt8] { + return InternalEVM.call( + from: self.addressBytes, + to: to.bytes, + data: data, + gasLimit: gasLimit, + value: value.flow + ) + } + } + + /// Creates a new bridged account + access(all) + fun createBridgedAccount(): @BridgedAccount { + return <-create BridgedAccount( + addressBytes: InternalEVM.createBridgedAccount() + ) + } + + /// 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) + } + + access(all) + fun encodeABI(_ values: [AnyStruct]): [UInt8] { + return InternalEVM.encodeABI(values) + } + + access(all) + fun decodeABI(types: [Type], data: [UInt8]): [AnyStruct] { + return InternalEVM.decodeABI(types: types, data: data) + } +} \ No newline at end of file From 42be8c3f4caf3390062732b1a19a2e734b0d0445 Mon Sep 17 00:00:00 2001 From: Giovanni Sanchez <108043524+sisyphusSmiling@users.noreply.github.com> Date: Tue, 13 Feb 2024 16:42:41 -0600 Subject: [PATCH 03/29] update FUSD for emulator FungibleToken conformance --- cadence/contracts/FUSD.cdc | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/cadence/contracts/FUSD.cdc b/cadence/contracts/FUSD.cdc index 3bd2e29..31e8874 100644 --- a/cadence/contracts/FUSD.cdc +++ b/cadence/contracts/FUSD.cdc @@ -3,7 +3,7 @@ import MetadataViews from "MetadataViews" import FungibleTokenMetadataViews from "FungibleTokenMetadataViews" import ViewResolver from "ViewResolver" -access(all) contract FUSD: FungibleToken { +access(all) contract FUSD { access(all) entitlement ProxyOwner @@ -75,6 +75,19 @@ access(all) contract FUSD: FungibleToken { self.balance = balance } + // TODO: REMOVE BELOW + // BEGIN OLD INTERFACE METHODS + access(all) view fun getBalance(): UFix64 { + return self.balance + } + access(all) view fun getDefaultStoragePath(): StoragePath? { + return /storage/fusdVault + } + access(all) view fun getDefaultPublicPath(): PublicPath? { + return /public/fusdReceiver + } + // END OLD INTERFACE METHODS + /// Called when a fungible token is burned via the `Burner.burn()` method access(contract) fun burnCallback() { if self.balance > 0.0 { @@ -116,7 +129,7 @@ access(all) contract FUSD: FungibleToken { // created Vault to the context that called so it can be deposited // elsewhere. // - access(FungibleToken.Withdraw) fun withdraw(amount: UFix64): @{FungibleToken.Vault} { + access(FungibleToken.Withdrawable) fun withdraw(amount: UFix64): @{FungibleToken.Vault} { self.balance = self.balance - amount emit TokensWithdrawn(amount: amount, from: self.owner?.address) return <-create Vault(balance: amount) From 12fb1c7133a0f655a933e86d47875ff1106356a1 Mon Sep 17 00:00:00 2001 From: Giovanni Sanchez <108043524+sisyphusSmiling@users.noreply.github.com> Date: Tue, 13 Feb 2024 18:06:50 -0600 Subject: [PATCH 04/29] add FlowPassthrough.sol contract --- solidity/src/FlowPassthrough.sol | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 solidity/src/FlowPassthrough.sol diff --git a/solidity/src/FlowPassthrough.sol b/solidity/src/FlowPassthrough.sol new file mode 100644 index 0000000..ef6799c --- /dev/null +++ b/solidity/src/FlowPassthrough.sol @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: GPL-3.0 + +pragma solidity >=0.7.0 <0.9.0; + +contract FlowPassthrough { + + event Tranfered(address from, address to, uint256 amount); + + function transferFlow(address payable _to) public payable { + (bool sent, bytes memory data) = _to.call{value: msg.value}(""); + require(sent, "Failed to send Flow"); + emit Tranfered(msg.sender, _to, msg.value); + } +} \ No newline at end of file From fa585cf2f31118af12028f0aece3f49f739217e0 Mon Sep 17 00:00:00 2001 From: Giovanni Sanchez <108043524+sisyphusSmiling@users.noreply.github.com> Date: Tue, 13 Feb 2024 18:07:52 -0600 Subject: [PATCH 05/29] add FlowEVMPassthrough.cdc contract & update flow.json --- cadence/contracts/FlowEVMPassthrough.cdc | 153 ++++++++++++++++++++ flow.json | 177 +++++++++++++---------- 2 files changed, 255 insertions(+), 75 deletions(-) create mode 100644 cadence/contracts/FlowEVMPassthrough.cdc diff --git a/cadence/contracts/FlowEVMPassthrough.cdc b/cadence/contracts/FlowEVMPassthrough.cdc new file mode 100644 index 0000000..8ca2352 --- /dev/null +++ b/cadence/contracts/FlowEVMPassthrough.cdc @@ -0,0 +1,153 @@ +import "FungibleToken" +import "FlowToken" + +import "EVM" + +/// This contract defines a resource implementing FungibleToken Receiver and Provider interfaces that enables deposits +/// to an encapsulated COA and transfers to EVM addresses. Recipients in Flow EVM will receive the transfer from the +/// FlowPassthrough.sol contract address, but an event is emitted in both the Cadence & EVM environments denoting the +/// source and target addresses involved in the passthrough transfer. +/// +access(all) contract FlowEVMPassthrough { + + /// The address of the passthrough Solidity contract + access(all) let passthroughEVMAddress: EVM.EVMAddress + + /// Default storage path for the Passthrough resource + access(all) let StoragePath: StoragePath + /// Default public path for the Passthrough resource + access(all) let PublicPath: PublicPath + + /// Event emitted when a transfer occurs, denoting the transferor, recipient and amount transfered via passthrough + access(all) event Transfered(from: EVM.EVMAddress, to: EVM.EVMAddress, amount: UFix64) + + /* --- Public Methods --- */ + /// + access(all) fun createPassthrough(with coa: Capability<&EVM.BridgedAccount>): @Passthrough { + return <-create Passthrough(coa) + } + + /* --- Passthrough --- */ + // + /// Encapsulates a COA and provides a FungibleToken Receiver and Provider interface through which to deposit & + /// withdraw to/from the COA. Also enables transfers to EVM addresses using a call to a passthrough Solidity + /// contract. + /// + access(all) resource Passthrough : FungibleToken.Receiver, FungibleToken.Provider { + /// Capability to owner's COA + access(self) let coaCapability: Capability<&EVM.BridgedAccount> + + init(_ coaCapability: Capability<&EVM.BridgedAccount>) { + pre { + coaCapability.check(): "Invalid COA Capability provided" + } + self.coaCapability = coaCapability + } + + /// getSupportedVaultTypes optionally returns a list of vault types that this receiver accepts + access(all) view fun getSupportedVaultTypes(): {Type: Bool} { + return { Type<@FlowToken.Vault>(): true } + } + + /// Returns whether or not the given type is accepted by the Receiver + /// A vault that can accept any type should just return true by default + access(all) view fun isSupportedVaultType(type: Type): Bool { + return self.getSupportedVaultTypes()[type] ?? false + } + + /// Deposits Flow to encapsulated COA according to Receiver interface + access(all) fun deposit(from: @{FungibleToken.Vault}) { + pre { + from.getType() == Type<@FlowToken.Vault>(): "Passthrough only supports FlowToken.Vaults" + } + let flowVault <- from as! @FlowToken.Vault + self.borrowCOA().deposit(from: <-flowVault) + } + + access(FungibleToken.Withdrawable) fun withdraw(amount: UFix64): @{FungibleToken.Vault} { + return <-self.borrowCOA().withdraw( + balance: EVM.Balance(flow: amount) + ) + } + + /// Deposits Flow to defined EVM Address using a call to passthrough Solidity contract + access(all) fun evmTransfer(from: @{FungibleToken.Vault}, to: EVM.EVMAddress, gasLimit: UInt64) { + pre { + from.getType() == Type<@FlowToken.Vault>(): "Passthrough only supports FlowToken.Vaults" + } + let amount = from.getBalance() + // TODO: Replace with UInt256(EVM.Balance(flow: from.balance).toAttoFlow()) + let amountInAttoFlow = FlowEVMPassthrough.ufix64ToUInt256(value: from.getBalance(), decimals: 8) + // TODO: Replace with EVM.encodeABIWithSignature("transferFlow(address)", [to]) + let calldata = FlowEVMPassthrough.encodeABIWithSignature( + "transferFlow(address)", + [to] + ) + let coa = self.borrowCOA() + let flowVault <- from as! @FlowToken.Vault + coa.deposit(from: <-flowVault) + coa.call( + to: FlowEVMPassthrough.passthroughEVMAddress, + data: calldata, + gasLimit: gasLimit, + value: EVM.Balance(flow: amount) + ) + + emit Transfered(from: self.borrowCOA().address(), to: to, amount: amount) + } + + access(self) fun borrowCOA(): &EVM.BridgedAccount { + return self.coaCapability.borrow() ?? panic("COA Capability has been revoked and is no longer valid") + } + } + + /* --- Internal Helpers --- */ + // TODO: Remove helpers once EVM.Balance.toAttoFlow() is implemented + + /// Raises the base to the power of the exponent + access(self) view fun pow(base: UInt256, exponent: UInt8): UInt256 { + if exponent == 0 { + return 1 + } + var r = base + var exp: UInt8 = 1 + while exp < exponent { + r = r * base + exp = exp + 1 + } + return r + } + + /// Converts a UFix64 to a UInt256 + access(self) view fun ufix64ToUInt256(value: UFix64, decimals: UInt8): UInt256 { + let integerPart: UInt64 = UInt64(value) + var r = UInt256(integerPart) + var multiplier: UInt256 = self.pow(base:10, exponent: decimals) + return r * multiplier + } + + access(self) fun encodeABIWithSignature( + _ signature: String, + _ values: [AnyStruct] + ): [UInt8] { + let methodID = HashAlgorithm.KECCAK_256.hash( + signature.utf8 + ).slice(from: 0, upTo: 4) + let arguments = EVM.encodeABI(values) + + return methodID.concat(arguments) + } + + init(passthroughBytecode: String) { + let coa = self.account.storage.borrow<&EVM.BridgedAccount>(from: /storage/evm) + ?? panic("COA not found") + let passthroughEVMAddress = coa.deploy( + code: passthroughBytecode.decodeHex(), + gasLimit: 12000000, + value: EVM.Balance(flow: 0.0) + ) + self.passthroughEVMAddress = passthroughEVMAddress + self.StoragePath = /storage/flowEVMPassthrough + self.PublicPath = /public/flowEVMPassthroughPublic + } +} diff --git a/flow.json b/flow.json index 36ba579..0a218e6 100644 --- a/flow.json +++ b/flow.json @@ -1,81 +1,108 @@ { - "emulators": { - "default": { - "port": 3569, - "serviceAccount": "emulator-account" - } - }, - "contracts": { - "Burner": { - "source": "./cadence/contracts/Burner.cdc", - "aliases": { - "emulator": "0xee82856bf20e2aa6" - } - }, - "FUSD": { - "source": "./cadence/contracts/FUSD.cdc", - "aliases": { - "testnet": "0xe223d8a629e49c68" - } - }, - "FungibleToken": { - "source": "./cadence/contracts/FungibleToken.cdc", - "aliases": { - "emulator": "0xee82856bf20e2aa6", - "testnet": "0x9a0766d93b6608b7" - } - }, - "MetadataViews": { - "source": "./cadence/contracts/MetadataViews.cdc", - "aliases": { - "emulator": "f8d6e0586b0a20c7", - "testnet": "631e88ae7f1d7c20" - } - }, - "NonFungibleToken": { - "source": "./cadence/contracts/NonFungibleToken.cdc", - "aliases": { - "emulator": "f8d6e0586b0a20c7", - "testnet": "631e88ae7f1d7c20" - } - }, - "ViewResolver": { - "source": "./cadence/contracts/ViewResolver.cdc", - "aliases": { - "emulator": "f8d6e0586b0a20c7", - "testnet": "631e88ae7f1d7c20" - } - }, - "FungibleTokenMetadataViews": { - "source": "./cadence/contracts/FungibleTokenMetadataViews.cdc", - "aliases": { - "emulator": "f8d6e0586b0a20c7", - "testnet": "0xf233dcee88fe0abe" - } - } - }, - "networks": { - "emulator": "127.0.0.1:3569", - "testnet": "access.devnet.nodes.onflow.org:9000", - "crescendo": "access.crescendo.nodes.onflow.org:9000" - }, - "accounts": { - "emulator-account": { - "address": "f8d6e0586b0a20c7", - "key": "91a22fbd87392b019fbe332c32695c14cf2ba5b6521476a8540228bdf1987068" - }, + "contracts": { + "Burner": { + "source": "./cadence/contracts/Burner.cdc", + "aliases": { + "emulator": "ee82856bf20e2aa6" + } + }, + "EVM": { + "source": "./cadence/contracts/EVM.cdc", + "aliases": { + "emulator": "f8d6e0586b0a20c7" + } + }, + "FUSD": { + "source": "./cadence/contracts/FUSD.cdc", + "aliases": { + "emulator": "0ae53cb6e3f42a79", + "testnet": "e223d8a629e49c68" + } + }, + "FlowEVMPassthrough": "./cadence/contracts/FlowEVMPassthrough.cdc", + "FlowToken": { + "source": "./cadence/contracts/FlowToken.cdc", + "aliases": { + "emulator": "0ae53cb6e3f42a79", + "testnet": "7e60df042a9c0868" + } + }, + "FungibleToken": { + "source": "./cadence/contracts/FungibleToken.cdc", + "aliases": { + "emulator": "ee82856bf20e2aa6", + "testnet": "9a0766d93b6608b7" + } + }, + "FungibleTokenMetadataViews": { + "source": "./cadence/contracts/FungibleTokenMetadataViews.cdc", + "aliases": { + "emulator": "f8d6e0586b0a20c7", + "testnet": "f233dcee88fe0abe" + } + }, + "MetadataViews": { + "source": "./cadence/contracts/MetadataViews.cdc", + "aliases": { + "emulator": "f8d6e0586b0a20c7", + "testnet": "631e88ae7f1d7c20" + } + }, + "NonFungibleToken": { + "source": "./cadence/contracts/NonFungibleToken.cdc", + "aliases": { + "emulator": "f8d6e0586b0a20c7", + "testnet": "631e88ae7f1d7c20" + } + }, + "ViewResolver": { + "source": "./cadence/contracts/ViewResolver.cdc", + "aliases": { + "emulator": "f8d6e0586b0a20c7", + "testnet": "631e88ae7f1d7c20" + } + } + }, + "networks": { + "crescendo": "access.crescendo.nodes.onflow.org:9000", + "emulator": "127.0.0.1:3569", + "testnet": "access.devnet.nodes.onflow.org:9000" + }, + "accounts": { + "emulator-account": { + "address": "f8d6e0586b0a20c7", + "key": "91a22fbd87392b019fbe332c32695c14cf2ba5b6521476a8540228bdf1987068" + }, + "emulator-flow": { + "address": "0ae53cb6e3f42a79", + "key": "686779d775e5fcbf8d2f4a85cb4c53525d02b7ef53230d180fc16f35d9b7d025" + }, + "emulator-ft": { + "address": "ee82856bf20e2aa6", + "key": "686779d775e5fcbf8d2f4a85cb4c53525d02b7ef53230d180fc16f35d9b7d025" + }, "testnet-account": { "address": "${NEXT_PUBLIC_SIGNER_ADDRESS}", "keys": "${SIGNER_PRIVATE_KEY}" } - }, - "deployments": { - "emulator": { - "emulator-account": [ - "FungibleTokenMetadataViews", - "Burner", - "FUSD" - ] - } - } + }, + "deployments": { + "emulator": { + "emulator-account": [ + "FungibleTokenMetadataViews", + "Burner", + "FUSD", + "ViewResolver", + { + "name": "FlowEVMPassthrough", + "args": [ + { + "type": "String", + "value": "608060405234801561000f575f80fd5b506103678061001d5f395ff3fe60806040526004361061001d575f3560e01c80637ebbc23914610021575b5f80fd5b61003b60048036038101906100369190610185565b61003d565b005b5f808273ffffffffffffffffffffffffffffffffffffffff1634604051610063906101dd565b5f6040518083038185875af1925050503d805f811461009d576040519150601f19603f3d011682016040523d82523d5f602084013e6100a2565b606091505b5091509150816100e7576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016100de9061024b565b60405180910390fd5b7fe4fc403eef87e48e40029827def82ae478cfa60ca7726e7f52e09d5384076e6633843460405161011a939291906102fc565b60405180910390a1505050565b5f80fd5b5f73ffffffffffffffffffffffffffffffffffffffff82169050919050565b5f6101548261012b565b9050919050565b6101648161014a565b811461016e575f80fd5b50565b5f8135905061017f8161015b565b92915050565b5f6020828403121561019a57610199610127565b5b5f6101a784828501610171565b91505092915050565b5f81905092915050565b50565b5f6101c85f836101b0565b91506101d3826101ba565b5f82019050919050565b5f6101e7826101bd565b9150819050919050565b5f82825260208201905092915050565b7f4661696c656420746f2073656e6420466c6f77000000000000000000000000005f82015250565b5f6102356013836101f1565b915061024082610201565b602082019050919050565b5f6020820190508181035f83015261026281610229565b9050919050565b5f6102738261012b565b9050919050565b61028381610269565b82525050565b5f819050919050565b5f6102ac6102a76102a28461012b565b610289565b61012b565b9050919050565b5f6102bd82610292565b9050919050565b5f6102ce826102b3565b9050919050565b6102de816102c4565b82525050565b5f819050919050565b6102f6816102e4565b82525050565b5f60608201905061030f5f83018661027a565b61031c60208301856102d5565b61032960408301846102ed565b94935050505056fea264697066735822122098caf1372532fe479c7df9f46e6fde68a0389c81b4a013a70088c2d701b1409864736f6c63430008180033" + } + ] + } + ] + } + } } \ No newline at end of file From 6b58373adf59179566dcfa4aba5707d51e6e2afa Mon Sep 17 00:00:00 2001 From: Giovanni Sanchez <108043524+sisyphusSmiling@users.noreply.github.com> Date: Tue, 13 Feb 2024 18:08:29 -0600 Subject: [PATCH 06/29] add evm scripts --- cadence/scripts/evm/get_balance.cdc | 14 ++++++++++++++ cadence/scripts/evm/get_evm_address_string.cdc | 15 +++++++++++++++ .../scripts/evm/get_passthrough_evm_address.cdc | 8 ++++++++ 3 files changed, 37 insertions(+) create mode 100644 cadence/scripts/evm/get_balance.cdc create mode 100644 cadence/scripts/evm/get_evm_address_string.cdc create mode 100644 cadence/scripts/evm/get_passthrough_evm_address.cdc diff --git a/cadence/scripts/evm/get_balance.cdc b/cadence/scripts/evm/get_balance.cdc new file mode 100644 index 0000000..ea03aca --- /dev/null +++ b/cadence/scripts/evm/get_balance.cdc @@ -0,0 +1,14 @@ +import "EVM" + +/// Returns the Flow balance of of a given EVM address in FlowEVM +/// +access(all) fun main(address: String): UFix64 { + let bytes = address.decodeHex() + let addressBytes: [UInt8; 20] = [ + bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], + bytes[5], bytes[6], bytes[7], bytes[8], bytes[9], + 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 +} diff --git a/cadence/scripts/evm/get_evm_address_string.cdc b/cadence/scripts/evm/get_evm_address_string.cdc new file mode 100644 index 0000000..a481d16 --- /dev/null +++ b/cadence/scripts/evm/get_evm_address_string.cdc @@ -0,0 +1,15 @@ +import "EVM" + +/// Returns the hex encoded address of the COA in the given Flow address +/// +access(all) fun main(flowAddress: Address): String? { + let account = getAuthAccount(flowAddress) + if let address = account.storage.borrow<&EVM.BridgedAccount>(from: /storage/evm)?.address() { + let bytes: [UInt8] = [] + for byte in address.bytes { + bytes.append(byte) + } + return String.encodeHex(bytes) + } + return nil +} diff --git a/cadence/scripts/evm/get_passthrough_evm_address.cdc b/cadence/scripts/evm/get_passthrough_evm_address.cdc new file mode 100644 index 0000000..ad103d0 --- /dev/null +++ b/cadence/scripts/evm/get_passthrough_evm_address.cdc @@ -0,0 +1,8 @@ +import "EVM" + +import "FlowEVMPassthrough" + +/// Returns the EVM address of the FlowEVM passthrough contract. +access(all) fun main(): EVM.EVMAddress { + return FlowEVMPassthrough.passthroughEVMAddress +} From 35ac42413949bca999fbca986a7f7c62f45e0837 Mon Sep 17 00:00:00 2001 From: Giovanni Sanchez <108043524+sisyphusSmiling@users.noreply.github.com> Date: Tue, 13 Feb 2024 18:08:51 -0600 Subject: [PATCH 07/29] add evm transactions --- cadence/transactions/evm/create_account.cdc | 28 ++++++++++++++++ .../evm/passthrough_evm_transfer.cdc | 33 +++++++++++++++++++ .../transactions/evm/setup_passthrough.cdc | 23 +++++++++++++ 3 files changed, 84 insertions(+) create mode 100644 cadence/transactions/evm/create_account.cdc create mode 100644 cadence/transactions/evm/passthrough_evm_transfer.cdc create mode 100644 cadence/transactions/evm/setup_passthrough.cdc diff --git a/cadence/transactions/evm/create_account.cdc b/cadence/transactions/evm/create_account.cdc new file mode 100644 index 0000000..a153a81 --- /dev/null +++ b/cadence/transactions/evm/create_account.cdc @@ -0,0 +1,28 @@ +import "FungibleToken" +import "FlowToken" + +import "EVM" + +/// Creates a COA and saves it in the signer's Flow account & passing the given value of Flow into FlowEVM +/// +transaction(amount: UFix64) { + let sentVault: @FlowToken.Vault + let auth: auth(Storage) &Account + + prepare(signer: auth(Storage) &Account) { + 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 + self.auth = signer + } + + execute { + let account <- EVM.createBridgedAccount() + account.address().deposit(from: <-self.sentVault) + + log(account.balance()) + self.auth.storage.save<@EVM.BridgedAccount>(<-account, to: StoragePath(identifier: "evm")!) + } +} diff --git a/cadence/transactions/evm/passthrough_evm_transfer.cdc b/cadence/transactions/evm/passthrough_evm_transfer.cdc new file mode 100644 index 0000000..1f674d2 --- /dev/null +++ b/cadence/transactions/evm/passthrough_evm_transfer.cdc @@ -0,0 +1,33 @@ +import "FungibleToken" +import "FlowToken" + +import "EVM" + +import "FlowEVMPassthrough" + +/// Withdraws tokens from the signer's FlowToken Vault and transfers them to the given EVM address via the saved +/// Passthrough resource. +/// +transaction(to: EVM.EVMAddress, amount: UFix64, gasLimit: UInt64) { + + let sentVault: @{FungibleToken.Vault} + let passthrough: &FlowEVMPassthrough.Passthrough + + prepare(signer: auth(BorrowValue) &Account) { + let sourceVault = signer.storage.borrow(from: /storage/flowTokenVault) + ?? panic("Could not borrow reference to the owner's Vault!") + self.sentVault <- sourceVault.withdraw(amount: amount) + self.passthrough = signer.storage.borrow<&FlowEVMPassthrough.Passthrough>(from: FlowEVMPassthrough.StoragePath) + ?? panic("Could not borrow reference to the owner's Passthrough!") + } + + execute { + let bytes = toAsHex.decodeHex() + let to = EVM.EVMAddress( + bytes: [ + bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5], bytes[6], bytes[7], bytes[8], bytes[9], + bytes[10], bytes[11], bytes[12], bytes[13], bytes[14], bytes[15], bytes[16], bytes[17], bytes[18], bytes[19] + ]) + self.passthrough.evmTransfer(from: <-self.sentVault, to: to, gasLimit: gasLimit) + } +} diff --git a/cadence/transactions/evm/setup_passthrough.cdc b/cadence/transactions/evm/setup_passthrough.cdc new file mode 100644 index 0000000..5dd39b8 --- /dev/null +++ b/cadence/transactions/evm/setup_passthrough.cdc @@ -0,0 +1,23 @@ +import "EVM" + +import "FlowEVMPassthrough" + +/// Configures a Passthrough resource in the signer's storage +/// +transaction { + + prepare(signer: auth(BorrowValue, IssueStorageCapabilityController, PublishCapability, SaveValue) &Account) { + if signer.storage.borrow<&FlowEVMPassthrough.Passthrough>(from: FlowEVMPassthrough.StoragePath) != nil { + return + } + if signer.storage.borrow<&EVM.BridgedAccount>(from: /storage/evm) == nil { + signer.storage.save(<-EVM.createBridgedAccount(), to: /storage/evm) + } + let coaCapability = signer.capabilities.storage.issue<&EVM.BridgedAccount>(/storage/evm) + signer.storage.save(<-FlowEVMPassthrough.createPassthrough(with: coaCapability), to: FlowEVMPassthrough.StoragePath) + let passthroughCapability = signer.capabilities.storage.issue<&FlowEVMPassthrough.Passthrough>( + FlowEVMPassthrough.StoragePath + ) + signer.capabilities.publish(passthroughCapability, at: FlowEVMPassthrough.PublicPath) + } +} From f2ac15a4c247ba969ce367ecb64f3c59fc35c2c6 Mon Sep 17 00:00:00 2001 From: Giovanni Sanchez <108043524+sisyphusSmiling@users.noreply.github.com> Date: Tue, 13 Feb 2024 18:17:17 -0600 Subject: [PATCH 08/29] update all transactions for Cadence 1.0 compatibility --- cadence/transactions/deposit_fusd_minter.cdc | 16 ++++++---------- cadence/transactions/setup_fusd_minter.cdc | 10 ++++------ cadence/transactions/transfer_flow.cdc | 8 ++++---- 3 files changed, 14 insertions(+), 20 deletions(-) diff --git a/cadence/transactions/deposit_fusd_minter.cdc b/cadence/transactions/deposit_fusd_minter.cdc index 43f9b44..03db622 100644 --- a/cadence/transactions/deposit_fusd_minter.cdc +++ b/cadence/transactions/deposit_fusd_minter.cdc @@ -19,23 +19,21 @@ transaction(minterAddress: Address) { let capabilityPrivatePath: CapabilityPath let minterCapability: Capability<&FUSD.Minter> - prepare(adminAccount: AuthAccount) { + prepare(adminAccount: auth(BorrowValue, IssueStorageCapabilityController, PublishCapability, SaveValue) &Account) { // These paths must be unique within the FUSD contract account's storage self.resourceStoragePath = /storage/fusdAdminMinter self.capabilityPrivatePath = /private/fusdAdminMinter // Create a reference to the admin resource in storage. - let tokenAdmin = adminAccount.borrow<&FUSD.Administrator>(from: FUSD.AdminStoragePath) + let tokenAdmin = adminAccount.storage.borrow<&FUSD.Administrator>(from: FUSD.AdminStoragePath) ?? panic("Could not borrow a reference to the admin resource") // Create a new minter resource and a private link to a capability for it in the admin's storage. let minter <- tokenAdmin.createNewMinter() adminAccount.save(<- minter, to: self.resourceStoragePath) - self.minterCapability = adminAccount.link<&FUSD.Minter>( - self.capabilityPrivatePath, - target: self.resourceStoragePath - ) ?? panic("Could not link minter") + self.minterCapability = adminAccount.capabilities.storage.issue<&FUSD.Minter>(self.resourceStoragePath) + ?? panic("Could not link minter") } @@ -43,10 +41,8 @@ transaction(minterAddress: Address) { // This is the account that the capability will be given to let minterAccount = getAccount(minterAddress) - let capabilityReceiver = minterAccount.getCapability - <&FUSD.MinterProxy{FUSD.MinterProxyPublic}> - (FUSD.MinterProxyPublicPath)! - .borrow() ?? panic("Could not borrow capability receiver reference") + let capabilityReceiver = minterAccount.capabilities.borrow<&FUSD.MinterProxy>(FUSD.MinterProxyPublicPath) + ?? panic("Could not borrow capability receiver reference") capabilityReceiver.setMinterCapability(cap: self.minterCapability) } diff --git a/cadence/transactions/setup_fusd_minter.cdc b/cadence/transactions/setup_fusd_minter.cdc index 8a01a65..5402e39 100644 --- a/cadence/transactions/setup_fusd_minter.cdc +++ b/cadence/transactions/setup_fusd_minter.cdc @@ -8,18 +8,16 @@ import FUSD from 0xFUSDADDRESS transaction { - prepare(minter: AuthAccount) { + prepare(minter: auth(IssueStorageCapabilityController, SaveValue) &Account) { let minterProxy <- FUSD.createMinterProxy() - minter.save( + minter.storage.save( <- minterProxy, to: FUSD.MinterProxyStoragePath, ) - minter.link<&FUSD.MinterProxy{FUSD.MinterProxyPublic}>( - FUSD.MinterProxyPublicPath, - target: FUSD.MinterProxyStoragePath - ) + let minterProxyCap = minter.capabilities.storage.issue<&FUSD.MinterProxy>(FUSD.MinterProxyStoragePath) + minter.capabilities.publish(minterProxyCap, at: FUSD.MinterProxyPublicPath) } } \ No newline at end of file diff --git a/cadence/transactions/transfer_flow.cdc b/cadence/transactions/transfer_flow.cdc index f637236..9d186a5 100644 --- a/cadence/transactions/transfer_flow.cdc +++ b/cadence/transactions/transfer_flow.cdc @@ -4,12 +4,12 @@ import FlowToken from "../contracts/FlowToken.cdc" transaction (amount: UFix64, to: Address) { // The Vault resource that holds the tokens that are being transferred - let sentVault: @FungibleToken.Vault + let sentVault: @{FungibleToken.Vault} - prepare(signer: AuthAccount) { + prepare(signer: auth(BorrowValue) &Account) { // Get a reference to the signer's stored vault - let vaultRef = signer.borrow<&FlowToken.Vault>(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 @@ -22,7 +22,7 @@ transaction (amount: UFix64, to: Address) { let recipient = getAccount(to) // Get a reference to the recipient's Receiver - let receiverRef = recipient.getCapability(/public/flowTokenReceiver)!.borrow<&{FungibleToken.Receiver}>() + let receiverRef = recipient.capabilities.borrow<&{FungibleToken.Receiver}>(/public/flowTokenReceiver) ?? panic("Could not borrow receiver reference to the recipient's Vault") // Deposit the withdrawn tokens in the recipient's receiver From 67a13d80ae33ebc284bbd19fc9cca1238397b8db Mon Sep 17 00:00:00 2001 From: Giovanni Sanchez <108043524+sisyphusSmiling@users.noreply.github.com> Date: Wed, 14 Feb 2024 14:05:21 -0600 Subject: [PATCH 09/29] update flow.json with crescendo aliases --- flow.json | 216 ++++++++++++++++++++++++++++-------------------------- 1 file changed, 114 insertions(+), 102 deletions(-) diff --git a/flow.json b/flow.json index 0a218e6..ed593c1 100644 --- a/flow.json +++ b/flow.json @@ -1,108 +1,120 @@ { - "contracts": { - "Burner": { - "source": "./cadence/contracts/Burner.cdc", - "aliases": { - "emulator": "ee82856bf20e2aa6" - } - }, - "EVM": { - "source": "./cadence/contracts/EVM.cdc", - "aliases": { - "emulator": "f8d6e0586b0a20c7" - } - }, - "FUSD": { - "source": "./cadence/contracts/FUSD.cdc", - "aliases": { - "emulator": "0ae53cb6e3f42a79", - "testnet": "e223d8a629e49c68" - } - }, - "FlowEVMPassthrough": "./cadence/contracts/FlowEVMPassthrough.cdc", - "FlowToken": { - "source": "./cadence/contracts/FlowToken.cdc", - "aliases": { - "emulator": "0ae53cb6e3f42a79", - "testnet": "7e60df042a9c0868" - } - }, - "FungibleToken": { - "source": "./cadence/contracts/FungibleToken.cdc", - "aliases": { - "emulator": "ee82856bf20e2aa6", - "testnet": "9a0766d93b6608b7" - } - }, - "FungibleTokenMetadataViews": { - "source": "./cadence/contracts/FungibleTokenMetadataViews.cdc", - "aliases": { - "emulator": "f8d6e0586b0a20c7", - "testnet": "f233dcee88fe0abe" - } - }, - "MetadataViews": { - "source": "./cadence/contracts/MetadataViews.cdc", - "aliases": { - "emulator": "f8d6e0586b0a20c7", - "testnet": "631e88ae7f1d7c20" - } - }, - "NonFungibleToken": { - "source": "./cadence/contracts/NonFungibleToken.cdc", - "aliases": { - "emulator": "f8d6e0586b0a20c7", - "testnet": "631e88ae7f1d7c20" - } - }, - "ViewResolver": { - "source": "./cadence/contracts/ViewResolver.cdc", - "aliases": { - "emulator": "f8d6e0586b0a20c7", - "testnet": "631e88ae7f1d7c20" - } - } - }, - "networks": { - "crescendo": "access.crescendo.nodes.onflow.org:9000", - "emulator": "127.0.0.1:3569", - "testnet": "access.devnet.nodes.onflow.org:9000" - }, - "accounts": { - "emulator-account": { - "address": "f8d6e0586b0a20c7", - "key": "91a22fbd87392b019fbe332c32695c14cf2ba5b6521476a8540228bdf1987068" - }, - "emulator-flow": { - "address": "0ae53cb6e3f42a79", - "key": "686779d775e5fcbf8d2f4a85cb4c53525d02b7ef53230d180fc16f35d9b7d025" - }, - "emulator-ft": { - "address": "ee82856bf20e2aa6", - "key": "686779d775e5fcbf8d2f4a85cb4c53525d02b7ef53230d180fc16f35d9b7d025" - }, + "contracts": { + "Burner": { + "source": "./cadence/contracts/Burner.cdc", + "aliases": { + "emulator": "ee82856bf20e2aa6" + } + }, + "EVM": { + "source": "./cadence/contracts/EVM.cdc", + "aliases": { + "emulator": "f8d6e0586b0a20c7" + } + }, + "FUSD": { + "source": "./cadence/contracts/FUSD.cdc", + "aliases": { + "crescendo": "e223d8a629e49c68", + "emulator": "0ae53cb6e3f42a79", + "testnet": "e223d8a629e49c68" + } + }, + "FlowEVMPassthrough": { + "source": "./cadence/contracts/FlowEVMPassthrough.cdc", + "aliases": { + "emulator": "f8d6e0586b0a20c7" + } + }, + "FlowToken": { + "source": "./cadence/contracts/FlowToken.cdc", + "aliases": { + "crescendo": "7e60df042a9c0868", + "emulator": "0ae53cb6e3f42a79", + "testnet": "7e60df042a9c0868" + } + }, + "FungibleToken": { + "source": "./cadence/contracts/FungibleToken.cdc", + "aliases": { + "crescendo": "9a0766d93b6608b7", + "emulator": "ee82856bf20e2aa6", + "testnet": "9a0766d93b6608b7" + } + }, + "FungibleTokenMetadataViews": { + "source": "./cadence/contracts/FungibleTokenMetadataViews.cdc", + "aliases": { + "crescendo": "f233dcee88fe0abe", + "emulator": "f8d6e0586b0a20c7", + "testnet": "f233dcee88fe0abe" + } + }, + "MetadataViews": { + "source": "./cadence/contracts/MetadataViews.cdc", + "aliases": { + "crescendo": "0x631e88ae7f1d7c20", + "emulator": "f8d6e0586b0a20c7", + "testnet": "631e88ae7f1d7c20" + } + }, + "NonFungibleToken": { + "source": "./cadence/contracts/NonFungibleToken.cdc", + "aliases": { + "crescendo": "0x631e88ae7f1d7c20", + "emulator": "f8d6e0586b0a20c7", + "testnet": "631e88ae7f1d7c20" + } + }, + "ViewResolver": { + "source": "./cadence/contracts/ViewResolver.cdc", + "aliases": { + "crescendo": "0x631e88ae7f1d7c20", + "emulator": "f8d6e0586b0a20c7", + "testnet": "631e88ae7f1d7c20" + } + } + }, + "networks": { + "crescendo": "access.crescendo.nodes.onflow.org:9000", + "emulator": "127.0.0.1:3569", + "testnet": "access.devnet.nodes.onflow.org:9000" + }, + "accounts": { + "emulator-account": { + "address": "f8d6e0586b0a20c7", + "key": "91a22fbd87392b019fbe332c32695c14cf2ba5b6521476a8540228bdf1987068" + }, + "emulator-flow": { + "address": "0ae53cb6e3f42a79", + "key": "686779d775e5fcbf8d2f4a85cb4c53525d02b7ef53230d180fc16f35d9b7d025" + }, + "emulator-ft": { + "address": "ee82856bf20e2aa6", + "key": "686779d775e5fcbf8d2f4a85cb4c53525d02b7ef53230d180fc16f35d9b7d025" + }, "testnet-account": { "address": "${NEXT_PUBLIC_SIGNER_ADDRESS}", "keys": "${SIGNER_PRIVATE_KEY}" } - }, - "deployments": { - "emulator": { - "emulator-account": [ - "FungibleTokenMetadataViews", - "Burner", - "FUSD", - "ViewResolver", - { - "name": "FlowEVMPassthrough", - "args": [ - { - "type": "String", - "value": "608060405234801561000f575f80fd5b506103678061001d5f395ff3fe60806040526004361061001d575f3560e01c80637ebbc23914610021575b5f80fd5b61003b60048036038101906100369190610185565b61003d565b005b5f808273ffffffffffffffffffffffffffffffffffffffff1634604051610063906101dd565b5f6040518083038185875af1925050503d805f811461009d576040519150601f19603f3d011682016040523d82523d5f602084013e6100a2565b606091505b5091509150816100e7576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016100de9061024b565b60405180910390fd5b7fe4fc403eef87e48e40029827def82ae478cfa60ca7726e7f52e09d5384076e6633843460405161011a939291906102fc565b60405180910390a1505050565b5f80fd5b5f73ffffffffffffffffffffffffffffffffffffffff82169050919050565b5f6101548261012b565b9050919050565b6101648161014a565b811461016e575f80fd5b50565b5f8135905061017f8161015b565b92915050565b5f6020828403121561019a57610199610127565b5b5f6101a784828501610171565b91505092915050565b5f81905092915050565b50565b5f6101c85f836101b0565b91506101d3826101ba565b5f82019050919050565b5f6101e7826101bd565b9150819050919050565b5f82825260208201905092915050565b7f4661696c656420746f2073656e6420466c6f77000000000000000000000000005f82015250565b5f6102356013836101f1565b915061024082610201565b602082019050919050565b5f6020820190508181035f83015261026281610229565b9050919050565b5f6102738261012b565b9050919050565b61028381610269565b82525050565b5f819050919050565b5f6102ac6102a76102a28461012b565b610289565b61012b565b9050919050565b5f6102bd82610292565b9050919050565b5f6102ce826102b3565b9050919050565b6102de816102c4565b82525050565b5f819050919050565b6102f6816102e4565b82525050565b5f60608201905061030f5f83018661027a565b61031c60208301856102d5565b61032960408301846102ed565b94935050505056fea264697066735822122098caf1372532fe479c7df9f46e6fde68a0389c81b4a013a70088c2d701b1409864736f6c63430008180033" - } - ] - } - ] - } - } + }, + "deployments": { + "emulator": { + "emulator-account": [ + "FungibleTokenMetadataViews", + "Burner", + "FUSD", + "ViewResolver", + { + "name": "FlowEVMPassthrough", + "args": [ + { + "type": "String", + "value": "608060405234801561000f575f80fd5b506103678061001d5f395ff3fe60806040526004361061001d575f3560e01c80637ebbc23914610021575b5f80fd5b61003b60048036038101906100369190610185565b61003d565b005b5f808273ffffffffffffffffffffffffffffffffffffffff1634604051610063906101dd565b5f6040518083038185875af1925050503d805f811461009d576040519150601f19603f3d011682016040523d82523d5f602084013e6100a2565b606091505b5091509150816100e7576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016100de9061024b565b60405180910390fd5b7fe4fc403eef87e48e40029827def82ae478cfa60ca7726e7f52e09d5384076e6633843460405161011a939291906102fc565b60405180910390a1505050565b5f80fd5b5f73ffffffffffffffffffffffffffffffffffffffff82169050919050565b5f6101548261012b565b9050919050565b6101648161014a565b811461016e575f80fd5b50565b5f8135905061017f8161015b565b92915050565b5f6020828403121561019a57610199610127565b5b5f6101a784828501610171565b91505092915050565b5f81905092915050565b50565b5f6101c85f836101b0565b91506101d3826101ba565b5f82019050919050565b5f6101e7826101bd565b9150819050919050565b5f82825260208201905092915050565b7f4661696c656420746f2073656e6420466c6f77000000000000000000000000005f82015250565b5f6102356013836101f1565b915061024082610201565b602082019050919050565b5f6020820190508181035f83015261026281610229565b9050919050565b5f6102738261012b565b9050919050565b61028381610269565b82525050565b5f819050919050565b5f6102ac6102a76102a28461012b565b610289565b61012b565b9050919050565b5f6102bd82610292565b9050919050565b5f6102ce826102b3565b9050919050565b6102de816102c4565b82525050565b5f819050919050565b6102f6816102e4565b82525050565b5f60608201905061030f5f83018661027a565b61031c60208301856102d5565b61032960408301846102ed565b94935050505056fea264697066735822122098caf1372532fe479c7df9f46e6fde68a0389c81b4a013a70088c2d701b1409864736f6c63430008180033" + } + ] + } + ] + } + } } \ No newline at end of file From 5901ff18e4cd495676687961fae94b594be54c27 Mon Sep 17 00:00:00 2001 From: Giovanni Sanchez <108043524+sisyphusSmiling@users.noreply.github.com> Date: Wed, 14 Feb 2024 14:08:28 -0600 Subject: [PATCH 10/29] update FlowEVMPassthrough pre-condition syntax --- cadence/contracts/FlowEVMPassthrough.cdc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cadence/contracts/FlowEVMPassthrough.cdc b/cadence/contracts/FlowEVMPassthrough.cdc index 8ca2352..6df409c 100644 --- a/cadence/contracts/FlowEVMPassthrough.cdc +++ b/cadence/contracts/FlowEVMPassthrough.cdc @@ -58,7 +58,7 @@ access(all) contract FlowEVMPassthrough { /// Deposits Flow to encapsulated COA according to Receiver interface access(all) fun deposit(from: @{FungibleToken.Vault}) { pre { - from.getType() == Type<@FlowToken.Vault>(): "Passthrough only supports FlowToken.Vaults" + self.isSupportedVaultType(type: from.getType()): "Passthrough only supports FlowToken.Vaults" } let flowVault <- from as! @FlowToken.Vault self.borrowCOA().deposit(from: <-flowVault) @@ -73,7 +73,7 @@ access(all) contract FlowEVMPassthrough { /// Deposits Flow to defined EVM Address using a call to passthrough Solidity contract access(all) fun evmTransfer(from: @{FungibleToken.Vault}, to: EVM.EVMAddress, gasLimit: UInt64) { pre { - from.getType() == Type<@FlowToken.Vault>(): "Passthrough only supports FlowToken.Vaults" + self.isSupportedVaultType(type: from.getType()): "Passthrough only supports FlowToken.Vaults" } let amount = from.getBalance() // TODO: Replace with UInt256(EVM.Balance(flow: from.balance).toAttoFlow()) From 12fa01b2bf0962df08625e328c8dfe8877fed075 Mon Sep 17 00:00:00 2001 From: Giovanni Sanchez <108043524+sisyphusSmiling@users.noreply.github.com> Date: Wed, 14 Feb 2024 14:55:09 -0600 Subject: [PATCH 11/29] add CryptoUtils contract & name in flow.json & env.example --- cadence/contracts/CryptoUtils.cdc | 34 +++++++++++++++++++++++++++++++ env.example | 3 +++ flow.json | 8 +++++++- 3 files changed, 44 insertions(+), 1 deletion(-) create mode 100644 cadence/contracts/CryptoUtils.cdc diff --git a/cadence/contracts/CryptoUtils.cdc b/cadence/contracts/CryptoUtils.cdc new file mode 100644 index 0000000..c464d80 --- /dev/null +++ b/cadence/contracts/CryptoUtils.cdc @@ -0,0 +1,34 @@ +/// Util methods to reconstruct HashAlgorithm and SignatureAlgorithm from their raw enum values +/// +access(all) contract CryptoUtils { + + access(all) fun getHashAlgo(fromRawValue: UInt8): HashAlgorithm? { + switch fromRawValue { + case HashAlgorithm.SHA2_256.rawValue: + return HashAlgorithm.SHA2_256 + case HashAlgorithm.SHA2_384.rawValue: + return HashAlgorithm.SHA2_384 + case HashAlgorithm.SHA3_256.rawValue: + return HashAlgorithm.SHA3_256 + case HashAlgorithm.SHA3_384.rawValue: + return HashAlgorithm.SHA3_384 + case HashAlgorithm.KMAC128_BLS_BLS12_381.rawValue: + return HashAlgorithm.KMAC128_BLS_BLS12_381 + case HashAlgorithm.KECCAK_256.rawValue: + return HashAlgorithm.KECCAK_256 + default: + return nil + } + } + + access(all) fun getSigAlgo(fromRawValue: UInt8): SignatureAlgorithm? { + switch fromRawValue { + case SignatureAlgorithm.ECDSA_P256.rawValue: + return SignatureAlgorithm.ECDSA_P256 + case SignatureAlgorithm.ECDSA_secp256k1.rawValue: + return SignatureAlgorithm.ECDSA_secp256k1 + default: + return nil + } + } +} \ No newline at end of file diff --git a/env.example b/env.example index 8e67d40..2d138c6 100644 --- a/env.example +++ b/env.example @@ -11,6 +11,9 @@ NEXT_PUBLIC_IS_LOCAL=true NEXT_PUBLIC_CONTRACT_FUNGIBLE_TOKEN=0xee82856bf20e2aa6 NEXT_PUBLIC_CONTRACT_FLOW_TOKEN=0x0ae53cb6e3f42a79 NEXT_PUBLIC_CONTRACT_FUSD=0xf8d6e0586b0a20c7 +NEXT_PUBLIC_CONTRACT_EVM=0xf8d6e0586b0a20c7 +NEXT_PUBLIC_CONTRACT_CRYPTO_UTILS=0xf8d6e0586b0a20c7 +NEXT_PUBLIC_CONTRACT_FLOW_EVM_PASSTHROUGH=0xf8d6e0586b0a20c7 NEXT_PUBLIC_GA_MEASUREMENT_ID= SIGNER_HASH_ALGO=SHA2_256 diff --git a/flow.json b/flow.json index ed593c1..aea2532 100644 --- a/flow.json +++ b/flow.json @@ -6,6 +6,12 @@ "emulator": "ee82856bf20e2aa6" } }, + "CryptoUtils": { + "source": "./cadence/contracts/CryptoUtils.cdc", + "aliases": { + "emulator": "f8d6e0586b0a20c7" + } + }, "EVM": { "source": "./cadence/contracts/EVM.cdc", "aliases": { @@ -105,7 +111,7 @@ "Burner", "FUSD", "ViewResolver", - { + "CryptoUtils", { "name": "FlowEVMPassthrough", "args": [ { From 9a9aed5f7824f81f97b478329a48f46310ad64a2 Mon Sep 17 00:00:00 2001 From: Giovanni Sanchez <108043524+sisyphusSmiling@users.noreply.github.com> Date: Wed, 14 Feb 2024 14:57:27 -0600 Subject: [PATCH 12/29] add env vars to publicConfig.ts --- lib/publicConfig.ts | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/lib/publicConfig.ts b/lib/publicConfig.ts index 66cce18..95feab2 100644 --- a/lib/publicConfig.ts +++ b/lib/publicConfig.ts @@ -1,4 +1,4 @@ -import {Networks, TokenTypes} from "./constants" +import { Networks, TokenTypes } from "./constants" const network = process.env.NEXT_PUBLIC_NETWORK as Networks | undefined if (!network) throw "Missing NEXT_PUBLIC_NETWORK" @@ -27,6 +27,15 @@ if (!contractFlowToken) throw "Missing NEXT_PUBLIC_CONTRACT_FLOW_TOKEN" const contractFUSD = process.env.NEXT_PUBLIC_CONTRACT_FUSD if (!contractFUSD) throw "Missing NEXT_PUBLIC_CONTRACT_FUSD" +const contractEVM = process.env.NEXT_PUBLIC_CONTRACT_EVM +if (!contractEVM) throw "Missing NEXT_PUBLIC_CONTRACT_EVM" + +const contractCryptoUtils = process.env.NEXT_PUBLIC_CONTRACT_CRYPTO_UTILS +if (!contractCryptoUtils) throw "Missing NEXT_PUBLIC_CONTRACT_CRYPTO_UTILS" + +const contractFlowEVMPassthrough = process.env.NEXT_PUBLIC_CONTRACT_FLOW_EVM_PASSTHROUGH +if (!contractFlowEVMPassthrough) throw "Missing NEXT_PUBLIC_CONTRACT_FLOW_EVM_PASSTHROUGH" + const walletDiscovery = process.env.NEXT_PUBLIC_WALLET_DISCOVERY // TODO: Integrate FCL wallets // if (!walletDiscovery) throw "Missing NEXT_PUBLIC_WALLET_DISCOVERY" From 7ccb0cb373e8e603a733462eab87a90bf36cf5b6 Mon Sep 17 00:00:00 2001 From: Giovanni Sanchez <108043524+sisyphusSmiling@users.noreply.github.com> Date: Wed, 14 Feb 2024 15:29:27 -0600 Subject: [PATCH 13/29] update lib cadence to 1.0 --- lib/flow/account.ts | 32 ++++++++++++++++++++++---------- lib/flow/fund.ts | 20 ++++++++------------ 2 files changed, 30 insertions(+), 22 deletions(-) diff --git a/lib/flow/account.ts b/lib/flow/account.ts index 9a43b2e..3ba6a97 100644 --- a/lib/flow/account.ts +++ b/lib/flow/account.ts @@ -7,25 +7,36 @@ import {sendTransaction} from "./send" const accountCreatedEventType = "flow.AccountCreated" const txCreateAccount = ` +import CryptoUtils from ${publicConfig.contractFlowToken} import FlowToken from ${publicConfig.contractFlowToken} import FungibleToken from ${publicConfig.contractFungibleToken} -transaction(publicKey: String, flowTokenAmount: UFix64) { +transaction(publicKey: String, flowTokenAmount: UFix64, sigAlgorithm: SignatureAlgorithm, hashAlgorithm: HashAlgorithm) { let tokenAdmin: &FlowToken.Administrator let tokenReceiver: &{FungibleToken.Receiver} - prepare(signer: AuthAccount) { + prepare(signer: auth(BorrowValue) &Account) { let account = AuthAccount(payer: signer) - account.addPublicKey(publicKey.decodeHex()) + let signatureAlgorithm: UInt8 = CryptoUtils.getSigAlgo(fromRawValue: sigAlgorithm) + ?? panic("Invalid SignatureAlgorithm") + let hashAlgorithm: UInt8 = CryptoUtils.getHashAlgo(fromRawValue: sigAlgorithm) + ?? panic("Invalid HashAlgorithm") - self.tokenAdmin = signer - .borrow<&FlowToken.Administrator>(from: /storage/flowTokenAdmin) + let key = PublicKey( + publicKey: publicKey.decodeHex(), + signatureAlgorithm: signatureAlgorithm + ) + account.keys.add( + publicKey: key, + hashAlgorithm: hashAlgorithm, + weight: 1000.0 + ) + + self.tokenAdmin = signer.storage.borrow<&FlowToken.Administrator>(from: /storage/flowTokenAdmin) ?? panic("Signer is not the token admin") - self.tokenReceiver = account - .getCapability(/public/flowTokenReceiver)! - .borrow<&{FungibleToken.Receiver}>() + self.tokenReceiver = account.capabilities.borrow<&{FungibleToken.Receiver}>(/public/flowTokenReceiver)! ?? panic("Unable to borrow receiver reference") } @@ -46,12 +57,13 @@ export async function createAccount( hashAlgo: number, authorization: fcl.Authorization ) { - const encodedPublicKey = encodeKey(publicKey, sigAlgo, hashAlgo, 1000) const result = await sendTransaction({ transaction: txCreateAccount, args: [ - fcl.arg(encodedPublicKey, t.String), + fcl.arg(publicKey, t.String), fcl.arg(publicConfig.tokenAmountFlow, t.UFix64), + fcl.arg(sigAlgo.toString(), t.UInt8), + fcl.arg(hashAlgo.toString(), t.UInt8), ], authorizations: [authorization], payer: authorization, diff --git a/lib/flow/fund.ts b/lib/flow/fund.ts index 78b59a8..d4cdc55 100644 --- a/lib/flow/fund.ts +++ b/lib/flow/fund.ts @@ -13,14 +13,12 @@ transaction(address: Address, amount: UFix64) { let tokenReceiver: &{FungibleToken.Receiver} prepare(signer: AuthAccount) { - self.tokenAdmin = signer - .borrow<&FlowToken.Administrator>(from: /storage/flowTokenAdmin) + self.tokenAdmin = signer.storage.borrow<&FlowToken.Administrator>(from: /storage/flowTokenAdmin) ?? panic("Signer is not the token admin") - self.tokenReceiver = getAccount(address) - .getCapability(/public/flowTokenReceiver)! - .borrow<&{FungibleToken.Receiver}>() - ?? panic("Unable to borrow receiver reference") + self.tokenReceiver = getAccount(address).capabilities.borrow<&{FungibleToken.Receiver}>( + /public/flowTokenReceiver + ) ?? panic("Could not borrow receiver reference to the recipient's Vault") } execute { @@ -43,14 +41,12 @@ transaction(address: Address, amount: UFix64) { let tokenReceiver: &{FungibleToken.Receiver} prepare(minterAccount: AuthAccount) { - self.tokenMinter = minterAccount - .borrow<&FUSD.MinterProxy>(from: FUSD.MinterProxyStoragePath) + self.tokenMinter = minterAccount.storage.borrow<&FUSD.MinterProxy>(from: FUSD.MinterProxyStoragePath) ?? panic("No minter available") - self.tokenReceiver = getAccount(address) - .getCapability(/public/fusdReceiver)! - .borrow<&{FungibleToken.Receiver}>() - ?? panic("Unable to borrow receiver reference") + self.tokenReceiver = getAccount(address).capabilities.borrow<&{FungibleToken.Receiver}>( + /public/fusdReceiver + ) ?? panic("Unable to borrow receiver reference") } execute { From 8cfa6b00dfd0d0e0822da987496437289106fe59 Mon Sep 17 00:00:00 2001 From: Giovanni Sanchez <108043524+sisyphusSmiling@users.noreply.github.com> Date: Wed, 14 Feb 2024 15:34:03 -0600 Subject: [PATCH 14/29] fix fund.ts Cadence --- lib/flow/fund.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/flow/fund.ts b/lib/flow/fund.ts index d4cdc55..b23d59b 100644 --- a/lib/flow/fund.ts +++ b/lib/flow/fund.ts @@ -37,12 +37,13 @@ import FUSD from ${publicConfig.contractFUSD} import FungibleToken from ${publicConfig.contractFungibleToken} transaction(address: Address, amount: UFix64) { - let tokenMinter: &FUSD.MinterProxy + let tokenMinter: auth(FUSD.ProxyOwner) &FUSD.MinterProxy let tokenReceiver: &{FungibleToken.Receiver} prepare(minterAccount: AuthAccount) { - self.tokenMinter = minterAccount.storage.borrow<&FUSD.MinterProxy>(from: FUSD.MinterProxyStoragePath) - ?? panic("No minter available") + self.tokenMinter = minterAccount.storage.borrow( + from: FUSD.MinterProxyStoragePath + ) ?? panic("No minter available") self.tokenReceiver = getAccount(address).capabilities.borrow<&{FungibleToken.Receiver}>( /public/fusdReceiver From 7e008868176752366847ae3348a7b2147df9284c Mon Sep 17 00:00:00 2001 From: Giovanni Sanchez <108043524+sisyphusSmiling@users.noreply.github.com> Date: Wed, 14 Feb 2024 16:11:52 -0600 Subject: [PATCH 15/29] update flow.json --- flow.json | 42 +++++++++++++++++++++--------------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/flow.json b/flow.json index aea2532..7f66e0f 100644 --- a/flow.json +++ b/flow.json @@ -102,25 +102,25 @@ "testnet-account": { "address": "${NEXT_PUBLIC_SIGNER_ADDRESS}", "keys": "${SIGNER_PRIVATE_KEY}" + }, + "deployments": { + "emulator": { + "emulator-account": [ + "FungibleTokenMetadataViews", + "Burner", + "FUSD", + "ViewResolver", + "CryptoUtils", + { + "name": "FlowEVMPassthrough", + "args": [ + { + "type": "String", + "value": "608060405234801561000f575f80fd5b506103678061001d5f395ff3fe60806040526004361061001d575f3560e01c80637ebbc23914610021575b5f80fd5b61003b60048036038101906100369190610185565b61003d565b005b5f808273ffffffffffffffffffffffffffffffffffffffff1634604051610063906101dd565b5f6040518083038185875af1925050503d805f811461009d576040519150601f19603f3d011682016040523d82523d5f602084013e6100a2565b606091505b5091509150816100e7576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016100de9061024b565b60405180910390fd5b7fe4fc403eef87e48e40029827def82ae478cfa60ca7726e7f52e09d5384076e6633843460405161011a939291906102fc565b60405180910390a1505050565b5f80fd5b5f73ffffffffffffffffffffffffffffffffffffffff82169050919050565b5f6101548261012b565b9050919050565b6101648161014a565b811461016e575f80fd5b50565b5f8135905061017f8161015b565b92915050565b5f6020828403121561019a57610199610127565b5b5f6101a784828501610171565b91505092915050565b5f81905092915050565b50565b5f6101c85f836101b0565b91506101d3826101ba565b5f82019050919050565b5f6101e7826101bd565b9150819050919050565b5f82825260208201905092915050565b7f4661696c656420746f2073656e6420466c6f77000000000000000000000000005f82015250565b5f6102356013836101f1565b915061024082610201565b602082019050919050565b5f6020820190508181035f83015261026281610229565b9050919050565b5f6102738261012b565b9050919050565b61028381610269565b82525050565b5f819050919050565b5f6102ac6102a76102a28461012b565b610289565b61012b565b9050919050565b5f6102bd82610292565b9050919050565b5f6102ce826102b3565b9050919050565b6102de816102c4565b82525050565b5f819050919050565b6102f6816102e4565b82525050565b5f60608201905061030f5f83018661027a565b61031c60208301856102d5565b61032960408301846102ed565b94935050505056fea264697066735822122098caf1372532fe479c7df9f46e6fde68a0389c81b4a013a70088c2d701b1409864736f6c63430008180033" + } + ] + } + ] + } } - }, - "deployments": { - "emulator": { - "emulator-account": [ - "FungibleTokenMetadataViews", - "Burner", - "FUSD", - "ViewResolver", - "CryptoUtils", { - "name": "FlowEVMPassthrough", - "args": [ - { - "type": "String", - "value": "608060405234801561000f575f80fd5b506103678061001d5f395ff3fe60806040526004361061001d575f3560e01c80637ebbc23914610021575b5f80fd5b61003b60048036038101906100369190610185565b61003d565b005b5f808273ffffffffffffffffffffffffffffffffffffffff1634604051610063906101dd565b5f6040518083038185875af1925050503d805f811461009d576040519150601f19603f3d011682016040523d82523d5f602084013e6100a2565b606091505b5091509150816100e7576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016100de9061024b565b60405180910390fd5b7fe4fc403eef87e48e40029827def82ae478cfa60ca7726e7f52e09d5384076e6633843460405161011a939291906102fc565b60405180910390a1505050565b5f80fd5b5f73ffffffffffffffffffffffffffffffffffffffff82169050919050565b5f6101548261012b565b9050919050565b6101648161014a565b811461016e575f80fd5b50565b5f8135905061017f8161015b565b92915050565b5f6020828403121561019a57610199610127565b5b5f6101a784828501610171565b91505092915050565b5f81905092915050565b50565b5f6101c85f836101b0565b91506101d3826101ba565b5f82019050919050565b5f6101e7826101bd565b9150819050919050565b5f82825260208201905092915050565b7f4661696c656420746f2073656e6420466c6f77000000000000000000000000005f82015250565b5f6102356013836101f1565b915061024082610201565b602082019050919050565b5f6020820190508181035f83015261026281610229565b9050919050565b5f6102738261012b565b9050919050565b61028381610269565b82525050565b5f819050919050565b5f6102ac6102a76102a28461012b565b610289565b61012b565b9050919050565b5f6102bd82610292565b9050919050565b5f6102ce826102b3565b9050919050565b6102de816102c4565b82525050565b5f819050919050565b6102f6816102e4565b82525050565b5f60608201905061030f5f83018661027a565b61031c60208301856102d5565b61032960408301846102ed565b94935050505056fea264697066735822122098caf1372532fe479c7df9f46e6fde68a0389c81b4a013a70088c2d701b1409864736f6c63430008180033" - } - ] - } - ] - } - } -} \ No newline at end of file + } \ No newline at end of file From fd30f592d1c84b942ee7a6bd103459271cd3d302 Mon Sep 17 00:00:00 2001 From: Giovanni Sanchez <108043524+sisyphusSmiling@users.noreply.github.com> Date: Wed, 14 Feb 2024 16:12:17 -0600 Subject: [PATCH 16/29] add contract alias info to fclConfig.ts --- lib/fclConfig.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/fclConfig.ts b/lib/fclConfig.ts index b938d96..0d0db0d 100644 --- a/lib/fclConfig.ts +++ b/lib/fclConfig.ts @@ -11,3 +11,6 @@ config() .put("0xFUNGIBLETOKENADDRESS", publicConfig.contractFungibleToken) .put("0xFLOWTOKENADDRESS", publicConfig.contractFlowToken) .put("0xFUSDADDRESS", publicConfig.contractFUSD) + .put("0xEVMADDRESS", publicConfig.contractEVM) + .put("0xCRYPTOUTILSADDRESS", publicConfig.contractCryptoUtils) + .put("0xFLOWEVMPASSTHROUGHADDRESS", publicConfig.contractFlowEVMPassthrough) From 1464d87e013ac7c60ef0840b0a27224206e2678c Mon Sep 17 00:00:00 2001 From: Giovanni Sanchez <108043524+sisyphusSmiling@users.noreply.github.com> Date: Wed, 14 Feb 2024 16:15:47 -0600 Subject: [PATCH 17/29] fix flow.json --- flow.json | 43 ++++++++++++++++++++++--------------------- 1 file changed, 22 insertions(+), 21 deletions(-) diff --git a/flow.json b/flow.json index 7f66e0f..a40bc0d 100644 --- a/flow.json +++ b/flow.json @@ -102,25 +102,26 @@ "testnet-account": { "address": "${NEXT_PUBLIC_SIGNER_ADDRESS}", "keys": "${SIGNER_PRIVATE_KEY}" - }, - "deployments": { - "emulator": { - "emulator-account": [ - "FungibleTokenMetadataViews", - "Burner", - "FUSD", - "ViewResolver", - "CryptoUtils", - { - "name": "FlowEVMPassthrough", - "args": [ - { - "type": "String", - "value": "608060405234801561000f575f80fd5b506103678061001d5f395ff3fe60806040526004361061001d575f3560e01c80637ebbc23914610021575b5f80fd5b61003b60048036038101906100369190610185565b61003d565b005b5f808273ffffffffffffffffffffffffffffffffffffffff1634604051610063906101dd565b5f6040518083038185875af1925050503d805f811461009d576040519150601f19603f3d011682016040523d82523d5f602084013e6100a2565b606091505b5091509150816100e7576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016100de9061024b565b60405180910390fd5b7fe4fc403eef87e48e40029827def82ae478cfa60ca7726e7f52e09d5384076e6633843460405161011a939291906102fc565b60405180910390a1505050565b5f80fd5b5f73ffffffffffffffffffffffffffffffffffffffff82169050919050565b5f6101548261012b565b9050919050565b6101648161014a565b811461016e575f80fd5b50565b5f8135905061017f8161015b565b92915050565b5f6020828403121561019a57610199610127565b5b5f6101a784828501610171565b91505092915050565b5f81905092915050565b50565b5f6101c85f836101b0565b91506101d3826101ba565b5f82019050919050565b5f6101e7826101bd565b9150819050919050565b5f82825260208201905092915050565b7f4661696c656420746f2073656e6420466c6f77000000000000000000000000005f82015250565b5f6102356013836101f1565b915061024082610201565b602082019050919050565b5f6020820190508181035f83015261026281610229565b9050919050565b5f6102738261012b565b9050919050565b61028381610269565b82525050565b5f819050919050565b5f6102ac6102a76102a28461012b565b610289565b61012b565b9050919050565b5f6102bd82610292565b9050919050565b5f6102ce826102b3565b9050919050565b6102de816102c4565b82525050565b5f819050919050565b6102f6816102e4565b82525050565b5f60608201905061030f5f83018661027a565b61031c60208301856102d5565b61032960408301846102ed565b94935050505056fea264697066735822122098caf1372532fe479c7df9f46e6fde68a0389c81b4a013a70088c2d701b1409864736f6c63430008180033" - } - ] - } - ] - } } - } \ No newline at end of file + }, + "deployments": { + "emulator": { + "emulator-account": [ + "FungibleTokenMetadataViews", + "Burner", + "FUSD", + "ViewResolver", + "CryptoUtils", + { + "name": "FlowEVMPassthrough", + "args": [ + { + "type": "String", + "value": "608060405234801561000f575f80fd5b506103678061001d5f395ff3fe60806040526004361061001d575f3560e01c80637ebbc23914610021575b5f80fd5b61003b60048036038101906100369190610185565b61003d565b005b5f808273ffffffffffffffffffffffffffffffffffffffff1634604051610063906101dd565b5f6040518083038185875af1925050503d805f811461009d576040519150601f19603f3d011682016040523d82523d5f602084013e6100a2565b606091505b5091509150816100e7576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016100de9061024b565b60405180910390fd5b7fe4fc403eef87e48e40029827def82ae478cfa60ca7726e7f52e09d5384076e6633843460405161011a939291906102fc565b60405180910390a1505050565b5f80fd5b5f73ffffffffffffffffffffffffffffffffffffffff82169050919050565b5f6101548261012b565b9050919050565b6101648161014a565b811461016e575f80fd5b50565b5f8135905061017f8161015b565b92915050565b5f6020828403121561019a57610199610127565b5b5f6101a784828501610171565b91505092915050565b5f81905092915050565b50565b5f6101c85f836101b0565b91506101d3826101ba565b5f82019050919050565b5f6101e7826101bd565b9150819050919050565b5f82825260208201905092915050565b7f4661696c656420746f2073656e6420466c6f77000000000000000000000000005f82015250565b5f6102356013836101f1565b915061024082610201565b602082019050919050565b5f6020820190508181035f83015261026281610229565b9050919050565b5f6102738261012b565b9050919050565b61028381610269565b82525050565b5f819050919050565b5f6102ac6102a76102a28461012b565b610289565b61012b565b9050919050565b5f6102bd82610292565b9050919050565b5f6102ce826102b3565b9050919050565b6102de816102c4565b82525050565b5f819050919050565b6102f6816102e4565b82525050565b5f60608201905061030f5f83018661027a565b61031c60208301856102d5565b61032960408301846102ed565b94935050505056fea264697066735822122098caf1372532fe479c7df9f46e6fde68a0389c81b4a013a70088c2d701b1409864736f6c63430008180033" + } + ] + } + ] + } + } +} \ No newline at end of file From 1210192e701cf819b40533c74e7a36e5b5ee223c Mon Sep 17 00:00:00 2001 From: Giovanni Sanchez <108043524+sisyphusSmiling@users.noreply.github.com> Date: Wed, 14 Feb 2024 16:19:31 -0600 Subject: [PATCH 18/29] rename coa creation transaction --- .../evm/{create_account.cdc => create_coa_and_fund.cdc} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename cadence/transactions/evm/{create_account.cdc => create_coa_and_fund.cdc} (100%) diff --git a/cadence/transactions/evm/create_account.cdc b/cadence/transactions/evm/create_coa_and_fund.cdc similarity index 100% rename from cadence/transactions/evm/create_account.cdc rename to cadence/transactions/evm/create_coa_and_fund.cdc From 28296e1ae33a25b02aa06c7f21ca1e8194afaba4 Mon Sep 17 00:00:00 2001 From: Giovanni Sanchez <108043524+sisyphusSmiling@users.noreply.github.com> Date: Wed, 14 Feb 2024 17:01:42 -0600 Subject: [PATCH 19/29] fix account.ts Cadence --- lib/flow/account.ts | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/lib/flow/account.ts b/lib/flow/account.ts index 3ba6a97..8557458 100644 --- a/lib/flow/account.ts +++ b/lib/flow/account.ts @@ -1,8 +1,8 @@ import * as fcl from "@onflow/fcl" import * as t from "@onflow/types" -import {encodeKey} from "@onflow/util-encode-key" +import { encodeKey } from "@onflow/util-encode-key" import publicConfig from "../publicConfig" -import {sendTransaction} from "./send" +import { sendTransaction } from "./send" const accountCreatedEventType = "flow.AccountCreated" @@ -11,16 +11,16 @@ import CryptoUtils from ${publicConfig.contractFlowToken} import FlowToken from ${publicConfig.contractFlowToken} import FungibleToken from ${publicConfig.contractFungibleToken} -transaction(publicKey: String, flowTokenAmount: UFix64, sigAlgorithm: SignatureAlgorithm, hashAlgorithm: HashAlgorithm) { +transaction(publicKey: String, flowTokenAmount: UFix64, sigAlgorithm: UInt8, hashAlgorithm: UInt8) { let tokenAdmin: &FlowToken.Administrator let tokenReceiver: &{FungibleToken.Receiver} prepare(signer: auth(BorrowValue) &Account) { - let account = AuthAccount(payer: signer) + let account = Account(payer: signer) - let signatureAlgorithm: UInt8 = CryptoUtils.getSigAlgo(fromRawValue: sigAlgorithm) + let signatureAlgorithm = CryptoUtils.getSigAlgo(fromRawValue: sigAlgorithm) ?? panic("Invalid SignatureAlgorithm") - let hashAlgorithm: UInt8 = CryptoUtils.getHashAlgo(fromRawValue: sigAlgorithm) + let hashAlgorithm = CryptoUtils.getHashAlgo(fromRawValue: sigAlgorithm) ?? panic("Invalid HashAlgorithm") let key = PublicKey( @@ -36,7 +36,7 @@ transaction(publicKey: String, flowTokenAmount: UFix64, sigAlgorithm: SignatureA self.tokenAdmin = signer.storage.borrow<&FlowToken.Administrator>(from: /storage/flowTokenAdmin) ?? panic("Signer is not the token admin") - self.tokenReceiver = account.capabilities.borrow<&{FungibleToken.Receiver}>(/public/flowTokenReceiver)! + self.tokenReceiver = account.capabilities.borrow<&{FungibleToken.Receiver}>(/public/flowTokenReceiver) ?? panic("Unable to borrow receiver reference") } From c4779d266009f72891afbdec780793f313915ab0 Mon Sep 17 00:00:00 2001 From: Giovanni Sanchez <108043524+sisyphusSmiling@users.noreply.github.com> Date: Wed, 14 Feb 2024 17:02:02 -0600 Subject: [PATCH 20/29] update evm transactions --- .../transactions/evm/create_coa_and_fund.cdc | 2 +- .../create_flow_account_with_coa_and_fund.cdc | 64 +++++++++++++++++++ .../transactions/evm/passthrough_evm_mint.cdc | 34 ++++++++++ .../evm/passthrough_evm_transfer.cdc | 6 -- 4 files changed, 99 insertions(+), 7 deletions(-) create mode 100644 cadence/transactions/evm/create_flow_account_with_coa_and_fund.cdc create mode 100644 cadence/transactions/evm/passthrough_evm_mint.cdc diff --git a/cadence/transactions/evm/create_coa_and_fund.cdc b/cadence/transactions/evm/create_coa_and_fund.cdc index a153a81..edcd415 100644 --- a/cadence/transactions/evm/create_coa_and_fund.cdc +++ b/cadence/transactions/evm/create_coa_and_fund.cdc @@ -23,6 +23,6 @@ transaction(amount: UFix64) { account.address().deposit(from: <-self.sentVault) log(account.balance()) - self.auth.storage.save<@EVM.BridgedAccount>(<-account, to: StoragePath(identifier: "evm")!) + self.auth.storage.save(<-account, to: StoragePath(identifier: "evm")!) } } 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 new file mode 100644 index 0000000..5df9e16 --- /dev/null +++ b/cadence/transactions/evm/create_flow_account_with_coa_and_fund.cdc @@ -0,0 +1,64 @@ +import "FungibleToken" +import "FlowToken" + +import "EVM" + +import "CryptoUtils" + +/// Creates a Flow account with a COA and saves it in the created account. The provided amount is then deposited into +/// the Flow account at the ratio provided and the remaining amount is deposited into the newly created COA. +/// +transaction( + publicKey: String, + flowTokenAmount: UFix64, + sigAlgorithm: UInt8, + hashAlgorithm: UInt8, + fundingRatio: UFix64 +) { + let tokenAdmin: &FlowToken.Administrator + let newAccount: auth(Keys, Storage) &Account + let tokenReceiver: &{FungibleToken.Receiver} + + prepare(signer: auth(Storage) &Account) { + self.newAccount = Account(payer: signer) + + let signatureAlgorithm = CryptoUtils.getSigAlgo(fromRawValue: sigAlgorithm) + ?? panic("Invalid SignatureAlgorithm") + let hashAlgorithm = CryptoUtils.getHashAlgo(fromRawValue: sigAlgorithm) + ?? panic("Invalid HashAlgorithm") + + let key = PublicKey( + publicKey: publicKey.decodeHex(), + signatureAlgorithm: signatureAlgorithm + ) + self.newAccount.keys.add( + publicKey: key, + hashAlgorithm: hashAlgorithm, + weight: 1000.0 + ) + + self.tokenAdmin = signer.storage.borrow<&FlowToken.Administrator>(from: /storage/flowTokenAdmin) + ?? panic("Signer is not the token admin") + + self.tokenReceiver = self.newAccount.capabilities.borrow<&{FungibleToken.Receiver}>(/public/flowTokenReceiver) + ?? panic("Unable to borrow receiver reference") + } + + pre { + fundingRatio <= 1.0: "Funding mix must be less than or equal to 1.0" + } + + execute { + let minter <- self.tokenAdmin.createNewMinter(allowedAmount: flowTokenAmount) + let flowVault <- minter.mintTokens(amount: flowTokenAmount * fundingRatio) + let evmVault <- minter.mintTokens(amount: flowTokenAmount * (1.0 - fundingRatio)) + destroy minter + + self.tokenReceiver.deposit(from: <-flowVault) + + let coa <- EVM.createBridgedAccount() + coa.address().deposit(from: <-evmVault) + + self.newAccount.storage.save(<-coa, to: StoragePath(identifier: "evm")!) + } +} diff --git a/cadence/transactions/evm/passthrough_evm_mint.cdc b/cadence/transactions/evm/passthrough_evm_mint.cdc new file mode 100644 index 0000000..87fe279 --- /dev/null +++ b/cadence/transactions/evm/passthrough_evm_mint.cdc @@ -0,0 +1,34 @@ +import "FungibleToken" +import "FlowToken" + +import "EVM" + +import "FlowEVMPassthrough" + +/// Mints Flow and transfers it to the given EVM address via the saved Passthrough resource. +/// +transaction(to: EVM.EVMAddress, amount: UFix64, gasLimit: UInt64) { + + let tokenAdmin: &FlowToken.Administrator + let auth: auth(Storage) &Account + let tokenReceiver: &{FungibleToken.Receiver} + let passthrough: &FlowEVMPassthrough.Passthrough + + prepare(signer: auth(BorrowValue) &Account) { + self.tokenAdmin = signer.storage.borrow<&FlowToken.Administrator>(from: /storage/flowTokenAdmin) + ?? panic("Signer is not the token admin") + + self.tokenReceiver = signer.capabilities.borrow<&{FungibleToken.Receiver}>(/public/flowTokenReceiver) + ?? panic("Unable to borrow receiver reference") + self.passthrough = signer.storage.borrow<&FlowEVMPassthrough.Passthrough>(from: FlowEVMPassthrough.StoragePath) + ?? panic("Could not borrow reference to the owner's Passthrough!") + } + + execute { + let minter <- self.tokenAdmin.createNewMinter(allowedAmount: amount) + let mintedVault <- minter.mintTokens(amount: amount) + destroy minter + + self.passthrough.evmTransfer(from: <-mintedVault, to: to, gasLimit: gasLimit) + } +} diff --git a/cadence/transactions/evm/passthrough_evm_transfer.cdc b/cadence/transactions/evm/passthrough_evm_transfer.cdc index 1f674d2..fa7498e 100644 --- a/cadence/transactions/evm/passthrough_evm_transfer.cdc +++ b/cadence/transactions/evm/passthrough_evm_transfer.cdc @@ -22,12 +22,6 @@ transaction(to: EVM.EVMAddress, amount: UFix64, gasLimit: UInt64) { } execute { - let bytes = toAsHex.decodeHex() - let to = EVM.EVMAddress( - bytes: [ - bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5], bytes[6], bytes[7], bytes[8], bytes[9], - bytes[10], bytes[11], bytes[12], bytes[13], bytes[14], bytes[15], bytes[16], bytes[17], bytes[18], bytes[19] - ]) self.passthrough.evmTransfer(from: <-self.sentVault, to: to, gasLimit: gasLimit) } } From 10b0256b117adf9d757559a5a8210696fb927e6e Mon Sep 17 00:00:00 2001 From: Giovanni Sanchez <108043524+sisyphusSmiling@users.noreply.github.com> Date: Wed, 14 Feb 2024 17:29:42 -0600 Subject: [PATCH 21/29] update dev-deploy-contracts command --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 7d30781..e950720 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,7 @@ "db-migrate-deploy": "prisma migrate deploy --preview-feature", "db-seed": "node prisma/seed.mjs", "deploy": "npm run build && npm run db-migrate-deploy && npm run db-seed", - "dev-deploy-contracts": "flow project deploy --network=emulator --update", + "dev-deploy-contracts": "flow evm create-account 1000.0 && flow project deploy --network=emulator --update", "initial-transactions": "ts-node lib/initialTransactions.ts --loader ts-node/esm", "lint": "eslint .", "lint-fix": "eslint . --fix", From 24c4f826324a912c81ad9512eacc6e2edf1e340f Mon Sep 17 00:00:00 2001 From: Giovanni Sanchez <108043524+sisyphusSmiling@users.noreply.github.com> Date: Thu, 15 Feb 2024 13:25:30 -0600 Subject: [PATCH 22/29] remove passthrough Cadence & solidity contracts --- cadence/contracts/FlowEVMPassthrough.cdc | 153 ----------- .../evm/get_passthrough_evm_address.cdc | 8 - .../transactions/evm/passthrough_evm_mint.cdc | 34 --- .../evm/passthrough_evm_transfer.cdc | 27 -- .../transactions/evm/setup_passthrough.cdc | 23 -- env.example | 1 - flow.json | 239 +++++++++--------- lib/fclConfig.ts | 1 - lib/publicConfig.ts | 3 - solidity/src/FlowPassthrough.sol | 14 - 10 files changed, 114 insertions(+), 389 deletions(-) delete mode 100644 cadence/contracts/FlowEVMPassthrough.cdc delete mode 100644 cadence/scripts/evm/get_passthrough_evm_address.cdc delete mode 100644 cadence/transactions/evm/passthrough_evm_mint.cdc delete mode 100644 cadence/transactions/evm/passthrough_evm_transfer.cdc delete mode 100644 cadence/transactions/evm/setup_passthrough.cdc delete mode 100644 solidity/src/FlowPassthrough.sol diff --git a/cadence/contracts/FlowEVMPassthrough.cdc b/cadence/contracts/FlowEVMPassthrough.cdc deleted file mode 100644 index 6df409c..0000000 --- a/cadence/contracts/FlowEVMPassthrough.cdc +++ /dev/null @@ -1,153 +0,0 @@ -import "FungibleToken" -import "FlowToken" - -import "EVM" - -/// This contract defines a resource implementing FungibleToken Receiver and Provider interfaces that enables deposits -/// to an encapsulated COA and transfers to EVM addresses. Recipients in Flow EVM will receive the transfer from the -/// FlowPassthrough.sol contract address, but an event is emitted in both the Cadence & EVM environments denoting the -/// source and target addresses involved in the passthrough transfer. -/// -access(all) contract FlowEVMPassthrough { - - /// The address of the passthrough Solidity contract - access(all) let passthroughEVMAddress: EVM.EVMAddress - - /// Default storage path for the Passthrough resource - access(all) let StoragePath: StoragePath - /// Default public path for the Passthrough resource - access(all) let PublicPath: PublicPath - - /// Event emitted when a transfer occurs, denoting the transferor, recipient and amount transfered via passthrough - access(all) event Transfered(from: EVM.EVMAddress, to: EVM.EVMAddress, amount: UFix64) - - /* --- Public Methods --- */ - /// - access(all) fun createPassthrough(with coa: Capability<&EVM.BridgedAccount>): @Passthrough { - return <-create Passthrough(coa) - } - - /* --- Passthrough --- */ - // - /// Encapsulates a COA and provides a FungibleToken Receiver and Provider interface through which to deposit & - /// withdraw to/from the COA. Also enables transfers to EVM addresses using a call to a passthrough Solidity - /// contract. - /// - access(all) resource Passthrough : FungibleToken.Receiver, FungibleToken.Provider { - /// Capability to owner's COA - access(self) let coaCapability: Capability<&EVM.BridgedAccount> - - init(_ coaCapability: Capability<&EVM.BridgedAccount>) { - pre { - coaCapability.check(): "Invalid COA Capability provided" - } - self.coaCapability = coaCapability - } - - /// getSupportedVaultTypes optionally returns a list of vault types that this receiver accepts - access(all) view fun getSupportedVaultTypes(): {Type: Bool} { - return { Type<@FlowToken.Vault>(): true } - } - - /// Returns whether or not the given type is accepted by the Receiver - /// A vault that can accept any type should just return true by default - access(all) view fun isSupportedVaultType(type: Type): Bool { - return self.getSupportedVaultTypes()[type] ?? false - } - - /// Deposits Flow to encapsulated COA according to Receiver interface - access(all) fun deposit(from: @{FungibleToken.Vault}) { - pre { - self.isSupportedVaultType(type: from.getType()): "Passthrough only supports FlowToken.Vaults" - } - let flowVault <- from as! @FlowToken.Vault - self.borrowCOA().deposit(from: <-flowVault) - } - - access(FungibleToken.Withdrawable) fun withdraw(amount: UFix64): @{FungibleToken.Vault} { - return <-self.borrowCOA().withdraw( - balance: EVM.Balance(flow: amount) - ) - } - - /// Deposits Flow to defined EVM Address using a call to passthrough Solidity contract - access(all) fun evmTransfer(from: @{FungibleToken.Vault}, to: EVM.EVMAddress, gasLimit: UInt64) { - pre { - self.isSupportedVaultType(type: from.getType()): "Passthrough only supports FlowToken.Vaults" - } - let amount = from.getBalance() - // TODO: Replace with UInt256(EVM.Balance(flow: from.balance).toAttoFlow()) - let amountInAttoFlow = FlowEVMPassthrough.ufix64ToUInt256(value: from.getBalance(), decimals: 8) - // TODO: Replace with EVM.encodeABIWithSignature("transferFlow(address)", [to]) - let calldata = FlowEVMPassthrough.encodeABIWithSignature( - "transferFlow(address)", - [to] - ) - let coa = self.borrowCOA() - let flowVault <- from as! @FlowToken.Vault - coa.deposit(from: <-flowVault) - coa.call( - to: FlowEVMPassthrough.passthroughEVMAddress, - data: calldata, - gasLimit: gasLimit, - value: EVM.Balance(flow: amount) - ) - - emit Transfered(from: self.borrowCOA().address(), to: to, amount: amount) - } - - access(self) fun borrowCOA(): &EVM.BridgedAccount { - return self.coaCapability.borrow() ?? panic("COA Capability has been revoked and is no longer valid") - } - } - - /* --- Internal Helpers --- */ - // TODO: Remove helpers once EVM.Balance.toAttoFlow() is implemented - - /// Raises the base to the power of the exponent - access(self) view fun pow(base: UInt256, exponent: UInt8): UInt256 { - if exponent == 0 { - return 1 - } - var r = base - var exp: UInt8 = 1 - while exp < exponent { - r = r * base - exp = exp + 1 - } - return r - } - - /// Converts a UFix64 to a UInt256 - access(self) view fun ufix64ToUInt256(value: UFix64, decimals: UInt8): UInt256 { - let integerPart: UInt64 = UInt64(value) - var r = UInt256(integerPart) - var multiplier: UInt256 = self.pow(base:10, exponent: decimals) - return r * multiplier - } - - access(self) fun encodeABIWithSignature( - _ signature: String, - _ values: [AnyStruct] - ): [UInt8] { - let methodID = HashAlgorithm.KECCAK_256.hash( - signature.utf8 - ).slice(from: 0, upTo: 4) - let arguments = EVM.encodeABI(values) - - return methodID.concat(arguments) - } - - init(passthroughBytecode: String) { - let coa = self.account.storage.borrow<&EVM.BridgedAccount>(from: /storage/evm) - ?? panic("COA not found") - let passthroughEVMAddress = coa.deploy( - code: passthroughBytecode.decodeHex(), - gasLimit: 12000000, - value: EVM.Balance(flow: 0.0) - ) - self.passthroughEVMAddress = passthroughEVMAddress - self.StoragePath = /storage/flowEVMPassthrough - self.PublicPath = /public/flowEVMPassthroughPublic - } -} diff --git a/cadence/scripts/evm/get_passthrough_evm_address.cdc b/cadence/scripts/evm/get_passthrough_evm_address.cdc deleted file mode 100644 index ad103d0..0000000 --- a/cadence/scripts/evm/get_passthrough_evm_address.cdc +++ /dev/null @@ -1,8 +0,0 @@ -import "EVM" - -import "FlowEVMPassthrough" - -/// Returns the EVM address of the FlowEVM passthrough contract. -access(all) fun main(): EVM.EVMAddress { - return FlowEVMPassthrough.passthroughEVMAddress -} diff --git a/cadence/transactions/evm/passthrough_evm_mint.cdc b/cadence/transactions/evm/passthrough_evm_mint.cdc deleted file mode 100644 index 87fe279..0000000 --- a/cadence/transactions/evm/passthrough_evm_mint.cdc +++ /dev/null @@ -1,34 +0,0 @@ -import "FungibleToken" -import "FlowToken" - -import "EVM" - -import "FlowEVMPassthrough" - -/// Mints Flow and transfers it to the given EVM address via the saved Passthrough resource. -/// -transaction(to: EVM.EVMAddress, amount: UFix64, gasLimit: UInt64) { - - let tokenAdmin: &FlowToken.Administrator - let auth: auth(Storage) &Account - let tokenReceiver: &{FungibleToken.Receiver} - let passthrough: &FlowEVMPassthrough.Passthrough - - prepare(signer: auth(BorrowValue) &Account) { - self.tokenAdmin = signer.storage.borrow<&FlowToken.Administrator>(from: /storage/flowTokenAdmin) - ?? panic("Signer is not the token admin") - - self.tokenReceiver = signer.capabilities.borrow<&{FungibleToken.Receiver}>(/public/flowTokenReceiver) - ?? panic("Unable to borrow receiver reference") - self.passthrough = signer.storage.borrow<&FlowEVMPassthrough.Passthrough>(from: FlowEVMPassthrough.StoragePath) - ?? panic("Could not borrow reference to the owner's Passthrough!") - } - - execute { - let minter <- self.tokenAdmin.createNewMinter(allowedAmount: amount) - let mintedVault <- minter.mintTokens(amount: amount) - destroy minter - - self.passthrough.evmTransfer(from: <-mintedVault, to: to, gasLimit: gasLimit) - } -} diff --git a/cadence/transactions/evm/passthrough_evm_transfer.cdc b/cadence/transactions/evm/passthrough_evm_transfer.cdc deleted file mode 100644 index fa7498e..0000000 --- a/cadence/transactions/evm/passthrough_evm_transfer.cdc +++ /dev/null @@ -1,27 +0,0 @@ -import "FungibleToken" -import "FlowToken" - -import "EVM" - -import "FlowEVMPassthrough" - -/// Withdraws tokens from the signer's FlowToken Vault and transfers them to the given EVM address via the saved -/// Passthrough resource. -/// -transaction(to: EVM.EVMAddress, amount: UFix64, gasLimit: UInt64) { - - let sentVault: @{FungibleToken.Vault} - let passthrough: &FlowEVMPassthrough.Passthrough - - prepare(signer: auth(BorrowValue) &Account) { - let sourceVault = signer.storage.borrow(from: /storage/flowTokenVault) - ?? panic("Could not borrow reference to the owner's Vault!") - self.sentVault <- sourceVault.withdraw(amount: amount) - self.passthrough = signer.storage.borrow<&FlowEVMPassthrough.Passthrough>(from: FlowEVMPassthrough.StoragePath) - ?? panic("Could not borrow reference to the owner's Passthrough!") - } - - execute { - self.passthrough.evmTransfer(from: <-self.sentVault, to: to, gasLimit: gasLimit) - } -} diff --git a/cadence/transactions/evm/setup_passthrough.cdc b/cadence/transactions/evm/setup_passthrough.cdc deleted file mode 100644 index 5dd39b8..0000000 --- a/cadence/transactions/evm/setup_passthrough.cdc +++ /dev/null @@ -1,23 +0,0 @@ -import "EVM" - -import "FlowEVMPassthrough" - -/// Configures a Passthrough resource in the signer's storage -/// -transaction { - - prepare(signer: auth(BorrowValue, IssueStorageCapabilityController, PublishCapability, SaveValue) &Account) { - if signer.storage.borrow<&FlowEVMPassthrough.Passthrough>(from: FlowEVMPassthrough.StoragePath) != nil { - return - } - if signer.storage.borrow<&EVM.BridgedAccount>(from: /storage/evm) == nil { - signer.storage.save(<-EVM.createBridgedAccount(), to: /storage/evm) - } - let coaCapability = signer.capabilities.storage.issue<&EVM.BridgedAccount>(/storage/evm) - signer.storage.save(<-FlowEVMPassthrough.createPassthrough(with: coaCapability), to: FlowEVMPassthrough.StoragePath) - let passthroughCapability = signer.capabilities.storage.issue<&FlowEVMPassthrough.Passthrough>( - FlowEVMPassthrough.StoragePath - ) - signer.capabilities.publish(passthroughCapability, at: FlowEVMPassthrough.PublicPath) - } -} diff --git a/env.example b/env.example index 2d138c6..f491c60 100644 --- a/env.example +++ b/env.example @@ -13,7 +13,6 @@ NEXT_PUBLIC_CONTRACT_FLOW_TOKEN=0x0ae53cb6e3f42a79 NEXT_PUBLIC_CONTRACT_FUSD=0xf8d6e0586b0a20c7 NEXT_PUBLIC_CONTRACT_EVM=0xf8d6e0586b0a20c7 NEXT_PUBLIC_CONTRACT_CRYPTO_UTILS=0xf8d6e0586b0a20c7 -NEXT_PUBLIC_CONTRACT_FLOW_EVM_PASSTHROUGH=0xf8d6e0586b0a20c7 NEXT_PUBLIC_GA_MEASUREMENT_ID= SIGNER_HASH_ALGO=SHA2_256 diff --git a/flow.json b/flow.json index a40bc0d..d0b1e21 100644 --- a/flow.json +++ b/flow.json @@ -1,127 +1,116 @@ { - "contracts": { - "Burner": { - "source": "./cadence/contracts/Burner.cdc", - "aliases": { - "emulator": "ee82856bf20e2aa6" - } - }, - "CryptoUtils": { - "source": "./cadence/contracts/CryptoUtils.cdc", - "aliases": { - "emulator": "f8d6e0586b0a20c7" - } - }, - "EVM": { - "source": "./cadence/contracts/EVM.cdc", - "aliases": { - "emulator": "f8d6e0586b0a20c7" - } - }, - "FUSD": { - "source": "./cadence/contracts/FUSD.cdc", - "aliases": { - "crescendo": "e223d8a629e49c68", - "emulator": "0ae53cb6e3f42a79", - "testnet": "e223d8a629e49c68" - } - }, - "FlowEVMPassthrough": { - "source": "./cadence/contracts/FlowEVMPassthrough.cdc", - "aliases": { - "emulator": "f8d6e0586b0a20c7" - } - }, - "FlowToken": { - "source": "./cadence/contracts/FlowToken.cdc", - "aliases": { - "crescendo": "7e60df042a9c0868", - "emulator": "0ae53cb6e3f42a79", - "testnet": "7e60df042a9c0868" - } - }, - "FungibleToken": { - "source": "./cadence/contracts/FungibleToken.cdc", - "aliases": { - "crescendo": "9a0766d93b6608b7", - "emulator": "ee82856bf20e2aa6", - "testnet": "9a0766d93b6608b7" - } - }, - "FungibleTokenMetadataViews": { - "source": "./cadence/contracts/FungibleTokenMetadataViews.cdc", - "aliases": { - "crescendo": "f233dcee88fe0abe", - "emulator": "f8d6e0586b0a20c7", - "testnet": "f233dcee88fe0abe" - } - }, - "MetadataViews": { - "source": "./cadence/contracts/MetadataViews.cdc", - "aliases": { - "crescendo": "0x631e88ae7f1d7c20", - "emulator": "f8d6e0586b0a20c7", - "testnet": "631e88ae7f1d7c20" - } - }, - "NonFungibleToken": { - "source": "./cadence/contracts/NonFungibleToken.cdc", - "aliases": { - "crescendo": "0x631e88ae7f1d7c20", - "emulator": "f8d6e0586b0a20c7", - "testnet": "631e88ae7f1d7c20" - } - }, - "ViewResolver": { - "source": "./cadence/contracts/ViewResolver.cdc", - "aliases": { - "crescendo": "0x631e88ae7f1d7c20", - "emulator": "f8d6e0586b0a20c7", - "testnet": "631e88ae7f1d7c20" - } - } - }, - "networks": { - "crescendo": "access.crescendo.nodes.onflow.org:9000", - "emulator": "127.0.0.1:3569", - "testnet": "access.devnet.nodes.onflow.org:9000" - }, - "accounts": { - "emulator-account": { - "address": "f8d6e0586b0a20c7", - "key": "91a22fbd87392b019fbe332c32695c14cf2ba5b6521476a8540228bdf1987068" - }, - "emulator-flow": { - "address": "0ae53cb6e3f42a79", - "key": "686779d775e5fcbf8d2f4a85cb4c53525d02b7ef53230d180fc16f35d9b7d025" - }, - "emulator-ft": { - "address": "ee82856bf20e2aa6", - "key": "686779d775e5fcbf8d2f4a85cb4c53525d02b7ef53230d180fc16f35d9b7d025" - }, - "testnet-account": { - "address": "${NEXT_PUBLIC_SIGNER_ADDRESS}", - "keys": "${SIGNER_PRIVATE_KEY}" - } - }, - "deployments": { - "emulator": { - "emulator-account": [ - "FungibleTokenMetadataViews", - "Burner", - "FUSD", - "ViewResolver", - "CryptoUtils", - { - "name": "FlowEVMPassthrough", - "args": [ - { - "type": "String", - "value": "608060405234801561000f575f80fd5b506103678061001d5f395ff3fe60806040526004361061001d575f3560e01c80637ebbc23914610021575b5f80fd5b61003b60048036038101906100369190610185565b61003d565b005b5f808273ffffffffffffffffffffffffffffffffffffffff1634604051610063906101dd565b5f6040518083038185875af1925050503d805f811461009d576040519150601f19603f3d011682016040523d82523d5f602084013e6100a2565b606091505b5091509150816100e7576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016100de9061024b565b60405180910390fd5b7fe4fc403eef87e48e40029827def82ae478cfa60ca7726e7f52e09d5384076e6633843460405161011a939291906102fc565b60405180910390a1505050565b5f80fd5b5f73ffffffffffffffffffffffffffffffffffffffff82169050919050565b5f6101548261012b565b9050919050565b6101648161014a565b811461016e575f80fd5b50565b5f8135905061017f8161015b565b92915050565b5f6020828403121561019a57610199610127565b5b5f6101a784828501610171565b91505092915050565b5f81905092915050565b50565b5f6101c85f836101b0565b91506101d3826101ba565b5f82019050919050565b5f6101e7826101bd565b9150819050919050565b5f82825260208201905092915050565b7f4661696c656420746f2073656e6420466c6f77000000000000000000000000005f82015250565b5f6102356013836101f1565b915061024082610201565b602082019050919050565b5f6020820190508181035f83015261026281610229565b9050919050565b5f6102738261012b565b9050919050565b61028381610269565b82525050565b5f819050919050565b5f6102ac6102a76102a28461012b565b610289565b61012b565b9050919050565b5f6102bd82610292565b9050919050565b5f6102ce826102b3565b9050919050565b6102de816102c4565b82525050565b5f819050919050565b6102f6816102e4565b82525050565b5f60608201905061030f5f83018661027a565b61031c60208301856102d5565b61032960408301846102ed565b94935050505056fea264697066735822122098caf1372532fe479c7df9f46e6fde68a0389c81b4a013a70088c2d701b1409864736f6c63430008180033" - } - ] - } - ] - } - } + "contracts": { + "Burner": { + "source": "./cadence/contracts/Burner.cdc", + "aliases": { + "emulator": "ee82856bf20e2aa6" + } + }, + "CryptoUtils": { + "source": "./cadence/contracts/CryptoUtils.cdc", + "aliases": { + "emulator": "f8d6e0586b0a20c7" + } + }, + "EVM": { + "source": "./cadence/contracts/EVM.cdc", + "aliases": { + "emulator": "f8d6e0586b0a20c7" + } + }, + "FUSD": { + "source": "./cadence/contracts/FUSD.cdc", + "aliases": { + "crescendo": "e223d8a629e49c68", + "emulator": "0ae53cb6e3f42a79", + "testnet": "e223d8a629e49c68" + } + }, + "FlowToken": { + "source": "./cadence/contracts/FlowToken.cdc", + "aliases": { + "crescendo": "7e60df042a9c0868", + "emulator": "0ae53cb6e3f42a79", + "testnet": "7e60df042a9c0868" + } + }, + "FungibleToken": { + "source": "./cadence/contracts/FungibleToken.cdc", + "aliases": { + "crescendo": "9a0766d93b6608b7", + "emulator": "ee82856bf20e2aa6", + "testnet": "9a0766d93b6608b7" + } + }, + "FungibleTokenMetadataViews": { + "source": "./cadence/contracts/FungibleTokenMetadataViews.cdc", + "aliases": { + "crescendo": "f233dcee88fe0abe", + "emulator": "f8d6e0586b0a20c7", + "testnet": "f233dcee88fe0abe" + } + }, + "MetadataViews": { + "source": "./cadence/contracts/MetadataViews.cdc", + "aliases": { + "crescendo": "631e88ae7f1d7c20", + "emulator": "f8d6e0586b0a20c7", + "testnet": "631e88ae7f1d7c20" + } + }, + "NonFungibleToken": { + "source": "./cadence/contracts/NonFungibleToken.cdc", + "aliases": { + "crescendo": "631e88ae7f1d7c20", + "emulator": "f8d6e0586b0a20c7", + "testnet": "631e88ae7f1d7c20" + } + }, + "ViewResolver": { + "source": "./cadence/contracts/ViewResolver.cdc", + "aliases": { + "crescendo": "631e88ae7f1d7c20", + "emulator": "f8d6e0586b0a20c7", + "testnet": "631e88ae7f1d7c20" + } + } + }, + "networks": { + "crescendo": "access.crescendo.nodes.onflow.org:9000", + "emulator": "127.0.0.1:3569", + "testnet": "access.devnet.nodes.onflow.org:9000" + }, + "accounts": { + "emulator-account": { + "address": "f8d6e0586b0a20c7", + "key": "91a22fbd87392b019fbe332c32695c14cf2ba5b6521476a8540228bdf1987068" + }, + "emulator-flow": { + "address": "0ae53cb6e3f42a79", + "key": "686779d775e5fcbf8d2f4a85cb4c53525d02b7ef53230d180fc16f35d9b7d025" + }, + "emulator-ft": { + "address": "ee82856bf20e2aa6", + "key": "686779d775e5fcbf8d2f4a85cb4c53525d02b7ef53230d180fc16f35d9b7d025" + }, + "test": { + "address": "179b6b1cb6755e31", + "key": "6d8452af4ffac3d09276ce58358b0fb7f959b48437bf368c1d4b9c02588cabda" + }, + "testnet-account": { + "address": "${NEXT_PUBLIC_SIGNER_ADDRESS}", + "keys": "${SIGNER_PRIVATE_KEY}" + } + }, + "deployments": { + "emulator": { + "emulator-account": [ + "FungibleTokenMetadataViews", + "Burner", + "FUSD", + "ViewResolver", + "CryptoUtils" + ] + } + } } \ No newline at end of file diff --git a/lib/fclConfig.ts b/lib/fclConfig.ts index 0d0db0d..ddccd7f 100644 --- a/lib/fclConfig.ts +++ b/lib/fclConfig.ts @@ -13,4 +13,3 @@ config() .put("0xFUSDADDRESS", publicConfig.contractFUSD) .put("0xEVMADDRESS", publicConfig.contractEVM) .put("0xCRYPTOUTILSADDRESS", publicConfig.contractCryptoUtils) - .put("0xFLOWEVMPASSTHROUGHADDRESS", publicConfig.contractFlowEVMPassthrough) diff --git a/lib/publicConfig.ts b/lib/publicConfig.ts index 95feab2..c3dd4b8 100644 --- a/lib/publicConfig.ts +++ b/lib/publicConfig.ts @@ -33,9 +33,6 @@ if (!contractEVM) throw "Missing NEXT_PUBLIC_CONTRACT_EVM" const contractCryptoUtils = process.env.NEXT_PUBLIC_CONTRACT_CRYPTO_UTILS if (!contractCryptoUtils) throw "Missing NEXT_PUBLIC_CONTRACT_CRYPTO_UTILS" -const contractFlowEVMPassthrough = process.env.NEXT_PUBLIC_CONTRACT_FLOW_EVM_PASSTHROUGH -if (!contractFlowEVMPassthrough) throw "Missing NEXT_PUBLIC_CONTRACT_FLOW_EVM_PASSTHROUGH" - const walletDiscovery = process.env.NEXT_PUBLIC_WALLET_DISCOVERY // TODO: Integrate FCL wallets // if (!walletDiscovery) throw "Missing NEXT_PUBLIC_WALLET_DISCOVERY" diff --git a/solidity/src/FlowPassthrough.sol b/solidity/src/FlowPassthrough.sol deleted file mode 100644 index ef6799c..0000000 --- a/solidity/src/FlowPassthrough.sol +++ /dev/null @@ -1,14 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0 - -pragma solidity >=0.7.0 <0.9.0; - -contract FlowPassthrough { - - event Tranfered(address from, address to, uint256 amount); - - function transferFlow(address payable _to) public payable { - (bool sent, bytes memory data) = _to.call{value: msg.value}(""); - require(sent, "Failed to send Flow"); - emit Tranfered(msg.sender, _to, msg.value); - } -} \ No newline at end of file From 6450b226530b88c2441e09c4a9f108112a093df1 Mon Sep 17 00:00:00 2001 From: Giovanni Sanchez <108043524+sisyphusSmiling@users.noreply.github.com> Date: Thu, 15 Feb 2024 13:26:07 -0600 Subject: [PATCH 23/29] add EVM funding & COA transactions --- .../transactions/evm/create_coa_and_fund.cdc | 15 ++++--- .../evm/mint_and_fund_evm_address.cdc | 43 +++++++++++++++++++ 2 files changed, 51 insertions(+), 7 deletions(-) create mode 100644 cadence/transactions/evm/mint_and_fund_evm_address.cdc diff --git a/cadence/transactions/evm/create_coa_and_fund.cdc b/cadence/transactions/evm/create_coa_and_fund.cdc index edcd415..7976805 100644 --- a/cadence/transactions/evm/create_coa_and_fund.cdc +++ b/cadence/transactions/evm/create_coa_and_fund.cdc @@ -7,7 +7,7 @@ import "EVM" /// transaction(amount: UFix64) { let sentVault: @FlowToken.Vault - let auth: auth(Storage) &Account + let coa: &EVM.BridgedAccount prepare(signer: auth(Storage) &Account) { let vaultRef = signer.storage.borrow( @@ -15,14 +15,15 @@ transaction(amount: UFix64) { ) ?? panic("Could not borrow reference to the owner's Vault!") self.sentVault <- vaultRef.withdraw(amount: amount) as! @FlowToken.Vault - self.auth = signer + if signer.storage.borrow<&EVM.BridgedAccount>(from: /storage/EVM) != nil { + signer.storage.save(<-EVM.createBridgedAccount(), to: /storage/evm) + } + self.coa = signer.storage.borrow<&EVM.BridgedAccount>(from: /storage/EVM) + ?? panic("Could not borrow reference to the signer's COA!") + } execute { - let account <- EVM.createBridgedAccount() - account.address().deposit(from: <-self.sentVault) - - log(account.balance()) - self.auth.storage.save(<-account, to: StoragePath(identifier: "evm")!) + self.coa.deposit(from: <-self.sentVault) } } diff --git a/cadence/transactions/evm/mint_and_fund_evm_address.cdc b/cadence/transactions/evm/mint_and_fund_evm_address.cdc new file mode 100644 index 0000000..e471f95 --- /dev/null +++ b/cadence/transactions/evm/mint_and_fund_evm_address.cdc @@ -0,0 +1,43 @@ +import "FungibleToken" +import "FlowToken" + +import "EVM" + +/// Mints Flow and transfers it to the given EVM address via the signer's CadenceOwnedAccount. +/// +transaction(to: EVM.EVMAddress, amount: UFix64, gasLimit: UInt64) { + + let tokenAdmin: &FlowToken.Administrator + let tokenReceiver: &{FungibleToken.Receiver} + let coa: &EVM.BridgedAccount + + prepare(signer: auth(BorrowValue) &Account) { + self.tokenAdmin = signer.storage.borrow<&FlowToken.Administrator>(from: /storage/flowTokenAdmin) + ?? panic("Signer is not the token admin") + + self.tokenReceiver = signer.capabilities.borrow<&{FungibleToken.Receiver}>(/public/flowTokenReceiver) + ?? panic("Unable to borrow receiver reference") + self.coa = signer.storage.borrow<&EVM.BridgedAccount>(from: /storage/evm) + ?? panic("Could not borrow reference to the signer's COA!") + } + + execute { + let minter <- self.tokenAdmin.createNewMinter(allowedAmount: amount) + let mintedVault <- minter.mintTokens(amount: amount) + destroy minter + + // TODO: REMOVE + let bytes = toStr.decodeHex() + let to = EVM.EVMAddress(bytes: [ + bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5], bytes[6], bytes[7], bytes[8], bytes[9], + bytes[10], bytes[11], bytes[12], bytes[13], bytes[14], bytes[15], bytes[16], bytes[17], bytes[18], bytes[19] + ]) + self.coa.deposit(from: <-mintedVault) + self.coa.call( + to: to, + data: [], + gasLimit: gasLimit, + value: EVM.Balance(flow: amount), + ) + } +} From 352fec45d13690e2ddfd969bd483b07565302e80 Mon Sep 17 00:00:00 2001 From: Giovanni Sanchez <108043524+sisyphusSmiling@users.noreply.github.com> Date: Thu, 15 Feb 2024 13:57:55 -0600 Subject: [PATCH 24/29] remove CryptoUtils --- cadence/contracts/CryptoUtils.cdc | 34 ------------------- .../transactions/evm/create_coa_and_fund.cdc | 8 ++--- .../create_flow_account_with_coa_and_fund.cdc | 13 +++---- .../evm/mint_and_fund_evm_address.cdc | 11 +++--- env.example | 1 - flow.json | 9 +---- lib/fclConfig.ts | 1 - lib/flow/account.ts | 5 ++- lib/publicConfig.ts | 3 -- 9 files changed, 18 insertions(+), 67 deletions(-) delete mode 100644 cadence/contracts/CryptoUtils.cdc diff --git a/cadence/contracts/CryptoUtils.cdc b/cadence/contracts/CryptoUtils.cdc deleted file mode 100644 index c464d80..0000000 --- a/cadence/contracts/CryptoUtils.cdc +++ /dev/null @@ -1,34 +0,0 @@ -/// Util methods to reconstruct HashAlgorithm and SignatureAlgorithm from their raw enum values -/// -access(all) contract CryptoUtils { - - access(all) fun getHashAlgo(fromRawValue: UInt8): HashAlgorithm? { - switch fromRawValue { - case HashAlgorithm.SHA2_256.rawValue: - return HashAlgorithm.SHA2_256 - case HashAlgorithm.SHA2_384.rawValue: - return HashAlgorithm.SHA2_384 - case HashAlgorithm.SHA3_256.rawValue: - return HashAlgorithm.SHA3_256 - case HashAlgorithm.SHA3_384.rawValue: - return HashAlgorithm.SHA3_384 - case HashAlgorithm.KMAC128_BLS_BLS12_381.rawValue: - return HashAlgorithm.KMAC128_BLS_BLS12_381 - case HashAlgorithm.KECCAK_256.rawValue: - return HashAlgorithm.KECCAK_256 - default: - return nil - } - } - - access(all) fun getSigAlgo(fromRawValue: UInt8): SignatureAlgorithm? { - switch fromRawValue { - case SignatureAlgorithm.ECDSA_P256.rawValue: - return SignatureAlgorithm.ECDSA_P256 - case SignatureAlgorithm.ECDSA_secp256k1.rawValue: - return SignatureAlgorithm.ECDSA_secp256k1 - default: - return nil - } - } -} \ No newline at end of file diff --git a/cadence/transactions/evm/create_coa_and_fund.cdc b/cadence/transactions/evm/create_coa_and_fund.cdc index 7976805..316073d 100644 --- a/cadence/transactions/evm/create_coa_and_fund.cdc +++ b/cadence/transactions/evm/create_coa_and_fund.cdc @@ -3,7 +3,8 @@ import "FlowToken" import "EVM" -/// Creates a COA and saves it in the signer's Flow account & passing the given value of Flow into FlowEVM +/// Creates a COA and saves it in the signer's Flow account (if one doesn't already exist at the expected path) and +/// transfers the given value of Flow into FlowEVM, funding with the signer's Flow Vault. /// transaction(amount: UFix64) { let sentVault: @FlowToken.Vault @@ -15,12 +16,11 @@ transaction(amount: UFix64) { ) ?? 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 { + if signer.storage.borrow<&EVM.BridgedAccount>(from: /storage/evm) == nil { signer.storage.save(<-EVM.createBridgedAccount(), to: /storage/evm) } - self.coa = signer.storage.borrow<&EVM.BridgedAccount>(from: /storage/EVM) + self.coa = signer.storage.borrow<&EVM.BridgedAccount>(from: /storage/evm) ?? panic("Could not borrow reference to the signer's COA!") - } execute { 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 5df9e16..8de4f8b 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 @@ -3,10 +3,9 @@ import "FlowToken" import "EVM" -import "CryptoUtils" - -/// Creates a Flow account with a COA and saves it in the created account. The provided amount is then deposited into -/// the Flow account at the ratio provided and the remaining amount is deposited into the newly created COA. +/// Creates a Flow account with a COA and saves it in the created account. The provided amount is then minted, +/// deposited into the Flow account at the specified ratio and the remaining amount is deposited into the newly +/// created COA. /// transaction( publicKey: String, @@ -22,10 +21,12 @@ transaction( prepare(signer: auth(Storage) &Account) { self.newAccount = Account(payer: signer) - let signatureAlgorithm = CryptoUtils.getSigAlgo(fromRawValue: sigAlgorithm) + let signatureAlgorithm = SignatureAlgorithm(sigAlgorithm) ?? panic("Invalid SignatureAlgorithm") - let hashAlgorithm = CryptoUtils.getHashAlgo(fromRawValue: sigAlgorithm) + log(signatureAlgorithm) + let hashAlgorithm = HashAlgorithm(hashAlgorithm) ?? panic("Invalid HashAlgorithm") + log(hashAlgorithm) let key = PublicKey( publicKey: publicKey.decodeHex(), diff --git a/cadence/transactions/evm/mint_and_fund_evm_address.cdc b/cadence/transactions/evm/mint_and_fund_evm_address.cdc index e471f95..696bece 100644 --- a/cadence/transactions/evm/mint_and_fund_evm_address.cdc +++ b/cadence/transactions/evm/mint_and_fund_evm_address.cdc @@ -11,12 +11,15 @@ transaction(to: EVM.EVMAddress, amount: UFix64, gasLimit: UInt64) { let tokenReceiver: &{FungibleToken.Receiver} let coa: &EVM.BridgedAccount - prepare(signer: auth(BorrowValue) &Account) { + prepare(signer: auth(Storage) &Account) { self.tokenAdmin = signer.storage.borrow<&FlowToken.Administrator>(from: /storage/flowTokenAdmin) ?? panic("Signer is not the token admin") self.tokenReceiver = signer.capabilities.borrow<&{FungibleToken.Receiver}>(/public/flowTokenReceiver) ?? panic("Unable to borrow receiver reference") + if signer.storage.borrow<&EVM.BridgedAccount>(from: /storage/evm) == nil { + signer.storage.save(<-EVM.createBridgedAccount(), to: /storage/evm) + } self.coa = signer.storage.borrow<&EVM.BridgedAccount>(from: /storage/evm) ?? panic("Could not borrow reference to the signer's COA!") } @@ -26,12 +29,6 @@ transaction(to: EVM.EVMAddress, amount: UFix64, gasLimit: UInt64) { let mintedVault <- minter.mintTokens(amount: amount) destroy minter - // TODO: REMOVE - let bytes = toStr.decodeHex() - let to = EVM.EVMAddress(bytes: [ - bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5], bytes[6], bytes[7], bytes[8], bytes[9], - bytes[10], bytes[11], bytes[12], bytes[13], bytes[14], bytes[15], bytes[16], bytes[17], bytes[18], bytes[19] - ]) self.coa.deposit(from: <-mintedVault) self.coa.call( to: to, diff --git a/env.example b/env.example index f491c60..fd34221 100644 --- a/env.example +++ b/env.example @@ -12,7 +12,6 @@ NEXT_PUBLIC_CONTRACT_FUNGIBLE_TOKEN=0xee82856bf20e2aa6 NEXT_PUBLIC_CONTRACT_FLOW_TOKEN=0x0ae53cb6e3f42a79 NEXT_PUBLIC_CONTRACT_FUSD=0xf8d6e0586b0a20c7 NEXT_PUBLIC_CONTRACT_EVM=0xf8d6e0586b0a20c7 -NEXT_PUBLIC_CONTRACT_CRYPTO_UTILS=0xf8d6e0586b0a20c7 NEXT_PUBLIC_GA_MEASUREMENT_ID= SIGNER_HASH_ALGO=SHA2_256 diff --git a/flow.json b/flow.json index d0b1e21..a7c0259 100644 --- a/flow.json +++ b/flow.json @@ -6,12 +6,6 @@ "emulator": "ee82856bf20e2aa6" } }, - "CryptoUtils": { - "source": "./cadence/contracts/CryptoUtils.cdc", - "aliases": { - "emulator": "f8d6e0586b0a20c7" - } - }, "EVM": { "source": "./cadence/contracts/EVM.cdc", "aliases": { @@ -108,8 +102,7 @@ "FungibleTokenMetadataViews", "Burner", "FUSD", - "ViewResolver", - "CryptoUtils" + "ViewResolver" ] } } diff --git a/lib/fclConfig.ts b/lib/fclConfig.ts index ddccd7f..5a63ca2 100644 --- a/lib/fclConfig.ts +++ b/lib/fclConfig.ts @@ -12,4 +12,3 @@ config() .put("0xFLOWTOKENADDRESS", publicConfig.contractFlowToken) .put("0xFUSDADDRESS", publicConfig.contractFUSD) .put("0xEVMADDRESS", publicConfig.contractEVM) - .put("0xCRYPTOUTILSADDRESS", publicConfig.contractCryptoUtils) diff --git a/lib/flow/account.ts b/lib/flow/account.ts index 8557458..9a819ff 100644 --- a/lib/flow/account.ts +++ b/lib/flow/account.ts @@ -7,7 +7,6 @@ import { sendTransaction } from "./send" const accountCreatedEventType = "flow.AccountCreated" const txCreateAccount = ` -import CryptoUtils from ${publicConfig.contractFlowToken} import FlowToken from ${publicConfig.contractFlowToken} import FungibleToken from ${publicConfig.contractFungibleToken} @@ -18,9 +17,9 @@ transaction(publicKey: String, flowTokenAmount: UFix64, sigAlgorithm: UInt8, has prepare(signer: auth(BorrowValue) &Account) { let account = Account(payer: signer) - let signatureAlgorithm = CryptoUtils.getSigAlgo(fromRawValue: sigAlgorithm) + let signatureAlgorithm = SignatureAlgorithm(sigAlgorithm) ?? panic("Invalid SignatureAlgorithm") - let hashAlgorithm = CryptoUtils.getHashAlgo(fromRawValue: sigAlgorithm) + let hashAlgorithm = HashAlgorithm(hashAlgorithm) ?? panic("Invalid HashAlgorithm") let key = PublicKey( diff --git a/lib/publicConfig.ts b/lib/publicConfig.ts index c3dd4b8..a07339d 100644 --- a/lib/publicConfig.ts +++ b/lib/publicConfig.ts @@ -30,9 +30,6 @@ if (!contractFUSD) throw "Missing NEXT_PUBLIC_CONTRACT_FUSD" const contractEVM = process.env.NEXT_PUBLIC_CONTRACT_EVM if (!contractEVM) throw "Missing NEXT_PUBLIC_CONTRACT_EVM" -const contractCryptoUtils = process.env.NEXT_PUBLIC_CONTRACT_CRYPTO_UTILS -if (!contractCryptoUtils) throw "Missing NEXT_PUBLIC_CONTRACT_CRYPTO_UTILS" - const walletDiscovery = process.env.NEXT_PUBLIC_WALLET_DISCOVERY // TODO: Integrate FCL wallets // if (!walletDiscovery) throw "Missing NEXT_PUBLIC_WALLET_DISCOVERY" From a037f9edb8f24bfdb82cf74a54c9dbb4ce6e7ba9 Mon Sep 17 00:00:00 2001 From: Giovanni Sanchez <108043524+sisyphusSmiling@users.noreply.github.com> Date: Thu, 15 Feb 2024 14:23:32 -0600 Subject: [PATCH 25/29] update npm run dev-deploy-contracts command --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index e950720..7d30781 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,7 @@ "db-migrate-deploy": "prisma migrate deploy --preview-feature", "db-seed": "node prisma/seed.mjs", "deploy": "npm run build && npm run db-migrate-deploy && npm run db-seed", - "dev-deploy-contracts": "flow evm create-account 1000.0 && flow project deploy --network=emulator --update", + "dev-deploy-contracts": "flow project deploy --network=emulator --update", "initial-transactions": "ts-node lib/initialTransactions.ts --loader ts-node/esm", "lint": "eslint .", "lint-fix": "eslint . --fix", From 399f0d595526d410c513f568724026d31e20a244 Mon Sep 17 00:00:00 2001 From: Giovanni Sanchez <108043524+sisyphusSmiling@users.noreply.github.com> Date: Thu, 15 Feb 2024 14:41:37 -0600 Subject: [PATCH 26/29] update ts formatting --- lib/flow/account.ts | 109 ++++++++++++++++++++++---------------------- lib/flow/fund.ts | 82 ++++++++++++++++----------------- lib/publicConfig.ts | 2 +- 3 files changed, 96 insertions(+), 97 deletions(-) diff --git a/lib/flow/account.ts b/lib/flow/account.ts index 9a819ff..4229bfe 100644 --- a/lib/flow/account.ts +++ b/lib/flow/account.ts @@ -1,6 +1,5 @@ import * as fcl from "@onflow/fcl" import * as t from "@onflow/types" -import { encodeKey } from "@onflow/util-encode-key" import publicConfig from "../publicConfig" import { sendTransaction } from "./send" @@ -11,32 +10,32 @@ import FlowToken from ${publicConfig.contractFlowToken} import FungibleToken from ${publicConfig.contractFungibleToken} transaction(publicKey: String, flowTokenAmount: UFix64, sigAlgorithm: UInt8, hashAlgorithm: UInt8) { - let tokenAdmin: &FlowToken.Administrator - let tokenReceiver: &{FungibleToken.Receiver} - - prepare(signer: auth(BorrowValue) &Account) { - let account = Account(payer: signer) - - let signatureAlgorithm = SignatureAlgorithm(sigAlgorithm) - ?? panic("Invalid SignatureAlgorithm") - let hashAlgorithm = HashAlgorithm(hashAlgorithm) - ?? panic("Invalid HashAlgorithm") - - let key = PublicKey( - publicKey: publicKey.decodeHex(), - signatureAlgorithm: signatureAlgorithm - ) - account.keys.add( - publicKey: key, - hashAlgorithm: hashAlgorithm, - weight: 1000.0 - ) + let tokenAdmin: &FlowToken.Administrator + let tokenReceiver: &{FungibleToken.Receiver} + + prepare(signer: auth(BorrowValue) &Account) { + let account = Account(payer: signer) + + let signatureAlgorithm = SignatureAlgorithm(sigAlgorithm) + ?? panic("Invalid SignatureAlgorithm") + let hashAlgorithm = HashAlgorithm(hashAlgorithm) + ?? panic("Invalid HashAlgorithm") + + let key = PublicKey( + publicKey: publicKey.decodeHex(), + signatureAlgorithm: signatureAlgorithm + ) + account.keys.add( + publicKey: key, + hashAlgorithm: hashAlgorithm, + weight: 1000.0 + ) self.tokenAdmin = signer.storage.borrow<&FlowToken.Administrator>(from: /storage/flowTokenAdmin) - ?? panic("Signer is not the token admin") + ?? panic("Signer is not the token admin") self.tokenReceiver = account.capabilities.borrow<&{FungibleToken.Receiver}>(/public/flowTokenReceiver) - ?? panic("Unable to borrow receiver reference") + ?? panic("Unable to borrow receiver reference") } execute { @@ -51,37 +50,37 @@ transaction(publicKey: String, flowTokenAmount: UFix64, sigAlgorithm: UInt8, has ` export async function createAccount( - publicKey: string, - sigAlgo: number, - hashAlgo: number, - authorization: fcl.Authorization + publicKey: string, + sigAlgo: number, + hashAlgo: number, + authorization: fcl.Authorization ) { - const result = await sendTransaction({ - transaction: txCreateAccount, - args: [ - fcl.arg(publicKey, t.String), - fcl.arg(publicConfig.tokenAmountFlow, t.UFix64), - fcl.arg(sigAlgo.toString(), t.UInt8), - fcl.arg(hashAlgo.toString(), t.UInt8), - ], - authorizations: [authorization], - payer: authorization, - proposer: authorization, - }) - - const accountCreatedEvent = result.events.find( - (event: fcl.Event) => event.type === accountCreatedEventType - ) - - if (!accountCreatedEvent) { - throw "Transaction did not emit account creation event" - } - - const address = accountCreatedEvent.data.address - const transactionId = accountCreatedEvent.transactionId - - return { - address, - transactionId, - } + const result = await sendTransaction({ + transaction: txCreateAccount, + args: [ + fcl.arg(publicKey, t.String), + fcl.arg(publicConfig.tokenAmountFlow, t.UFix64), + fcl.arg(sigAlgo.toString(), t.UInt8), + fcl.arg(hashAlgo.toString(), t.UInt8), + ], + authorizations: [authorization], + payer: authorization, + proposer: authorization, + }) + + const accountCreatedEvent = result.events.find( + (event: fcl.Event) => event.type === accountCreatedEventType + ) + + if (!accountCreatedEvent) { + throw "Transaction did not emit account creation event" + } + + const address = accountCreatedEvent.data.address + const transactionId = accountCreatedEvent.transactionId + + return { + address, + transactionId, + } } diff --git a/lib/flow/fund.ts b/lib/flow/fund.ts index b23d59b..526d1b7 100644 --- a/lib/flow/fund.ts +++ b/lib/flow/fund.ts @@ -9,16 +9,16 @@ import FlowToken from ${publicConfig.contractFlowToken} import FungibleToken from ${publicConfig.contractFungibleToken} transaction(address: Address, amount: UFix64) { - let tokenAdmin: &FlowToken.Administrator - let tokenReceiver: &{FungibleToken.Receiver} + let tokenAdmin: &FlowToken.Administrator + let tokenReceiver: &{FungibleToken.Receiver} - prepare(signer: AuthAccount) { - self.tokenAdmin = signer.storage.borrow<&FlowToken.Administrator>(from: /storage/flowTokenAdmin) - ?? panic("Signer is not the token admin") + prepare(signer: AuthAccount) { + self.tokenAdmin = signer.storage.borrow<&FlowToken.Administrator>(from: /storage/flowTokenAdmin) + ?? panic("Signer is not the token admin") - self.tokenReceiver = getAccount(address).capabilities.borrow<&{FungibleToken.Receiver}>( - /public/flowTokenReceiver - ) ?? panic("Could not borrow receiver reference to the recipient's Vault") + self.tokenReceiver = getAccount(address).capabilities.borrow<&{FungibleToken.Receiver}>( + /public/flowTokenReceiver + ) ?? panic("Could not borrow receiver reference to the recipient's Vault") } execute { @@ -37,52 +37,52 @@ import FUSD from ${publicConfig.contractFUSD} import FungibleToken from ${publicConfig.contractFungibleToken} transaction(address: Address, amount: UFix64) { - let tokenMinter: auth(FUSD.ProxyOwner) &FUSD.MinterProxy - let tokenReceiver: &{FungibleToken.Receiver} - - prepare(minterAccount: AuthAccount) { - self.tokenMinter = minterAccount.storage.borrow( - from: FUSD.MinterProxyStoragePath - ) ?? panic("No minter available") - - self.tokenReceiver = getAccount(address).capabilities.borrow<&{FungibleToken.Receiver}>( - /public/fusdReceiver - ) ?? panic("Unable to borrow receiver reference") - } - - execute { - let mintedVault <- self.tokenMinter.mintTokens(amount: amount) - self.tokenReceiver.deposit(from: <-mintedVault) - } + let tokenMinter: auth(FUSD.ProxyOwner) &FUSD.MinterProxy + let tokenReceiver: &{FungibleToken.Receiver} + + prepare(minterAccount: AuthAccount) { + self.tokenMinter = minterAccount.storage.borrow( + from: FUSD.MinterProxyStoragePath + ) ?? panic("No minter available") + + self.tokenReceiver = getAccount(address).capabilities.borrow<&{FungibleToken.Receiver}>( + /public/fusdReceiver + ) ?? panic("Unable to borrow receiver reference") + } + + execute { + let mintedVault <- self.tokenMinter.mintTokens(amount: amount) + self.tokenReceiver.deposit(from: <-mintedVault) + } } ` type TokenType = "FLOW" | "FUSD" type Token = { - tx: string - amount: string + tx: string + amount: string } type Tokens = Record export const tokens: Tokens = { - FLOW: {tx: txFundAccountFLOW, amount: TOKEN_FUNDING_AMOUNTS[FLOW_TYPE]}, - FUSD: {tx: txFundAccountFUSD, amount: TOKEN_FUNDING_AMOUNTS[FUSD_TYPE]}, + FLOW: {tx: txFundAccountFLOW, amount: TOKEN_FUNDING_AMOUNTS[FLOW_TYPE]}, + FUSD: {tx: txFundAccountFUSD, amount: TOKEN_FUNDING_AMOUNTS[FUSD_TYPE]}, } export async function fundAccount( - address: string, - token: TokenType, - authorization: fcl.Authorization + address: string, + token: TokenType, + authorization: fcl.Authorization ) { - const {tx, amount} = tokens[token] + const {tx, amount} = tokens[token] - await sendTransaction({ - transaction: tx, - args: [fcl.arg(address, t.Address), fcl.arg(amount, t.UFix64)], - authorizations: [authorization], - payer: authorization, - proposer: authorization, - }) + await sendTransaction({ + transaction: tx, + args: [fcl.arg(address, t.Address), fcl.arg(amount, t.UFix64)], + authorizations: [authorization], + payer: authorization, + proposer: authorization, + }) - return amount + return amount } diff --git a/lib/publicConfig.ts b/lib/publicConfig.ts index a07339d..6dbc192 100644 --- a/lib/publicConfig.ts +++ b/lib/publicConfig.ts @@ -1,4 +1,4 @@ -import { Networks, TokenTypes } from "./constants" +import {Networks, TokenTypes} from "./constants" const network = process.env.NEXT_PUBLIC_NETWORK as Networks | undefined if (!network) throw "Missing NEXT_PUBLIC_NETWORK" From d94652a7144d645aa53791da5aabc02e24a164ab Mon Sep 17 00:00:00 2001 From: Giovanni Sanchez <108043524+sisyphusSmiling@users.noreply.github.com> Date: Thu, 15 Feb 2024 14:58:54 -0600 Subject: [PATCH 27/29] update ts formatting & add ci EVM contract env alias --- .github/workflows/ci.yml | 1 + lib/flow/account.ts | 66 ++++++++++++++++++++-------------------- lib/flow/fund.ts | 32 +++++++++---------- 3 files changed, 50 insertions(+), 49 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4600b59..5ef3648 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -34,6 +34,7 @@ jobs: NEXT_PUBLIC_CONTRACT_FUNGIBLE_TOKEN: "0xee82856bf20e2aa6" NEXT_PUBLIC_CONTRACT_FLOW_TOKEN: "0x0ae53cb6e3f42a79" NEXT_PUBLIC_CONTRACT_FUSD: "0xf8d6e0586b0a20c7" + NEXT_PUBLIC_CONTRACT_EVM: "0xf8d6e0586b0a20c7" NEXT_PUBLIC_NETWORK: "testnet" strategy: diff --git a/lib/flow/account.ts b/lib/flow/account.ts index 4229bfe..bf20c48 100644 --- a/lib/flow/account.ts +++ b/lib/flow/account.ts @@ -1,7 +1,7 @@ import * as fcl from "@onflow/fcl" import * as t from "@onflow/types" import publicConfig from "../publicConfig" -import { sendTransaction } from "./send" +import {sendTransaction} from "./send" const accountCreatedEventType = "flow.AccountCreated" @@ -50,37 +50,37 @@ transaction(publicKey: String, flowTokenAmount: UFix64, sigAlgorithm: UInt8, has ` export async function createAccount( - publicKey: string, - sigAlgo: number, - hashAlgo: number, - authorization: fcl.Authorization + publicKey: string, + sigAlgo: number, + hashAlgo: number, + authorization: fcl.Authorization ) { - const result = await sendTransaction({ - transaction: txCreateAccount, - args: [ - fcl.arg(publicKey, t.String), - fcl.arg(publicConfig.tokenAmountFlow, t.UFix64), - fcl.arg(sigAlgo.toString(), t.UInt8), - fcl.arg(hashAlgo.toString(), t.UInt8), - ], - authorizations: [authorization], - payer: authorization, - proposer: authorization, - }) - - const accountCreatedEvent = result.events.find( - (event: fcl.Event) => event.type === accountCreatedEventType - ) - - if (!accountCreatedEvent) { - throw "Transaction did not emit account creation event" - } - - const address = accountCreatedEvent.data.address - const transactionId = accountCreatedEvent.transactionId - - return { - address, - transactionId, - } + const result = await sendTransaction({ + transaction: txCreateAccount, + args: [ + fcl.arg(publicKey, t.String), + fcl.arg(publicConfig.tokenAmountFlow, t.UFix64), + fcl.arg(sigAlgo.toString(), t.UInt8), + fcl.arg(hashAlgo.toString(), t.UInt8), + ], + authorizations: [authorization], + payer: authorization, + proposer: authorization, + }) + + const accountCreatedEvent = result.events.find( + (event: fcl.Event) => event.type === accountCreatedEventType + ) + + if (!accountCreatedEvent) { + throw "Transaction did not emit account creation event" + } + + const address = accountCreatedEvent.data.address + const transactionId = accountCreatedEvent.transactionId + + return { + address, + transactionId, + } } diff --git a/lib/flow/fund.ts b/lib/flow/fund.ts index 526d1b7..af5e2b7 100644 --- a/lib/flow/fund.ts +++ b/lib/flow/fund.ts @@ -59,30 +59,30 @@ transaction(address: Address, amount: UFix64) { type TokenType = "FLOW" | "FUSD" type Token = { - tx: string - amount: string + tx: string + amount: string } type Tokens = Record export const tokens: Tokens = { - FLOW: {tx: txFundAccountFLOW, amount: TOKEN_FUNDING_AMOUNTS[FLOW_TYPE]}, - FUSD: {tx: txFundAccountFUSD, amount: TOKEN_FUNDING_AMOUNTS[FUSD_TYPE]}, + FLOW: {tx: txFundAccountFLOW, amount: TOKEN_FUNDING_AMOUNTS[FLOW_TYPE]}, + FUSD: {tx: txFundAccountFUSD, amount: TOKEN_FUNDING_AMOUNTS[FUSD_TYPE]}, } export async function fundAccount( - address: string, - token: TokenType, - authorization: fcl.Authorization + address: string, + token: TokenType, + authorization: fcl.Authorization ) { - const {tx, amount} = tokens[token] + const {tx, amount} = tokens[token] - await sendTransaction({ - transaction: tx, - args: [fcl.arg(address, t.Address), fcl.arg(amount, t.UFix64)], - authorizations: [authorization], - payer: authorization, - proposer: authorization, - }) + await sendTransaction({ + transaction: tx, + args: [fcl.arg(address, t.Address), fcl.arg(amount, t.UFix64)], + authorizations: [authorization], + payer: authorization, + proposer: authorization, + }) - return amount + return amount } From 70b5e50af93ca8a9a4903d1d17cb31f56778e9ee Mon Sep 17 00:00:00 2001 From: Giovanni Sanchez <108043524+sisyphusSmiling@users.noreply.github.com> Date: Thu, 15 Feb 2024 15:03:46 -0600 Subject: [PATCH 28/29] add contractEVM to PublicConfig --- lib/publicConfig.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/publicConfig.ts b/lib/publicConfig.ts index 6dbc192..531580a 100644 --- a/lib/publicConfig.ts +++ b/lib/publicConfig.ts @@ -45,6 +45,7 @@ export type PublicConfig = { contractFungibleToken: string contractFlowToken: string contractFUSD: string + contractEVM: string accessAPIHost: string isLocal: boolean walletDiscovery?: string @@ -63,6 +64,7 @@ const publicConfig: PublicConfig = { contractFungibleToken, contractFlowToken, contractFUSD, + contractEVM, accessAPIHost: process.env.NEXT_PUBLIC_ACCESS_API_HOST || "http://localhost:8080", isLocal: process.env.NEXT_PUBLIC_IS_LOCAL === "true" || false, From df81478e253bcf3cb2f1c7af7fbd085cce23207d Mon Sep 17 00:00:00 2001 From: Giovanni Sanchez <108043524+sisyphusSmiling@users.noreply.github.com> Date: Wed, 21 Feb 2024 10:56:30 -0600 Subject: [PATCH 29/29] update mint_and_fund_evm_address transaction --- cadence/transactions/evm/mint_and_fund_evm_address.cdc | 3 --- 1 file changed, 3 deletions(-) diff --git a/cadence/transactions/evm/mint_and_fund_evm_address.cdc b/cadence/transactions/evm/mint_and_fund_evm_address.cdc index 696bece..f4a2df8 100644 --- a/cadence/transactions/evm/mint_and_fund_evm_address.cdc +++ b/cadence/transactions/evm/mint_and_fund_evm_address.cdc @@ -8,15 +8,12 @@ import "EVM" transaction(to: EVM.EVMAddress, amount: UFix64, gasLimit: UInt64) { let tokenAdmin: &FlowToken.Administrator - let tokenReceiver: &{FungibleToken.Receiver} let coa: &EVM.BridgedAccount prepare(signer: auth(Storage) &Account) { self.tokenAdmin = signer.storage.borrow<&FlowToken.Administrator>(from: /storage/flowTokenAdmin) ?? panic("Signer is not the token admin") - self.tokenReceiver = signer.capabilities.borrow<&{FungibleToken.Receiver}>(/public/flowTokenReceiver) - ?? panic("Unable to borrow receiver reference") if signer.storage.borrow<&EVM.BridgedAccount>(from: /storage/evm) == nil { signer.storage.save(<-EVM.createBridgedAccount(), to: /storage/evm) }