Skip to content
Open
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: 0 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,3 @@ docs/

# Dotenv file
.env

remappings.txt
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,6 @@
[submodule "lib/openzeppelin-contracts"]
path = lib/openzeppelin-contracts
url = https://github.com/openzeppelin/openzeppelin-contracts
[submodule "lib/solmate"]
path = lib/solmate
url = https://github.com/transmissions11/solmate
1 change: 1 addition & 0 deletions lib/solmate
Submodule solmate added at 97bdb2
2 changes: 2 additions & 0 deletions remappings.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
@openzeppelin/=lib/openzeppelin-contracts/contracts/
@solmate/=lib/solmate/src/
156 changes: 110 additions & 46 deletions src/InfernalRiftAbove.sol
Original file line number Diff line number Diff line change
@@ -1,79 +1,103 @@
// SPDX-License-Identifier: AGPL-3.0
pragma solidity ^0.8.26;

/* solhint-disable private-vars-leading-underscore */
/* solhint-disable var-name-mixedcase */
/* solhint-disable func-param-name-mixedcase */

pragma solidity ^0.8.0;

import {IERC721Metadata} from "@openzeppelin/token/ERC721/extensions/IERC721Metadata.sol";
import {ERC2981} from "@openzeppelin/token/common/ERC2981.sol";
import {IERC2981} from "@openzeppelin/interfaces/IERC2981.sol";

import {IInfernalPackage} from "./interfaces/IInfernalPackage.sol";
import {IRoyaltyRegistry} from "./interfaces/IRoyaltyRegistry.sol";
import {IInfernalRiftAbove} from "./interfaces/IInfernalRiftAbove.sol";
import {IInfernalRiftBelow} from "./interfaces/IInfernalRiftBelow.sol";
import {ICrossDomainMessenger} from "./interfaces/ICrossDomainMessenger.sol";
import {IOptimismPortal} from "./interfaces/IOptimismPortal.sol";

import {InfernalRiftBelow} from "./InfernalRiftBelow.sol";


contract InfernalRiftAbove is IInfernalPackage, IInfernalRiftAbove {
uint256 constant BPS_MULTIPLIER = 10000;
uint constant internal BPS_MULTIPLIER = 10000;

address immutable PORTAL;
address immutable L1_CROSS_DOMAIN_MESSENGER;
address immutable ROYALTY_REGISTRY;
address INFERNAL_RIFT_BELOW;
IOptimismPortal immutable public PORTAL;
address immutable public L1_CROSS_DOMAIN_MESSENGER;
IRoyaltyRegistry immutable public ROYALTY_REGISTRY;
address public INFERNAL_RIFT_BELOW;

error RiftBelowAlreadySet();
error NotCrossDomainMessenger();
error CrossChainSenderIsNotRiftBelow();
error CollectionNotERC2981Compliant();
error CallerIsNotRoyaltiesReceiver(address _caller, address _receiver);

constructor(address _PORTAL, address _L1_CROSS_DOMAIN_MESSENGER, address _ROYALTY_REGISTRY) {
PORTAL = _PORTAL;
PORTAL = IOptimismPortal(_PORTAL);
L1_CROSS_DOMAIN_MESSENGER = _L1_CROSS_DOMAIN_MESSENGER;
ROYALTY_REGISTRY = _ROYALTY_REGISTRY;
ROYALTY_REGISTRY = IRoyaltyRegistry(_ROYALTY_REGISTRY);
}

function setInfernalRiftBelow(address a) external {
/**
* Allows the {InfernalRiftBelow} contract to be set.
*
* @dev This contract address cannot be updated if a non-zero address already set.
*
* @param _infernalRiftBelow Address of the {InfernalRiftBelow} contract
*/
function setInfernalRiftBelow(address _infernalRiftBelow) external {
if (INFERNAL_RIFT_BELOW != address(0)) {
revert RiftBelowAlreadySet();
}
INFERNAL_RIFT_BELOW = a;

INFERNAL_RIFT_BELOW = _infernalRiftBelow;
}

/**
* Sends ERC721 tokens from the L1 chain to L2.
*
* @param collectionAddresses Addresses of collections returning from L2
* @param idsToCross Array of tokenIds, with the first iterator referring to collectionAddress
* @param recipient The recipient of the tokens on L2
* @param gasLimit The maximum amount of gas to spend in transaction
*/
function crossTheThreshold(
address[] calldata collectionAddresses,
uint256[][] calldata idsToCross,
uint[][] calldata idsToCross,
address recipient,
uint64 gasLimit
) external payable {
// Set up payload
uint256 numCollections = collectionAddresses.length;
uint numCollections = collectionAddresses.length;
Package[] memory package = new Package[](numCollections);

// Cache variables ahead of our loops
uint numIds;
address collectionAddress;
string[] memory uris;
IERC721Metadata erc721;

// Go through each collection, set values if needed
for (uint256 i; i < numCollections;) {
for (uint i; i < numCollections; ++i) {
// Cache values needed
uint256 numIds = idsToCross[i].length;
address collectionAddress = collectionAddresses[i];
numIds = idsToCross[i].length;
collectionAddress = collectionAddresses[i];

erc721 = IERC721Metadata(collectionAddress);

// Go through each NFT, set its URI and escrow it
string[] memory uris = new string[](numIds);
for (uint256 j; j < numIds;) {
uris[j] = IERC721Metadata(collectionAddress).tokenURI(idsToCross[i][j]);
IERC721Metadata(collectionAddress).transferFrom(msg.sender, address(this), idsToCross[i][j]);
unchecked {
++j;
}
uris = new string[](numIds);
for (uint j; j < numIds; ++j) {
uris[j] = erc721.tokenURI(idsToCross[i][j]);
erc721.transferFrom(msg.sender, address(this), idsToCross[i][j]);
}

// Grab royalty value from first ID
address royaltyLookupAddress = IRoyaltyRegistry(ROYALTY_REGISTRY).getRoyaltyLookupAddress(collectionAddress);
uint96 royaltyBps;
try ERC2981(royaltyLookupAddress).royaltyInfo(idsToCross[i][0], BPS_MULTIPLIER) returns (
address, uint256 _royaltyAmount
) {
try ERC2981(
ROYALTY_REGISTRY.getRoyaltyLookupAddress(collectionAddress)
).royaltyInfo(idsToCross[i][0], BPS_MULTIPLIER) returns (address, uint _royaltyAmount) {
royaltyBps = uint96(_royaltyAmount);
} catch {
// It's okay if it reverts (:
Expand All @@ -85,15 +109,13 @@ contract InfernalRiftAbove is IInfernalPackage, IInfernalRiftAbove {
ids: idsToCross[i],
uris: uris,
royaltyBps: royaltyBps,
name: IERC721Metadata(collectionAddress).name(),
symbol: IERC721Metadata(collectionAddress).symbol()
name: erc721.name(),
symbol: erc721.symbol()
});
unchecked {
++i;
}
}

// Send package off to the portal
IOptimismPortal(PORTAL).depositTransaction{value: msg.value}(
PORTAL.depositTransaction{value: msg.value}(
INFERNAL_RIFT_BELOW,
0,
gasLimit,
Expand All @@ -102,33 +124,75 @@ contract InfernalRiftAbove is IInfernalPackage, IInfernalRiftAbove {
);
}

/**
* Handle NFTs being transferred back to the L1 from the L2.
*
* @dev The NFTs must be stored in this contract to redistribute back on L1
*
* @param collectionAddresses Addresses of collections returning from L2
* @param idsToCross Array of tokenIds, with the first iterator referring to collectionAddress
* @param recipient The recipient of the tokens
*/
function returnFromTheThreshold(
address[] calldata collectionAddresses,
uint256[][] calldata idsToCross,
uint[][] calldata idsToCross,
address recipient
) external {
// Validate caller is cross-chain and comes from rift below
if (msg.sender != L1_CROSS_DOMAIN_MESSENGER) {
revert NotCrossDomainMessenger();
}

if (ICrossDomainMessenger(msg.sender).xDomainMessageSender() != INFERNAL_RIFT_BELOW) {
revert CrossChainSenderIsNotRiftBelow();
}

// Unlock NFTs to caller
uint256 numCollections = collectionAddresses.length;
for (uint256 i; i < numCollections;) {
address l1CollectionAddress = collectionAddresses[i];
uint256 numIds = idsToCross[i].length;
for (uint256 j; j < numIds;) {
IERC721Metadata(l1CollectionAddress).transferFrom(address(this), recipient, idsToCross[i][j]);
unchecked {
++j;
}
}
unchecked {
++i;
uint numCollections = collectionAddresses.length;

IERC721Metadata erc721;
uint numIds;

for (uint i; i < numCollections; ++i) {
erc721 = IERC721Metadata(collectionAddresses[i]);
numIds = idsToCross[i].length;

for (uint j; j < numIds; ++j) {
erc721.transferFrom(address(this), recipient, idsToCross[i][j]);
}
}
}

/**
* If the contract address on L1 implements `EIP-2981`, then we can allow the recipient
* of the L1 royalties make the claim against the L2 equivalent.
*
* @param _collectionAddress The address of the L1 collection
* @param _recipient The L2 recipient of the claim
* @param _tokens Addresses of tokens to claim
* @param _gasLimit The limit of gas to send
*/
function claimRoyalties(address _collectionAddress, address _recipient, address[] calldata _tokens, uint32 _gasLimit) external {
// We then need to make sure that the L1 contract supports royalties via EIP-2981
if (!IERC2981(_collectionAddress).supportsInterface(type(IERC2981).interfaceId)) revert CollectionNotERC2981Compliant();

// We can now pull the royalty information from the L1 to confirm that the caller
// is the receiver of the royalties. We can't actually pull in the default royalty
// provider so instead we just use token0.
(address receiver,) = IERC2981(_collectionAddress).royaltyInfo(0, 0);

// Check that the receiver of royalties is making this call
if (receiver != msg.sender) revert CallerIsNotRoyaltiesReceiver(msg.sender, receiver);

// Make our call to the L2 that will pull tokens from the contract
ICrossDomainMessenger(L1_CROSS_DOMAIN_MESSENGER).sendMessage(
INFERNAL_RIFT_BELOW,
abi.encodeCall(
IInfernalRiftBelow.claimRoyalties,
(_collectionAddress, _recipient, _tokens)
),
_gasLimit
);
}

}
Loading