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

Init EVM bridging project #277

Merged
merged 5 commits into from
Feb 7, 2025
Merged
Show file tree
Hide file tree
Changes from 3 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
12 changes: 12 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
[submodule "evm-bridging/lib/forge-std"]
path = evm-bridging/lib/forge-std
url = https://github.com/foundry-rs/forge-std
[submodule "evm-bridging/lib/openzeppelin-contracts"]
path = evm-bridging/lib/openzeppelin-contracts
url = https://github.com/openzeppelin/openzeppelin-contracts
[submodule "evm-bridging/lib/openzeppelin-contracts-upgradeable"]
path = evm-bridging/lib/openzeppelin-contracts-upgradeable
url = https://github.com/openzeppelin/openzeppelin-contracts-upgradeable
[submodule "evm-bridging/lib/openzeppelin-foundry-upgrades"]
path = evm-bridging/lib/openzeppelin-foundry-upgrades
url = https://github.com/openzeppelin/openzeppelin-foundry-upgrades
9 changes: 9 additions & 0 deletions evm-bridging/.env.example.testnet
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@

FLOW_RPC_URL=https://testnet.evm.nodes.onflow.org
VERIFIER_URL=https://evm-testnet.flowscan.io/api
VERIFIER_PROVIDER=blockscout

DEPLOYED_PROXY_CONTRACT_ADDRESS=

DEPLOYER_ADDRESS=
DEPLOYER_PRIVATE_KEY=
16 changes: 16 additions & 0 deletions evm-bridging/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# foundry - compiler files
/out
/cache

# foundry - broadcast logs
/broadcast

# keys
*.pkey
*.pem

# dotenv file
.env

# macOS
.DS_Store
85 changes: 85 additions & 0 deletions evm-bridging/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
# <h1 align="center"> NBA TopShot on FlowEVM [Initial Draft Version] </h1>

**! This directory currently contains work in progress only !**

## Introduction

The `BridgedTopShotMoments` smart contract facilitates the creation of 1:1 ERC721 references for existing Cadence-native NBA Top Shot moments. By associating these references with the same metadata, it ensures seamless integration and interaction between Cadence and FlowEVM environments. This allows users to enjoy the benefits of both ecosystems while maintaining the integrity and uniqueness of their NBA Top Shot moments.

## Getting Started

Install Foundry:

```sh
curl -L https://foundry.paradigm.xyz | bash
foundryup
```

Compile contracts and run tests:
```sh
forge test --force -vvv
```

### Deploy & Verify Contracts

Load environment variables after populating address and key details:

```sh
cp .env.example.testnet .env
source .env
```

Run script to deploy and verify contracts (proxy and implementation):

```sh
forge script --rpc-url $FLOW_RPC_URL --private-key $DEPLOYER_PRIVATE_KEY --legacy scripts/Deploy.s.sol:DeployScript --broadcast --verify --verifier $VERIFIER_PROVIDER --verifier-url $VERIFIER_URL
```

If verification fails for one or both contracts, verify separately:

```sh
forge verify-contract --rpc-url $FLOW_RPC_URL --verifier $VERIFIER_PROVIDER --verifier-url $VERIFIER_URL <address-of-contract-to-verify>
```

## Run Transactions

Set NFT symbol (admin):

```sh
cast send $DEPLOYED_PROXY_CONTRACT_ADDRESS --rpc-url $FLOW_RPC_URL --private-key $PRIVATE_KEY --legacy "setSymbol(string)" <new-nft-symbol>
```

## Execute Queries

BalanceOf:
```sh
cast call $DEPLOYED_PROXY_CONTRACT_ADDRESS --rpc-url $FLOW_RPC_URL "balanceOf(address)(uint256)" $DEPLOYER_ADDRESS
```

OwnerOf:
```sh
cast call $DEPLOYED_PROXY_CONTRACT_ADDRESS --rpc-url $FLOW_RPC_URL "ownerOf(uint256)(address)" <nft-id>
```

## Misc

Fund testnet Flow EVM account:

1. Use Flow Faucet: https://faucet.flow.com/fund-account

2. Transfer FLOW to EVM address:

```sh
flow transactions send ./cadence/transfer_flow_to_evm_address.cdc <evm_address_hex> <ufix64_amount> --network testnet --signer testnet-account
```

## Useful links

- [Flow Developers Doc - Using Foundry with Flow](https://developers.flow.com/evm/guides/foundry)
- [Flow Developers Doc - Interacting with COAs from Cadence](https://developers.flow.com/evm/cadence/interacting-with-coa)
- [evm-testnet.flowscan.io](https://evm-testnet.flowscan.io)
- [Foundry references](https://book.getfoundry.sh/reference)
- [OpenZeppelin Doc - Foundry Upgrades](https://docs.openzeppelin.com/upgrades-plugins/foundry-upgrades)
- [OpenZeppelin Doc - ERC721 Contracts v5](https://docs.openzeppelin.com/contracts/5.x/api/token/erc721)
- [GitHub - OpenZeppelin Upgradeable Contracts](https://github.com/OpenZeppelin/openzeppelin-contracts-upgradeable)
- [GitHub - LimitBreak Creator Token Standards](https://github.com/limitbreakinc/creator-token-standards)
Copy link
Contributor

Choose a reason for hiding this comment

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

great read me!

37 changes: 37 additions & 0 deletions evm-bridging/cadence/transfer_flow_to_evm_address.cdc
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import "FungibleToken"
import "FlowToken"

import "EVM"

/// Transfers $FLOW from the signer's account Cadence Flow balance to the recipient's hex-encoded EVM address.
///
transaction(recipientEVMAddressHex: String, amount: UFix64) {

var sentVault: @FlowToken.Vault
let recipientEVMAddress: EVM.EVMAddress
let recipientPreBalance: UFix64

prepare(signer: auth(BorrowValue, SaveValue) &Account) {
// Borrow a reference to the signer's FlowToken.Vault and withdraw the amount
let vaultRef = signer.storage.borrow<auth(FungibleToken.Withdraw) &FlowToken.Vault>(
from: /storage/flowTokenVault
) ?? panic("Could not borrow reference to the owner's Vault!")
self.sentVault <- vaultRef.withdraw(amount: amount) as! @FlowToken.Vault

// Get the recipient's EVM address
self.recipientEVMAddress = EVM.addressFromString(recipientEVMAddressHex)

// Get the recipient's balance before the transfer to check the amount transferred
self.recipientPreBalance = self.recipientEVMAddress.balance().inFLOW()
}

execute {
// Deposit the amount to the recipient's EVM address
self.recipientEVMAddress.deposit(from: <-self.sentVault)
}

post {
self.recipientEVMAddress.balance().inFLOW() == self.recipientPreBalance + amount:
"Problem transferring value to EVM address"
}
}
17 changes: 17 additions & 0 deletions evm-bridging/foundry.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
[profile.default]
solc_version = "0.8.24"
optimizer = true
optimizer_runs = 200

src = "src"
out = "out"
libs = ["lib"]
test = "test"
cache_path = "cache"
build_info = true
extra_output = ["storageLayout"]

ffi = true
ast = true

# See more config options https://github.com/foundry-rs/foundry/blob/master/crates/config/README.md#all-options
1 change: 1 addition & 0 deletions evm-bridging/lib/forge-std
Submodule forge-std added at 3b20d6
1 change: 1 addition & 0 deletions evm-bridging/lib/openzeppelin-contracts
Submodule openzeppelin-contracts added at 441dc1
1 change: 1 addition & 0 deletions evm-bridging/lib/openzeppelin-foundry-upgrades
3 changes: 3 additions & 0 deletions evm-bridging/remappings.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
@openzeppelin/contracts/=lib/openzeppelin-contracts/contracts/
@openzeppelin/contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/contracts/
openzeppelin-foundry-upgrades/=lib/openzeppelin-foundry-upgrades/
51 changes: 51 additions & 0 deletions evm-bridging/scripts/Deploy.s.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.24;

import {Script} from "forge-std/Script.sol";
import {Upgrades} from "openzeppelin-foundry-upgrades/src/Upgrades.sol";
import {BridgedTopShotMoments} from "../src/BridgedTopShotMoments.sol";

contract DeployScript is Script {
function setUp() public {}

function run() external returns (address, address) {
// Start broadcast with deployer private key
vm.startBroadcast(vm.envUint("DEPLOYER_PRIVATE_KEY"));
console.log("Deployer address:", msg.sender);

// Set contract initialization parameters
address owner = msg.sender;
string memory name = "Bidged NBA TopShot Moments";
string memory symbol = "TOPSHOT";
string memory cadenceNFTAddress = "cadenceNFTAddress";
Copy link
Contributor

Choose a reason for hiding this comment

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

do we get to change these values when deploying the bridge contract?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Right, we should specify the proper values to be used when deploying. Also, only symbol currently has admin update method - we could add more if needed via contract update later, though I don't think we're expecting any of these to change

string memory cadenceNFTIdentifier = "cadenceNFTIdentifier";
string memory contractURI = "contractURI";

// Deploy NFT contract using UUPS proxy for upgradeability
address proxyAddr = Upgrades.deployUUPSProxy(
"BridgedTopShotMoments.sol",
abi.encodeCall(
BridgedTopShotMoments.initialize,
(
owner,
name,
symbol,
cadenceNFTAddress,
cadenceNFTIdentifier,
contractURI
)
)
);
console.log("Proxy contract deployed at address:", proxyAddr);

// Get implementation contract address
address implementationAddr = Upgrades.getImplementationAddress(
proxyAddr
);
console.log("Implementation contract deployed at address:", implementationAddr);

// Stop broadcast and return implementation and proxy addresses
vm.stopBroadcast();
return (implementationAddr, proxyAddr);
}
}
113 changes: 113 additions & 0 deletions evm-bridging/src/BridgedTopShotMoments.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.24;

import {ERC721Upgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC721/ERC721Upgradeable.sol";
import {ERC721URIStorageUpgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC721/extensions/ERC721URIStorageUpgradeable.sol";
import {ERC721EnumerableUpgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC721/extensions/ERC721EnumerableUpgradeable.sol";
import {ERC721BurnableUpgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC721/extensions/ERC721BurnableUpgradeable.sol";
import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";

import {IERC721Metadata} from "@openzeppelin/contracts/token/ERC721/extensions/IERC721Metadata.sol";
import {IERC721Enumerable} from "@openzeppelin/contracts/token/ERC721/extensions/IERC721Enumerable.sol";
import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.sol";
import {ERC165} from "@openzeppelin/contracts/utils/introspection/ERC165.sol";

import {ICrossVM} from "./interfaces/ICrossVM.sol";

// Initial draft version of the BridgedTopShotMoments contract
contract BridgedTopShotMoments is
Copy link
Contributor

Choose a reason for hiding this comment

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

should we not implement the upgradable interfaces or we just leave this for now util get back from opensea?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

you mean for creator token/royalties? planning to add these in follow-up changes - changed the base for this PR to a new evm-bridging branch

Initializable,
ERC721Upgradeable,
ERC721URIStorageUpgradeable,
ERC721BurnableUpgradeable,
ERC721EnumerableUpgradeable,
OwnableUpgradeable,
ICrossVM
{
string public cadenceNFTAddress;
string public cadenceNFTIdentifier;
string public contractMetadata;
string private _customSymbol;

function initialize(
address owner,
string memory name_,
string memory symbol_,
string memory _cadenceNFTAddress,
string memory _cadenceNFTIdentifier,
string memory _contractMetadata) public initializer
{
__ERC721_init(name_, symbol_);
__Ownable_init(owner);
_customSymbol = symbol_;
cadenceNFTAddress = _cadenceNFTAddress;
cadenceNFTIdentifier = _cadenceNFTIdentifier;
contractMetadata = _contractMetadata;
}

function getCadenceAddress() external view returns (string memory) {
return cadenceNFTAddress;
}

function getCadenceIdentifier() external view returns (string memory) {
return cadenceNFTIdentifier;
}

function symbol() public view override returns (string memory) {
return _customSymbol;
}

function safeMint(address to, uint256 tokenId, string memory uri) public onlyOwner {
_safeMint(to, tokenId);
_setTokenURI(tokenId, uri);
}

function updateTokenURI(uint256 tokenId, string memory uri) public onlyOwner {
_setTokenURI(tokenId, uri);
}

function setSymbol(string memory newSymbol) public onlyOwner {
_setSymbol(newSymbol);
}

function contractURI() public view returns (string memory) {
return contractMetadata;
}

function tokenURI(uint256 tokenId) public view override(ERC721Upgradeable, ERC721URIStorageUpgradeable) returns (string memory) {
return super.tokenURI(tokenId);
}

function supportsInterface(bytes4 interfaceId)
public
view
override(ERC721Upgradeable, ERC721EnumerableUpgradeable, ERC721URIStorageUpgradeable)
returns (bool)
{
return interfaceId == type(IERC165).interfaceId || interfaceId == type(IERC721Metadata).interfaceId
|| interfaceId == type(IERC721Enumerable).interfaceId || interfaceId == type(ERC721BurnableUpgradeable).interfaceId
|| interfaceId == type(OwnableUpgradeable).interfaceId || interfaceId == type(ICrossVM).interfaceId
|| super.supportsInterface(interfaceId);
}

function exists(uint256 tokenId) public view returns (bool) {
return _ownerOf(tokenId) != address(0);
}

function _setSymbol(string memory newSymbol) internal {
_customSymbol = newSymbol;
}

function _update(address to, uint256 tokenId, address auth)
internal
override(ERC721Upgradeable, ERC721EnumerableUpgradeable)
returns (address)
{
return super._update(to, tokenId, auth);
}

function _increaseBalance(address account, uint128 value) internal override(ERC721Upgradeable, ERC721EnumerableUpgradeable) {
super._increaseBalance(account, value);
}
}
6 changes: 6 additions & 0 deletions evm-bridging/src/interfaces/ICrossVM.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
pragma solidity 0.8.24;

interface ICrossVM {
function getCadenceAddress() external view returns (string memory);
function getCadenceIdentifier() external view returns (string memory);
}
Loading