Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adds USDCFlow, README, and transactions, and basic tests #2

Merged
merged 6 commits into from
Aug 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ jobs:
restore-keys: |
${{ runner.os }}-go-
- name: Install Flow CLI
run: sh -ci "$(curl -fsSL https://raw.githubusercontent.com/onflow/flow-cli/master/install.sh)" -- v1.5.0
run: sh -ci "$(curl -fsSL https://raw.githubusercontent.com/onflow/flow-cli/master/install.sh)"
- name: Flow CLI Version
run: flow version
- name: Update PATH
Expand Down
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
#.gitignore
#.env
.env
coverage.json
7 changes: 7 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
.PHONY: test
test:
flow test --cover --covercode="contracts" tests/*.cdc

.PHONY: ci
ci:
flow test --cover --covercode="contracts" tests/*.cdc
86 changes: 84 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,86 @@
# Flow Bridged USDC

This repo holds the Flow Cadence Wrapped USDC contract as well as
the Flow bridged USDC standard for Flow EVM
This repo holds the `USDCFlow` smart contract, the Cadence
Fungible Token contract that acts as the Cadence bridged version of
Flow EVM USDC.

# What is Flow?

Flow is the blockchain for open worlds. Read more about it [here](https://www.flow.com/).

# What is Cadence?

Cadence is a Resource-oriented programming language
for developing smart contracts for the Flow Blockchain.
Read more about it [here](https://www.cadence-lang.org)

We recommend that anyone who is reading this should have already
completed the [Cadence Tutorials](https://cadence-lang.org/docs/tutorial/first-steps)
so they can build a basic understanding of the programming language.

# USDCFlow

`contracts/USDCFlow.cdc`

| Network | Contract Address |
| ------------- | -------------------- |
| Previewnet | Coming Soon |
| Testnet | `` |
| Mainnet | Coming Soon |

This is the contract that defines the Cadence version of Flow USDC.
Before the Sept 4th Crescendo migration, users can send
old `FiatToken` vaults to the `USDCFlow.wrapFiatToken()` function
and receive `USDCFlow` vaults back with the exact same balance.

After the Crescendo migration, the `USDCFlow` smart contract
will integrate directly with the Flow VM bridge to become
the bridged version of Flow EVM USDC. These tokens will be backed
by real USDC via Flow EVM, so they will retain their value.

This contract will be deployed to a different address
and have a different name than the original `FiatToken`,
so contracts that want to continue to support USDC on Flow
will need to migrate their code and state to the new `USDCFlow`
smart contract.

You can see a guide for how to migrate to the `USDCFlow` contract
in the [Cadence 1.0 Migration Guide](https://cadence-lang.org/docs/cadence-migration-guide/).

You can find transactions and scripts for interacting with the `USDCFlow` contract in the `transactions/` directory.

# Local Development

The contract in this repo is not yet included in the Flow emulator.
If you want to use this contract with the emulator,
you must add it to your `flow.json` and deploy it yourself.

As is, the contract can only mint new USDC if an old `FiatToken.Vault`
is passed into the `wrapFiatToken()` function. If you want to
test with this token on the emulator, you'll either need
to deploy the simplified version of `FiatToken`
in this repo and mint and wrap tokens,
or you will need to uncomment the lines of code
in the `init()` function to mint tokens during deployment.

### Prerequisites

- Install Flow CLI on your machine. For instructions, see the [Flow CLI documentation](https://developers.flow.com/tools/flow-cli/install).

Ensure it is installed with:

```sh
flow version
```

### Run the Tests

To run the tests in this repo, simply navigate
to the root directory and run `make test`.
That will run all the tests in the `tests/` directory.

### Additional Resources

- [Old `FiatToken` code](https://github.com/flow-usdc/flow-usdc)
- [Blog Post Announcing Migration from old USDC to new USDC](https://www.flow.com/post/stablecoins-on-flow-evolving-for-interoperability)
- [Flow VM Bridge Github Repo](https://github.com/onflow/flow-evm-bridge/tree/main)
203 changes: 203 additions & 0 deletions contracts/USDCFlow.cdc
Original file line number Diff line number Diff line change
@@ -0,0 +1,203 @@
import "FungibleToken"
import "MetadataViews"
import "FungibleTokenMetadataViews"
import "FiatToken"

/// USDCFlow
///
/// Defines the Cadence version of bridged Flow USDC.
/// Before the Sept 4th, 2024 Crescendo migration, users can send
/// `FiatToken` vaults to the `USDCFlow.wrapFiatToken()` function
/// and receive `USDCFlow` vaults back with the exact same balance.

/// After the Crescendo migration, the `USDCFlow` smart contract
/// will integrate directly with the Flow VM bridge to become
/// the bridged version of Flow EVM USDC. These tokens will be backed
/// by real USDC via Flow EVM.

/// This is not the official Circle USDC, only a bridged version
/// that is still backed by official USDC on the other side of the bridge

pub contract USDCFlow: FungibleToken {

/// Total supply of USDCFlows in existence
pub var totalSupply: UFix64

/// Storage and Public Paths
pub let VaultStoragePath: StoragePath
pub let VaultPublicPath: PublicPath
pub let ReceiverPublicPath: PublicPath

/// The event that is emitted when the contract is created
pub event TokensInitialized(initialSupply: UFix64)

/// The event that is emitted when tokens are withdrawn from a Vault
pub event TokensWithdrawn(amount: UFix64, from: Address?)

/// The event that is emitted when tokens are deposited to a Vault
pub event TokensDeposited(amount: UFix64, to: Address?)

/// The event that is emitted when new tokens are minted
pub event TokensMinted(amount: UFix64, depositedUUID: UInt64, mintedUUID: UInt64)

pub resource Vault: FungibleToken.Provider, FungibleToken.Receiver, FungibleToken.Balance, MetadataViews.Resolver {

/// The total balance of this vault
pub var balance: UFix64

/// Initialize the balance at resource creation time
init(balance: UFix64) {
self.balance = balance
}

/// withdraw
/// @param amount: The amount of tokens to be withdrawn from the vault
/// @return The Vault resource containing the withdrawn funds
///
pub fun withdraw(amount: UFix64): @FungibleToken.Vault {
self.balance = self.balance - amount
emit TokensWithdrawn(amount: amount, from: self.owner?.address)
return <-create Vault(balance: amount)
}

/// deposit
/// @param from: The Vault resource containing the funds that will be deposited
///
pub fun deposit(from: @FungibleToken.Vault) {
let vault <- from as! @USDCFlow.Vault
self.balance = self.balance + vault.balance
emit TokensDeposited(amount: vault.balance, to: self.owner?.address)
vault.balance = 0.0
destroy vault
}

destroy() {
if self.balance > 0.0 {
USDCFlow.totalSupply = USDCFlow.totalSupply - self.balance
}
}

/// Gets an array of all the Metadata Views implemented by USDCFlow
///
/// @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.
///
pub fun getViews(): [Type] {
return [
Type<FungibleTokenMetadataViews.FTView>(),
Type<FungibleTokenMetadataViews.FTDisplay>(),
Type<FungibleTokenMetadataViews.FTVaultData>(),
Type<FungibleTokenMetadataViews.TotalSupply>()
]
}

/// Resolves Metadata Views out of the USDCFlow
///
/// @param view: The Type of the desired view.
/// @return A structure representing the requested view.
///
pub fun resolveView(_ view: Type): AnyStruct? {
switch view {
case Type<FungibleTokenMetadataViews.FTView>():
return FungibleTokenMetadataViews.FTView(
ftDisplay: self.resolveView(Type<FungibleTokenMetadataViews.FTDisplay>()) as! FungibleTokenMetadataViews.FTDisplay?,
ftVaultData: self.resolveView(Type<FungibleTokenMetadataViews.FTVaultData>()) as! FungibleTokenMetadataViews.FTVaultData?
)
case Type<FungibleTokenMetadataViews.FTDisplay>():
let media = MetadataViews.Media(
file: MetadataViews.HTTPFile(
url: "https://assets.website-files.com/5f6294c0c7a8cdd643b1c820/5f6294c0c7a8cda55cb1c936_Flow_Wordmark.svg"
),
mediaType: "image/svg+xml"
)
let medias = MetadataViews.Medias([media])
return FungibleTokenMetadataViews.FTDisplay(
name: "USDC (Flow)",
symbol: "USDCf",
description: "This fungible token representation of USDC is bridged from Flow EVM.",
joshuahannan marked this conversation as resolved.
Show resolved Hide resolved
externalURL: MetadataViews.ExternalURL("https://www.circle.com/en/usdc"),
logos: medias,
socials: {
"x": MetadataViews.ExternalURL("https://x.com/circle")
}
)
case Type<FungibleTokenMetadataViews.FTVaultData>():
return FungibleTokenMetadataViews.FTVaultData(
storagePath: USDCFlow.VaultStoragePath,
receiverPath: USDCFlow.ReceiverPublicPath,
metadataPath: USDCFlow.VaultPublicPath,
providerPath: /private/usdcFlowVault,
receiverLinkedType: Type<&USDCFlow.Vault{FungibleToken.Receiver}>(),
metadataLinkedType: Type<&USDCFlow.Vault{FungibleToken.Balance, MetadataViews.Resolver}>(),
providerLinkedType: Type<&USDCFlow.Vault{FungibleToken.Provider}>(),
createEmptyVaultFunction: (fun (): @USDCFlow.Vault {
return <-USDCFlow.createEmptyVault()
})
)
case Type<FungibleTokenMetadataViews.TotalSupply>():
return FungibleTokenMetadataViews.TotalSupply(totalSupply: USDCFlow.totalSupply)
}
return nil
}
}

/// createEmptyVault
///
/// @return The new Vault resource with a balance of zero
///
pub fun createEmptyVault(): @Vault {
return <-create Vault(balance: 0.0)
}

/// wrapFiatToken
///
/// Provides a way for users to exchange a FiatToken Vault
/// for a USDCFlow Vault with the same balance
pub fun wrapFiatToken(_ from: @FungibleToken.Vault): @Vault {
post {
result.balance == before(from.balance):
"The USDCFlow Vault that was returned does not have the same balance as the Vault that was deposited!"
}

let vault <- from as! @FiatToken.Vault

// Get a reference to the contract account's stored Vault
let fiatTokenVaultRef = self.account.borrow<&FiatToken.Vault>(from: FiatToken.VaultStoragePath)
?? panic("Could not borrow reference to the owner's FiatToken Vault!")

let wrappedFiatTokenVault <- create Vault(balance: vault.balance)

emit TokensMinted(amount: wrappedFiatTokenVault.balance, depositedUUID: vault.uuid, mintedUUID: wrappedFiatTokenVault.uuid)

fiatTokenVaultRef.deposit(from: <-vault)

self.totalSupply = self.totalSupply + wrappedFiatTokenVault.balance

return <-wrappedFiatTokenVault
}

init() {
self.totalSupply = 0.0
self.VaultStoragePath = /storage/usdcFlowVault
self.VaultPublicPath = /public/usdcFlowMetadata
self.ReceiverPublicPath = /public/usdcFlowReceiver

// Create the Vault with the total supply of tokens and save it in storage.
let vault <- create Vault(balance: self.totalSupply)
self.account.save(<-vault, to: self.VaultStoragePath)

// Create a public capability to the stored Vault that exposes
// the `deposit` method through the `Receiver` interface.
self.account.link<&{FungibleToken.Receiver}>(
self.ReceiverPublicPath,
target: self.VaultStoragePath
)

// Create a public capability to the stored Vault that only exposes
// the `balance` field and the `resolveView` method through the `Balance` interface
self.account.link<&USDCFlow.Vault{FungibleToken.Balance}>(
self.VaultPublicPath,
target: self.VaultStoragePath
)
}
}
Loading
Loading