communities-contracts/test/CommunityTokenDeployer.t.sol

279 lines
12 KiB
Solidity
Raw Normal View History

feat: implement `CommunityTokenDeployer` contract (#2) This commit introduces the `CommunityTokenDeployer` contract discussed in https://github.com/status-im/status-desktop/issues/11954. The idea is that, instead of having accounts deploy `OwnerToken` and `MasterToken` directly, they'd use a deployer contract instead, which maintains a registry of known `OwnerToken` addresses, mapped to Status community addresses. The following changes have been made: It was, and still is, a requirement that both, `OwnerToken` and `MasterToken` are deployed within a single transaction, so that when something goes wrong, we don't end up in an inconsistent state. That's why `OwnerToken` used to instantiated `MasterToken` and required all of its constructor arguments as well. Unfortunately, this resulted in compilation issues in the context of the newly introduce deployer contract, where there are too many function arguments. Because we now delegate deployment to a dedicated contract, we can instantiate both `OwnerToken` and `MasterToken` in a single transaction, without having `OwnerToken` being responsible to instantiate `MasterToken`. This fixes the compilation issues and simplifies the constructor of `OwnerToken`. The new `CommunityTokenDeployer` contract is now responsble for deploying the aforementioned tokens and ensures that they are deployed within a single transaction. To deploy an `OwnerToken` and `MasterToken` accounts can now call `CommunityDeloyerToken.deploy(TokenConfig, TokenConfig, DeploymentSignature)`. The `DeploymentSignature` uses `EIP712` structured type hash data to let the contract verify that the deployer is allowed to deploy the contracts on behalf of a community account.
2023-09-19 09:39:55 +00:00
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.17;
import { Test } from "forge-std/Test.sol";
import { ECDSA } from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
import { DeployContracts } from "../script/DeployContracts.s.sol";
import { DeploymentConfig } from "../script/DeploymentConfig.s.sol";
import { BaseTokenFactory } from "../contracts/factories/BaseTokenFactory.sol";
import { OwnerToken } from "../contracts/tokens/OwnerToken.sol";
import { MasterToken } from "../contracts/tokens/MasterToken.sol";
import { CommunityOwnerTokenRegistry } from "../contracts/CommunityOwnerTokenRegistry.sol";
import { CommunityTokenDeployer } from "../contracts/CommunityTokenDeployer.sol";
contract CommunityTokenDeployerTest is Test {
DeploymentConfig internal deploymentConfig;
CommunityTokenDeployer internal tokenDeployer;
CommunityOwnerTokenRegistry internal tokenRegistry;
address internal deployer;
address internal immutable owner = makeAddr("owner");
address internal communityAddress;
uint256 internal communityKey;
function setUp() public virtual {
DeployContracts deployment = new DeployContracts();
(tokenDeployer, tokenRegistry,,, deploymentConfig) = deployment.run();
deployer = deploymentConfig.deployer();
(communityAddress, communityKey) = makeAddrAndKey("community");
}
function test_Deployment() public {
assertEq(tokenDeployer.deploymentRegistry(), address(tokenRegistry));
assertEq(tokenDeployer.owner(), deployer);
}
function _getOwnerTokenConfig() internal view returns (CommunityTokenDeployer.TokenConfig memory, bytes memory) {
(
string memory ownerTokenName,
string memory ownerTokenSymbol,
string memory ownerTokenBaseURI,
bytes memory signerPublicKey
) = deploymentConfig.ownerTokenConfig();
CommunityTokenDeployer.TokenConfig memory ownerTokenConfig =
CommunityTokenDeployer.TokenConfig(ownerTokenName, ownerTokenSymbol, ownerTokenBaseURI);
return (ownerTokenConfig, signerPublicKey);
}
function _getMasterTokenConfig() internal view returns (CommunityTokenDeployer.TokenConfig memory) {
(string memory masterTokenName, string memory masterTokenSymbol, string memory masterTokenBaseURI,) =
deploymentConfig.masterTokenConfig();
CommunityTokenDeployer.TokenConfig memory masterTokenConfig =
CommunityTokenDeployer.TokenConfig(masterTokenName, masterTokenSymbol, masterTokenBaseURI);
return masterTokenConfig;
}
function _createDeploymentSignature(
uint256 _signerKey,
address _signer,
address _deployer
)
internal
view
returns (CommunityTokenDeployer.DeploymentSignature memory)
{
bytes32 digest = ECDSA.toTypedDataHash(
tokenDeployer.DOMAIN_SEPARATOR(),
keccak256(abi.encode(tokenDeployer.DEPLOYMENT_SIGNATURE_TYPEHASH(), _signer, _deployer))
);
(uint8 v, bytes32 r, bytes32 s) = vm.sign(_signerKey, digest);
return CommunityTokenDeployer.DeploymentSignature(_signer, _deployer, v, r, s);
}
}
contract SetDeploymentRegistryAddressTest is CommunityTokenDeployerTest {
event DeploymentRegistryAddressChange(address indexed);
function setUp() public virtual override {
CommunityTokenDeployerTest.setUp();
}
function test_RevertWhen_SenderIsNotOwner() public {
vm.expectRevert(bytes("Ownable: caller is not the owner"));
tokenDeployer.setDeploymentRegistryAddress(makeAddr("someAddress"));
}
function test_RevertWhen_InvalidDeploymentRegistryAddress() public {
vm.prank(deployer);
vm.expectRevert(CommunityTokenDeployer.CommunityTokenDeployer_InvalidDeploymentRegistryAddress.selector);
tokenDeployer.setDeploymentRegistryAddress(address(0));
}
function test_SetDeploymentRegistryAddress() public {
address newAddress = makeAddr("newAddress");
vm.prank(deployer);
vm.expectEmit(true, true, true, true);
emit DeploymentRegistryAddressChange(newAddress);
tokenDeployer.setDeploymentRegistryAddress(newAddress);
assertEq(tokenDeployer.deploymentRegistry(), newAddress);
}
}
contract SetOwnerTokenFactoryAddressTest is CommunityTokenDeployerTest {
event OwnerTokenFactoryAddressChange(address indexed);
function setUp() public virtual override {
CommunityTokenDeployerTest.setUp();
}
function test_RevertWhen_SenderIsNotOwner() public {
vm.expectRevert(bytes("Ownable: caller is not the owner"));
tokenDeployer.setOwnerTokenFactoryAddress(makeAddr("someAddress"));
}
function test_RevertWhen_InvalidTokenFactoryAddress() public {
vm.prank(deployer);
vm.expectRevert(CommunityTokenDeployer.CommunityTokenDeployer_InvalidTokenFactoryAddress.selector);
tokenDeployer.setOwnerTokenFactoryAddress(address(0));
}
function test_SetOwnerTokenFactoryAddress() public {
address newAddress = makeAddr("newAddress");
vm.prank(deployer);
vm.expectEmit(true, true, true, true);
emit OwnerTokenFactoryAddressChange(newAddress);
tokenDeployer.setOwnerTokenFactoryAddress(newAddress);
assertEq(tokenDeployer.ownerTokenFactory(), newAddress);
}
}
contract SetMasterTokenFactoryAddressTest is CommunityTokenDeployerTest {
event MasterTokenFactoryAddressChange(address indexed);
function setUp() public virtual override {
CommunityTokenDeployerTest.setUp();
}
function test_RevertWhen_SenderIsNotOwner() public {
vm.expectRevert(bytes("Ownable: caller is not the owner"));
tokenDeployer.setMasterTokenFactoryAddress(makeAddr("someAddress"));
}
function test_RevertWhen_InvalidTokenFactoryAddress() public {
vm.prank(deployer);
vm.expectRevert(CommunityTokenDeployer.CommunityTokenDeployer_InvalidTokenFactoryAddress.selector);
tokenDeployer.setMasterTokenFactoryAddress(address(0));
}
function test_SetOwnerTokenFactoryAddress() public {
address newAddress = makeAddr("newAddress");
vm.prank(deployer);
vm.expectEmit(true, true, true, true);
emit MasterTokenFactoryAddressChange(newAddress);
tokenDeployer.setMasterTokenFactoryAddress(newAddress);
assertEq(tokenDeployer.masterTokenFactory(), newAddress);
}
}
contract DeployTest is CommunityTokenDeployerTest {
function setUp() public virtual override {
CommunityTokenDeployerTest.setUp();
}
function test_RevertWhen_InvalidDeployerAddress() public {
(CommunityTokenDeployer.TokenConfig memory ownerTokenConfig, bytes memory signerPublicKey) =
_getOwnerTokenConfig();
CommunityTokenDeployer.TokenConfig memory masterTokenConfig = _getMasterTokenConfig();
CommunityTokenDeployer.DeploymentSignature memory signature =
_createDeploymentSignature(communityKey, communityAddress, makeAddr("someone else"));
vm.prank(owner);
vm.expectRevert(CommunityTokenDeployer.CommunityTokenDeployer_InvalidDeployerAddress.selector);
tokenDeployer.deploy(ownerTokenConfig, masterTokenConfig, signature, signerPublicKey);
}
function test_RevertWhen_InvalidDeploymentSignature() public {
(CommunityTokenDeployer.TokenConfig memory ownerTokenConfig, bytes memory signerPublicKey) =
_getOwnerTokenConfig();
CommunityTokenDeployer.TokenConfig memory masterTokenConfig = _getMasterTokenConfig();
CommunityTokenDeployer.DeploymentSignature memory signature =
_createDeploymentSignature(communityKey, makeAddr("invalid address"), owner);
vm.prank(owner);
vm.expectRevert(CommunityTokenDeployer.CommunityTokenDeployer_InvalidDeploymentSignature.selector);
tokenDeployer.deploy(ownerTokenConfig, masterTokenConfig, signature, signerPublicKey);
}
function test_RevertWhen_InvalidTokenMetadata() public {
(, bytes memory signerPublicKey) = _getOwnerTokenConfig();
CommunityTokenDeployer.TokenConfig memory ownerTokenConfig = CommunityTokenDeployer.TokenConfig("", "", "");
CommunityTokenDeployer.TokenConfig memory masterTokenConfig = CommunityTokenDeployer.TokenConfig("", "", "");
CommunityTokenDeployer.DeploymentSignature memory signature =
_createDeploymentSignature(communityKey, communityAddress, owner);
vm.prank(owner);
vm.expectRevert(BaseTokenFactory.BaseTokenFactory_InvalidTokenMetadata.selector);
tokenDeployer.deploy(ownerTokenConfig, masterTokenConfig, signature, signerPublicKey);
// fill `masterTokenConfig` with data
masterTokenConfig = _getMasterTokenConfig();
vm.prank(owner);
vm.expectRevert(BaseTokenFactory.BaseTokenFactory_InvalidTokenMetadata.selector);
tokenDeployer.deploy(ownerTokenConfig, masterTokenConfig, signature, signerPublicKey);
// fill `ownerTokenConfig` with data and reset `masterTokenConfig`
(ownerTokenConfig,) = _getOwnerTokenConfig();
masterTokenConfig = CommunityTokenDeployer.TokenConfig("", "", "");
vm.prank(owner);
vm.expectRevert(BaseTokenFactory.BaseTokenFactory_InvalidTokenMetadata.selector);
tokenDeployer.deploy(ownerTokenConfig, masterTokenConfig, signature, signerPublicKey);
}
function test_RevertWhen_InvalidSignerPublicKey() public {
(CommunityTokenDeployer.TokenConfig memory ownerTokenConfig,) = _getOwnerTokenConfig();
CommunityTokenDeployer.TokenConfig memory masterTokenConfig = _getMasterTokenConfig();
CommunityTokenDeployer.DeploymentSignature memory signature =
_createDeploymentSignature(communityKey, communityAddress, owner);
vm.prank(owner);
vm.expectRevert(CommunityTokenDeployer.CommunityTokenDeployer_InvalidSignerKeyOrCommunityAddress.selector);
tokenDeployer.deploy(ownerTokenConfig, masterTokenConfig, signature, bytes(""));
}
function test_RevertWhen_InvalidCommunityAddress() public {
(CommunityTokenDeployer.TokenConfig memory ownerTokenConfig, bytes memory signerPublicKey) =
_getOwnerTokenConfig();
CommunityTokenDeployer.TokenConfig memory masterTokenConfig = _getMasterTokenConfig();
CommunityTokenDeployer.DeploymentSignature memory signature =
_createDeploymentSignature(communityKey, address(0), owner);
vm.prank(owner);
vm.expectRevert(CommunityTokenDeployer.CommunityTokenDeployer_InvalidSignerKeyOrCommunityAddress.selector);
tokenDeployer.deploy(ownerTokenConfig, masterTokenConfig, signature, signerPublicKey);
}
function test_RevertWhen_AlreadyDeployed() public {
(CommunityTokenDeployer.TokenConfig memory ownerTokenConfig, bytes memory signerPublicKey) =
_getOwnerTokenConfig();
CommunityTokenDeployer.TokenConfig memory masterTokenConfig = _getMasterTokenConfig();
CommunityTokenDeployer.DeploymentSignature memory signature =
_createDeploymentSignature(communityKey, communityAddress, owner);
vm.startPrank(owner);
tokenDeployer.deploy(ownerTokenConfig, masterTokenConfig, signature, signerPublicKey);
vm.expectRevert(CommunityTokenDeployer.CommunityTokenDeployer_AlreadyDeployed.selector);
tokenDeployer.deploy(ownerTokenConfig, masterTokenConfig, signature, signerPublicKey);
}
function test_Deploy() public {
(CommunityTokenDeployer.TokenConfig memory ownerTokenConfig, bytes memory signerPublicKey) =
_getOwnerTokenConfig();
CommunityTokenDeployer.TokenConfig memory masterTokenConfig = _getMasterTokenConfig();
CommunityTokenDeployer.DeploymentSignature memory signature =
_createDeploymentSignature(communityKey, communityAddress, owner);
vm.prank(owner);
(address ownerTokenAddress, address masterTokenAddress) =
tokenDeployer.deploy(ownerTokenConfig, masterTokenConfig, signature, signerPublicKey);
assertEq(ownerTokenAddress, tokenRegistry.getEntry(communityAddress));
assertEq(OwnerToken(ownerTokenAddress).balanceOf(owner), 1);
MasterToken masterToken = MasterToken(masterTokenAddress);
assertEq(masterToken.ownerToken(), ownerTokenAddress);
assertEq(masterToken.balanceOf(owner), 0);
assertEq(masterToken.remoteBurnable(), true);
assertEq(masterToken.transferable(), false);
}
}