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

Add fungible token support #28

Merged
merged 61 commits into from
Apr 15, 2024
Merged

Add fungible token support #28

merged 61 commits into from
Apr 15, 2024

Conversation

sisyphusSmiling
Copy link
Contributor

@sisyphusSmiling sisyphusSmiling commented Apr 6, 2024

Closes: #5 #6 #10 #15

Description

This PR adds support for fungible token bridging, largely following patterns & interfaces found in the existing NFT bridge feature set.

In addition, some optimizations were made where low-hanging fruit was found. These include

  • Refactoring ERC identifying fields such as name and symbol with getters. This prevents widely redundant storage at the token-level of fields that are actually contract-level values in ERC contracts.
  • Adding params to access(account methods (such as initializeEscrow) to reduce redundant EVM calls made in a single bridging transaction

As mentioned in the FLIP, support for non-standard cases will be added, but is considered out of scope for the introduction of the feature and will follow in subsequent PRs.


For contributor use:

  • Targeted PR against master branch
  • Linked to Github issue with discussion and accepted design OR link to spec that describes this work.
  • Code follows the standards mentioned here.
  • Updated relevant documentation
  • Re-reviewed Files changed in the Github PR explorer
  • Added appropriate labels

aishairzay and others added 30 commits April 3, 2024 15:57
symbol: onboardingValues.symbol,
erc721Address: onboardingValues.evmContractAddress
)
} else {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might want to make the check explicit for fungible tokens and panic if neither NFT or FT

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since that check is done in several places, I include it in typeRequiresOnboarding() which is validated in the pre-condition. Beyond the pre-check, we know the type is either an NFT or FT. By the point of the if block above, we know it's either NFT or FT, and by the else block it must be FT. Do you think I should organize these checks differently?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense.
I personally like very explicit checks because sometimes assumptions break with future code updates. Not confident it applies here tho

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can see how it would be more resilient to add the explicit checks here. Doesn't hurt to add them so I'll include an else if + else -> panic

cadence/contracts/bridge/FlowEVMBridge.cdc Outdated Show resolved Hide resolved
assert(!isERC20, message: "Contract is mixed asset and is not currently supported by the bridge")
// Derive the contract name from the ERC721 contract
cadenceContractName = FlowEVMBridgeUtils.deriveBridgedNFTContractName(from: evmContractAddress)
} else {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might want an explicit check and panic here too. It stops any errors to fall through the cracks.

Copy link
Contributor Author

@sisyphusSmiling sisyphusSmiling Apr 9, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you mean check isERC20? Further up the call stack, isERC20 would've been called

onboardByEVMAddress
|_evmAddressRequiresOnboarding
  |_FlowEVMBridgeUtils.isValidEVMAsset
    |_isERC20
...
|_deployDefiningContract

When I was testing locally, I found the redundant EVM calls consumed a lot of gas and made a pass at minimizing them.. But if we feel the explicit checks are worth it I can add them back in.

@@ -74,14 +109,37 @@ contract FlowEVMBridge : IFlowEVMNFTBridge {
let feeVault <-feeProvider.withdraw(amount: FlowEVMBridgeConfig.onboardFee) as! @FlowToken.Vault
FlowEVMBridgeUtils.deposit(<-feeVault)
// Deploy an EVM defining contract via the FlowBridgeFactory.sol contract
let erc721Address = self.deployEVMContract(forAssetType: type)
// let evmContractAddress = self.deployEVMContract(forAssetType: type)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can this be removed since it is commented out?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah yeah, thanks for catching that

Comment on lines 161 to +162
/// Maps EVM NFT ID to Flow NFT ID, covering cross-VM project NFTs
access(self)
let evmIDToFlowID: {UInt256: UInt64}
access(self) let evmIDToFlowID: {UInt256: UInt64}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it make sense to have a reverse mapping of Cadence ID to EVM ID?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah that could make sense. To minimize the storage usage, I check against the EVMNFT implementation on demand in getEVMID. It's the same mechanism I would use to assign a value in a reverse mapping but without the associated storage cost. Think it's worth the cost here?

/// Initializes the Locker for the given fungible token type if it hasn't been initialized yet
///
access(account) fun initializeEscrow(
with vault: @{FungibleToken.Vault},
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what is the with here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

with vault: @{FungibleToken.Vault} is the FT vault being used to initialize escrow. FT Escrow needs an empty vault to wrap in a Locker so that future bridged funds can be escrowed. Think the parameter naming is alright?

self.deployedAddress = coa.deploy(
access(all) let deployedAddresses: {String: EVM.EVMAddress}

access(all) fun deploy(name: String, bytecode: String, value: UInt) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this be access(all) or something more restrictive?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This contract is a temporary test measure until I can get visibility into EVM events from within the testing framework. The problem is I need to know the EVM address of a deployed contract and can only discover that by saving the address somewhere upon deployment.

Wrapping in a resource would be the more secure practice here, but I made it a public method to reduce complexity in testing. Just added a contract-level comment at the top, lmk if you think I should move this to a resource.

@sisyphusSmiling
Copy link
Contributor Author

Thanks for the review @joshuahannan! I pushed changes based on recent feedback. Those are largely in two commits:

  • 96e9226 adds updated access, restricting asset bridging from EVM to the COA interface methods
  • 83d7c5f adds $FLOW handling for requests bridging FlowToken to EVM

let cadenceAddress = FlowEVMBridgeUtils.getContractAddress(fromType: forTokenType)
?? panic("Could not derive contract address for token type: ".concat(identifier))
// Assign a default symbol
var symbol = "BRDG"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rather than having a default, I think it might be better to panic if neither metadata field is filled in as expected.

I don't think anyone would want to accidentally have their token bridged with a name/ticker that can't be changed on all evm consuming indexed websites which they didnt have control over

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, that's a good point. One of those calls that's a balance between ensuring bridging is permissionless with respecting how projects wish to be represented tough. That said, I'd be surprised if a Cadence NFT/FT doesn't define this value so I agree, it's probably best to just revert here

Copy link
Contributor Author

@sisyphusSmiling sisyphusSmiling Apr 12, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking at MetadataViews, there isn't a view that supports symbol. So we can make this call for fungible tokens, but we'll have to consider how to dynamically create a symbol if a project doesn't implement the EVMBridgedMetadata view. Maybe the first n characters of the name with special characters and spaces removed? Have any thoughts?

Update: Created #29 to track discussion

@sisyphusSmiling sisyphusSmiling mentioned this pull request Apr 13, 2024
6 tasks
@sisyphusSmiling sisyphusSmiling merged commit 9b6dc21 into main Apr 15, 2024
2 checks passed
@sisyphusSmiling sisyphusSmiling deleted the add-ft-bridge branch April 15, 2024 21:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
Status: Done
Status: ✅ Done
Development

Successfully merging this pull request may close these issues.

[MVP] EVM-native FT VM bridging path
5 participants