234 lines
10 KiB
Solidity
234 lines
10 KiB
Solidity
// SPDX-License-Identifier: UNLICENSED
|
|
|
|
pragma solidity ^0.8.17;
|
|
|
|
import { Ownable2Step } from "@openzeppelin/contracts/access/Ownable2Step.sol";
|
|
import { ECDSA } from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
|
|
import { EIP712 } from "@openzeppelin/contracts/utils/cryptography/EIP712.sol";
|
|
import { ITokenFactory } from "./interfaces/ITokenFactory.sol";
|
|
import { IAddressRegistry } from "./interfaces/IAddressRegistry.sol";
|
|
|
|
/**
|
|
* @title CommunityTokenDeployer contract
|
|
* @author 0x-r4bbit
|
|
*
|
|
* This contract serves as a deployment process for Status community owners
|
|
* to deploy access control token contracts on behalf of their Status community.
|
|
* The contract keep a reference to token factories that are used for deploying tokens.
|
|
* The contract deploys the two token contracts `OwnerToken` and `MasterToken` those factories.
|
|
* The contract maintains a registry which keeps track of `OwnerToken` contract
|
|
* addresses per community.
|
|
*
|
|
* Only one deployment per community can be done.
|
|
* Status community owners have to provide an EIP712 hash signature that was
|
|
* created using their community's private key to successfully execute a deployment.
|
|
*
|
|
* @notice This contract is used by Status community owners to deploy
|
|
* community access control token contracts.
|
|
* @notice This contract maintains a registry that tracks contract addresses
|
|
* and community addresses
|
|
* @dev This contract will be deployed by Status, making Status the owner
|
|
* of the contract.
|
|
* @dev A contract address for a `CommunityTokenRegistry` contract has to be provided
|
|
* to create this contract.
|
|
* @dev A contract address for a `CommunityOwnerTokenFactory` contract has to be provided
|
|
* to create this contract.
|
|
* @dev A contract address for a `CommunityMasterTokenFactory` contract has to be provided
|
|
* to create this contract.
|
|
* @dev The `CommunityTokenRegistry` address can be changed by the owner of this contract.
|
|
* @dev The `CommunityOwnerTokenFactory` address can be changed by the owner of this contract.
|
|
* @dev The `CommunityMasterTokenFactory` address can be changed by the owner of this contract.
|
|
*/
|
|
contract CommunityTokenDeployer is EIP712("CommunityTokenDeployer", "1"), Ownable2Step {
|
|
using ECDSA for bytes32;
|
|
|
|
error CommunityTokenDeployer_InvalidDeploymentRegistryAddress();
|
|
error CommunityTokenDeployer_InvalidTokenFactoryAddress();
|
|
error CommunityTokenDeployer_EqualFactoryAddresses();
|
|
error CommunityTokenDeployer_AlreadyDeployed();
|
|
error CommunityTokenDeployer_InvalidSignerKeyOrCommunityAddress();
|
|
error CommunityTokenDeployer_InvalidTokenMetadata();
|
|
error CommunityTokenDeployer_InvalidDeployerAddress();
|
|
error CommunityTokenDeployer_InvalidDeploymentSignature();
|
|
|
|
event OwnerTokenFactoryAddressChange(address indexed);
|
|
event MasterTokenFactoryAddressChange(address indexed);
|
|
event DeploymentRegistryAddressChange(address indexed);
|
|
event DeployOwnerToken(address indexed);
|
|
event DeployMasterToken(address indexed);
|
|
|
|
/// @dev Needed to avoid "Stack too deep" error.
|
|
struct TokenConfig {
|
|
string name;
|
|
string symbol;
|
|
string baseURI;
|
|
}
|
|
|
|
/// @dev Used to verify signatures.
|
|
struct DeploymentSignature {
|
|
address signer;
|
|
address deployer;
|
|
uint8 v;
|
|
bytes32 r;
|
|
bytes32 s;
|
|
}
|
|
|
|
bytes32 public constant DEPLOYMENT_SIGNATURE_TYPEHASH = keccak256("Deploy(address signer,address deployer)");
|
|
|
|
/// @dev Address of the `CommunityTokenRegistry` contract instance.
|
|
address public deploymentRegistry;
|
|
|
|
/// @dev Address of the `CommunityOwnerTokenFactory` contract instance.
|
|
address public ownerTokenFactory;
|
|
|
|
/// @dev Address of the `CommunityMasterTokenFactory` contract instance.
|
|
address public masterTokenFactory;
|
|
|
|
/// @param _registry The address of the `CommunityTokenRegistry` contract.
|
|
/// @param _ownerTokenFactory The address of the `CommunityOwnerTokenFactory` contract.
|
|
/// @param _masterTokenFactory The address of the `CommunityMasterTokenFactory` contract.
|
|
constructor(address _registry, address _ownerTokenFactory, address _masterTokenFactory) {
|
|
if (_registry == address(0)) {
|
|
revert CommunityTokenDeployer_InvalidDeploymentRegistryAddress();
|
|
}
|
|
if (_ownerTokenFactory == address(0) || _masterTokenFactory == address(0)) {
|
|
revert CommunityTokenDeployer_InvalidTokenFactoryAddress();
|
|
}
|
|
if (_ownerTokenFactory == _masterTokenFactory) {
|
|
revert CommunityTokenDeployer_EqualFactoryAddresses();
|
|
}
|
|
deploymentRegistry = _registry;
|
|
ownerTokenFactory = _ownerTokenFactory;
|
|
masterTokenFactory = _masterTokenFactory;
|
|
}
|
|
|
|
/**
|
|
* @notice Deploys an instance of `OwnerToken` and `MasterToken` on behalf
|
|
* of a Status community account, provided `_signature` is valid and was signed
|
|
* by that Status community account, using the configured factory contracts.
|
|
* @dev Anyone can call this function but a valid EIP712 hash signature has to be
|
|
* provided for a successful deployment.
|
|
* @dev Emits {CreateToken} events via underlying token factories.
|
|
* @dev Emits {DeployOwnerToken} event.
|
|
* @dev Emits {DeployMasterToken} event.
|
|
*
|
|
* @param _ownerToken A `TokenConfig` containing ERC721 metadata for `OwnerToken`
|
|
* @param _masterToken A `TokenConfig` containing ERC721 metadata for `MasterToken`
|
|
* @param _signature A `DeploymentSignature` containing a signer and deployer address,
|
|
* and a signature created by a Status community
|
|
* @return address The address of the deployed `OwnerToken` contract.
|
|
* @return address The address of the deployed `MasterToken` contract.
|
|
*/
|
|
function deploy(
|
|
TokenConfig calldata _ownerToken,
|
|
TokenConfig calldata _masterToken,
|
|
DeploymentSignature calldata _signature,
|
|
bytes memory _signerPublicKey
|
|
)
|
|
external
|
|
returns (address, address)
|
|
{
|
|
if (_signature.signer == address(0) || _signerPublicKey.length == 0) {
|
|
revert CommunityTokenDeployer_InvalidSignerKeyOrCommunityAddress();
|
|
}
|
|
|
|
if (_signature.deployer != msg.sender) {
|
|
revert CommunityTokenDeployer_InvalidDeployerAddress();
|
|
}
|
|
|
|
if (IAddressRegistry(deploymentRegistry).getEntry(_signature.signer) != address(0)) {
|
|
revert CommunityTokenDeployer_AlreadyDeployed();
|
|
}
|
|
|
|
if (!_verifySignature(_signature)) {
|
|
revert CommunityTokenDeployer_InvalidDeploymentSignature();
|
|
}
|
|
|
|
address ownerToken = ITokenFactory(ownerTokenFactory).create(
|
|
_ownerToken.name, _ownerToken.symbol, _ownerToken.baseURI, msg.sender, _signerPublicKey
|
|
);
|
|
|
|
emit DeployOwnerToken(ownerToken);
|
|
|
|
address masterToken = ITokenFactory(masterTokenFactory).create(
|
|
_masterToken.name, _masterToken.symbol, _masterToken.baseURI, ownerToken, bytes("")
|
|
);
|
|
|
|
emit DeployMasterToken(masterToken);
|
|
|
|
IAddressRegistry(deploymentRegistry).addEntry(_signature.signer, ownerToken);
|
|
return (ownerToken, masterToken);
|
|
}
|
|
|
|
/**
|
|
* @notice Sets a deployment registry address.
|
|
* @dev Only the owner can call this function.
|
|
* @dev Emits a {DeploymentRegistryAddressChange} event.
|
|
* @dev Reverts if the provided address is a zero address.
|
|
*
|
|
* @param _deploymentRegistry The address of the deployment registry contract.
|
|
*/
|
|
function setDeploymentRegistryAddress(address _deploymentRegistry) external onlyOwner {
|
|
if (_deploymentRegistry == address(0)) {
|
|
revert CommunityTokenDeployer_InvalidDeploymentRegistryAddress();
|
|
}
|
|
deploymentRegistry = _deploymentRegistry;
|
|
emit DeploymentRegistryAddressChange(deploymentRegistry);
|
|
}
|
|
|
|
/**
|
|
* @notice Sets the `OwnerToken` factory contract address.
|
|
* @dev Only the owner can call this function.
|
|
* @dev Emits a {OwnerTokenFactoryChange} event.
|
|
* @dev Reverts if the provided address is a zero address.
|
|
*
|
|
* @param _ownerTokenFactory The address of the `OwnerToken` factory contract.
|
|
*/
|
|
function setOwnerTokenFactoryAddress(address _ownerTokenFactory) external onlyOwner {
|
|
if (_ownerTokenFactory == address(0)) {
|
|
revert CommunityTokenDeployer_InvalidTokenFactoryAddress();
|
|
}
|
|
ownerTokenFactory = _ownerTokenFactory;
|
|
emit OwnerTokenFactoryAddressChange(ownerTokenFactory);
|
|
}
|
|
|
|
/**
|
|
* @notice Sets the `MasterToken` factory contract address.
|
|
* @dev Only the owner can call this function.
|
|
* @dev Emits a {MasterTokenFactoryChange} event.
|
|
* @dev Reverts if the provided address is a zero address.
|
|
*
|
|
* @param _masterTokenFactory The address of the `MasterToken` factory contract.
|
|
*/
|
|
function setMasterTokenFactoryAddress(address _masterTokenFactory) external onlyOwner {
|
|
if (_masterTokenFactory == address(0)) {
|
|
revert CommunityTokenDeployer_InvalidTokenFactoryAddress();
|
|
}
|
|
masterTokenFactory = _masterTokenFactory;
|
|
emit MasterTokenFactoryAddressChange(masterTokenFactory);
|
|
}
|
|
|
|
/**
|
|
* @notice Returns an EIP712 domain separator hash
|
|
* @return bytes32 An EIP712 domain separator hash
|
|
*/
|
|
function DOMAIN_SEPARATOR() external view returns (bytes32) {
|
|
return _domainSeparatorV4();
|
|
}
|
|
|
|
/**
|
|
* @notice Verifies provided `DeploymentSignature` which was created by
|
|
* the Status community account for which the access control token contracts
|
|
* will be deployed.
|
|
* @dev This contract does not maintain nonces for the typed data hash, which
|
|
* is typically done to prevent signature replay attacks. The `deploy()` function
|
|
* allows only one deployment per Status community, so replay attacks are not possible.
|
|
* @return bool Whether the provided signature could be recovered.
|
|
*/
|
|
function _verifySignature(DeploymentSignature calldata signature) internal view returns (bool) {
|
|
bytes32 digest =
|
|
_hashTypedDataV4(keccak256(abi.encode(DEPLOYMENT_SIGNATURE_TYPEHASH, signature.signer, signature.deployer)));
|
|
return signature.signer == digest.recover(signature.v, signature.r, signature.s);
|
|
}
|
|
}
|