Skip to content
Merged
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
12 changes: 6 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,21 +27,21 @@ Add the following in a `remappings.txt` file:
@modular-contracts/=lib/modular-contracts/src/
```

Import `ModularCore` inherit to build a Modular Core contract (e.g. ERC-721 Core):
Import `Core` inherit to build a Modular Core contract (e.g. ERC-721 Core):

```solidity
import {ModularCore} from "@modular-contracts/ModularCore.sol";
import {Core} from "@modular-contracts/Core.sol";
import {ERC721A} from "@erc721a/extensions/ERC721AQueryable.sol";

contract ModularNFTCollection is ERC721A, ModularCore {}
contract ModularNFTCollection is ERC721A, Core {}
```

Import `ModularModule` to create an Module for your Core contract (e.g. `Soulbound`):
Import `Module` to create an Module for your Core contract (e.g. `Soulbound`):

```solidity
import {ModularModule} from "@modular-contracts/ModularModule.sol";
import {Module} from "@modular-contracts/Module.sol";

contract SoulboundERC721 is ModularModule {}
contract SoulboundERC721 is Module {}
```

# Run this repo
Expand Down
30 changes: 15 additions & 15 deletions design-document.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,12 +77,12 @@ The `ModuleConfig` struct contains all information that a Core uses to check whe

### Modular Core

A router contract MUST implement `IModularCore` and ERC-165 interfaces to comply with the Modular Contract architecture.
A router contract MUST implement `ICore` and ERC-165 interfaces to comply with the Modular Contract architecture.

The `ERC165.supportsInterface` function MUST return true for all interfaces supported by the Core and the supported interfaces expressed in the ModuleConfig of installed modules.

```solidity
interface IModularCore is IModuleConfig {
interface ICore is IModuleConfig {
/**
* @dev Whether execution reverts when the callback function is not implemented by any installed Module.
* @param OPTIONAL Execution does not revert when the callback function is not implemented.
Expand Down Expand Up @@ -157,7 +157,7 @@ Any given callback function in the ModuleConfig of an installed Module MUST be c
Any given fallback function in the ModuleConfig of an installed Module MUST be called by the Core via its fallback, when called with the given fallback function’s calldata.

```solidity
interface IModularModule is IModuleConfig {
interface IModule is IModuleConfig {
/**
* @dev Returns the ModuleConfig of the Module contract.
*/
Expand Down Expand Up @@ -187,7 +187,7 @@ For example, an Module may be written a stateless logic contract, or a stateful

An Module is compatible to install in a Core if:

1. all of the Module’s callback functions (specified in ModuleConfig) are included in the Core’s supported callbacks (specified in IModularCore.getSupportedCallbackFunctions).
1. all of the Module’s callback functions (specified in ModuleConfig) are included in the Core’s supported callbacks (specified in ICore.getSupportedCallbackFunctions).

This is because we assume that an Module only specifies a callback function in its ModuleConfig when it expects a Core to call it.

Expand All @@ -197,9 +197,9 @@ An Module is compatible to install in a Core if:

### Pure getter functions

Both IModularCore.getSupportedCallbackFunctions and IModularModule.getModuleConfig are pure functions, which means their return value does not change based on any storage.
Both ICore.getSupportedCallbackFunctions and IModule.getModuleConfig are pure functions, which means their return value does not change based on any storage.

For a given Module, it is important for the Core’s stored representation of an ModuleConfig to not go out of sync with the actual return value of IModularModule.getModuleConfig at any time, since this may lead to unintended consequences such as the Core calling functions on the Module that no longer exist or be called on the Module contract.
For a given Module, it is important for the Core’s stored representation of an ModuleConfig to not go out of sync with the actual return value of IModule.getModuleConfig at any time, since this may lead to unintended consequences such as the Core calling functions on the Module that no longer exist or be called on the Module contract.

### Permissions in FallbackFunction and CallbackFunction structs

Expand All @@ -215,14 +215,14 @@ This is because a callback function call is specified in the function body of a

## Reference Implementation

### IModularCore
### ICore

https://github.com/thirdweb-dev/modular-contracts/blob/jl/patch-7/core/src/ModularCore.sol
https://github.com/thirdweb-dev/modular-contracts/blob/jl/patch-7/core/src/Core.sol

### IModularModule
### IModule

```solidity
contract MockModule is IModularModule {
contract MockModule is IModule {
mapping(address => uint256) index;

function increment() external {
Expand Down Expand Up @@ -286,7 +286,7 @@ thirdweb is rolling out the _Modular Contracts_ architecture with token Core con
All 3 token core contracts implement:

- The token standard itself. ([ERC-20](https://eips.ethereum.org/EIPS/eip-20) + [EIP-2612](https://eips.ethereum.org/EIPS/eip-2612) Permit / [ERC-721](https://eips.ethereum.org/EIPS/eip-721) / [ERC-1155](https://eips.ethereum.org/EIPS/eip-1155)).
- `ModularCore` interface
- `Core` interface
- [EIP-7572](https://eips.ethereum.org/EIPS/eip-7572) Contract-level metadata via `contractURI()` standard
- Multicall interface
- External mint() and burn() functions.
Expand Down Expand Up @@ -370,11 +370,11 @@ thirdweb will roll out Token Core contracts with the following Modules available

thirdweb has implemented the Modular Contracts architecture with upgradeability and permissions in mind.

`ModularCoreUpgradeable` is an implementation of the IModularCore interface that works with upgradeable Module contracts, without compromising the security of the Core contract.
`CoreUpgradeable` is an implementation of the ICore interface that works with upgradeable Module contracts, without compromising the security of the Core contract.

https://github.com/thirdweb-dev/modular-contracts/blob/jl/patch-7/core/src/ModularCoreUpgradeable.sol
https://github.com/thirdweb-dev/modular-contracts/blob/jl/patch-7/core/src/CoreUpgradeable.sol

1. The `ModularCoreUpgradeable.installModule` function expects you to pass an address of an implementation/logic contract.
1. The `CoreUpgradeable.installModule` function expects you to pass an address of an implementation/logic contract.
2. The Core contract then uses the canonical ERC1967 Factory to deploy an ERC-1967 proxy pointing to the provided module *implementation* address.

The "canonical ERC1967 Factory" is 0age's ImmutableCreate2Factory located at `0x0000000000FFe8B47B3e2130213B802212439497` on all EVM chains. Deployment instructions are [here on the Seaport github](https://github.com/ProjectOpenSea/seaport/blob/main/docs/Deployment.md).
Expand Down Expand Up @@ -409,7 +409,7 @@ The end user / developer is always dealing with implementation contract addresse

## Permission Model

`ModularCoreUpgradeable` uses role based permissions implementation of Solady’s [OwnableRoles](https://github.com/Vectorized/solady/blob/main/src/auth/OwnableRoles.sol), and follows [EIP-173: Contract Ownership Standard](https://eips.ethereum.org/EIPS/eip-173).
`CoreUpgradeable` uses role based permissions implementation of Solady’s [OwnableRoles](https://github.com/Vectorized/solady/blob/main/src/auth/OwnableRoles.sol), and follows [EIP-173: Contract Ownership Standard](https://eips.ethereum.org/EIPS/eip-173).

The contract owner can grant and revoke roles from other addresses.

Expand Down
18 changes: 8 additions & 10 deletions src/ModularCore.sol → src/Core.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,17 @@ pragma solidity ^0.8.20;

// Interface

import {ICore} from "./interface/ICore.sol";
import {IInstallationCallback} from "./interface/IInstallationCallback.sol";
import {IModularCore} from "./interface/IModularCore.sol";
import {IModularModule} from "./interface/IModularModule.sol";
import {IModule} from "./interface/IModule.sol";

// Utils
import {Role} from "./Role.sol";
import {OwnableRoles} from "@solady/auth/OwnableRoles.sol";
import {EnumerableSetLib} from "@solady/utils/EnumerableSetLib.sol";
import {ReentrancyGuard} from "@solady/utils/ReentrancyGuard.sol";

abstract contract ModularCore is IModularCore, OwnableRoles, ReentrancyGuard {
abstract contract Core is ICore, OwnableRoles, ReentrancyGuard {

using EnumerableSetLib for EnumerableSetLib.AddressSet;

Expand Down Expand Up @@ -116,10 +116,8 @@ abstract contract ModularCore is IModularCore, OwnableRoles, ReentrancyGuard {

for (uint256 i = 0; i < totalInstalled; i++) {
address implementation = modules.at(i);
_installedModules[i] = InstalledModule({
implementation: implementation,
config: IModularModule(implementation).getModuleConfig()
});
_installedModules[i] =
InstalledModule({implementation: implementation, config: IModule(implementation).getModuleConfig()});
}
}

Expand Down Expand Up @@ -180,9 +178,9 @@ abstract contract ModularCore is IModularCore, OwnableRoles, ReentrancyGuard {
}

// Get module config.
ModuleConfig memory config = IModularModule(_module).getModuleConfig();
ModuleConfig memory config = IModule(_module).getModuleConfig();

// Check: ModularCore supports interface required by module.
// Check: Core supports interface required by module.
if (config.requiredInterfaces.length != 0) {
for (uint256 i = 0; i < config.requiredInterfaces.length; i++) {
if (!supportsInterface(config.requiredInterfaces[i])) {
Expand Down Expand Up @@ -263,7 +261,7 @@ abstract contract ModularCore is IModularCore, OwnableRoles, ReentrancyGuard {
}

// Get module config.
ModuleConfig memory config = IModularModule(_module).getModuleConfig();
ModuleConfig memory config = IModule(_module).getModuleConfig();

uint256 supportedInterfaceLength = config.supportedInterfaces.length;
for (uint256 i = 0; i < supportedInterfaceLength; i++) {
Expand Down
4 changes: 2 additions & 2 deletions src/ModularModule.sol → src/Module.sol
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.20;

import {IModularModule} from "./interface/IModularModule.sol";
import {IModule} from "./interface/IModule.sol";

abstract contract ModularModule is IModularModule {
abstract contract Module is IModule {

function getModuleConfig() external pure virtual returns (ModuleConfig memory);

Expand Down
8 changes: 4 additions & 4 deletions src/core/token/ERC1155Core.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ pragma solidity ^0.8.20;
import {ERC1155} from "@solady/tokens/ERC1155.sol";
import {Multicallable} from "@solady/utils/Multicallable.sol";

import {ModularCore} from "../../ModularCore.sol";
import {Core} from "../../Core.sol";

import {BeforeApproveForAllCallback} from "../../callback/BeforeApproveForAllCallback.sol";
import {BeforeBatchMintCallbackERC1155} from "../../callback/BeforeBatchMintCallbackERC1155.sol";
Expand All @@ -15,7 +15,7 @@ import {BeforeTransferCallbackERC1155} from "../../callback/BeforeTransferCallba

import {OnTokenURICallback} from "../../callback/OnTokenURICallback.sol";

contract ERC1155Core is ERC1155, ModularCore, Multicallable {
contract ERC1155Core is ERC1155, Core, Multicallable {

/*//////////////////////////////////////////////////////////////
STORAGE
Expand Down Expand Up @@ -109,13 +109,13 @@ contract ERC1155Core is ERC1155, ModularCore, Multicallable {
* @notice Returns whether the contract implements an interface with the given interface ID.
* @param interfaceId The interface ID of the interface to check for
*/
function supportsInterface(bytes4 interfaceId) public view override(ERC1155, ModularCore) returns (bool) {
function supportsInterface(bytes4 interfaceId) public view override(ERC1155, Core) returns (bool) {
return interfaceId == 0x01ffc9a7 // ERC165 Interface ID for ERC165
|| interfaceId == 0xd9b67a26 // ERC165 Interface ID for ERC1155
|| interfaceId == 0x0e89341c // ERC165 Interface ID for ERC1155MetadataURI
|| interfaceId == 0xe8a3d485 // ERC-7572
|| interfaceId == 0x7f5828d0 // ERC-173
|| super.supportsInterface(interfaceId); // right-most ModularCore
|| super.supportsInterface(interfaceId); // right-most Core
}

function getSupportedCallbackFunctions()
Expand Down
8 changes: 4 additions & 4 deletions src/core/token/ERC1155CoreInitializable.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import {ERC1155} from "@solady/tokens/ERC1155.sol";
import {Initializable} from "@solady/utils/Initializable.sol";
import {Multicallable} from "@solady/utils/Multicallable.sol";

import {ModularCore} from "../../ModularCore.sol";
import {Core} from "../../Core.sol";

import {BeforeApproveForAllCallback} from "../../callback/BeforeApproveForAllCallback.sol";
import {BeforeBatchMintCallbackERC1155} from "../../callback/BeforeBatchMintCallbackERC1155.sol";
Expand All @@ -16,7 +16,7 @@ import {BeforeTransferCallbackERC1155} from "../../callback/BeforeTransferCallba

import {OnTokenURICallback} from "../../callback/OnTokenURICallback.sol";

contract ERC1155CoreInitializable is ERC1155, ModularCore, Multicallable, Initializable {
contract ERC1155CoreInitializable is ERC1155, Core, Multicallable, Initializable {

/*//////////////////////////////////////////////////////////////
STORAGE
Expand Down Expand Up @@ -114,13 +114,13 @@ contract ERC1155CoreInitializable is ERC1155, ModularCore, Multicallable, Initia
* @notice Returns whether the contract implements an interface with the given interface ID.
* @param interfaceId The interface ID of the interface to check for
*/
function supportsInterface(bytes4 interfaceId) public view override(ERC1155, ModularCore) returns (bool) {
function supportsInterface(bytes4 interfaceId) public view override(ERC1155, Core) returns (bool) {
return interfaceId == 0x01ffc9a7 // ERC165 Interface ID for ERC165
|| interfaceId == 0xd9b67a26 // ERC165 Interface ID for ERC1155
|| interfaceId == 0x0e89341c // ERC165 Interface ID for ERC1155MetadataURI
|| interfaceId == 0xe8a3d485 // ERC-7572
|| interfaceId == 0x7f5828d0 // ERC-173
|| super.supportsInterface(interfaceId); // right-most ModularCore
|| super.supportsInterface(interfaceId); // right-most Core
}

function getSupportedCallbackFunctions()
Expand Down
4 changes: 2 additions & 2 deletions src/core/token/ERC20Core.sol
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.20;

import {ModularCore} from "../../ModularCore.sol";
import {Core} from "../../Core.sol";

import {ERC20} from "@solady/tokens/ERC20.sol";
import {Multicallable} from "@solady/utils/Multicallable.sol";
Expand All @@ -15,7 +15,7 @@ import {BeforeMintCallbackERC20} from "../../callback/BeforeMintCallbackERC20.so

import {BeforeTransferCallbackERC20} from "../../callback/BeforeTransferCallbackERC20.sol";

contract ERC20Core is ERC20, Multicallable, ModularCore {
contract ERC20Core is ERC20, Multicallable, Core {

/*//////////////////////////////////////////////////////////////
STORAGE
Expand Down
4 changes: 2 additions & 2 deletions src/core/token/ERC20CoreInitializable.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ pragma solidity ^0.8.20;

import {Initializable} from "@solady/utils/Initializable.sol";

import {ModularCore} from "../../ModularCore.sol";
import {Core} from "../../Core.sol";

import {ERC20} from "@solady/tokens/ERC20.sol";
import {Multicallable} from "@solady/utils/Multicallable.sol";
Expand All @@ -16,7 +16,7 @@ import {BeforeMintCallbackERC20} from "../../callback/BeforeMintCallbackERC20.so

import {BeforeTransferCallbackERC20} from "../../callback/BeforeTransferCallbackERC20.sol";

contract ERC20CoreInitializable is ERC20, ModularCore, Multicallable, Initializable {
contract ERC20CoreInitializable is ERC20, Core, Multicallable, Initializable {

/*//////////////////////////////////////////////////////////////
STORAGE
Expand Down
13 changes: 4 additions & 9 deletions src/core/token/ERC721Core.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ pragma solidity ^0.8.20;
import {ERC721A, ERC721AQueryable, IERC721A} from "@erc721a/extensions/ERC721AQueryable.sol";
import {Multicallable} from "@solady/utils/Multicallable.sol";

import {ModularCore} from "../../ModularCore.sol";
import {Core} from "../../Core.sol";

import {BeforeApproveCallbackERC721} from "../../callback/BeforeApproveCallbackERC721.sol";
import {BeforeApproveForAllCallback} from "../../callback/BeforeApproveForAllCallback.sol";
Expand All @@ -14,7 +14,7 @@ import {BeforeTransferCallbackERC721} from "../../callback/BeforeTransferCallbac

import {OnTokenURICallback} from "../../callback/OnTokenURICallback.sol";

contract ERC721Core is ERC721AQueryable, ModularCore, Multicallable {
contract ERC721Core is ERC721AQueryable, Core, Multicallable {

/*//////////////////////////////////////////////////////////////
STORAGE
Expand Down Expand Up @@ -89,18 +89,13 @@ contract ERC721Core is ERC721AQueryable, ModularCore, Multicallable {
* @notice Returns whether the contract implements an interface with the given interface ID.
* @param interfaceId The interface ID of the interface to check for
*/
function supportsInterface(bytes4 interfaceId)
public
view
override(ERC721A, IERC721A, ModularCore)
returns (bool)
{
function supportsInterface(bytes4 interfaceId) public view override(ERC721A, IERC721A, Core) returns (bool) {
return interfaceId == 0x01ffc9a7 // ERC165 Interface ID for ERC165
|| interfaceId == 0x80ac58cd // ERC165 Interface ID for ERC721
|| interfaceId == 0x5b5e139f // ERC165 Interface ID for ERC721Metadata
|| interfaceId == 0xe8a3d485 // ERC-7572
|| interfaceId == 0x7f5828d0 // ERC-173
|| super.supportsInterface(interfaceId); // right-most ModularCore
|| super.supportsInterface(interfaceId); // right-most Core
}

function getSupportedCallbackFunctions()
Expand Down
8 changes: 4 additions & 4 deletions src/core/token/ERC721CoreInitializable.sol
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import {
import {Initializable} from "@solady/utils/Initializable.sol";
import {Multicallable} from "@solady/utils/Multicallable.sol";

import {ModularCore} from "../../ModularCore.sol";
import {Core} from "../../Core.sol";

import {BeforeApproveCallbackERC721} from "../../callback/BeforeApproveCallbackERC721.sol";
import {BeforeApproveForAllCallback} from "../../callback/BeforeApproveForAllCallback.sol";
Expand All @@ -19,7 +19,7 @@ import {BeforeTransferCallbackERC721} from "../../callback/BeforeTransferCallbac

import {OnTokenURICallback} from "../../callback/OnTokenURICallback.sol";

contract ERC721CoreInitializable is ERC721AQueryableUpgradeable, ModularCore, Multicallable, Initializable {
contract ERC721CoreInitializable is ERC721AQueryableUpgradeable, Core, Multicallable, Initializable {

/*//////////////////////////////////////////////////////////////
STORAGE
Expand Down Expand Up @@ -107,15 +107,15 @@ contract ERC721CoreInitializable is ERC721AQueryableUpgradeable, ModularCore, Mu
function supportsInterface(bytes4 interfaceId)
public
view
override(ERC721AUpgradeable, IERC721AUpgradeable, ModularCore)
override(ERC721AUpgradeable, IERC721AUpgradeable, Core)
returns (bool)
{
return interfaceId == 0x01ffc9a7 // ERC165 Interface ID for ERC165
|| interfaceId == 0x80ac58cd // ERC165 Interface ID for ERC721
|| interfaceId == 0x5b5e139f // ERC165 Interface ID for ERC721Metadata
|| interfaceId == 0xe8a3d485 // ERC-7572
|| interfaceId == 0x7f5828d0 // ERC-173
|| super.supportsInterface(interfaceId); // right-most ModularCore
|| super.supportsInterface(interfaceId); // right-most Core
}

function getSupportedCallbackFunctions()
Expand Down
2 changes: 1 addition & 1 deletion src/interface/IModularCore.sol → src/interface/ICore.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ pragma solidity ^0.8.20;
import {IERC165} from "./IERC165.sol";
import {IModuleConfig} from "./IModuleConfig.sol";

interface IModularCore is IModuleConfig, IERC165 {
interface ICore is IModuleConfig, IERC165 {

/*//////////////////////////////////////////////////////////////
STRUCTS & ENUMS
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ pragma solidity ^0.8.20;

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

interface IModularModule is IModuleConfig {
interface IModule is IModuleConfig {

/*//////////////////////////////////////////////////////////////
VIEW FUNCTIONS
Expand Down
Loading