Skip to content

Commit

Permalink
add bridge permissions, fulfillment
Browse files Browse the repository at this point in the history
  • Loading branch information
loic1 committed Feb 10, 2025
1 parent 6169b18 commit 8b98082
Show file tree
Hide file tree
Showing 8 changed files with 212 additions and 4 deletions.
7 changes: 4 additions & 3 deletions evm-bridging/script/Deploy.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -22,16 +22,17 @@ contract DeployScript is Script {
string memory cadenceNFTAddress = "877931736ee77cff";
string memory cadenceNFTIdentifier = "A.877931736ee77cff.NFT";
string memory contractURI = "add-contract-URI-here";
address underlyingToken = address(0x12345);

address underlyingNftContractAddress = address(0x12345);
address vmBridgeAddress = address(0x67890);
// Deploy NFT contract using UUPS proxy for upgradeability
address proxyAddr = Upgrades.deployUUPSProxy(
"BridgedTopShotMoments.sol",
abi.encodeCall(
BridgedTopShotMoments.initialize,
(
owner,
underlyingToken,
underlyingNftContractAddress,
vmBridgeAddress,
name,
symbol,
baseTokenURI,
Expand Down
13 changes: 12 additions & 1 deletion evm-bridging/src/BridgedTopShotMoments.sol
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ import {ITransferValidator721} from "./interfaces/ITransferValidator.sol";
import {ERC721TransferValidator} from "./lib/ERC721TransferValidator.sol";

import {ICrossVM} from "./interfaces/ICrossVM.sol";
import {BridgePermissionsUpgradeable} from "./lib/BridgePermissionsUpgradeable.sol";
import {CrossVMBridgeFulfillmentUpgradeable} from "./lib/CrossVMBridgeFulfillmentUpgradeable.sol";

/**
* @title ERC-721 BridgedTopShotMoments
Expand All @@ -39,6 +41,8 @@ contract BridgedTopShotMoments is
OwnableUpgradeable,
ERC721WrapperUpgradeable,
ERC721TransferValidator,
CrossVMBridgeFulfillmentUpgradeable,
BridgePermissionsUpgradeable,
ICrossVM
{
// Cadence-specific identifiers for cross-chain bridging
Expand Down Expand Up @@ -77,6 +81,7 @@ contract BridgedTopShotMoments is
function initialize(
address owner,
address underlyingToken,
address vmBridgeAddress,
string memory name_,
string memory symbol_,
string memory baseTokenURI_,
Expand All @@ -90,6 +95,8 @@ contract BridgedTopShotMoments is
__ERC721_init(name_, symbol_);
__Ownable_init(owner);
__ERC721Wrapper_init(IERC721(underlyingToken));
__CrossVMBridgeFulfillment_init(vmBridgeAddress);
__BridgePermissions_init();
_customSymbol = symbol_;
_baseTokenURI = baseTokenURI_;
cadenceNFTAddress = _cadenceNFTAddress;
Expand Down Expand Up @@ -132,7 +139,7 @@ contract BridgedTopShotMoments is
function supportsInterface(bytes4 interfaceId)
public
view
override(ERC721Upgradeable, ERC721EnumerableUpgradeable)
override(ERC721Upgradeable, ERC721EnumerableUpgradeable, BridgePermissionsUpgradeable, CrossVMBridgeFulfillmentUpgradeable)
returns (bool)
{
return interfaceId == type(IERC165).interfaceId || interfaceId == type(IERC721Metadata).interfaceId
Expand Down Expand Up @@ -166,6 +173,10 @@ contract BridgedTopShotMoments is
super._increaseBalance(account, value);
}

function setBridgePermissions(bool permissions) external onlyOwner {
_setPermissions(permissions);
}

function setRoyaltyInfo(RoyaltyInfo calldata newInfo) external onlyOwner {
// Revert if the new royalty address is the zero address.
if (newInfo.royaltyAddress == address(0)) {
Expand Down
16 changes: 16 additions & 0 deletions evm-bridging/src/interfaces/IBridgePermissions.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.24;

import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.sol";

interface IBridgePermissions is IERC165 {
/**
* @dev Emitted when the permissions for the contract are updated.
*/
event PermissionsUpdated(bool newPermissions);

/**
* @dev Returns true if the contract allows bridging of its assets.
*/
function allowsBridging() external view returns (bool);
}
6 changes: 6 additions & 0 deletions evm-bridging/src/interfaces/ICrossVMBridgeFulfillment.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
pragma solidity 0.8.24;

interface ICrossVMBridgeFulfillment {
function fulfillToEVM(address to, uint256 id, bytes memory data) external;
function vmBridgeAddress() external view returns (address);
}
53 changes: 53 additions & 0 deletions evm-bridging/src/lib/BridgePermissionsUpgradeable.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.24;

import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.sol";
import {ERC165Upgradeable} from "@openzeppelin/contracts-upgradeable/utils/introspection/ERC165Upgradeable.sol";
import {IBridgePermissions} from "../interfaces/IBridgePermissions.sol";

/**
* @dev Contract for which implementation is checked by the Flow VM bridge as an opt-out mechanism
* for non-standard asset contracts that wish to opt-out of bridging between Cadence & EVM. By
* default, the VM bridge operates on a permissionless basis, meaning anyone can request an asset
* be onboarded. However, some small subset of non-standard projects may wish to opt-out of this
* and this contract provides a way to do so while also enabling future opt-in.
*
* Note: The Flow VM bridge checks for permissions at asset onboarding. If your asset has already
* been onboarded, setting `permissions` to `false` will not affect movement between VMs.
*/
abstract contract BridgePermissionsUpgradeable is Initializable, ERC165Upgradeable, IBridgePermissions {
// The permissions for the contract to allow or disallow bridging of its assets.
bool private _permissions;

function __BridgePermissions_init() internal onlyInitializing {
_permissions = false;
}

/**
* @dev See {IERC165-supportsInterface}.
*/
function supportsInterface(bytes4 interfaceId) public view virtual override(ERC165Upgradeable, IERC165) returns (bool) {
return interfaceId == type(IBridgePermissions).interfaceId || super.supportsInterface(interfaceId);
}

/**
* @dev Returns true if the contract allows bridging of its assets. Checked by the Flow VM
* bridge at asset onboarding to enable non-standard asset contracts to opt-out of bridging
* between Cadence & EVM. Implementing this contract opts out by default but can be
* overridden to opt-in or used in conjunction with a switch to enable opting in.
*/
function allowsBridging() external view virtual returns (bool) {
return _permissions;
}

/**
* @dev Set the permissions for the contract to allow or disallow bridging of its assets.
*
* Emits a {PermissionsUpdated} event.
*/
function _setPermissions(bool permissions) internal {
_permissions = permissions;
emit PermissionsUpdated(permissions);
}
}
52 changes: 52 additions & 0 deletions evm-bridging/src/lib/CrossVMBridgeCallableUpgradeable.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
// SPDX-License-Identifier: Unlicense
pragma solidity 0.8.24;

import {ContextUpgradeable} from "@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol";
import {ICrossVMBridgeFulfillment} from "../interfaces/ICrossVMBridgeFulfillment.sol";

/**
* @title CrossVMBridgeCallable
* @dev A base contract intended for use in implementations on Flow, allowing a contract to define
* access to the Cadence X EVM bridge on certain methods.
*/
abstract contract CrossVMBridgeCallableUpgradeable is ContextUpgradeable {

address private _vmBridgeAddress;

error CrossVMBridgeCallableZeroInitialization();
error CrossVMBridgeCallableUnauthorizedAccount(address account);

/**
* @dev Sets the bridge EVM address such that only the bridge COA can call the privileged methods
*/
function _init_vm_bridge_address(address vmBridgeAddress_) internal {
if (vmBridgeAddress_ == address(0)) {
revert CrossVMBridgeCallableZeroInitialization();
}
_vmBridgeAddress = vmBridgeAddress_;
}

/**
* @dev Modifier restricting access to the designated VM bridge EVM address
*/
modifier onlyVMBridge() {
_checkVMBridgeAddress();
_;
}

/**
* @dev Returns the designated VM bridge’s EVM address
*/
function vmBridgeAddress() public view virtual returns (address) {
return _vmBridgeAddress;
}

/**
* @dev Checks that msg.sender is the designated vm bridge address
*/
function _checkVMBridgeAddress() internal view virtual {
if (vmBridgeAddress() != _msgSender()) {
revert CrossVMBridgeCallableUnauthorizedAccount(_msgSender());
}
}
}
66 changes: 66 additions & 0 deletions evm-bridging/src/lib/CrossVMBridgeFulfillmentUpgradeable.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
// SPDX-License-Identifier: Unlicense
pragma solidity 0.8.24;

import {CrossVMBridgeCallableUpgradeable} from "./CrossVMBridgeCallableUpgradeable.sol";
import {ERC721Upgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC721/ERC721Upgradeable.sol";
import {ERC165Upgradeable} from "@openzeppelin/contracts-upgradeable/utils/introspection/ERC165Upgradeable.sol";
import {ICrossVMBridgeFulfillment} from "../interfaces/ICrossVMBridgeFulfillment.sol";

abstract contract CrossVMBridgeFulfillmentUpgradeable is CrossVMBridgeCallableUpgradeable, ERC165Upgradeable, ERC721Upgradeable {

error FulfillmentFailedTokenNotEscrowed(uint256 id, address escrowAddress);

function __CrossVMBridgeFulfillment_init(address vmBridgeAddress_) internal onlyInitializing {
__CrossVMBridgeFulfillment_init_unchained(vmBridgeAddress_);
}

function __CrossVMBridgeFulfillment_init_unchained(address vmBridgeAddress_) internal onlyInitializing {
_init_vm_bridge_address(vmBridgeAddress_);
}

/**
* @dev Fulfills the bridge request, minting (if non-existent) or transferring (if escrowed) the
* token with the given ID to the provided address. For dynamic metadata handling between
* Cadence & EVM, implementations should override and assign metadata as encoded from Cadence
* side. If overriding, be sure to preserve the mint/escrow pattern as shown in the default
* implementation.
*
* @param _to address of the token recipient
* @param _id the id of the token being moved into EVM from Cadence
*/
function fulfillToEVM(address _to, uint256 _id, bytes memory /*_data*/) external onlyVMBridge {
if (_ownerOf(_id) == address(0)) {
_validateMint(_to, _id);
_mint(_to, _id); // Doesn't exist, mint the token
} else {
// Should be escrowed under vm bridge - transfer from escrow to recipient
_requireEscrowed(_id);
safeTransferFrom(vmBridgeAddress(), _to, _id);
}
}

function _validateMint(address _to, uint256 _id) internal view {
// no-op, override in implementation if needed
}

/**
* @dev Allows a caller to determine the contract conforms to the `ICrossVMFulfillment` interface
*/
function supportsInterface(bytes4 interfaceId) public view virtual override(ERC165Upgradeable, ERC721Upgradeable) returns (bool) {
return interfaceId == type(ICrossVMBridgeFulfillment).interfaceId || super.supportsInterface(interfaceId);
}

/**
* @dev Internal method that reverts with FulfillmentFailedTokenNotEscrowed if the provided
* token is not escrowed with the assigned vm bridge address as owner.
*
* @param _id the token id that must be escrowed
*/
function _requireEscrowed(uint256 _id) internal view {
address owner = _ownerOf(_id);
address vmBridgeAddress_ = vmBridgeAddress();
if (owner != vmBridgeAddress_) {
revert FulfillmentFailedTokenNotEscrowed(_id, vmBridgeAddress_);
}
}
}
3 changes: 3 additions & 0 deletions evm-bridging/test/BridgedTopShotMoments.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ contract BridgedTopShotMomentsTest is Test {
address owner;
address underlyingNftContractOwner;
address underlyingNftContractAddress;
address vmBridgeAddress;

string name;
string symbol;
Expand Down Expand Up @@ -51,6 +52,7 @@ contract BridgedTopShotMomentsTest is Test {

// Set NFT contract initialization parameters
underlyingNftContractAddress = address(underlyingNftContract);
vmBridgeAddress = address(0x67890);
name = "name";
symbol = "symbol";
baseTokenURI = "https://example.com/";
Expand All @@ -66,6 +68,7 @@ contract BridgedTopShotMomentsTest is Test {
(
owner,
underlyingNftContractAddress,
vmBridgeAddress,
name,
symbol,
baseTokenURI,
Expand Down

0 comments on commit 8b98082

Please sign in to comment.