Skip to content

Commit

Permalink
Merge pull request #122 from open-format/feature/ERC721-badge-contract
Browse files Browse the repository at this point in the history
Feature: ERC721 badge contract
  • Loading branch information
george-openformat authored May 14, 2024
2 parents 0804e2f + 0d28910 commit 85c245f
Show file tree
Hide file tree
Showing 13 changed files with 1,031 additions and 19 deletions.
5 changes: 5 additions & 0 deletions .changeset/modern-clocks-argue.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@openformat/contracts": minor
---

added ERC721Badge contract, updated ERC721FactoryFacet to add tokenURI on initialize
26 changes: 25 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ deploy:; make \
deploy-ERC20Base \
deploy-ERC721Base \
deploy-ERC721LazyMint \
deploy-ERC721Badge \
deploy-RewardsFacet \
deploy-SettingsFacet \
deploy-ERC721FactoryFacet \
Expand All @@ -43,7 +44,9 @@ deploy-AppFactory:; forge script scripts/core/AppFactory.s.sol:Deploy --rpc-url
# token implementations
deploy-ERC721Base:; forge script scripts/tokens/ERC721Base.s.sol:Deploy --rpc-url $(rpc) --broadcast $(verbose) $(gasPrice) $(legacy) $(slow)
deploy-ERC721LazyMint:; forge script scripts/tokens/ERC721LazyMint.s.sol:Deploy --rpc-url $(rpc) --broadcast $(verbose) $(gasPrice) $(legacy) $(slow)
deploy-ERC721Badge:; forge script scripts/tokens/ERC721Badge.s.sol:Deploy --rpc-url $(rpc) --broadcast $(verbose) $(gasPrice) $(legacy) $(slow)
deploy-ERC20Base:; forge script scripts/tokens/ERC20Base.s.sol:Deploy --rpc-url $(rpc) --broadcast $(verbose) $(gasPrice) $(legacy) $(slow)

# facets
deploy-RewardsFacet:; forge script scripts/facet/RewardsFacet.s.sol:Deploy --rpc-url $(rpc) --broadcast $(verbose) $(gasPrice) $(legacy) $(slow)
deploy-SettingsFacet:; forge script scripts/facet/SettingsFacet.s.sol:Deploy --rpc-url $(rpc) --broadcast $(verbose) $(gasPrice) $(legacy) $(slow)
Expand All @@ -66,7 +69,6 @@ CreateApp:; forge script \
$(legacy) \
$(slow) \
$(verbose) \
$(gasPrice) \
`cast --format-bytes32-string $(args)`

# example: `make SetPlatformFee args="0.01 0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266" rpc=anvil`
Expand All @@ -82,6 +84,16 @@ SetPlatformFee:; forge script \
`cast --to-wei $(word 1, $(args))` $(word 2, $(args))


hasCreatorAccess:; forge script scripts/facet/SettingsFacet.s.sol:hasCreatorAccess --rpc-url $(rpc) --broadcast $(verbose) $(legacy) $(slow)

# example `make createERC721Base`
# Note: make sure app is setup with correct permissions and APP_ID env is set.
createERC721Base:; forge script scripts/facet/ERC721FactoryFacet.s.sol:CreateBase --rpc-url $(rpc) --broadcast $(verbose) $(legacy) $(slow)

# example `make createERC721Badge`
# Note: make sure app is setup with correct permissions and APP_ID env is set.
createERC721Badge:; forge script scripts/facet/ERC721FactoryFacet.s.sol:CreateBadge --rpc-url $(rpc) --broadcast $(verbose) $(legacy) $(slow)

# Run all update scripts
update:; make \
update-ERC721FactoryFacet \
Expand All @@ -92,6 +104,18 @@ update:; make \
update-ERC721FactoryFacet:; forge script scripts/facet/ERC721FactoryFacet.s.sol:Update --rpc-url $(rpc) --broadcast $(verbose) $(legacy) $(slow)
update-ERC20FactoryFacet:; forge script scripts/facet/ERC20FactoryFacet.s.sol:Update --rpc-url $(rpc) --broadcast $(verbose) $(legacy) $(slow)

# Add ERC721Badge contract
# Date 14.05.24
# updates ERC721FactoryFacet to change the createERC721 function to include a baseTokenURI paramerter
# deploys and registers ERC721Badge contract
# PR #122 https://github.com/open-format/contracts/pull/122
update-ERC721Badge:; make \
update-ERC721FactoryFacet-add-createERC721WithTokenURI \
deploy-ERC721Badge

update-ERC721FactoryFacet-add-createERC721WithTokenURI:; forge script scripts/facet/ERC721FactoryFacet.s.sol:Update_Add_createERC721WithTokenURI --rpc-url $(rpc) --broadcast $(verbose) $(legacy) $(slow)


# Add platform fee to tokens
# Date: 29.03.23
# redeploys erc20FactoryFacet, erc721FactoyFacet and replaces all functions on registry
Expand Down
4 changes: 3 additions & 1 deletion scripts/core/AppFactory.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ contract Deploy is Script, Utils {
contract CreateApp is Script, Utils {
function run(string memory appName) external {
uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY");
address deployerAddress = vm.addr(deployerPrivateKey);
vm.startBroadcast(deployerPrivateKey);

bytes32 appNameBytes32 = vm.parseBytes32(appName);
Expand All @@ -40,7 +41,8 @@ contract CreateApp is Script, Utils {
revert("please provide an app name, make CreateApp args=appName");
}

address appAddress = AppFactory(getContractDeploymentAddress(CONTRACT_NAME)).create(appNameBytes32, address(0));
address appAddress =
AppFactory(getContractDeploymentAddress(CONTRACT_NAME)).create(appNameBytes32, deployerAddress);

console.log("App:", appName);
console.log("Deployed:", appAddress);
Expand Down
71 changes: 64 additions & 7 deletions scripts/facet/ERC721FactoryFacet.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
pragma solidity ^0.8.16;

import "forge-std/Script.sol";
import "forge-std/console.sol";
import {
IDiamondWritable,
IDiamondWritableInternal
Expand All @@ -21,10 +22,11 @@ contract Deploy is Script, Utils {
ERC721FactoryFacet erc721FactoryFacet = new ERC721FactoryFacet();

// construct array of function selectors
bytes4[] memory selectors = new bytes4[](3);
bytes4[] memory selectors = new bytes4[](4);
selectors[0] = erc721FactoryFacet.createERC721.selector;
selectors[1] = erc721FactoryFacet.getERC721FactoryImplementation.selector;
selectors[2] = erc721FactoryFacet.calculateERC721FactoryDeploymentAddress.selector;
selectors[1] = erc721FactoryFacet.createERC721WithTokenURI.selector;
selectors[2] = erc721FactoryFacet.getERC721FactoryImplementation.selector;
selectors[3] = erc721FactoryFacet.calculateERC721FactoryDeploymentAddress.selector;

// construct and ADD facet cut
IDiamondWritableInternal.FacetCut[] memory cuts = new IDiamondWritableInternal.FacetCut[](1);
Expand All @@ -41,7 +43,7 @@ contract Deploy is Script, Utils {
}
}

contract Create is Script, Utils {
contract CreateBase is Script, Utils {
function run() external {
uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY");
address appId = vm.envAddress("APP_ID");
Expand All @@ -55,6 +57,20 @@ contract Create is Script, Utils {
}
}

contract CreateBadge is Script, Utils {
function run() external {
uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY");
address appId = vm.envAddress("APP_ID");
vm.startBroadcast(deployerPrivateKey);

ERC721FactoryFacet erc721FactoryFacet = ERC721FactoryFacet(appId);

erc721FactoryFacet.createERC721WithTokenURI("TEST", "TEST", "TokenURI", address(0x1), 1000, "Badge");

vm.stopBroadcast();
}
}

contract Update is Script, Utils {
function run() external {
uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY");
Expand All @@ -64,10 +80,11 @@ contract Update is Script, Utils {
ERC721FactoryFacet erc721FactoryFacet = new ERC721FactoryFacet();

// construct array of function selectors
bytes4[] memory selectors = new bytes4[](3);
bytes4[] memory selectors = new bytes4[](4);
selectors[0] = erc721FactoryFacet.createERC721.selector;
selectors[1] = erc721FactoryFacet.getERC721FactoryImplementation.selector;
selectors[2] = erc721FactoryFacet.calculateERC721FactoryDeploymentAddress.selector;
selectors[1] = erc721FactoryFacet.createERC721WithTokenURI.selector;
selectors[2] = erc721FactoryFacet.getERC721FactoryImplementation.selector;
selectors[3] = erc721FactoryFacet.calculateERC721FactoryDeploymentAddress.selector;

// construct and REPLACE facet cut
IDiamondWritableInternal.FacetCut[] memory cuts = new IDiamondWritableInternal.FacetCut[](1);
Expand All @@ -84,3 +101,43 @@ contract Update is Script, Utils {
exportContractDeployment(CONTRACT_NAME, address(erc721FactoryFacet), block.number);
}
}

/**
* @dev use this script to update deployments previous to PR #122 https://github.com/open-format/contracts/pull/122
*/
contract Update_Add_createERC721WithTokenURI is Script, Utils {
function run() external {
uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY");
vm.startBroadcast(deployerPrivateKey);

// deploy new facet
ERC721FactoryFacet erc721FactoryFacet = new ERC721FactoryFacet();

// function selectors to add
bytes4[] memory addSelectors = new bytes4[](1);
addSelectors[0] = erc721FactoryFacet.createERC721WithTokenURI.selector;

// function selectors to replace
bytes4[] memory replaceSelectors = new bytes4[](3);
replaceSelectors[0] = erc721FactoryFacet.createERC721.selector;
replaceSelectors[1] = erc721FactoryFacet.getERC721FactoryImplementation.selector;
replaceSelectors[2] = erc721FactoryFacet.calculateERC721FactoryDeploymentAddress.selector;

// construct facet cuts
IDiamondWritableInternal.FacetCut[] memory cuts = new IDiamondWritableInternal.FacetCut[](2);
cuts[0] = IDiamondWritableInternal.FacetCut(
address(erc721FactoryFacet), IDiamondWritableInternal.FacetCutAction.ADD, addSelectors
);
cuts[1] = IDiamondWritableInternal.FacetCut(
address(erc721FactoryFacet), IDiamondWritableInternal.FacetCutAction.REPLACE, replaceSelectors
);

// replace on registry
RegistryMock(payable(getContractDeploymentAddress("Registry"))).diamondCut(cuts, address(0), "");

vm.stopBroadcast();

// update new address
exportContractDeployment(CONTRACT_NAME, address(erc721FactoryFacet), block.number);
}
}
27 changes: 27 additions & 0 deletions scripts/tokens/ERC721Badge.s.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.16;

import "forge-std/Script.sol";
import {Utils} from "scripts/utils/Utils.sol";
import {ERC721Badge} from "src/tokens/ERC721/ERC721Badge.sol";
import {Globals} from "src/globals/Globals.sol";

string constant CONTRACT_NAME = "ERC721Badge";
bytes32 constant implementationId = "Badge";

contract Deploy is Script, Utils {
function run() external {
uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY");
vm.startBroadcast(deployerPrivateKey);

// deploy
ERC721Badge erc721Badge = new ERC721Badge(false);

// add to globals
Globals(getContractDeploymentAddress("Globals")).setERC721Implementation(implementationId, address(erc721Badge));

vm.stopBroadcast();

exportContractDeployment(CONTRACT_NAME, address(erc721Badge), block.number);
}
}
53 changes: 51 additions & 2 deletions src/extensions/ERC721Factory/ERC721Factory.sol
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,59 @@ interface CompatibleERC721Implementation {
* compatible to be inherited by facet contract
* there is an internal dependency on the globals extension.
* @dev inheriting contracts must override the internal _canCreate function
* @dev _baseTokenURI should be set to an empty string "" if not compatible with ERC721 implementation
*/

abstract contract ERC721Factory is IERC721Factory, ERC721FactoryInternal, MinimalProxyFactory, ReentrancyGuard {
/**
* @notice creates an erc721 contract based on implementation, with option to add token URI
* @dev the deployed contract is a minimal proxy that points to the implementation chosen
* @param _name the name of the ERC721 contract
* @param _symbol the symbol of the ERC721 contract
* @param _tokenURI the token URI to initialize the erc721 contract with, use an empty string if not applicable
* @param _royaltyRecipient the default address that royalties are sent to
* @param _royaltyBps the default royalty percent in BPS
* @param _implementationId the chosen implementation of ERC721 contract
*/
function createERC721WithTokenURI(
string calldata _name,
string calldata _symbol,
string calldata _tokenURI,
address _royaltyRecipient,
uint16 _royaltyBps,
bytes32 _implementationId
) external payable virtual nonReentrant returns (address id) {
if (!_canCreate()) {
revert ERC721Factory_doNotHavePermission();
}

address implementation = _getImplementation(_implementationId);
if (implementation == address(0)) {
revert ERC721Factory_noImplementationFound();
}

// hook to add functionality before create
_beforeCreate();

// deploys new proxy using CREATE2
id = _deployMinimalProxy(implementation, _getSalt(msg.sender));
_increaseContractCount(msg.sender);

// add the app address and globals as encoded data
// this enables ERC721 contracts to grant minter role to the app and pay platform fee's
bytes memory data = bytes(_tokenURI).length > 0
? abi.encode(address(this), _getGlobalsAddress(), _tokenURI)
: abi.encode(address(this), _getGlobalsAddress());

// initialize ERC721 contract
try CompatibleERC721Implementation(payable(id)).initialize(
msg.sender, _name, _symbol, _royaltyRecipient, _royaltyBps, data
) {
emit Created(id, msg.sender, _name, _symbol, _royaltyRecipient, _royaltyBps, _implementationId);
} catch {
revert ERC721Factory_failedToInitialize();
}
}

/**
* @notice creates an erc721 contract based on implementation
* @dev the deployed contract is a minimal proxy that points to the implementation chosen
Expand Down Expand Up @@ -63,7 +113,6 @@ abstract contract ERC721Factory is IERC721Factory, ERC721FactoryInternal, Minima
id = _deployMinimalProxy(implementation, _getSalt(msg.sender));
_increaseContractCount(msg.sender);

// add the app address and globals as encoded data
// this enables ERC721 contracts to grant minter role to the app and pay platform fee's
bytes memory data = abi.encode(address(this), _getGlobalsAddress());

Expand Down
9 changes: 9 additions & 0 deletions src/extensions/ERC721Factory/IERC721Factory.sol
Original file line number Diff line number Diff line change
Expand Up @@ -24,5 +24,14 @@ interface IERC721Factory {
bytes32 _implementationId
) external payable returns (address id);

function createERC721WithTokenURI(
string memory _name,
string memory _symbol,
string memory _tokenURI,
address _royaltyRecipient,
uint16 _royaltyBps,
bytes32 _implementationId
) external payable returns (address id);

function getERC721FactoryImplementation(bytes32 _implementationId) external view returns (address);
}
Loading

0 comments on commit 85c245f

Please sign in to comment.