-
Notifications
You must be signed in to change notification settings - Fork 105
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
Changes from 3 commits
8586902
11002d4
e2d7804
57788e5
f613dd8
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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 |
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= |
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 |
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) | ||
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" | ||
} | ||
} |
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 |
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/ |
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"; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. do we get to change these values when deploying the bridge contract? There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 |
||
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); | ||
} | ||
} |
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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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? There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 |
||
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); | ||
} | ||
} |
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); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
great read me!